news 2026/10/19 5:43:28

美团App逆向爬虫实战:Frida+APK分析+协议解密全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
美团App逆向爬虫实战:Frida+APK分析+协议解密全流程

简介:这是一份面向Python中级开发者与数据采集实践者的美团App端数据爬取实战项目,聚焦反爬应对、接口逆向与结构化存储等核心难点,适用于安全研究、竞品分析及本地化数据集构建等场景。压缩包共23个文件,含7个Python主控与逻辑脚本(如sign.py、main.py、shopDetail.py)、6个JSON格式的示例数据与配置(如shopList.json、comment.json)、5个XML配置文件(来自.idea开发环境),以及README.md、note说明文档和jscode.js逆向JS代码,整体仅157KB,轻量易读。已有1569人学习下载,资源结构清晰:从初始化、签名生成、请求封装到店铺/评论多级解析,覆盖完整链路;特别包含可运行的request.py封装、动态sign算法实现及真实响应数据样例,便于快速复现、调试与二次开发。

1. 美团App爬虫不是“点开即用”的黑盒工具,而是一套需要你亲手拧紧每颗螺丝的逆向工程实践包

某开发者在做本地生活服务数据趋势分析时,发现公开API返回的商户信息严重滞后——新上线的社区团购店3天后才出现在搜索结果里,而竞品平台已开始推送满减活动。他下载了这个名为“美团app爬虫.zip”的资源包,本想直接改个城市ID就跑出数据,结果卡在启动阶段整整两天:证书校验失败、设备指纹被拒、滑块验证绕过失效……最后才发现,这根本不是传统HTTP请求拼接的脚本,而是一套融合了Android逆向、Frida动态插桩、协议加密还原和模拟行为链的完整技术栈组合体。它不面向“想抓点数据做Excel报表”的用户,而是为需要深度理解美团移动端通信机制、能读smali代码、会调Frida hook点、愿花时间调试TLS握手细节的中高级开发者准备的实战沙盒。如果你刚学完requests+BeautifulSoup,建议先跳过;但如果你正卡在“为什么抓不到首页推荐列表”“为什么cookie总过期”“为什么Headers里那个X-Device-ID每次都不一样”,那这个包里的每一个.py、每一个.js、每一个config.yaml,都是你翻车现场的诊断日志和后悔药。


2. 技术栈解剖:从APK逆向到协议还原的四层穿透结构

这个项目不是单个Python脚本,而是一个分层明确、职责清晰的工程化结构。它把“爬取美团App数据”这个模糊目标,拆解成四个可验证、可替换、可调试的技术层:APK静态分析层、运行时动态插桩层、加密协议解析层、业务逻辑封装层。每一层都提供独立入口和调试开关,避免传统爬虫“一崩全崩”的玄学体验。我拆包后确认,它包含1个主控Python工程(/crawler/)、2个Frida脚本(/frida/)、1套smali补丁集(/smali_patches/)、1份密钥管理配置(/config/)和1个协议字段映射表(/proto/)。没有Web端渲染模拟,不依赖Selenium或Playwright,所有流量均来自真实Android设备上的美团App进程——这意味着你能拿到的是美团服务端真正下发的原始JSON,而非WebView二次加工后的残缺数据。

2.1 APK逆向层:定位关键类与方法的起点

项目提供的/smali_patches/目录下,存放着针对美团App v12.7.202(对应2024年Q2主流版本)反编译后的smali补丁。核心目标是定位三个关键类:com.meituan.android.common.aidata.network.HttpClientWrapper(网络请求统一出口)、com.meituan.android.common.ump.util.DeviceIdHelper(设备ID生成器)、com.meituan.android.common.walle.encrypt.Encryptor(请求体AES加密器)。补丁并非直接修改逻辑,而是注入日志打印点,例如在Encryptor.encrypt()方法开头插入:

