blibli任意搜尋關鍵字,相關視訊的彈幕資料采集
參考網址:B站蔡徐坤

爬蟲邏輯:【分頁url采集】-【視訊頁面url采集】-【視訊頁面資料采集 / cid資訊 / 彈幕xml資料采集】
彈幕xml網址示例:https://comment.bilibili.com/84682646.xml(通過cid擷取彈幕的網址,後面會詳細介紹)
要求
1)函數式程式設計
函數1:get_outer_urls(n) → 【分頁網址url采集】
n:爬取頁數
結果:得到分頁網頁的list
函數2:get_inner_urls(ui,d_h,d_c) → 【視訊頁面url采集】
ui:分頁網址
d_h:user-agent資訊
d_c:cookies資訊
結果:得到一個視訊頁面的list
函數3:get_data(ui,d_h,d_c,table) → 【視訊頁面資料采集 / cid資訊 / 彈幕xml資料采集】
ui:視訊頁面網址
d_h:user-agent資訊
d_c:cookies資訊
table:mongo集合對象
2)采集字段
① 視訊頁面資料采集(标題、時間、cid)
② 彈幕資訊采集
步驟分解
步驟一、前期準備并封裝第一個函數
1)導入相關的庫,和設定代碼分開辨別
2)分析網頁的規律,檢視網頁的2-4頁(一般選取2-4就可以看出規律),網址如下:
u2 = https://search.bilibili.com/all?keyword=%E8%94%A1%E5%BE%90%E5%9D%A4&page=2
u3 = https://search.bilibili.com/all?keyword=%E8%94%A1%E5%BE%90%E5%9D%A4&page=3
u4 = https://search.bilibili.com/all?keyword=%E8%94%A1%E5%BE%90%E5%9D%A4&page=4
......
通過url分析,可知除了最後面的“page=”後面的數字随着頁面在變化外,幾乎沒有變化。比如擷取有關蔡徐坤20頁的視訊内容(每頁包含了20個視訊),建立20個url如下
urllst = []
for i in range(20):
ui = f'https://search.bilibili.com/all?keyword=%E8%94%A1%E5%BE%90%E5%9D%A4&page={i+1}'
urllst.append(ui)
輸出結果為:
3) 封裝第一個函數
def get_outer_urls(n):
'''
【分頁網址url采集】
n:爬取頁數
結果:得到分頁網頁的list
'''
urllst = []
for i in range(n):
ui = f'https://search.bilibili.com/all?keyword=%E8%94%A1%E5%BE%90%E5%9D%A4&page={i+1}'
urllst.append(ui)
return urllst
print(get_outer_urls(20))
輸出結果為:(當輸入20時,結果輸出和上面的一緻,證明封裝第一個函數完成)
步驟二、設定請求頭headers和登入資訊cookies
這一步需要使用者登入,沒有賬号的話,需要進行注冊,cookies和headers的擷取方式如下,
文字詳述: 以上面的登入後的網頁界面為例,滑鼠右鍵檢查,然後選擇右邊标簽Network,然後重新整理一下該網頁,這時候在右方Doc下面的Name菜單欄下會出現一個新的資訊,選擇第一個檔案,然後拉到底,就可以找到cookies和headers的資訊。
步驟歸納:【登入的頁面】–> 【右鍵檢查】–> 【Network】–> 【重新整理】–> 【Doc下面的Name菜單欄】–> 【點選第一個檔案下拉到底】–> 【Request Headers下】
圖示
查找到headers和cookies之後,将其寫入到字典中儲存,如下(代碼在spyder裡運作)
dic_headers = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36"}
cookies = "_uuid=3D3C1683-5F16-D3EC-36E2-5967E731F7DA81323infoc; buvid3=3EB2F2F9-8EE3-4AFE-B3D6-7620A3B2E636155823infoc; LIVE_BUVID=AUTO4015671567822432; sid=bj42fy4m; CURRENT_FNVAL=16; stardustvideo=1; rpdid=|(umYuYRkm~|0J'ulY~ul~JlY; UM_distinctid=16ce1f17c989db-0c9243ec350826-e343166-144000-16ce1f17c99a18; CURRENT_QUALITY=0; DedeUserID=38449436; DedeUserID__ckMd5=272068a4511232d7; SESSDATA=3a11597f%2C1583975698%2C7bfdfc21; bili_jct=d8d63e8aa5a2eb9adf9f30698873d271"
dic_cookies = {}
for i in cookies.split("; "):
dic_cookies[i.split("=")[0]] = i.split("=")[1]
print(dic_headers)
print(dic_cookies)
輸出結果如下:
步驟三、網頁資訊請求和網頁初解析
首先嘗試進行網頁資訊請求,代碼如下
url = 'https://search.bilibili.com/all?keyword=%E8%94%A1%E5%BE%90%E5%9D%A4'
r = requests.get(url,headers = dic_headers, cookies = dic_cookies)
print(r)
輸出結果為:<Response [200]> (說明網站資訊可以正常通路,接下來進行頁面的解析)
在網頁界面滑鼠右鍵,選擇檢查,可以發現,搜到的視訊都是在【ul】标簽下的【li】标簽裡面,如下
代碼實作視訊資訊的擷取,如下
soup = BeautifulSoup(r.text, 'lxml')
lis = soup.find('ul',class_="video-list clearfix").find_all('li')
print(lis[0])
輸出結果如下:(和第一個視訊的資訊對應上,而且輸出的内容裡面包含了标題和上傳的時間資訊)
接着就可以擷取該視訊的url,至于标題和上傳時間,可以再進入該url頁面後進行擷取,通過上面的輸出,可以發現url在【li】标簽下的【a】的href屬性裡面
li_0 = lis[0]
url_inner = li_0.a['href']
print(url_inner)
輸出的結果如下:(最後輸出的标題都是沒有https,可以在封裝函數輸出的時候加上)
至此擷取第一個視訊的url的試錯就成功了,接下來就是封裝第二個函數了
步驟四、封裝第二個函數
上面實作了單個視訊url的擷取,接下來隻需要進行周遊循環并把結果儲存到清單即可,也就是封裝第二個函數的要求,代碼如下
def get_inter_urls(ui,d_h,d_c):
'''
【視訊頁面url采集】
ui:視訊資訊網頁url
d_h:user-agent資訊
d_c:cookies資訊
結果:得到一個視訊頁面的list
'''
ri = requests.get(ui, headers = d_h, cookies = d_c)
soupi = BeautifulSoup(ri.text, 'lxml')
lis = soupi.find('ul',class_="video-list clearfix").find_all('li')
lst = []
for li in lis:
lst.append('https:' + li.a['href'])
return lst
url = 'https://search.bilibili.com/all?keyword=%E8%94%A1%E5%BE%90%E5%9D%A4'
#這裡的url就是第一頁的分頁網址
print(get_inter_urls(url,dic_headers,dic_cookies))
輸出的結果為:(封裝的第二個函數可以正常調用)
步驟五、網頁深度解析(擷取标題、時間和彈幕)并将資料存入資料庫
1) 擷取标題
和之前的網頁初解析一樣,找到标題所對應的的标簽資訊,如下
① 可以看出這裡面有個id的屬性,可以直接用來擷取标題資訊(标題資訊文本資料在其下面的【span】标簽下第一個),代碼如下
urllst = get_outer_urls(20) #擷取前20頁的網址
u1 = urllst[0] #擷取第一頁的全部20個視訊url
url_inter_1 = get_inter_urls(u1,dic_headers,dic_cookies)[0] #擷取第一個視訊的url
ri = requests.get(url_inter_1, headers = dic_headers,cookies =dic_cookies)
soupi = BeautifulSoup(ri.text, 'lxml')
title = soupi.find(id = "viewbox_report").span.text
print(title)
輸出的結果為:(和第一個視訊的标題一緻)
② 補充:也可以通過查找h1标簽,然後使用.h1[‘title’]直接擷取(較為簡單)
2) 擷取上傳時間
定位到第一個視訊的url位置,前面1)擷取标題時候已經做了,隻需要在下面輸入擷取上傳時間的代碼就可以了,從下面的标簽中可以看出,上傳時間是在【div class=‘video-data’】标簽下的【span】裡面
直接使用如下代碼看看能否擷取文本資訊
upload_time = soupi.find("div",class_ ="video-data").text
print(upload_time)
輸出的結果為:
輸出結果并不是我們希望得到的,原因是這裡面有兩個【span】标簽,可以使用.next_subling兄弟标來擷取上傳時間的文本資訊也可以使用正規表達式擷取文本資訊(find()方法是擷取找到的第一個标簽,那麼它的兄弟節點就是第二個目标标簽了)
① 使用find()方法配合兄弟節點比對文本資訊,代碼如下
upload_time = soupi.find("span", class_ = "a-crumbs").next_sibling.text
print(upload_time)
輸出的結果為:
② 使用正規表達式比對文本資訊(目标字段是以‘20’開頭的,以數字結尾),代碼如下
upload_time = re.search(r'(20.*\d)',soupi.find("div",class_ ="video-data").text)
print(upload_time.group(1))
輸出的結果為:
3) 擷取彈幕資料
① 解析一下cid
首先要從 B 站彈幕的說起,B 站視訊的 ID 名字是 cid,一個 AV (視訊)号下如果有多個分 P,就會占用多個 cid,cid 可以看做是視訊的唯一 ID,通過這個 ID ,我們可以讀取到 B 站的彈幕格式為 comment.bilibili.com/[cid].xml,在一開始給出的彈幕網址就為:https://comment.bilibili.com/84682646.xml
比如在第一個視訊的網頁界面點選滑鼠右鍵檢視源代碼,然後搜尋“cid”,就會發現相關的資訊
複制"cid":後的數字檢視一下這個資訊在“檢查”界面對應的标簽的資訊是怎麼樣的,搜尋如下,可以看出都是在【script】标簽下面
② cid 資料采集
對比“源代碼"和"檢查"頁面,發現可以很好的擷取cid的方式是通過在源代碼視窗下查找,因為這裡面的資料直接以字典的形式存儲的,而如果再"檢查"界面進行比對标簽,在來查找内容就顯着很複雜,代碼如下
cid = re.search(r'"cid":(\d*),', ri.text).group(1)
print(cid)
輸出結果如下:(由此就擷取了cid資訊)
③ 彈幕資訊采集
這裡隻需要将對應位置的數字換成cid資訊即可,然後嘗試擷取該cid對應的url下面的内容(注意亂碼的解決方式)
cid = re.search(r'"cid":(\d*),', ri.text).group(1)
cid_url = f"https://comment.bilibili.com/{cid}.xml"
r2 = requests.get(cid_url)
r2.encoding = r2.apparent_encoding
soup2 = BeautifulSoup(r2.text, 'lxml')
print(soup2)
輸出的結果為:(部分結果截圖)
檢驗能否擷取正常資料(一般一個網頁是會存放最新更新的1000條彈幕,是以可以通過清單的長度進行判斷)
dmlst = re.findall(r'<d.*?>',r2.text)
print(dmlst,len(dmlst))
輸出的結果為:
彈幕的标簽資訊全部存儲到了dmlst裡面了,接下來是提取裡面的内容,順便把之前的内容也寫入到字典裡面
for dm in dmlst:
dic = {}
dic['标題'] = title
dic['上傳時間'] = upload_time
dic['cid'] = cid
dic['彈幕内容'] = re.search(r'>(.*)<',dm).group(1)
dic['其他資訊'] = re.search(r'p="(.*)">',dm).group(1)
print(dic)
輸出結果為:
至此,就把相應的資料全部儲存在字典裡面了,接下來就是配置資料庫和封裝第三個函數了
步驟六、配置資料庫和封裝第三個函數
1) 配置資料庫
import pymongo
myclient = myclient = pymongo.MongoClient("mongodb://localhost:27017/")
db = myclient['blibli']
datatable = db['data']
上述代碼實作資料庫的建立及命名,以及存放資料表格的建立
2)封裝第三個函數,并将資料寫入到資料庫
隻需要将每次生成的dic直接插入到建立的資料表格中即可,為了可視化儲存過程,可以進行計數統計
def get_data(ui,d_h,d_c,table):
'''
ui:視訊頁面網址
d_h:user-agent資訊
d_c:cookies資訊
table:mongo集合對象
'''
ri = requests.get(url = ui, headers = d_h, cookies = d_c)
soupi = BeautifulSoup(ri.text, 'lxml')
#title = soupi.find(id = "viewbox_report").span.text
title = soupi.h1['title']
upload_time = soupi.find("span", class_ = "a-crumbs").next_sibling.text
#upload_time = re.search(r'(20.*\d)',soupi.find("div",class_ ="video-data").text)
cid = re.search(r'"cid":(\d*),', ri.text).group(1)
cid_url = f"https://comment.bilibili.com/{cid}.xml"
r2 = requests.get(cid_url)
r2.encoding = r2.apparent_encoding
dmlst = re.findall(r'<d.*?/d>',r2.text)
n = 0
for dm in dmlst:
dic = {}
dic['标題'] = title
dic['上傳時間'] = upload_time
dic['cid'] = cid
dic['彈幕内容'] = re.search(r'>(.*)<',dm).group(1)
dic['其他資訊'] = re.search(r'p="(.*)">',dm).group(1)
table.insert_one(dic)
n += 1
return n
最後的運作代碼(錯誤異常處理和可視化輸出,這裡隻以第一頁的視訊中的彈幕數為例)
urllst = get_outer_urls(20) #擷取前20頁的網址
u1 = urllst[0] #擷取第一頁的全部20個視訊url
url_inter = get_inter_urls(u1,dic_headers,dic_cookies) #擷取第一個視訊的url
errorlst =[]
count = 0
for u in url_inter:
try:
count += get_data(u,dic_headers,dic_cookies,datatable)
print('資料采集并存入成功,總共采集{}條資料'.format(count))
except:
errorlst.append(u)
print('資料采集失敗,資料網址為:',u)
輸出為:這裡調試的時候可以将第三個函數裡面的table.insert_one(dic)注釋掉,檢視是否有資料采內建功的輸出,如下
全部代碼以及輸出結果如下
import re
import requests
from bs4 import BeautifulSoup
import pymongo
def get_outer_urls(n):
'''
【分頁網址url采集】
n:爬取頁數
結果:得到分頁網頁的list
'''
urllst = []
for i in range(n):
ui = f'https://search.bilibili.com/all?keyword=%E8%94%A1%E5%BE%90%E5%9D%A4&page={i+1}'
urllst.append(ui)
return urllst
def get_inter_urls(ui,d_h,d_c):
'''
【視訊頁面url采集】
u:起始網址
d_h:user-agent資訊
d_c:cookies資訊
結果:得到一個視訊頁面的list
'''
ri = requests.get(ui, headers = d_h, cookies = d_c)
soupi = BeautifulSoup(ri.text, 'lxml')
lis = soupi.find('ul',class_="video-list clearfix").find_all('li')
lst = []
for li in lis:
lst.append('https:' + li.a['href'])
return lst
def get_data(ui,d_h,d_c,table):
'''
ui:視訊頁面網址
d_h:user-agent資訊
d_c:cookies資訊
table:mongo集合對象
'''
ri = requests.get(url = ui, headers = d_h, cookies = d_c)
soupi = BeautifulSoup(ri.text, 'lxml')
#title = soupi.find(id = "viewbox_report").span.text
title = soupi.h1['title']
upload_time = soupi.find("span", class_ = "a-crumbs").next_sibling.text
#upload_time = re.search(r'(20.*\d)',soupi.find("div",class_ ="video-data").text)
cid = re.search(r'"cid":(\d*),', ri.text).group(1)
cid_url = f"https://comment.bilibili.com/{cid}.xml"
r2 = requests.get(cid_url)
r2.encoding = r2.apparent_encoding
dmlst = re.findall(r'<d.*?/d>',r2.text)
n = 0
for dm in dmlst:
dic = {}
dic['标題'] = title
dic['上傳時間'] = upload_time
dic['cid'] = cid
dic['彈幕内容'] = re.search(r'>(.*)<',dm).group(1)
dic['其他資訊'] = re.search(r'p="(.*)">',dm).group(1)
table.insert_one(dic)
n += 1
return n
if __name__ == '__main__':
dic_headers = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36"}
cookies = "_uuid=3D3C1683-5F16-D3EC-36E2-5967E731F7DA81323infoc; buvid3=3EB2F2F9-8EE3-4AFE-B3D6-7620A3B2E636155823infoc; LIVE_BUVID=AUTO4015671567822432; sid=bj42fy4m; CURRENT_FNVAL=16; stardustvideo=1; rpdid=|(umYuYRkm~|0J'ulY~ul~JlY; UM_distinctid=16ce1f17c989db-0c9243ec350826-e343166-144000-16ce1f17c99a18; CURRENT_QUALITY=0; DedeUserID=38449436; DedeUserID__ckMd5=272068a4511232d7; SESSDATA=3a11597f%2C1583975698%2C7bfdfc21; bili_jct=d8d63e8aa5a2eb9adf9f30698873d271"
dic_cookies = {}
for i in cookies.split("; "):
dic_cookies[i.split("=")[0]] = i.split("=")[1]
urllst = get_outer_urls(20) #擷取前20頁的網址
u1 = urllst[0] #擷取第一頁的全部20個視訊url
url_inter = get_inter_urls(u1,dic_headers,dic_cookies) #擷取第一個視訊的url
myclient = myclient = pymongo.MongoClient("mongodb://localhost:27017/")
db = myclient['blibli']
datatable = db['data']
#get_data(url_inter[0],dic_headers,dic_cookies,datatable)
errorlst =[]
count = 0
for u in url_inter:
try:
count += get_data(u,dic_headers,dic_cookies,datatable)
print('資料采集并存入成功,總共采集{}條資料'.format(count))
except:
errorlst.append(u)
print('資料采集失敗,資料網址為:',u)
輸出的結果如下:
發現最後上傳動态的圖檔時間較短,沒有把最後資料寫入到資料庫的界面錄上去,又重新從把後面的補上了,如下