淘宝商品信息爬虫全链路:从H5接口抓包到签名调试与反爬规避
你随便搜一下“Python爬虫 淘宝商品信息”能找到几千篇帖子但能真正跑通的没几篇。我刚做电商数据采集那会儿照着热门博客抄代码一跑就是滑块、sign异常、登录失效来回折腾了好几天。后来把所有环节串起来才发现真正的难点根本不在写代码而在你对接口、参数、风控规律的把握。这篇文章我会把项目从零到一的完整链路拆开从抓包定位H5搜索接口到手动扫码拿Cookie再到签名参数调试、JSON解析、分页采集、并发节奏每一步都给出我自己验证过的方案。适合已经会用requests、想进阶处理真实平台级反爬的Python开发者。1. 先搞清楚淘宝反爬在拦什么四个层级与方法选型在动手写代码之前我建议你先花半小时想清楚一件事淘宝到底在防什么它不是防你一个人而是防所有非人工的机器访问。这套风控体系大致分成四个层级我按实际遇到过的频率排列。1.1 平台反爬的四个层级第一层是IP维度。同一个IP在短时间内高频访问会被直接标记。我最初用本机IP连续跑了几百次搜索第二天就发现请求返回的响应体开始出现验证页面这就是IP维度被盯上了。第二层是账号维度。未登录、登录环境异常、登录后操作频率异常都会触发滑块验证或短信验证。淘宝对账号的风控更细同一账号短时间内搜索多个关键词、翻页过快都会触发二次验证。第三层是接口维度。这是很多新手最头疼的所有业务请求都必须带上签名参数、时间戳、加密字段。也就是说就算你构造的URL看起来和浏览器一模一样只要签名不正确服务器就拒绝返回业务数据。第四层是行为维度。正常人不会每0.1秒点一次搜索也不会从搜索页直接跳到第50页更不会持续几个小时不间断翻页。平台会根据这些行为特征识别出“非人类操作”。这四个层级给我的感觉很像一个商场的安保系统门口看你是不是会员登录进门前检查你的包签名参数进去之后盯着你拿东西的频率行为监控同一个面孔一天来一百次也会被记住IP。你只解决其中任何一个点都不够必须整体配合。1.2 网上教程跑不通的三个原因聊完风控再聊一个更现实的问题为什么你抄的那些代码跑不通我这些年看过不下50篇淘宝爬虫教程发现失效的原因基本集中在三个地方。第一PC页面结构改版。早期很多教程是基于PC端商品列表页写的用lxml或正则直接从HTML里提取商品信息。淘宝PC端前端早就改成了异步渲染HTML源码里根本没有完整商品数据这类代码自然失效。第二PC搜索接口加了复杂的加密参数。后来一部分教程转向PC端的某个搜索接口但那个接口的请求参数里有ASRC之类的加密字段算法不定期更新照着抄的代码隔一段时间就报错。第三H5接口的签名算法变化。手机端H5接口相对稳定但签名参数的生成方式并不公开网上流传的拼串公式经常过期。如果不理解签名的构成原理只会复制粘贴平台一调整你就抓瞎。所以如果你想让爬虫稳定跑一阵子核心不是“找到一段能跑的代码”而是“掌握定位接口和分析参数的能力”。参数变了你能重新抓包排查这才是可持续的。1.3 我最终选型为什么走H5接口而不是PC页面综合比较下来我最终选定的技术链路是这样的请求侧用requests配合ThreadPoolExecutor做轻量并发入口选择手机淘宝H5站点搜索接口相对稳定返回的是JSON而不是HTML登录态通过浏览器手动扫码登录后导出Cookie数据解析直接解析JSON字段不做页面正则存储用pandas加CSV量大了再切sqlite3为什么不选PC网页端因为PC端页面结构复杂接口加密参数多而且当前PC端的反爬等级明显比H5端高一截。为什么不选App客户端接口因为App端抓包需要配置代理证书签名算法更复杂还涉及App壳的环境检测对新手门槛太高。H5端介于两者之间接口是标准HTTP请求参数结构清晰风险控制相对友好最适合做学习实践和中小规模的数据采集。2. 抓包定位找到商品搜索的真实接口别去硬啃HTML明确了方向之后第一件事不是写代码而是分析网络请求。你要知道浏览器在搜索商品的那一刻到底向服务器发送了什么又拿回了什么。2.1 环境准备与依赖安装我在Windows和macOS上都跑过这套流程依赖其实很少。Python版本建议3.8以上我自己用的是3.10。需要的第三方库只有requests、pandas再加一个标准库json用于解析数据。pip install requests pandas如果你想把数据存进数据库再装一个sqlite3即可Python 3自带不需要额外安装。考虑到有些朋友可能会用MySQL我这里先用SQLite举例因为零配置、单文件、迁移方便。2.2 手机模拟模式下的抓包步骤抓包工具不用额外装Chrome自带的开发者工具就够用。整个流程我拆成五步你照着操作一遍比读十篇教程都管用。第一步用Chrome打开手机淘宝首页https://m.taobao.com此时地址栏的页面应该是移动端样式。如果打开还是电脑版按F12进入开发者工具再按CtrlShiftM进入手机模拟模式。第二步在开发者工具的Network面板里勾选XHR和Fetch这样可以过滤掉图片、CSS、JS等资源请求只看接口调用。第三步在页面的搜索框输入一个测试关键词比如“机械键盘”点击搜索。注意观察Network面板里新出现的请求。第四步在过滤框里输入关键词“mtop”或者“appsearch”你会发现一个以https://h5api.m.taobao.com/h5/开头的请求这就是我们要找的商品搜索接口。完整路径通常是https://h5api.m.taobao.com/h5/mtop.taobao.wsearch.appsearch/1.0/第五步点击这个请求查看它的Headers和Payload。在Headers里能看到请求头在Payload里能看到URL参数这是接下来构造请求的基础。2.3 接口URL与请求头的关键字段下面这张表是我抓包记录下来的核心信息你做分析时可以直接对照参数/字段说明jsv前端框架版本号可视为静态参数appKey应用标识H5端固定值我抓到的是12574478t毫秒级时间戳随请求变化sign请求签名由token和时间戳等参数计算而来api接口名这里是mtop.taobao.wsearch.appsearchv接口版本1.0dataURL编码后的JSON字符串包含关键词和页码请求头里也需要特别注意两个字段User-Agent必须用手机端UA比如Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X)Referer要带上https://m.taobao.com/否则部分场景会拒绝响应很多教程跑不通问题就出在User-Agent上面。你从PC浏览器复制一个电脑端的UA去请求移动端接口服务端第一眼就能识别出来不是正常H5客户端风控等级直接拉满。3. 登录态是命门Cookie获取、Session维持与失效自救抓包抓到了接口不等于就能拿到数据。你直接拿浏览器里复制的完整参数去请求一次大概率能通但很快会被风控拦住。原因就在于登录态。登录态是淘宝风控判断“你这个账号是不是真实用户”的重要依据没有登录态或者登录态异常几乎什么都拿不到。3.1 没有登录态会怎样我在调试早期做过一个对比实验。不携带任何Cookie直接请求搜索接口返回的JSON里retCode状态是失败业务字段为空偶尔还会返回一个HTML格式的验证页面。带上登录态的Cookie之后同样参数立刻返回完整的商品列表数据包括价格、销量、店铺名。差距非常明显。所以登录态不是可选项而是必选项。你可能会觉得这样很麻烦但换个角度想这也是平台保护用户数据的一种方式。我们做技术学习应该在这个前提下进行合理调试。3.2 手动扫码获取Cookie的完整流程有几种方式获取Cookie我首推手动扫码原因是稳定、门槛低、不容易触发账号风控。用selenium自动登录虽然也是方案但淘宝对自动化浏览器有很成熟的特征识别手段新手用selenium反而更容易被检测这里先不展开。手动扫码的步骤我整理如下在Chrome手机模拟模式下访问https://m.taobao.com点击页面上的登录入口会弹出二维码用手机淘宝App扫描二维码在手机上确认登录回到电脑浏览器确认页面已登录按F12进入开发者工具切到Application面板找到Storage下的Cookies能看到域名下的Cookie列表找到关键的Cookie名比如_m_h5_tk、_m_h5_tk_enc、cookie2、unb、sgcookie等把这些键值对用分号拼起来整个链接串起来之后大概长这样_m_h5_tkxxxxxxxx; _m_h5_tk_encxxxxxxxx; cookie2xxxxxxxx; unbxxxxxxxx这里有一个容易被忽略的细节_m_h5_tk这个Cookie非常关键它就是签名算法里的token来源后面的sign参数计算会用到它。如果你在调试签名时发现sign一直不对首先检查这个Cookie是不是最新的。3.3 Session会话与Cookie本地化拿到Cookie之后建议用requests.Session管理这样后续每个请求都会自动携带Cookie和请求头不用每次手动拼。import requests session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1, Referer: https://m.taobao.com/, Cookie: 你的登录Cookie, }) resp session.get(https://h5api.m.taobao.com/h5/mtop.taobao.wsearch.appsearch/1.0/, paramsparams, timeout10)Cookie属于敏感数据建议存到本地文件而不是硬编码在代码里。我习惯把Cookie单独放在一个config.json里用代码读取这样即使代码传到Git仓库也不会把Cookie带出去。3.4 登录失效的信号与处理策略Cookie不会永久有效短则几小时长则几天。判断登录态是否失效不用等请求报错直接看响应内容就行。如果返回的JSON中ret字段是SUCCESS说明登录态正常。如果出现FAIL_SYS_TOKEN_EMPTY、FAIL_SYS_TOKEN_EXPIRED或者返回的是一个HTML验证页面那就是Cookie过期了。此时不要盲目重试重试也没有用正确做法是重新扫码登录更新Cookie后再继续。我在实际项目里是这么处理的写一个检查函数每次请求后判断返回结构如果发现Token过期就立即停止任务、发送通知可以用Server酱或钉钉机器人然后人工更新Cookie再接着跑。自动化程度高一点的做法是把Cookie失效做成一个异常类在重试逻辑里统一拦截。4. 让一个请求真实发出去参数构造与签名调试登录态解决了下一个拦路虎就是参数构造。从浏览器里复制整个请求链接去访问确实能返回数据但当你把page从1改成2就算改了data里的页码sign还是旧的服务器同样会拒绝。所以你必须理解签名是怎么来的才有办法构造出跨请求可复用的请求体。4.1 data业务参数的组装先看核心的data参数。它本身是一段JSON字符串里面包含了搜索的业务条件。最简单的搜索请求data长这样import json keyword 机械键盘 page 1 data_str json.dumps({ q: keyword, page: page, }, ensure_asciiFalse)实际抓包你会发现data里的字段不止这些还有tab、sort、browseSwitch等但最小可用的集合就是关键词加页码。多余的字段可以不加请求照样能通。我的建议是先用最小集合跑通再逐步增加字段这样排查问题更容易。4.2 签名参数的生成原理与调试方法sign参数是请求能通过服务端校验的关键。它本质上是对请求参数做摘要签名防止参数被篡改。生成时需要用到token就是Cookie里的_m_h5_tk、时间戳t、appKey、以及data字符串计算出一个MD5摘要。这里需要特别说明不同接口版本、不同时间段的签名算法会有差异直接给出一个固定的拼串公式是不负责任的。但调试思路是通用的。我通常这样做从浏览器复制一个真实请求的完整参数包括t、sign、data用同样的token和时间戳尝试不同拼接顺序计算MD5将计算结果和抓包得到的sign对比如果匹配成功说明拼串逻辑是对的你可以把下面这段代码作为调试模板import hashlib import time def debug_sign(token: str, t: str, app_key: str, data_str: str) - str: # 这里只演示一种常见拼法具体顺序需要你自己对照抓包验证 raw f{token}{t}{app_key}{data_str} md5 hashlib.md5(raw.encode(utf-8)).hexdigest() return md5把计算出的md5值和抓包请求里的sign对比如果一模一样说明拼法正确不一致就换顺序重新试。这个过程对理解签名机制很有帮助。4.3 一个能跑通的请求示例签名调通之后整个请求的构造就清晰了。下面是一个完整的请求示例我把需要你替换的地方都标出来了import requests import json import time import random session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1, Referer: https://m.taobao.com/, Cookie: 你的登录Cookie, }) keyword 机械键盘 page 1 t str(int(time.time() * 1000)) data_str json.dumps({q: keyword, page: page}, ensure_asciiFalse) app_key 12574478 # 这里的token从Cookie中的_m_h5_tk提取 token 你的token sign debug_sign(token, t, app_key, data_str) params { jsv: 2.7.2, appKey: app_key, t: t, sign: sign, api: mtop.taobao.wsearch.appsearch, v: 1.0, type: originaljson, dataType: json, data: data_str, } resp session.get( https://h5api.m.taobao.com/h5/mtop.taobao.wsearch.appsearch/1.0/, paramsparams, timeout10, ) print(resp.status_code) print(resp.text[:500])如果一切正常返回的响应里会出现商品列表数据。如果返回的是错误码别急逐项排查先确认token是否正确再确认sign拼串是否匹配最后确认data里的参数是否完整。5. 数据落到手里列表解析、字段清洗与分页采集请求通了数据也返回了接下来就是解析和清洗。这部分看着简单但有不少坑。如果你直接拿返回值里的字段去写数据库过两天你会发现数据脏得没法用。5.1 返回JSON结构与字段映射淘宝搜索接口返回的JSON结构大致是这样{ ret: [SUCCESS::调用成功], data: { itemList: [ { itemId: 123456, title: span classH机械键盘/span 有线静音, img: //img.alicdn.com/xxx.jpg, price: 129.00, sold: 月销300, sellerNick: 某某数码专营店, deliveryLoc: 广东深圳 } ] } }先判断ret里的状态码是不是SUCCESS是再继续解析。否则程序直接退出或进入异常处理避免拿脏数据往下写。解析函数可以这样封装def parse_item(item): return { item_id: item.get(itemId), title: item.get(title), img: item.get(img), price: item.get(price), sold: item.get(sold), shop: item.get(sellerNick), location: item.get(deliveryLoc), url: fhttps://item.taobao.com/item.htm?id{item.get(itemId)} }这里我把商品链接直接拼接出来了因为搜索结果里不一定带完整链接但通过itemId可以确定唯一的商品详情页地址。5.2 字段清洗的常见坑解析完之后清洗环节有几个坑需要提醒。第一个坑是标题里带HTML标签。不少商品标题里会有高亮标签比如span classH机械键盘/span直接存进数据库很乱。需要用正则把HTML标签去掉import re def clean_title(title: str) - str: if not title: return return re.sub(r.*?, , title).strip()第二个坑是价格字段可能不是固定格式。有些商品显示的是区间价比如“129.00”有些显示“129.00起”还有的显示“券后价”。如果后续要做价格分析建议统一提取数字部分def clean_price(price): if not price: return None match re.search(r\d\.?\d*, str(price)) return match.group(0) if match else None第三个坑是销量字段。销量字段往往带着“月销”“人收货”等后缀而且可能缺失。注意用get方法时给默认值别在遍历时因为KeyError把整个任务中断。5.3 翻页与终止条件分页逻辑也不复杂核心在于什么时候停。淘宝搜索结果的页数有上限翻到后面可能返回空列表这时如果你不设终止条件程序会一直请求无效页面浪费大量请求额度。我的做法是写一个带前置判断的循环all_items [] for page in range(1, 101): data_list fetch_page(page) if not data_list: break all_items.extend(data_list) time.sleep(random.uniform(3, 8))这里有两个关键点设置最大页码我一般设50到100防止异常情况导致死循环当返回的商品列表为空时立即break不要继续请求另外每一页请求之间一定要加随机睡眠。sleep(3)和sleep(5)交替出现比固定sleep(3)更接近真人操作能在一定程度上降低行为风控的触发概率。6. 并发怎么做、封禁怎么防、边界怎么守数据解析和分页能做通之后很多人的下一个问题就是“怎么跑得更快”。这里我想泼一点冷水淘宝这类平台对速度非常敏感一上来就高并发大概率是号没了、IP也进了黑名单。合理的设计应该是在速度和稳定之间找平衡。6.1 串行、线程池与协程的取舍网上关于爬虫并发方案讨论很多多线程、协程、分布式各有拥趸。我的实际体验是对淘宝这个场景不用为了并发而并发。串行加随机延迟最安全适合请求量小、对速度不敏感的场景。ThreadPoolExecutor加信号量适合一次要抓几百页、同时不想把代码搞复杂的场景。Asyncio协程虽然并发效率高但requests是同步库需要配合httpx或者自己封装异步请求复杂度上去以后排查问题也麻烦。我推荐一个折中方案线程池最大并发数控制在3以内每个任务内部再做重试和退避。示例from concurrent.futures import ThreadPoolExecutor, as_completed import random import time def fetch_with_retry(page): for attempt in range(3): try: return fetch_page(page) except Exception: wait 2 * (attempt 1) random.uniform(0, 1) time.sleep(wait) return None with ThreadPoolExecutor(max_workers3) as pool: futures [pool.submit(fetch_with_retry, p) for p in range(1, 11)] for future in as_completed(futures): result future.result() if result: process_page(result)注意线程池里的任务最好不要共享同一个Session因为requests.Session本身不是完全线程安全的。更稳妥的做法是每个线程单独建立Session或者用threading.local来隔离。6.2 重试机制与请求节奏网络请求总会遇到超时、连接被重置等问题重试机制是必须的。但重试要讲究策略不能无脑重试否则会把风控系统的注意力集中到你身上。我的策略是网络异常超时、连接错误可以重试最多3次业务异常Cookie过期、签名错误不重试直接停止并告警每次重试的间隔递增比如2秒、4秒、8秒节奏方面我自己跑通的参考值是这样的同一登录态下相邻请求间隔不低于3秒并把延迟随机化到3到8秒连续请求超过20页就主动停60秒单账号每天控制在300到500个请求以内。这不是什么标准值但对我来说够用且安全。6.3 数据去重与本地存储爬虫跑起来之后数据量增长很快去重是必须的。我建议在入库时就以item_id作为唯一键用INSERT OR IGNORE天然去重。SQLite的例子如下import sqlite3 conn sqlite3.connect(taobao_items.db) conn.execute( CREATE TABLE IF NOT EXISTS items ( item_id TEXT PRIMARY KEY, title TEXT, price TEXT, sold TEXT, shop TEXT, location TEXT, url TEXT ) ) rows [(123456, 机械键盘, 129.00, 月销300, 某某数码专营店, 广东深圳, https://item.taobao.com/item.htm?id123456)] conn.executemany( INSERT OR IGNORE INTO items VALUES (?,?,?,?,?,?,?), rows ) conn.commit()SQLite的好处是单文件、零配置适合个人项目和中小型数据量。如果以后数据量超过几百万条再迁移到MySQL也不难。6.4 合规使用提醒这一段我特意放在最后因为最重要。写爬虫不难难的是知道哪些事能碰、哪些事不能碰。淘宝平台对爬虫采集有明确的使用协议和技术约束做技术学习、写博客示例是一回事未经授权大规模抓取、把数据用于商业竞争或侵犯用户个人信息是另一回事。这些行为可能违反《网络安全法》《数据安全法》《个人信息保护法》需要自行评估风险。我给你的建议有三条优先研究和使用淘宝开放平台提供的官方API合规获取数据才是可持续的路爬虫只用于学习和小规模验证不用于商业采集不突破平台访问控制处理好敏感数据Cookie、手机号、地址等都不能泄露更不能用来做用户画像最后再分享一个我个人的调试习惯每次请求都把URL、状态码、返回体的前200个字符落盘保存。遇到问题的时候翻日志就能定位是参数错误、Cookie失效还是被风控拦截比瞎猜高效得多。这套流程你完整跑通一遍之后会发现对付其他平台的网页数据采集思路也是相通的。