
简介一套面向Python爬虫学习与美团App数据采集场景的压缩包工程聚焦签名生成、请求封装、店铺与评论信息解析等核心模块。包内共二十三个文件以七个Python脚本为主干涵盖入口调度、签名计算、主流程请求、列表页与详情页解析等职责另有六个JSON示例数据、五个XML工程配置、一个JavaScript脚本及Markdown说明文档整体约一百五十七KB目录层次清晰方便直接阅读和二次修改。目前已有1565人学习下载。该工程演示了从URL收集、请求网页、解析内容到数据存储的完整爬虫链路并在代码中体现User-Agent设置、JS辅助签名、反爬应对等实践还给出店铺、评论、详情等样例数据用于结果校验。适合具备一定Python基础、希望了解App爬虫签名机制与数据采集流程的开发者既可作为入门到进阶的参考案例也能在研究反爬策略、设计合规采集方案时提供直接素材。1. 解压这份爬虫项目之前先看清它到底能干什么解压一份“本地生活App爬虫.zip”之后大多数人会卡在同一个地方代码能跑但它抓的不是你想抓的数据。这类压缩包其实是一条研究链路先通过中间人抓包拿到App的HTTP请求再反编译定位签名生成逻辑最后用Hook把动态参数补全复现出能主动控制的请求。它能解决的问题是App接口在网络层是加密且带签名的浏览器里看不到抓包也只是一堆乱码。适合手里正好有逆向任务、又不想从零啃协议的人。这篇文章按抓包→反编译→Hook→脚本化这条链路讲每一步的命令、参数和翻车点都会交代清楚。2. 先把路线定对抓包、逆向与Hook为什么一个都不能少整条路线的难点不在某一个工具而在于把三个阶段的产出串起来抓包出协议反编译出算法Hook出参数。顺序反了会事倍功半。我见过有人先把APK反编译了个遍没抓包就去找签名逻辑最后连哪个接口要签名都不知道。所以这一章先把“为什么是这个顺序”讲透。2.1 为什么从抓包入手让请求先“裸奔”App里所有能被采集的数据最后都以HTTP请求的形式出现在网络层。抓包的意义是把请求从黑匣子里拉出来让你看到URL、请求头、请求体、参数名。我一般会先抓一轮完整流量再决定要不要进入逆向阶段。判断标准很直接请求里有没有sign、token、timestamp这类字段。如果有说明每个请求都带签名直接看网页版接口没有参考意义必须进入反编译如果只是普通的公开数据接口抓包之后直接用脚本复现就行根本不用碰代码。这里有个新手常犯的误区一上来就装抓包工具但不对HTTPS解密抓到的全是加密流于是认为App做了高强度防护。其实大多数时候只是证书信任问题密解开之后请求结构一目了然。抓包阶段的目标不是拿到全部数据而是拿到三样东西接口路径、参数名、签名参数的位置。这三样东西直接决定后面往哪个方向搜代码。如果连这三样都没摸清反编译只会变成大海捞针。2.2 反编译不是玄学定位签名与请求生成代码反编译的目的不是把整个App读一遍而是按图索骥。抓到请求后记下几个特殊字段名比如token、sign、nonce然后用jadx按关键词搜索往往能直接跳到生成签名的工具类。常见套路是签名算法集中在一个工具类的静态方法里入参是参数集合出参是一个摘要串。这类类在反编译结果里非常好认全是静态方法内部全是字符串拼接和hash调用几乎没有UI相关代码。比如你搜“sign”命中某个工具类打开之后看到generateSign(Map params)下一步就是分析拼接格式和hash类型。如果APK加固过直接反编译出来的全是壳代码看不到业务逻辑。这时要先过脱壳这一步常见做法是用动态加载方式把dex从内存里dump出来再丢给jadx。这条路稍微麻烦一点但原理不复杂壳再强最终要执行真正的逻辑内存里一定存在完整的dex。脱壳工具选型上不追求最新追求和你的模拟器环境匹配不然又搭进去半天环境问题。2.3 Hook为什么是最后一块拼图静态代码能告诉我们签名由哪些参数算出来但算不出这次请求用到的具体值。时间戳、随机数、设备ID这些参数都是App运行时产生的光看代码没法知道当前值是多少。Hook就是在这里介入让App跑起来在指定的方法入口挂一个回调把入参和返回值打印出来一次性拿到真实请求的完整上下文。选型上Frida是目前最常见的方案。它不需要重打包APK命令行客户端负责连接设备JS脚本描述拦截逻辑改脚本比改代码重打包快得多。代价是要在一个可控的调试环境里运行比如root过的模拟器。Hook不是替代抓包和反编译而是给它们补上动态上下文抓包确认目标接口反编译还原签名结构Hook拿到真实参数值。三条信息凑齐才可能写出一份能主动控制的请求脚本。三个阶段的关系可以简化成下面这张表阶段产出典型工具抓包接口路径、参数名、请求头任意中间人抓包工具反编译签名算法、拼接顺序jadxHook动态参数值、返回值Frida3. 从抓包到反编译三步还原目标App请求的完整链路这一章把静态分析和动态观察连起来先让流量可见再从代码里找算法入口最后确认哪个方法对应哪一条请求。三步走完你就有了一个明确的Hook目标而不是对着反编译源码发呆。3.1 打通HTTPS中间人证书安装与端口转发的最小配置先把抓包入口跑起来。常见做法是用带Web控制台的中间人工具命令只需要一行# 启动带控制台的抓包入口监听8080端口 mitmweb --listen-port 8080说明默认监听8080端口如果被占用换成8888或任意空闲端口。mitmweb会打开一个浏览器控制台所有经过的请求都显示在里面适合边看边改。如果是长期采集我更习惯用mitmdump把流量写到文件命令是mitmdump --listen-port 8080 -w flows.mitm抓完再慢慢分析。接下来让模拟器流量经过这个入口。在模拟器的WiFi设置里找到当前网络把HTTP转发指向电脑IP和刚才的端口。模拟器NAT模式下访问宿主机常用固定IP通常是10.0.2.2真机则填电脑的局域网IP。这里只是系统配置不需要改App。配好之后打开浏览器访问任意网页如果抓包控制台能看到这条请求就说明链路通了。然后是HTTPS证书。用浏览器访问抓包工具的证书下载页安装证书。Android 7以上系统默认不信任用户证书装完用户证书后HTTPS仍然解密不了。常见做法是把证书放到系统证书目录第5章的排查里会给出具体命令。提示抓包环境只影响调试不影响App本身。不要在生产环境机器上做这套调试证书信任链会被污染。3.2 用jadx按关键词反查从字段名倒追加密入口抓包拿到字段名之后就该让jadx上场了。反编译命令很简单# 反编译目标APK输出到output_dir目录 jadx -d output_dir target_app.apk说明-d指定输出目录最后一个参数是APK路径。反编译完成后output_dir下是按包名组织的Java源码。如果只看代码不关心资源加--no-res可以跳过资源解析速度快不少机器核数多的时候用--threads-count调大并行线程数也能明显提速。然后在反编译结果里搜索抓包时看到的参数名。如果请求里有个字段叫x-token直接搜x-token命中点通常就是字符串拼接位置签名算法就在附近。搜不到的时候换URL路径搜比如/api/poi/list从请求入口往上找参数构造逻辑。这里踩过的坑是硬搜参数名搜不到一搜URL路径立刻定位因为App里参数名经常被动态拼接而URL路径通常是完整字面量。3.3 模拟器联调把抓包结果和反编译结果对上抓包和反编译的结果要对上靠的是模拟器联调。先把设备连接确认一下# 查看当前连接的设备 adb devices # 将电脑的8080端口映射到模拟器本地 adb reverse tcp:8080 tcp:8080说明adb reverse把模拟器里的localhost:8080转发到电脑的8080适合宿主机和模拟器在同一台机器上的情况。如果转发不生效回WiFi设置里检查HTTP转发入口是否指向正确的IP和端口。这条命令在部分模拟器上有兼容问题不生效时不要死磕直接在WiFi设置里手动填IP。联调动作启动App手动点击一次搜索立刻回抓包控制台找刚才那条请求记下URL里的路径特征再到jadx代码里搜这个路径找到发请求的代码。这一步的价值在于把“网络侧看到的请求”和“代码侧生成的请求”对齐。对齐之后你知道该Hook的类名、方法名也知道它大概在哪个位置被调用下一步就顺了。4. Hook签名计算用Frida把动态参数变成可复用的代码签名算法在代码里了但你看不到本次请求用到的值。这一章把Frida环境搭起来第一次把动态参数打印出来。环境搭好之后后面所有验证都是在模拟器里点几下屏幕、看几行输出的事情。4.1 环境准备模拟器、Frida与目标App的版本匹配Frida是典型的C/S结构宿主机装Python工具链设备上跑一个frida-server。两边的版本必须一致这是整个流程里最玄学也最频繁出问题的一环。# 在宿主机安装Frida工具链 pip install frida frida-tools # 将frida-server推送到模拟器并启动 adb push frida-server /data/local/tmp/ adb shell chmod 755 /data/local/tmp/frida-server adb shell /data/local/tmp/frida-server 说明frida-server是设备端的驻留服务chmod 755给执行权限最后一条命令在模拟器里后台启动。这个终端窗口不要关关掉服务就停了。验证连接是否正常在宿主机执行frida-ps -U能列出设备进程就说明通了。如果报错先看设备连没连上再确认frida-server有没有起来最后检查版本。我的习惯是固定模拟器和PC两端版本升级任何一边之前都先确认另一边兼容性省得定位问题定位到深夜。4.2 定位签名函数从反编译结果里找到Hook点从jadx里定位到类似这样的方法类名com.demo.business.network.SignHelper方法public static String generateToken(Map params, String secret)。Hook目标就是这个方法。Frida脚本里类名用点分隔方法名直接写参数类型要写全。如果方法有多个重载也就是同名方法参数不同必须用overload指定具体重载否则注入阶段就失败。我一般会先在反编译代码里找到调用点看业务代码是怎么调用的。比如调用是generateToken(params, abc)那么Hook脚本里参数类型就是java.util.Map和java.lang.String。不要凭猜猜重载大概率翻车。反编译代码里调用点就在业务方法旁边顺着点进去看比对着源码猜快得多。4.3 写第一个Frida脚本把参数和返回值打印出来第一次写Hook脚本目标不要定太高先把参数和返回值打出来就行// hook_token.js Java.perform(function () { // 请替换成jadx里定位到的实际类名 var SignHelper Java.use(com.demo.business.network.SignHelper); // 单个实现直接赋值多实现需要用overload区分 SignHelper.generateToken.implementation function (params, secret) { var result this.generateToken(params, secret); console.log(params params.toString()); console.log(secret secret); console.log(result result); // 不修改返回值让App原有逻辑照常执行 return result; }; });运行命令# -U连接USB设备-f冷启动App-l加载脚本 frida -U -f com.demo.package -l hook_token.js --no-pause说明-U是连接设备的方式-f指定要启动的App包名并等待注入-l指定脚本路径--no-pause让App启动后不暂停在入口。跑起来之后手动触发一次App里的搜索终端就会打印出签名参数。打印出来的params可能不止一条手动找包含目标接口特征的那条比如有city_id就是列表请求有order_id就是详情请求。这个脚本只是观察不改返回值App运行逻辑不受影响。5. 高频问题排查抓不到包、Hook闪退、复放即被拦把这条链路完整走一遍下面几个问题几乎人人都会遇到。每一条按“现象→原因→解决”写清楚你可以直接对照自己的报错信息找到对应的处理方式。5.1 HTTPS流量全是Tunnel抓不到任何请求内容现象抓包里只能看到目标域名的连接记录点开只有Tunnel看不到请求路径和参数。原因App检测到证书不受信任TLS握手失败抓包工具只能显示建立连接的状态请求内容没有解密。解决把证书放进系统证书目录而不是用户证书目录# openssl把证书转成DER格式文件名需要按Android证书主题的哈希规则命名 openssl x509 -inform PEM -in mitm.pem -outform DER -out 12345678.0 adb root adb remount adb push 12345678.0 /system/etc/security/cacerts/ adb shell chmod 644 /system/etc/security/cacerts/12345678.0 adb reboot说明12345678.0是示意文件名实际要把证书主题算出的哈希值作为文件名否则系统不加载。adb root和adb remount是为了拿到可写系统分区权限这两个命令在部分模拟器上不支持那就换一个可写系统分区的模拟器镜像别在权限上死磕。5.2 Frida注入后App闪退现象运行frida命令后App启动就闪退或者注入后没有任何输出过几秒进程消失。原因最常见的是frida-server与宿主机版本不一致其次是方法签名写错导致注入异常少数情况是App自身带调试检测。解决先用空脚本验证连接是否正常// empty.js Java.perform(function () { console.log(frida inject ok); });如果空脚本也闪退问题在环境版本不匹配、root权限不足、包名错误。如果空脚本正常、换成正式脚本才闪退就是方法签名错了回jadx里核对参数类型是不是java.util.Map和java.lang.String或者是不是有多个重载没区分。大多数“秒退”其实是签名错误不一定是反调试。5.3 浏览器里能打开的接口脚本复放却被拦截现象在抓包工具里手动改参数能正常返回用Python脚本发送同样的请求却被拦响应里出现验证码或风控提示。原因脚本的请求头太“干净”App真实请求里带的User-Agent、Accept、Cookie等Python默认请求头都没有服务端很容易识别这不是客户端发出的。解决先把抓包里那条成功请求的请求头完整照抄进代码再逐步精简找出哪几个是必须的。同时控制频率用随机间隔而不是固定sleep。我一般会在每次请求后等待2到5秒之间的随机值这个优先级比签名还高。签名算对了但节奏不对照样被断。5.4 反编译结果里搜不到目标参数名现象jadx反编译结果里搜token、sign搜不到有效业务代码。原因字符串被拆开拼接过或者做了混淆字段名在代码里不以完整形式出现。解决搜URL路径特征比如/api/poi/list定位到请求入口后再往上找参数组装逻辑。也可以搜算法常量比如MD5、SHA1从算法类反查封装位置。搜索粒度要从字段名切换到路径特征这是我在这条路上换过的最有效的一个思路。6. 把签名模块搬进Python从手工验证到批量复跑的采集脚本走完前面几步你手里已经有一份能看懂的签名逻辑和一次真实请求的完整参数。最后要做的是把手工验证变成代码里的可复用模块。最常见做法是让签名函数独立成一个模块采集主流程里只负责传参# sign_client.py签名逻辑独立成模块方便采集脚本复用 import hashlib def generate_token(params: dict, secret: str) - str: # 拼接顺序必须和App端一致错了签名就失效 raw for key in sorted(params.keys()): raw f{key}{params[key]} raw raw.rstrip() secret return hashlib.md5(raw.encode(utf-8)).hexdigest()主调用侧保持简单把频率控制和请求头都放在循环外面# collect.py按可控节奏请求避免固定频率 import time import requests from sign_client import generate_token session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 ... # 照抄抓包里的UA }) for page in range(1, 101): params {page: page, city_id: 110000} params[token] generate_token(params, secret_value) resp session.get(https://api.example.com/poi/list, paramsparams) print(page, resp.status_code) time.sleep(2 (page % 3) * 0.5)说明generate_token保持和App源码一致的拼接顺序这是最容易错的一环。如果App源码里是先拼时间戳再拼密钥这里也必须一模一样。请求头里的User-Agent一定要照抄真实抓包结果不要自己编一个。最后强调一下边界这套流程只用于学习和自用采集公开的商户或POI信息时控制频率不碰个人隐私字段商用前拿到明确授权。我在这条路上吃过亏的地方是只盯着签名算法忽略了请求头和节奏。签名算对了请求头缺失照样返回风控页面把脚本间隔改成随机值之后数据才真正能持续跑起来。希望帮到你。本文还有配套的精品资源点击获取