摘要:通常這種加密都是通過加密的,所以首先要找到這個有加密算法的。追蹤函數(shù),發(fā)現(xiàn)它指向一個叫的函數(shù),仔細研究許久后大概知道加密算法經(jīng)兩次加密獲得,模式為,偏移量為。
前言
某寶評論區(qū)已經(jīng)成功爬取了,jd的也是差不多的方法,說實話也沒什么好玩的,我是看上它們分析簡單,又沒加密才拿來試手的。如果真的要看些有趣的評論的話,我會選擇網(wǎng)易云音樂,里面匯聚了哲學家,小說家,story-teller,皮皮蝦等各種人才,某些評論非常值得收藏(甚至開了一個歌單專門收藏它們)。竟然這么好玩,何不嘗試把他們爬取下來呢?
所以這個(大規(guī)模)網(wǎng)易云音樂評論爬取project就成型了
整個過程并不順利,網(wǎng)上找到的解決方案清一色用的是pycrypto模塊(已經(jīng)沒人維護,且還要裝一個臃腫的VS14才能安裝),非常麻煩。而少數(shù)用pycryptodome模塊的也出現(xiàn)了報錯/不可行的結果。最后是看了很多github大佬的源碼,結合網(wǎng)上的思路,才重新寫了出來。在此分享出來,提供一個少走彎路的解決方案
這個實戰(zhàn)將詳細的展示一次手動分析解決動態(tài)爬取,同時還會接觸加密形post請求,更好的解決一些刁難的動態(tài)包
參考文章/代碼網(wǎng)易云音樂新版WebAPI分析
網(wǎng)易云音樂常用API淺析
參考代碼
可選:fiddler 捉包工具 (官網(wǎng)下載)
可選:了解一點AES,RSA加密
任一瀏覽器
pycryptodome模塊 (直接pip安裝)
base64及binascii模塊 (直接導入)
可選是指:如果你要深入了解如何找到加密方法,就選
開始之前感謝網(wǎng)易云給我?guī)淼囊魳泛蜌g樂,記住爬取需適度
結構分析我們要爬的是歌曲的評論,而歌曲的來源有多種,有的來源于專輯,有的來源于歌單,有的來源于歌手頁;而歌單和專輯的來源又有多種。所以爬取多個歌曲的評論之前,我們要分析一下信息的結構,最好寫下來,這樣頭腦會更清晰減少代碼修改量。這里放出一張我自己整理的結構,并選擇一條線路來實現(xiàn)(發(fā)現(xiàn)音樂→→歌單→→歌曲→→評論)
至于上圖所列的其他信息,讀者可以過完這個實戰(zhàn)后自己動手實現(xiàn),但是要注意的是:某些信息是無法直接通過網(wǎng)頁源碼提取出來的,需要通過加密的動態(tài)包(其實是API)獲得,如果有需要的話我可能會出一篇文章總結網(wǎng)易云音樂的API
收集歌單id每個歌單都有唯一的id,通過http://music.163.com/playlist... 這個鏈接就可以找到歌單,所以第一步我們要收集發(fā)現(xiàn)音樂下的多個歌單id
首先進入官網(wǎng)的“發(fā)現(xiàn)音樂”的“歌單”一欄,這里可以看到很多高分歌單,先到處點一下,可以發(fā)現(xiàn)鏈接是在改變的,說明部分數(shù)據(jù)不是動態(tài)加載的,可通過網(wǎng)頁源碼獲得。最后發(fā)現(xiàn)鏈接有cat,order,offset,和limit四個對我們有用的參數(shù),cat是分類,order是排序,offset=(頁數(shù)-1)*35,limit=35。還有注意使用前要把鏈接的井號和一個斜杠去掉,否者會導致網(wǎng)頁源碼缺失。
先隨便找一條鏈接requests一下先,可以發(fā)現(xiàn)目標信息是完整的,和F12看到的源碼一樣,那歌單id就可以放心提取了,具體用什么方法取決于讀者。參考代碼:
def get_playlists(pages,order,cat):#頁數(shù)(一頁獲取35個歌單id),排序,分類 playlist_ids = [] for page in range(pages): url = "http://music.163.com/discover/playlist/?order={}&cat={}&limit=35&offset={}".format(order,cat,str(page*35)) print(url) r = requests.get(url,headers=headers) playlist_ids.extend(re.findall(r"playlist?id=(d+?)" class="msk"",r.text)) return playlist_ids收集歌單內(nèi)歌曲id
每個歌單都有多首歌曲,所以第二步我們要獲取每個歌單下的所有歌曲id順便把歌單名也獲取。
歌單鏈接是http://music.163.com/playlist...,先隨便找一個requests一下先,目標沒缺失但是requests結果是和F12源碼是不同的,篩選時請照著requests結果寫(requests結果只有id和歌名,暫時夠用那就這樣吧)
另一種方法是通過API(http://music.163.com/weAPI/v3...)獲取,包含更全的信息(包括歌手,所屬專輯,歌單介紹等),因涉及加密和js調(diào)試較麻煩就不先介紹了(讀者可以根據(jù)本文的加密算法詳解自行調(diào)試),以后會寫篇文章介紹各種API。
參考代碼:
def get_songs(playlist_id="778462085"): r = requests.get("http://music.163.com/playlist?id={}".format(playlist_id),headers=headers) song_ids = re.findall(r"song?id=(d+?)".+?",r.text)#歌id列表 song_titles = re.findall(r"song?id=d+?">(.+?)",r.text)#歌名列表 list_title = re.search(r">(.+?) - 歌單 - 網(wǎng)易云音樂",r.text).group(1)#歌單名 list_url = "http://music.163.com/playlist?id="+playlist_id #歌單鏈接 return [song_ids, song_titles, list_title, list_url]#一次性返回這些信息給評論爬取器請求動態(tài)數(shù)據(jù)(評論)
進入某首歌http://music.163.com/song?id=...,很自然就想到requests一下,然而這不會得到任何評論信息,因為評論區(qū)是動態(tài)加載的(翻頁鏈接不變,動態(tài)標志),所以打開F12捉包吧,在xhr中查看response很快找到
搗弄過后發(fā)現(xiàn),請求鏈接中“R_SO_4_”后接的是歌曲的id,同一首歌下不同頁數(shù)的動態(tài)包的請求鏈接除csrf_token外是相同的。
請求類型為post,需要兩個參數(shù),無論是刷新還是評論翻頁這兩個參數(shù)都會變,應該是加密過的。
先不理加密先,嘗試把第一頁的兩個參數(shù)傳給請求鏈接是能獲得數(shù)據(jù)的,對應第一頁的評論,嘗試把csrf_token參數(shù)去除,還是能獲取數(shù)據(jù),所以csrf_token參數(shù)可以不要。我們大膽一點,繼續(xù)把這對參數(shù)傳給不同歌曲的請求鏈接,發(fā)現(xiàn)都能獲取對應的第一頁評論;而把第二頁的兩個參數(shù)傳給不同歌曲的請求鏈接,就會得到對應第二頁評論,以此類推。所以得出結論,任一頁數(shù)的兩個參數(shù)對不同歌曲是通用的,第n頁的參數(shù)post過去會得到第n頁的評論。這樣就成功繞過了加密問題。
然而還是存在缺點的,請看下面對話
A:哈哈哈——這樣就不用理會怎樣加密了!!!
B:只爬前幾頁的話確實是的,但是如果你要爬很多頁或全部爬取怎么辦,那些10W+評論的歌曲難道你要手動復制粘貼5000+對參數(shù)嗎?
C(對著A):你不知道網(wǎng)易云音樂的API是共用一套加密算法的嗎?如果你想爬評論以外的信息怎么辦?
所以如果你要大量爬取評論/各種信息時,加密算法就顯得很重要。具體怎樣加密可以不用了解,直接套用就可(代碼在最后),想了解的話繼續(xù)往下看。
這里簡單提供一下獲取評論的參考思路,交給讀者補全
def get_comments(arg): # 接收get_songs方法返回的數(shù)據(jù),爬取頁數(shù)等 post_urls = [......] # 通過get_songs方法返回的數(shù)據(jù)構造每首歌的請求鏈接列表 data = [{}] # 手動寫入或加密算法生成 for i in range(len(post_url)): # 爬每首歌評論 #for j in range(pages): # 如果每首歌要爬多頁,那要再設一個循環(huán) r = requests.post(post_urls[i],data=data,headers=headers) print(r.json()) # 剩下解析json數(shù)據(jù)并寫入容器。其中json數(shù)據(jù)可能會有坑,詳看github中的代碼。 """"""
最終帶加密算法的爬評論代碼:github(代碼笨了,應該一次性生成多組params和encSecKey再索引使用而不是每首歌都加密一次,懶得改了....)(暫時是單線程,比較慢,有時間加個多線程下去)
(可選)加密算法詳解可以確定params和encSecKey這兩個參數(shù)是加密過的了,里面包含著頁數(shù)信息,服務器收到參數(shù),解密后根據(jù)內(nèi)容返回信息。通常這種加密都是通過js加密的,所以首先要找到這個有加密算法的js。
通過F12查看包的initiator可以得知其發(fā)起者是core.js,馬上去JS包那里找。
其內(nèi)容是巨量堆砌在一起的,丟去排版一下后拷貝到本地文件中,代碼量20000+,先用搜索一下params和encSecKey看看能否定位到加密算法那里。
結果是可行的,看到這個熟悉的data就知道加密函數(shù)是window.asrsea(),接收了4個參數(shù)!!!又加大了分析難度,根本不知道這些參數(shù)是什么。這時就要上fiddler了來調(diào)試js了,能實現(xiàn)本地js覆蓋原來的js,讓瀏覽器執(zhí)行本地的js。(使用fiddler前請配置好代理,網(wǎng)上查)
fiddler調(diào)試配置選到autoresponder,把三個選項全勾上,然后按add rule,添加要替換的js,如圖添加rule,第一欄是待替換的js(就是那個core.js包的鏈接),第二欄是替換物的絕對路徑(就是拷貝回來修改過的js文件的絕對路徑),然后按save
對剛才找到的代碼塊進行修改,添加5條語句讓它分別輸出四個參數(shù)和params,通過比較包和輸出的params確定成組的4個參數(shù)。
注意:①拷貝回來的js一定要趁熱修改趁熱使用,原來的core.js一段時間后會變動(如上面兩幅圖第一個參數(shù)中的j3x變成了j5o),所以不要照抄我的,以你拷貝回來的為準
②如果修改后的js沒在瀏覽器中加載,fiddler也捉不到這個core.js的話,請清空瀏覽器的緩存再嘗試
配置好fiddler修改好js后馬上運行fiddler,然后馬上打開瀏覽器,開啟F12選擇console控制臺監(jiān)測輸出,打開測試歌曲鏈接http://music.163.com/#/song?i...,可以看到有很多組輸出,我們可以通過比較評論包的params參數(shù)和輸出的params參數(shù)找到評論對應的那組參數(shù)(如下圖紅色圈著的那組)
我們可以看到,不同組的第二第三第四個輸出值都是一樣的,所以window.asrsea()除第一個參數(shù)是會變外,其余三個參數(shù)是定值。研究對象一下子減到一個。對評論來說,第一個參數(shù)是"{rid: "R_SO_4_411907742", offset: "0", total: "true", limit: "20", csrf_token: "f15b016ca1e43812f78a260998917527"}" ,是json object,為了搞清其變化規(guī)律,我們把評論翻到第二頁看看會變成怎樣。第二頁評論得到"{rid: "R_SO_4_411907742", offset: "20", total: "false", limit: "20", csrf_token: "f15b016ca1e43812f78a260998917527"}"......
按多幾頁,多切幾首歌后就會總結出第一個參數(shù)的規(guī)律,這個object包含了歌曲id,頁數(shù)等信息,應該是被加密之前的原始數(shù)據(jù)。
rid——‘R_SO_4_’加上歌曲id(其實rid參數(shù)可以不要,剛才說過任一頁數(shù)的兩個參數(shù)對不同歌曲是通用的,可以讓它為空字符串)
offset——字符化的數(shù)字,值等于(頁數(shù)-1)*20
total——第一頁是"true",其余頁數(shù)是"false"
limit——固定"20"
csrf_token——之前遇到過,無規(guī)律字符串(這個可以不要,直接讓它為空字符串)
window.asrsea()接收的第一個參數(shù)還經(jīng)過JSON.stringify()處理,讓其變成了json數(shù)據(jù),這個過程我們可以用python的json.dumps(dict)實現(xiàn)
#window.asrsea()接收參數(shù) "{......}"#第一參數(shù),那個json數(shù)據(jù) "010001"#第二參數(shù) "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"#第三參數(shù) "0CoJUm6Qyw8W8jud"#第四參數(shù)加密算法
參數(shù)是搞明白了,但是如何加密還是不清楚,于是回到剛才的js文件中。追蹤window.asrsea()函數(shù),發(fā)現(xiàn)它指向一個叫d的函數(shù),仔細研究許久后大概知道加密算法
params經(jīng)兩次aes加密獲得,模式為CBC,偏移量為b"0102030405060708"。第一次aes加密的明文是處理后的第一參數(shù)(具體處理方法看代碼),密匙為第四參數(shù);第二次aes加密的明文是第一次加密獲得的密文,密匙是那個隨機數(shù),之后獲得params,一些具體處理看代碼
encSecKey經(jīng)過rsa加密,明文和aes第二次加密同一個隨機數(shù),公匙是(第二參數(shù),第三參數(shù))。
參數(shù)處理細節(jié)處理請看代碼
import json from Crypto.Cipher import AES #新的加密模塊只接受bytes數(shù)據(jù),否者報錯,密匙明文什么的要先轉(zhuǎn)碼 import base64 import binascii import random secret_key = b"0CoJUm6Qyw8W8jud"#第四參數(shù),aes密匙 pub_key ="010001"#第二參數(shù),rsa公匙組成 modulus = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7" #第三參數(shù),rsa公匙組成 #生成隨機長度為16的字符串的二進制編碼 def random_16(): return bytes("".join(random.sample("1234567890DeepDarkFantasy",16)),"utf-8") #aes加密 def aes_encrypt(text,key): pad = 16 - len(text)%16 #對長度不是16倍數(shù)的字符串進行補全,然后在轉(zhuǎn)為bytes數(shù)據(jù) try: #如果接到bytes數(shù)據(jù)(如第一次aes加密得到的密文)要解碼再進行補全 text = text.decode() except: pass text = text + pad * chr(pad) try: text = text.encode() except: pass encryptor = AES.new(key,AES.MODE_CBC,b"0102030405060708") ciphertext = encryptor.encrypt(text) ciphertext = base64.b64encode(ciphertext)#得到的密文還要進行base64編碼 return ciphertext #rsa加密 def rsa_encrypt(ran_16,pub_key,modulus): text = ran_16[::-1]#明文處理,反序并hex編碼 rsa = int(binascii.hexlify(text), 16) ** int(pub_key, 16) % int(modulus, 16) return format(rsa, "x").zfill(256) #返回加密后內(nèi)容 def encrypt_data(data):#接收第一參數(shù),傳個字典進去 ran_16 = random_16() text = json.dumps(data) params = aes_encrypt(text,secret_key)#兩次aes加密 params = aes_encrypt(params,ran_16) encSecKey = rsa_encrypt(ran_16,pub_key,modulus) return {"params":params.decode(), "encSecKey":encSecKey }關于API
剛才分析加密算法進行fiddler調(diào)試的時候,有沒有注意到有很多組輸出,本文只選了評論那組進行分析。細心的讀者可以發(fā)現(xiàn),不同組的第一個輸出都是一個json object,內(nèi)容具有可讀性且輸出的params都能找到對應的xhr包。沒錯,那就是明文,不同的明文對應不同的API,明文通過加密算法得到的params和encSecKey由對應API接收,然后返回對應的信息。
API有很多個,除了獲取評論的API外,還有歌詞API,歌單API,專輯API,搜索API,mp3API等等,甚至還有簽到API(這個要登錄先,所以也有登錄API)
API的鏈接很好找,捉下包就可以了;所以要利用這些API,重點是找到對應明文的規(guī)律,就像上面分析json object一樣,要多次采樣多次試驗。在面對數(shù)十個API時,無疑是非常耗時的,讀者可以自行探索。
文章版權歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/41755.html
摘要:時間永遠都過得那么快,一晃從年注冊,到現(xiàn)在已經(jīng)過去了年那些被我藏在收藏夾吃灰的文章,已經(jīng)太多了,是時候把他們整理一下了。那是因為收藏夾太亂,橡皮擦給設置私密了,不收拾不好看呀。 ...
摘要:之前提到動態(tài)加載就兩個解決方案手動分析和。背后有許多不為人知的交易進行著,動態(tài)爬取的任務就是攔截它們揭開它們的真面目。在爬蟲界有著霸王硬上弓的稱號,管它情不情愿,來了動態(tài)加載也只有屈服的份了。 之前提到動態(tài)加載就兩個解決方案——手動分析和selenium。接下來的文章我們會來深入探討它們,本文將首先,重點介紹前者——手動分析 手動分析是一個比較有難度,比較麻煩的解決方案,但優(yōu)點也很明顯...
摘要:行爬取頂點全網(wǎng)任意小說掘金之前連續(xù)多篇文章介紹客戶端爬取平臺,今天我們從零開始,實現(xiàn)爬取頂點小說網(wǎng)任意一本小說的功能。文件標記所有文件我的后端書架后端掘金我的后端書架月前本書架主要針對后端開發(fā)與架構。 30行js爬取頂點全網(wǎng)任意小說 - 掘金之前連續(xù)多篇文章介紹客戶端爬取平臺(dspider),今天我們從零開始,實現(xiàn)爬取頂點小說網(wǎng)任意一本小說的功能。 如果你還不知道客戶端爬取,可以先看...
摘要:行爬取頂點全網(wǎng)任意小說掘金之前連續(xù)多篇文章介紹客戶端爬取平臺,今天我們從零開始,實現(xiàn)爬取頂點小說網(wǎng)任意一本小說的功能。文件標記所有文件我的后端書架后端掘金我的后端書架月前本書架主要針對后端開發(fā)與架構。 30行js爬取頂點全網(wǎng)任意小說 - 掘金之前連續(xù)多篇文章介紹客戶端爬取平臺(dspider),今天我們從零開始,實現(xiàn)爬取頂點小說網(wǎng)任意一本小說的功能。 如果你還不知道客戶端爬取,可以先看...
摘要:其次,使用后,還需要針對做特定處理。看到這就可以構想一下爬蟲的爬取邏輯了。 運行環(huán)境 我的運行環(huán)境如下: 系統(tǒng)版本 Windows10。 Python版本 Python3.5,推薦使用Anaconda 這個科學計算版本,主要是因為它自帶一個包管理工具,可以解決有些包安裝錯誤的問題。去Anaconda官網(wǎng),選擇Python3.5版本,然后下載安裝。 IDE 我使用的是PyCharm,是專...
閱讀 1324·2023-04-26 00:10
閱讀 2427·2021-09-22 15:38
閱讀 3744·2021-09-22 15:13
閱讀 3502·2019-08-30 13:11
閱讀 645·2019-08-30 11:01
閱讀 3027·2019-08-29 14:20
閱讀 3205·2019-08-29 13:27
閱讀 1724·2019-08-29 11:33