1. 程式人生 > >Python爬蟲---爬取抖音短視訊

Python爬蟲---爬取抖音短視訊

[TOC] ##前言 最近一直想要寫一個抖音爬蟲來批量下載抖音的短視訊,但是經過幾天的摸索我發現了一個很嚴重的問題......抖音實在是難爬!從一開始的網頁分析中就有著很多的坑,但是這幾天的摸索也不是一無所獲,我鼓搗出來了一個問題版的抖音爬蟲(操作較為複雜),所以我也想通過這篇部落格來記錄下我分析網頁的過程,也想請教一下路過大佬們,歡迎各位大佬指出問題! ##抖音爬蟲製作 ###選定網頁 想要爬取抖音上面的視訊,就要先找到可以刷小視訊的地址,於是我就開始在網上尋找網頁版的抖音。經過一番尋找,發現抖音根本就沒有網頁版的這個板塊,開啟的網頁大多都是如下圖所示提示你下載app的網頁: ![](https://img2020.cnblogs.com/blog/1971945/202005/1971945-20200502003356423-570786931.png) 想要爬取小視訊的內容,沒有網頁地址可不行。於是我又想到了另一種尋找網頁的方法: 首先我打開了手機抖音,選定了一個喜歡的抖音號,使用複製連結的方法來嘗試是否可以在網頁中開啟: ![](https://img2020.cnblogs.com/blog/1971945/202005/1971945-20200502004103373-1207369429.png) 將連結貼上到記事本中,發現它是長這個樣子的 ``` https://v.douyin.com/wGf4e1/ ``` 將這個網址在瀏覽器中開啟,發現這個網址可以正常顯示 ![](https://img2020.cnblogs.com/blog/1971945/202005/1971945-20200502004546127-799043097.png) 向下滑動,也可以看到這個賬號釋出的視訊 ![](https://img2020.cnblogs.com/blog/1971945/202005/1971945-20200502004701760-1739651115.png) ok,到現在為止,我已經選定了將這個頁面作為我獲取資料的起始頁面 選定起始頁之後,我的下一步想法是要去獲取這些小視訊的單獨的網頁地址,於是我又點選了下面的這些小視訊。這裡噁心的地方出現了,無論我點選哪一個小視訊,彈出來的都是強迫你下載app的介面 於是我又想嘗試上面獲取到網頁的操作來獲取視訊的地址,再次開啟手機上的抖音,在這個漫威的賬號下隨便開啟一個視訊,點選右下角的分享,複製它的連結: ![](https://img2020.cnblogs.com/blog/1971945/202005/1971945-20200502010321032-1003373600.png) 這個連結地址長這個樣子: ``` #黑寡婦 北美終極預告!黑暗過往揭曉,2020即將強勢開啟漫威電影宇宙第四階段! https://v.douyin.com/wGqCNG/ 複製此連結,開啟【抖音短視訊】,直接觀看視訊! ``` 我再把這段地址複製到瀏覽器中開啟: ![](https://img2020.cnblogs.com/blog/1971945/202005/1971945-20200502010730090-92902031.png) 開啟的確實是視訊頁,點選播放按鈕也可以播放視訊,所以這就是我們需要記住的第二個頁面。 ###分析網頁 現在又出現了一個比較麻煩的事情,在瀏覽器中輸入網址過後,跳轉到了視訊的播放頁,但是此時的播放頁地址經過了重定向生成了非常長的一串地址,乍一看毫無規律可講 這是重定向之後的網址: ![](https://img2020.cnblogs.com/blog/1971945/202005/1971945-20200502170610833-1048541837.png) 正常來說請求第一種連結和第二種重定向之後的連結都可以獲取到資訊,但是我發現第一種連結地址是找不到規律的,所以我猜測第二種網址的規律會更加的好找,先把連結地址複製到記事本中: ``` https://www.iesdouyin.com/share/video/6802189485015633160/?region=CN&mid=6802184753988471559&u_code=388k48lba520&titleType=title&utm_source=copy_link&utm_campaign=client_share&utm_medium=android &app=aweme ``` 看了這麼長一串的連結,連結包含的內容也是非常多的,對分析規律有著很大的干擾,於是我試著精簡一下這個連結(刪掉連結裡面的一些內容,看看是否還能找到頁面)經過了一次又一次的嘗試,我所得到的最簡單的網址如下: ``` https://www.iesdouyin.com/share/video/6802189485015633160/?mid=6802184753988471559 ``` 這個網址依舊可以開啟視訊頁,如果在刪掉一點東西,出來的就是抖音的宣傳頁,所以這個網址就是我所需要的最簡單網址 ![](https://img2020.cnblogs.com/blog/1971945/202005/1971945-20200502172752828-102430608.png) 就一個網址當然是分析不出規律的,於是我又用同樣的方法來得到兩個新網址: ``` https://www.iesdouyin.com/share/video/6818885848784702728/?region=CN&mid=6818885858780203783&u_code=388k48lba520&titleType=title&utm_source=copy_link&utm_campaign=client_share &utm_medium=android&app=aweme https://www.iesdouyin.com/share/video/6820605884050181379/?region=CN&mid=6820605916115864328&u_code=388k48lba520&titleType=title&utm_source=copy_link&utm_campaign=client_share &utm_medium=android&app=aweme ``` 精簡網址之後,將三個網址放在一起觀察: ``` https://www.iesdouyin.com/share/video/6802189485015633160/?mid=6802184753988471559 https://www.iesdouyin.com/share/video/6818885848784702728/?mid=6818885858780203783 https://www.iesdouyin.com/share/video/6820605884050181379/?mid=6820605916115864328 ``` 不難發現,這三個網址的區別就在於數字的不同 接下來猜測:這串數字會不會是每個視訊的Id值? 隨後我開啟[漫威影業抖音號](https://www.iesdouyin.com/share/user/77886805522?u_code=388k48lba520&sec_uid=MS4wLjABAAAAF5ZfVgdRbJ3OPGJPMFHnDp2sdJaemZo3Aw6piEtkdOA×tamp=1588236354&utm_source=copy&utm_campaign=client_share&utm_medium=android&share_app_name=douyin),右擊檢查,按下ctrl+f搜尋內容,在搜尋框內分別搜尋
連結中的6802189485015633160和6802189485015633160兩個值 ![](https://img2020.cnblogs.com/blog/1971945/202005/1971945-20200502174737380-683704657.png) 第一個值順利找到,就是我們所猜測的id值,但是搜尋第二個值卻得不到任何的返回 ![](https://img2020.cnblogs.com/blog/1971945/202005/1971945-20200502175511198-1137838961.png) 這就叫我非常苦惱了,我開始想其他的辦法來獲取到這個值,我嘗試了抓包和其他的一些方法,但是都沒有找到這個值的相關資訊。 我經過了一番思考,突然間冒出了一個想法:這個值是不是隨機生成的? 然後我做了一個小小的嘗試,我將這個值改成了隨便的一個數 ``` https://www.iesdouyin.com/share/video/6802189485015633160/?mid=18987 ``` 然而神奇的是,這個網址請求出了資料(去掉這個mid鍵不出資料,將mid隨機賦值卻可以得到資料emmm) ![](https://img2020.cnblogs.com/blog/1971945/202005/1971945-20200502180332050-859850587.png) ###提取id構造網址 經過剛剛的分析,我發現我們想要提取的資料就只有一個id值,然後再用Id值替換掉網址中的數字就可以得到相應的視訊頁面了 id是這段程式碼的最重要的部分,經過前面曲折又困難的網頁分析之後,我認為提取id只需要從網頁中用表示式提取資料就可以了,但是我沒想到的是這一步也是比較困難的 我先是在主頁右擊了檢查,然後仔細的觀察了elements裡面的元素,我發現id就儲存在elements之中 ![](https://img2020.cnblogs.com/blog/1971945/202005/1971945-20200503010755514-874632859.png) 接著我就想辦法獲得這個頁面中的id資訊,我嘗試了直接請求,發現輸出的資料中沒有Id資訊; 我又加上了請求頭,依舊沒有id值輸出(這個頁面的元素是動態載入的,雖然不能一次獲取全部Id,但是也不至於一個沒有) ;然後我想到了selenium自動化測試模組,使用webdriver開啟網址列印原始碼,可是輸出還是沒變 我查了百度的一些方法,也做了一些嘗試,發現這個頁面所做的反爬確實很難破解。於是我就只能換一條路去嘗試 在谷歌開發者工具中,點選network選項卡,重新整理介面 隨著我重新整理的操作,在XHR選項卡下出現了一個名字很奇怪的資料包 ![](https://img2020.cnblogs.com/blog/1971945/202005/1971945-20200503012002256-1041107886.png) 點選他的preview選項卡,點出他的下拉選單,這裡面儲存的正是小視訊的Id資訊。但是需要注意的是,這個包裡面只儲存了0-20序列的共21條資訊 ![](https://img2020.cnblogs.com/blog/1971945/202005/1971945-20200503012549206-2132963471.png) 但是從這個主頁中我可以知道,它一共釋出了64個短視訊,所以我推斷他還有三個資料包沒有加載出來 ---->
我滾動下拉條,觀察有沒有資料包 ![](https://img2020.cnblogs.com/blog/1971945/202005/1971945-20200503012928057-1605163260.png) 滾動到最後,驗證了我的猜想,這個頁面有四個資料包且為動態載入 好了,我們已經分析出來了這個網頁的構造,先不要管一共有幾個資料包,先以一個數據包為例提取id資訊: 複製網頁連結作為他的請求網址 ![](https://img2020.cnblogs.com/blog/1971945/202005/1971945-20200503013511178-905976613.png) 將user-agaent加到請求頭中(這個網頁必須要加請求頭,不然獲取不到資料) ![](https://img2020.cnblogs.com/blog/1971945/202005/1971945-20200503013705984-600942788.png) 請求得到資料: ``` import requests import json class Douyin: def page_num(self,max_cursor): #網址後面的隨機引數(我實在分析不出規律) random_field = 'RVb7WBAZG.rGG9zDDDoezEVW-0&dytk=a61cb3ce173fbfa0465051b2a6a9027e' #網址的主體 url = 'https://www.iesdouyin.com/web/api/v2/aweme/post/?sec_uid=MS4wLjABAAAAF5ZfVgdRbJ3OPGJPMFHnDp2sdJaemZo3Aw6piEtkdOA&count=21&max_cursor=0&aid=1128&_signature=' + random_field #請求頭 headers = { 'user-agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36', } response = requests.get(url,headers=headers).text #轉換成json資料 resp = json.loads(response) #遍歷 for data in resp["aweme_list"]: # id值 video_id = data['aweme_id'] # 視訊簡介 video_title = data['desc'] # 構造視訊網址 video_url = 'https://www.iesdouyin.com/share/video/{}/?mid=1' # 填充內容 video_douyin = video_url.format(video_id) print(video_id) print(video_title) print(video_douyin) if __name__ == '__main__': douyin = Douyin() douyin.page_num() ``` print一下i相關資訊: ![](https://img2020.cnblogs.com/blog/1971945/202005/1971945-20200503191743150-1070460534.png) 當前乍一看好像這個程式碼沒有問題,但是在執行四五次之後,會出現請求不到資料或返回False的情況,一開始我以為是ip被限制的原因,但是加上了ip池之後也是一樣的結果,後來我才發現:請求不到資料是因為之前請求的url被禁用了,要在抖音詳情頁重新整理一下,再把新的資料包的網址複製過來才能重新得到資料(我也不知道這是什麼型別的反爬,希望知道的老哥可以告訴我一下) *我之所以把這個網址分成兩部分來寫,是因為當網址請求不到資料的時候,改變的是末尾的_signature=random_field欄位,在請求不到資料的時候只需重新複製一下這個欄位就可以了,會簡化一點點程式碼 ![](https://img2020.cnblogs.com/blog/1971945/202005/1971945-20200503192656646-1835755350.png) ###拼接資料包連結 上面提取Id的時候講到,我們先拿一個數據包做例子,但是我們要爬的這個使用者的全部視訊,所以就要將它所有的資料包地址都訪問一遍 想要得到這些資料包的地址,就需要分析他們的網址構造,我把這四個資料包的網址全部複製到記事本中,逐個分析他們構造規律 ![](https://img2020.cnblogs.com/blog/1971945/202005/1971945-20200503152501454-319874904.png) 不難看出,這四個資料包的區別就在於max_cursor後面的值的不同,而這個值正好就包包含在它前一個數據包之中,這就說明我們可以從前一個數據包中提取到下一個資料包的max_curso值,從而構造出下一個資料包的連結地址 ![](https://img2020.cnblogs.com/blog/1971945/202005/1971945-20200503185023484-530839734.png) 可是第四個資料包也包含著max_cursor的值,我們該在何時停止構造下一個資料包呢? 我把最後一個數據包的max_cursor值複製下來並替換到構造的資料包連結中,發現可以跳轉到一個新的網址,這個網址中也有max_cursor的值,但是這個值為0 ![](https://img2020.cnblogs.com/blog/1971945/202005/1971945-20200503185429433-2074713247.png) 也可以多找幾個網址測試,最後的結果指向都是0,所以我們可以通過if語句來判斷這個值為0的時候就終止迴圈 構造網址程式碼: ``` import requests import json class Douyin: def page_num(self,max_cursor): #網址後面的隨機引數(我實在分析不出規律) random_field = 'pN099BAV-oInkBpv2D3.M6TdPe&dytk=a61cb3ce173fbfa0465051b2a6a9027e' #網址的主體 url = 'https://www.iesdouyin.com/web/api/v2/aweme/post/?sec_uid=MS4wLjABAAAAF5ZfVgdRbJ3OPGJPMFHnDp2sdJaemZo3Aw6piEtkdOA&count=21&max_cursor=' + str(max_cursor) + '&aid=1128&_signature=' + random_field #請求頭 headers = { 'user-agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36', } response = requests.get(url,headers=headers).text #轉換成json資料 resp = json.loads(response) #提取到max_cursor max_cursor = resp['max_cursor'] #判斷停止構造網址的條件 if max_cursor==0: return 1 else: print(url) douyin.page_num(max_cursor) if __name__ == '__main__': douyin = Douyin() douyin.page_num(max_cursor=0) ``` 輸出構造後的網址: ![](https://img2020.cnblogs.com/blog/1971945/202005/1971945-20200503190313868-646519991.png) ###獲取視訊地址 現在我們已經可以成功的進入獲取到視訊頁面了,複雜的網頁分析基本已經結束了,後續操作就變得簡單了 先開啟小視訊的頁面,右擊檢查,檢視元素 ![](https://img2020.cnblogs.com/blog/1971945/202005/1971945-20200503193226830-153901245.png) 經過檢查發現,這個原始碼中並沒有視訊地址,點選了一下播放按鍵,視訊的地址才會加載出來 ![](https://img2020.cnblogs.com/blog/1971945/202005/1971945-20200503193547654-788569979.png) 這個網址中只包含了一個小視訊 ![](https://img2020.cnblogs.com/blog/1971945/202005/1971945-20200503193741697-690468143.png) 看到這種動態載入的機制,我們首先就應該想到[selenium自動化測試工具](https://www.cnblogs.com/cherish-hao/p/12527274.html),步驟是先用selenium開啟視訊頁面,再點選播放按鈕,將此時已經重新整理完的網頁原始碼儲存, 再從中提取到視訊的真正網址,最後再將除錯好的webdriver設定為無介面模式 實現程式碼: ``` from selenium import webdriver from lxml import etree from selenium.webdriver.chrome.options import Options import requests import json import time class Douyin: def page_num(self,max_cursor): #網址後面的隨機引數(我實在分析不出規律) # 設定谷歌無介面瀏覽器 chrome_options = Options() chrome_options.add_argument('--headless') chrome_options.add_argument('--disable-gpu') # chromdriver地址 path = r'/home/jmhao/chromedriver' #隨機碼 random_field = 'IU4uXRAbf-iiAwnGoS-puCFOLk&dytk=a61cb3ce173fbfa0465051b2a6a9027e' #網址的主體 url = 'https://www.iesdouyin.com/web/api/v2/aweme/post/?sec_uid=MS4wLjABAAAAF5ZfVgdRbJ3OPGJPMFHnDp2sdJaemZo3Aw6piEtkdOA&count=21&max_cursor=' + str(max_cursor) + '&aid=1128&_signature=' + random_field #請求頭 headers = { 'user-agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36', } response = requests.get(url,headers=headers).text #轉換成json資料 resp = json.loads(response) #提取到max_cursor max_cursor = resp['max_cursor'] #遍歷 for data in resp["aweme_list"]: # id值 video_id = data['aweme_id'] # 視訊簡介 video_title = data['desc'] # 構造視訊網址 video_url = 'https://www.iesdouyin.com/share/video/{}/?mid=1' # 填充內容 video_douyin = video_url.format(video_id) driver = webdriver.Chrome(executable_path=path, options=chrome_options) # 開啟視訊介面 driver.get(video_douyin) # 點選播放按鈕 driver.find_element_by_class_name('play-btn').click() time.sleep(2) # 將網頁原始碼存放到變數中 information = driver.page_source # 退出 driver.quit() html = etree.HTML(information) # 提取視訊地址 video_adress = html.xpath("//video[@class='player']/@src") print(video_adress) #判斷停止構造網址的條件 if max_cursor==0: return 1 else: #否則迴圈構造網址 douyin.page_num(max_cursor) if __name__ == '__main__': douyin = Douyin() douyin.page_num(max_cursor=0) ``` 列印一下視訊的真實網址: ![](https://img2020.cnblogs.com/blog/1971945/202005/1971945-20200503195315767-1202227202.png) ###下載視訊 視訊的真實網址我們已經獲得了,接下來只剩下最後一步的操作了---->
下載視訊 視訊下載的操作就非常簡單了: ``` for i in video_adress: #請求視訊 video = requests.get(i,headers=headers).content with open('douyin/' + video_title,'wb') as f: print('正在下載:',video_title) f.write(video) ``` ##全部程式碼 ``` from selenium import webdriver from lxml import etree from selenium.webdriver.chrome.options import Options import requests import json import time class Douyin: def page_num(self,max_cursor): #網址後面的隨機引數(我實在分析不出規律) # 設定谷歌無介面瀏覽器 chrome_options = Options() chrome_options.add_argument('--headless') chrome_options.add_argument('--disable-gpu') # chromdriver地址 path = r'/home/jmhao/chromedriver' #隨機碼 random_field = '.E3AgBAdou1.AOcbGzS2IvxNwJ&dytk=a61cb3ce173fbfa0465051b2a6a9027e' #網址的主體 url = 'https://www.iesdouyin.com/web/api/v2/aweme/post/?sec_uid=MS4wLjABAAAAF5ZfVgdRbJ3OPGJPMFHnDp2sdJaemZo3Aw6piEtkdOA&count=21&max_cursor' + str(max_cursor) + '&aid=1128&_signature=' + random_field #請求頭 headers = { 'user-agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36', } response = requests.get(url,headers=headers).text #轉換成json資料 resp = json.loads(response) #提取到max_cursor max_cursor = resp['max_cursor'] #遍歷 for data in resp["aweme_list"]: # id值 video_id = data['aweme_id'] # 視訊簡介 video_title = data['desc'] # 構造視訊網址 video_url = 'https://www.iesdouyin.com/share/video/{}/?mid=1' # 填充內容 video_douyin = video_url.format(video_id) driver = webdriver.Chrome(executable_path=path, options=chrome_options) # 開啟視訊介面 driver.get(video_douyin) # 點選播放按鈕 driver.find_element_by_class_name('play-btn').click() time.sleep(2) # 將網頁原始碼存放到變數中 information = driver.page_source # 退出 driver.quit() html = etree.HTML(information) # 提取視訊地址 video_adress = html.xpath("//video[@class='player']/@src") for i in video_adress: # 請求視訊 video = requests.get(i, headers=headers).content with open('douyin/' + video_title, 'wb') as f: print('正在下載:', video_title) f.write(video) #判斷停止構造網址的條件 if max_cursor==0: return 1 else: douyin.page_num(max_cursor) return url if __name__ == '__main__': douyin = Douyin() douyin.page_num(max_cursor=0) ``` ##實現結果 ![](https://img2020.cnblogs.com/blog/1971945/202005/1971945-20200502215658591-1783821365.png) 可以看到這些視訊已經下載到本地了,我們在開啟本地資料夾看一下 ![](https://img2020.cnblogs.com/blog/1971945/202005/1971945-20200502222428289-671296036.png) 隨便開啟一個視訊檔案: ![](https://img2020.cnblogs.com/blog/1971945/202005/1971945-20200502215956257-1916123728.png) 可以播放!至此我這個問題版的抖音爬蟲就做完了 ##待解決的問題 * 如何獲取主頁中所有的id地址 + 為什麼請求的url字尾會一直變化,該怎麼破解 其實所有的問題都指向了一個地方:該怎麼獲取小視訊的id 如果大佬們有更好的方法可以獲取id值,希望大佬們可以不吝提出建議讓我把這個爬蟲的功能再完善!感謝各