1. 程式人生 > 其它 >Py爬取b站視訊教程

Py爬取b站視訊教程

​前言:我是一個爬蟲萌新,所以這裡面有一些錯誤的歡迎指正.
本教程面向有一定Python基礎的人.
1.爬取普通的視訊.
首先,我們先來解析一下的網址,看看能不能直接獲取啥資訊.
我們先開啟視訊原始碼.如圖所示,我用的edge.

由於網址一般對應url,所以我們搜一下url.
當你搜到第4個的時候你應該會注意到什麼------
沒錯,這是一串json.這個baseUrl屬於video的子項.也就是視訊的子項.音訊的json也是這種結構,只不過將video換成了audio.
順便提一句,b站的audio和video的url是分開的.所以我們需要分別獲取對應的url.
再繼續解析,我們可以獲取如下資訊:
1.音訊和視訊都處於data子項下的dash子項,並且都是一個數組,且baseurl都存在陣列的第1個元素.
2.可以看到,整串程式碼是這樣的結構:

1 <script>
2 window.__playinfo__={
3     //json
4 }
5 </script>

那我們可以通過這串url獲得視訊所在的地址嗎?試一下

啊這......
是的,你沒有看錯,就是403.
還好不是404......
既然是403,那就意味著檔案存在,只不過我們沒有許可權訪問罷了.
這時候,我們得想辦法偽造許可權.正好,我們有python.(假設你已經裝了requests庫)
那麼,我們能不能通過偽造headers讓他請求成功而不顯示403呢?
我們可以嘗試一下---

1 headers={
2   "User-Agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36
", 3 "referer": "https://message.bilibili.com/"#這段程式碼的意思是你從哪兒獲得這個網址的(換一句話講,誰推薦你去訪問這個網址的).有了它就能夠正常訪問. 4 }

然後通過re來獲取json並且載入它,然後獲取音訊和視訊---

1 import re
2 
3 #htmltxt為你之前通過requests.get()獲得的html程式碼.
4 r=re.findall(r'<script>window.__playinfo__=(.*?)</script>',htmltxt)[0]
5 #之前我們講過這串json所在的程式碼的結構,現在我們可以用正則分析了.(.*?):所有字元且匹配一次
6 js=json.load(r) 7 8 audiourl=js["data"]["dash"]["audio"][0]["baseUrl"] 9 videourl=js["data"]["dash"]["video"][0]["baseUrl"]

之後我們還要下載視訊---

 1 import requests
 2 import io
 3 
 4 headers={
 5   "User-Agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
 6    "referer": "https://message.bilibili.com/"#這段程式碼的意思是你從哪兒獲得這個網址的(換一句話講,誰推薦你去訪問這個網址的).有了它就能夠正常訪問.
 7 }
 8 
 9 res=requests.get(url=audiourl,headers=headers)
10 with open("你的音訊名字.mp3","wb") as f:
11   f.write(res.content)
12 
13 res=requests.get(url=videourl,headers=headers)
14 with open("你的視訊名字.mp4","wb") as f:
15   f.write(res.content)

所以總合起來就是這樣的程式碼---

 1 import requests
 2 import re
 3 
 4 headers={
 5   "User-Agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
 6    "referer": "https://message.bilibili.com/"#這段程式碼的意思是你從哪兒獲得這個網址的(換一句話講,誰推薦你去訪問這個網址的).有了它就能夠正常訪問.
 7 }
 8 
 9 def get_url_html(url):
10   req=requests.get(url,headers=headers)
11   htmltext=req.text
12   get_json(htmltext)
13   
14 def get_json(htmltxt):
15   r=re.findall(r'<script>window.__playinfo__=(.*?)</script>',htmltxt)[0]
16   js=json.load(r)
17   audiourl=js["data"]["dash"]["audio"][0]["baseUrl"]
18   videourl=js["data"]["dash"]["video"][0]["baseUrl"]
19   download(audiourl,videourl)
20   
21 def download(audiourl,videourl):
22   res=requests.get(url=audiourl,headers=headers)
23   with open("你的音訊名字.mp3","wb") as f:
24     f.write(res.content)
25 
26   res=requests.get(url=videourl,headers=headers)
27   with open("你的視訊名字.mp4","wb") as f:
28     f.write(res.content)
29     
30 if __name__=="__main__":
31   get_url_html(url) #url對應網址

ok,現在你可以去嘗試一下,將url換成對應b站視訊網址,看看可不可以下載.
--END--

2.分頁視訊下載
有很多時候,視訊並非只有1集,而是有很多集.
那麼遇見這種情況我們不可能執行n遍程式然後分別輸入網址進行下載吧
那麼,遇到這種情況就沒辦法了嗎?
當然有.

我們隨意進入一個有很多集視訊的網站,然後隨意切換視訊的集數,觀察網址的變化.

通過多次實踐,我們可以得出結論:
視訊某一集對應的url:www.bilibili.com/video/bv號?p=集數
那麼我們可不可以改動我們之前寫的程式讓他支援下載網址中的所有視訊呢?
當然可以.
我們可以通過獲得視訊的集數,然後通過for i in range(0,集數)來依次獲得視訊.

我們首先翻開視訊的原始碼.
然後我們輸入隨意一個視訊的標題搜尋.

ok,搜尋到了.存在part子項裡面.
然後我們再來解析一下這個json,可以獲取以下資訊:
1.pages是一個數組,裡面存放著視訊的資訊.
2.pages位於videoData中.也就是:

1 {
2   "videoData":{
3     "pages":[
4       {...}
5      ]
6    }
7  }

既然搜尋到了,那我們還是沿用原來的方法,查詢這個子項所對應的整個json及其程式碼.

我們可以看到,這個script跟之前不一樣了,不是window.__playinfo__,而是window.__INITIAL_STATE__.
然後我們再去看這個json的末尾.

哦不!這串程式碼的格式和之前也有點不一樣,因為這後面有一串小尾巴,而它不屬於json格式型別,故整個script程式碼塊是無法通過json.loads()來載入的.
怎麼辦呢?

在上面的教程中,我們用了正則表示式.那這兒我們也可以用正則表示式來篩選.
由於最後面的小尾巴不能包括,所以我們的正則表示式可以是這樣的:

1 r'<script>window.__INITIAL_STATE__=(.*?);\(function\(\)\{var s;\(s=document.currentScript\|\|document.scripts\[document.scripts.length-1\]\).parentNode.removeChild\(s\)\;}\(\)\);</script>'

ps:**注意轉義.例如"|" "()" "{}"等字元需要轉義.**
之後,我們可以考慮新增一個函式用來獲取json:

1 def getpagejson(txt): #txt:對應b站網頁原始碼
2     r=re.findall("<script>window.__INITIAL_STATE__=(.*?);\(function\(\)\{var s;\(s=document.currentScript\|\|document.scripts\[document.scripts.length-1\]\).parentNode.removeChild\(s\)\;}\(\)\);</script>",txt)[0]#獲得json
3     js=json.loads(r)#載入json
4     p=js["videoData"]["pages"]
5     return p

ok,我們把解析json的函式也改一下---

 1 def get_json(htmltxt):
 2     collections=[]
 3      r=re.findall(r'<script>window.__playinfo__=(.*?)</script>',htmltxt)[0]
 4       js=json.loads(r)
 5       jsobj=getpagejson(htmltxt)#獲得json
 6       page=jsobj["videodata"]["pages"]#獲取pages陣列
 7       pagecount=len(page)#通過len獲取陣列長度
 8       for i in range(0,pagecount):
 9           audiourl=js["data"]["dash"]["audio"][0]["baseUrl"]
10           videourl=js["data"]["dash"]["video"][0]["baseUrl"]
11           name=page[i]["part"]
12           collections.append([audiourl,videiurl,name])
13       download(collections)

順便我們改一下下載的函式:

1 def download(collection):
2   for i in collection):#遍歷集合
3       res=requests.get(i[0],headers=headers)
4       with open(i[2]+" - audio,mp3","wb") as f:
5          f.write(res.content)
6     res=requests.get(i[1],headers=headers)
7       with open(i[2]+" - video.mp4","wb") as f:
8         f.write(res.content)

總程式碼--

 1 import requests
 2 import re
 3 
 4 headers={
 5   "User-Agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
 6    "referer": "https://message.bilibili.com/"#這段程式碼的意思是你從哪兒獲得這個網址的(換一句話講,誰推薦你去訪問這個網址的).有了它就能夠正常訪問.
 7 }
 8 
 9 def get_url_html(url):
10   req=requests.get(url,headers=headers)
11   htmltext=req.text
12   get_json(htmltext)
13  
14 def getpagejson(txt): #txt:對應b站網頁原始碼
15     r=re.findall("<script>window.__INITIAL_STATE__=(.*?);\(function\(\)\{var s;\(s=document.currentScript\|\|document.scripts\[document.scripts.length-1\]\).parentNode.removeChild\(s\)\;}\(\)\);</script>",txt)[0]#獲得json
16     js=json.loads(r)#載入json
17     p=js["videoData"]["pages"]
18     return p
19     
20 def get_json(htmltxt):
21     collections=[]
22      r=re.findall(r'<script>window.__playinfo__=(.*?)</script>',htmltxt)[0]
23       js=json.loads(r)
24       jsobj=getpagejson(htmltxt)#獲得json
25       page=jsobj["videodata"]["pages"]#獲取pages陣列
26       pagecount=len(page)#通過len獲取陣列長度
27       for i in range(0,pagecount):
28           audiourl=js["data"]["dash"]["audio"][0]["baseUrl"]
29           videourl=js["data"]["dash"]["video"][0]["baseUrl"]
30           name=page[i]["part"]
31           collections.append([audiourl,videiurl,name])
32       download(collections)
33   
34 def download(collection):
35   for i in collection):#遍歷集合
36       res=requests.get(i[0],headers=headers)
37       with open(i[2]+" - audio,mp3","wb") as f:
38          f.write(res.content)
39     res=requests.get(i[1],headers=headers)
40       with open(i[2]+" - video.mp4","wb") as f:
41         f.write(res.content)
42     
43 if __name__=="__main__":
44   get_url_html(url) #url對應網址

--END--