美团外卖 H5 签名逆向工程实录

美团外卖 H5 签名逆向工程实录

美团外卖 H5 签名逆向工程实录

目标: `https://h5.waimai.meituan.com/waimai/mindex/home` (美团外卖 H5 首页)

工具: Chrome DevTools Protocol + js-reverse MCP + Python

日期: 2026-05-15


1. 侦察 —— 安全架构总览

打开目标 URL,页面是一个标准 SPA(Single Page Application)。通过 Chrome DevTools 监控所有脚本和网络请求,识别出美团外卖 H5 的多层安全防护体系。

1.1 安全 SDK 全景

SDK URL 功能
**H5guard.js** `s3plus.meituan.net/…/h5guard/H5guard.js` (343 KB) 核心签名与请求拦截
**H5guard Package** `msp.meituan.net/h5guard-files/package-1.1.2.js` (241 KB) H5guard 扩展模块
**Yoda Seed** `s0.meituan.net/mxx/yoda/yoda.seed.js` (24 KB) 风险验证 & 人机识别 SDK
**OWL** `www.dpfile.com/app/owl/static/owl_1.10.1.js` 设备指纹采集
**Rohr** `s0.meituan.net/mx/rohr/rohr.min.js` 数据上报 SDK
**LX** `lx.meituan.net/lx.js` 用户行为追踪
**Logan** `s3.meituan.net/…/logan-websdk/logan_2.1.5.js` 客户端日志系统
**Perf** `s3.meituan.net/mnpm-cdn/@mtfe-perf-h5-1.8.0/web.fsp.min.js` 性能监控

1.2 业务脚本架构

文件 大小 功能
`runtime.fcfe3512.js` Webpack runtime 模块加载器
`vendor.568501c4.js` 第三方依赖 Vue/BetterScroll 等
`common.0a2863a5.js` 135 KB **全局 AJAX 封装 + 签名拦截**
`index.9ebc1220.js` 925 KB 全局组件与路由
`home.0c4f4a88.js` 195 KB **首页业务逻辑**

2. mtgsig 签名机制 —— 核心逆向

2.1 签名发起流程

业务代码通过 jQuery AJAX 发送请求时,common.0a2863a5.js 中的 XHR 包装器在请求打开之前调用 H5guard.sign() 进行签名:

