前言:在做电商运营时,摸清竞品店铺的真实经营数据是制定策略的前提——销量区间、主营类目、达人合作矩阵、动销结构等指标,直接决定了我们对自身店铺的优化方向。本文以某电商平台 “找小店”页面为例,介绍如何通过浏览器开发者工具分析网络请求、定位解密逻辑,并用 Python 批量抓取竞品店铺数据用于横向对比。仅供学习交流,请勿用于商业用途。
一、业务背景
电商运营中,“找小店”页面的核心价值在于竞品调研。商家输入关键词(如“花间颂官方旗舰店”),平台返回符合条件的店铺列表,字段通常涵盖销量区间、主营类目、店铺评分、合作达人数量、动销率等。将这些数据批量采集下来,可以做几件事:
-
横向对比 Top 店铺:识别头部玩家的销量规模、价格带分布、达人矩阵差异;
-
发现市场空白:找出竞争薄弱的价格带或类目空隙;
-
追踪运营动作:监控竞品上新节奏、促销策略、达人合作频次的变化趋势。
-
相比人工逐页翻看,接口逆向 + 脚本批量采集的效率可提升一个数量级。下面进入技术实操。
二、抓包分析
2.1 开启开发者工具
在浏览器中打开“找小店”页面,按 F12 打开开发者工具,切换到 Network(网络) 面板,勾选 Fetch/XHR 过滤。在页面搜索框输入竞品关键词或调整筛选项,观察网络请求列表的变化。
2.2 定位关键请求
列表中出现 BSCHeaKx/ 开头的请求,该请求返回了店铺列表数据。观察请求详情:
- 请求方式:GET
- 状态码:200
- 返回信息:
"GET Request successful"
- 关联数据:店铺名称(如"花间颂官方旗舰店")、筛选条件等信息
然而直接查看响应内容时,发现返回的 data 字段是加密的——无法直接 JSON 解析。这就需要进入下一步:定位解密逻辑。
三、定位加密逻辑
3.1 搜索关键词
在 Network 面板右键关键请求 → Copy as cURL,复制其中的接口路径片段(如 search 或 permit),切换到 Sources(源代码) 面板,使用 Ctrl+Shift+F 全局搜索,定位发起该请求的 JavaScript 文件(通常是某个 chunk-*.js)。
3.2 进入断点调试
找到目标 JS 文件后,点击左下角的格式化按钮 {} 美化代码,在 XMLHttpRequest.send 或 fetch 调用前后设置断点。刷新页面触发请求,程序会停在断点处。
在右侧的 Debug 面板中可查看:
- Breakpoints:当前断点列表
- Call Stack(调用栈):函数调用链,方便逐层回溯
- Scope(作用域):当前上下文中的变量值
3.3 逐层追踪
通过 Call Stack 逐层向上回溯,找到 XHR 请求的响应回调函数,观察 responseText 在被业务代码使用前经过了哪一层处理。
在追踪过程中,注意到响应数据进入业务逻辑前,先被传入了一个名为 decrypt 的函数——这是解密的入口。
四、分析解密函数
4.1 定位 decrypt 函数
继续单步进入 decrypt 函数,在 Sources 面板中定位到其定义位置:
该函数的主要逻辑包括:
- 接收加密的响应字符串
- 对 URL / 请求路径进行拼接处理(部分实现会从 URL 中派生密钥)
- 使用特定算法 AES进行解密
- 返回明文 JSON 字符串
4.2 解密逻辑分析
在断点处观察 decrypt 函数的入参和返回值,可以推断出:
- 加密数据格式:Base64 编码的字符串
- 密钥来源:从请求头、URL 参数或前端常量中获取
- 解密算法:AES-ECB 或 CBC 模式,密钥长度通常为 16/24/32 字节
Tips:调试时把断点处的密钥、IV、密文都复制出来,方便后续在 Python 中对照验证,避免凭空猜测算法。
五、Python 实现
5.1 完整代码
将分析得到的加解密逻辑用 Python 复现,下面是核心实现:
import base64
import urllib.parse
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
def decrypt(url, text):
"""
模拟原 JavaScript 的 decrypt 函数。
:param url: 用于派生密钥和 IV 的 URL 字符串
:param text: Base64 编码的密文
:return: 解密后的明文字符串,失败返回 None
"""
if not isinstance(url, str):
return None
safe_chars = ";/?:@&=+$,-_.!~*'()#"
url_encoded = urllib.parse.quote(url, safe=safe_chars)
b64_str = base64.b64encode(url_encoded.encode('utf-8')).decode('ascii')
repeated = b64_str * 3
org_key = repeated[:16] # 前 16 字符作为 AES 密钥
org_iv = repeated[12:28] # 第 12-27 字符作为 IV
key_bytes = org_key.encode('utf-8')
iv_bytes = org_iv.encode('utf-8')
try:
ciphertext = base64.b64decode(text)
except Exception:
return None
try:
cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
padded_plaintext = cipher.decrypt(ciphertext)
plaintext = unpad(padded_plaintext, AES.block_size).decode('utf-8')
return plaintext
except Exception:
return None
data = decrypt(url='/api/shop/sales/day7/BSCHeaKx', text="dB06BjCWi3QxZNlr3iYBT7VuSJRMpr6B327Z7kAtZ+leDhgV6+TERt0x7onxFRLJHEtd0Fie6RtHwbyRX/TVdL5WmEN5e6jb9z0u3Ts2VlNo2jAJ73Hu3XVm6/c+HtWlxPa8WfIkxnB35842UK10SGx3UipjFPAj2231TGlF/BmfaD6SlK7LiSFMRUx7e/4uFyprv8LKS5E5PGIudh5Qo7Rig01BBZluDeR4RSMBuhUwUrqVe1OfC32VvmSsjZ174zfaHwCI4RR743/t818OIh6edM7CTYL51su7lyh4rihSPM5M8OKwyqc+wOtx/7rI2ZFZd4CfbWZ2JajrncYESxCBCpPknfAHl9+RsYuDDgKw4xqULxrmqx2UYtkraxHj8q8eSQC/2jK28bynn1GAZYoADNRGTq1xOLcVsknJJfQN5ZCVEQPL4R7EKgJ4YliOX/YoNBhzYIDphSr8NWs78L3+lPecXW4PVctSCb0yd+xOJz/B8in3KG6acjGelERgJVYhryrW01mLBedrxeK8Vog42NGXhjDI2P23xmEAjl8=")
print(data)
5.2 运行结果
运行脚本后,成功获取到竞品店铺的结构化数据: