天天看點

【python】爬蟲入門:爬取網易雲音樂的歌曲評論、使用者歌單、使用者聽歌記錄等目錄一、概述二、爬取流程三、總結

目錄

一、概述

二、爬取流程

1、爬取評論

1.1、資源定位

1.2、爬取準備

1.3、代碼實作

2、爬取聽歌記錄

2.1、資源定位

2.2、爬取準備

2.3、js劫持

三、總結

一、概述

第一次學爬蟲,正常來講應該是爬百度百科或者是豆瓣之類的,但這倆網站我沒興趣,是以選擇爬網易雲。

學習過程中主要參考該網址。

二、爬取流程

1、爬取評論

1.1、資源定位

當我們進入網易雲音樂的網頁版,進入一首歌的頁面:

【python】爬蟲入門:爬取網易雲音樂的歌曲評論、使用者歌單、使用者聽歌記錄等目錄一、概述二、爬取流程三、總結

我們可以看到歌名、歌詞、評論、相似歌曲、收藏該歌曲的歌單等。

這些資訊我們如何得知的?

浏覽器向網易的伺服器發送請求,網易的伺服器傳回資料,浏覽器再給我們看。

嗯,意思就是,如果我們能模仿浏覽器的請求,讓網易的伺服器把我們想要的資訊給我們,這樣我們就能不斷的收集資訊了。

這也就是爬蟲的基本功能了。

怎麼看浏覽器向網易發送請求呢?f12。

【python】爬蟲入門:爬取網易雲音樂的歌曲評論、使用者歌單、使用者聽歌記錄等目錄一、概述二、爬取流程三、總結

當我們在按下f12之後按f5重新整理網頁時,控制台就會顯示所有的浏覽器向用戶端發出的請求。

注意,第一行為“Network”。filter選擇“All”。

下面有一大堆亂七八糟的東西:jpg、png、js這些是檔案,還有一些像是網址的東西:cdns?csrf_token=。看不明白。

由于我們需要的是浏覽器向伺服器發出的請求,我們不要選擇“All”,選擇“XHR”。這個XHR是個什麼?

XMLHttpRequest對象(簡稱XHR)是ajax技術的核心,ajax可以無重新整理更新頁面得益于xhr。

沒怎麼看明白,不過不要緊,這個是前端的活,不用了解。看XHR之後,隻剩下以下幾項:

【python】爬蟲入門:爬取網易雲音樂的歌曲評論、使用者歌單、使用者聽歌記錄等目錄一、概述二、爬取流程三、總結

這不是之前看起來很像是網頁的東西嘛。随便點進去一個看看,這個lyric看上去應該是歌詞:

【python】爬蟲入門:爬取網易雲音樂的歌曲評論、使用者歌單、使用者聽歌記錄等目錄一、概述二、爬取流程三、總結

嗯,猜對了。也就是說,我們想要爬取歌詞的話,就要分析這個。

R_SO呢?這個是啥?

【python】爬蟲入門:爬取網易雲音樂的歌曲評論、使用者歌單、使用者聽歌記錄等目錄一、概述二、爬取流程三、總結

看上去應該是評論。成了,我們需要的就是這個。

于是我們就找到了評論的位置。

對于一些小網站,對反爬蟲沒什麼要求或者是沒有那麼多資源的話,整個網頁的内容都在element裡面。xhr是一個沒有的,比如這樣一個小說網站:

【python】爬蟲入門:爬取網易雲音樂的歌曲評論、使用者歌單、使用者聽歌記錄等目錄一、概述二、爬取流程三、總結

網易雲應該是爬的人太多了加上為了使用者體驗,是以用了那麼多XHR。

1.2、爬取準備

既然我們知道了評論是由XHR中的某一條顯示的,那就看看這一條裡面有什麼:

先看Headers,這是浏覽器向伺服器發送的資訊儲存的位置:

【python】爬蟲入門:爬取網易雲音樂的歌曲評論、使用者歌單、使用者聽歌記錄等目錄一、概述二、爬取流程三、總結

第一個url很有用,要記下來。

再看Response Headers,回複頭部?應該是伺服器傳回的資訊的頭部:

【python】爬蟲入門:爬取網易雲音樂的歌曲評論、使用者歌單、使用者聽歌記錄等目錄一、概述二、爬取流程三、總結

