在擁有了數(shù)據(jù)獲取和存儲(chǔ)的能力之后堰氓,如何利用好這些數(shù)據(jù)成為一個(gè)問題袄秩。
本來也一直打算把之前學(xué)習(xí)的flask框架用起來,現(xiàn)在有了這些數(shù)據(jù)裆赵,我打算利用這些數(shù)據(jù)做一個(gè)對(duì)斗魚網(wǎng)站的小型監(jiān)控頁面东囚。
初步構(gòu)想
通過彈幕可以基本上獲取到所有的斗魚火箭禮物發(fā)送和接收情況以及這些火箭發(fā)送的時(shí)間,因此可以利用這些數(shù)據(jù)得到每天每個(gè)小時(shí)斗魚全站火箭的發(fā)送情況战授,利用發(fā)送者的id和接收者的id可以統(tǒng)計(jì)出每天的火箭排名页藻。每天的數(shù)據(jù)在凌晨完成統(tǒng)計(jì)匯總桨嫁,可以構(gòu)成歷史數(shù)據(jù)。
在這個(gè)小項(xiàng)目中份帐,我主要用到兩種方法完成前后端數(shù)據(jù)交互:一種是直接根據(jù)請(qǐng)求url知道前端需要的數(shù)據(jù)具體是什么璃吧,我就直接在后端完成數(shù)據(jù)的獲取和處理,通過flask模板完成要返回的頁面废境;另一種方法則是實(shí)時(shí)性比較強(qiáng)的或者是需要和前端js有交集的我才用socket.io完成數(shù)據(jù)交互畜挨。
通過抓取斗魚直播間分類的靜態(tài)頁面可以按照人氣值進(jìn)行排名,獲取到當(dāng)前的熱門房間噩凹,利用這些房間id可以獲取到該直播間的直播視頻數(shù)據(jù)巴元,在flash插件上就可以直接轉(zhuǎn)播(本來一直很糾結(jié)視頻流如何獲取,在參考了dotamax的轉(zhuǎn)播方法之后就醒悟了)栓始。
另外务冕,在獲取實(shí)時(shí)聊天彈幕的時(shí)候可以獲取到火箭發(fā)送信息血当,將這些信息通過redis轉(zhuǎn)發(fā)幻赚,再由socket.io提供給前端頁面,實(shí)時(shí)推送火箭禮物消息臊旭。
火箭發(fā)送數(shù)據(jù)交互
上邊說到落恼,火箭發(fā)送信息通過彈幕獲取,在數(shù)據(jù)庫中存儲(chǔ)包括發(fā)送者id离熏、接收者id和發(fā)送時(shí)間佳谦,這些數(shù)據(jù)同樣可以通過redis完成pub/sub。
因此為了得到每日火箭逐小時(shí)發(fā)送情況滋戳,在后端完成了數(shù)據(jù)的預(yù)處理:
def sortbyDay(date):
if isinstance(date, datetime):
year = date.year
m = date.month
d = date.day
singledate = datetime(year, m, d)
print singledate
singledata = []
count = 0
hour = range(0, 24)
for h in hour:
data = []
value = {}
start = datetime(year, m, d, h, 0, 0)
end = datetime(year, m, d, h, 59, 59)
daydata = col.find(
{'date': {'$gt': start, '$lt': end}}, {'_id': 0})
if daydata is not None:
for item in daydata:
data.append(item)
count = count + 1
else:
data = None
value['hour'] = h
value['rockets'] = data
singledata.append(value)
if count != 0:
insertdata = {
'date': singledate,
'data': singledata,
'count': count
}
return insertdata
else:
return None
通過輸入指定的日期date钻蔑,在數(shù)據(jù)庫中檢索該天內(nèi)rocket表中所有數(shù)據(jù),返回當(dāng)天火箭禮物總量奸鸯、以及每個(gè)小時(shí)火箭的發(fā)送量咪笑。
在獲取到每天所有火箭數(shù)據(jù)之后,統(tǒng)計(jì)每天發(fā)送者和接收者排名:
def valuebyHour(date):
daydata = sortbyDay(date)
count = 0
hourvalue = []
sendervalue = {}
recvervalue = {}
if daydata is not None:
count = daydata['count']
hourdata = daydata['data']
for h in hourdata:
hourvalue.append(len(h['rockets']))
sender = 'sender_id'
recver = 'recver_id'
rocket = h['rockets']
if len(rocket) != 0:
sendervalue = sortNames(rocket, sender, sendervalue)
recvervalue = sortNames(rocket, recver, recvervalue)
return (count, hourvalue, sendervalue, recvervalue)
else:
return None
最終返回當(dāng)天火箭發(fā)送總量娄涩,火箭逐小時(shí)發(fā)送情況窗怒,發(fā)送者對(duì)應(yīng)的發(fā)送量和接收者對(duì)應(yīng)的接收量。
完成數(shù)據(jù)預(yù)處理之后蓄拣,就可以根據(jù)前端的請(qǐng)求返回對(duì)應(yīng)的數(shù)據(jù)了扬虚。由于前端獲取數(shù)據(jù)之后前端js根據(jù)數(shù)據(jù)完成不同圖表的繪制,我是采用socket.io的方式完成數(shù)據(jù)交互球恤。
在后端辜昵,我是直接使用flask的一個(gè)擴(kuò)展flask_socketio完成socket.io服務(wù)器的搭建(當(dāng)然,socket.io擁有不同開發(fā)語言版本的實(shí)現(xiàn)咽斧,可根據(jù)具體情況有不同選擇)堪置。在flask中建立一個(gè)socket.io服務(wù)器是很簡便的:
from flask_socketio import SocketIO
from flask_socketio import send, emit
# flask 主程序
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret'
socketio = SocketIO(app)
if __name__ == '__main__':
socketio.run(app, host='0.0.0.0', port=3000)
建立socket服務(wù)器之后贷洲,只需要根據(jù)具體情況建立不同的監(jiān)聽事件。
# historyDate事件晋柱,接收來自前端的日期date优构,將其解析之后調(diào)用上述數(shù)據(jù)獲取方法
@socketio.on('historyDate')
def sendDate(date):
if date:
print date
time = date.split('-')
timevalue = [int(item) for item in time]
y = timevalue[0]
m = timevalue[1]
d = timevalue[2]
recordDate = datetime(y,m,d)
returnValue = valuebyHour(recordDate)
if returnValue:
(a, b, c, d) = returnValue
if b is not None:
socketio.emit('historyRockets', b)
else:
socketio.emit('historyRockets',None)
# historyRockets事件,接收來自historyDate的數(shù)據(jù)并將其返回給前端
@socketio.on('historyRockets')
def sendHistory(data):
emit('historyRockets',data)
服務(wù)器端的內(nèi)容弄完之后雁竞,前端只需要建立連接钦椭,并在恰當(dāng)?shù)臅r(shí)候觸發(fā)對(duì)應(yīng)的事件即可:
// 連接socket.io服務(wù)器
var socket = io.connect('http://ip:' + port);
// 觸發(fā)事件
socket.emit('historyDate',date);
//接收數(shù)據(jù)
socket.on('historyRockets', function(msg){
dosth();
});
每天的數(shù)據(jù)只需要前端發(fā)送一個(gè)指定的date就可以獲取到了。
最熱房間數(shù)據(jù)獲取
通過socket.io可以完成數(shù)據(jù)交互碑诉,但是這些數(shù)據(jù)都需要建立額外的連接彪腔、消耗額外的網(wǎng)絡(luò)資源,一些直接可以從后端獲取的數(shù)據(jù)則可直接通過flask模板完成渲染进栽。
以最熱房間為例德挣,由于通過爬蟲抓取到的數(shù)據(jù)都在數(shù)據(jù)庫中,頁面在加載的時(shí)候直接調(diào)用即可快毛。
首先獲取人氣排名前21位(頁面顯示3*7)格嗅。獲取這些房間的房間標(biāo)題、主播名唠帝、房間編號(hào)和房間封面屯掖。
# 按照觀眾人數(shù),前20名房間
def HotRoom():
hotroom = roomcol.find({}, {"_id": 0, "date": 0}).sort(
"audience", pymongo.DESCENDING).limit(21)
rooms = []
if hotroom:
for item in hotroom:
rooms.append(item)
return rooms
數(shù)據(jù)獲取之后襟衰,只需要在flask的router中添加對(duì)應(yīng)規(guī)則并則模板中完成元素添加即可:
# 添加路由規(guī)則
@app.route('/chatmsg')
def chatmsg():
rooms = HotRoom()
return render_template('gift.html', hotroom=rooms, flag=0)
完成模板:
<h2 style="color:rgba(228, 230, 232, 0.53);">熱門房間</h2>
{%if hotroom%}
{%for room in hotroom%}
{%if flag%3==0 %}
<div class="row nopx">
{%endif%}
<div class="col-sm-6 col-md-4 nopx">
<div class="thumbnail" style="border:1px solid rgba(135, 144, 160, 0.15);line-height: 0px;
background-color: rgba(8, 8, 8, 0.72);">
<img src={{room['img']}} alt="http://eclipsesv.com:4321/tv/{{room['roomid']}}">
<div class="caption">
<h4 style="color:rgba(228, 230, 232, 0.53);">{{room['roomtitle']}}</h4>
<p>
<a target="_blank">
{{room['anchor']}}@{{room['tag']}}
</a>
</p>
</div>
</div>
</div>
{%set flag=flag+1%}
{%if flag%3==0 %}
</div>
{%endif%}
{%endfor%}
{%endif%}
這樣就可以啦贴铜。
視頻流轉(zhuǎn)播
熱門房間頁面完成之后,想要直接在頁面中觀看視頻而不是跳轉(zhuǎn)到斗魚瀑晒,之前一直想復(fù)雜了绍坝,在看了dotamax直播視頻之后看了源碼就恍然大悟了。原來只需要一個(gè)直播間id就可以了苔悦。
在flask中添加路由規(guī)則轩褐,通過roomid返回對(duì)應(yīng)頁面:
@app.route('/tv/<int:roomid>')
def tvstream(roomid):
if roomid:
return render_template('tv.html', roomid=roomid)
前端模板只需要這樣即可:
{%if roomid%}
<object type="application/x-shockwave-flash" data="http://staticlive.douyutv.com/common/share/play.swf?room_id={{roomid}}" width="1200px" height="750px" allowscriptaccess="always" allowfullscreen="true" allowfullscreeninteractive="true"><param name="quality" value="high"><param name="bgcolor" value="#000000"><param name="allowscriptaccess" value="always"><param name="allowfullscreen" value="true"><param name="wmode" value="transparent"><param name="allowFullScreenInteractive" value="true">
<param name="quality" value="high">
<param name="bgcolor" value="#000000">
<param name="allowscriptaccess" value="always">
<param name="allowfullscreen" value="true">
<param name="wmode" value="transparent">
<param name="allowFullScreenInteractive" value="true">
</object>
{%endif%}
這樣就可以盜用斗魚的視頻啦,哈哈哈间坐。
TODO
到目前為止灾挨,站點(diǎn)基本上可以正常運(yùn)行,由于前端的內(nèi)容都是初步接觸竹宋,總體上應(yīng)該還有較大的可改進(jìn)余地劳澄。接下來準(zhǔn)備完成的點(diǎn)主要包括這些:
- 數(shù)據(jù)庫結(jié)構(gòu)優(yōu)化,充分考慮利用mongodb自身aggregation來完成數(shù)據(jù)預(yù)處理
- 提供完善的restful api 完成數(shù)據(jù)調(diào)用
- 提供針對(duì)直播分類和主播的人氣監(jiān)控
暫時(shí)就先這些蜈七,繼續(xù)努力秒拔!