Python爬取斗魚的彈幕贡茅,看看奇葩網(wǎng)友都說了些什么

0.前言

前幾天(寒假前咯)閑著無聊城菊,看到舍友們都在看斗魚TV备燃,雖然我對那些網(wǎng)絡游戲都不是非常感興趣,但是我突然間想到,如果我可以獲取上面的彈幕內(nèi)容,不就有點意思了么凌唬?

1.分析階段

如果我想要抓取網(wǎng)頁上面的東西并齐,無非就是兩種方法

使用瀏覽器,手工(自己點擊)或者非手工(使用JS腳本)客税,存取我想要的東西况褪。

編寫HTTP客戶端(斗魚無HTTPS通訊)

第一種方法是萬能的,但顯然是不行的更耻, 原因如下:

手動保存實在是不可行测垛,程序員不為也。

瀏覽器與本地交互有限秧均,換而言之食侮,也就是即使我抓取了對應的彈幕,我也沒有辦法解決持久化的問題目胡。

假設你選擇的是Chrome或者firefox瀏覽器锯七,也不是不能實現(xiàn)持久化,但這需要寫擴展,Chrome擴展沒有寫過誉己,也不是很感興趣眉尸。

第二種方法顯然是一個正常的程序員的做法。

語言選用Ruby

寫一個客戶端巫延,也就是寫一個小爬蟲,使用的場景:

用戶在終端執(zhí)行命令

gem install danmudanmu douyu [room_id/url]#比如

danmu douyu qiuri

danmu douyu http://www.douyutv.com/13861

然后就可以在終端欣賞彈幕咯.


Screen Shot 2016-02-09 at 12.23.15 PM.png

回想一下抓取網(wǎng)站的方法

四步走:請求網(wǎng)頁(原始數(shù)據(jù)) - 提取數(shù)據(jù)(提純數(shù)據(jù)) - 保存數(shù)據(jù) - 分析數(shù)據(jù)

很顯然效五,只要解決了請求網(wǎng)頁地消,其他的也就無非解析和SQL語句什么的炉峰。

1.1.斗魚TV彈幕抓取的思路確定

如果是像我上面說的那么簡單,也就不必再寫一篇文章脉执。畢竟疼阔,網(wǎng)頁小爬蟲沒有什么技術含量。分布式爬蟲才有。

通常情況下的網(wǎng)頁小爬蟲無非要解決如下問題:

請求婆廊,如果對方有一定策略的反爬蟲迅细,那需要反反爬蟲。比如淘邻,

header帶上host茵典,帶上refer,帶上其他

需要驗證宾舅,那就申請用戶名和密碼统阿,然后登陸

如果在登錄時期有防跨站機制,那就先獲取一次登錄頁面筹我,然后解析出token扶平,帶上對應的token然后登陸。

在程序中加入Log蔬蕊,并且存到本地结澄。防止出現(xiàn)各種各樣的反爬蟲機制ban掉了程序,從而方便進行下一步防反爬蟲對策岸夯。

并且麻献,由于請求響應機制的存在,通常情況下猜扮,每一個請求對應一個響應,如果出錯了赎瑰,要么超時,要么有狀態(tài)碼破镰,所以普通的web爬蟲也相對而言比較容易些餐曼。

那么,斗魚TV的站點是不是這樣子的就能夠容易爬取呢鲜漩?

你猜到了源譬,答案是“不是”。

由于彈幕具有實時性孕似,就決定了斗魚TV的彈幕無法通過保存完整指定時間端彈幕的XML(比如BILIBILI的一個視頻彈幕是存在一段xml中的)或者Json數(shù)據(jù)來顯示彈幕踩娘。要不然的話,那主播操作很出色的時候喉祭,觀眾的彈幕豈不是無法實時顯示了么养渴?

那么,肯定就是WebSocket了泛烙,于是理卑,我一如既往的打開F12,查看網(wǎng)絡流量蔽氨。

正如你想到的那樣藐唠,沒有任何的彈幕流量來往帆疟。一個WebSocket的消息都沒有。

那么宇立,消息肯定是有的踪宠,但是消息并不是通過HTTP協(xié)議或者WebSocket協(xié)議傳輸?shù)模敲磫栴}會出在哪呢妈嘹?

分析前端的代碼柳琢,找出獲取彈幕的JS代碼,苦于代碼太多润脸,找了很久沒有找到染厅。那也就是執(zhí)行邏輯可能在flash里面。