時間啊,解碼方式什麼的。伺服器竟然用的是nginx,有點熟悉。

然後是Request Headers,重頭戲:

【python】爬蟲入門:爬取網易雲音樂的歌曲評論、使用者歌單、使用者聽歌記錄等目錄一、概述二、爬取流程三、總結

這個是浏覽器發往伺服器的request的頭部,主要記住兩個參數:referer和user-agent。這兩個參數在伺服器傳回cheating時候加載head裡。

接下來是Form data。不知道有什麼用:

【python】爬蟲入門:爬取網易雲音樂的歌曲評論、使用者歌單、使用者聽歌記錄等目錄一、概述二、爬取流程三、總結

來分析一下:

我們的浏覽器向伺服器發送一條請求:

發送的位址應該就是第一個url,通過Request Headers表明我的身份。那問題來了:如何表明我的目的呢?

比如說我的目的是要殘酷天使的行動綱領這首歌的第100-200條評論,那麼我們要發給伺服器的資訊有:

歌名:殘酷天使的行動綱領

需求:評論

條數:100

起始評論:第100條

一共四個關鍵資訊。

根據我們分析這個XHR,顯示提供的資訊有:url、浏覽器配置、時間等。

平平無奇。

這說明有隐式資訊。

首先分析顯示資訊的url:

https://music.163.com/weapi/v1/resource/comments/R_SO_4_657666?csrf_token=

當我們調換不同的歌的時候,發現該url整體不會變動,隻有R_SO後面的數字會變。是以,這數字就是我們傳遞的一個變量,該數字應與歌一一對應。

url中的comments指的是評論,可以與我們的需求對應上。

現在還有兩個關鍵資訊,不能顯示傳遞:起始評論和評論條數。

url有傳遞過去的參數。藏在哪裡呢?

藏在Form Data裡。

這兩串好長的字元串是加密過的,解密的過程參見我在第一部分所示的連結。

我的主要代碼也是參考的這位大佬。

解密的過程可以不去在意,但是這種定位方式一定要學會,十分有用。

1.3、代碼實作

大佬的代碼為python2,如下:

#coding = utf-8
from Crypto.Cipher import AES
import base64
import requests
import json


headers = {
    'Cookie': 'appver=1.5.0.75771;',
    'Referer': 'http://music.163.com/'
}

first_param = "{rid:\"\", offset:\"0\", total:\"true\", limit:\"20\", csrf_token:\"\"}"
second_param = "010001"
third_param = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
forth_param = "0CoJUm6Qyw8W8jud"

def get_params():
    iv = "0102030405060708"
    first_key = forth_param
    second_key = 16 * 'F'
    h_encText = AES_encrypt(first_param, first_key, iv)
    h_encText = AES_encrypt(h_encText, second_key, iv)
    return h_encText


def get_encSecKey():
    encSecKey = "257348aecb5e556c066de214e531faadd1c55d814f9be95fd06d6bff9f4c7a41f831f6394d5a3fd2e3881736d94a02ca919d952872e7d0a50ebfa1769a7a62d512f5f1ca21aec60bc3819a9c3ffca5eca9a0dba6d6f7249b06f5965ecfff3695b54e1c28f3f624750ed39e7de08fc8493242e26dbc4484a01c76f739e135637c"
    return encSecKey
    

def AES_encrypt(text, key, iv):
    pad = 16 - len(text) % 16
    text = text + pad * chr(pad)
    encryptor = AES.new(key, AES.MODE_CBC, iv)
    encrypt_text = encryptor.encrypt(text)
    encrypt_text = base64.b64encode(encrypt_text)
    return encrypt_text


def get_json(url, params, encSecKey):
    data = {
         "params": params,
         "encSecKey": encSecKey
    }
    response = requests.post(url, headers=headers, data=data)
    return response.content


if __name__ == "__main__":
    url = "http://music.163.com/weapi/v1/resource/comments/R_SO_4_30953009/?csrf_token="
    params = get_params();
    encSecKey = get_encSecKey();
    json_text = get_json(url, params, encSecKey)
    json_dict = json.loads(json_text)
    print json_dict['total']
    for item in json_dict['comments']:
        print item['content'].encode('gbk', 'ignore')
           

