dpop_analysis.md 4.6 KB

Mercari DPoP 参数分析文档

一、什么是 DPoP

DPoP(Demonstrating Proof-of-Possession)是 RFC 9449 定义的标准协议,用于将请求绑定到特定客户端的密钥对上。Mercari API 使用 DPoP 作为请求鉴权的一部分。

DPoP Token 本质是一个 JWT(JSON Web Token),格式为 header.payload.signature,三段 Base64URL 编码。


二、Token 结构解析

2.1 Header(两个接口相同)

{
  "typ": "dpop+jwt",
  "alg": "ES256",
  "jwk": {
    "crv": "P-256",
    "kty": "EC",
    "x": "j9I6kLKekdSNdHyHsaZl5gkbbFhDaAP3DwsuvVjCrWg",
    "y": "NLtDkddYVafFykQGbk-x6AaJzAjUnVepYt_jsvewpgI"
  }
}
字段 说明
typ dpop+jwt 标识为 DPoP 类型的 JWT
alg ES256 签名算法:ECDSA + P-256 曲线 + SHA-256
jwk EC 公钥 内嵌椭圆曲线公钥,服务端用来验证签名

2.2 Payload(两个接口不同)

LIST_DPOP(搜索列表接口):

{
  "iat": 1778046219,
  "jti": "44bc836e-abaa-4258-a248-3e691153f665",
  "htu": "https://api.mercari.jp/v2/entities:search",
  "htm": "POST",
  "uuid": "a00429c5-ad26-4be4-83ae-60b7239e14d5"
}

DETAIL_DPOP(商品详情接口):

{
  "iat": 1778046009,
  "jti": "1f4f049a-7f0f-4c4f-af71-20baad8a1784",
  "htu": "https://api.mercari.jp/items/get",
  "htm": "GET",
  "uuid": "a00429c5-ad26-4be4-83ae-60b7239e14d5"
}

2.3 Payload 字段说明

字段 说明 LIST 值 DETAIL 值
iat 签发时间(Unix 时间戳) 1778046219 1778046009
jti JWT ID(UUID v4 随机值,防重放) 44bc836e-... 1f4f049a-...
htu HTTP Target URI(目标接口地址) https://api.mercari.jp/v2/entities:search https://api.mercari.jp/items/get
htm HTTP Method(请求方法) POST GET
uuid 设备标识(与 LAPLACE_DEVICE_UUID 相同) a00429c5-... a00429c5-...

2.4 Signature

使用 EC 私钥对 header_base64url.payload_base64url 进行 ES256 签名,输出 64 字节的 raw r||s 格式(各 32 字节),再做 Base64URL 编码。


三、两个关键问题

3.1 为什么会过期?

iat(issued at)字段是一个固定的时间戳。服务端会校验该时间与当前时间的差值,超过有效期窗口(通常几天)就会拒绝请求。硬编码的 token 中 iat 永远不变,所以过几天就失效。

3.2 为什么 LIST 和 DETAIL 不一样?

DPoP 规范要求 token 绑定到具体的接口和方法

差异字段 LIST(搜索列表) DETAIL(商品详情)
htu /v2/entities:search /items/get
htm POST GET
jti 各自独立的随机 UUID 各自独立的随机 UUID
iat 各自的签发时间 各自的签发时间

服务端会验证 htuhtm 是否匹配实际请求,所以两个接口不能共用同一个 DPoP token。


四、生成流程

1. 生成 EC P-256 密钥对(私钥 + 公钥)
         ↓
2. 构造 JWT Header(typ + alg + 公钥 jwk)
         ↓
3. 构造 JWT Payload(iat=当前时间戳, jti=随机UUID, htu=目标URL, htm=方法, uuid=设备ID)
         ↓
4. 拼接 base64url(header).base64url(payload)
         ↓
5. 用私钥对拼接字符串做 ES256 签名
         ↓
6. 输出 base64url(header).base64url(payload).base64url(signature)

五、Python 实现

已封装在 dpop_generator.py 中,依赖 cryptography 库。

5.1 快速使用

from dpop_generator import generate_list_dpop, generate_detail_dpop

# 搜索列表接口
list_dpop = generate_list_dpop()

# 商品详情接口
detail_dpop = generate_detail_dpop()

5.2 自定义设备 UUID

from dpop_generator import DPoPGenerator

gen = DPoPGenerator(device_uuid="your-device-uuid")
list_dpop = gen.generate(htu="https://api.mercari.jp/v2/entities:search", htm="POST")
detail_dpop = gen.generate(htu="https://api.mercari.jp/items/get", htm="GET")

5.3 在爬虫中集成

mercari_jp_spider.py 中的硬编码常量替换为动态调用:

from dpop_generator import generate_list_dpop, generate_detail_dpop

# build_headers() 中:
"dpop": generate_list_dpop(),

# get_detail_page() 中:
"dpop": generate_detail_dpop(),

每次调用自动生成新的 iatjti,不会再过期。同一个 DPoPGenerator 实例复用同一密钥对,保证签名一致性。


六、依赖安装

pip install cryptography