尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Python爬虫实战:抓包盒马App价格接口,实现定时监控

Python爬虫实战:抓包盒马App价格接口,实现定时监控 干过生鲜电商的朋友应该都有同感每天打开App看价格和拆盲盒一样刺激。今天38.8的黑虎虾明天可能直接跳到59.9后天又变成限时特价29.9。人工盯着太累而且促销时段价格波动频繁等你想起来去看的时候优惠早没了。这个项目就是干这件事用Python写一个爬虫通过抓包分析盒马App的API接口实现生鲜价格的定时采集与波动监控让你不用天天手动点开App也能对心仪商品的价格变化了如指掌。整个项目的技术链路大概是Fiddler抓包获取App请求信息分析API参数规律再用requests库模拟请求拿到价格数据最后存入SQLite并做简单的价格波动记录。整个过程适合有一定Python基础、想进阶爬虫实战的开发者参考尤其是对App端接口分析、HTTPS抓包、反爬策略绕过感兴趣的读者这篇博文会拆得很细。我会把踩过的坑和排查思路都写出来争取让你照着走一遍就能跑通。1. 项目整体设计拆解盒马App的请求链路1.1 为什么选择App端API而不是网页端很多人第一反应是直接爬网页端。盒马官网和H5商城确实存在但网页端的反爬相对严格而且价格数据接口往往做了混淆和加密部分关键参数是动态生成的单纯用requests去拿网页源码解析起来非常痛苦。更麻烦的是网页端的接口经常改版今天能用的规则明天就失效维护成本太高。反过来看App端虽然数据走的是HTTPS加密通道但接口路径和数据结构的稳定性通常比网页端好很多。App版本迭代周期长接口一般会保持向前兼容只要拿到一次完整的请求报文后续模拟起来就比较稳定。再加上App端接口返回的是标准JSON不需要像解析HTML那样折腾XPath和正则数据处理干净利落。这也是我最终选择从App开刀的核心原因。1.2 抓包方案选型Fiddler还是CharlesWindows环境下做HTTPS抓包主流工具就两个Fiddler和Charles。两者原理相同都是在手机和服务器之间架一个中间人代理把HTTPS证书换成自己的根证书从而解密流量。我个人偏好Fiddler主要是它的会话面板很直观能一眼看到所有请求的域名、URL、状态码和耗时接口筛选也方便直接按域名过滤就行。Charles的优势在跨平台macOS环境下用得多UI更精致一些。如果你在Windows上干活我个人推荐先用Fiddler。Fiddler的另一个好处是自带脚本能力可以用FiddlerScript在抓包阶段直接改请求或响应方便做重放测试和参数调试。当然只要思路通了这两款工具选哪个都行后面讲的原理是完全通用的。1.3 整体技术架构预览这个项目我分成了四个核心模块抓包分析模块、接口签名分析模块、数据采集模块、数据存储与监控模块。抓包分析模块负责拿到App的完整请求报文梳理出关键接口、参数和请求头。接口签名分析模块解决App请求中动态签名参数的问题让模拟请求能通过服务端校验。数据采集模块用requests库构造请求模拟App的登录态和请求特征定时拉取价格数据。数据存储与监控模块将采集到的价格写入SQLite并提供简单的价格变化记录查询语句。这四个模块的依赖关系是单向的先抓包分析再搞定签名接着写采集最后做监控。任何一个环节卡住后面都没法推进。接下来按这个顺序详细拆解。2. 抓包实操把盒马App的HTTPS流量扒干净2.1 环境准备Fiddler安装与HTTPS解密配置Fiddler安装没什么好说的官网下载安装包一路Next就行。关键是配置HTTPS解密这一步漏了抓到的全是乱码。打开Fiddler进入Tools菜单找到Options切到HTTPS选项卡勾选Decrypt HTTPS traffic。弹出的证书安装提示框全部选“是”把Fiddler的根证书安装到系统信任列表里。注意这里一定要勾选Ignore server certificate errors否则部分App请求会因为证书链不完整而失败。手机端配置比较复杂两台设备必须在同一个局域网内。先查电脑的IP地址命令行输入ipconfig找到无线网卡对应的IPv4地址。然后手机连上同一个WiFi进入WiFi设置把代理改为手动主机名填电脑IP端口填8888。最后用手机浏览器访问http://电脑IP:8888下载并安装Fiddler的根证书。Android 7.0以上系统对用户证书默认不信任如果App不走代理可能需要把证书装到系统证书目录这个后面在常见问题里详细讲。2.2 盒马App抓包实战锁定价格接口配置好之后打开盒马App随便点进一个生鲜商品详情页。这时候Fiddler会话面板会刷出一大堆请求不要慌按以下步骤过滤在Filters选项卡里勾选Use FiltersHost栏输入包含hema或freshippo的域名。按F5清空之前的会话再重新操作一遍App让干扰请求降到最少。找请求方法为GET或POST的接口看URL路径里是否包含item、sku、price、detail之类的关键词。我实操中锁定的关键接口是商品详情页的数据接口路径大致长这样/x/pc/online/detail。这个接口的响应里包含了商品名称、规格、原价、售价、促销标签等完整的商品信息。值得注意的是盒马的接口路径带渠道标志PC和App端会走不同的路径App端的路径里通常带app或者mobile字样抓包时要看仔细。2.3 关键请求头和参数梳理拿到接口之后先别急着写代码把请求头和参数抄下来才是正经事。以下是我整理出的核心关键项User-Agent盒马App的UA里有明显的客户端标志包含App版本号和平台信息。模拟请求时UA必须和真实App保持一致否则容易被风控识别。Cookie登录态凭证里面包含用户ID、会话ID和登录token。Cookie失效后需要重新登录App抓取。x-mini-wua和x-sign这两个是蚂蚁系App常见的动态签名参数由客户端SDK生成每次请求都不一样。这是整个项目里最棘手的部分后面单独讲。Referer有些接口会校验来源必须带上App内的页面地址否则会报参数错误。sts_token部分接口需要临时令牌这个是通过另一个鉴权接口获取的过期时间一般不长。请求参数方面详情页接口通常需要传itemId或skuId这个值就是商品在货架上的唯一标识。每个商品对应一个固定的ID采集时可以用ID列表循环遍历。3. 接口签名分析破解x-sign和x-mini-wua3.1 动态签名参数是怎么产生的蚂蚁系App的接口签名用的是自家SDK里的算法规类算法核心思路是把请求参数、时间戳、密钥拼接后做HmacSHA256或者MD5摘要。关键在于密钥和摘要算法都写在SO文件里客户端通过JNI层调用单纯通过Java层反编译很难直接拿到完整逻辑。如果你不具备逆向破解SO文件的实力还有一条路可以走Hook方案。用Frida对App进程做动态插桩直接调用SO文件里的签名函数把生成的签名结果和原始明文参数拿回来。具体做法是写一段JavaScript脚本注入到App进程里Hook住签名相关的方法把入参和返回值都打印出来。这样你不用关心内部算法只需要把参数和签名结果对齐找到规律后用Python复现。3.2 一次可行的破解流程我用Frida做过一次完整的参数提取流程大致如下手机Root安装Frida Server。注意Android版本要和Frida版本匹配否则会报错。电脑端安装frida-tools用pip install frida-tools即可。用frida-ps -U命令查看App进程名确认盒马App的进程ID。编写Frida脚本枚举Java层所有类找出包含sign或wua字样的方法Hook住后打印调用栈。在App里正常浏览商品触发详情页请求此时脚本会实时输出签名函数的入参和返回值。将多次抓取的入参和返回值做对比找出哪个参数在变化、哪个参数是固定密钥。这套流程下来我确实摸清了签名的生成逻辑。但说实话逆向过程耗时很长而且要反复调试光是为了一个监控脚本投入这么大精力性价比不高。所以我后来换了个思路直接通过抓包工具把签名参数从真实请求里原样复制出来然后把请求头里的动态参数和请求体绑定做成模板每次请求前填充新的时间戳和商品ID签名参数直接复用抓包时的固定值。3.3 固定签名参数的合法性边界这里必须说清楚上述“固定签名参数”的做法只适用于短时间内的价格查询时间一长签名会过期服务端会返回签名校验失败的提示。而且这种操作本质上是在钻接口校验的空子不建议大量高频调用。我之前被风控限流过一次整个IP被封了几个小时后来就老实了改成低频率采集。合规性上也要提醒一句个人学习爬虫和接口分析没有问题但不要拿这个接口去做商业用途比如抓大量商品数据做比价平台对外提供服务这是明确的红线。拿来做自己日用品的价格监控频率控制在一天几次问题不大。4. Python代码实战构造请求并解析价格数据4.1 环境准备与依赖安装这一步就很常规了。Python版本建议3.9以上依赖库只需要requests和sqlite3后者是Python内置的不用额外装。如果你后面要做数据可视化可以加一个pandas和matplotlib但核心采集阶段用不上。安装命令很简单pip install requests可能还要装一个fake-useragent用来随机生成UA实测发现盒马的风控对UA比较敏感每次请求用同一个UA容易被盯上随机UA能降低被识别为脚本的概率。不过盒马接口对UA的格式要求比较严格不是随便一个浏览器UA就能过的所以我在项目里还是优先用真实抓包拿到的App UA只有当它失效时才切换成fake-useragent生成的UA。4.2 构造请求的核心代码下面直接上核心代码。这个类封装了单商品的价格查询逻辑import requests import json import time import sqlite3 class HemaPriceMonitor: def __init__(self, cookie, user_agent): self.session requests.Session() self.session.headers.update({ User-Agent: user_agent, Cookie: cookie, Referer: https://h5.m.hemaos.com/, Origin: https://h5.m.hemaos.com, Content-Type: application/json, x-mini-wua: 你的抓包值, x-sign: 你的抓包值, }) def get_item_price(self, item_id): url https://api.m.hemaos.com/x/pc/online/detail payload { itemId: item_id, scene: online, channel: app } resp self.session.post(url, jsonpayload, timeout10) resp.raise_for_status() data resp.json() # 解析商品信息 item data.get(data, {}).get(item, {}) price_info item.get(priceInfo, {}) result { item_id: item_id, item_name: item.get(title, ), spec: item.get(spec, ), original_price: price_info.get(originalPrice, ), current_price: price_info.get(price, ), promotion: item.get(promotionTags, []), timestamp: int(time.time()) } return result代码逻辑很直白构造会话和请求头提交商品ID拿到响应后用JSON解析出价格信息。需要特别说明的是promotionTags字段盒马的促销标签有时是一串文本数组有时是嵌套的JSON对象不同商品返回结构可能不一样解析时要加一个类型判断否则容易报KeyError。4.3 多商品轮询与容错处理监控一个商品没什么意思要监控肯定是一批商品。我把商品ID放在一个列表里循环请求并按顺序写入数据库。但这里有一个坑如果其中某个商品ID失效接口会返回错误码如果代码不做处理整个循环都会中断。解决办法是加异常捕获和重试机制def batch_query(self, item_ids, retry_times3): results [] for item_id in item_ids: for attempt in range(retry_times): try: result self.get_item_price(item_id) results.append(result) break except Exception as e: print(f商品{item_id}第{attempt 1}次请求失败: {e}) time.sleep(2 ** attempt) else: print(f商品{item_id}重试{retry_times}次后仍失败跳过) # 每次请求间隔1秒避免请求过于频繁 time.sleep(1) return results重试间隔用指数退避策略第一次失败等2秒第二次等4秒第三次等8秒。这个策略在接口暂时性抖动时非常有效但如果接口连续报签名错误那就不是重试能解决的需要重新抓包更新签名参数。4.4 数据库设计SQLite存储价格快照数据存储这块我设计了两个表一个是products商品静态信息表一个是price_history价格历史表。核心查询逻辑都围绕后者展开。建表语句如下CREATE TABLE IF NOT EXISTS products ( item_id TEXT PRIMARY KEY, item_name TEXT, spec TEXT, first_seen INTEGER ); CREATE TABLE IF NOT EXISTS price_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, item_id TEXT, current_price REAL, original_price REAL, promotion_tags TEXT, recorded_at INTEGER );插入数据时先查products表有没有这个商品没有就插入有就更新名称和规格这两个可能变动的字段。price_history表则无条件插入这样每次采集都能留下一个快照后续要做价格趋势分析就有了原始数据。历史数据积累多了以后可以写一句简单的SQL就能找出两次采集之间价格有变化的商品SELECT item_id, current_price, recorded_at FROM price_history WHERE item_id ? ORDER BY recorded_at DESC LIMIT 2;对比最新两条记录的价格字段如果不一致说明价格变了可以触发通知。5. 定时监控与价格提醒让脚本自己干活5.1 定时任务方案选择数据采集脚本写好了总不能每半小时手动跑一次。这时候需要一个定时调度方案。Windows环境下主流选择有几种Windows任务计划程序、APScheduler库、或者干脆写个死循环加sleep。Windows任务计划程序的好处是不需要常驻Python进程系统到了时间点自动拉起脚本跑完自动退出资源零残留。缺点是配置步骤稍微繁琐新建任务时需要在“操作”里指定调用的程序、参数和起始目录。如果Python脚本里用了相对路径起始目录配置错了脚本会直接报文件找不到。APScheduler是纯Python方案在脚本里写一个BlockingScheduler设置好每天的采集时间点进程常驻运行。这种方式调试方便代码改动即时生效但是进程不能关关了就停。我自己更推荐用小脚本加死循环的方案简单粗暴配合日志输出很容易排查问题import time from datetime import datetime monitor HemaPriceMonitor(cookie, user_agent) item_ids [10001, 10002, 10003] while True: now datetime.now() # 每天采集6次8点、10点、12点、14点、16点、20点 if now.hour in [8, 10, 12, 14, 16, 20] and now.minute 0: results monitor.batch_query(item_ids) monitor.save_to_db(results) # 检查价格变化并触发通知 check_price_change(results) time.sleep(60) time.sleep(30)这个方案有个小缺点如果脚本是在整点过几分才启动那一分钟内的判断窗口就错过了。所以实际代码里最好判断now.minute 5而不是等于0这样五分钟内的窗口都能捕获到。5.2 价格变化通知飞书机器人最简单价格变了得让自己知道。通知渠道有很多种邮件、短信、Server酱、企业微信机器人、飞书机器人。我推荐飞书机器人原因无他配置简单、免费、消息实时推送。飞书群里添加自定义机器人后会给你一个Webhook地址。用requests.post往那个地址POST一段JSON就能发消息。核心代码就这几行def send_feishu_message(webhook_url, content): payload { msg_type: text, content: {text: content} } requests.post(webhook_url, jsonpayload, timeout5)实际使用时我会把商品降价信息拼成一段可读文本例如“【降价提醒】鲜活鲍鱼 500g原价49.9现价39.9降价幅度20%快去抢。”然后在主循环里比较两次价格快照触发降价消息。5.3 日志记录与可视化脚本跑久了各种请求失败、网络波动的问题都会出现。没有日志的话出了问题只能干瞪眼。我在代码里加了一个简单的日志模块写入本地文件同时在控制台输出import logging logging.basicConfig( levellogging.INFO, format%(asctime)s - %(name)s - %(levelname)s - %(message)s, handlers[ logging.FileHandler(hema_monitor.log, encodingutf-8), logging.StreamHandler() ] )后续如果想把价格波动可视化直接把price_history表里的数据导出来用pandas读进来matplotlib画一条折线图就能看到商品价格在一周内的高点和低点。这个扩展我在后面会简单聊一下。6. 常见问题与排查技巧实录6.1 抓包数据全是Tunnel to解密不了怎么办这是Fiddler用户最常踩的坑表现为会话列表里全是CONNECT请求或者显示Tunnel to域名:443没有实际的HTTP请求细节。排查步骤按顺序走确认Fiddler的HTTPS解密复选框有没有勾上。确认手机上的证书装好了没有浏览器访问代理IP的8888端口能下载证书但安装了不一定代表信任。确认手机和电脑在同一个网段代理的端口号是不是被其他程序占用。有些App做了SSL Pinning内置了服务端证书的指纹中间人的证书会被直接拒绝请求直接失败。这种情况抓包工具就没用了只能走Hook方案。6.2 模拟请求返回“签名错误”或“参数错误”这个问题的根源基本都出在x-sign和x-mini-wua上。我第一次跑通代码后过了两个小时再跑突然就报签名错误了检查了半天最后发现是Cookie过期导致重新登录而登录后x-sign的值也会变。所以遇到签名错误不要先去纠结算法先重新抓一次包确认当前App请求里有没有新的签名参数有的话替换掉再试。另外请求体里的字段顺序也会影响签名结果如果服务端校验的是整个请求体的摘要那么JSON里的字段顺序变了签名对不上。解决方案是在代码里固定payload字段的书写顺序不要依赖字典的默认排列。6.3 Android 7.0以上抓不到App的HTTPS流量Android 7.0开始系统默认不再信任用户安装的CA证书App如果不开“允许用户证书”的开关抓包工具就会失效。解决办法有两个。一个是把Fiddler的证书装成系统证书这需要手机有Root权限操作步骤是把证书文件转成系统格式后push到/system/etc/security/cacerts/目录。另一个是改App的AndroidManifest.xml在AndroidManifest里加上networkSecurityConfig引用配置trust-anchors信任用户证书。前者ROOT操作有风险后者需要重新打包App都比较折腾。如果只是想快速验证接口逻辑还有个取巧的办法用旧版本Android模拟器比如Android 6.0的系统镜像。老系统对证书的信任策略宽松装了用户证书就能直接抓实测省事不少。6.4 采集频率怎么控制才安全这个是我最想强调的。爬虫和反爬的博弈本质是频率博弈你请求太快服务端必然限流封IP。实测下来盒马的价格接口对高频请求非常敏感。我之前为了测试写了一个循环每5秒请求一次跑了十几分钟IP直接被风控系统拉黑换网才恢复。给一个我自己验证过的安全频率参考单商品每小时不超过4次多商品轮询时每次请求间隔至少2秒。一天下来总请求量控制在几百次级别到目前为止没有再被限流过。6.5 代码快速排错确认Cookie和UA是否过期写爬虫的人都知道一个脚本昨天跑得好好的今天突然报错九成是Cookie过期了。为了快速定位这类问题我在代码里加了一个简单的请求状态检查如果返回的code字段是401或403就输出“登录态失效请重新抓包获取Cookie”的提示而不是等接口返回一大段错误JSON再猜。另外一个实用技巧是把Cookie和UA放到独立的配置文件里用Python的configparser模块读取这样过期了只需要改配置文件不用翻代码。同时建议每次重新抓包后把新的UA和Cookie丢到配置里再跑一次确认无误再让定时任务接管。7. 踩坑复盘与后续扩展方向实际跑这个项目我最深的感受是抓包和写代码都只是体力活真正的难点在接口签名和风控对抗。说实话如果你只是想了解App爬虫的流程没必要把精力全花在逆向SO文件上固定签名参数或者低频率调用已经能满足个人监控需求。同时要尊重平台的规则个人学习用途低频率调用是一回事爬数据商用是另一回事这个界限要拎清楚。后续想扩展的话我准备做两件小事。第一是把数据采集和Web展示打通用Flask写一个简单的页面通过图表展示价格趋势这样就不用每天去看日志了。第二是把通知范围扩大不只监控降价还可以监控库存状态比如某个商品缺货后什么时候补货这在“抢菜”场景下非常实用。这两块做完整个项目就从“能用”变成了“好用”算是给自己日常买菜添了个小助理。最后再分享一个小技巧抓包的时候顺手把请求的响应体也存一份JSON到本地方便后续离线分析。很多字段的含义只看一次是记不住的多存几份报文对照着看字段结构规律排查问题会顺手很多。
返回列表