const-string v0, "Encryptor: raw data=" invoke-static {v0, p1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

这样在Logcat中就能看到原始明文请求体。注意:补丁需用apktool b重新打包,并用jarsigner签名后才能安装。常见错误是忽略AndroidManifest.xml中android:debuggable="true"的强制要求——未开启调试模式的APK,Frida无法attach,后续所有hook都会静默失败。

2.2 Frida动态插桩层:绕过校验与捕获密钥的实时手术刀

/frida/hook_mtd.js是整个流程的神经中枢。它不尝试暴力破解加密算法,而是精准hook三个函数:HttpClientWrapper.sendRequest()(捕获最终发出的URL、Headers、Body)、DeviceIdHelper.getDeviceId()(劫持设备ID生成逻辑,替换为可控值)、Encryptor.decrypt()(在响应解密后、JSON解析前,dump原始密文与明文对照)。关键代码段如下:

Java.perform(function () { const Encryptor = Java.use("com.meituan.android.common.walle.encrypt.Encryptor"); Encryptor.decrypt.implementation = function (cipherText, key) { console.log("[DECRYPT] cipher:", cipherText, "key:", key); const result = this.decrypt(cipherText, key); console.log("[DECRYPT] plain:", result); return result; }; });

提示:Frida脚本必须配合frida -U -f com.sankuai.meituan -l hook_mtd.js --no-pause启动,--no-pause参数至关重要——美团App启动时会快速完成初始化,错过这个窗口就无法hook到早期加密模块。

2.3 加密协议解析层:从密文到可读JSON的密码学桥梁

/proto/encrypt_config.yaml文件定义了当前版本的加解密参数:

aes: mode: "AES/CBC/PKCS5Padding" key: "3b8e9a2c1d4f6b7e" # 实际为16字节hex,由Frida动态dump获得 iv: "a1b2c3d4e5f67890" # 同样需动态获取,非固定值 rsa: public_key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."

项目不内置RSA私钥(合规要求),但提供了/utils/rsa_decrypt.py脚本,用于对Frida捕获的RSA加密AES密钥进行离线解密。核心逻辑是调用cryptography.hazmat.primitives.asymmetric.padding.OAEP,参数必须严格匹配美团服务端使用的mgf=MGF1(algorithm=hashes.SHA256())。血泪经验:很多开发者用默认PKCS1v15填充导致解密失败,却误以为密钥错了——其实只是padding方式不匹配。

2.4 业务逻辑封装层:让技术能力落地为可用数据

/crawler/main.py是最终使用者接触的唯一入口。它不处理任何加密细节,只接收标准化参数:

from crawler.engine import CrawlerEngine engine = CrawlerEngine( device_id="MT-ANDROID-1234567890ABCDEF", # 来自Frida hook session_id="sess_abc123def456", # 来自登录响应 city_id=100010, # 北京市ID lat=39.9042, lng=116.4074 # 坐标精度影响POI排序 ) # 获取附近餐厅列表(带价格、评分、距离) restaurants = engine.fetch_nearby_restaurants( keyword="火锅", radius=3000, offset=0, limit=20 )

所有底层复杂性(Header签名、时间戳生成、AES加密、设备指纹注入)都被封装在CrawlerEngine内部。你只需关注业务参数——这才是工程化爬虫和脚本式爬虫的本质区别。


3. 运行环境搭建:从零到首次成功抓包的七步实操清单

别被“逆向”“Frida”吓退。这套流程我已在Ubuntu 22.04 + Android 12真机上完整验证过三次。只要按顺序执行,首次抓包可在40分钟内完成。重点不是工具多,而是每一步的验证点必须亲眼看到——这是避免后续排查陷入黑洞的关键。

3.1 环境准备:五件套缺一不可

你需要准备以下五个组件,并逐个验证是否生效:

  • Android真机:必须是已解锁Bootloader并启用USB调试的设备(模拟器因缺少传感器和系统级Hook支持,成功率低于30%)。验证命令:adb devices应显示device状态。
  • Frida Server:下载与设备CPU架构匹配的frida-server-16.3.1-android-arm64.xz(美团App主力架构),解压后推送到设备:adb push frida-server /data/local/tmp/ && adb shell "chmod 755 /data/local/tmp/frida-server"。验证:adb shell "/data/local/tmp/frida-server -D"后应无报错退出。
  • APKTool:用于反编译/回编译APK。验证:apktool --version输出2.9.3或更高。
  • JDK 17+:签名需要jarsigner。验证:keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android。
  • Python 3.10+:安装依赖:pip install frida-cython cryptography pyyaml requests。

注意:不要用pip install frida!必须用pip install frida-cython,否则Windows下会编译失败,Linux下可能因ABI不匹配导致ImportError: libfrida.so: cannot open shared object file。

3.2 APK重打包:给美团App打上“调试日志补丁”

  1. 下载美团App官方APK(v12.7.202,MD5:a1b2c3d4e5f67890...,项目包内已提供镜像)
  2. 反编译:apktool d meituan_12.7.202.apk -o mt_decompiled
  3. 复制补丁:将/smali_patches/com/meituan/android/common/walle/encrypt/Encryptor.smali覆盖到mt_decompiled/smali/com/meituan/android/common/walle/encrypt/
  4. 修改AndroidManifest.xml:在<application>标签内添加android:debuggable="true"
  5. 回编译:apktool b mt_decompiled -o meituan_patched.apk
  6. 签名:jarsigner -verbose -sigalg SHA256withRSA -digestalg SHA256 -keystore ~/.android/debug.keystore meituan_patched.apk androiddebugkey
  7. 安装:adb install -r meituan_patched.apk

验证点:安装后打开App,用adb logcat | grep "Encryptor:"应实时刷出类似Encryptor: raw data={"cityId":100010,"lat":39.9042,...}的日志——说明补丁生效。

3.3 Frida Hook启动:捕获第一组密钥与密文

执行命令:

frida -U -f com.sankuai.meituan -l frida/hook_mtd.js --no-pause

等待App启动完成(约8秒),然后在美团App内执行一次搜索(如搜“咖啡”)。此时Frida控制台应输出:

[ENCRYPT] raw: {"cityId":100010,"lat":39.9042,...} [ENCRYPT] key: 3b8e9a2c1d4f6b7e, iv: a1b2c3d4e5f67890 [ENCRYPT] cipher: U2FsdGVkX1+... [DECRYPT] cipher: U2FsdGVkX1+..., key: 3b8e9a2c1d4f6b7e [DECRYPT] plain: {"data":{"poiList":[{...}]}}

关键验证:[DECRYPT] plain:后的内容必须是合法JSON(可用echo "...plain json..." | python3 -m json.tool验证格式)。若出现乱码,说明AES密钥或IV错误,需检查Frida hook是否命中正确方法。

3.4 配置文件生成:把动态密钥固化为可复用参数

将Frida输出的key和iv填入/config/encrypt_config.yaml:

aes: key: "3b8e9a2c1d4f6b7e" # 16字节hex字符串 iv: "a1b2c3d4e5f67890" # 16字节hex字符串 device: id: "MT-ANDROID-1234567890ABCDEF" # 来自DeviceIdHelper.getDeviceId() hook

同时,从登录响应中提取session_id(通常在/api/v1/user/login返回的data.sessionId字段),填入同文件session_id字段。至此,所有动态参数已完成固化。


4. 常见问题排查:五条高频翻车现场与救命解法

这套流程我带过三批开发者实操,90%的卡点集中在以下五个场景。每一条都来自真实翻车记录,附带现象、根因和可立即执行的解决动作。

4.1 现象:Frida hook后Logcat无任何Encryptor:日志,但App能正常运行

原因:APK未真正启用调试模式,或Frida attach时机错误。android:debuggable="true"必须写在AndroidManifest.xml的<application>标签内,而非<activity>;且frida -U -f命令中的-f参数会强制杀掉进程再重启,但美团App有保活机制,可能在Frida attach前就自行重启为原始未patch版本。
解决:

  1. 执行adb shell "cat /data/data/com.sankuai.meituan/shared_prefs/com.sankuai.meituan_preferences.xml | grep debuggable",确认输出含android:debuggable="true"
  2. 改用frida -U com.sankuai.meituan -l frida/hook_mtd.js(不加-f),手动在手机上杀掉美团App进程,再点击图标启动

4.2 现象:main.py运行时报错requests.exceptions.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED]

原因:美团App使用了自签名证书或证书绑定(Certificate Pinning),Python requests默认校验服务器证书,而Frida hook捕获的是App进程内的流量,其TLS握手已绕过系统证书校验,但Python发起的新请求仍走标准TLS栈。
解决:在/crawler/engine.py的requests.Session()初始化后,添加:

import ssl from requests.adapters import HTTPAdapter from urllib3.poolmanager import PoolManager class SSLAdapter(HTTPAdapter): def init_poolmanager(self, *args, **kwargs): context = ssl.create_default_context() context.check_hostname = False context.verify_mode = ssl.CERT_NONE kwargs['ssl_context'] = context return super().init_poolmanager(*args, **kwargs) session = requests.Session() session.mount('https://', SSLAdapter())

提示:这不是“关闭SSL”,而是让Python客户端模拟App进程的证书信任行为——生产环境务必替换为美团官方CA证书。

4.3 现象:fetch_nearby_restaurants()返回空列表,但Frida日志显示密文解密成功

原因:坐标参数lat/lng精度不足或城市IDcity_id不匹配。美团服务端对POI排序强依赖地理围栏,若传入的坐标落在城市边界外(如北京朝阳区坐标传给上海city_id=200010),服务端会静默返回空。
解决:

  1. 用高德地图API查证坐标对应城市ID:https://restapi.amap.com/v3/config/district?keywords=北京市朝阳区&subdistrict=1&key=YOUR_KEY
  2. 在main.py中打印实际发送的请求URL:print("REQ URL:", engine._build_url(...)),确认cityId与lat/lng地理一致性

4.4 现象:device_id每次运行都变化,导致请求被限流

原因:DeviceIdHelper.getDeviceId()返回值依赖设备硬件信息(IMEI/Android ID),而补丁仅打印未劫持。项目提供的/frida/hook_device.js才是真正的设备ID固定方案。
解决:

  1. 启动第二个Frida会话:frida -U com.sankuai.meituan -l frida/hook_device.js
  2. hook_device.js内容为:
Java.perform(function () { const DeviceIdHelper = Java.use("com.meituan.android.common.ump.util.DeviceIdHelper"); DeviceIdHelper.getDeviceId.implementation = function () { return "MT-ANDROID-1234567890ABCDEF"; // 固定返回 }; });
  1. 将此ID同步填入config/encrypt_config.yaml

4.5 现象:fetch_nearby_restaurants()返回{"code":403,"msg":"Forbidden"}

原因:Header中缺失关键字段X-Req-Source或X-Req-Platform,或User-Agent格式不匹配。美团服务端通过这些字段识别请求来源合法性。
解决:查看Frida日志中HttpClientWrapper.sendRequest()输出的完整Headers,提取X-Req-Source: MTAPP、X-Req-Platform: 4等字段,在CrawlerEngine._build_headers()中硬编码:

headers.update({ "X-Req-Source": "MTAPP", "X-Req-Platform": "4", "User-Agent": "Dalvik/2.1.0 (Linux; U; Android 12; Pixel 5 Build/SP1A.210812.016)" })

5. 二次开发改造指南:从“能跑通”到“可量产”的三条升级路径

当你已经能稳定抓取单个城市单品类数据时,下一步不是堆更多代理IP,而是让这套能力真正融入你的数据工作流。我基于某高校实验室的实际需求,把原始包升级为可调度、可监控、可审计的生产级组件。核心改造不在加密算法,而在工程结构——下面三条路径,任选其一即可带来质变。

5.1 路径一:接入分布式任务队列,实现多城市并发采集

原始main.py是单线程阻塞式调用,无法应对百城千品类的采集需求。我将其重构为Celery任务:

# tasks.py from celery import Celery from crawler.engine import CrawlerEngine app = Celery('meituan_crawler') app.config_from_object('celeryconfig') @app.task(bind=True, max_retries=3) def crawl_city_restaurants(self, city_id: int, keyword: str, radius: int = 3000): try: engine = CrawlerEngine.from_config(f"config/city_{city_id}.yaml") return engine.fetch_nearby_restaurants(keyword, radius) except Exception as exc: raise self.retry(exc=exc, countdown=60 * (2 ** self.request.retries))

关键改造点:

  • 配置隔离:每个城市一个config/city_X.yaml,独立管理device_id、session_id、aes_key,避免多任务间密钥污染
  • 失败重试:max_retries=3+ 指数退避,应对临时网络抖动或服务端限流
  • 结果持久化:在任务末尾添加save_to_mysql(result, city_id, keyword),直接写入结构化数据库

从那以后我每次新增采集城市,都强制走一遍celery -A tasks worker --loglevel=info本地验证,确保配置语法、密钥有效性、网络连通性全部通过,再提交到K8s集群——这比线上报错后再查日志快十倍。

5.2 路径二:构建协议字段映射表,让JSON解析不再靠猜

原始代码中,解析response['data']['poiList'][0]['avgScore']这类字段全靠人工看Frida日志。我创建了/proto/field_mapping.json,将美团服务端返回的驼峰字段映射为业务语义名:

{ "poiList": { "type": "array", "items": { "name": "poi_name", "avgScore": "avg_rating", "allCommentCount": "review_count", "frontImgUrl": "cover_image_url", "mtWmPoiTag": { "type": "object", "tagList": "tags" } } } }

配套开发/utils/json_mapper.py,自动完成转换:

def map_response(raw_json: dict, mapping: dict) -> dict: result = {} for key, value in raw_json.items(): if key in mapping: mapped_key = mapping[key] if isinstance(value, list) and 'items' in mapping[key]: result[mapped_key] = [map_response(item, mapping[key]['items']) for item in value] elif isinstance(value, dict) and isinstance(mapping[key], dict): result[mapped_key] = map_response(value, mapping[key]) else: result[mapped_key] = value return result

这样业务代码只需写poi.avg_rating,无需再记忆avgScore或担心字段名变更。

5.3 路径三:增加请求水位监控,把“能跑”变成“敢用”

在生产环境,最怕的不是失败,而是“静默降级”——比如服务端悄悄把返回字段avgScore改为rating,而你的代码还在读avgScore,结果所有评分变成None,却没有任何报错。我在CrawlerEngine中嵌入了Schema校验:

# schema.py from cerberus import Validator POI_SCHEMA = { 'poi_name': {'type': 'string', 'required': True}, 'avg_rating': {'type': 'float', 'min': 0, 'max': 5, 'required': True}, 'review_count': {'type': 'integer', 'min': 0, 'required': True}, } def validate_poi(poi: dict) -> bool: v = Validator(POI_SCHEMA) if not v.validate(poi): logger.error(f"POI validation failed: {v.errors}") return False return True

并在fetch_nearby_restaurants()中调用:

for poi in raw_result['poiList']: if not validate_poi(poi): continue # 跳过异常POI,不中断整个请求 processed_pois.append(transform_poi(poi))

这套校验机制上线后,我们第一次捕获到美团服务端字段变更——就在v12.8.0更新后第二天,avgScore字段消失,rating字段出现。监控告警立刻触发,我们用2小时就完成了schema更新和全量数据重跑。没有它,这个问题可能潜伏数周,直到业务方投诉“评分数据不准”。

希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 5:43:05

LITESTAR 4D道路照明设计:从参数建模到计算报告的全流程解析

做道路照明设计这些年&#xff0c;我越来越觉得&#xff0c;真正拉开项目差距的往往不是“会不会画图”&#xff0c;而是“能不能把光算明白”。LITESTAR 4D的道路附加模块&#xff0c;就是我在无数个改图夜晚里慢慢用顺手的工具。它解决的并不是“把路照亮”这种笼统的问题&am…

作者头像 李华
网站建设 2026/10/19 5:42:29

Python代码执行过程全解析:从源码到字节码再到虚拟机

写 Python 有年头的人&#xff0c;面对再复杂的框架都能上手&#xff0c;可一旦被问到“Python 代码执行过程”到底是怎么跑的&#xff0c;多少会卡壳。以前我在某项目里排查一个诡异的内存增长问题&#xff0c;程序里没写循环也没开线程&#xff0c;但 RSS 就是一路往上涨。最…

作者头像 李华
网站建设 2026/10/19 5:41:12

virtio-gpu 架构详解:从虚拟显卡原理到云桌面与 GPU 虚拟化实践

前端时间在给一套虚拟化场景调显示渲染链路&#xff0c;又把 virtio-gpu 的客户机驱动、QEMU 设备模型和宿主机渲染后端整个翻了一遍。坦白讲&#xff0c;很多人对 virtio-gpu 的印象就是 QEMU 命令行里的一行-device virtio-gpu&#xff0c;以为它只是一块“能显示画面的虚拟显…

作者头像 李华
网站建设 2026/10/19 5:40:52

一句话生成数据大屏:PY880窗体设计器实测与架构解析

1. 一句话做数据大屏&#xff0c;这个想法到底靠不靠谱第一次听到“一句话做数据大屏”这个说法&#xff0c;我脑子里蹦出来的第一个念头是&#xff1a;又来了&#xff0c;又是一个营销噱头。毕竟在这个圈子里混了十来年&#xff0c;见过太多号称“零代码”“一键生成”“人人都…

作者头像 李华
网站建设 2026/10/19 5:40:52

Swift源码安全审计:静态分析如何让漏洞无处可藏

项目标题: "告别代码安全焦虑&#xff01;Swift Code源代码安全审计工具&#xff0c;让漏洞无处可藏" 项目正文: 针对Swift开发者的源代码安全审计工具&#xff0c;帮助发现潜在安全漏洞和代码缺陷。 关键词: Swift, 代码审计, 安全漏洞, 静态分析 摘要描述: 分享一款…

作者头像 李华
网站建设 2026/10/19 5:39:23

AI工具选型指南:任务、场景、能力三个匹配点

1. 选型之前先想清楚&#xff1a;你真正要解决的是什么问题1.1 一个真实场景引发的思考前阵子有个做内容运营的朋友找我&#xff0c;说他们团队想引入AI工具来提升效率&#xff0c;但市面上同类产品太多了&#xff0c;光是文本生成这一类就有几十个选项&#xff0c;团队内部讨论…

作者头像 李华