距離上次更新又有一段時間了,畢業答辯之后,確實和同學們一起出去嗨了一段時間,由于還沒入職,在家清凈的環境中可以好好學一下一直感興趣的東西啦。
一直對網絡爬蟲很感興趣,所以就開始學習很想學的python,用了之后也是感覺非常棒。期間抓過包括知乎、豆瓣、煎蛋還有個壁紙網站的數據,而抓去最多的還是直播網站斗魚。數據抓下來之后如何使用是個問題,我的辦法是用這些數據通過python的web框架flask搭建一個網站,也算是這段時間的學習成果。網站的構建自然少不了前端,也是硬著頭皮學習了bootstrap,了解了一些css、javascript的知識。這段時間的學習成果主要是LearningFlask、BeautifulPics、Danmu和DouyuFan這四個項目(由于也是剛接觸python,代碼質量可能不是太高 -。-)。而最后這個DouyuFan算是對前邊幾個項目的總結。DouyuFan主要是通過斗魚網站彈幕信息的抓取,獲取直播禮物的分布情況,歷史數據記錄以及當前最熱門房間信息。
接下來我就用三次分別介紹我在數據抓取、后臺搭建以及前后端數據通訊中學到的知識和遇到的問題。
開播房間數據獲取
使用python抓取過數據的同學肯定對request和Beautiful Soup這兩個庫不陌生。
號稱http for humans的requests缺失不是沽名釣譽,他在頁面數據的抓取上確實簡單明了。
通常情況下,requests和Beautiful Soup配合使用。以對斗魚當前直播房間的抓取為例:
# -*- coding: utf-8 -*-
from bs4 import BeautifulSoup # 導入BeautifulSoup,提取網頁中目標元素
import re # re 正則表達式,在快速查找和過濾元素中有出色表現
import requests # reqeusts 用以獲取頁面數據
from datetime import datetime
from pymongo import MongoClient
首先導入上述這幾個庫,然后可以偽造http請求header,這樣可以減少爬蟲被服務器ban掉的可能。
HOST = "http://www.douyu.com"
Directory_url = "http://www.douyu.com/directory?isAjax=1"
Qurystr = "/?page=1&isAjax=1"
agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.86 Safari/537.36'
accept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"
connection = "keep-alive"
CacheControl = "no-cache"
UpgradeInsecureRequests = 1
headers = {
'User-Agent': agent,
'Host': HOST,
'Accept': accept,
'Cache-Control': CacheControl,
'Connection': connection,
'Upgrade-InsecureRequests': UpgradeInsecureRequests
}
然后就是開播房間數據的獲取和入庫:
cli = MongoClient(host="ip",port=xxx)
db = cli["Douyu"]
col = db["Roominfo"]
def get_roominfo(data):
if data:
firstpage = BeautifulSoup(data)
roomlist = firstpage.select('li')
print len(roomlist)
if roomlist:
for room in roomlist:
try:
roomid = room["data-rid"]
roomtitle = room.a["title"]
roomtitle = roomtitle.encode('utf-8')
roomowner = room.select("p > span")
roomtag = room.select("div > span")
roomimg = room.a
roomtag = roomtag[0].string
date = datetime.now()
# now = datetime.datetime(
# date.year, date.month, date.day, date.hour, date.minute)
if len(roomowner) == 2:
zbname = roomowner[0].string
audience = roomowner[1].get_text()
audience = audience.encode('utf-8').decode('utf-8')
image = roomimg.span.img["data-original"]
word = u"萬" # 在頁面中獲取的房間人數以萬為單位的str需要轉換為int型,以便入庫
if word in audience:
r = re.compile(r'(\d+)(\.?)(\d*)')
data = r.match(audience).group(0)
audience = int(float(data) * 10000)
else:
audience = int(audience)
roominfo = {
"roomid": int(roomid),
"roomtitle": roomtitle,
"anchor": zbname,
"audience": audience,
"tag": roomtag,
"date": date,
"img" : image
}
col.insert_one(roominfo)
# print roomid,":",roomtitle
except Exception, e:
pass
def insert_info():
session = requests.session()
pagecontent = session.get(Directory_url).text
pagesoup = BeautifulSoup(pagecontent)
games = pagesoup.select('a')
col.drop()
for game in games:
links = game["href"]
gameurl = HOST + links + Qurystr
print gameurl
gamedata = session.get(gameurl).text
get_roominfo(gamedata)
我平常習慣使用mongodb作為數據存儲,首先建立與數據庫的連接,然后通過獲取斗魚當前所有房間分類,接著逐一獲取每個分類中開播的房間數據,并記錄每個房間的roomid(房間號,斗魚直播間唯一標識)、roomtitle(房間標題)、anchor(主播id)、audience(觀眾人數)、tag(房間所屬分類)、date(數據獲取時間)、img(直播間封面圖片)。通過定時執行此腳本,可以獲取當前觀眾人數最多的房間(通常大都是lol的直播 =。=),也可以在之后通過roomid查詢到關于對應直播間必要的信息。
彈幕數據獲取
經常看斗魚直播的同學肯定知道“彈幕大神”這個詞,我最初想要抓取彈幕的目的是想通過大量的獲取直播間彈幕數據進行一些自然語言分析,由于那些東西一直沒學習,也就沒再弄,但是,通過彈幕,可以獲取到在全頻道廣播的火箭信息,長時間監測這些數據應該也是一件有意思的事情。
說干就干,想要獲取到直播間的彈幕數據不同于上邊所說的頁面數據抓取,好在斗魚官方也提供了一個獲取彈幕的途徑斗魚彈幕服務器第三方接入協議,文檔中對如何獲取彈幕數據、以及彈幕信息類型有具體的說明,這也大大降低了獲取彈幕數據的難度。
看過這個協議之后,通過建立與彈幕服務器的tcp連接,可以不斷的獲取到彈幕數據,我使用的是socket這個庫。
HOST = 'openbarrage.douyutv.com'
PORT = 8601
RID = 97376
LOGIN_INFO = "type@=loginreq/username@=qq_aPSMdfM5" + \
"/password@=1234567890123456/roomid@=" + str(RID) + "/"
JION_GROUP = "type@=joingroup/rid@=" + str(RID) + "/gid@=-9999" + "/"
ROOM_ID = "type@=qrl/rid@=" + str(RID) + "/"
KEEP_ALIVE = "type@=keeplive/tick@=" + \
str(int(time.time())) + "/vbw@=0/k@=19beba41da8ac2b4c7895a66cab81e23/"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
在這里,需要注意幾個變量: host port roomid gid 。其中HOST是彈幕服務器地址,port是對外開放的端口,roomid則是主播間對應的id,gid是要加入的彈幕頻道,-9999頻道可以獲取到所有彈幕,也就是“海量彈幕”頻道。
def get_Hotroom():
hotroom = roomcol.find().limit(1).sort(
[("audience", pymongo.DESCENDING), ("date", pymongo.DESCENDING)])
for item in hotroom:
return item["roomid"]
def create_Conn():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
RID = get_Hotroom()
print "當前最熱房間:", RID
LOGIN_INFO = "type@=loginreq/username@=qq_aPSMdfM5" + \
"/password@=1234567890123456/roomid@=" + str(RID) + "/"
print LOGIN_INFO
JION_GROUP = "type@=joingroup/rid@=" + str(RID) + "/gid@=-9999" + "/"
print JION_GROUP
s.sendall(tranMsg(LOGIN_INFO))
s.sendall(tranMsg(JION_GROUP))
return s
之后,通過get_Hotroom()獲取到當前最熱門房間(人數最多的房間),通過create_Conn()建立與服務器的連接。連接建立之后就可以開心的獲取并保存彈幕數據了:
def insert_msg(sock):
sendtime = 0
while True:
if sendtime % 20 == 0:
print "----------Keep Alive---------"
try:
sock.sendall(tranMsg(KEEP_ALIVE))
except socket.error:
print "alive error"
sock = create_Conn()
insert_msg(sock)
sendtime += 1
print sendtime
try:
data = sock.recv(4000)
if data:
strdata = repr(data)
if "type@=spbc" in strdata:
get_rocket(data)
if "type@=chatmsg" in strdata:
get_chatmsg(data)
except socket.error:
print "chat error"
sock = create_Conn()
insert_msg(sock)
time.sleep(1)
每20秒向服務器發送一條KEEP_ALIVE用以使連接保活,通過獲取到的數據特點,將普通聊天彈幕和火箭廣播彈幕區分開來,并且保存在不同的數據庫中從而為之后提供不同的用途。
彈幕數據獲取大致是這樣的:

后續內容
至此,此項目的數據獲取工作已經完成,在接下來兩篇內容會分別介紹如何使用這些數據構建頁面,以及在此過程中遇到的問題。