于是祭出大殺器WireShark津函,抓一下流量肖粮。終于看到彈幕的樣子了。

是這樣的尔苦。



原來使用的是Flash的Socket功能涩馆。

那么,我們只需要模擬Socket的每一條消息就好了.

多分析幾組數(shù)據(jù),但還是對發(fā)送消息內(nèi)容缺乏把握允坚,特別是在用戶認證魂那,用戶接收彈幕這一塊。在搜索引擎上搜索了一陣稠项,發(fā)現(xiàn)知乎上有個帖子涯雅,讀完終于解了我的疑惑。

地址為:www.zhihu.com/question/29…

在此基礎上,省略若干消息分析過程展运。

總結后得出斗魚TV網(wǎng)站的服務器分布活逆。


1.2.房間信息和彈幕認證服務器獲取

首先我們拿隨便一個主播房間來說,比如拗胜,qiuri

Ta的房間鏈接分為兩種

www.douyutv.com/qiuri

www.douyutv.com/[房間id]

對這個主播房間頁面請求蔗候,正常,所有的有用信息都不是放在HTML中渲染出來埂软,而是有一條放在HTML中內(nèi)置的JS腳本中锈遥,這是為了減少服務器渲染HTML的壓力?可是渲染放在JS里面不也一樣需要渲染勘畔?(不明白)總之所灸,就是程序先加載沒有具體數(shù)據(jù)填充頁面,然后JS更新數(shù)據(jù)炫七。

內(nèi)置的兩段JS腳本爬立,JS腳本中有兩個變量,該變量很容易轉換成JSON數(shù)據(jù)诉字,也就是兩段JSON數(shù)據(jù)懦尝,一個是關于主播的個人信息,另一個是關于彈幕認證服務器的列表(該列表中的任意一個服務器均可以認證壤圃,但每一次請求主播頁面得到的認證服務器列表都不一樣)




通過這步陵霉,我們就拿到了主播的信息以及彈幕服務器的認證地址,端口伍绳。

1.3.發(fā)送Socket消息的流程簡介

我們通過抓包踊挠,分析那一大坨數(shù)據(jù)包,可以確定以下通過以下的流程便可以獲取彈幕消息冲杀。(分析過程比較繁瑣)

首先建立兩個Socket效床。一個用于認證(@danmu_auth_socket),另一個用戶獲取彈幕(@danmu_client)权谁。

步驟1:?@danmu_auth_socket 發(fā)送消息登陸,獲取消息1解析出匿名用戶的用戶名剩檀,再獲取消息2解析出gid

步驟2:?@danmu_auth_socket 發(fā)送qrl消息,獲取兩個沒有什么用的消息

步驟3:?@danmu_auth_socket 發(fā)送keeplive消息

步驟4:?@danmu_socket 發(fā)送偽登陸消息(所有匿名用戶都一樣只需要輸入步驟一中用戶名就行了旺芽,因為認證已經(jīng)在上面做過了)

步驟5:?@danmu_socket 發(fā)送join_group消息需要步驟一中國的gid

步驟6:?@danmu_socket 不斷的recv消息就可以獲取彈幕消息了

后面會詳細解釋

2.1.消息Socket消息格式以及發(fā)送一條消息

既然是發(fā)消息沪猴,那么每條消息總是有些格式的。

斗魚的消息格式大致如下:


每一條消息并遵循下面的格式:

1.通信協(xié)議長度,后四個部分的長度,四個字節(jié)

2.第二部分與第一部分一樣

3.請求代碼,發(fā)送給斗魚的話,內(nèi)容為0xb1,0x02, 斗魚返回的代碼為0xb2,0x02

4.發(fā)送內(nèi)容

5.末尾字節(jié)

# -*- encoding : utf-8 -*-classMessage# 向斗魚發(fā)送的消息# 1.通信協(xié)議長度,后四個部分的長度,四個字節(jié)# 2.第二部分與第一部分一樣# 3.請求代碼,發(fā)送給斗魚的話,內(nèi)容為0xb1,0x02, 斗魚返回的代碼為0xb2,0x02# 4.發(fā)送內(nèi)容# 5.末尾字節(jié)#pack('c*')是字節(jié)數(shù)組轉字符串的一種詭異的轉化方式definitialize(content)? ? @length = [content.size +9,0x00,0x00,0x00].pack('c*')? ? @code = @length.dup? ? @magic = [0xb1,0x02,0x00,0x00].pack('c*')? ? @content? = content? ? @end = [0x00].pack('c*')enddefto_s? ? @length + @code + @magic + @content + @endendend

