十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Python的多平台电商商品信息爬虫框架设计实战

基于Python的多平台电商商品信息爬虫框架设计实战 简介本资源是一套面向Python爬虫初学者与电商数据分析爱好者的多平台商品信息采集工具集聚焦淘宝、京东、拼多多、1688及京喜五大主流电商平台解决跨平台商品数据批量获取难、结构化提取弱、运行状态不可视等实际问题。压缩包共20个文件总计1.21MB包含8个核心爬虫脚本如taobaoSpider.py、jdSpider.py、pdd_HAR_reader.py等、5张开发调试截图含抓包工具界面与网络面板实拍、4份说明文档含get_har.md、readme.txt及行为规范、以及wav提示音、LICENSE授权文件等辅助组件兼顾功能实现、环境配置与合规参考。已有346人学习下载提供完整GUI交互界面基于Tkinter、Cookie自动管理、HAR解析能力及异常反馈机制读者可直接运行、分模块调试并深入理解电商反爬应对策略与多源数据统一建模思路。 做了几年数据采集最常被问的一句话就是“能不能写个爬虫把淘宝、京东、拼多多、1688的数据都抓下来”每次听到这种需求我都得先泼盆冷水能但这个“能”背后是一整套工程问题不是丢几个requests请求就能交差的。这个项目——基于Python的淘宝、京东、拼多多、1688、京喜商品信息爬虫设计源码——就是冲着这个需求来的。它不是简单地把五个网站的爬虫脚本堆在一起而是设计了一套能同时管理多平台采集任务的框架。文章会从立项时遇到的真实问题讲起拆解五个平台的差异、源码的核心模块设计、反爬对抗的实际经验以及数据清洗和合规边界。适合有Python基础、想做电商数据采集框架、或者正在被“多平台数据需求”折磨的开发者参考。1. 立项分析多平台商品信息采集真正难在哪先想清楚一个问题淘宝、京东、拼多多、1688、京喜这五个平台卖的东西高度重合商品信息也都能在页面上看到为什么不能分别写五个脚本非要搞一个“设计源码”我一开始也是这么想的直到被现实毒打过一轮。五个平台独立开发每个平台的代码结构都不一样今天淘宝改版要修明天拼多多加了签名要调后天1688登录态过期要处理。最崩溃的是数据字段不统一同一个商品淘宝叫“标题”京东叫“商品名称”拼多多叫“goods_name”存进数据库之前还得写一堆if else做映射。这种开发模式第一版可能三天就能跑通但维护成本会指数级上升。所以这个项目的核心价值不是“会爬”而是“怎么设计一个能长期跑、能扩展、能统一管理多个平台的爬虫框架”。立项阶段需要梳理清楚几个关键点数据字段的统一五个平台的商品详情页结构完全不同但落库层面我们要的是同一套标准字段——商品ID、标题、价格、销量、店铺名、店铺ID、链接、更新时间。采集策略的差异有的平台反爬强必须严格控制频率有的平台有现成接口可以直接调有的平台需要登录态Cookie管理是单独的模块。运行状态的监控多平台任务同时跑一个平台挂了不能影响其他平台还得能自动重试、告警。如果这些没想清楚就直接写代码后面大概率会陷入“天天修修补补”的泥潭。我还遇到过另一个坑很多人以为只要把页面HTML下载下来正则一把梭就能拿到数据。但现在的电商平台尤其是淘宝和拼多多数据基本都是通过异步接口加载的直接在HTML里搜关键词根本拿不到完整数据。所以立项时就得确认每个平台的数据该从页面结构里解析还是从接口返回里提取这直接决定了后面解析模块的设计方式。2. 五个平台的抓取特征盘点看着都是商品页底层数据流却完全不一样动手写代码之前我先把五个平台的数据获取途径梳理了一遍。这个步骤特别重要它能避免你抱着“统一用requests抓HTML”的思路一条道走到黑。平台数据加载方式是否需要登录主要反爬特征建议数据入口淘宝异步接口加载HTML里没有完整数据搜索/list需要登录态Cookie校验严格频繁请求会弹滑块验证移动端接口或已登录的Web接口京东部分页面服务端渲染部分异步加载搜索无需登录但部分字段受限有用户行为检测短时间高频请求会封IPWeb页面HTML 异步接口结合拼多多完全异步页面数据由JS动态渲染H5端可匿名访问有签名参数anti-content参数校验严格H5端接口需要处理签名1688服务端渲染为主搜索页数据完整搜索可匿名查看详情需登录登录态要求高B端用户行为模型更敏感Web页面HTML加强Cookie管理京喜部分服务端渲染部分异步可匿名访问整体管控比京东主站宽松但接口签名有变化Web页面HTML 接口这张表基本决定了爬虫的整体架构走向。2.1 淘宝动态接口和Cookie是第一道门槛淘宝页面本身的数据是通过脚本动态渲染的直接抓HTML只能拿到一个空壳。必须通过抓包工具分析页面加载时发出的XHR请求找到真正的商品列表接口。但接口也不是随便就能调的每次请求都要带上签名参数和有效的Cookie而且Cookie的生成和淘宝的登录态强绑定。实际测试下来淘宝的反爬是五个平台里最“敏感”的。我试过用一个低频率的脚本连续跑几个小时没问题但只要稍微加快速度就会触发滑块验证。所以淘宝部分的设计核心是慢而稳宁可一小时只跑几百个商品也不要几分钟跑几千个然后被封锁。后面讲调度模块时我会专门讲这个限速策略。2.2 京东数据相对开放但别高兴太早京东是个很有意思的案例。它的搜索页有一部分商品信息是直接渲染在HTML里的解析起来比淘宝省事很多甚至不需要登录就能获取列表数据。但问题在于京东的用户行为检测模型同样严格尤其是当你在短时间内频繁翻页时IP会很快被限制访问。我在测试中发现京东对于同一个IP的请求频率容忍度比淘宝高一些但一旦触发限制解封时间可能长达几十分钟。所以京东这块的实践是走“页面HTML 异步接口补充”的路线既能保证字段完整度又能控制请求量。2.3 拼多多签名参数绕不开拼多多的H5端是最容易入手的数据入口但也不是free lunch。它会生成一个类似anti-content的签名参数每次接口请求都要重新计算而且这个参数的生成逻辑会不定期更新。这就是为什么很多拼多多爬虫脚本“今天能跑明天就废了”——签名逻辑一变整个请求流程就得重新适配。对于这种动态签名的处理方法常见的思路是用浏览器自动化工具先跑一遍拿到完整的请求参数再逆向出签名逻辑。我在源码里设计了独立的签名处理模块这样就算拼多多更新了签名算法也只需要改这一个文件不需要动主流程代码。2.4 1688B端属性决定了它的“谨慎”1688是B2B平台整体搜索页的数据反而是服务端渲染的解析复杂度不高。但1688的登录态管理比C端平台严格很多如果你需要采集的商品详情页字段比较深入比如供应商联系方式、起订量基本必须保持登录状态。实际做下来1688最麻烦的不是抓数据而是Cookie的维护。普通这种企业账号一旦触发风控登录态直接失效而且解封流程很麻烦。所以我通常建议有1688业务需求的团队直接走官方API毕竟B端数据的价值更高更值得投入合规成本。2.5 京喜多快好省的“亲儿子”反而最简单京喜作为下沉市场产品页面结构和京东主站类似但反爬策略要宽松不少。商品列表数据在服务端渲染的HTML里就有异步接口的签名校验也相对简单。如果你只是想快速验证这个框架是否跑得通京喜是个不错的起步目标。不过要提醒一句京喜的接口变化频率并不低可能几个月就调整一次参数结构所以源码里同样要预留灵活的适配层不能为了图省事把解析逻辑写死。3. 框架设计一个轮子怎么同时服务五个“性格迥异”的站点多平台爬虫框架的核心设计目标用一句话概括就是公共逻辑下沉平台差异隔离。调度重试、速率控制、数据清洗、日志告警这些所有平台都通用的能力放到底层而每个平台的请求构造、URL规则、签名逻辑、页面解析放各自的模块里。3.1 模块划分配置、下载、解析、存储各自独立我用的项目结构大概是这样spider_framework/ ├── configs/ │ ├── base.py # 基础配置日志级别、请求超时、重试次数 │ ├── taobao.py # 淘宝平台配置 │ ├── jd.py # 京东平台配置 │ ├── pdd.py # 拼多多平台配置 │ ├── alibaba.py # 1688平台配置 │ └── jingxi.py # 京喜平台配置 ├── core/ │ ├── scheduler.py # 调度器任务排队、频控、分发 │ ├── downloader.py # 下载器统一请求入口代理和重试 │ ├── parser.py # 解析器根据平台分发到对应解析函数 │ ├── storage.py # 存储器标准化字段写入数据库 │ └── monitor.py # 监控告警失败任务统计、异常日志 ├── adapters/ │ ├── base_adapter.py # 各平台适配器的抽象基类 │ ├── taobao_adapter.py │ ├── jd_adapter.py │ ├── pdd_adapter.py │ ├── alibaba_adapter.py │ └── jingxi_adapter.py └── main.py # 启动入口每个平台的核心逻辑都放在adapter里新增一个平台只需要写一个新的adapter类并用configs里增加对应配置。下载器、调度器、存储器这些模块完全不用动。3.2 Adapter模式的基类设计基类定义了一组所有平台必须实现的方法# adapters/base_adapter.py from abc import ABC, abstractmethod class BaseAdapter(ABC): 平台适配器基类。 每个子类负责定义 - 请求URL的构造规则 - 请求头、Cookie、签名参数的初始化 - 从HTML或JSON中解析商品信息的逻辑 - 翻页的URL规则和终止条件 def __init__(self, config): self.config config self.session None self.cookie_manager None abstractmethod def build_search_url(self, keyword: str, page: int) - str: 根据关键词和页码构造搜索页/接口的URL pass abstractmethod def parse_search_response(self, response_text: str) - list: 解析搜索页响应返回一组商品的原始信息dict列表。 注意这里只做字段提取不做标准化。 pass abstractmethod def parse_next_page(self, response_text: str) - int | None: 判断是否存在下一页返回下一页页码没有则返回None。 pass abstractmethod def make_headers(self) - dict: 构造该平台所需的请求头 pass以京东作为例子一个具体的adapter大概长这样# adapters/jd_adapter.py import json import re from adapters.base_adapter import BaseAdapter class JdAdapter(BaseAdapter): def build_search_url(self, keyword: str, page: int) - str: # 京东搜索页的URL格式page从1开始 return ( fhttps://search.jd.com/Search f?keyword{keyword}page{page} ) def make_headers(self) - dict: return { User-Agent: ( Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 ), Referer: https://www.jd.com/, Accept: text/html,application/xhtmlxml, } def parse_search_response(self, response_text: str) - list: 京东搜索页里渲染后的HTML包含一部分商品信息 同时页面里会内嵌一个包含完整列表数据的变量。 优先从内嵌变量里提取提取失败再用正则补充。 items [] # 尝试从内嵌JSON中解析 matched re.search(rwindow\._context (\{.*?\});, response_text) if matched: try: data json.loads(matched.group(1)) # 假定的数据结构需根据实际页面调整 for item in data.get(products, []): items.append({ title: item.get(title, ), price: item.get(price, 0), shop: item.get(shop, {}).get(name, ), product_id: item.get(sku, ), }) return items except json.JSONDecodeError: pass # 退路使用正则从HTML片段中提取 pattern re.compile(rdata-sku\(\d)\.*?title\(.*?)\, re.S) for match in pattern.finditer(response_text): items.append({ product_id: match.group(1), title: match.group(2), }) return items每个平台的adapter都遵循同样的接口约定但内部实现完全独立。这样写的好处是当某个平台反爬升级时你只需要修一个文件其他四个平台不受影响。3.3 调度器怎么做到“一个平台挂了其他平台照跑”调度器要解决两件事一是控制每个平台的请求频率二是任务失败后的重试隔离。# 频率控制示例使用简单的令牌桶思想 rate_control { taobao: {min_interval: 5.0, max_retries: 3}, jd: {min_interval: 2.0, max_retries: 5}, pdd: {min_interval: 3.0, max_retries: 3}, alibaba: {min_interval: 4.0, max_retries: 3}, jingxi: {min_interval: 1.5, max_retries: 5}, }每个平台独立计时任务执行前检查“距上次请求的时间间隔”是否已达标不够就等待。这样即便淘宝触发了滑块验证京东和其他平台的任务也不会受到影响它们不在同一个“锁”上。重试方面我把失败分为两类网络错误超时、连接重置直接重试最多重试3次反爬拦截滑块、验证码、418/403状态码停止当前关键词的抓取不重试记录日志并告警反爬拦截还重试是最蠢的做法只会让封禁变得更加严重。所以调度器里对这两类异常做了严格区分遇到反爬信号就切换任务保留现场等待人工处理。4. 数据管道五个平台的“方言”怎么统一成一套标准字段每次采集任务跑完最让人头疼的不是数据没抓到而是抓回来的数据五花八门。所以框架里专门有一个数据处理模块负责把五个平台的原始响应转换成统一的商品信息结构。4.1 标准字段定义我在storage模块里定义了统一的数据模型# core/storage.py from datetime import datetime from dataclasses import dataclass, field dataclass class ProductInfo: platform: str # 来源平台taobao/jd/pdd/alibaba/jingxi keyword: str # 搜索关键词 product_id: str # 平台内商品ID title: str # 商品标题 price: float 0.0 # 当前价格浮点化 original_price: float 0.0 # 原价/划线价 sales: int 0 # 销量尽量规整成整数 shop_name: str # 店铺名 shop_id: str # 店铺ID product_url: str # 商品详情页URL crawled_at: datetime field(default_factorydatetime.now)平台适配器返回的原始数据可能长这样# 淘宝返回 {itemId: 123456, title: xxx, price: 29.90, sales: 已售300} # 京东返回 {sku: 234567, name: xxx, p: 29.90, commentCount: 300} # 拼多多返回 {goods_id: 345678, goods_name: xxx, min_group_price: 2990, sales_tip: 300件}存储模块会先做一次“字段归一化”把不同平台返回的字段映射到标准字段FIELD_MAPPING { taobao: {itemId: product_id, title: title, price: price}, jd: {sku: product_id, name: title, p: price}, pdd: {goods_id: product_id, goods_name: title}, } def normalize(raw_item: dict, platform: str) - ProductInfo: mapping FIELD_MAPPING.get(platform, {}) normalized {} for raw_key, standard_key in mapping.items(): if raw_key in raw_item: normalized[standard_key] raw_item[raw_key] return ProductInfo( platformplatform, product_idstr(normalized.get(product_id, )), titlestr(normalized.get(title, )), priceparse_price(normalized.get(price, 0)), )4.2 价格和销量的清洗陷阱这里有个容易忽略的小细节不同平台返回的价格格式不一样。拼多多的接口里价格有些是“分”为单位的整数2990代表29.90元京东和淘宝返回的则可能是字符串“29.90”。如果在清洗阶段没处理好后面做价格分析的时候会给所有数据除以100得到一堆错得离谱的结论。销量也一样“已售300”和“300件”和“300”这种字符串必须提取数字部分并统一成int。我写了一个小的解析工具函数来处理这些格式差异import re def parse_price(value) - float: 把各种价格格式统一成浮点数 if isinstance(value, (int, float)): # 如果平台约定价格单位是分除以100 return value / 100 text str(value).replace(, ).replace(,, ) match re.search(r\d\.?\d*, text) return float(match.group()) if match else 0.0 def parse_sales(value) - int: 把已售300、300件等格式提取为纯数字 text str(value) match re.search(r\d, text) return int(match.group()) if match else 0很多初学者会在这一步偷懒觉得“反正能看就行”结果后面做价格区间分析的时候才发现拼多多的价格比别人贵100倍排查了半天才发现是单位没换算。这类问题越早做标准化后面越省力。4.3 增量更新与去重策略商品数据不是抓一次就完了价格监控、竞品分析都需要长期跑。如果每天全量采集一是数据量太大二是会给目标平台造成不必要的请求压力。所以我设计了一套简单的增量机制每个商品以platform product_id作为唯一键采集新数据前先查询数据库里是否已存在该商品如果存在且价格没变化只更新crawled_at不更新价格字段如果价格变了则更新价格和爬取时间这样做的好处是数据库里的历史价格记录可以保留下来用于之后的价格走势分析。我在表设计时专门加了一个price_history的JSON字段记录每次价格变化的时间和值。5. 反爬对抗的实战经验不追求“强攻”而是活得久聊到爬虫反爬是绕不开的话题。但我想先说一个很多人走过的弯路一上来就琢磨着怎么硬刚验证码、怎么逆向加密算法结果花了两周时间连数据都没拿到。我在这个项目里的实战经验是先做减法再做加法。5.1 把请求频率降到“不打扰”的程度最笨但最有效的方法就是限速。我测试下来一个正常用户浏览商品列表的节奏大概是十几秒翻一次页几分钟换一次关键词。如果你能模拟这个节奏大部分平台的反爬系统不会轻易触发。我在配置里默认的请求间隔设置得比较保守taobao: 8 ~ 12秒/请求 jd: 3 ~ 6秒/请求 pdd: 5 ~ 8秒/请求 alibaba: 6 ~ 10秒/请求 jingxi: 2 ~ 4秒/请求当然你也可以根据采集任务的紧急程度适当调快但需要承担触发验证码的风险。我的建议是如果是长期运营的数据采集任务慢一点没关系稳定才是第一位。5.2 Cookie管理比代理池更核心的东西很多人一遇到反爬就想着搞代理池但电商平台最有效的检测手段不是IP而是行为模型。一个IP频繁换地区登录反而更容易触发风控。我做过对比实验同一个IP用干净的Cookie高频请求比用高匿代理低频请求的封禁率更低。所以Cookie池的管理优先级要高于代理池。具体做法是为每个平台维护一个Cookie池数据库表或内存队列每个请求随机选择一个Cookie当某个Cookie触发验证码时自动将其标记为“需人工处理”并从池中移除import random class CookiePool: def __init__(self, platform: str): self.platform platform self.cookies [] self.blocked set() def get_cookie(self) - dict: available [ c for c in self.cookies if c[name] not in self.blocked ] return random.choice(available) if available else None def mark_blocked(self, cookie_name: str): self.blocked.add(cookie_name)5.3 验证码别自己解交给人工介入现在的验证码系统滑块、点选、无感验证基本已经不是纯算法能解决的了硬碰硬只会消耗大量时间。我在源码里设计了验证码通知逻辑当检测到需要验证码时把页面截图保存下来发送到钉钉/企业微信机器人由人工在浏览器里完成验证然后把新的Cookie重新导入Cookie池。这种方式虽然需要一定的人工参与但长期来看是性价比最高的方案。至少我在实际运营里一个团队每天花十几分钟处理验证码就能让整套采集系统稳定跑上一个季度。5.4 合规边界与红线这部分我必须说清楚。做电商数据采集有几个边界必须守住只采集公开信息商品标题、价格、销量、店铺名这些公开展示的数据可以采集但不能碰用户个人信息比如买家昵称、收货地址、手机号。尊重平台规则和robots协议虽然代码里可以主动忽略robots但法律风险是真实存在的。商业项目做数据采集前建议让法务过一遍平台用户协议。控制请求规模不要对平台造成实质性影响。把采集频率控制在模拟单用户行为的合理范围内既是技术策略也是合规底线。优先考虑官方接口1688、京东都有开放平台API拼多多也提供开放平台。如果数据需求量真的很大我更建议申请官方接口稳定性和合规性都远胜爬虫。从长期项目运营的角度看爬虫只是数据获取的“过渡方案”。如果某个数据需求被验证是刚需后续一定值得投入资源接入官方API或购买正规数据服务。6. 踩坑实录从“上线当天就跑挂”到“稳定运行三个月”最后分享几个我在这个项目里实际踩过的坑都是文档里不会写但实战中几乎必然会遇到的事情。6.1 淘宝首页和搜索页的Cookie不能混用我在开发初期犯过一个低级错误从淘宝首页登录拿到的Cookie直接拿去请求搜索接口结果发现请求返回的页面里没有搜索结果。排查了半天才发现淘宝的Cookie在不同域之间是有作用范围区分的。首页的Cookie可能只对首页域生效搜索接口需要的Cookie必须从搜索页面或对应的登录流程获取。后来我在适配器里单独维护了一个“搜索专用Cookie”的获取入口这个问题才彻底解决。6.2 拼多多字段经常变别用固定索引拼多多的接口返回结构不同时期可能调整嵌套层级。有一次我用了固定的层级索引去取商品ID代码跑了几天都好好的结果某天突然全部报KeyError。排查下来是接口在响应里多套了一层结构。从那以后我在解析拼多多响应时都强制使用.get()并提供默认值就算字段层级变了也只是某个字段取不到不至于整个解析流程崩溃。6.3 京东的“懒加载”导致翻页失效京东搜索页的商品列表是分页加载的但下一页的URL并不是简单递增页码。我用build_search_url构造的?page2在测试时是正常的上线后跑了一段时间却发现翻到第三页以后返回的HTML里商品数量明显变少甚至为空。原因是京东的动态加载需要特定的请求头和点击行为触发。后来我改成先解析首页里内嵌的“翻页配置”再动态构造后续页面的请求参数才稳定下来。6.4 数据库写入要批量不能一条条insert初期我把每条商品数据单独insert一次结果采集1000个商品要建立1000次数据库连接时间全都耗在IO上了。后来改成用executemany每50条批量提交一次采集速度提升了好几倍。这个优化虽然简单但对长跑任务来说是质的区别。def save_products(conn, products: list): if not products: return sql INSERT INTO products (platform, product_id, title, price, sales, shop_name, crawled_at) VALUES (%s, %s, %s, %s, %s, %s, %s) ON DUPLICATE KEY UPDATE price VALUES(price), sales VALUES(sales), crawled_at VALUES(crawled_at) batch [ (p.platform, p.product_id, p.title, p.price, p.sales, p.shop_name, p.crawled_at) for p in products ] cursor conn.cursor() cursor.executemany(sql, batch) conn.commit()6.5 监控日志是救命稻草上线初期我总觉得“代码能跑就行”日志打印得特别随意。结果有一次任务跑到了凌晨突然批量失败醒来一看全平台数据都是空的连失败原因都查不出来。后来我老老实实接了一套完整的日志监控每个请求记录状态码、耗时、当前关键词、当前页码失败时保存完整响应体到本地。再出问题时我只需要拉日志就能定位是哪个环节出了问题。写在最后多平台电商爬虫这个项目难的不是某个单独的爬虫脚本而是怎么在一套框架里平衡五个平台的差异。我的经验是平台差异用Adapter隔离请求频率按平台单独配置数据字段统一标准化反爬策略以控制和人工介入为主。这样设计出来的系统不敢说永远稳定但至少能让你在某个平台改版后只用改一个文件就能满血复活。最后说个实际感受爬虫这东西七分靠工程三分靠技巧。网上能搜到的“高深逆向教程”大多是屠龙之技真正让一个项目活下来的是清晰的模块划分、合理的限速策略、完善的日志监控以及严格的合规意识。希望这套设计思路能帮你少踩几个坑。本文还有配套的精品资源点击获取
返回列表