Hack On Douyu -- 3

作者: wangmengcn | 来源:发表于2016-07-27 10:15 被阅读120次

    在拥有了数据获取和存储的能力之后,如何利用好这些数据成为一个问题。
    本来也一直打算把之前学习的flask框架用起来,现在有了这些数据,我打算利用这些数据做一个对斗鱼网站的小型监控页面。

    初步构想

    通过弹幕可以基本上获取到所有的斗鱼火箭礼物发送和接收情况以及这些火箭发送的时间,因此可以利用这些数据得到每天每个小时斗鱼全站火箭的发送情况,利用发送者的id和接收者的id可以统计出每天的火箭排名。每天的数据在凌晨完成统计汇总,可以构成历史数据。
    在这个小项目中,我主要用到两种方法完成前后端数据交互:一种是直接根据请求url知道前端需要的数据具体是什么,我就直接在后端完成数据的获取和处理,通过flask模板完成要返回的页面;另一种方法则是实时性比较强的或者是需要和前端js有交集的我才用socket.io完成数据交互。

    通过抓取斗鱼直播间分类的静态页面可以按照人气值进行排名,获取到当前的热门房间,利用这些房间id可以获取到该直播间的直播视频数据,在flash插件上就可以直接转播(本来一直很纠结视频流如何获取,在参考了dotamax的转播方法之后就醒悟了)。

    另外,在获取实时聊天弹幕的时候可以获取到火箭发送信息,将这些信息通过redis转发,再由socket.io提供给前端页面,实时推送火箭礼物消息。

    火箭发送数据交互

    上边说到,火箭发送信息通过弹幕获取,在数据库中存储包括发送者id、接收者id和发送时间,这些数据同样可以通过redis完成pub/sub。
    因此为了得到每日火箭逐小时发送情况,在后端完成了数据的预处理:

    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,在数据库中检索该天内rocket表中所有数据,返回当天火箭礼物总量、以及每个小时火箭的发送量。
    在获取到每天所有火箭数据之后,统计每天发送者和接收者排名:

    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
    

    最终返回当天火箭发送总量,火箭逐小时发送情况,发送者对应的发送量和接收者对应的接收量。

    完成数据预处理之后,就可以根据前端的请求返回对应的数据了。由于前端获取数据之后前端js根据数据完成不同图表的绘制,我是采用socket.io的方式完成数据交互。
    在后端,我是直接使用flask的一个扩展flask_socketio完成socket.io服务器的搭建(当然,socket.io拥有不同开发语言版本的实现,可根据具体情况有不同选择)。在flask中建立一个socket.io服务器是很简便的:

    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服务器之后,只需要根据具体情况建立不同的监听事件。

    # historyDate事件,接收来自前端的日期date,将其解析之后调用上述数据获取方法
    @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的数据并将其返回给前端           
    @socketio.on('historyRockets')
    def sendHistory(data):
        emit('historyRockets',data)
    

    服务器端的内容弄完之后,前端只需要建立连接,并在恰当的时候触发对应的事件即可:

    // 连接socket.io服务器
    var socket = io.connect('http://ip:' + port);
    // 触发事件
    socket.emit('historyDate',date);
    //接收数据
    socket.on('historyRockets', function(msg){
        dosth();
     });
    

    每天的数据只需要前端发送一个指定的date就可以获取到了。

    最热房间数据获取

    通过socket.io可以完成数据交互,但是这些数据都需要建立额外的连接、消耗额外的网络资源,一些直接可以从后端获取的数据则可直接通过flask模板完成渲染。
    以最热房间为例,由于通过爬虫抓取到的数据都在数据库中,页面在加载的时候直接调用即可。

    首先获取人气排名前21位(页面显示3*7)。获取这些房间的房间标题、主播名、房间编号和房间封面。

    # 按照观众人数,前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
    

    数据获取之后,只需要在flask的router中添加对应规则并则模板中完成元素添加即可:

    # 添加路由规则
    @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 href="http://eclipsesv.com:4321/tv/{{room['roomid']}}" target="_blank">
                                    {{room['anchor']}}@{{room['tag']}}
                                </a>
                            </p>
                        </div>
                    </div>
                </div>
                {%set flag=flag+1%}
                {%if flag%3==0 %}
                </div>
                {%endif%}
            {%endfor%}
        {%endif%}
    

    这样就可以啦。

    视频流转播

    热门房间页面完成之后,想要直接在页面中观看视频而不是跳转到斗鱼,之前一直想复杂了,在看了dotamax直播视频之后看了源码就恍然大悟了。原来只需要一个直播间id就可以了。

    在flask中添加路由规则,通过roomid返回对应页面:

    @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

    到目前为止,站点基本上可以正常运行,由于前端的内容都是初步接触,总体上应该还有较大的可改进余地。接下来准备完成的点主要包括这些:

    • 数据库结构优化,充分考虑利用mongodb自身aggregation来完成数据预处理
    • 提供完善的restful api 完成数据调用
    • 提供针对直播分类和主播的人气监控

    暂时就先这些,继续努力!

    相关文章

      网友评论

        本文标题:Hack On Douyu -- 3

        本文链接:https://www.haomeiwen.com/subject/iwdojttx.html