Hack On Douyu -- 1

距离上次更新又有一段时间了,毕业答辩之后,确实和同学们一起出去嗨了一段时间,由于还没入职,在家清净的环境中可以好好学一下一直感兴趣的东西啦。

一直对网络爬虫很感兴趣,所以就开始学习很想学的python,用了之后也是感觉非常棒。期间抓过包括知乎、豆瓣、煎蛋还有个壁纸网站的数据,而抓去最多的还是直播网站斗鱼。数据抓下来之后如何使用是个问题,我的办法是用这些数据通过python的web框架flask搭建一个网站,也算是这段时间的学习成果。网站的构建自然少不了前端,也是硬着头皮学习了bootstrap,了解了一些css、javascript的知识。这段时间的学习成果主要是LearningFlaskBeautifulPicsDanmuDouyuFan这四个项目(由于也是刚接触python,代码质量可能不是太高 -。-)。而最后这个DouyuFan算是对前边几个项目的总结。DouyuFan主要是通过斗鱼网站弹幕信息的抓取,获取直播礼物的分布情况,历史数据记录以及当前最热门房间信息。
接下来我就用三次分别介绍我在数据抓取、后台搭建以及前后端数据通讯中学到的知识和遇到的问题。

开播房间数据获取

使用python抓取过数据的同学肯定对requestBeautiful 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用以使连接保活,通过获取到的数据特点,将普通聊天弹幕和火箭广播弹幕区分开来,并且保存在不同的数据库中从而为之后提供不同的用途。
弹幕数据获取大致是这样的:


获取弹幕
获取弹幕

后续内容

至此,此项目的数据获取工作已经完成,在接下来两篇内容会分别介绍如何使用这些数据构建页面,以及在此过程中遇到的问题。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容