有以下幾點要注意:

第一,代碼使用了Crypto.Cipher包,這個包我安裝失敗,可以通過安裝pycrypto代替。

第二,由于我用的python3,而python3和2的一大差別就是str與bytes的分家,是以有以下幾句代碼要更改一下:

def get_params():
    iv = "0102030405060708"
    first_key = forth_param
    second_key = 16 * 'F'
    h_encText = AES_encrypt(first_param.encode(), first_key, iv)#第一個參數要更改
    h_encText = AES_encrypt(h_encText, second_key, iv)
    return h_encText

def AES_encrypt(text, key, iv):
    pad = 16 - len(text) % 16
    text2=bytes(pad * chr(pad),encoding='gbk')#這裡也要改
    text3 = text + text2#這裡也要改
    encryptor = AES.new(key, AES.MODE_CBC, iv)
    encrypt_text = encryptor.encrypt(text3)
    encrypt_text = base64.b64encode(encrypt_text)
    return encrypt_text
           

第三,該url需要五個參數,在代碼中展現為:

first_param = "{rid:\"\", offset:\"0\", total:\"true\", limit:\"20\", csrf_token:\"\"}"
           

即:rid,offset,total,limit,token。

整份代碼絕大多數工作就是把這五個參數組成的bytes轉換為加密後的形式。

其中,rid是歌曲id,這個為空的話就直接預設是url帶的參數,是以可以無視掉,當我們需要在爬蟲中換歌的時候就要用這個了;

offset是偏移量,也就是相對第一個評論向後便宜多少;

total在第一頁是true,其餘是false。

其實知道參數以後自己試就可以了,整個爬蟲最難的就在于解密,其次就是url傳遞的參數的擷取。

然後就是按部就班的帶參數post,解析傳回值即可。

其四,在解析傳回值時,傳回的是一個嵌套字典,字典對應的關鍵字可以在PreView中檢視,如下:

【python】爬蟲入門:爬取網易雲音樂的歌曲評論、使用者歌單、使用者聽歌記錄等目錄一、概述二、爬取流程三、總結

這就指的是“熱門評論”中的第一條的内容。

效果如下:

【python】爬蟲入門:爬取網易雲音樂的歌曲評論、使用者歌單、使用者聽歌記錄等目錄一、概述二、爬取流程三、總結

這樣就實作了評論的爬取。

2、爬取聽歌記錄

這部分才是我自己主要做的。難點就在于url的參數找不到。

2.1、資源定位

類似評論,我們要想找聽歌記錄,肯定要去他的個人首頁。這個有點涉及隐私,我就以爬我自己的為例:

【python】爬蟲入門:爬取網易雲音樂的歌曲評論、使用者歌單、使用者聽歌記錄等目錄一、概述二、爬取流程三、總結

如圖,XHR有以下幾個,很容易就一眼看到records,以我半吊子的英語水準也能看得出來這個是記錄的意思。

點進去看一下吧:

【python】爬蟲入門:爬取網易雲音樂的歌曲評論、使用者歌單、使用者聽歌記錄等目錄一、概述二、爬取流程三、總結

不出所料,果然是。不過網頁版沒法看聽歌次數,而是用一個score代替,看上去這個分值應該就是100*count/countMAX,看我第一首歌是100分,第二首就隻有41分了。

2.2、爬取準備

爬聽歌記錄肯定也要看Headers啊。

先看General:

【python】爬蟲入門:爬取網易雲音樂的歌曲評論、使用者歌單、使用者聽歌記錄等目錄一、概述二、爬取流程三、總結

wc,這個url好狠毒,一個參數都沒有顯示給我們,也就是說全部參數都是在DataForm裡。

看看Request Headers:

【python】爬蟲入門:爬取網易雲音樂的歌曲評論、使用者歌單、使用者聽歌記錄等目錄一、概述二、爬取流程三、總結

平平無奇,隻有referer變成了我的首頁的連結。看來想要攻克這個,就得去找url帶的參數了。

參數不可能是憑空出來的,它一定是一個函數生成的,我們找到這個函數就好了。怎麼找呢?要看控制台中的一項:

