上周做了个同步歌单的脚本(/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, 有什么问题大家多多指教, 乐意接受批评.
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.