1. 移动端数据采集的底层逻辑与方案选型
1.1 为什么移动端采集比网页采集更棘手
做过网页采集的人第一次转向移动端时,往往会有一个错觉:不就是换个请求地址吗?实际上,移动端的数据采集难度比网页端高出一个量级。网页端你打开开发者工具,所有请求一目了然,请求头、参数、响应体全部透明。但移动端App是一个封装好的黑盒,你无法直接看到它发出了哪些请求、带了什么参数、走了什么协议。
更麻烦的是,移动端App普遍采用加密传输。以资讯类App为例,客户端会对请求参数做签名处理,常见的有MD5签名、HMAC签名、AES加密等。你就算抓到了请求,直接复制到代码里重放,大概率会返回“签名校验失败”或者“参数错误”。这就意味着,移动端采集的核心难点不在于写代码,而在于搞清楚请求的构造规则。
我刚开始接触移动端采集时,踩过最大的坑就是以为抓包工具能直接看到明文参数。实际上,很多App的关键参数在抓包工具里显示的是加密后的字符串,你需要逆向分析或者通过其他手段还原。这个过程需要耐心,也需要对HTTP协议、加密算法有基本的理解。
1.2 抓包工具的选择:为什么是Fiddler
市面上抓包工具不少,Charles、Wireshark、mitmproxy、Fiddler各有各的适用场景。我选Fiddler作为主力工具,主要基于以下几个考量。
第一,Fiddler对HTTP/HTTPS的解密支持非常成熟。它本质上是一个中间人代理,客户端把请求发给Fiddler,Fiddler再转发给服务器,服务器返回的数据也经过Fiddler再回到客户端。只要在客户端安装了Fiddler的根证书,HTTPS流量就能被解密查看。这个机制虽然简单,但Fiddler在证书管理和会话展示上做得非常顺手。
第二,Fiddler的过滤和搜索功能很强。移动端App的请求量非常大,一个页面可能触发几十个请求,其中大部分是图片、日志、埋点等无关请求。Fiddler支持按域名、按响应类型、按状态码过滤,能快速定位到你需要的那几个接口。
第三,Fiddler支持自定义脚本。它内置了JScript.NET脚本引擎,你可以写脚本自动修改请求或响应,比如批量替换请求头、自动解密某个字段。这个功能在调试阶段非常实用。
当然,Fiddler也不是没有缺点。它只支持Windows平台(虽然有Linux和Mac的替代方案,但体验不如Windows原生),而且界面相对老旧。但就移动端抓包这个场景来说,Fiddler的稳定性和功能完整度依然是第一梯队的。
1.3 整体技术路线拆解
整个采集方案可以拆成四个阶段:环境搭建、抓包分析、请求模拟、数据解析与存储。
环境搭建阶段需要解决三个问题:Fiddler的安装与配置、移动端设备的代理设置、HTTPS证书的安装与信任。这三个环节任何一个出问题,都会导致抓不到包或者抓到乱码。
抓包分析阶段的核心任务是找到目标接口。你需要反复操作App,观察Fiddler会话列表的变化,通过对比不同操作下的请求差异,定位到承载目标数据的那个接口。这个阶段最考验耐心,因为App的请求命名往往没有规律,你需要靠响应内容来判断。
请求模拟阶段是把抓到的请求翻译成Python代码。这里的关键是还原请求头、请求参数和签名逻辑。请求头里的User-Agent、Cookie、Referer等字段往往有校验,参数里的签名更是必须还原。我通常会先用requests库直接重放抓到的请求,确认能拿到数据,再逐步精简参数,找到最小必要集合。
数据解析与存储阶段相对简单,主要是JSON解析和数据库写入。但要注意的是,移动端接口返回的数据结构往往比网页端更复杂,嵌套层级更深,需要仔细梳理字段关系。
2. Fiddler抓包环境搭建与配置细节
2.1 Fiddler的安装与基础配置
Fiddler的安装没什么好说的,官网下载安装包,一路下一步就行。但安装完成后的配置才是关键,很多人卡在这一步。
首先打开Fiddler,进入Tools > Options,在HTTPS选项卡里勾选“Capture HTTPS CONNECTs”和“Decrypt HTTPS traffic”。这两个选项是解密HTTPS流量的前提。勾选后Fiddler会提示你安装根证书,点击“Yes”即可。安装完成后,你可以在Windows的证书管理器里看到Fiddler的根证书,说明安装成功。
接下来是Connections选项卡。默认情况下Fiddler监听的是8888端口,这个端口你可以改成其他值,但没必要。关键是勾选“Allow remote computers to connect”,这个选项允许局域网内其他设备把Fiddler作为代理。不勾选的话,手机连上来也抓不到包。
还有一个细节:Fiddler默认只捕获来自浏览器的流量,对于非浏览器进程的流量需要额外配置。在Tools > Options > Connections里,有一个“Act as system proxy on startup”选项,勾选后Fiddler会自动设置系统代理。但移动端抓包不依赖这个,手机是手动设置代理的。
配置完成后,建议重启一次Fiddler,确保所有设置生效。我遇到过好几次配置没生效的情况,重启后就好了。
2.2 移动端代理设置与证书安装
移动端设置代理的步骤因系统而异,但核心逻辑是一样的:让手机的HTTP/HTTPS流量经过Fiddler所在的电脑。
以Android为例,进入WiFi设置,长按当前连接的WiFi,选择“修改网络”,在高级选项里把代理设为“手动”,主机名填电脑的局域网IP,端口填8888。这里有个坑:电脑的IP必须是局域网IP,不能是127.0.0.1。你可以在电脑上打开命令行,输入ipconfig查看局域网IP,通常是192.168.x.x或10.x.x.x。
设置完代理后,用手机浏览器访问一个HTTP网站,比如http://www.example.com,如果Fiddler里能看到这个请求,说明代理设置成功。如果看不到,检查防火墙是否拦截了8888端口,或者手机和电脑是否在同一个局域网。
接下来是安装证书。在手机浏览器里访问http://电脑IP:8888,Fiddler会返回一个页面,点击“FiddlerRoot certificate”下载证书。下载完成后,在手机的设置里找到“安全”或“加密与凭据”,选择“从存储设备安装证书”,找到刚才下载的文件安装。
这里有个关键点:Android 7.0及以上版本,App默认不信任用户安装的证书。也就是说,你安装了Fiddler证书,但App的HTTPS流量依然无法解密。解决办法有两个:一是把证书安装到系统证书区(需要root权限),二是用Xposed模块或VirtualApp等工具绕过证书校验。如果目标App没有做证书固定(SSL Pinning),用第一种方法就行;如果做了证书固定,就需要更复杂的处理。
iOS的证书安装相对简单,下载证书后在“设置 > 通用 > 关于本机 > 证书信任设置”里手动信任即可。但iOS对代理的管控更严格,某些App会检测代理设置,发现代理后直接拒绝请求。
2.3 抓包环境验证与常见问题排查
环境搭好后,不要急着去抓目标App,先用一个简单的场景验证整个链路是否通畅。我通常会用手机浏览器访问一个HTTPS网站,比如百度,然后在Fiddler里看能不能解密出明文内容。如果能,说明证书安装正确;如果不能,说明证书没被信任。
验证通过后,再打开目标App。这时候你会看到Fiddler里涌出大量请求。不要慌,先按域名过滤,找到目标App的主域名。然后逐个点击请求,看响应内容里有没有你需要的数据。
常见问题有几个:一是Fiddler里显示“Tunnel to”,说明HTTPS解密没生效,检查证书是否安装正确;二是请求显示红色感叹号,说明连接失败,可能是代理设置有问题;三是响应内容乱码,说明编码格式不对,在Fiddler的Response面板里切换编码即可。
还有一个容易被忽略的问题:Fiddler的会话列表默认只保留最近的请求,如果请求太多,旧的会被冲掉。你可以在File > Capture Traffic里调整缓冲区大小,或者用Filters过滤掉无关请求。
3. 今日头条App接口分析与请求还原
3.1 定位目标接口的实操方法
打开今日头条App,在Fiddler里观察请求。你会发现请求量非常大,有图片、有日志、有埋点、有广告,真正承载内容的接口往往藏在其中。
我的方法是:先清空Fiddler会话列表,然后在App里做一个明确的操作,比如刷新首页,观察新增的请求。通常首页内容接口的响应体比较大,返回的是JSON格式,里面包含文章标题、摘要、图片链接等字段。你可以按响应大小排序,快速找到大响应体的请求。
找到疑似接口后,不要急着写代码,先在Fiddler里仔细分析这个请求。看它的请求方法(GET还是POST)、请求URL、请求头、请求参数、响应结构。今日头条的接口通常是GET请求,参数里包含设备信息、用户信息、时间戳、签名等。
这里有个技巧:在Fiddler里右键点击请求,选择“Copy > Just URL”,可以把完整URL复制出来。然后把这个URL粘贴到浏览器的无痕模式里访问,看能不能拿到数据。如果能,说明这个接口没有严格的签名校验,直接模拟请求即可;如果不能,说明有签名或Cookie校验,需要进一步分析。
3.2 请求头与关键参数拆解
今日头条的请求头里有几个关键字段需要特别注意。
User-Agent字段标识了客户端类型和版本。今日头条的UA通常包含“NewsArticle”或“news_article”等关键词,后面跟着版本号。这个字段必须和抓包时一致,否则服务器可能返回不同的数据格式。
Cookie字段里包含了用户标识和会话信息。今日头条的Cookie里通常有“sessionid”、“sid_tt”等字段,这些是用户登录后的凭证。如果你不登录,有些接口也能访问,但返回的数据可能不完整。
请求参数里最麻烦的是签名参数。今日头条的接口通常有一个“_signature”参数,这个参数是通过特定算法生成的,算法逻辑在App的so文件里。如果你直接复制抓到的签名,短时间内能用,但签名有时效性,过期后就失效了。
我的做法是:先不纠结签名,用抓到的完整URL和请求头直接重放,看能拿到多少数据。如果签名过期导致失败,再考虑逆向分析签名算法。对于学习目的来说,抓包后短时间内重放通常能成功,足够用来练习数据解析和存储。
3.3 用Python还原请求的完整过程
假设我们已经抓到了一个可用的请求,现在用Python来还原它。首先安装requests库:
pip install requests然后写一个最简单的重放脚本:
import requests url = "抓到的完整URL" headers = { "User-Agent": "抓到的UA", "Cookie": "抓到的Cookie", # 其他请求头 } response = requests.get(url, headers=headers) print(response.status_code) print(response.text[:500])运行后如果返回200且内容正常,说明请求还原成功。如果返回403或签名错误,说明有额外的校验。
接下来是精简参数。把URL里的参数逐个删除,看哪些是必需的,哪些是可选的。通常设备信息、时间戳、签名是必需的,其他参数可以省略。精简后的URL更简洁,也更容易维护。
如果签名是必需的且有时效性,你需要分析签名算法。今日头条的签名算法涉及多个参数的拼接和加密,逆向难度较高。对于学习目的,我建议先用抓到的签名做短时间内的数据采集,或者寻找其他不需要签名的接口。今日头条有一些接口(如搜索接口)的签名校验相对宽松,适合练手。
4. 数据解析、存储与采集策略优化
4.1 JSON响应结构解析与字段提取
今日头条的接口返回的是JSON格式,结构通常是这样:
{ "data": [ { "title": "文章标题", "abstract": "文章摘要", "article_url": "文章链接", "publish_time": "发布时间", "source": "来源", "image_list": [...] } ], "has_more": true, "next_page": "..." }解析的时候用response.json()直接转成字典,然后逐层提取。注意有些字段可能是None,提取时要加判断。图片链接通常在image_list里,是一个数组,每个元素包含url字段。
我习惯把提取逻辑封装成函数,传入JSON对象,返回一个扁平化的字典。这样后续存储时更方便。
def parse_article(item): return { "title": item.get("title", ""), "abstract": item.get("abstract", ""), "url": item.get("article_url", ""), "publish_time": item.get("publish_time", ""), "source": item.get("source", ""), "images": ",".join([img.get("url", "") for img in item.get("image_list", [])]) }4.2 数据存储方案:从CSV到数据库
小规模采集用CSV就够了,简单直接,用pandas的to_csv方法一行搞定。但CSV有几个问题:不支持并发写入、字段多了容易乱、查询不方便。
数据量大了之后,建议用SQLite。SQLite是文件型数据库,不需要额外安装服务,Python内置支持。建表的时候把字段类型定义好,插入的时候用executemany批量插入,效率比逐条插入高很多。
import sqlite3 conn = sqlite3.connect("toutiao.db") cursor = conn.cursor() cursor.execute(""" CREATE TABLE IF NOT EXISTS articles ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, abstract TEXT, url TEXT UNIQUE, publish_time TEXT, source TEXT, images TEXT ) """) cursor.executemany(""" INSERT OR IGNORE INTO articles (title, abstract, url, publish_time, source, images) VALUES (?, ?, ?, ?, ?, ?) """, data_list) conn.commit()注意url字段加了UNIQUE约束,配合INSERT OR IGNORE可以自动去重。这个技巧在采集场景里非常实用,避免重复数据。
4.3 采集频率控制与反爬应对
今日头条对频繁请求有风控机制。如果你短时间内发送大量请求,服务器会返回验证码或者直接封IP。控制采集频率是必须的。
我的做法是:每次请求后随机sleep 1到3秒,模拟人工操作的节奏。如果发现返回状态码异常(如403、429),立即停止采集,等待几分钟后再试。
另外,请求头里的User-Agent和Cookie要定期更换。如果你有多个账号,可以轮换使用。没有多个账号的话,至少把User-Agent换成不同机型的,降低被识别的概率。
还有一个细节:今日头条的接口返回的next_page参数是分页的关键。每次请求后从响应里提取next_page,作为下一次请求的参数。如果next_page为空,说明没有更多数据了,停止采集。
5. 常见问题排查与实操避坑指南
5.1 抓包工具与证书类问题
问题一:Fiddler抓不到手机的请求。排查步骤:确认手机和电脑在同一局域网;确认Fiddler的“Allow remote computers to connect”已勾选;确认手机代理设置正确;确认电脑防火墙没有拦截8888端口。如果都不行,试试关闭电脑的防火墙再测试。
问题二:HTTPS请求显示“Tunnel to”,无法解密。这说明证书没被信任。Android 7.0以上需要把证书安装到系统证书区,或者用VirtualApp等工具绕过。iOS需要在“证书信任设置”里手动开启信任。
问题三:App检测到代理,拒绝请求。有些App会检测系统代理设置,发现代理后直接返回错误。解决办法是用透明代理或者用iptables做流量转发,但这需要root权限。对于学习目的,可以换一个检测不严格的App练手。
5.2 请求模拟与签名类问题
问题四:重放请求返回“签名错误”。签名有时效性,抓到的签名可能已经过期。解决办法是尽快重放,或者分析签名算法。如果签名算法太复杂,可以寻找不需要签名的接口。
问题五:请求返回的数据和App里看到的不一样。可能是请求头里的某个字段缺失,导致服务器返回了不同的数据版本。仔细对比抓包时的请求头和代码里的请求头,确保完全一致。
问题六:Cookie过期导致请求失败。Cookie里的sessionid有时效性,过期后需要重新抓包获取新的Cookie。建议把Cookie存在配置文件里,方便更新。
5.3 数据解析与存储类问题
问题七:JSON解析报错。有些接口返回的不是标准JSON,可能包含多余字符或者编码问题。用response.text查看原始内容,确认格式后再解析。如果是编码问题,设置response.encoding = 'utf-8'。
问题八:数据重复插入。用INSERT OR IGNORE配合UNIQUE约束可以自动去重。如果url字段没有UNIQUE约束,可以在插入前先查询是否存在。
问题九:采集速度太慢。单线程采集确实慢,但多线程容易触发风控。折中方案是用异步IO(aiohttp),在控制频率的前提下提高并发。但要注意,异步请求也要加sleep,否则和单线程没区别。
5.4 实操避坑速查表
| 问题类型 | 具体表现 | 排查方向 | 解决方案 |
|---|---|---|---|
| 抓包失败 | Fiddler无请求 | 代理设置、防火墙 | 检查局域网、端口、代理配置 |
| 证书问题 | Tunnel to | 证书未信任 | 安装证书到系统区或绕过校验 |
| 签名错误 | 403/签名校验失败 | 签名过期或算法不对 | 尽快重放或逆向签名算法 |
| 数据不一致 | 返回内容与App不同 | 请求头缺失 | 对比请求头,补全字段 |
| Cookie过期 | 登录态失效 | sessionid过期 | 重新抓包更新Cookie |
| 解析报错 | JSONDecodeError | 返回非标准JSON | 检查原始内容,处理编码 |
| 数据重复 | 同一条数据多次入库 | 无去重机制 | 加UNIQUE约束,用INSERT OR IGNORE |
| 采集被限 | 429/验证码 | 频率过高 | 加随机sleep,更换UA和Cookie |
这张表是我在实际操作中反复踩坑后总结的,基本上覆盖了移动端采集80%的问题。遇到新问题时,先对照这张表排查,能省不少时间。
6. 采集策略的进阶思考与经验沉淀
6.1 从单接口到多接口的扩展思路
掌握了单个接口的采集方法后,你可以把思路扩展到多个接口。今日头条的App里有很多不同类型的接口:首页推荐、频道列表、搜索、评论、用户主页等。每个接口的请求参数和签名逻辑可能不同,但抓包分析的方法是一样的。
我的建议是先从最简单的接口入手,比如搜索接口。搜索接口的参数少,签名校验相对宽松,适合练手。等熟悉了整个流程,再去攻克首页推荐这种签名复杂的接口。
多接口采集时,建议把每个接口的请求逻辑封装成独立的函数,统一用同一个请求会话(requests.Session)来管理Cookie和连接池。这样代码结构清晰,也方便维护。
6.2 数据采集的边界与合规意识
做数据采集这些年,我越来越意识到边界感的重要性。技术本身没有对错,但使用技术的方式有边界。采集公开数据用于学习研究,和采集用户隐私数据用于商业目的,性质完全不同。
我的原则是:只采集公开可见的内容,不碰用户隐私数据;控制采集频率,不影响目标服务的正常运行;不将采集的数据用于商业用途或二次分发。这些原则不是法律要求,而是作为一个技术从业者的基本自律。
另外,采集到的数据要妥善保管,不要随意分享或上传到公开平台。尤其是包含用户信息的数据,一旦泄露,后果很严重。
6.3 工具链的持续优化方向
Fiddler加requests的组合适合入门和中小规模采集。如果你要长期做数据采集,可以考虑以下几个优化方向。
一是用mitmproxy替代Fiddler。mitmproxy是命令行工具,支持Python脚本扩展,可以自动化处理请求和响应。它更适合集成到自动化流程里,比如自动保存抓到的请求、自动重放、自动解析。
二是用Scrapy框架替代手写requests。Scrapy提供了完整的采集框架,包括调度器、下载器、管道、中间件等组件。对于大规模采集,Scrapy的效率和可维护性远超手写脚本。
三是用代理池解决IP限制问题。如果你需要大量采集,单个IP肯定不够用。代理池可以轮换IP,降低被封的风险。但代理池的搭建和维护成本不低,需要权衡投入产出比。
四是把数据存储从SQLite升级到PostgreSQL或MongoDB。SQLite适合单机小规模,数据量大了之后查询和写入都会变慢。PostgreSQL适合结构化数据,MongoDB适合半结构化数据,根据你的数据类型选择。
这些优化方向不是必须的,而是根据实际需求逐步引入的。我见过很多人一上来就搭复杂的架构,结果采集量根本没到那个级别,白白浪费精力。先把最简单的方案跑通,遇到瓶颈再优化,这才是务实的做法。
6.4 我个人在实际操作中的几点体会
最后分享几点个人体会,都是踩坑踩出来的。
第一,抓包前先想清楚要什么数据。不要打开Fiddler就开始乱抓,先明确目标:你要采集的是文章列表、评论、还是用户信息?目标明确了,才能在海量请求里快速定位。
第二,请求还原不要追求一步到位。先用完整请求跑通,再逐步精简。我见过有人一开始就想构造最小请求,结果调了半天调不通,浪费时间。
第三,签名问题不要死磕。如果签名算法太复杂,逆向成本太高,就换个思路:找不需要签名的接口,或者用抓到的签名做短时间采集。学习阶段,跑通流程比攻克算法更重要。
第四,数据解析要留容错。接口返回的字段可能缺失、类型可能变化,解析代码要加try-except和默认值。我吃过亏,一个字段为None导致整个解析崩溃,采集了几千条数据全丢了。
第五,采集频率宁慢勿快。快了一时爽,被封火葬场。控制好频率,细水长流,比一次性采完然后被封IP划算得多。
这些体会没有什么高深的技术含量,但都是实打实的经验。技术可以学,经验只能攒。希望这些内容能帮你少走点弯路。