
?
日常跳轉:
-
- 匯入與簡介
-
- 方法一
-
- 分析與主要代碼:
- 代碼
-
- 關于代碼的一些注解:
- 方法二
-
- 分析與主要代碼:
- 代碼
-
- 關于代碼的一些注解:
- 方法三
-
- 分析與主要代碼:
- 代碼
- 全部原始碼
- BIG福利
很多人學習python,不知道從何學起,
很多人學習python,掌握了基本語法過后,不知道在哪里尋找案例上手,
很多已經做案例的人,卻不知道如何去學習更加高深的知識,
那么針對這三類人,我給大家提供一個好的學習平臺,免費領取視頻教程,電子書籍,以及課程的源代碼!??¤
QQ群:623406465
匯入與簡介
轉載請標明作者和原文鏈接!!!
CSDN個人主頁: 高智商白癡
原文地址: https://blog.csdn.net/qq_44700693/article/details/108828909
規則更新日期: 2020-9-27
說起B站,肯定人人都知道吧,B站的反扒機制并不是太嚴格,所以今天我準備給大家說說我能想到的幾種方式,目前大概想到了三種方式:
- 1、模擬手機端請求,視頻鏈接就添加在原始碼中,(最簡單、但清晰度不好)
- 2、通過呼叫別人的介面來下載視頻,(根據介面的破解難度而定,可選擇清晰度,不過最高的清晰度僅為未登錄時能觀看的最大清晰度)
- 3、直接通過B站的網頁版來抓取,(難度稍大,不過清晰度很好,有大會員的話,能下載4K視頻)
那么接下來我就來依次給大家介紹介紹我的方法!
方法一
為了方便分析,先拿一個鏈接作為測驗:
https://www.bilibili.com/video/BV1R54y1e7J3?spm_id_from=333.5.b_646f7567615f6d6164.4
分析與主要代碼:
既然方法一已經確定是模擬手機端的方式去請求,那么我們就直接開始分析:
對該鏈接進行抓包,找了半天,并沒有找到什么有用的資訊,所以我就直接去查看網頁原始碼:
?
在仔細地查看原始碼后,就發現了如上所示的資訊,視頻的 MP4 鏈接和視頻名就加載在原始碼之中,
因為這些資訊都儲存在 <script>…</script> 標簽中,雖然能夠將所有內容提取出來,再轉換為 JSON 格式進行提取,但是這樣的話就顯得有些麻煩了,我們直接用正則運算式來提取:
代碼
class BiLiBiLi_phone():
def __init__(self,s_url):
self.url=s_url
self.headers={
'origin': 'https://m.bilibili.com',
'referer': self.url,
'user-agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N)'
}
def bili_Download(self):
r = requests.get(self.url, proxies=proxy, headers=self.headers)
video_name=re.findall(',"title":"(.*?)","pubdate":', r.text)[0]
if video_name == '':
video_name = int(random.random() * 2 * 1000)
if len(str(video_name)) > 20:
video_name = video_name[:20]
video_url = re.findall(',"url":"(.*?)","backup_url"', r.text)[0].encode('utf-8').decode('unicode_escape')
r=requests.get(video_url,proxies=proxy,headers=self.headers)
with open(path+video_name+'.mp4','wb')as f:
f.write(r.content)
print("【BiLiBiLi】: {} 下載完成!".format(video_name))
關于代碼的一些注解:
- video_url = re.findall(’,“url”:"(.*?)",“backup_url”’, r.text)[0] .encode(‘utf-8’).decode(‘unicode_escape’)
標黃字體之前的代碼為正則運算式的基本操作,而標黃的字體的原因是:
從原始碼中提取的到的鏈接為:
http:\u002F\u002Fupos-sz-mirrorkodo.bilivideo.com \u002F upgcxcode \u002F 10 \u002F 14 \u002F 230501410 \u002F 230501410-1-16.mp4?e=ig8euxZM2rNcNbdlhoNvNC8BqJIzNbfq9rVEuxTEnE8L5F6VnEsSTx0vkX8fqJeYTj_lta53NCM=&uipk=5&nbs=1&deadline=1601216024&gen=playurl&os=kodobv&oi=1971851232&trid=fcde238782674b78bf4425427c2a9ea3h&platform=html5&upsig=b98cc40700e7f05e614acf0acbd9b671&uparams=e,uipk,nbs,deadline,gen,os,oi,trid,platform&mid=262968904&logo=80000000
鏈接中包含了大量的 \u002F 欄位,這是因為原始碼中加載的是轉換為 Unicode 編碼后的鏈接,所以要進行編碼轉化,?
方法二
為了方便分析,我還是拿之前的鏈接來作為測驗:
https://www.bilibili.com/video/BV1R54y1e7J3?spm_id_from=333.5.b_646f7567615f6d6164.4
分析與主要代碼:
對于方法二,我們首先需要找到一個第三方的網站來決議視頻,然后將整個程序進行包裝,
不同的網站有不同的決議方式,我這里只寫出我隨便選擇的一個網站,清晰度就還行吧,
介于這個網站的特殊性:當輸入鏈接為:
https://www.bilibili.com/video/BV1R54y1e7J3?spm_id_from=333.5.b_646f7567615f6d6164.4 時會出現以下報錯?
所以需要將后面的部分資訊去掉!
將準備好的鏈接放到決議網站可以得到以下資訊:
?
由于網站的特殊性,若選取 MP4檔案 ,有時會出現視頻分成多個的情況,所以我在這里主要選取 FLV檔案 ,
?
我們可以很清楚的看到,該介面回傳的內容中,是一些屬于 HTML標簽 的資訊,在這里不撰寫清晰度選擇的代碼,有需要的可以自行撰寫,直接選取清晰度最好的一個來決議,
代碼
這是為了防止這個 B 站視頻決議服務網站被濫用,在這里我對該決議網站進行了隱藏,想要使用這個決議服務的地址,可以私信我,
這種決議網站的一種特點就是,知道的人越多,它失效的也就越快,
希望這樣,它可以盡量活得久一點點,
class BiLiBiLi_api(): def __init__(self, s_url): self.url = s_url.split('?')[0] self.header1 = { 'Host': 'www.****.com', 'Origin': 'http://www.****.com', 'Referer': quote('http://www.****.com/video?url={}&page=video&submit=視頻下載'.format(self.url)), 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36 Edg/85.0.564.63' } self.data = { 'url': self.url, 'format': 'flv', 'from': 'parse', 'retry': '1' } self.header2 = { 'origin': 'https://www.bilibili.com/', 'referer': self.url, 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36 Edg/85.0.564.63' } def BL_api_Download(self): r = requests.post('http://www.****.com/parse', proxies=proxy, data=https://www.cnblogs.com/sn8888/p/self.data, headers=self.header1) video_name = re.findall('data-clipboard-text="(.*?)"', r.json()['msg'])[0] video_url = re.findall('href="https://www.cnblogs.com/sn8888/p/(.*?)"', r.json()['msg'])[0].replace('amp;', '') r1 = requests.get(video_url, proxies=proxy, headers=self.header2) with open(path + video_name + '.flv', 'wb')as f: f.write(r1.content) print("【BiLiBiLi】: {} 下載完成!".format(video_name))
關于代碼的一些注解:
- 初始化中為什么含有兩個請求頭資訊,
1、header1 的請求頭資訊為決議網站時所需要的一些資訊,
2、header2 的請求頭資訊為下載視頻時所需要的一些資訊,
請求頭為什么要分開寫成兩個:
第一個請求頭所需的資訊就不用多說,都是常規操作,第二個請求頭所需是資訊時因為該網站所決議出的視頻鏈接為B站中的原鏈接,所以要帶上關于B站資訊的請求頭來進行下載,否則服務器將會 拒絕 我們訪問,
- data資料是什么:
在上述決議網站的操作程序中我們還記得,在請求完決議鏈接后,仍然需要選擇視頻檔案的格式,我們才能得到視頻的鏈接,而當我們選擇完格式以后,會再次對原鏈接進行請求,并且會攜帶上固定格式的data資料,
- quote(‘http://www.****.com/video?url={}&page=video&submit=視頻下載’.format(self.url))
為什么要進行編碼轉換:
網站就是這樣,不更換編碼,它要報錯,嘿嘿嘿,
- video_url = re.findall(‘href="https://www.cnblogs.com/sn8888/p/(.*?)"’, r.json()[‘msg’])[0] .replace(‘amp;’, ‘’)
同之前所說的,未標黃的部分也還是基本操作,就是利用正則運算式來提取資訊,而對于所標黃的部分,這是因為所決議到的鏈接中含有 HTML的轉移字符:
http://cn-cq-gd-bcache-15.bilivideo.com/upgcxcode/10/14/230501410/230501410-1-80.flv?e=ig8euxZM2rNcNbu1hbUVhoMahWNBhwdEto8g5X10ugNcXBlqNxHxNEVE5XREto8KqJZHUa6m5J0SqE85tZvEuENvNC8xNEVE9EKE9IMvXBvE2ENvNCImNEVEK9GVqJIwqa80WXIekXRE9IMvXBvEuENvNCImNEVEua6m2jIxux0CkF6s2JZv5x0DQJZY2F8SkXKE9IB5QK &deadline=1601220310 & gen=playurl & nbs=1 & oi=1696943910 & os=bcache & platform=pc &trid=380e02a6015c4f6c89df5944e35a87a8 & uipk=5 & upsig=062c2af07c4454f8641dc7552b1c1f3e &uparams=e,deadline,gen,nbs,oi,os,platform,trid,uipk & mid=0?
方法三
對于方法三,既然選擇直接去官網直接爬取,就需要分析網站的請求資訊:
我們依然拿之前的鏈接來做測驗:
https://www.bilibili.com/video/BV1R54y1e7J3?spm_id_from=333.5.b_646f7567615f6d6164.4
分析與主要代碼:
打開網頁進行抓包可以看到,對于視頻播放時的資料請求為兩種形式:
?
- 1、…230501410-1-30080.m4s?..
- 2、…230501410-1-30280.m4s?..
這種情況也很常見,網站將音頻和視頻分隔開,分別進行請求得到我們所看到的視頻,
但是!要怎么分辨誰是誰呢?
我們都知道視頻所占位元組比音頻多,所以答案就是誰大誰就是視頻的請求鏈接,我們對兩個鏈接線進行嘗試性的請求:
import requests path = './Spider' url='https://upos-sz-mirrorhw.bilivideo.com/upgcxcode/10/14/230501410/230501410-1-30280.m4s?e=ig8euxZM2rNcNbdlhoNvNC8BqJIzNbfqXBvEqxTEto8BTrNvN0GvT90W5JZMkX_YN0MvXg8gNEV4NC8xNEV4N03eN0B5tZlqNxTEto8BTrNvNeZVuJ10Kj_g2UB02J0mN0B5tZlqNCNEto8BTrNvNC7MTX502C8f2jmMQJ6mqF2fka1mqx6gqj0eN0B599M=&uipk=5&nbs=1&deadline=1601485367&gen=playurl&os=hwbv&oi=1971851232&trid=e3eae34ab9ba4a579931f56205e7e749u&platform=pc&upsig=0ae578040544b08da62d89e843ff92be&uparams=e,uipk,nbs,deadline,gen,os,oi,trid,platform&mid=45803217&orderid=0,3&agrr=0&logo=80000000' headers={ 'origin': 'https://www.bilibili.com/', 'range': 'bytes=0-1198620', 'referer': 'https://www.bilibili.com/video/BV1R54y1e7J3', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36 Edg/85.0.564.63' } r=requests.get(url,headers=headers) with open(path+"123.flv","wb")as f: f.write(r.content)
經過反復的嘗試我發現請求頭中的 range 引數是必不可少的,當前請求的 range 的范圍為998814-1198620,但是因為只下載中間的片段,播放器不能識別出編碼格式并解碼,所以我將首位置修改成了 0,
實驗證明我們的確能夠得出這兩個請求鏈接一個為音頻一個為視頻,并且鏈接具有時效性,
至于音頻和視頻的系啊在格式都設為 flv ,當該鏈接為音頻時播放是沒有畫面的,
而如果想要下載整個視頻或整個音頻, range 的右值怎么設定呢?
方法有多種,我再這里把我能夠想到的寫出來:
- 1、先用一個小范圍的 range 來請求鏈接,可以從回傳的資訊中的 Content-Length 欄位得到下一次的請求范圍,然后一直發送不同大小的請求,直到請求完成,
- 2、直接將 range 的范圍定為 0-一個足夠大的數,怎樣才算足夠大呢?只要大小大于或等于視頻或音頻的位元組數就好,
- 3、這第三種就是我要使用的一種,因為它特別簡單:直接將 range 的范圍定為 0-(注意是數字 0 和一個 - ),則鏈接就會回傳從0開始到結束時的資料,
如果還有什么新的方法,歡迎留言分享!
但是!就算知道了誰是誰,怎么請求,請求的鏈接又從哪里來呢?
我翻遍了請求鏈接都沒有找到有用的資訊,直到我查看到網頁的原始碼:
?
通過鏈接的部分資訊我找到了在原始碼中一些鏈接,可以大致看出鏈接及資訊儲存在 JSON 資料,我們將它格式化后得到以下頁面:
?
為了后期方面添加選擇清晰度的功能,所以這里我會通過對 JSON 資料
的操作來獲取資料,對應著最高清晰度,我們直接獲取 video欄位 中的第一條鏈接,也就是當前的最高清晰度:
代碼
class BiLiBiLi(): def __init__(self, s_url): self.url = s_url self.header = { 'Range': 'bytes=0-', 'referer': self.url, 'origin': 'https://www.bilibili.com/', # 'cookie':'填寫自己的B站大會員cookie', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36 Edg/85.0.564.63' } def BL_download(self): html = requests.get(self.url, headers=self.header).text json_data = re.findall('window.__playinfo__=(.*?)</script>', html)[0] video_name=re.findall(',"title":"(.*?)","', html)[0] if video_name == '': video_name = int(random.random() * 2 * 1000) if len(str(video_name)) > 20: video_name = video_name[:20] video = json.loads(json_data)['data']['dash']['video'][0]['baseUrl'] self.download(video,path+video_name+'.flv') print("【BiLiBiLi】: {} 視頻下載完成!".format(video_name)) audio = json.loads(json_data)['data']['dash']['audio'][0]['baseUrl'] self.download(audio, path + video_name + '.mp3') print("【BiLiBiLi】: {} 音頻下載完成!".format(video_name)) def download(self,url,rel_path): r = requests.get(url, headers=self.header) with open(rel_path, 'wb')as f: f.write(r.content)
如果你有B站大會員的話,可以填寫自己的 cookie ,支持下載 4K 視頻,親測有效!!!
全部原始碼
import random import re from urllib.parse import quote import requests url = 'https://ip.jiangxianli.com/api/proxy_ip' r = requests.get(url) proxy = {'HTTP': 'http://' + r.json()['data']['ip'] + ':' + r.json()['data']['port']} print(proxy) path = './Spider/' class BiLiBiLi_phone(): def __init__(self, s_url): self.url = s_url self.headers = { 'origin': 'https://m.bilibili.com', 'referer': self.url, 'user-agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N)' } def bili_Download(self): r = requests.get(self.url, proxies=proxy, headers=self.headers) video_name = re.findall(',"title":"(.*?)","pubdate":', r.text)[0] if video_name == '': video_name = int(random.random() * 2 * 1000) if len(str(video_name)) > 20: video_name = video_name[:20] video_url = re.findall(',"url":"(.*?)","backup_url"', r.text)[0].encode('utf-8').decode('unicode_escape') r = requests.get(video_url, proxies=proxy, headers=self.headers) with open(path + video_name + '.mp4', 'wb')as f: f.write(r.content) print("【BiLiBiLi】: {} 下載完成!".format(video_name)) class BiLiBiLi_api(): def __init__(self, s_url): self.url = s_url.split('?')[0] self.header1 = { 'Host': 'www.****.com', 'Origin': 'http://www.****.com', 'Referer': quote('http://www.****.com/video?url={}&page=video&submit=視頻下載'.format(self.url)), 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36 Edg/85.0.564.63' } self.data = { 'url': self.url, 'format': 'flv', 'from': 'parse', 'retry': '1' } self.header2 = { 'origin': 'https://www.bilibili.com/', 'referer': self.url, 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36 Edg/85.0.564.63' } def BL_api_Download(self): r = requests.post('http://www.****.com/parse', proxies=proxy, data=https://www.cnblogs.com/sn8888/p/self.data, headers=self.header1) video_name = re.findall('data-clipboard-text="(.*?)"', r.json()['msg'])[0] video_url = re.findall('href="https://www.cnblogs.com/sn8888/p/(.*?)"', r.json()['msg'])[0].replace('amp;', '') r1 = requests.get(video_url, proxies=proxy, headers=self.header2) with open(path + video_name + '.flv', 'wb')as f: f.write(r1.content) print("【BiLiBiLi】: {} 下載完成!".format(video_name)) class BiLiBiLi(): def __init__(self, s_url): self.url = s_url self.header = { 'Range': 'bytes=0-', 'referer': self.url, 'origin': 'https://www.bilibili.com/', # 'cookie':'填寫自己的B站大會員cookie', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36 Edg/85.0.564.63' } def BL_download(self): html = requests.get(self.url, headers=self.header).text json_data = re.findall('window.__playinfo__=(.*?)</script>', html)[0] video_name=re.findall(',"title":"(.*?)","', html)[0] if video_name == '': video_name = int(random.random() * 2 * 1000) if len(str(video_name)) > 20: video_name = video_name[:20] video = json.loads(json_data)['data']['dash']['video'][0]['baseUrl'] self.download(video,path+video_name+'.flv') print("【BiLiBiLi】: {} 視頻下載完成!".format(video_name)) audio = json.loads(json_data)['data']['dash']['audio'][0]['baseUrl'] self.download(audio, path + video_name + '.mp3') print("【BiLiBiLi】: {} 音頻下載完成!".format(video_name)) def download(self,url,rel_path): r = requests.get(url, headers=self.header) with open(rel_path, 'wb')as f: f.write(r.content) def user_ui(): print('*' * 10 + '\t BiLiBiLi視頻下載\t' + '*' * 10) print('*' * 5 + "\t\tAuthor: 高智商白癡\t\t" + '*' * 5) share_url = input('請輸入分享鏈接: ') choice = int(input("1、模擬手機端下載 2、呼叫介面下載 3、直接下載\n選擇下載方式:")) if choice == 1: BiLiBiLi_phone(share_url).bili_Download() if choice == 2: BiLiBiLi_api(share_url).BL_api_Download() if choice == 3: BiLiBiLi(share_url).BL_download() if __name__ == '__main__': user_ui()
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/182736.html
標籤:Python



