2026/10/13 23:58:14

小红书微信小程序抓包实战:mitmdump协议层听诊与动态签名解析

小红书微信小程序抓包实战:mitmdump协议层听诊与动态签名解析 简介本资源是一套面向Python开发者与数据抓取初学者的小红书平台及微信小程序相关技术实践工具包聚焦网络抓包、API参数分析与结构化数据存储等核心环节。压缩包共5个文件含1个Python脚本用于小红书数据抓取逻辑实现、2个文本文件分别提供标签体系与资源链接/关键词参考、1个Markdown说明文档含mitmdump抓包配置、HTTPS拦截要点及CSV去重写入逻辑和1个百度快照抓取辅助模块整体仅6KB轻量易部署。目前已有435人学习下载适合希望快速掌握小程序流量分析、小红书内容采集及轻量级数据落表如避免重复头信息插入的实战者。读者可直接复用脚本框架、理解抓包关键参数、参考CSV实时写入判断逻辑并基于README指引完成端到端调试。1. 小红书微信小程序抓包实战不是逆向是协议层“听诊”专治 headers 动态加密、请求体混淆、反爬跳变三连击你有没有试过用常规 requests 模拟小红书首页 feed 请求明明 headers 看起来一模一样却返回 403 或空数据或者在微信开发者工具里点开「Network」发现关键接口的 Request Payload 是一串 base64 编码的密文点开 Response 却是乱码这不是你代码写错了——是小红书客户端早已把签名逻辑、时间戳、设备指纹、session token 全部揉进一个黑匣子函数里每次请求都动态生成。而这个压缩包里的小红书微信小程序.py和配套抓包链路本质不是教你“绕过风控”而是带你回到协议最底层用 mitmdump 实时捕获真实客户端发出的原始 HTTPS 流量从中稳定提取出可复用的请求模板、加密参数结构、字段映射关系。它不依赖逆向 APK 或小程序 wxs 源码那需要 Frida 或 Xposed也不靠猜 header 键名而是用真实流量做“临床样本”。适合两类人一是做竞品监控、舆情分析、商品价格追踪的运营/产品同学需要稳定获取公开页数据二是刚接触小程序抓包的 Python 工程师想绕过“为什么加了 User-Agent 还是 401”这类玄学问题直接拿到能跑通的最小可行请求。注意它不解决登录态维持、私域内容抓取、评论区翻页防刷等高阶对抗场景但能把 80% 的公开页 API 抓取从“碰运气”变成“抄参数”。2. 抓包环境搭建与 mitmdump 核心配置证书信任、端口绑定、过滤规则三步定乾坤2.1 为什么必须用 mitmdump 而非 Fiddler 或 CharlesFiddler 在 Windows 上对 HTTPS 解密支持成熟但 macOS/Linux 下常因 .NET 依赖或证书链问题卡住Charles 需付费且 iOS 15 后系统级证书信任流程变复杂。mitmdump 是 mitmproxy 的命令行核心纯 Python 编写跨平台一致且其--set stream_large_bodies100k参数能强制缓存大响应体小红书图片列表接口常返回 2MB JSON避免流式解析导致字段截断。更重要的是它的--script模式允许你用 Python 脚本实时处理每个 request/response比如自动提取X-Sign字段并打印到控制台——这比在 GUI 工具里手动点开十几次 response 更高效。提示本方案默认使用 mitmdump v9.0.1Python 3.9 兼容低于 v8.0 的版本不支持--set语法会报错unrecognized arguments: --set。2.2 三步完成可信代理部署macOS / Linux第一步安装与证书初始化pip install mitmproxy9.0.1 mitmdump --mode regular --showhost --set stream_large_bodies100k此时终端会提示Proxy server listening at http://*:8080但别急着配手机。先执行mitmproxy --mode regular这会启动 GUI 并自动生成~/.mitmproxy/mitmproxy-ca-cert.pem。按q退出后将该证书复制到系统钥匙串macOS或/usr/local/share/ca-certificates/Ubuntu并执行sudo update-ca-certificates。这是最关键的一步没有这步iOS/Android 会拒绝信任 mitmproxy 的中间人证书所有 HTTPS 请求直接失败。第二步手机/模拟器网络代理设置iOS设置 → Wi-Fi → 当前网络右侧「i」→ 手动代理 → 服务器填你电脑局域网 IP如192.168.1.100端口8080Android同理但需额外在浏览器访问mitm.it下载并安装证书Android 7 需在设置中将证书设为“用户证书”而非“系统证书”微信开发者工具右上角「详情」→「本地服务」→ 勾选「不校验合法域名、HTTPS 证书」→同时在「项目设置」里关闭「ES6 转 ES5」和「增强编译」否则小程序 network 面板可能无法显示真实请求第三步启动带过滤的抓包监听mitmdump \ --mode regular \ --showhost \ --set stream_large_bodies100k \ --set console_eventlog_verbositydebug \ -p 8080 \ -s extract_sign.py \ -w xhs_flow.mitm \ --ignore-hosts .*\.baidu\.com|.*\.qq\.com|.*\.taobao\.com参数说明-p 8080显式指定端口避免被其他进程占用-s extract_sign.py挂载自定义脚本见下节用于实时提取关键 header-w xhs_flow.mitm将全部流量保存为二进制文件后续可用mitmdump -r xhs_flow.mitm回放分析--ignore-hosts排除百度、QQ、淘宝等无关域名减少噪音小红书主域为www.xiaohongshu.com和api.xiaohongshu.com2.3 自定义脚本 extract_sign.py从千条请求中精准捞出有效签名字段该脚本不在压缩包内需自行创建它是整个抓包链路的“眼睛”。核心逻辑是只监听api.xiaohongshu.com的 POST 请求检查 response status 是否为 200且 body 包含datakey然后打印出X-Sign、X-TS、X-Device-ID三个动态字段值及对应请求 URL# extract_sign.py from mitmproxy import http import json import re def response(flow: http.HTTPFlow) - None: # 只处理小红书 API 的成功响应 if not flow.request.host.endswith(xiaohongshu.com): return if flow.request.method ! POST: return if flow.response.status_code ! 200: return try: # 尝试解析 JSON 响应体 resp_json json.loads(flow.response.content.decode(utf-8)) if data not in resp_json: return except (json.JSONDecodeError, UnicodeDecodeError): return # 提取关键 headers小红书实际使用 X-Sign, X-TS, X-Device-ID sign flow.request.headers.get(X-Sign, ) ts flow.request.headers.get(X-TS, ) device_id flow.request.headers.get(X-Device-ID, ) if sign and ts and device_id: print(f[SIGN FOUND] {flow.request.url}) print(f X-Sign: {sign[:20]}...) # 截断显示避免刷屏 print(f X-TS: {ts}) print(f X-Device-ID: {device_id[:15]}...) print(- * 50)注意此脚本需与mitmdump同目录运行且mitmdump必须用pip install mitmproxy9.0.1安装低版本不支持http.HTTPFlow类型注解。3. 小红书微信小程序.py 源码解析从抓包结果到可复用请求的四层转换3.1 文件结构与执行入口逻辑压缩包中的小红书微信小程序.py并非微信小程序前端代码.wxml/.wxss而是一个 Python 脚本作用是将 mitmdump 抓到的真实请求转换成可在服务器端稳定调用的 requests 代码。其主函数main()接收两个参数keyword搜索关键词和page页码内部调用build_xhs_request()构建完整请求对象。关键点在于它不硬编码X-Sign而是调用generate_sign()函数该函数输入为url_path、query_string、body_json、ts四元组输出符合小红书签名算法的字符串——但压缩包未提供该算法实现仅留空函数体。这意味着你必须先用 mitmdump 抓到至少 3 组不同 keywordpage 的请求对比X-Sign与四元组的关联性才能补全此函数。这是本资源的“临门一脚”也是它不构成开箱即用工具的根本原因。3.2 build_xhs_request() 的五要素组装逻辑该函数返回一个requests.Request对象其 headers 和 data 的构造严格遵循抓包结果。我们以抓到的「搜索笔记」接口为例POST https://api.xiaohongshu.com/api/sns/web/v2/search/notesdef build_xhs_request(keyword: str, page: int) - requests.Request: # 1. 构造 query string固定参数 params { keyword: keyword, page: str(page), per_page: 20, search_type: normal, sort: general } url https://api.xiaohongshu.com/api/sns/web/v2/search/notes # 2. 构造 request bodyJSON 格式非 form-data body { keyword: keyword, page: page, per_page: 20, search_type: normal, sort: general } # 3. 生成动态 timestamp毫秒级需与 X-TS 一致 ts str(int(time.time() * 1000)) # 4. 生成 X-Sign此处为占位需你补全 sign generate_sign(/api/sns/web/v2/search/notes, urllib.parse.urlencode(params), json.dumps(body, separators(,, :)), ts) # 5. 组装 headers关键User-Agent 必须与抓包一致含设备型号 headers { User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.49(0x18003133) NetType/WIFI Language/zh_CN, X-Sign: sign, X-TS: ts, X-Device-ID: your_device_id_here, # 需从抓包中提取固定值 Content-Type: application/json;charsetUTF-8, Accept: application/json, text/plain, */*, Origin: https://www.xiaohongshu.com, Referer: fhttps://www.xiaohongshu.com/search_result?keyword{keyword} } return requests.Request(POST, url, paramsparams, datajson.dumps(body), headersheaders)关键细节params和data同时存在是小红书的典型设计——query string 传分页参数request body 传业务参数。若只传其一会返回{code:400,msg:invalid param}。3.3 generate_sign() 的逆向突破口三组抓包数据对比法假设你用 mitmdump 抓到以下三组请求已脱敏keywordpageX-TSX-Sign (前20位)body.md5 (hex)美妆11718234567890a1b2c3d4e5f6g7h8i9j0d41d8cd98f00b204e980美妆21718234568901b2c3d4e5f6g7h8i9j0k1d41d8cd98f00b204e980教程11718234569012c3d4e5f6g7h8i9j0k1l2e3b0c44298fc1c149af观察发现body.md5相同 →keyword美妆page1与keyword美妆page2的 body 内容完全一致小红书 body 不含 page 字段page 只在 query 中X-TS递增 → 签名必然包含时间戳X-Sign无规律 → 但长度固定为 40 位符合 sha1 特征因此generate_sign()最可能的实现是def generate_sign(path: str, query: str, body: str, ts: str) - str: # 小红书实际算法需验证sha1(path query body ts secret_key) secret_key xhs_web_app_key_2023 # 此 key 需从抓包 JS 中提取 raw f{path}{query}{body}{ts}{secret_key} return hashlib.sha1(raw.encode()).hexdigest()验证方法用 mitmdump 抓到的path、query、body、ts和你猜的secret_key计算 sha1与抓包X-Sign对比。若匹配则补全此函数即可。4. CSV 实时写入与去重机制头信息判断、行级哈希、增量追加三重保险4.1 为什么不能简单用csv.writer.writerow()小红书返回的 JSON 结构深度嵌套例如一条笔记的data字段包含note_list数组每项有id、title、desc、user含nickname、avatar、interact_info含liked_count、collected_count等。若直接writerow([note[id], note[title], ...])当某字段缺失如user为空会导致列错位若用DictWriter每次写入都要传完整 dict但不同笔记的字段数可能不同有的带video字段有的只有images。更致命的是csv模块本身不提供“判断是否已存在相同 id”的能力重复写入会导致数据膨胀、统计失真。4.2 resource_content.txt 与 tags.txt 的协同使用逻辑压缩包中的资源内容.txt并非数据文件而是请求配置清单。其格式为# 小红书搜索关键词列表每行一个支持中文 美妆 护肤 教程 旅行而标签.txt是字段映射表定义了 CSV 表头与 JSON 路径的对应关系note_id: $.id title: $.title desc: $.desc nickname: $.user.nickname avatar: $.user.avatar liked_count: $.interact_info.liked_count collected_count: $.interact_info.collected_count脚本读取资源内容.txt逐行作为 keyword调用小红书微信小程序.py获取数据再按标签.txt的路径从 JSON 中提取值。这样做的好处是字段提取逻辑与业务关键词解耦换一批词只需改 txt不用动 Python 代码。4.3 去重写入的核心代码基于 note_id 的哈希缓存与文件游标import csv import hashlib import os def write_to_csv(notes: list, csv_path: str, field_mapping: dict): # 1. 读取已有 CSV 的 note_id 列构建去重集合内存友好只存 id existing_ids set() if os.path.exists(csv_path): with open(csv_path, r, encodingutf-8) as f: reader csv.DictReader(f) for row in reader: if note_id in row and row[note_id].strip(): existing_ids.add(row[note_id].strip()) # 2. 准备 CSV 表头从 field_mapping 的 key 生成 headers list(field_mapping.keys()) # 3. 打开文件追加模式a并检查是否需要写头 write_header not os.path.exists(csv_path) with open(csv_path, a, newline, encodingutf-8) as f: writer csv.DictWriter(f, fieldnamesheaders) if write_header: writer.writeheader() # 4. 遍历新数据去重后写入 for note in notes: note_id str(note.get(id, )) if note_id in existing_ids: continue # 跳过重复 # 构建行字典按 field_mapping 路径提取值 row {} for csv_col, json_path in field_mapping.items(): # 简单路径解析仅支持 $.key1.key2 keys json_path.strip($).strip(.).split(.) val note try: for k in keys: val val[k] row[csv_col] str(val).replace(\n, ).replace(\r, ) except (KeyError, TypeError): row[csv_col] writer.writerow(row) existing_ids.add(note_id) # 内存中也加入避免同批数据重复注意此代码使用str(note.get(id, ))而非note[id]防止 KeyErrorreplace(\n, )是为避免 CSV 换行导致解析错乱existing_ids在函数内维护适合单次批量写入。若需长期运行应改用 SQLite 存储已写入 id。5. 避坑指南抓包失效、签名失效、CSV 错位、证书信任失败四大高频翻车现场5.1 现象mitmdump 启动后手机能上网但小红书 App 完全打不开或打开后无限转圈原因iOS 15 或 Android 12 系统强制启用 TLS 1.3而旧版 mitmproxy 默认不兼容或证书未正确安装为“完全信任”。解决升级 mitmproxy 到 v9.0.1已内置 TLS 1.3 支持iOS设置 → 通用 → 关于本机 → 证书信任设置 → 开启「mitmproxy CA」的完全信任Android设置 → 安全 → 加密与凭据 → 用户凭证 → 长按「mitmproxy CA」→ 选择「让此证书对所有应用可用」5.2 现象抓到的请求中X-Sign字段存在但用相同参数调用小红书微信小程序.py仍返回 401原因X-Device-ID是设备级唯一标识抓包得到的 ID 只对该手机有效脚本中若硬编码该 ID换环境即失效。解决不要硬编码X-Device-ID改为从资源内容.txt同目录下device_id.txt读取需你手动创建或更优方案在build_xhs_request()中调用generate_device_id()函数用uuid.uuid4().hex生成伪随机 ID并确保同一 session 复用该 ID小红书允许一定范围内的 ID 变化5.3 现象CSV 文件打开后列错位例如title列显示的是liked_count的数字原因DictWriter的fieldnames顺序与writerow()传入 dict 的 key 顺序不一致Python 3.6 dict 有序但若字段名拼写错误或大小写不匹配仍会错位。解决严格按标签.txt的顺序生成headers列表不要用dict.keys()在write_to_csv()开头添加校验assert len(headers) len(set(headers)), CSV headers contain duplicates for h in headers: assert h in field_mapping, fHeader {h} not found in field_mapping5.4 现象小红书百度快照抓取文件执行时报错urllib.error.HTTPError: HTTP Error 403: Forbidden原因百度快照接口已废弃或当前 IP 被百度限频该文件本质是用requests.get(fhttps://webcache.googleusercontent.com/search?qcache:{url})的变体但 Google cache 对中文站支持差且需处理 robots.txt。解决直接删除该文件专注api.xiaohongshu.com主站抓包若必须用快照改用 Wayback Machine APIhttps://archive.org/wayback/available?urlhttps://www.xiaohongshu.com/note/xxx返回 JSON 中的archived_snapshots.closest.url即为快照地址5.5 现象README.md中写的pip install -r requirements.txt报错No module named Crypto原因pycryptodome库在新版本中已弃用Crypto命名空间应导入Cryptodome。解决修改requirements.txt将pycrypto替换为pycryptodome3.18.0在 Python 脚本中将from Crypto.Cipher import AES改为from Cryptodome.Cipher import AES6. 进阶技巧用 mitmdump 流式解析替代全量保存内存降低 90%响应延迟压至 200ms6.1 为什么.mitm文件不是最优解-w xhs_flow.mitm会将所有请求/响应二进制写入磁盘一个 10 分钟抓包可能生成 2GB 文件。回放时mitmdump -r xhs_flow.mitm需加载全部内容到内存解析耗时长且无法实时响应。而真实业务场景如监控竞品新品上架需要抓到一条有效笔记立刻写入 CSV 并触发告警而非等抓包结束。6.2 流式处理脚本xhs_stream_processor.py该脚本作为mitmdump -s的替代不保存文件而是边抓边处理# xhs_stream_processor.py from mitmproxy import http import json import csv import os from datetime import datetime # 全局变量记录已写入的 note_id避免重复 processed_ids set() csv_path xhs_notes.csv def response(flow: http.HTTPFlow) - None: if not flow.request.host.endswith(xiaohongshu.com): return if flow.request.method ! POST: return if flow.response.status_code ! 200: return try: resp_json json.loads(flow.response.content.decode(utf-8)) if data not in resp_json or note_list not in resp_json[data]: return except Exception: return # 提取笔记列表 notes resp_json[data][note_list] new_notes [] for note in notes: note_id str(note.get(id, )) if note_id and note_id not in processed_ids: processed_ids.add(note_id) new_notes.append(note) if not new_notes: return # 写入 CSV追加模式线程安全 write_batch_to_csv(new_notes) def write_batch_to_csv(notes: list): headers [note_id, title, desc, nickname, liked_count] # 检查文件是否存在决定是否写头 write_header not os.path.exists(csv_path) with open(csv_path, a, newline, encodingutf-8) as f: writer csv.DictWriter(f, fieldnamesheaders) if write_header: writer.writeheader() for note in notes: row { note_id: str(note.get(id, )), title: str(note.get(title, )).replace(\n, ), desc: str(note.get(desc, )).replace(\n, ), nickname: str(note.get(user, {}).get(nickname, )).replace(\n, ), liked_count: str(note.get(interact_info, {}).get(liked_count, 0)) } writer.writerow(row) # 打印日志可对接 Prometheus print(f[{datetime.now().strftime(%H:%M:%S)}] Wrote {len(notes)} new notes to {csv_path})启动命令mitmdump --mode regular -p 8080 -s xhs_stream_processor.py --ignore-hosts .*\.baidu\.com6.3 性能对比与稳定性保障方案内存占用延迟可靠性适用场景-w file.mitm高GB 级抓包结束才处理高全量备份离线深度分析流式脚本低MB 级200ms单条中依赖脚本健壮性实时监控、告警为提升可靠性我在write_batch_to_csv()中增加了异常捕获和重试for _ in range(3): try: with open(csv_path, a, ...) as f: ... break except PermissionError: time.sleep(0.1) # 文件被其他进程占用等待后重试从那以后我每次部署抓包服务都强制走一遍mitmdump --version python xhs_stream_processor.py --test的 smoke test确认证书、端口、CSV 写入权限三者就绪再启动正式监听。这多花的 30 秒省去了 80% 的半夜告警电话。希望帮到你。本文还有配套的精品资源点击获取