恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
美团App逆向爬虫实战:Frida+APK分析+协议解密全流程
首页
资讯中心
/
美团App逆向爬虫实战:Frida+APK分析+协议解密全流程
美团App逆向爬虫实战:Frida+APK分析+协议解密全流程
发布时间:2026/10/19 5:43:28
简介这是一份面向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握手细节的中高级开发者准备的实战沙盒。如果你刚学完requestsBeautifulSoup建议先跳过但如果你正卡在“为什么抓不到首页推荐列表”“为什么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:debuggabletrue的强制要求——未开启调试模式的APKFrida无法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参数必须严格匹配美团服务端使用的mgfMGF1(algorithmhashes.SHA256())。血泪经验很多开发者用默认PKCS1v15填充导致解密失败却误以为密钥错了——其实只是padding方式不匹配。2.4 业务逻辑封装层让技术能力落地为可用数据/crawler/main.py是最终使用者接触的唯一入口。它不处理任何加密细节只接收标准化参数from crawler.engine import CrawlerEngine engine CrawlerEngine( device_idMT-ANDROID-1234567890ABCDEF, # 来自Frida hook session_idsess_abc123def456, # 来自登录响应 city_id100010, # 北京市ID lat39.9042, lng116.4074 # 坐标精度影响POI排序 ) # 获取附近餐厅列表带价格、评分、距离 restaurants engine.fetch_nearby_restaurants( keyword火锅, radius3000, offset0, limit20 )所有底层复杂性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打上“调试日志补丁”下载美团App官方APKv12.7.202MD5:a1b2c3d4e5f67890...项目包内已提供镜像反编译apktool d meituan_12.7.202.apk -o mt_decompiled复制补丁将/smali_patches/com/meituan/android/common/walle/encrypt/Encryptor.smali覆盖到mt_decompiled/smali/com/meituan/android/common/walle/encrypt/修改AndroidManifest.xml在application标签内添加android:debuggabletrue回编译apktool b mt_decompiled -o meituan_patched.apk签名jarsigner -verbose -sigalg SHA256withRSA -digestalg SHA256 -keystore ~/.android/debug.keystore meituan_patched.apk androiddebugkey安装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.yamlaes: 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:debuggabletrue必须写在AndroidManifest.xml的application标签内而非activity且frida -U -f命令中的-f参数会强制杀掉进程再重启但美团App有保活机制可能在Frida attach前就自行重启为原始未patch版本。解决执行adb shell cat /data/data/com.sankuai.meituan/shared_prefs/com.sankuai.meituan_preferences.xml | grep debuggable确认输出含android:debuggabletrue改用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 PinningPython 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_id200010服务端会静默返回空。解决用高德地图API查证坐标对应城市IDhttps://restapi.amap.com/v3/config/district?keywords北京市朝阳区subdistrict1keyYOUR_KEY在main.py中打印实际发送的请求URLprint(REQ URL:, engine._build_url(...))确认cityId与lat/lng地理一致性4.4 现象device_id每次运行都变化导致请求被限流原因DeviceIdHelper.getDeviceId()返回值依赖设备硬件信息IMEI/Android ID而补丁仅打印未劫持。项目提供的/frida/hook_device.js才是真正的设备ID固定方案。解决启动第二个Frida会话frida -U com.sankuai.meituan -l frida/hook_device.jshook_device.js内容为Java.perform(function () { const DeviceIdHelper Java.use(com.meituan.android.common.ump.util.DeviceIdHelper); DeviceIdHelper.getDeviceId.implementation function () { return MT-ANDROID-1234567890ABCDEF; // 固定返回 }; });将此ID同步填入config/encrypt_config.yaml4.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(bindTrue, max_retries3) def crawl_city_restaurants(self, city_id: int, keyword: str, radius: int 3000): try: engine CrawlerEngine.from_config(fconfig/city_{city_id}.yaml) return engine.fetch_nearby_restaurants(keyword, radius) except Exception as exc: raise self.retry(excexc, countdown60 * (2 ** self.request.retries))关键改造点配置隔离每个城市一个config/city_X.yaml独立管理device_id、session_id、aes_key避免多任务间密钥污染失败重试max_retries3 指数退避应对临时网络抖动或服务端限流结果持久化在任务末尾添加save_to_mysql(result, city_id, keyword)直接写入结构化数据库从那以后我每次新增采集城市都强制走一遍celery -A tasks worker --loglevelinfo本地验证确保配置语法、密钥有效性、网络连通性全部通过再提交到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(fPOI 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更新和全量数据重跑。没有它这个问题可能潜伏数周直到业务方投诉“评分数据不准”。希望帮到你。本文还有配套的精品资源点击获取