资讯动态

Python爬虫实战:Fiddler抓包与手机APP接口数据抓取

发布时间:2026/10/17 18:35:43 来源:尧图企业网站定制
简介本资源面向具备Python基础、对网络请求与爬虫开发感兴趣的技术爱好者与研发工程师围绕手机APP数据传输的抓取与解析展开重点讲解如何借助Fiddler抓包定位登录与话题接口并通过urllib2构造HTTP请求、携带Header与表单数据模拟客户端登录进而获取并解析JSON格式的返回内容。资源包共1个文件为PDF格式整体约143KB内容紧凑便于随时查阅与对照实践。目前已有366人学习下载说明该主题在爬虫入门与实战方向具有一定关注度。读者可从中掌握从数据捕获、请求构造到JSON解析提取的完整流程设计思路理解Header、Cookie与表单参数在模拟登录中的作用并借鉴话题列表获取与下拉加载更新的实现方式为开发定制化信息采集系统打下基础。需注意案例基于Python2.7环境实际应用时应结合平台反爬机制与相关法律法规、服务条款做适当调整。1. 从抓包到落库手机 APP 传输数据抓取到底在抓什么很多人第一次听到「Python爬虫抓取手机APP传输数据」脑子里浮现的是网页上那种右键查看源代码就能扒下来的场景。但真到动手时才发现APP 里根本没有 HTML 给你解析Fiddler 抓出来的是一串串 POST 请求和 JSON 响应连个像样的标签都找不到。这个资源要解决的就是这个问题当目标数据不在网页上而是藏在 APP 客户端与服务端之间的 HTTP 接口里怎么用 Python 把这条链路复现出来。它适合两类人一类是有 Python 基础、做过网页爬虫但没碰过 APP 接口抓取的研发另一类是想理解移动端数据交互结构、需要做数据采集系统设计的技术爱好者。核心链路其实就三步用 Fiddler 抓包定位接口用 urllib2 构造带 Header 和 Cookie 的请求模拟登录再对返回的 JSON 做递归解析和分页加载。资源里以某课程表类 APP 的话题接口为例把登录、取话题、下拉加载更多这三段代码都跑通了环境是 Python 2.7代码可直接复现。下面按实际拆解顺序把每一步的参数含义和容易翻车的地方讲清楚。2. 抓包定位接口Fiddler 配置与请求结构拆解2.1 为什么 APP 抓包比网页抓包更依赖工具网页爬虫可以直接在浏览器开发者工具里看 Network 面板请求头、响应体、Cookie 一目了然。但 APP 运行在手机上你没有开发者工具可用必须借助中间人代理工具把 HTTP 流量截下来。Fiddler 在这里扮演的角色就是「中间人」手机的网络请求先经过 FiddlerFiddler 记录完整请求和响应后再转发给服务端。配置的关键是让手机信任 Fiddler 的证书并把手机代理指向运行 Fiddler 的那台机器。这一步如果没做对抓到的全是 CONNECT 隧道请求看不到任何明文数据。配置完成后在手机上操作目标 APPFiddler 左侧会话列表会实时刷新。你需要关注的是那些 Content-Type 为application/x-www-form-urlencoded或application/json的 POST 请求尤其是 URL 里带login、action这类关键词的条目。资源里定位到的登录地址是http://120.55.151.61/V2/StudentSkip/loginCheckV4.action话题列表地址是http://120.55.151.61/V2/Treehole/Message/getMessageByTopicIdV3.action。这两个地址就是后续所有代码的靶子。2.2 从 Fiddler 会话里提取关键请求参数点开一个会话切到 Inspectors 面板重点看三个区域Request Headers、RequestBody 的 WebForms 视图、Response Body 的 JSON 视图。资源里的登录表单包含这些字段字段名示例值说明phoneBrandMeizu手机品牌服务端可能用于风控platform1平台标识1 通常代表 AndroiddeviceCode868033014919494设备唯一标识模拟器上每次可能不同accountFCF030E1F2F6341C1C93BE5BBC422A3D加密后的账号passwordA55B48BB75C79200379D82A18C5F47D6加密后的密码phoneVersion16系统版本号channelMXMarket应用市场渠道phoneModelM040手机型号versionNumber7.2.1APP 版本号这些字段里account 和 password 是加密过的你不需要知道加密算法直接从抓包结果里复制即可。deviceCode 和 phoneModel 这类设备信息服务端一般只做记录不做强校验但缺失可能导致返回异常。Header 里必须带上的有Content-Type、User-Agent、Host、Connection、Accept-Encoding和Content-Length。资源里特别提到「一开始没有加 header 得到的是登录错误」这就是典型的 Header 校验翻车。提示Fiddler 抓包时如果看到的是乱码或二进制内容先检查响应头的 Content-Encoding 是否为 gzip。Python 的 urllib2 不会自动解压 gzip需要在代码里手动处理或者把请求头里的Accept-Encoding去掉让服务端返回明文。2.3 用 Python 复现登录请求的完整代码资源里的登录代码基于 Python 2.7 的 urllib2 和 cookielib核心思路是用HTTPCookieProcessor自动管理 Cookie用urllib2.Request构造带 Header 的 POST 请求。下面把这段代码整理成可直接运行的版本并补上关键注释# -*- coding: utf-8 -*- import urllib2 from cookielib import CookieJar # 登录接口地址来自 Fiddler 抓包结果 loginUrl http://120.55.151.61/V2/StudentSkip/loginCheckV4.action # 请求头必须与抓包结果一致尤其是 User-Agent 和 Content-Type headers { Content-Type: application/x-www-form-urlencoded; charsetUTF-8, User-Agent: Dalvik/1.6.0 (Linux; U; Android 4.1.1; M040 Build/JRO03H), Host: 120.55.151.61, Connection: Keep-Alive, Accept-Encoding: gzip, Content-Length: 207, } # 表单数据account 和 password 直接使用抓包得到的加密值 loginData (phoneBrandMeizuplatform1deviceCode868033014919494 accountFCF030E1F2F6341C1C93BE5BBC422A3D phoneVersion16 passwordA55B48BB75C79200379D82A18C5F47D6 channelMXMarketphoneModelM040versionNumber7.2.1) # 用 CookieJar 自动保存服务端下发的 Cookie cookieJar CookieJar() opener urllib2.build_opener(urllib2.HTTPCookieProcessor(cookieJar)) # 构造请求并发送 req urllib2.Request(loginUrl, loginData, headers) loginResult opener.open(req).read() print loginResult这段代码的逻辑说明CookieJar负责在内存中保存登录成功后服务端返回的 Cookie后续请求通过同一个opener发出时会自动带上这些 Cookie从而维持登录态。Content-Length必须与实际表单字符串的字节长度一致否则服务端可能截断请求体。资源里登录成功的判断依据是返回的 JSON 中包含账号信息实际使用时可以用json.loads(loginResult).get(data)是否存在来判断。参数调整方面如果你换一个 APP 或换一个账号需要改的是loginUrl、headers里的Host和User-Agent、以及loginData里的加密字段。Content-Length建议用len(loginData)动态计算不要硬编码否则表单字段一改就会出错。3. 话题数据抓取与 JSON 递归解析从 timestamp 到无限加载3.1 话题接口的请求构造与分页机制登录成功后资源里的下一步是抓取话题列表。话题接口地址是http://120.55.151.61/V2/Treehole/Message/getMessageByTopicIdV3.action请求方式同样是 POST表单里关键字段是timestamp、topicId、selectType和genderType。其中timestamp是分页游标第一次请求传 0服务端返回当前页数据和下一页的timestampLong后续请求把这个值传回去就能加载更多。# 话题接口地址 topicUrl http://120.55.151.61/V2/Treehole/Message/getMessageByTopicIdV3.action # 首次请求timestamp 传 0 topicData (timestamp0phoneBrandMeizuplatform1genderType-1 topicId19phoneVersion16selectType3 channelMXMarketphoneModelM040versionNumber7.2.1) # Content-Length 需要根据实际表单长度调整 headers[Content-Length] str(len(topicData)) topicRequest urllib2.Request(topicUrl, topicData, headers) topicHtml opener.open(topicRequest).read() topicJson json.loads(topicHtml)这里topicId19是目标话题的编号selectType3和genderType-1是筛选条件分别控制排序方式和性别过滤。这些值都来自 Fiddler 抓包不同话题只需要改topicId。Content-Length从登录时的 207 变成了 147 或 159这就是为什么不能硬编码——表单字段数量变了长度必须跟着变。3.2 JSON 解析提取嵌套字段与递归加载服务端返回的 JSON 结构是嵌套的顶层有status和datadata里有timestampLong和messageBOs数组每个messageBO里又有studentBO子对象。资源里的fetch_data函数做了扁平化提取把每条话题的content、schoolName、messageId、gender、time抽成一个字典。import json def fetch_data(json_data): 从 JSON 响应中提取话题列表和下一页游标 data json_data[data] timestampLong data[timestampLong] # 下一页的 timestamp messageBO data[messageBOs] # 话题数组 topicList [] for each in messageBO: topicDict {} # 有些话题可能没有正文用 get 做安全判断 if each.get(content, False): topicDict[content] each[content] topicDict[schoolName] each[schoolName] topicDict[messageId] each[messageId] topicDict[gender] each[studentBO][gender] topicDict[time] each[issueTime] print each[schoolName], each[content] topicList.append(topicDict) return timestampLong, topicList这段代码的关键点有三个第一each.get(content, False)用来过滤掉没有正文的条目避免 KeyError第二studentBO是嵌套对象取值时要逐层访问第三返回的timestampLong是下一页的游标必须原样传给下一次请求。3.3 递归加载与终止条件资源里的load函数用递归实现无限加载每次请求成功后用返回的timestampLong构造下一次请求直到服务端返回的status不等于 1 为止。def load(timestamp, headers, url): 递归加载更多话题 headers[Content-Length] 159 loadData (timestamp%sphoneBrandMeizuplatform1genderType-1 topicId19phoneVersion16selectType3 channelMXMarketphoneModelM040versionNumber7.2.1 % timestamp) req urllib2.Request(url, loadData, headers) loadResult opener.open(req).read() loginStatus json.loads(loadResult).get(status, False) if loginStatus 1: print load successful! timestamp, topicList fetch_data(json.loads(loadResult)) load(timestamp, headers, url) # 递归调用 else: print load fail print loadResult return False递归的终止条件是status ! 1通常出现在没有更多数据或登录态失效时。实际使用时建议加一个最大页数限制或time.sleep控制频率否则递归太深可能触发 Python 的递归深度限制也容易给服务端造成压力。资源里没有加延时这是可以优化的点。注意Python 2.7 的urllib2在 Python 3 中已经拆分为urllib.request和http.cookiejar如果要在 Python 3 环境运行需要改导入语句和字符串编码方式。资源代码是 Python 2.7 实测有效的迁移时注意print语句和CookieJar的导入路径。4. 避坑与排查Header、Cookie、编码和递归的五个翻车点4.1 登录返回错误但抓包显示成功现象Fiddler 里手动重放请求能拿到正确响应但 Python 代码执行后返回登录失败或空数据。原因最常见的是 Header 缺失或不一致。资源里明确提到「一开始没有加 header 得到的是登录错误」。服务端会校验User-Agent、Host、Content-Type等字段尤其是User-Agent必须与 APP 客户端一致否则可能被识别为异常请求。解决把 Fiddler 抓到的 Request Headers 完整复制到代码里逐项比对。特别注意Content-Length必须与实际请求体的字节长度一致Python 2.7 中中文字符和特殊符号会影响长度计算。4.2 Cookie 丢失导致后续请求 401现象登录接口返回成功但话题接口返回未登录或权限错误。原因没有用同一个opener发送后续请求或者CookieJar没有正确绑定到opener。资源里用urllib2.build_opener(urllib2.HTTPCookieProcessor(cookieJar))创建 opener所有请求都通过这个 opener 发出Cookie 才能自动携带。解决确保登录和后续请求共用同一个opener实例。如果手动管理 Cookie需要从登录响应的Set-Cookie头里提取并拼接到后续请求的Cookie头中。4.3 gzip 压缩导致 JSON 解析失败现象json.loads()报错提示无法解析或内容为乱码。原因请求头里带了Accept-Encoding: gzip服务端返回 gzip 压缩的响应体而 urllib2 不会自动解压。解决两种方案。一是去掉请求头里的Accept-Encoding让服务端返回明文二是在读取响应后用gzip模块解压import gzip import StringIO response opener.open(req) if response.info().get(Content-Encoding) gzip: buf StringIO.StringIO(response.read()) gz gzip.GzipFile(fileobjbuf) content gz.read() else: content response.read()4.4 递归加载触发最大递归深度现象程序运行一段时间后抛出RuntimeError: maximum recursion depth exceeded。原因load函数用递归实现分页数据量大时递归层数超过 Python 默认限制通常 1000 层。解决把递归改成循环或者加一个页数上限。循环版本更直观def load_all(timestamp, headers, url, max_pages50): page 0 while page max_pages: headers[Content-Length] 159 loadData (timestamp%sphoneBrandMeizuplatform1genderType-1 topicId19phoneVersion16selectType3 channelMXMarketphoneModelM040versionNumber7.2.1 % timestamp) req urllib2.Request(url, loadData, headers) loadResult opener.open(req).read() status json.loads(loadResult).get(status, False) if status ! 1: break timestamp, topicList fetch_data(json.loads(loadResult)) page 1 time.sleep(1) # 控制频率避免触发风控4.5 设备信息硬编码导致换设备后失效现象代码在某一台设备或模拟器上能跑换一台设备后登录失败。原因deviceCode、phoneModel、phoneBrand等字段与抓包时的设备绑定服务端可能校验设备一致性。解决如果只是做技术验证保持抓包时的设备信息不变即可。如果需要多设备运行需要动态获取设备信息或使用固定的虚拟设备参数。资源里的deviceCode868033014919494和phoneModelM040是抓包时的真实值直接复用即可。5. 从能跑到好用接口稳定性验证与请求频率控制代码能跑通只是第一步真正要把它用起来还得解决两个问题怎么确认接口没有悄悄改结构以及怎么控制请求频率不被服务端限制。接口稳定性验证我一般会做三件事。第一在fetch_data里加字段存在性检查把each[schoolName]改成each.get(schoolName, )避免服务端新增或删除字段时直接抛异常。第二把每次响应的status和message字段打印出来服务端返回非 1 状态时能第一时间看到原因。第三定期用 Fiddler 重新抓一次包比对请求头和表单字段是否有变化——APP 版本更新后接口参数变更是常有的事资源里的versionNumber7.2.1就是一个会随版本变化的字段。请求频率控制方面资源里的递归加载没有任何延时实际跑起来会在几秒内发出几十个请求。我的习惯是在每次请求后加time.sleep(1)到time.sleep(3)的随机延时模拟人工操作节奏。如果目标接口对频率敏感还可以在 Header 里加上X-Requested-With或自定义的请求标识但前提是抓包时确认客户端确实带了这些头。还有一个容易被忽略的点是Content-Length的动态计算。资源里登录用 207、话题首次用 147、加载更多用 159这三个值都是硬编码的。一旦表单字段增减这些值就会错。我后来改成headers[Content-Length] str(len(loadData))让代码自己算省去了每次手动调整的麻烦。这个改动很小但能避免很多「明明抓包一样却请求失败」的玄学问题。从那以后我每次写这类接口抓取代码都会先把 Fiddler 抓到的请求头和表单字段完整复制到一个配置文件里代码只负责读取和拼接不硬编码任何长度和字段值。这样换接口、换参数时只需要改配置不用动逻辑。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