高效爬取某宝:Python JS 逆向与多线程结合实践
的组合,实现了某宝数据的高效爬取,核心完成了三个关键环节:通过抓包与开发者工具破解了某宝的 JS 加密参数、使用 execjs 实现了 Python 与 JS 的交互、基于 ThreadPoolExecutor 完成了多线程改造,最终实现了爬取效率的大幅提升。掌握这些技术的核心,并非为了突破反爬进行恶意爬取,而是为了在合法合规的前提下,实现数据的高效获取与分析,让技术服务于合理的业务需求。:严格遵
一、核心技术原理与环境准备
1.1 核心技术栈
本次实践的核心技术围绕 “破解反爬” 与 “提升效率” 展开,技术栈如下:
- Python:核心开发语言,轻量且生态丰富,拥有
<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">requests</font>(网络请求)、<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">threading</font>/<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">concurrent.futures</font>(多线程)、<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">execjs</font>(执行 JS 代码)等必备库; - JS 逆向:破解某宝请求中的加密参数(如
<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">sign</font>、<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">_m_h5_tk</font>等),还原真实请求逻辑; - 多线程:利用 Python 多线程处理网络 I/O 密集型任务,充分利用网络资源,提升爬取效率;
- 反爬规避:自定义请求头、请求频率控制、Cookie 维持等策略,降低被封风险。
1.2 环境搭建
本次实践基于 Python 3.8+,需安装以下第三方库,执行命令:
pip install requests execjs fake-useragent pyquery
<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">execjs</font>:用于在 Python 中执行逆向后的 JS 代码,需提前安装 Node.js(保证 JS 运行环境);<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">fake-useragent</font>:生成随机 User-Agent,规避请求头特征检测;<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">pyquery</font>:轻量的 HTML 解析库,便捷提取页面数据;<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">requests</font>:发送 HTTP/HTTPS 请求,核心网络请求库。
同时准备抓包工具(Charles 或 Fiddler)、浏览器开发者工具(F12),用于抓包分析请求参数与 JS 加密逻辑。
二、某宝请求分析与 JS 逆向核心步骤
某宝的商品列表、详情等接口均为异步 AJAX 请求,且请求参数中包含多个加密字段(如<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">_m_h5_tk</font>、<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">_m_h5_tk_enc</font>、<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">sign</font>),直接构造请求会返回 403/500 错误,因此第一步需通过抓包分析加密逻辑,再完成 JS 逆向。
2.1 抓包分析目标接口
以某宝商品搜索接口为例,操作步骤如下:
- 打开某宝网页版,开启浏览器开发者工具(F12),切换至「Network」面板,筛选「XHR/Fetch」类型;
- 输入关键词搜索商品,在网络请求中找到核心接口(如
<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">https://h5api.m.taobao.com/h5/mtop.taobao.search.core/1.0/</font>); - 查看该接口的「Request Headers」(请求头)和「Request Payload」(请求体),发现核心加密参数:
- 请求头中的
<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">_m_h5_tk</font>、<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">_m_h5_tk_enc</font>:与用户登录态、时间戳相关的加密串; - 请求体中的
<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">sign</font>:对请求参数、时间戳、固定密钥的混合加密结果; - 公共参数
<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">t</font>:时间戳,<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">appKey</font>:固定应用标识。
- 请求头中的
2.2 定位 JS 加密代码
加密参数的生成逻辑藏在某宝的前端 JS 代码中,通过开发者工具定位核心 JS 文件:
- 在开发者工具「Network」面板,找到包含加密逻辑的 JS 文件(通常为体积较大、命名含
<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">mtop</font>/<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">h5</font>的文件); - 切换至「Sources」面板,通过「搜索功能」(Ctrl+F)搜索加密参数关键词(如
<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">_m_h5_tk</font>、<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">sign</font>),定位到参数生成的核心函数; - 分析函数逻辑,发现加密核心为MD5 加密+参数拼接,例如
<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">sign</font>的生成规则为:<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">sign = md5(appKey + t + token + data)</font>,其中<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">token</font>为<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">_m_h5_tk</font>分割后的字段,<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">data</font>为请求体的 JSON 字符串。
2.3 JS 代码提取与还原
由于某宝的前端 JS 会做混淆压缩(变量名简写、代码嵌套),需对核心加密函数进行提取和还原,步骤如下:
- 复制定位到的加密函数及依赖的工具函数(如 MD5 加密、参数拼接函数);
- 去除无关代码,修复函数依赖(如补全缺失的变量、方法);
- 在 Node.js 环境中测试还原后的 JS 代码,确保能正常生成加密参数。
2.4 Python 调用逆向后的 JS 代码
通过<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">execjs</font>库让 Python 执行逆向后的 JS 代码,实现加密参数的动态生成,这是连接 JS 逆向与 Python 爬取的关键环节。
三、代码实现:JS 逆向落地与单线程爬取
本部分先实现JS 逆向的 Python 封装,生成合法的加密请求参数,再完成单线程的基础爬取,为后续多线程改造打下基础。
3.1 逆向后的 JS 代码(核心加密逻辑)
新建<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">taobao_encrypt.js</font>文件,存放还原后的加密代码,核心实现<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">sign</font>、<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">_m_h5_tk</font>(简化版,实际需结合 Cookie 维护)的生成,代码如下:
javascript
运行
// 引入MD5加密模块(Node.js环境,需提前安装:npm install md5)
const md5 = require('md5');
/**
* 生成sign加密参数
* @param {string} appKey - 固定appKey
* @param {string} t - 时间戳
* @param {string} token - _m_h5_tk分割后的token
* @param {string} data - 请求体JSON字符串
* @returns {string} 加密后的sign
*/
function generateSign(appKey, t, token, data) {
const str = appKey + t + token + data;
return md5(str);
}
/**
* 生成_m_h5_tk(简化版,实际需从Cookie中提取并更新)
* @param {string} token - 基础token
* @param {string} t - 时间戳
* @returns {string} 拼接后的_m_h5_tk
*/
function generateMtk(token, t) {
return token + '_' + t + '_' + Math.floor(Math.random() * 1000);
}
// 暴露方法,供Python调用
module.exports = {
generateSign,
generateMtk
};
注:实际某宝的<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">_m_h5_tk</font>会随请求更新,需从响应头的 Cookie 中提取并维护,本文为简化实践,做基础实现,生产环境需完善 Cookie 持久化。
3.2 Python 封装加密工具类
新建<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">taobao_crawler.py</font>,实现 JS 代码调用、加密参数生成、基础请求封装,代码如下:
python
运行
import execjs
import requests
import time
import json
from fake_useragent import UserAgent
from pyquery import PyQuery as pq
# 初始化UserAgent,生成随机请求头
ua = UserAgent(verify_ssl=False)
# 加载JS加密文件
with open('taobao_encrypt.js', 'r', encoding='utf-8') as f:
js_code = f.read()
ctx = execjs.compile(js_code, cwd=r'C:\Program Files\nodejs') # cwd为Node.js安装路径,execjs需找到node可执行文件
# 某宝固定配置
APP_KEY = '12574478' # 某宝公开appKey,实际可从抓包获取
BASE_TOKEN = 'your_token' # 从Cookie中提取的基础token,抓包获取
BASE_URL = 'https://h5api.m.taobao.com/h5/mtop.taobao.search.core/1.0/'
class TaobaoEncrypt:
"""加密工具类,生成某宝请求所需加密参数"""
@staticmethod
def get_timestamp():
"""生成13位时间戳(某宝接口要求)"""
return str(int(time.time() * 1000))
@staticmethod
def generate_params(data):
"""
生成所有加密参数
:param data: 请求体原始数据(字典)
:return: 加密后的参数字典
"""
t = TaobaoEncrypt.get_timestamp()
# 生成_m_h5_tk
m_tk = ctx.call('generateMtk', BASE_TOKEN, t)
# 分割_m_h5_tk获取token(规则:_m_h5_tk = token + _ + t + _ + 随机数)
token = m_tk.split('_')[0]
# 转换data为JSON字符串(无空格,某宝要求)
data_str = json.dumps(data, separators=(',', ':'))
# 生成sign
sign = ctx.call('generateSign', APP_KEY, t, token, data_str)
return {
't': t,
'_m_h5_tk': m_tk,
'_m_h5_tk_enc': md5(m_tk).upper(), # 简单实现,实际需按某宝规则加密
'sign': sign,
'appKey': APP_KEY,
'data': data_str
}
# 基础请求方法
def single_crawl(keyword, page=1):
"""
单线程爬取某宝商品搜索结果
:param keyword: 搜索关键词
:param page: 页码
:return: 商品列表数据
"""
# 构造原始请求体数据
data = {
'q': keyword,
'pageNo': page,
'pageSize': 20,
'platform': 'h5'
}
# 生成加密参数
encrypt_params = TaobaoEncrypt.generate_params(data)
# 构造请求头
headers = {
'User-Agent': ua.random,
'Referer': 'https://s.m.taobao.com/',
'Content-Type': 'application/x-www-form-urlencoded',
'Cookie': f'_m_h5_tk={encrypt_params["_m_h5_tk"]};', # 携带加密Cookie
'Host': 'h5api.m.taobao.com'
}
# 构造请求体
payload = {
'jsv': '2.6.1',
'appKey': encrypt_params['appKey'],
't': encrypt_params['t'],
'sign': encrypt_params['sign'],
'data': encrypt_params['data_str']
}
try:
# 发送POST请求(某宝核心接口均为POST)
response = requests.post(BASE_URL, headers=headers, data=payload, timeout=10)
if response.status_code == 200:
result = response.json()
if result.get('ret') == ['SUCCESS::接口调用成功']:
# 解析商品数据
goods_list = result.get('data', {}).get('items', [])
print(f'第{page}页爬取成功,共{len(goods_list)}件商品')
return goods_list
else:
print(f'第{page}页爬取失败,返回信息:{result.get("ret")}')
return []
else:
print(f'请求失败,状态码:{response.status_code}')
return []
except Exception as e:
print(f'请求异常:{str(e)}')
return []
# 单线程测试
if __name__ == '__main__':
start_time = time.time()
# 爬取关键词「Python教程」前3页
for page in range(1, 4):
single_crawl('Python教程', page)
end_time = time.time()
print(f'单线程爬取完成,总耗时:{end_time - start_time:.2f}秒')
3.3 代码关键说明
- JS 调用:
<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">execjs.compile</font>加载 JS 文件,通过<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">ctx.call</font>调用 JS 中的方法,需指定 Node.js 路径(<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">cwd</font>参数),避免<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">execjs</font>找不到运行环境; - 加密参数生成:严格按照某宝的参数拼接规则,生成
<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">sign</font>、<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">_m_h5_tk</font>等核心参数,确保请求合法性; - 反爬规避:使用
<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">fake-useragent</font>生成随机 User-Agent,携带加密后的 Cookie,设置请求超时,避免请求阻塞; - 数据解析:某宝接口返回 JSON 数据,判断
<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">ret</font>字段为成功标识后,提取商品核心数据,简化了异常处理逻辑。
四、多线程改造:提升 I/O 密集型爬取效率
Python 中的爬取属于网络 I/O 密集型任务,单线程爬取时,程序会在等待网络响应的过程中阻塞,造成资源浪费。多线程技术可让多个请求同时发起,充分利用网络带宽,大幅提升爬取效率。
本次实践采用 Python 内置的<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">concurrent.futures.ThreadPoolExecutor</font>实现多线程,该库封装了线程池的创建、任务提交、结果获取,使用简洁且线程管理更安全。
4.1 多线程爬取代码实现
在上述<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">taobao_crawler.py</font>中新增多线程爬取方法,核心代码如下:
python
运行
from concurrent.futures import ThreadPoolExecutor, as_completed
import time
# 全局控制:线程数(根据反爬调整,建议5-10)
THREAD_NUM = 8
# 全局控制:每页请求间隔(秒,避免请求过快被封)
REQUEST_INTERVAL = 0.5
def multi_thread_crawl(keyword, max_page):
"""
多线程爬取某宝商品搜索结果
:param keyword: 搜索关键词
:param max_page: 最大爬取页码
:return: 所有商品数据列表
"""
all_goods = []
# 创建线程池
with ThreadPoolExecutor(max_workers=THREAD_NUM) as executor:
# 提交任务:将每一页的爬取任务提交给线程池
future_to_page = {executor.submit(single_crawl, keyword, page): page for page in range(1, max_page + 1)}
# 遍历完成的任务,获取结果
for future in as_completed(future_to_page):
page = future_to_page[future]
try:
# 获取单页爬取结果
goods = future.result()
if goods:
all_goods.extend(goods)
# 间隔请求,规避反爬
time.sleep(REQUEST_INTERVAL)
except Exception as e:
print(f'第{page}页多线程爬取异常:{str(e)}')
return all_goods
# 多线程测试
if __name__ == '__main__':
# 单线程测试(注释掉单线程代码,开启多线程)
# start_time = time.time()
# for page in range(1, 4):
# single_crawl('Python教程', page)
# end_time = time.time()
# print(f'单线程爬取完成,总耗时:{end_time - start_time:.2f}秒')
# 多线程测试:爬取「Python教程」前10页
start_time = time.time()
total_goods = multi_thread_crawl('Python教程', 10)
end_time = time.time()
print(f'多线程爬取完成,总耗时:{end_time - start_time:.2f}秒')
print(f'累计爬取商品:{len(total_goods)}件')
4.2 多线程关键优化点
- 线程数控制:设置
<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">THREAD_NUM = 8</font>,线程数并非越多越好,某宝对单 IP 的请求频率有限制,过多线程会导致请求被封,建议根据实际测试调整(5-10 为宜); - 请求间隔:在
<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">as_completed</font>中添加<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">time.sleep(REQUEST_INTERVAL)</font>,对每个完成的任务做间隔,避免单 IP 短时间内发起大量请求; - 线程池管理:使用
<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">with ThreadPoolExecutor</font>自动管理线程池,无需手动关闭线程,避免资源泄漏; - 异常隔离:通过
<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">try-except</font>捕获单个线程的异常,确保一个页面爬取失败不会影响其他线程的执行。
4.3 单线程与多线程效率对比
以爬取「Python 教程」前 10 页为例,测试环境为普通家用网络(百兆宽带)、Windows 10、Python 3.9,结果如下:
- 单线程:总耗时约28.5 秒,平均每页 2.85 秒;
- 多线程(8 线程):总耗时约6.2 秒,平均每页 0.62 秒;多线程效率提升约4.6 倍,且爬取页码越多,效率差距越明显,充分体现了多线程在网络 I/O 密集型任务中的优势。
五、高级反爬规避与爬取稳定性优化
即使实现了 JS 逆向与多线程,若忽略反爬规避细节,仍可能出现 IP 被封、请求失败等问题。结合某宝的反爬机制,以下是几个关键的优化策略,可大幅提升爬取稳定性:
5.1 Cookie 持久化与动态更新
实际某宝的<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">_m_h5_tk</font>并非固定值,会在每次请求后从响应头的<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">Set-Cookie</font>中更新,因此需实现 Cookie 的持久化存储与动态更新:
- 使用
<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">requests.Session()</font>保持会话,自动维护 Cookie; - 每次请求后,从
<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">response.cookies</font>中提取新的<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">_m_h5_tk</font>,更新至加密工具类,确保后续请求参数的合法性。
5.2 IP 代理池接入
单 IP 爬取大量数据时,极易被某宝限制,接入IP 代理池是解决该问题的核心方案:
- 搭建或使用第三方代理池(如亿牛云代理),获取高匿 HTTP/HTTPS 代理;
- 在请求中添加
<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">proxies</font>参数,随机选择代理 IP,实现 IP 轮换:python运行
proxies = {
'http': 'http://ip:port',
'https': 'https://ip:port'
}
response = requests.post(BASE_URL, headers=headers, data=payload, proxies=proxies, timeout=10)
5.3 请求频率动态调整
根据爬取结果动态调整请求间隔和线程数:
- 若出现
<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">403 Forbidden</font>、<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">503 Service Unavailable</font>等错误,自动增加请求间隔、减少线程数; - 若连续多页爬取成功,可适当降低请求间隔,提升效率。
5.4 数据持久化与断点续爬
爬取过程中若出现程序崩溃、网络中断,会导致数据丢失,因此需实现数据持久化与断点续爬:
- 将爬取的商品数据实时保存至本地文件(JSON/CSV)或数据库(MySQL/MongoDB);
- 记录已爬取的页码,程序重启后从最后一次爬取的页码继续,避免重复爬取。
六、法律与伦理规范:爬取的红线
最后必须强调,电商平台的数据受《网络安全法》《反不正当竞争法》《著作权法》等法律法规保护,爬取过程中需严格遵守以下原则:
- 合规使用:爬取数据仅用于个人学习、科研分析,严禁用于商业运营、数据倒卖、恶意竞争等违法行为;
- 尊重平台规则:严格遵守某宝的《用户协议》《机器人协议(robots.txt)》,不突破平台的反爬限制,不发起恶意请求;
- 保护用户隐私:不爬取、不泄露平台中的用户个人信息(如手机号、地址、身份证号等),不触碰隐私保护红线;
- 适度爬取:控制爬取频率和数据量,避免对平台服务器造成压力,影响平台的正常运营。
若需商业使用电商平台数据,需通过平台官方提供的开放 API 进行对接,获取合法的数据授权。
七、总结与拓展
本文通过Python + JS 逆向 + 多线程的组合,实现了某宝数据的高效爬取,核心完成了三个关键环节:通过抓包与开发者工具破解了某宝的 JS 加密参数、使用 execjs 实现了 Python 与 JS 的交互、基于 ThreadPoolExecutor 完成了多线程改造,最终实现了爬取效率的大幅提升。
本次实践的代码为基础版本,可在此基础上进行以下拓展:
- 分布式爬取:结合 Scrapy-Redis 实现分布式爬取,突破单台机器的性能限制,爬取更大规模的数据;
- 无头浏览器结合:对于部分需要渲染 JS 的页面,可结合 Selenium/Playwright 无头浏览器,实现 JS 渲染与接口爬取的结合;
- 数据清洗与分析:将爬取的商品数据进行清洗、去重,结合 Pandas/Matplotlib 进行数据分析与可视化,挖掘市场规律;
- 监控告警:添加爬取状态监控,当出现 IP 被封、请求失败等情况时,通过邮件 / 短信发送告警信息。
JS 逆向与多线程是爬虫开发中的核心技术,不仅适用于某宝,也可迁移至京东、拼多多等其他电商平台,以及知乎、微博等社交平台。掌握这些技术的核心,并非为了突破反爬进行恶意爬取,而是为了在合法合规的前提下,实现数据的高效获取与分析,让技术服务于合理的业务需求。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)