上周做了个同步歌单的脚本(/t/369133), 在这里分享下写脚本的一些经验以及遇到的一些坑。 项目地址: https://github.com/Denon/syncPlaylist
整个爬虫的流程大致如下
这个步骤比较简单, 正常访问该歌单的手机端页面爬取就好, 观察页面可以发现所有歌曲都在<span class="detail">...</span>
里面. 那么直接用 requests + beautifulsoup 获取下就好.
其实一开始的思路是想通过发送模拟请求来登录的. 但看来看去没有找到怎么做(有人知道的话谢谢分享下). 考虑到后面比较复杂的操作, 最后就直接用 selenium 来做了. 用 selenium 来做就比较简单了. 唯一要注意的是, 登录 qq 的那个弹出框是在一个 iframe 里面.
def login_qq(): # 切换 iframe browser.switch_to.frame("frame_tips") wait.until(lambda browse: browser.find_element_by_id("switcher_plogin")) sleep(0.5) browser.find_element_by_id("switcher_plogin").click() user_input = browser.find_element_by_id("u") user_input.send_keys("account") pwd_input = browser.find_element_by_id("p") pwd_input.send_keys("password") submit = browser.find_element_by_id("login_button") submit.click() # 登录成功以后要切换回来 browser.switch_to.default_content()
这里找到 qq 音乐的搜索 url 然后把关键字填入就好. 搜索到歌曲以后, 我这里比较偷懒, 只选择把第一个搜索到的结果添加进去. 添加的操作实际上分为三步:
def add_song(): # 点击出歌单 browser.execute_script("document.getElementsByClassName('songlist__list')[0].firstElementChild.getElementsByClassName('list_menu__add')[0].click()") sleep(0.5) # 通过 data-dirid 来选择歌单 browser.find_element_by_css_selector("a[data-dirid='{}']".format(playlist_id)).click() return
选择使用 py2exe 来打包. 这里有个坑就是由于我们用到了 selenium, selenium 里面的某些函数依赖了两个 js 文件, 需要把这两个 js 文件添加到打包的脚本里面
from distutils.core import setup import py2exe from glob import glob setup( cOnsole=["run.py"], data_files=[ (r'.', glob(r'D:\myproject\syncPlaylist\config.json')), (r'.', glob(r'D:\ProgramData\Anaconda3\envs\python27\Lib\site-packages\selenium\webdriver\remote\getAttribute.js')), (r'.', glob(r'D:\ProgramData\Anaconda3\envs\python27\Lib\site-packages\selenium\webdriver\remote\isDisplayed.js')) ] )
在执行脚本过程中发现, 偶尔会出现点击登录以后 qq 登录还是没成功的情况, 以及添加歌曲时, 脚本偶尔会出错. 这里为了不中断整个脚本执行, 有必要加上重试这个操作, 因此写了一个重试的装饰器
def retry(retry_times=0, exc_class=Exception, notice_message=None): """retry_times: 重试次数 exc_class: 捕捉的异常 class notice_message: 发生异常时候输出的错误信息, 为 None 时则不输出 """ def wrapper(f): @functools.wraps(f) def inner_wrapper(*args, **kwargs): current = 0 while True: try: return f(*args, **kwargs) except exc_class as e: if current >= retry_times: raise RetryException() if notice_message: print notice_message current += 1 return inner_wrapper return wrapper
说实话, 本来以为写这个脚本难度不是很大. 但前前后后差不多花了两三天的时间 T_T. 问题在于之前爬虫这方面不是很熟悉以及项目结构在一开始比较混乱(其实就是懒= =). 平时也比较少写这种技术分享的 blog, 有什么问题大家多多指教, 乐意接受批评.
1 bearqq 2017-06-19 14:22:59 +08:00 for _ in range(0,retry_times): try: dosomething() break#执行成功,跳出 for exception: continue else:#for 执行完毕未跳出,即错误次数超出 raise RetryException() return return "success" 我一般这么 retry -。- |
![]() | 4 wq2016 2017-06-19 16:21:48 +08:00 有趣~ |
5 crashX 2017-06-19 17:33:50 +08:00 感觉功能做反了。 |
![]() | 6 natforum 2017-06-19 17:35:09 +08:00 很强势 |
![]() | 7 HypoChen 2017-06-19 17:40:07 +08:00 感觉功能做反了+1 |
9 newbie269 2017-06-19 17:44:00 +08:00 via iPhone 然而 QQ 音乐 web 端有添加网易云和虾米音乐的歌单的功能 |
![]() | 10 GlobalNPC 2017-06-19 17:46:15 +08:00 QQ 音乐 那么难用。。。 |
![]() | 11 AsherG 2017-06-19 17:49:36 +08:00 via iPhone 感觉挺不错,关注一个 |
![]() | 12 denonw OP @willhunger 是的。。但是那个歌单我怎么都复制不出来= =. 所以只能自己搞一个了 |
13 mingyun 2017-06-19 23:52:35 +08:00 感觉功能做反了 +1 |
![]() | 14 weaming 2017-06-20 00:12:53 +08:00 感觉功能做反了 +1 |
15 furch 2017-06-20 10:59:33 +08:00 感觉功能做反了 +1 |
![]() | 16 Antidictator 2017-06-20 15:24:06 +08:00 网易云音乐的还没有 QQ 音乐的那么全。 |