Python爬蟲實踐-網易雲音樂

ihtcboy發表於2018-09-09

1、前言

最近,網易的音樂很多聽不到了,剛好也看到很多教程,跟進學習了一下,也集大全了吧,本來想優化一下的,但是發現問題還是有點複雜,最後另闢捷徑,提供了簡單的方法啊!

本文主要參考 python編寫GUI版網易雲音樂爬蟲 後改寫,有興趣的可以看看文章的GUI,瞭解更多知識~

2、Python + 爬蟲

首先,說一下準備工作:

  • Python:需要基本的python語法基礎
  • requests:專業用於請求處理,requests庫學習文件中文版
  • lxml:其實可以用pythonth自帶的正規表示式庫re,但是為了更加簡單入門,用 lxml 中的 etree 進行網頁資料定位爬取。
  • re:python正規表示式處理
  • json:python的json處理庫

如果大家對上面的庫還比不懂,可以看看我的之前文章 《Python爬蟲實踐入門篇》

然後,說一下我們現在已經知道下載連結是這樣的:

http://music.163.com/song/media/outer/url?id=`

id 就是歌曲的id!

所以,現在我們爬蟲主要的工作就是找到這個id,當然為了更好的儲存,也要找到這個歌名啦!

那現在就是要找到我們需要爬蟲的網站連結啦!我分析了一下,大概是下面三種:

#歌曲清單
music_list = `https://music.163.com/#/playlist?id=2412826586` 
#歌手排行榜
artist_list = `https://music.163.com/#/artist?id=8325`
#搜尋列表 
search_list = `https://music.163.com/#/search/m/?order=hot&cat=全部&limit=435&offset=435&s=梁靜茹` 

如果你已經只是想下載一首歌,比如靜茹-勇氣:https://music.163.com/#/song?id=254485,那你直接就用瀏覽器開啟 http://music.163.com/song/media/outer/url?id=254485 就可以了,沒必要爬蟲啊!

好啦!感覺重點都說完了,提取和解析就是用 lxml,不懂的就看我之前的文章啊 《Python爬蟲實踐入門篇》

3、下載歌詞

如果還要下載歌詞,那也很簡單,通過介面,有歌曲的id就可以:

url = `http://music.163.com/api/song/lyric?id={}&lv=-1&kv=-1&tv=-1`.format(song_id)

返回的json資料大概長這樣:

{
    sgc: true,
    sfy: false,
    qfy: false,
    lrc:
    {
        version: 7,
        lyric: "[00:39.070]開了窗 等待天亮
[00:46.160]看這城市 悄悄的 熄了光
[00:51.850]聽風的方向
[00:55.090]這一刻 是否和我一樣
[00:58.730]孤單的飛翔
[01:02.300]模糊了眼眶
[01:07.760]廣播裡 那首歌曲
[01:14.830]重複當時 那條街那個你
[01:20.410]相同的桌椅
[01:23.740]不用言語 就會有默契
[01:27.470]這份親密
[01:30.560]那麼熟悉
[01:33.850]在愛里 等著你
[01:37.480]被你疼惜 有種暖意
[01:41.090]在夢裡 全是你
[01:43.920]不要再遲疑 把我抱緊"
    },
    klyric:
    {
        version: 0,
        lyric: null
    },
    tlyric:
    {
        version: 0,
        lyric: null
    },
    code: 200
}

剩下的也沒有什麼好說的啦!

4、坑點與進階

表面上很簡單,但是需要注意的是,網易返回的連結,資料是js動態載入,也就是爬蟲得到的網頁資料和瀏覽器得到的dom內容和結構不一樣!


  • 其中,搜尋列表爬蟲回來的內容,完全得不到歌曲id!!!

  • 解決
    解決方法也是有的!

    • python模擬瀏覽器
      使用selenium+phantomjs無介面瀏覽器,這兩者的結合其實就是直接操作瀏覽器,可以獲取JavaScript渲染後的頁面資料。

    缺點:

    由於是無介面瀏覽器,採用此方案效率極低,如果大批量抓取不推薦。
    對於非同步請求並且資料在原始碼中並不存在的,同時也就無法抓取到的資料。

    • 搜尋的歌曲變成歌單
      比如想下載全部的某一歌手的全部音樂,用手機雲音樂搜尋,然後全部儲存到新建一個歌單,這樣就可以啦!
  • 進階
    如果想使用瞭解更多網易雲音樂js的加密解密過程,可以看看這個 Python 爬蟲如何獲取 JS 生成的 URL 和網頁內容? – 路人甲的回答 – 知乎

