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音樂歌單的互相同步。

相關文章
相關標籤/搜索