以前在学校就有接触到爬虫相关的工作,直到现在还在写爬虫(呃...),最近发现光靠网页爬虫数据,不管是从频率还是数据都有一定的限制。为了绕过这些限制,最近我开始尝试通过APP来进行数据采集,不过有些APP对数据采集不太友好,比如请求需要签名、SSL/TLS加密等等,今天就以X平台为例来解决这个问题。

注意:


本文有小部分内容由AI辅助创作,部分内容的真实性存疑,请自行判断。
本文会使用到一些方法绕过GFW,请在使用互联网时遵守当地的相关法律法规。
本文涉及到软件逆向分析,请注意在实践过程中的法律问题。
根据《中华人民共和国刑法》第286条:
违反国家规定,对计算机信息系统功能进行删除、修改、增加、干扰,造成计算机信息系统不能正常运行,后果严重的,处5年以下有期徒刑或者拘役;后果特别严重的,处5年以上有期徒刑。
违反国家规定,对计算机信息系统中存储、处理或者传输的数据和应用程序进行删除、修改、增加的操作,后果严重的,依照前款的规定处罚。
故意制作、传播计算机病毒等破坏性程序,影响计算机系统正常运行,后果严重的,依照第1款的规定处罚。

常规的数据采集

学习或开发过爬虫的同学都知道,常用的数据采集方式有几种:

一是网页抓取,即通过requests或其他类似的网络工具直接对页面进行请求,并通过xpath等工具处理返回的页面文件,多见于内容不经常变化的网页或纯静态页面数据采集。这种方法的优点是简单易用,缺点是当网页结构发生变化时,需要调整解析规则。

二是API接口请求,即通过浏览器开发者工具,抓取到数据的请求接口;或者使用官方公布的API接口。一般来说,前后端分离的系统都会有单独的数据请求接口,返回格式多为json。该方法处理简单,速度快,但有可能会被服务器限制请求速率。且如果没有官方的接口文档,有些时候返回的数据比较难以分析。部分官方开放接口可能限制较多,甚至需要收费使用。

三是使用selenium模拟浏览器行为进行动态网页采集,该方法适用于网页通过js动态加载数据的情况。但通过模拟用户操作的方法,可以尽可能减少采集行为被发现、账号被风控的情况发生。同时该方法缺点也很明显,即速度较慢,占用系统资源较多,尤其是进行多线程采集的情况,每个线程需要单独启动一个浏览器内核,对资源需求极高。

除此之外,还有一些较为特殊的数据采集方式,比如某弹幕网站的APP平台就使用到了gRPC接口用于获取视频弹幕信息,以及socket套接字用于获取直播弹幕和礼物数据流1。还有使用RSS订阅源进行数据采集2的方法,该方法优点是通用、开发简单,有很多线程的库可以直接使用,并且开发一套RSS采集爬虫基本上就可以在所有RSS订阅源通用;缺点是RSS数据量一般来说比较少,很多网站甚至没有官方的RSS订阅连接,还有类似某乎这样的网站,需要登陆才能获取RSS订阅3

基于APP接口的数据采集

由于X平台的网页端限制较大,本文尝试使用APP的接口来进行数据采集,不知道能否比网页端情况好一些。

APP接口请求数据的方式与网页类似,除了上面一节提到的弹幕采集这种特殊情况以外,基本上也是以HTTP请求为主,所以实际上我们只需要构造好请求数据,然后请求对应的HTTP接口即可。不过实际上,整个开发过程中,最难的一步就是寻找需要的HTTP接口,以及解析需要发送的请求数据。

下面,我们先了解一下需要用到的基本原理和理论知识,然后尝试以X平台为例,进行逆向分析和数据采集。

需要提前了解的信息

超文本传输协议(http)

超文本传输协议,全称为HyperText Transfer Protocol,更常用的说法是http协议。HTTP协议七层OSI模型的应用层,关于七层OSI模型的相关内容,可以在这里了解到。HTTP最开始的设计目的是为了传输HTML(HyperText Markup Language,超文本标记语言)页面。其请求的资源通过URI(Uniform Resource Identifiers,统一资源标识符,与URL不同)来进行标记。

关于HTTP的发展和历史故事,这里就不详细描述了,感兴趣的同学可以到Wikipedia上面了解一下。这里主要讨论一下HTTP的技术相关问题。

在很久很久以前......well,我并不是要讲什么故事,我想说的是在很久以前我已经写过一篇文章,来简单介绍HTTP协议请求方法了,这篇文章可以在这里看到。关于这篇文章当中已经写过的内容,就不再重复了。这里主要对请求和响应结构进行一下补充。

在之前那篇文章中提到了,报文是有三部分组成,分别为报头、空行和报体,实际上在报头之前还有请求行,以本站主页(https://kalinote.top/)为例,请求的完整报文如下:

1
2
3
4
5
6
7
8
9
10
11
12
GET https://kalinote.top/ HTTP/2.0
upgrade-insecure-requests: 1
user-agent: Mozilla/5.0 (Linux; Android 9; SM-S906N Build/PQ3B.190801.06281541; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/91.0.4472.114 Mobile Safari/537.36
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
x-requested-with: com.android.browser
sec-fetch-site: none
sec-fetch-mode: navigate
sec-fetch-user: ?1
sec-fetch-dest: document
accept-encoding: gzip, deflate
accept-language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
content-length: 0

实际上这是一个HTTPS的GET请求,其版本为2.04,在该请求中,第一行为请求行,其格式为:

1
请求方法|空格|URL|空格|协议版本|回车符|换行符

后面的内容,每一行为一个请求头(Header),每个请求头一般都有自己的意义,比如user-agent(UA,用户标识或用户代理)一般用于服务器识别请求发出的客户端,在上述例子中的请求头的UA为安卓9系统默认浏览器的UA,这一点在x-requested-with中也体现到了(其值为浏览器包名)。

实际上,各网站一般会有自己的请求头参数需求,缺少某些参数可能会导致权限错误(401)或Bad Request(400)等其他请求失败的情况发生。

使用上述请求后,服务器返回了如下数据:

1
2
3
4
5
6
7
8
HTTP/2.0 200
server: nginx
date: Tue, 09 Jul 2024 08:58:05 GMT
content-type: text/html; charset=UTF-8
vary: Accept-Encoding
x-frame-options: SAMEORIGIN
strict-transport-security: max-age=31536000
content-length: 60097

当然,这些操作都是我们在浏览器中输入了网址后,浏览器自动帮我们完成的,我们也可以使用类似telnet的工具手动来完成这个过程,比如向baidu.com发送一个http/1.1请求:

在Powershell中执行命令:

1
2
PS C:\Users\Administrator>telnet
Microsoft Telnet>open baidu.com 80

然后输入如下请求数据(1.1的格式与2.0有一些不同,比如在HTTP/1.1中Host是必须的,URL的位置为URI):

1
2
GET / HTTP/1.1
Host: baidu.com

因为telnet软件在输入时不会清屏,所以输入的字符会和原来的字符重叠,这是正常的情况。

服务器返回结果如下: