使用Java爬取斗鱼弹幕消息

爬取斗鱼弹幕看了很多博客,借鉴了很多别人的代码才完成的,具体见github项目


本来就是无意中想做的,但是看了大大小小几十篇博客、知乎和github项目以后,真正做的过程发现了太多自己不知道的东西,记录一下简直太有必要了。
首先知乎的那个问题,如何获取斗鱼直播间的弹幕信息?,这里面有很多回答的都让人很启发,不过2016年斗鱼的api没开放,所以很多答案从wireshark的tcp的报文中找到规律然后写出的,大神我服。
斗鱼弹幕服务器第三方接入协议v1.6.2

学到的东西

学到的东西简要的说,包括:
1.wireshark抓包,这个下面单独讲,然后计算机网络的书的tcp三次握手相关的又复习了一遍
2.计算机存储的大端和小端byte转int为什么& 0xff,这个要结合一楼的评论去看。

网络传输一般都用大端,但是斗鱼这个弹幕传输的消息格式用的小端形式。在乔纳森·斯威夫特的著名讽刺小说《格列夫游记》中,小人国内部分裂成Big-endian和Little-endian两派,区别在于一派要求从鸡蛋的大头把鸡蛋打破,另一派要求从鸡蛋的小头把鸡蛋打破。斯威夫特借以讽刺英国的政党之争,在计算机工业中指数据储存顺序的分歧。

3.java Socket通信,这个是基础,注意TCP的保活计时器(keepalive timer)。
4.看了别人github的项目,学习人家对项目的结构安排,意外的收获
5.一般http请求可以做到对静态页面进行爬取,selenium对js和ajax的页面进行爬取,弹幕这种消息我试了用selenium去隔几秒监控页面元素的变化,发现效果不太好,很容易报页面元素不存在的异常,究其根本,斗鱼的弹幕消息使用的websocket这种TCP通信,所以抓包是综合下来最好的解决方案。

WireShark抓包操作

其实对于本次爬取弹幕,wireshark并没有实际被用到项目里,因为用斗鱼的PI就可以了,但是可以看到很多东西来验证一下。
主界面就是下面这样:

还没开始用,就遇到第一个问题——权限问题,解决方案参见stackoverflow.com
具体使用那个网络设备,然后终端用ifconfig就可以看到active的设备并且还分配来IP,MBP无线就是en0,如果有权限问题看上面那个连接。打开之后就是下面这个样子:

wireshark可以帮助过滤,在上面的filter表达式里写,基本的比如tcp.port这些,这也是最主要用的。
另外就是右键--跟踪流,可以查到流的所有前后信息。

斗鱼弹幕数据包的爬取

这个需要模拟请求发送,会看到很多信息,不过这是懂得请求的结果,具体请求思路可以去看那个知乎问题下面的答案。

小端相关的各种转换

因为这个弹幕里主要涉及的是四位小端数和两位小端数,因此代码就是这两个的相互转换:

public static byte[] intToBytesLittle(int value) {
    byte[] src = new byte[4];
    src[3] = (byte) ((value >> 24) & 0xFF);
    src[2] = (byte) ((value >> 16) & 0xFF);
    src[1] = (byte) ((value >> 8) & 0xFF);
    src[0] = (byte) (value & 0xFF);
    return src;
}

public static byte[] shortToBytesLittle(short n) {
    byte[] b = new byte[2];
    b[1] = (byte) (n >> 8 & 0xff);
    b[0] = (byte) (n & 0xff);
    return b;
}

public static int bytesToIntLittle(byte[] src, int offset) {
    int value;
    value =  ((src[offset] & 0xFF)
            | ((src[offset + 1] & 0xFF) << 8)
            | ((src[offset + 2] & 0xFF) << 16)
            | ((src[offset + 3] & 0xFF) << 24));
    return value;
}

public static int bytesToShortLittle(byte[] src, int offset) {
    int value;
    value = ((src[offset] & 0xFF)
            | ((src[offset + 1] & 0xFF) << 8));
    return value;

&0xFF是和二进制11111111做位操作,一个int是32位的,0xFF是8位的,等于前面24位不存在都是0。

Socket通信

斗鱼Socket地址是openbarrage.douyutv.com,port是8601,所以直接连:

socket = new Socket(DOUYUURL, PORT);
bos = new BufferedOutputStream(socket.getOutputStream());
    bis = new BufferedInputStream(socket.getInputStream());

bos就是前面发请求以及定期发送心跳包,发完之后bis一直读,然后进行解析就行了。

正则表达式学习--善于使用分组

正则表达式里()代表分组,然后可以\1代表第几个分组,分组0是整体匹配到的。

public static void main( String args[] ){

    // 按指定模式在字符串查找
    String line = "allochirally";
    String pattern = "(\\w{3}).*\\1.*";

    // 创建 Pattern 对象
    Pattern r = Pattern.compile(pattern);

    // 现在创建 matcher 对象
    Matcher m = r.matcher(line);
    if (m.find( )) {
        System.out.println("Found value: " + m.group(0) );
        System.out.println("Found value: " + m.group(1) );
    } else {
        System.out.println("NO MATCH");
    }
}

具体还是要看github的项目,感觉项目写的还是有点乱,还是要注意项目结构设置,有些类单一责任原则违背的很厉害(即是Bean又有Util的方法),多总结吧。

发表评论

电子邮件地址不会被公开。