【python】爬蟲入門:爬取網易雲音樂的歌曲評論、使用者歌單、使用者聽歌記錄等目錄一、概述二、爬取流程三、總結

也就是initiator。這一列中就是對應的XHR的媽媽,調用這個檔案産生所需的XHR。我們需要的檔案叫core_亂碼.js。

看看這個檔案裡有什麼:

wdnmd當機了。這檔案有點大,複制到notepad++裡面好長好長。我們需要什麼呢?需要Data Form裡面的params和encSerKey。直接ctrl+f找一找,定位到第90行:

(function()
{
    var c4g=NEJ.P,et7m=c4g("nej.g"),v4z=c4g("nej.j"),k4o=c4g("nej.u"),
            Sj0x=c4g("nm.x.ek"),l4p=c4g("nm.x");
    if(v4z.bg5l.redefine)
        return;
    window.GEnc=true;
    var bry7r=function(cxa3x)
    {
        var m4q=[];
        k4o.be5j(cxa3x,function(cwZ3x)
                                {
                                    m4q.push(Sj0x.emj[cwZ3x])
                                });
        return m4q.join("")
    };
    var cwX3x=v4z.bg5l;
    v4z.bg5l=function(Z5e,e4i)
    {
        var i4m={},e4i=NEJ.X({},e4i),md9U=Z5e.indexOf("?");
        if(window.GEnc&&/(^|\.com)\/api/.test(Z5e)&&!                
           (e4i.headers&&e4i.headers[et7m.zx3x]==et7m.Gv5A)&&!e4i.noEnc)
        {
            if(md9U!=-1)
            {
                i4m=k4o.gU7N(Z5e.substring(md9U+1));
                Z5e=Z5e.substring(0,md9U)
            }
            if(e4i.query)
            {
                i4m=NEJ.X(i4m,k4o.fO7H(e4i.query)?k4o.gU7N(e4i.query):e4i.query)
            }
            if(e4i.data)
            {
                i4m=NEJ.X(i4m,k4o.fO7H(e4i.data)?k4o.gU7N(e4i.data):e4i.data)
            }
            i4m["csrf_token"]=v4z.gQ7J("__csrf");
            Z5e=Z5e.replace("api","weapi");
            e4i.method="post";
            delete e4i.query;
            window.console.info(i4m);//這一行是後加的
            var bUS6M=window.asrsea(JSON.stringify(i4m),
                                    bry7r(["流淚","強"]),
                                    bry7r(Sj0x.md),
                                    bry7r(["愛心","女孩","驚恐","大笑"]));
            e4i.data=k4o.cx5C(
                    {
                        params:bUS6M.encText,
                        encSecKey:bUS6M.encSecKey
                    })
        }
        cwX3x(Z5e,e4i)
    };
    v4z.bg5l.redefine=true
}
)();
           

我真是日了,原檔案裡面一行代碼結果弄出來這麼多行。看最後可以看到我們需要的params和encSecKey。

這倆怎麼來的呢?看一下它們是BUS6M的兩個屬性,這個BUS6M又是怎麼來的?是window.asrsea的傳回值。

這個window.asrsea就是加密算法了。具體解析去看我上文的連結。

現在隻看這個函數的參數,有四個,第一個是i4m轉為string格式,剩下三個看上去怪怪的。你說這個流淚啊,強啊,愛心,女孩什麼的都是常量,肯定不是我們要的參數,那參數隻可能是i4m或者Sj0x.md了。先看前者吧。

看上一行,i4m應該是一個字典,裡面的鍵有csrf_token,那有九成機率确定它就是參數的原始形态了。

我們隻要輸出i4m不就行了。怎麼輸出呢?

修改這個core就行啊。

那問題來了,這檔案在人家網易的伺服器裡,浏覽器是調用伺服器的這個檔案生成參數加密前的序列的。我又沒法進網易的伺服器。

這時候就需要js劫持了。

2.3、js劫持

簡而言之,就是浏覽器要使用core這個檔案的時候,不去向網易要這個檔案,而是和我要——反正我都有這個檔案的所有代碼了,從我這裡運作一樣能得到結果不是。

怎麼實作“浏覽器和我要這個檔案”的功能呢?

要用到fiddler。這個程式很厲害,很簡單就能實作js劫持:

上面連結中的方法不好用,我建議用我的方法:

