Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
關于雪球兄,大家應該都熟悉了,之前他寫過Python實戰文章,好評如潮,沒來得及看的小伙伴,可以戳這里了:
盤點常用驗證碼標注和識別項目線上部署三種方式——VUE前端、Java后端和Python后端部署
Python項目實戰篇——常用驗證碼標注&識別(CNN神經網絡模型訓練/測試/部署)
Python項目實戰篇——常用驗證碼標注&識別(前端+后端實現高效率數據標注)
Python項目實戰篇——常用驗證碼標注&識別(數據采集/預處理/字符圖切割)
Python項目實戰篇——常用驗證碼標注和識別(需求分析和實現思路)
之前也有給大家分享B站的一些文章,感興趣的話可以看看這個文章,Python網絡爬蟲+數據分析:手把手教你用Python網絡爬蟲獲取B站UP主10萬條數據并用Pandas庫進行趣味數據分析。
一提到B站,第一印象就是視頻,相信很多小伙伴和我一樣,都想著去利用網絡爬蟲技術獲取B站的視頻吧,但是B站視頻其實沒有那么好拿到的,關于B站的視頻獲取,之前有介紹通過you-get庫進行實現,感興趣的小伙伴可以看這篇文章:You-Get 就是這么強勢!。
言歸正傳,經常在B站上學習的小伙伴們可能經常會遇到有的博主連載幾十個,甚至幾百個視頻,尤其像這種編程語言、課程、工具使用等連續的教程,就會出現選集系列,如下圖所示。
當然這些選集的字段我們肉眼也是可以看得到的。只是通過程序來實現的話,可能真沒有想象的那么簡單。那么這篇文章的目標呢,就是通過Python網絡爬蟲技術,基于selenium庫,實現視頻選集的獲取。
這篇文章我們用的庫是selenium,這個是一個用于模擬用戶登錄的庫,雖然給人的感覺是慢,但是在網絡爬蟲領域,這個庫還是用的蠻多的,用它來模擬登錄、獲取數據屢試不爽。下面是實現視頻選集采集的所有代碼,歡迎大家親自動手實踐。
# coding: utf-8
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
class Item:
page_num = ""
part = ""
duration = ""
def __init__(self, page_num, part, duration):
self.page_num = page_num
self.part = part
self.duration = duration
def get_second(self):
str_list = self.duration.split(":")
sum = 0
for i, item in enumerate(str_list):
sum += pow(60, len(str_list) - i - 1) * int(item)
return sum
def get_bilili_page_items(url):
options = webdriver.ChromeOptions()
options.add_argument('--headless') # 設置無界面
options.add_experimental_option('excludeSwitches', ['enable-automation'])
# options.add_experimental_option("prefs", {"profile.managed_default_content_settings.images": 2,
# "profile.managed_default_content_settings.flash": 0})
browser = webdriver.Chrome(options=options)
# browser = webdriver.PhantomJS()
print("正在打開網頁...")
browser.get(url)
print("等待網頁響應...")
# 需要等一下,直到頁面加載完成
wait = WebDriverWait(browser, 10)
wait.until(EC.visibility_of_element_located((By.XPATH, '//*[@class="list-box"]/li/a')))
print("正在獲取網頁數據...")
list = browser.find_elements_by_xpath('//*[@class="list-box"]/li')
# print(list)
itemList = []
second_sum = 0
# 2.循環遍歷出每一條搜索結果的標題
for t in list:
# print("t text:",t.text)
element = t.find_element_by_tag_name('a')
# print("a text:",element.text)
arr = element.text.split('\n')
print(" ".join(arr))
item = Item(arr[0], arr[1], arr[2])
second_sum += item.get_second()
itemList.append(item)
print("總數量:", len(itemList))
# browser.page_source
print("總時長/分鐘:", round(second_sum / 60, 2))
print("總時長/小時:", round(second_sum / 3600.0, 2))
browser.close()
return itemList
get_bilili_page_items("https://www.bilibili.com/video/BV1Eb411u7Fw")
這里用到的選擇器是xpath,利用視頻示例是B站的《高等數學》同濟版 全程教學視頻(宋浩老師)視頻選集,大家如果想抓取其他視頻選集的話,只需要更改上述代碼的最后一行的URL鏈接即可。
在運行過程中小伙伴們應該會經常遇到這個問題,如下圖所示。
這個是因為谷歌驅動版本問題導致的,只需要根據提示,去下載對應的驅動版本即可,驅動下載鏈接:
https://chromedriver.storage.googleapis.com/index.html
我是Python進階者。這篇文章主要給大家介紹了B站視頻選集內容的獲取方法,基于網絡爬蟲,通過selenium庫和xpath選擇器進行實現,并且給大家例舉了常見問題的處理方法。小伙伴們,快快用實踐一下吧!如果在學習過程中,有遇到任何問題,歡迎加我好友,我拉你進Python學習交流群共同探討學習。
不久看到一篇關于BibiBili視頻地址解析的源碼,正好有興趣,就自己也研究了研究,記錄一下。
1.以http://www.bilibili.com/video/av12535537/ 此視頻為例用HttpWatch抓包,很容易就能看出視頻的地址是從https://interface.bilibili.com/playurl?quality=4&player=1&cid=20627240&ts=1504780011&sign=f37df88434579a4b009a2bee708a2a71&qn=112里獲取的
按照常例分析下參數:
cid很容易猜測出是視頻的ID號,在網頁源碼里面也能找到
?
Quality和qn是視頻的清晰度
ts則是時間戳,這個也不用多說
關鍵就是這個sign,在HttpWatch中是完全搜不到任何相關的信息的,但網頁訪問了http://static.hdslb.com/play.swf這個文件,那么sign算法很可能就在這個播放器里。
2.下載這個SWF文件后,載入AS3 Sorcerer,代碼一大片不是很好找
?
那就保存到文本里,在文本中查找”視頻地址”這個關鍵字。
?
很快就能定位到關鍵的函數LoadPreview和LoadCidVideo那么接著搜索LoadPreview和LoadCidVideo定位到函數部分,可以看到代碼如下
?
那么關鍵算法就是在getSign和getSign_v2里面了
[mw_shl_code=actionscript3,true]public function getSign(_arg_1:String):String
{
var _local_2:int;
var _local_4 = null;
var _local_7:int;
var _local_8:int;
var _local_6:int;
var _local_5:int;
var _local_3:int = ESP;
_local_2 = _local_3;
_local_3 = (_local_3 - 48);
_local_5 = 16;
_local_6 = (_local_2 - 37);
ESP = (_local_3 & -16);
_local_7 = CModule.mallocString(_arg_1);
_local_8 = _arg_1.length;
do
{
var _local_9:int = (L__2E_str2 - _local_5);
_local_9 = li8((_local_9 + 16)) /*FlasCC (Alchemy)*/ ;
_local_3 = (_local_3 - 16);
si32(_local_9, (_local_3 + 4)); //FlasCC (Alchemy)
si32(_local_6, _local_3); //FlasCC (Alchemy)
ESP = _local_3;
F_sprintf();
_local_3 = (_local_3 + 16);
_local_5 = (_local_5 + -1);
_local_6 = (_local_6 + 2);
} while (_local_5 != 0);
_local_3 = (_local_3 - 16);
_local_9 = (_local_2 - 4);
si32(_local_9, (_local_3 + 12)); //FlasCC (Alchemy)
_local_9 = (_local_2 - 37);
si32(_local_9, (_local_3 + 8)); //FlasCC (Alchemy)
si32(_local_8, (_local_3 + 4)); //FlasCC (Alchemy)
si32(_local_7, _local_3); //FlasCC (Alchemy)
ESP = _local_3;
F_get_sign();
_local_3 = (_local_3 + 16);
_local_5 = eax;
_local_9 = li32((_local_2 - 4)) /*FlasCC (Alchemy)*/ ;
_local_4 = CModule.readString(_local_5, _local_9);
if (_local_7 != 0)
{
_local_3 = (_local_3 - 16);
si32(_local_7, _local_3); //FlasCC (Alchemy)
ESP = _local_3;
F_IDAlloc();
_local_3 = (_local_3 + 16);
};
if (_local_5 != 0)
{
_local_3 = (_local_3 - 16);
si32(_local_5, _local_3); //FlasCC (Alchemy)
ESP = _local_3;
F_idalloc();
_local_3 = (_local_3 + 16);
};
var _local_10 = _local_4;
_local_3 = _local_2;
ESP = _local_3;
return (_local_10);
}
}//package com.bilibili.interfaces[/mw_shl_code]
[mw_shl_code=actionscript3,true]public function getSign_v2(_arg_1:String, _arg_2:int):String
{
var _local_12:*;
var _local_3:int;
var _local_5 = null;
var _local_6:int;
var _local_11:int;
var _local_8:int;
var _local_10:int;
var _local_9:int;
var _local_4:int = ESP;
_local_3 = _local_4;
_local_4 = (_local_4 - 48);
_local_6 = _arg_2;
if (_local_6 >= 5)
{
_local_12 = _arg_1;
}
else
{
_local_8 = (L__2E_str2 + (_local_6 << 4));
_local_9 = 16;
_local_10 = (_local_3 - 37);
ESP = (_local_4 & -16);
_local_6 = CModule.mallocString(_arg_1);
_local_11 = _arg_1.length;
do
{
var _local_7:int = (_local_8 - _local_9);
_local_7 = li8((_local_7 + 16)) /*FlasCC (Alchemy)*/ ;
_local_4 = (_local_4 - 16);
si32(_local_7, (_local_4 + 4)); //FlasCC (Alchemy)
si32(_local_10, _local_4); //FlasCC (Alchemy)
ESP = _local_4;
F_sprintf();
_local_4 = (_local_4 + 16);
_local_9 = (_local_9 + -1);
_local_10 = (_local_10 + 2);
} while (_local_9 != 0);
_local_4 = (_local_4 - 16);
_local_7 = (_local_3 - 4);
si32(_local_7, (_local_4 + 12)); //FlasCC (Alchemy)
_local_7 = (_local_3 - 37);
si32(_local_7, (_local_4 + 8)); //FlasCC (Alchemy)
si32(_local_11, (_local_4 + 4)); //FlasCC (Alchemy)
si32(_local_6, _local_4); //FlasCC (Alchemy)
ESP = _local_4;
F_get_sign();
_local_4 = (_local_4 + 16);
_local_10 = eax;
_local_7 = li32((_local_3 - 4)) /*FlasCC (Alchemy)*/ ;
_local_5 = CModule.readString(_local_10, _local_7);
if (_local_6 != 0)
{
_local_4 = (_local_4 - 16);
si32(_local_6, _local_4); //FlasCC (Alchemy)
ESP = _local_4;
F_idalloc();
_local_4 = (_local_4 + 16);
};
if (_local_10 != 0)
{
_local_4 = (_local_4 - 16);
si32(_local_10, _local_4); //FlasCC (Alchemy)
ESP = _local_4;
F_idalloc();
_local_4 = (_local_4 + 16);
};
_local_12 = _local_5;
};
_local_4 = _local_3;
ESP = _local_4;
return (_local_12);
}
}//package com.bilibili.interfaces[/mw_shl_code]
可以看到,二者代碼非常相似,而且光看代碼無法找到有用的信息,那么就得想辦法去調試了
3.目前我只知道的辦法是:使用JPEXS反編譯軟件來修改SWF代碼(插Log),然后通過Fiddler劫持替換SWF,通過顯示Log信息來進行調試?
要顯示log信息就得安裝debug版本的flash
http://www.adobe.com/support/flashplayer/debug_downloads.html
?
為方便閱讀日志信息可以安裝Cygwin
?
具體步驟我也是參考了http://blog.csdn.net/hot_vc/article/details/50600717這篇文章?
————————————————————————————————————————————————————————————————————————————
將SWF載入JPEXS
?
要想修改SWF的源碼就得去修改PCODE,第一次接觸這種也不是很懂,覺得有點類似于C#的IL代碼
通過AS3代碼和PCODE代碼的一一進行對比,還是能找到一些規律的
為了查看代碼中變量值,用到以下代碼
[mw_shl_code=javascript,true]findpropstrict Qname(PackageNamespace(""),"trace")
getlocal 5
callpropvoid Qname(PackageNamespace(""),"trace") 1[/mw_shl_code]
這個代碼就相當于trace(_loc5_);
修改保存后利用Fiddler來劫持替換我們的SWF
?
這樣Cygwin就能輸出我們想要看到的結果了。
其中有些變量的值是地址,而不是數據,為了讀取地址中的數據,利用到了這句代碼CModule.readString(地址,讀取長度);
按照原PCODE的格式,寫出插LOG時的PCODE
[mw_shl_code=asm,true]pushint 60
setlocal 8
getlex Qname(PackageNamespace("com.bilibili.interfaces"),"CModule")
getlocal 7
getlocal 8
callproperty Qname(PackageNamespace(""),"readString") 2
coerce_s
setlocal 5
findpropstrict Qname(PackageNamespace(""),"trace")
getlocal 5
callpropvoid Qname(PackageNamespace(""),"trace") 1[/mw_shl_code]
此代碼相當于
_loc8_:int = 60;
_loc5_:* = CModule.readString(_loc7_,_loc8_);
trace(_loc5_);
?
4.其實關鍵算法在F_get_sign()中,因為從F_get_sign()函數中出來后,eax中就已經存放帶著sign的視頻URL的地址了。
F_get_sign()函數過于復雜,總之通過不斷地插這兩種Log查看變量信息,最終找到了算法?
?
由這個地方可以看出是MD5,還有一個位置能夠準確地查看到進行MD5處理的數據(已經忘了.....)
算法如下:
五種清晰度
qn=16,quality=1
qn=32,quality=5
qn=48,quality=2
qn=64,quality=2
qn=80,quality=3
qn=112,quality=4
三種類型的視頻bili2,bangumi,movie
bili2:
time = F_clock_gettime();//相當于取一個時間隨機數
sign = MD5(“cid=” + cid + “&player=1&qn=112&quality=4&ts=” + time + “1c15888dc316e05a15fdd0a02ed6584f”)
url = “https://interface.bilibili.com/playurl?player=1&qn=112&cid=” + cid + “&quality=4&ts=” + time + “&sign=” + sign
bangumi:
time = F_clock_gettime();//相當于取一個時間隨機數
sign = MD5(“cid=” + cid + “&module=bangumi” + “&player=1” + “&qn=112” + “&quality=4” + “&ts=” + time + “9b288147e5474dd2aa67085f716c560d”)
url = “https://bangumi.bilibili.com/player/web_api/playurl?cid=” + cid + “&player=1” + “&module=bangumi” + “&qn=112” + “&quality=4&ts=” + time + “&sign=” + sign
movie:
time = F_clock_gettime();//相當于取一個時間隨機數
sign = MD5(“cid=” + cid + “&module=movie” + “&player=1” + “&qn=112” + “&quality=4” + “&ts=” + time + “9b288147e5474dd2aa67085f716c560d”)
url = “https://bangumi.bilibili.com/player/web_api/playurl?cid=” + cid + “&player=1” + “&module=movie” + “&qn=112” + “&quality=4&ts=” + time + “&sign=” + sign
也就只分析到了這里?
順便說下,有些tx.acgvideo.com開頭的視頻無法下載是因為服務器檢查了Refer,只要在協議頭里面加入Refer:[media]https://static.hdslb.com/play.swf[/media],就能下載了
水平有限,分析若有什么錯誤,大家看著辦吧
歡迎大家在評論區留下你的意見。謝謝 諸葛商學院
家好,今天小編就以B站為例,帶大家爬取視頻,學會之后你也能爬取你想要的視頻!或不多說,上正文
爬蟲用的好,牢飯吃的早!
本文僅作知識分享,切勿用于違法行為!
git@github.com:inspurer/PythonSpider.git
或者直接下載:https://github.com/inspurer/PythonSpider/tree/master/bilibili
隨便打開一個b站的界面,比如
將url復制到代碼中去,運行代碼,稍等一會兒,上述圖中的視頻就被下載下來了。
按f12瀏覽器開發者工具 ,通過一番審查,我們定位到視頻的url在網頁源代碼的位置如下:
window.__playinfo__={
"from":"local",
"result":"suee",
"quality":32,
"format":"flv480",
"timelength":408884,
"accept_format":"flv720,flv480,flv360",
"accept_description":["高清 720P","清晰 480P","流暢 360P"],
"accept_quality":[64,32,15],
"video_codecid":7,
"video_project":true,
"seek_param":"start",
"seek_type":"offset",
"durl":[{"order":1,"length":408884,"size":42782550,"ahead":"EhA=","vhead":"AWQAHv/hAB5nZAAerNlA2D3n//AoACfxAAADAAEAAAMAMA8WLZYBAAVo6+zyPA==",
"url":"http://upos-hz-mirrorkodo.acgvideo.com/upgcxcode/48/61/45596148/45596148-1-32.flv?e=ig8euxZM2rNcNbRa7b4VhoMz7WhjhwdEto8g5X10ugNcXBlqNxHxNEVE5XREto8KqJZHUa6m5J0SqE85tZvEuENvNC8xNEVE9EKE9IMvXBvE2ENvNCImNEVEK9GVqJIwqa80WXIekXRE9IB5QK==&deadline=1543136253&dynamic=1&gen=playurl&oi=1862807981&os=kodo&platform=pc&rate=176800&trid=69ea1a81ac21448f9e2189ef479a2d6d&uipk=5&uipv=5&um_deadline=1543136253&um_sign=c479f3fd3075b359d0a04e5eb584ac55&upsig=1c05ca3838af92d2c1411cf3000e8345http://upos-hz-mirrorkodo.acgvideo.com/upgcxcode/48/61/45596148/45596148-1-32.flv?e=ig8euxZM2rNcNbRa7b4VhoMz7WhjhwdEto8g5X10ugNcXBlqNxHxNEVE5XREto8KqJZHUa6m5J0SqE85tZvEuENvNC8xNEVE9EKE9IMvXBvE2ENvNCImNEVEK9GVqJIwqa80WXIekXRE9IB5QK==&deadline=1543136253&dynamic=1&gen=playurl&oi=1862807981&os=kodo&platform=pc&rate=176800&trid=69ea1a81ac21448f9e2189ef479a2d6d&uipk=5&uipv=5&um_deadline=1543136253&um_sign=c479f3fd3075b359d0a04e5eb584ac55&upsig=1c05ca3838af92d2c1411cf3000e8345","backup_url":["http://upos-hz-mirrorcos.acgvideo.com/upgcxcode/48/61/45596148/45596148-1-32.flv?um_deadline=1543136253&platform=pc&rate=176800&oi=1862807981&um_sign=c479f3fd3075b359d0a04e5eb584ac55&gen=playurl&os=cos&trid=69ea1a81ac21448f9e2189ef479a2d6d"]}]}
最后的url就是我們想要的結果。
如果在瀏覽器中查找不方便的話,我們可以把通過代碼把網頁源碼輸出到本地
response = requests.get(url='https://www.bilibili.com/video/av26522634', headers= self.getHtmlHeaders)
print(response.status_code)
if response.status_code == 200:
print(response.text)
為了偽裝成瀏覽器,我們需要在reqests添加Headers
這個Headers需要我們去瀏覽器中手動獲取
切換到NetWork標簽下,再選擇Headers,
self.getHtmlHeaders={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'zh-CN,zh;q = 0.9'
}
這里只選擇了幾個關鍵的
根據上一步分析,我們得到了網頁的源碼,并在源碼中定位到了視頻地址,接下來,我們就用代碼自動獲取這個地址了
#用正則、json得到視頻url;用pq失敗后的無奈之舉
pattern = r'\<script\>window\.__playinfo__=(.*?)\</script\>'
result = re.findall(pattern, html)[0]
temp = json.loads(result)
#temp['durl']是一個列表,里面有很多字典
#video_url = temp['durl']
for item in temp['data']['durl']:
if 'url' in item.keys():
video_url = item['url']
順便獲取下視頻的名字:
#用pq解析得到視頻標題
doc = pq(html)
video_title = doc('#viewbox_report > h1 > span').text()然后組合返回下:
然后組合返回下:
return{
'title': video_title,
'url': video_url
}
通過在開發者工具中搜索關鍵詞,比如上面得到的視頻url,我們可以定位到在瀏覽器中真正下載視頻的請求在哪
然后把它的Headers添加到reqests中,就可以下載視頻了
with open(filename, "wb") as f:
f.write(requests.get(url=url, headers=self.downloadVideoHeaders, stream=True, verify=False).content)
你下載的視頻在本地播放不了,請不要試圖修改源代碼中保存文件的格式由.flv改成.mp4,因為b站的視頻本來就是flv格式的,需要用特殊的視頻播放器播放,這里推薦一個無毒無害的KMPlayer,鏈接:https://pan.baidu.com/s/1O4-Uia04Vm-jbUjyrVWfkw 提取碼:4l11
最后多說一句,小編是一名python開發工程師,這里有我自己整理了一套最新的python系統學習教程,包括從基礎的python腳本到web開發、爬蟲、數據分析、數據可視化、機器學習等。想要這些資料的可以關注小編,并在后臺私信小編:“01”即可領取。
本文的文字及圖片來源于網絡加上自己的想法,僅供學習、交流使用,不具有任何商業用途,版權歸原作者所有,如有問題請及時聯系我們以作處理。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。