Python爬蟲---爬取抖音短視訊
阿新 • • 發佈:2020-05-03
[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值,希望大佬們可以不吝提出建議讓我把這個爬蟲的功能再完善!感謝各