第一:安裝并使用fiddler。安裝很簡單,使用它可能會有一點麻煩,要是有代理就更麻煩。我建議使用

【python】爬蟲入門:爬取網易雲音樂的歌曲評論、使用者歌單、使用者聽歌記錄等目錄一、概述二、爬取流程三、總結

Chrome的switchOmega插件,選擇系統代理。

判斷是否好用的一個方法為:開着fiddler,随便開一個網址,如果fiddler界面出來一堆花花綠綠的就是好用。

第二:修改fiddler的過濾器。預設過濾器是不會顯示js檔案的請求的,我們需要知道浏覽器發出調用core的請求,是以要把它顯示出來。如下圖:

【python】爬蟲入門:爬取網易雲音樂的歌曲評論、使用者歌單、使用者聽歌記錄等目錄一、概述二、爬取流程三、總結

在Show only if URL contains中添加如下的正規表達式:

REGEX:(?insx)/[^\?/]*\.(css|js|json|ico|jpg|png|gif|bmp|wav)(\?.*)?$
           

即可。

第三:設定劫持條件。如下圖:

【python】爬蟲入門:爬取網易雲音樂的歌曲評論、使用者歌單、使用者聽歌記錄等目錄一、概述二、爬取流程三、總結

在界面右側選擇AutoResponder,也就是1;下面三個框勾上前面兩個也就是2和3;add之後添加4和5,其中4不用自己寫,5是我們上面複制下來的core.js的絕對位址,我是放在桌面的,加上輸出i4m的那一句。

如何自動填充4呢?

首先清除浏覽器緩存,如果不清除緩存,浏覽器會将core下載下傳下來,不會再次請求伺服器(在這裡可能也可以改,但我沒試過),fiddler就找不到調用core的請求了。

然後進入網易雲音樂使用者首頁,重新整理,就可以看到了:

【python】爬蟲入門:爬取網易雲音樂的歌曲評論、使用者歌單、使用者聽歌記錄等目錄一、概述二、爬取流程三、總結

如圖,第一條紫色的就是core的調用請求。左鍵單擊,按住後拽到右邊的AutoResponder裡面,4就填充好了。

【python】爬蟲入門:爬取網易雲音樂的歌曲評論、使用者歌單、使用者聽歌記錄等目錄一、概述二、爬取流程三、總結

這時我們把預設的*200-SESSION_10改成修改後的core的絕對路徑即可。

然後重新重新整理網頁,就是調用我們自己的core了。效果如下:

【python】爬蟲入門:爬取網易雲音樂的歌曲評論、使用者歌單、使用者聽歌記錄等目錄一、概述二、爬取流程三、總結

在f12的console裡,輸出了以下幾個i4m,我們可以猜測,後兩個應該就是歌單和聽歌曆史的url對應參數了。這樣我們就找到了答案。然後将1中的代碼改一下就可以:

#first_param = "{rid:\"\", offset:\"0\", total:\"true\", limit:\"20\", csrf_token:\"\"}"#歌曲評論
first_param = "{uid: \"77824233\",type: \"-1\",limit: \"1000\",offset: \"0\",total: \"true\",csrf_token: \"\"}"#聽歌記錄
           

上面是歌曲評論的參數,下面是我們找到的聽歌曆史的參數。

然後把url改一下,顯示部分的代碼也改一下就好了。

如果報錯

【python】爬蟲入門:爬取網易雲音樂的歌曲評論、使用者歌單、使用者聽歌記錄等目錄一、概述二、爬取流程三、總結

把fiddler關閉即可。

效果如下:

【python】爬蟲入門:爬取網易雲音樂的歌曲評論、使用者歌單、使用者聽歌記錄等目錄一、概述二、爬取流程三、總結

成功擷取到聽歌記錄。

三、總結

本次爬蟲結束之後,對爬蟲的具體流程有了一個大概的認識:

簡而言之,爬蟲就是定位資源→模拟請求→擷取資源的一個過程。

通過在f12中尋找可以找到資源的位置,所需的url或者直接的元素等,通過将網頁元素儲存或者是向url送出請求得到所有資源,在所有資源中尋找我們想要的。

沒有利用ip池等技術,八成我這個爬一百首歌ip就會被封了。