Selenium实战-同步网易云音乐歌单到qq音乐

来源:http://www.51testing.com

  本文主要介绍selenium在爬虫脚本的实际应用。适合刚接触python,没使用过selenium的童鞋。(如果你是老司机路过的话,帮忙点个star吧)

项目地址

  https://github.com/Denon/sync...

selenium介绍

  selenium官网. 直接引用官网的话

  Selenium automates browsers. That's it! What you do with that power is entirely up to you. Primarily, it is for automating web applications for testing purposes, but is certainly not limited to just that. Boring web-based administration tasks can (and should!) also be automated as well.

 简单翻译下

  selenium是一个自动化的浏览器, 主要使用来做web应用的自动化测试

  个人认为用selenium主要的好处是: 可以解析js渲染的页面。对于这次的爬虫来说, 由于网易云音乐以及qq音乐网页中大部分元素都是使用js渲染生成的, 因此选择使用selenium来完成这次的脚本。

环境准备

  python 2.7

  selenium

  phantomjs / Chromium

  selenium 运行需要额外的浏览器支持. 其中phantomjs可以在这里下载, Chromium可以在这里下载。 前期debug阶段建议使用 Chromium 。

  详细的包依赖请查看github项目

流程

  初始化selenium

  从网易云音乐歌单网页中获取歌曲列表

  登录qq音乐

  搜索音乐

  添加到qq音乐的歌单中

初始化selenium

from selenium import webdriver  # 这里是使用PhantomJs, 如果使用chromium则使用webdriver.Chrome(),  # 并替换对应的驱动路径即可  phantomjs_driver = phantomjs_driver_path  opts = Options()  opts.add_argument("user-agent={}".format(headers["User-Agent"]))  browser = webdriver.PhantomJS(phantomjs_driver)

从网易云音乐中获取音乐

  对于一般爬虫来说, 如果能用手机端网页爬取那就无脑选网页端爬取。可以发现网易云音乐的手机版歌单地址是: http://music.163.com/m/playli... 。 这个地址么一看就知道, 后面那串id就是歌单id。chrome浏览器打开调试工具, 可以看到所有的歌曲都在<span class="detail">...</span>里面。 那么直接用requests + beautifulsoup 爬取元素就好。 这里就不深入讨论了。 具体的代码请参考项目

 登录qq音乐

  一般来说,爬虫做登录有两种选择。一种是抓包,分析登录请求体,直接模拟登录,这种稳定性较好,只要解析出请求体后,登录一般都能成功。一种是模拟正常登录操作,在输入框中输入账号密码,然后点击登录按钮来登录,这种稳定性较差,有可能会有各种意外的情况,比如验证码之类的。这里当然要使用第二种来做(不然就跑题了)。

  首先打开qq音乐网站, 发现qq登录的按钮在

  这里介绍selenium第一个函数find_element_by_xpath,这个函数就是根据element的xpath来获取元素的。

browser.find_element_by_xpath("/html/body/div[1]/div/div[2]/span/a[2]").click()

  点击完后, 页面应该会弹出一个登录框, 不过默认应该是扫码登录, 这个时候就要点击下“帐号密码登录”来切换。可以发现, 这个切换按钮的id是switcher_plogin. 那么使用selenium的 find_element_by_id 函数:

browser.find_element_by_id("switcher_plogin").click()

  按理来说这段代码应该能运行成功,但是如无意外的话,我们只能获得一个报错

selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"id","selector":"switcher_plogin"}

  这是什么情况?

  细心点观察可以发现,这个弹出来的登录框是在一个iframe里面。这个时候需要使用到另外一个函数switch_to.frame,

# 切换iframe  browser.switch_to.frame("frame_tips")  browser.find_element_by_id("switcher_plogin").click()  # 输入账号密码, 用到send_keys函数  user_input = browser.find_element_by_id("u")  user_input.send_keys("qq_account")  pwd_input = self.browser.find_element_by_id("p")  pwd_input.send_keys("qq_password")  # 最后要切换回来  browser.switch_to.default_content()  可以发现ok了,然后账号密码等输入框直接用上面介绍过的函数直接获取就行。

搜索歌曲

  在浏览器中打开qq音乐实际搜索一下,发现搜索的url是https://y.qq.com/portal/search.html#page=1&searchid=1&remoteplace=txt.yqq.top&t=song&w=%E6%B5%AE%E5%A4%B8,可以看到搜索的关键词在 w 这个参数里面,并且中文字是被url encode过的。那么这里使用python内置的urllib2包即可

from urllib2 import quote  url_sw = quote(search_word.encode('utf8'))

  由于python2坑爹的编码问题, 一般把字符存储成unicode, 在需要使用的时候再转换对应编码比较合适。

添加到歌单

  人工添加歌单的操作实际分为三步:

  鼠标移动到歌曲上

  点击 + 号

  点击对应的歌单

  观察html元素可以发现,搜索出来的歌曲都在<div class="songlist__songname">...</div>里面。这里使用find_elements_by_class_name这个函数

all_song = browser.find_elements_by_class_name("songlist__list")

  点击完以后,可以看到歌单的html元素都在<li class="operate_menu__item">里面。

all_playlist = browser.find_elements_by_class_name("operate_menu__item")

  而其中每个歌单是以data-dirid这个属性来区分的,这里介绍另外一个元素选择函数find_element_by_css_selector

browser.find_element_by_css_selector("a[data-dirid='{}']").click()

  那么就这样结束了么?

  当然不是! 实际运行中发现,这里面大部分元素都是js渲染生成的,直接使用selenium函数去获取这些元素,很大可能会报错

selenium.common.exceptions.ElementNotVisibleException: Message: element not visible

  碰到这种情况,最好的解决办法是,用selenium直接执行js脚本来调用元素,selenium执行js脚本的函数为execute_script

browser.execute_script("document.getElementsByClassName('songlist__list')[0].firstElementChild.getElementsByClassName('list_menu__add')[0].click()"

  而js代码是可以直接在浏览器上debug的,一般现在浏览器上执行成功在复制回来。

 其他一些辅助方法

  在实际操作中,虽然使用的方法是正确的,但会出现很多意外的情况导致本次操作是失败的,这时候就需要来一次重试来解决问题(如果一次重试解决不了问题,那就来两次)。这里使用一个装饰器来写

def retry(retry_times=0, exc_class=Exception, notice_message=None, print_exc=False):  '''retry_times: 重试次数  exc_class: 捕捉的异常  notice_message: 提示信息  print_exc: 是否打印错误信息  '''  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 print_exc:  traceback.print_exc()  if current >= retry_times:  raise RetryException()  if notice_message:  print notice_message  current += 1  return inner_wrapper  return wrapper

总结

  介绍了selenium获取元素的各种用法,更多的请参考文档

  解决使用selenium可能会碰到的一些坑。

  最后在安利一次github项目, https://github.com/Denon/sync...。欢迎点赞以及提issue。现在已经支持网易云音乐与qq音乐歌单的互相同步。

星云测试

http://www.teststars.cc

奇林软件

http://www.kylinpet.com

联合通测

http://www.quicktesting.net