// common.0a2863a5.js (解混淆)
if (window.top === window) {
    o.method = o.type || "GET";
    H5guard.sign(o).then(function(t) {
        if (!e.guardIngnore && 
            o.url.match(/\.waimai\.test\.sankuai|\.waimai\.st\.meituan|\.waimai\.meituan/) &&
            k.setRequestHeader("mtgsig", t.headers.mtgsig),
        k.open(o.type, o.url, S, o.username, o.password),
        // ... 继续发送请求
    });
}

对于 *.waimai.* 域名的请求,签名以 HTTP Header mtgsig 形式附加。对于 open.meituan.com 域名,签名以 URL Query Parameter mtgsig 形式附加。

2.2 H5guard 全局对象

window.H5guard = {
    init:             // 初始化 SDK
    getfp:            // 获取设备指纹 → 返回 base64 编码的指纹数据
    getId:            // 获取设备 ID
    initWithKey:      // 使用密钥初始化
    sign:             // 【核心】对请求签名,返回 Promise
    xhrResHandle:     // XHR 响应处理
    fetchResHandle:   // Fetch 响应处理
    addCommonParams:  // 向 URL 添加公共参数
    getSGRandom:      // 获取随机数 → 返回 32 位 hex 字符串
}

2.3 mtgsig 结构解析

调用 H5guard.sign({url, type, data}) 返回:

{
  "url": "https://i.waimai.meituan.com/test/api",
  "type": "GET",
  "headers": {
    "mtgsig": "{...}"   // JSON 字符串 (作为 header),或 URL-encoded (作为 query param)
  }
}

mtgsig 内部 JSON 结构:

{
  "a1": "1.2",
  "a2": 1778780005745,
  "a3": "55652zwv1u7x5200z5xuu9yw58z3588280v722uxy0w979580v345z16",
  "a5": "Y7s6hL/GrkWXLe5g5Lr0/0eHPYXecpdJmVf0z0+TOhkT5cnppN7One/PE5kDKLtu8fMvGvO2txYDwwPXNMlXl1XEGQtLcxtOxFzlp5cYqmDcbDT4WWX3xaPE/7c=",
  "a6": "h1.9ui8Cof1dao7hUufqz5zz531irHScaljh7BO4WaVLHltE9lODhQ5218RB2awHqz7vvlQxCSjN+qRCBvua2IJT8sHnPM6/6lWIPpQ1ilwaJIeeXtHABzTRf6KfUDoijM0r2kpGdK8TnUS3d4c07c/HJAMIdswkkapqfzbTiZTzWjB5Ahh+58R9m21COSJnaRKESMK+O4Hx1m0vHHUPZLqmi9X6AXaY9FXyRZhA0pCoy7UzykDNq+bHOro8umcU7rVeiwrn8A63ed9zjI6wabTk+96NthVX6lMSWZexZo1BI/hjuwcV9/+VqwZEjp2FnAMX0Ps1RWmjyGRnzBaTcHYMlAirno2WBgUMA+7wG4hVMzsyQ+FRRu2A26BtxePtcJR7x8sql9grk3DShN4t52Do75ebLTD0sxppQoLFyMGpoztpwULeGnioQOlSGJ+7mxbkDB7XXnjVdfZdx8q90dYYbQ==",
  "a8": "83ff90b83e2aab091d3726a131c52067",
  "a9": "4.2.0,7,250",
  "a10": "8c",
  "x0": 4,
  "d1": "61ab3d654604ea209e2141f727afd756"
}

2.4 字段含义总结

字段 类型 含义 变化规律
`a1` String mtgsig 协议版本号 固定 “1.2”
`a2` Number 毫秒时间戳 每次请求更新
`a3` String 设备指纹 ID (dfpId) 会话级固定
`a5` String **核心签名** (Base64) 每次请求重新计算
`a6` String H5guard Signature v1 (hs1) **设备+时间窗口绑定**,约几分钟轮换
`a8` String MD5 哈希 (32 hex) 每次请求更新
`a9` String SDK 版本信息 固定 “4.2.0,7,250”
`a10` String 状态码 固定 “8c”
`x0` Number 签名协议版本标识 固定 4
`d1` String MD5 哈希 (32 hex) 每次请求更新

2.5 关键实验结果

通过三组对比实验验证签名参数变化规律:

实验 1: 相同 URL,连续三次调用

– a2 (时间戳) 递增

– a5 (核心签名) 每次都不同 → 包含时间戳参与计算

– a6 (hs1) 完全相同 → 与具体 URL 无关

– a8, d1 (MD5) 每次都不同

实验 2: 不同 URL,相同时间窗口

– a6 (hs1) 不变 → 确认是设备+时间窗口签名

– a5 (核心签名) 随 URL 变化

实验 3: POST 请求带 Body

– a5 (核心签名) 变化 → POST body 参与签名计算

– a8, d1 也随 body 变化

结论: a5 是由 (URL + 时间戳 + 请求方法 + Body + 设备指纹) 经加密/签名算法生成;a6 是设备+时间窗口的静态签名。


3. 公共请求参数

H5guard SDK 在请求截获时自动向 URL 添加公共参数:

参数 示例值 说明
`yodaReady` `h5` Yoda SDK 就绪状态
`csecplatform` `4` 客户端安全平台标识
`csecversion` `4.2.0` 安全 SDK 版本
`_` `1778779790056` 毫秒时间戳 (防缓存)
`uuid` `19e277fca29c8-…` 设备 UUID
`dfpId` `55652zwv1u7x5200…` 设备指纹 ID (也作为 mtgsig.a3)

4. API 端点枚举

通过网络请求监控,发现以下 API 端点:

4.1 首页数据 (`i.waimai.meituan.com`)

方法 路径 说明
POST `/openh5/homepage/dsp/resource` DSP 广告资源
POST `/tsp/open/openh5/home/shopList` **商家列表** (需完整签名)
POST `/tsp/open/openh5/home/rcmd` 推荐商家
GET `/tsp/open/openh5/set/info` 集合/标签信息

4.2 用户相关 (`i.waimai.meituan.com`)

方法 路径 说明
GET `/openh5/address/list` 用户地址列表
POST `/openh5/order/manager/v3/myuncompleteorder` 未完成订单

4.3 用户中心 (`open.meituan.com`)

方法 路径 说明
GET `/user/privacy/get-switches` 隐私开关配置

4.4 安全与风控

方法 路径 说明
GET `portal-portm.meituan.com/horn/v1/modules/H5guard_BaseSec/prod` H5guard 基础安全配置
GET `portal-portm.meituan.com/horn/v1/modules/H5guardTrack/prod` H5guard 追踪配置
POST `msp.meituan.com/fingerprint/v1/notapp/bio/info/report` 设备指纹生物信息上报

4.5 监控与日志

方法 路径 说明
POST `catfront.dianping.com/api/pv` 页面浏览统计
POST `catfront.dianping.com/api/metric` 性能指标
POST `catfront.dianping.com/api/log` 错误日志
POST `catfront.dianping.com/batch` 批量上报
POST `prism-report-web.dreport.meituan.net` 前端监控

4.6 LX 用户行为追踪

域名 说明
`lx.meituan.net`, `lx1.meituan.net`, `lx2.meituan.net` 用户行为数据上报

5. 请求头要求

# i.waimai.meituan.com / open.meituan.com
headers = {
    "Referer": "https://h5.waimai.meituan.com/",
    "User-Agent": "Mozilla/5.0 ...",
    "mtgsig": "{...}",              # JSON 格式签名 (仅 *.waimai.* 域名)
    "Cookie": "",                   # 可选,影响访问权限
}

# URL Query Parameters (所有 API)
params = {
    "yodaReady": "h5",
    "csecplatform": "4",
    "csecversion": "4.2.0",
    "mtgsig": "{...}",              # URL-encoded JSON (open.meituan.com)
    "uuid": "...",
}

6. 商家列表 403 分析 — 【2026-05-15 深度调查】

shopList 接口返回 HTTP 403,与其他接口 (200 OK) 形成对比:

dsp/resource (POST) → 200 ✓

rcmd (POST) → 200 ✓

set/info (GET) → 200 ✓ (但返回 code: "ILLEGAL_USER_ID")

address/list (POST) → 200 ✓

home/shopList (POST) → 403 ✗

channel/shopList (POST) → 403 ✗

6.1 深入调查过程

为定位 403 根因,进行了系统性排查:

尝试方案 结果 说明
登录后调用 403 userId=394265134,Cookie 完整
伪造移动端 UA 403 `Linux; Android 13; Pixel 7`
覆写 navigator.platform 403 `Linux armv8l`, `maxTouchPoints=5`
添加 `_token` 参数 (from rcmd) 403 rcmd 的 _token 无法复用
修改坐标到用户真实地址 403 贵州坐标 vs 北京坐标均失败
添加 dsp/resource 的额外参数 403 `wm_ctype`, `wm_appversion` 等无效
搜索替代端点 (shopListV2/poi/shopList) 不存在 API gateway 返回 “no matched api config”

6.2 关键证据

网关层拦截特征:

– 响应: HTTP 403 Forbidden from openresty (nginx)

– 响应头: x-forbid-reason: . (仅一个点号)

– 响应体: nginx 标准 403 HTML 页面

– 与其他端点的 nginx 直接放行形成鲜明对比

OWL 设备指纹缺失:

– 在浏览器环境中 window.OWL 未定义

– 页面加载了 OWL SDK (owl_1.10.1.js) 但未初始化

– 移动端 Navigator 属性覆写无法触发 OWL 初始化逻辑

common.js 请求增强流程 (逆向分析):

// common.0a2863a5.js — 所有请求统一经过此增强流程:
e.data.wmUuidDeregistration = parseInt(cookie("uuidCanceled") || 0);
e.data.wmUserIdDeregistration = parseInt(cookie("userIdCanceled") || 0);
e.data.openh5_uuid = getUUID();
e.data.uuid = getUUID();

// rcmd 额外添加 _token:
if (e.appendMTSI) {
  e.data._token = generateMTSToken(e.url, e.data);
}

// 所有请求统一注入:
e.data = Object.assign({optimus_code: 10, optimus_risk_level: 71}, e.data);

shopList 请求体 (来自 home.0c4f4a88.js):

// requestShopList() 方法逆向:
data: {
  pageSize: 20,
  page_index: this.pageIndex,
  offset: this.pageSize * this.pageIndex,
  content_personalized_switch: this.rcmdStatus,
  ...this.chooseFilterData,  // {sort_type:"", slider_select_data:"", activity_filter_codes:""}
  ...getEncryptLatLng()      // 加密坐标 → {optimus_code, optimus_risk_level}
}

6.3 根因分析

403 由 openresty WAF 网关层 直接拦截,而非应用层拒绝。x-forbid-reason: . (点号) 暗示 WAF 规则以暗号形式标记。最可能的原因:

1. TLS/JA3 指纹检测 — Chrome CDP 控制环境与真实移动端 TLS 握手特征不同

2. OWL 设备指纹完整性 — 缺失 WebGL/Canvas/Audio 等多维特征,WAF 判定为非真实设备

3. IP 风控 — 当前 IP 可能在美团风控系统中风险评分较高

4. set_name/region_id 不匹配 — 用户地址在贵州(26.58°N, 106.72°E),但 sessionStorage 中 set_name=waimai-west, region_id=1000520100 指向北京区域

5. 非移动端环境 — 即使伪造 UA 和 navigator,Chrome Desktop 的底层行为(如 WebGL 渲染器、字体列表等)与真机不同

6.4 结论

shopList 端点不可用于 H5 Web 端数据采集。 该端点需要真实的移动设备环境(完整的 TLS 指纹 + OWL 设备指纹 + Native SDK 能力),Web 浏览器无论怎样伪装都无法通过 WAF 检测。

可行的替代数据源:

rcmd API → 分类配置(金刚位)、筛选器组

dsp/resource API → 广告/Banner 数据

address/list API → 用户配送地址

6.5 最新验证 (2026-05-15) — 两个方向同时尝试

基于新发现继续验证了两个方向:补全参数移动端 UA 注入

方向1: 参数补全 — dsp/resource 完整参数

逆向 home.js 中 dsp/resource 调用的 V 对象:

// home.0c4f4a88.js — dsp/resource 请求数据对象
var V = {
  wm_ctype: "openapi",
  wm_appversion: "4.0.0",
  wm_order_channel: j.get("wm_order_channel") || "",
  userId: j.get("userId") || "",
  uuid: j.get("uuid") || j.get("iuuid") || "",
  userToken: j.get("token") || j.get("mt_c_token") || "",
  utm_term: "0", utm_campaign: "0", utm_medium: "0", utm_source: "0",
  wm_dtype: "0", wm_dversion: "0", wm_visited: "0",
  req_time: (new Date).getTime()
};

dsp/resource 完整请求体 (V + common.js 请求增强):

optimus_code=10&optimus_risk_level=71
&wm_ctype=openapi&wm_appversion=4.0.0&wm_order_channel=default
&userId=394265134&uuid=<UUID>&userToken=
&utm_term=0&utm_campaign=0&utm_medium=0&utm_source=0
&wm_dtype=0&wm_dversion=0&wm_visited=0&req_time=<ts>
&wm_latitude=<lat>&wm_longitude=<lng>
&wm_actual_latitude=<lat>&wm_actual_longitude=<lng>
&wmUuidDeregistration=0&wmUserIdDeregistration=0&openh5_uuid=<UUID>

dsp/resource 返回 {"code":0,"msg":"成功"} 但仅包含 Banner/弹窗/浮标 配置,不包含商家列表数据。

方向2: 移动端 UA — 平台检测变化

注入移动端 UA 前后对比:

检测项 注入前 注入后
`navigator.userAgent` Chrome Desktop `Linux; Android 13; Pixel 7`
`navigator.platform` `Win32` `Linux armv8l`
`navigator.maxTouchPoints` 0 5
Rohr `deviceModel` `Win32` `Linux armv8l`

平台伪装在 JavaScript 层面 已生效,但 403 依然存在。

方向3: 关键验证 — 签名中 body 数据的哈希

H5guard.sign() 将 body 数据内容哈希到签名字段 a5 中,因此 body 格式必须与签名时一致:

data: 'optimus_code=10&wm_ctype=openapi...'  → a5: oIwJTmq5QjoK...
data: '{"optimus_code":"10","wm_ctype":"openapi"...}'  → a5: B5ewPxuwZiRG...
data: null/undefined  → a5: TKX7pm1OE0zD...

这说明 mtgsig 不仅仅是 URL 签名,而是 URL + Method + Body 的联合签名。在 Python 脚本中必须先将 body 字典 URL-编码,再传给 H5guard.sign(),否则签名验证失败。

方向4: 终局验证 — 从页面自身 JS 上下文调用

为彻底排除参数问题,在浏览器页面自身 JavaScript 上下文中直接调用 H5guard.sign() + fetch():

// 从页面自身调用 shopList — 所有参数与页面本身一致
const body = 'optimus_code=10&...wm_latitude=<lat>...';
const signed = await H5guard.sign({url: shopListUrl, type: 'POST', data: body});
const resp = await fetch(signed.url, {
  method: 'POST',
  headers: {'Content-Type': 'application/x-www-form-urlencoded', 'mtgsig': signed.headers.mtgsig},
  body: signed.data
});
// → HTTP 403, x-forbid-reason: .

即使从页面自身执行,shopList 仍然返回 403。这确凿证明:

WAF 网关对 `/tsp/open/openh5/home/shopList` 的拦截是**路径级别的**,与参数完整性、签名正确性、UA 伪装均无关。

对比: dsp/resource 从页面自身调用

const body = 'optimus_code=10&wm_ctype=openapi&...wm_actual_latitude=...';
const signed = await H5guard.sign({url: dspUrl, type: 'POST', data: body});
// → HTTP 200, {"code":0,"data":{...banner...}}

补充发现: common.js LocationWhiteUrl

从 common.0a2863a5.js 逆向出坐标注入白名单 (27 个端点):

LocationWhiteUrl = [
  "openh5/homepage/dsp/resource",     ← dsp/resource 在列表中
  "openh5/poi/filterconditions",
  "openh5/channel/kingkongshoplist",
  "openh5/address/list",
  "openh5/homepage/kingkong",
  "openh5/homepage/poilist",
  "openapi/v1/poi/food",
  "openh5/poi/comments",
  "openh5/order/v2/submit",
  "openh5/order/v2/preview",
  ... 共 27 个端点
]

shopList (/tsp/open/openh5/home/shopList) 不在白名单中 — home.js 直接调用 getEncryptLatLng() 加密坐标后内联到请求体。

6.6 终局对比

端点 mtgsig 位置 WAF 状态 返回数据 备注
dsp/resource HTTP Header 200 (带 mt-gateway-error:true) Banner/弹窗配置 工作正常
rcmd HTTP Header 200 分类/金刚位/筛选器 工作正常
address/list HTTP Header 200 用户地址列表 工作正常
**shopList** HTTP Header **403 (x-forbid-reason:.)** **路径级 WAF 拦截**
shopListV2 HTTP Header 200 (body: “no matched api”) 接口未实现
poi/shopList HTTP Header 200 (body: “no matched api”) 接口未实现

shopList 端点不可用于 H5 Web 端数据采集。 该端点需要真实的移动设备环境(完整的 TLS 指纹 + OWL 设备指纹 + Native SDK 能力),Web 浏览器无论怎样伪装都无法通过 WAF 检测。

可行的替代数据源:

rcmd API → 分类配置(金刚位)、筛选器组

dsp/resource API → 广告/Banner 数据

address/list API → 用户配送地址


7. Yoda Seed SDK —— 风险验证流程

Yoda Seed (yoda.seed.js, v1.6.9) 负责加载人机验证模块:

页面加载 → Yoda Seed init()
    ├── 检查 KNB (美团 Native SDK) 环境
    │   └── KNB.ready() → 获取设备信息
    ├── GET {verify_host}/v2/ext_api/page_data
    │   └── 请求参数: requestCode, feVersion, source, layer
    ├── filterRiskLevel() → 遍历风险等级列表
    │   └── 匹配设备平台 (iOS/Android/Web/MiniProgram)
    ├── loadSource() → 动态加载验证 JS/CSS
    └── moduleInit() → 初始化验证模块 (滑块/点选/短信等)

风险等级 (riskLevel) 路由:

riskLevel 验证方式 说明
1-70 低风险 无需验证
71-105 中风险 滑块/点选验证
106-208 高风险 需 KNB Native 验证
200 特殊 小程序环境

8. 安全架构总结

┌─────────────────────────────────────────────────────────┐
│                  h5.waimai.meituan.com                    │
│  ┌───────────────────────────────────────────────────┐  │
│  │  业务层 (home / index / common)                     │  │
│  │    ├── jQuery AJAX 全局拦截器                      │  │
│  │    └── 调用 H5guard.sign() 签名每个请求             │  │
│  └──────────────────┬────────────────────────────────┘  │
│                     │                                     │
│  ┌──────────────────▼────────────────────────────────┐  │
│  │  H5guard.js (343 KB) — 请求签名与拦截              │  │
│  │    ├── XMLHttpRequest.prototype 劫持               │  │
│  │    ├── fetch() 劫持                                │  │
│  │    ├── mtgsig 签名生成 (a1-a10, x0, d1)           │  │
│  │    ├── 公共参数注入 (yodaReady, csecplatform, ...)  │  │
│  │    └── 设备指纹采集 (getfp)                        │  │
│  └──────────────────┬────────────────────────────────┘  │
│                     │                                     │
│  ┌──────────────────▼────────────────────────────────┐  │
│  │  辅助安全层                                        │  │
│  │    ├── OWL — 设备指纹 + 生物特征采集               │  │
│  │    ├── Yoda Seed — 人机验证 (滑块/点选/短信)        │  │
│  │    ├── Rohr — 业务数据监控上报                     │  │
│  │    ├── LX — 用户行为追踪                           │  │
│  │    └── Logan — 客户端日志                          │  │
│  └───────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────┘

关键安全机制

1. 双层签名: mtgsig (a5 核心签名) + hs1 (a6 设备签名),双重验证

2. 全量劫持: 劫持 XHR/Fetch 原型,无法绕过签名

3. 时间绑定: a6 随时间窗口轮换,防止重放

4. 设备绑定: a3 (dfpId) 和 a6 都绑定设备指纹

5. 多层风控: 商家列表 403 表明存在分层的接口访问控制


9. 技术总结

9.1 攻击面分析

mtgsig 签名算法: 实现在 343KB 的 H5guard.js 中,高度混淆,核心签名算法通过原生加密 API (SubtleCrypto) 实现,难以纯静态分析

设备指纹: OWL + H5guard 双重采集,反模拟能力强

Yoda 验证: 动态加载验证模块,未通过验证的请求被拦截

时间窗口: a6 签名定期轮换,限制了重放攻击窗口

9.2 复现难度

组件 难度 说明
mtgsig 静态分析 343KB 混淆代码,内联加密逻辑
mtgsig 运行时调用 `H5guard.sign()` 可直接在浏览器控制台调用
设备指纹 (OWL) 采集 WebGL/Canvas/Audio 等多维特征
Yoda 验证 服务端动态下发验证策略

9.3 安全建议 (给美团)

– mtgsig 签名建议加入 URL path 级别的绑定,防止跨接口复用

H5guard.sign() 应在代码中增加 console.log / debugger 检测

– 设备指纹采集应增加环境一致性校验(如 User-Agent vs Navigator 属性匹配)

9.4 脚本运行注意事项

Chrome 启动命令 (Chrome 130+ 必须加 --remote-allow-origins):

"C:\Program Files\Google\Chrome\Application\chrome.exe" ^
  --remote-debugging-port=9222 ^
  --remote-allow-origins=* ^
  --user-data-dir="C:\temp\chrome_debug"

CDP WebSocket 连接修复: Python websocket-client 库默认发送 Origin: http://localhost:9222,Chrome 130+ 会拒绝此自引用 Origin。通过 suppress_origin=True 跳过 Origin 头即可。MCP (puppeteer) 不存在此问题因为不发送同源 Origin。

采集脚本返回值: 当 shopList 被 403 阻塞时,collect() 返回 dict 中混合了 category 数据和 _rcmd_config / _note 等元数据键。CSV 写入和摘要打印需过滤下划线开头的键。

9.5 最终能力评估

端点 状态 返回数据
rcmd ✅ 200 分类金刚位、筛选器配置
dsp/resource ✅ 200 Banner/弹窗/浮标配置
address/list ✅ 200 用户配送地址列表
shopList ❌ 403 WAF 路径级拦截
set/info ⚠️ 200 (ILLEGAL_USER_ID) 需合法账号绑定

附录 A: mtgsig 调用示例 (浏览器控制台)

// 在美团外卖 H5 页面控制台执行 — 注意 H5guard.sign() 是 async 函数
const result = await H5guard.sign({
    url: 'https://i.waimai.meituan.com/openh5/homepage/dsp/resource',
    type: 'POST',
    data: 'optimus_code=10&wm_ctype=openapi'  // body 必须 URL-编码
});
console.log(JSON.parse(result.headers.mtgsig));
// → {a1: "1.2", a2: 1778780005745, a3: "...", a5: "...", a6: "h1...", ...}

附录 B: CDP 连接最小示例 (Python)

from websocket import create_connection
import json, urllib.request

# 1. 获取 WebSocket URL
resp = urllib.request.urlopen("http://localhost:9222/json")
pages = json.loads(resp.read())
ws_url = pages[0]["webSocketDebuggerUrl"]

# 2. 连接 (Chrome 130+ 必须 suppress_origin)
ws = create_connection(ws_url, timeout=30, suppress_origin=True)

# 3. 执行 JavaScript
msg = json.dumps({
    "id": 1, "method": "Runtime.evaluate",
    "params": {"expression": "document.title", "returnByValue": True}
})
ws.send(msg)
print(json.loads(ws.recv()))  # → {id:1, result:{result:{value:"美团外卖"}}}
ws.close()

完整仓库见 mtgsig_demo.py —— 包含签名调用、多分类采集、WAF 回退的完整实现。

© 版权声明
THE END
喜欢就支持一下吧
点赞7 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容