總結

用python,就一定要簡單,我認為複雜的東西,還是儘量少做,能取巧就取巧,所以本文沒有使用selenium+phantomjs實踐,如果想了解更多selenium+phantomjs內容,可以參考文末引用連結。

注:本文只是技術交流,請不要商業用途~ 如有違反,本人一概不負責。

全部程式碼

又是非常簡單的100行程式碼完事!!!

GitHub: WebCrawlerExample/163_NeteaseMusic.py at master · iHTCboy/WebCrawlerExample


import os
import re
import json
import requests
from lxml import etree


def download_songs(url=None):
    if url is None:
        url = `https://music.163.com/#/playlist?id=2384642500`

    url = url.replace(`/#`, ``).replace(`https`, `http`)  # 對字串進行去空格和轉協議處理
    # 網易雲音樂外鏈url介面:http://music.163.com/song/media/outer/url?id=xxxx
    out_link = `http://music.163.com/song/media/outer/url?id=`
    # 請求頭
    headers = {
        `User-Agent`: `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36`,
        `Referer`: `https://music.163.com/`,
        `Host`: `music.163.com`
    }
    # 請求頁面的原始碼
    res = requests.get(url=url, headers=headers).text

    tree = etree.HTML(res)
    # 音樂列表
    song_list = tree.xpath(`//ul[@class="f-hide"]/li/a`)
    # 如果是歌手頁面
    artist_name_tree = tree.xpath(`//h2[@id="artist-name"]/text()`)
    artist_name = str(artist_name_tree[0]) if artist_name_tree else None

    # 如果是歌單頁面:
    #song_list_tree = tree.xpath(`//*[@id="m-playlist"]/div[1]/div/div/div[2]/div[2]/div/div[1]/table/tbody`)
    song_list_name_tree = tree.xpath(`//h2[contains(@class,"f-ff2")]/text()`)
    song_list_name = str(song_list_name_tree[0]) if song_list_name_tree else None

    # 設定音樂下載的資料夾為歌手名字或歌單名
    folder = `./` + artist_name if artist_name else `./` + song_list_name

    if not os.path.exists(folder):
        os.mkdir(folder)

    for i, s in enumerate(song_list):
        href = str(s.xpath(`./@href`)[0])
        song_id = href.split(`=`)[-1]
        src = out_link + song_id  # 拼接獲取音樂真實的src資源值
        title = str(s.xpath(`./text()`)[0])  # 音樂的名字
        filename = title + `.mp3`
        filepath = folder + `/` + filename
        print(`開始下載第{}首音樂:{}
`.format(i + 1, filename))

        try:  # 下載音樂
            #下載歌詞
            #download_lyric(title, song_id)

            data = requests.get(src).content  # 音樂的二進位制資料

            with open(filepath, `wb`) as f:
                f.write(data)
        except Exception as e:
            print(e)

    print(`{}首全部歌曲已經下載完畢!`.format(len(song_list)))


def download_lyric(song_name, song_id):
    url = `http://music.163.com/api/song/lyric?id={}&lv=-1&kv=-1&tv=-1`.format(song_id)
    # 請求頭
    headers = {
        `User-Agent`: `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36`,
        `Referer`: `https://music.163.com/`,
        `Host`: `music.163.com`
        # `Origin`: `https://music.163.com`
    }
    # 請求頁面的原始碼
    res = requests.get(url=url, headers=headers).text
    json_obj = json.loads(res)
    lyric = json_obj[`lrc`][`lyric`]
    reg = re.compile(r`[.*]`)
    lrc_text = re.sub(reg, ``, lyric).strip()

    print(song_name, lrc_text)




if __name__ == `__main__`:
    #music_list = `https://music.163.com/#/playlist?id=2384642500` #歌曲清單
    music_list = `https://music.163.com/#/artist?id=8325` #歌手排行榜
    # music_list = `https://music.163.com/#/search/m/?order=hot&cat=全部&limit=435&offset=435&s=梁靜茹` #搜尋列表
    download_songs(music_list)

參考

  • 如有疑問,歡迎在評論區一起討論!
  • 如有不正確的地方,歡迎指導!

注:本文首發於 iHTCboy`s blog,如若轉載,請注來源


相關文章