經(jīng)過封裝采章,我們僅僅關注那些可見的字符串运嗜,也就是Content部分就可以了。

content部分悯舟,也就是發(fā)送消息的內(nèi)容担租,在文章后面將會詳解。

開啟兩個Socket抵怎,一個用戶認證奋救,另一個用于彈幕的獲取。

用于用戶彈幕認證的反惕,是2.1中所說的認證服務器列表中任意一個菠镇。挑選出來一組ip和端口

@danmu_auth_socket = TCPSocket.new @auth_dst_ip,@auth_dst_port

用戶獲取彈幕的只要為

danmu.douyutv.com:8601danmu.douyutv.com:8602danmu.douyutv.com:12601danmu.douyutv.com:12602

四組域名:端口均可以作為如下的DANMU_SERVER和PORT

@danmu_socket = TCPSocket.new DANMU_SERVER,DANMU_PORT

發(fā)送一條消息只需如此

data ="type@=loginreq/username@="+@username+"/password@=1234567890123456/roomid@=" + @room_id.to_s +"/"

? ? all_data = message(data)

? ? @danmu_socket.write all_data

把需要傳輸?shù)淖址胚M去就好了.

接下來,我們需處理上面說的六個步驟

2.2.發(fā)送消息詳細流程之步驟一

發(fā)送消息內(nèi)容為:

type@=loginreq/username@=/ct@=0/password@=/roomid@=156277/devid@=DF9E4515E0EE766B39F8D8A2E928BB7C/rt@=1453795822/vk@=4fc6e613fc650a058757331ed6c8a619/ver@=20150929/

我們需要注意的內(nèi)容如下:

type 表示消息的類型登陸消息為loginrequsername 不需要承璃,請求登陸以后系統(tǒng)會自動的返回對應的游客賬號利耍。ct 不清楚什么意思,默認為0并無影響password 不需要roomid 房間的iddevid 為設備標識盔粹,無所謂隘梨,所以我們使用隨機的UUID生成rt 應該是runtime吧,時間戳vk 為時間戳+"7oE9nPEG9xXV69phU31FYCLUagKeYtsF"+devid的字符串拼接結果的MD5值(這個是參考了一篇文章舷嗡,關于這一處我也不大明白怎么探究出來的)

ver 默認

通過這一步轴猎,我們可以獲取兩條消息,并從消息中使用正則表達式獲取對應的用戶名以及gid

