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

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

项目地址

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

selenium介绍

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

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.git

简单翻译下github

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

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

环境准备

  • python 2.7浏览器

  • seleniumapp

  • phantomjs / Chromium函数

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

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


流程

  1. 初始化selenium

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

  3. 登陆qq音乐

  4. 搜索音乐

  5. 添加到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, 在须要使用的时候再转换对应编码比较合适。

添加到歌单

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

  1. 鼠标移动到歌曲上

  2. 点击 + 号

  3. 点击对应的歌单

观察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

总结

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

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

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

相关文章
相关标签/搜索