## ***爬蟲實戰(zhàn):如何自動化爬取bilibili視頻***
## **1.**使用的python模塊
import requests
import json
from multiprocessing import Pool
import os
from lxml import etree
import regex
爬取的網(wǎng)站:[B站](https://www.bilibili.com/)完整的代碼在最后
## 2.獲取我們所需要的的網(wǎng)站視頻url
進入某個專區(qū)
比如我現(xiàn)在進入動畫專區(qū),現(xiàn)在我想要按視頻熱度爬取

F12可以幫我們觀察網(wǎng)站隨頁數(shù)的變化
當你點擊某一頁,比如第二頁,你就會發(fā)現(xiàn)在下圖我所定位的6500ms 到7000ms區(qū)間中有一條信息
(當然時間區(qū)間不一定相同,主要是當你點擊時,會出現(xiàn)一個不合群的信息,那這一般就是ajax請求。)
這其實就ajax異步請求
我們所需要的東西都在里面

點擊這個請求
會出現(xiàn)如下界面
很明顯,這個result ,這個英文單詞意義太明顯了。
再次點擊進去

result里面一系列,我們可以觀察我們所爬取的界面
來確定是否是我們所需要的的信息
很明顯的確是我們所需要的,并且是一個json對象

到現(xiàn)在為止我們需要視頻的url,title
也就是里面的arcurl,title
也就是說我們找到了正確的請求,現(xiàn)在我們要返回到請求頭,來構(gòu)造我們的請求頭

我們只需要根據(jù)參數(shù)來構(gòu)造請求頭就可以了
其中很多參數(shù)我們并不知道是什么,但無所謂
我們只需使用控制變量法,一個一個刪除然后再次請求就可以確定哪個參數(shù)是重要的,哪個不重要
這里我就不演示了,
這些參數(shù)我們可能不明白什么意思,但里面還是有一些很明顯的參數(shù),比如page,這個就是頁數(shù),pagesize就是每頁的視頻數(shù)量


referer為防盜鏈
這樣我們就可以請求我們所需要的頁數(shù)
url='https://s.search.bilibili.com/cate/search?'
headers={
? ? 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36',
? ? 'referer': 'https://www.bilibili.com/'
}
params = {
? ? ? ? 'main_ver': 'v3',
? ? ? ? 'search_type': 'video',
? ? ? ? 'view_type': 'hot_rank',
? ? ? ? 'order': 'click',
? ? ? ? 'copy_right': '-1',
? ? ? ? 'cate_id': '24',
? ? ? ? 'page': page,
? ? ? ? 'pagesize': 20,
? ? ? ? 'jsonp': 'jsonp',
? ? ? ? 'time_from': '20200912',
? ? ? ? 'time_to': '20200919'
? ? }
? ? try:
? ? ? ? res = session.get(url, headers=headers, params=params)
? ? ? ? res.raise_for_status()
? ? except Exception as e:
? ? ? ? print(e)
json.loads()可以把字符串變?yōu)閖son
這樣我們就提取了每一頁的視頻信息
result = json.loads(res.text)['result']
? ? for info in result:
? ? ? ? ? description = info['description']
? ? ? ? ? arcurl = info['arcurl']
? ? ? ? ? bvid = info['bvid']
? ? ? ? ? tag = info['tag']
? ? ? ? ? title = info['title']
## 3.真正的爬取視頻
我們再進入某個視頻 F12一下

這里出現(xiàn)了很多后綴為m4s的請求,再根據(jù)B站視頻進度條播放的特點 :陸陸續(xù)續(xù)的加載。
可以確定后綴為m4s的請求 應該是視頻的某一片段。那么我們同樣的只需要構(gòu)造他的請求頭應該可以同樣的獲取他的視頻。

到這里他的請求參數(shù)出現(xiàn)了很大的變化,不在像之前那么簡單易讀,很多參數(shù)很難確定
所以我們換一種方式。我們可以查看他的源代碼仔細觀看