str = @danmu_auth_socket.recv(4000)? ? @username= str[/\/username@=(.+)\/nickname/,1]? ? str = @danmu_auth_socket.recv(4000)? ? @gid = str[/\/gid@=(\d+)\//,1]

2.3.發(fā)送消息詳細流程之步驟二

發(fā)送的消息內(nèi)容為

"type@=qrl/rid@=" + @room_id.to_s + "/"

無需多說进萄,類型為qrl捻脖,rid為roomid锐峭,直接發(fā)送這條消息就好。返回的兩條消息也沒有什么價值可婶。

data? ="type@=qrl/rid@=" + @room_id.to_s +"/"? ? msg = message(data)? ? @danmu_auth_socket.write msg? ? str = @danmu_auth_socket.recv(4000)? ? str = @danmu_auth_socket.recv(4000)

2.4.發(fā)送消息詳細流程之步驟三

發(fā)送的消息內(nèi)容為

"type@=keeplive/tick@=" + timestamp + "/vbw@=0/k@=19beba41da8ac2b4c7895a66cab81e23/"

直接發(fā)送沿癞。無太大意義。

data ="type@=keeplive/tick@=" + timestamp +"/vbw@=0/k@=19beba41da8ac2b4c7895a66cab81e23/"? ? msg = message(data)? ? @danmu_auth_socket.write msg? ? str = @danmu_auth_socket.recv(4000)

前三步矛渴,也就是2.2-2.3-2.4三步驟椎扬,也就是使用@danmu_auth_socket 完成獲取username和gid的重要步驟。獲取這兩個字段以后具温,也就完成了它存在的使命蚕涤。

接下來的就是@danmu_socket獲取彈幕的時候了!

2.5.發(fā)送消息詳細流程之步驟四

消息內(nèi)容為:"type@=loginreq/username@="+@username+"/password@=1234567890123456/roomid@=" + @room_id.to_s + "/"

和上面2.2中略有不同铣猩。但是躁垛,需要注意的是

username 為2.2中所得到的username

? password 的值得變化

data ="type@=loginreq/username@="+@username+"/password@=1234567890123456/roomid@=" + @room_id.to_s +"/"? ? all_data = message(data)? ? @danmu_socket.write all_data? ? str = @danmu_socket.recv(4000)

2.6.發(fā)送消息詳細流程之步驟五

接下來就是完成認證的最后一步了寄猩,join_group的消息內(nèi)容為

"type@=joingroup/rid@=" + @room_id.to_s + "/gid@="+@gid+"/"

gid為2.2中所得到的gid召衔。

data? ="type@=joingroup/rid@=" + @room_id.to_s +"/gid@="+@gid+"/"

? ? ? msg = message(data)

? ? ? @danmu_socket.write msg

2.7.發(fā)送消息詳細流程之步驟六

獲取彈幕逊移,并且打印出來。

danmu_data = @danmu_socket.recv(4000)? ? type = danmu_data[danmu_data.index("type@=")..-3]? ? puts type.gsub('sui','').gsub('@S','/').gsub('@A=',':').gsub('@=',':').split('/')

后三步鳞绕,則是@danmu_socket?獲取彈幕的步驟失仁。

于是,通過這些步驟们何,就可以完成了簡單的danmu的核心代碼萄焦,接下來的步驟就是完善,重構這些代碼了冤竹。

總結

痛點一拂封,至今還沒有解決rtmp地址的獲取

找了很久沒有辦法解決rtmp地址的自動獲取:

路徑如下

www.douyutv.com/swf_api/roo…

這一處的請求不是XHR鹦蠕,也就是不是JS腳本通過XMLHttpRequest異步加載;那么冒签,八成是flash通過http協(xié)議獲取的。我估計八成執(zhí)行邏輯應該是在flash之中钟病。也就不方便獲取其中的sign值.故,暫時無法解析rtmp視頻流地址了

學習Python過程中會遇到很多問題萧恕,可以到我們的 python學習交流群【六 五 三,四 六 六肠阱,六 六 八】票唆,基礎,進階屹徘。從企業(yè)招聘人才需求 到怎么學習python走趋,和學習什么內(nèi)容都有免費系統(tǒng)分享。希望可以幫助你快速了解Python噪伊,學習python

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末簿煌,一起剝皮案震驚了整個濱河市氮唯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌姨伟,老刑警劉巖惩琉,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異授滓,居然都是意外死亡琳水,警方通過查閱死者的電腦和手機肆糕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門般堆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人诚啃,你說我怎么就攤上這事淮摔。” “怎么了始赎?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵和橙,是天一觀的道長。 經(jīng)常有香客問我造垛,道長魔招,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任五辽,我火速辦了婚禮办斑,結果婚禮上,老公的妹妹穿的比我還像新娘杆逗。我一直安慰自己乡翅,他們只是感情好,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布罪郊。 她就那樣靜靜地躺著蠕蚜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪悔橄。 梳的紋絲不亂的頭發(fā)上靶累,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機與錄音癣疟,去河邊找鬼尺铣。 笑死,一個胖子當著我的面吹牛争舞,可吹牛的內(nèi)容都是我干的凛忿。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼竞川,長吁一口氣:“原來是場噩夢啊……” “哼店溢!你這毒婦竟也來了叁熔?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤床牧,失蹤者是張志新(化名)和其女友劉穎荣回,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體戈咳,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡心软,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了著蛙。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片删铃。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖踏堡,靈堂內(nèi)的尸體忽然破棺而出猎唁,到底是詐尸還是另有隱情,我是刑警寧澤顷蟆,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布诫隅,位于F島的核電站,受9級特大地震影響帐偎,放射性物質發(fā)生泄漏逐纬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一削樊、第九天 我趴在偏房一處隱蔽的房頂上張望豁生。 院中可真熱鬧,春花似錦嫉父、人聲如沸沛硅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽摇肌。三九已至,卻和暖如春仪际,著一層夾襖步出監(jiān)牢的瞬間围小,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工树碱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留肯适,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓成榜,卻偏偏與公主長得像框舔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容