在他的網(wǎng)頁源碼里面居然就有視頻的url,那我們就不需要解析請求頭了,偷個懶直接利用xpath解析源碼好吧。
好好觀察一下,這個Scripts的位置就可以,然后我們只需要里面的json數(shù)據(jù),所以我們再次定位了一下
[20:]就是這個意思,也可以用正則表達式更簡單的來解決
然后B站的視頻和音頻是分開放的,所以需要兩個url。
? ? ? ? ? ? try:
? ? ? ? ? ? ? ? response = session.get(arcurl, headers=headers)
? ? ? ? ? ? ? ? response.raise_for_status()
? ? ? ? ? ? except Exception as e:
? ? ? ? ? ? ? ? print(e)
? ? ? ? ? ? html = etree.HTML(response.content)
? ? ? ? ? ? video_infos = html.xpath('//head/script[5]/text()')[0][20:]
? ? ? ? ? ? video_json = json.loads(video_infos)
? ? ? ? ? ? VideoURL = video_json['data']['dash']['video'][0]['baseUrl']
? ? ? ? ? ? AudioURl = video_json['data']['dash']['audio'][0]['baseUrl']
## 4.文件存放
因為文件名有一定限制,有些字符不能取,但是B站視頻標題可以有這些字符,所以我們要用正則來替換
違法字符,替換的字符隨你 ,我選擇了 and。
然后先通過os模塊創(chuàng)建文件,
再利用BilibiliDownload函數(shù)下載
re = regex.compile('[/\\:?*><|"]')
? ? ? ? ? ? dirname = re.sub(' and ', title)
? ? ? ? ? ? if not os.path.exists('E:/Bilibili/' + dirname):
? ? ? ? ? ? ? ? os.makedirs('E:/Bilibili/' + dirname)
? ? ? ? ? ? ? ? print('目錄文件創(chuàng)建成功!')
? ? ? ? ? ? # 下載視頻和音頻
? ? ? ? ? ? print('正在下載 "' + dirname + '"? 視頻····')
? ? ? ? ? ? BiliBiliDownload(homeurl=url, url=VideoURL,
? ? ? ? ? ? ? ? ? ? ? ? ? ? name='E:/Bilibili' + '/' + dirname + '/' + dirname + '_Video_.mp4', session=session)
? ? ? ? ? ? print('正在下載 "' + dirname + '" 的音頻····')
? ? ? ? ? ? BiliBiliDownload(homeurl=url, url=AudioURl,
? ? ? ? ? ? ? ? ? ? ? ? ? ? name='E:/Bilibili' + '/' + dirname + '/' + dirname + '_Audio_.mp3',
? ? ? ? ? ? ? ? ? ? ? ? ? ? session=session)
def BiliBiliDownload(url, name, session):
? ? session.options(url=url, headers=headers,verify=False)
? ? # 每次下載1M的數(shù)據(jù)
? ? begin = 0
? ? end = 1024*512-1
? ? flag=0
? ? while True:
? ? ? ? headers.update({'Range': 'bytes='+str(begin) + '-' + str(end)})
? ? ? ? res = session.get(url=url, headers=headers,verify=False)
? ? ? ? if res.status_code != 416:
? ? ? ? ? ? begin = end + 1
? ? ? ? ? ? end = end + 1024*512
? ? ? ? else:
? ? ? ? ? ? headers.update({'Range': str(end + 1) + '-'})
? ? ? ? ? ? res = session.get(url=url, headers=headers,verify=False)
? ? ? ? ? ? flag=1
? ? ? ? with open(name, 'ab') as fp:
? ? ? ? ? ? fp.write(res.content)
? ? ? ? if flag==1:
? ? ? ? ? ? break
我最后使用了多進程,進程池加快了下載速度
## 5.代碼
import requests
import json
from multiprocessing import Pool
import os
from lxml import etree
import regex
headers={
? ? 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36',
? ? 'referer': 'https://www.bilibili.com/'
}
def BiliBiliDownload(homeurl,url, name, session=requests.session()):
? ? session.options(url=url, headers=headers,verify=False)
? ? # 每次下載1M的數(shù)據(jù)
? ? begin = 0
? ? end = 1024*512-1
? ? flag=0
? ? while True:
? ? ? ? headers.update({'Range': 'bytes='+str(begin) + '-' + str(end)})
? ? ? ? res = session.get(url=url, headers=headers,verify=False)
? ? ? ? if res.status_code != 416:
? ? ? ? ? ? begin = end + 1
? ? ? ? ? ? end = end + 1024*512
? ? ? ? else:
? ? ? ? ? ? headers.update({'Range': str(end + 1) + '-'})
? ? ? ? ? ? res = session.get(url=url, headers=headers,verify=False)
? ? ? ? ? ? flag=1
? ? ? ? with open(name, 'ab') as fp:
? ? ? ? ? ? fp.write(res.content)
? ? ? ? if flag==1:
? ? ? ? ? ? break
def get_Video_url(session,url,page):
? ? params = {
? ? ? ? 'main_ver': 'v3',
? ? ? ? 'search_type': 'video',
? ? ? ? 'view_type': 'hot_rank',
? ? ? ? 'order': 'click',
? ? ? ? 'copy_right': '-1',
? ? ? ? 'cate_id': '24',
? ? ? ? 'page': page,
? ? ? ? 'pagesize': 20,
? ? ? ? 'jsonp': 'jsonp',
? ? ? ? 'time_from': '20200912',
? ? ? ? 'time_to': '20200919'
? ? }
? ? try:
? ? ? ? res = session.get(url, headers=headers, params=params)
? ? ? ? res.raise_for_status()
? ? except Exception as e:
? ? ? ? print(e)
? ? result = json.loads(res.text)['result']
? ? for info in result:
? ? ? ? if int(info['play']) < 50000:
? ? ? ? ? ? description = info['description']
? ? ? ? ? ? arcurl = info['arcurl']
? ? ? ? ? ? bvid = info['bvid']
? ? ? ? ? ? tag = info['tag']
? ? ? ? ? ? title = info['title']
? ? ? ? ? ? try:
? ? ? ? ? ? ? ? response = session.get(arcurl, headers=headers)
? ? ? ? ? ? ? ? response.raise_for_status()
? ? ? ? ? ? except Exception as e:
? ? ? ? ? ? ? ? print(e)
? ? ? ? ? ? html = etree.HTML(response.content)
? ? ? ? ? ? video_infos = html.xpath('//head/script[5]/text()')[0][20:]
? ? ? ? ? ? video_json = json.loads(video_infos)
? ? ? ? ? ? VideoURL = video_json['data']['dash']['video'][0]['baseUrl']
? ? ? ? ? ? AudioURl = video_json['data']['dash']['audio'][0]['baseUrl']
? ? ? ? ? ? # 獲取文件夾的名稱
? ? ? ? ? ? re = regex.compile('[/\\:?*><|"]')
? ? ? ? ? ? dirname = re.sub(' and ', title)
? ? ? ? ? ? if not os.path.exists('E:/Bilibili/' + dirname):
? ? ? ? ? ? ? ? os.makedirs('E:/Bilibili/' + dirname)
? ? ? ? ? ? ? ? print('目錄文件創(chuàng)建成功!')
? ? ? ? ? ? # 下載視頻和音頻
? ? ? ? ? ? print('正在下載 "' + dirname + '"? 視頻····')
? ? ? ? ? ? BiliBiliDownload(homeurl=url, url=VideoURL,
? ? ? ? ? ? ? ? ? ? ? ? ? ? name='E:/Bilibili' + '/' + dirname + '/' + dirname + '_Video_.mp4', session=session)
? ? ? ? ? ? print('正在下載 "' + dirname + '" 的音頻····')
? ? ? ? ? ? BiliBiliDownload(homeurl=url, url=AudioURl,
? ? ? ? ? ? ? ? ? ? ? ? ? ? name='E:/Bilibili' + '/' + dirname + '/' + dirname + '_Audio_.mp3',
? ? ? ? ? ? ? ? ? ? ? ? ? ? session=session)
if __name__ == '__main__':
? ? session=requests.session()
? ? url='https://s.search.bilibili.com/cate/search?'
? ? pool=Pool(8)
? ? for i in range(5):
? ? ? ? pool.apply_async(get_Video_url,(session,url,i+1))
? ? pool.close()
? ? pool.join()