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

资讯详情

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

通用电商数据采集框架设计与实践:从单平台到多平台爬虫架构

通用电商数据采集框架设计与实践:从单平台到多平台爬虫架构 简介本资源是一套面向Python爬虫初学者与电商数据分析爱好者的多平台商品信息采集工具聚焦淘宝、京东、拼多多、1688及京喜五大主流电商平台解决跨平台商品数据批量获取难、结构化提取弱、运行状态不可视等实际问题。压缩包共20个文件大小1.21MB包含8个核心爬虫脚本如taobaoSpider.py、jdSpider.py、pdd_HAR_reader.py等、5张开发调试截图含抓包工具界面与网络面板实拍、4份说明文档含get_har.md、readme.txt等、1个授权文件及1段提示音wav兼顾代码逻辑、环境配置与可视化交互。已有346人学习下载提供完整GUI界面基于Tkinter、Cookie自动处理机制、HAR解析能力及模块化函数封装读者可直接运行、快速调试并深入理解电商反爬应对策略与多源数据统一建模思路。 先说一下这个项目的情况。市面上打着“淘宝、京东、拼多多、1688、京喜商品信息爬虫”旗号的源码很多但真正能落地、能持续维护的极少。原因不在于Python代码本身有多难而在于这些平台的防护策略、页面结构、数据接口一直在变一套写死的代码往往上线第一天能跑第二天就失效。所以我更愿意把这篇文章定位成从零设计一套通用电商商品信息采集框架的思路拆解与实操记录。重点讲清楚架构怎么搭、字段怎么兼容、反爬怎么面对、增量更新怎么做而不是贴一份跑不通的源码就完事。如果你正准备做电商数据采集、比价分析、市场调研或者自建商品库这篇文章会比较适合你。它能帮你快速理解一套爬虫系统从设计到落地的完整链路也告诉你哪些环节最容易踩坑。另外提醒一句任何爬虫项目都必须先确认目标网站的robots协议和使用条款个人学习、少量公开数据采集没问题商业用途优先考虑官方开放平台API这是做这行的基本职业素养。1. 项目整体设计与需求拆解1.1 多平台商品数据采集的真实需求先看这个标题里的关键词淘宝、京东、拼多多、1688、京喜。这五个平台本质上分两类一类是C端零售一类是B端批发。1688走的是批发采购逻辑商品字段更侧重起订量、价格阶梯、供应商信息而淘宝、京东、拼多多、京喜更偏向零售字段围绕销量、评价、优惠信息来设计。如果你要设计一套覆盖五个平台的爬虫第一步不是写代码而是先梳理清楚各平台商品页的数据差异抽象出一套统一的商品字段模型。我在实际项目中常用的统一字段模型大约长这样字段分组字段名说明基础信息item_id, title, brand, category_path商品唯一标识、标题、品牌、类目路径价格信息price_min, price_max, original_price, promo_price多规格商品需区分价格区间销量与评价monthly_sales, total_comments, avg_rating销量维度各平台统计口径不同图片与描述main_image, image_list, description详情页的图片和富文本描述商家信息shop_name, shop_id, seller_location旗舰店/专营店等店铺类型供应链字段min_order_qty, price_tiers, delivery_area1688特有批发业务重点这套模型的好处是无论你最后是存MySQL、MongoDB还是导CSV都能统一处理。代码里解析完各平台原始页面后直接把结果映射到这个模型里后续的统计、分析、展示都不需要再针对每个平台单独开发。1.2 项目模块划分与工作流程整个爬虫系统我一般拆成五个模块调度器、下载器、解析器、存储器、监控模块。调度器负责任务队列的管理比如你是要全站采集某个类目还是按关键词搜索商品还是只增量更新指定商品ID列表这对应三种不同的调度策略。下载器负责HTTP请求的发送与重试需要处理请求头、代理、Cookie、限速。解析器是核心负责把HTML或JSON转成统一商品模型。存储器负责数据落库与去重。监控模块负责日志收集、任务失败告警、数据量统计。实际项目中这几个模块的调用关系是调度器从任务队列取出一个任务交给下载器下载页面下载成功后将页面内容传给解析器解析器产出结构化数据交给存储器同时把解析出的新链接比如分页链接、商品详情链接回传给调度器形成循环闭环。每个模块之间通过队列解耦这样任何一个模块升级或挂了其他模块还能独立运行。1.3 关于“源码”这件事的真相很多人说“求一份爬虫源码”但源码拿到手能不能用取决于你对这套系统的理解程度。我看到过太多人下载了一份所谓源码连依赖库版本都对不上requests、lxml、scrapy版本冲突直接跑不起来。电商爬虫的代码从来不是一锤子买卖它是一个需要持续维护的工程。我的建议是源码可以看但更重要的是自己动手搭一遍框架把每个模块的职责理解清楚然后根据目标网站的实际页面去写解析逻辑。这篇文章后面的实操部分我会用一个公开的练习站点做演示代码可以完整跑通你把它替换成真实平台的页面结构时也能知道要改哪些地方。2. 技术选型为什么是Python以及核心库怎么选2.1 Python在爬虫领域的生态优势选Python做爬虫几乎是行业默认原因很直白生态成熟上手快第三方库覆盖了爬虫全链路。requests或httpx负责HTTP请求parsel或lxml负责HTML解析pandas负责数据处理SQLAlchemy负责ORM存储APScheduler负责定时调度celery负责分布式任务分发。这些库组合起来一个人也能撑起一套中等规模的采集系统。有个细节值得说一下requests和httpx怎么选。requests是爬虫入门首选接口简洁文档多遇到问题搜一下全是答案。httpx的优势在于支持HTTP/2和异步对于需要高并发请求的场景更合适。如果只是中小规模采集requests完全够用。我实际项目的经验是requests库用久了你会慢慢积累一套自己的请求头模板和重试机制反而比追求新库更稳定。2.2 解析库选择正则、XPath还是CSS选择器解析是爬虫开发里最费时间的一环。页面结构一变解析代码就得跟着调。三个方案各有适用场景正则表达式适合从JSON字符串或script标签里提取数据比如很多电商平台会把商品数据直接以JavaScript变量的形式写在页面源码里这种情况用正则提取最直接。XPath适合复杂嵌套的HTML结构尤其适合需要根据层级关系定位元素的场景。lxml库的XPath解析效率高写起来表达力强。CSS选择器适合结构相对规整的页面语法简洁parsel库直接支持配合浏览器开发者工具复制selector路径入门很快。我个人的习惯是优先CSS选择器因为浏览器的DevTools能直接帮我把选择器路径复制出来省时间。遇到CSS选不中的情况再转XPath最后才用正则去做兜底。这套组合在电商平台上基本够用。2.3 请求伪装与反爬应对的边界这里必须说清楚一件事反爬的本质是网站为了保护自身数据和服务资源而设置的技术门槛爬虫方的所有伪装手段本质上是在和网站方做对抗。作为技术分享我可以讲清楚常见的反爬机制原理和应对思路但不鼓励任何绕过平台核心风控、破解验证码、攻击服务器接口的行为。真正的商业项目合规的路径是申请开放平台API或者与平台方达成数据合作。常见的基础反爬手段包括检查User-Agent、检测请求频率、校验Referer、Cookie鉴权、JS动态渲染、WebSocket推送等。对应的工程应对思路是设置合理的User-Agent和请求头、控制请求频率、使用Cookie池、分析接口数据而非页面数据。这些手段的边界是“模拟正常用户行为”而不是“突破平台安全防护”。3. 核心架构设计与数据模型详解3.1 任务调度与队列设计一个电商爬虫系统通常会面对三种任务类型批量采集、增量采集、垂直采集。批量采集是指第一次跑全量数据比如某个类目下所有商品增量采集是后续定期更新只抓取有变化的商品垂直采集是指只关注特定条件的数据比如某个店铺的全部商品或某个关键词下的搜索结果。对应到调度器设计上我习惯用一张task表来管理所有任务字段包括task_id、task_type、status、params、retry_count、create_time。task_type标识任务类型params存JSON格式的参数比如关键词、类目ID、页码范围、排序方式。调度器启动后从task表拉取待处理任务按策略分发给下载器。下载完成后回调更新任务状态。这样一套设计的好处是任务可以持久化系统重启后能接着跑不会丢任务。3.2 商品ID与去重策略商品ID是整个系统的核心关联键。各平台的商品ID有不同的命名规则淘宝是纯数字ID京东的商品ID也是数字拼多多的是较长数字串1688的产品ID同样是数字形式。统一处理时建议统一转为字符串存储因为你无法预知未来某平台的ID是否会出现非数字字符。去重策略要分为URL去重和数据去重两层。URL去重是为了避免重复下载同一个页面常用做法是维护一个已访问URL的集合用哈希值做索引。数据去重则是针对商品数据本身同一个商品可能通过不同入口被抓到比如搜索页和类目页都包含它。数据层去重我通常以item_id platform为唯一键用数据库唯一索引约束插入时采用INSERT ... ON DUPLICATE KEY UPDATE的方式既能去重又能实现字段更新。3.3 增量采集的实现方式增量采集在电商场景下是个很实际的需求。你不可能每天把全站几百万商品都重爬一遍所以需要设计一套只更新变动数据的机制。常见的思路有两种基于时间戳和基于数据版本。时间戳方案是在商品数据表里加一个updated_at字段每次采集时只处理过去24小时内更新过的商品。实现上可以通过搜索接口的排序参数按更新时间排序取最近更新的商品列表。数据版本方案是保存每次采集的商品数据快照下次采集时对比字段变化发生变化才更新。前者实现简单适合中小规模数据后者更精准但需要额外的存储空间。3.4 数据存储选型关于存储很多人一上来就纠结用MySQL还是MongoDB其实主要看数据量级和查询需求。商品数据的特点是结构相对固定字段嵌套不算特别深但是会涉及大量条件查询比如按价格区间、销量、店铺ID筛选。这种情况下MySQL表现稳定配合JSON字段类型可以兼顾灵活性。如果数据量超过千万级并且有复杂的聚合分析需求可以考虑ClickHouse。但中小规模项目直接用MySQL就行我见过很多项目用MySQL撑到几千万条商品数据依然没问题关键在于索引设计和分表策略。索引方面platform item_id的联合唯一索引必须有price和monthly_sales做普通索引应对条件查询。当单表数据量过大时按platform做分表是最简单的方案。4. 实操环节最小可运行的商品信息采集框架4.1 演示环境准备下面用一个公开的练习站点来演示完整流程。选练习站点而不是真实电商平台是因为真实平台的反爬策略和页面结构变化太快今天写好的代码可能明天就失效而且未经授权对真实平台发起持续请求也可能产生合规风险。练习站点虽然数据简单但请求、解析、存储、去重这套技术流程完全一致你把解析逻辑换成实际目标站点的selector即可。# 创建虚拟环境 python -m venv .venv source .venv/bin/activate # 安装依赖 pip install requests parsel pandas pymysql版本说明requests 2.31.0parsel 1.8.0pandas 2.0.0pymysql 1.1.0Python 使用 3.9 及以上版本。如果环境里已有其他版本建议先用pip freeze看一下依赖情况避免版本冲突。4.2 请求模块的实现import requests import time import random class Downloader: def __init__(self): self.session requests.Session() self.session.headers.update({ 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, Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8 }) self.retry_count 3 self.timeout 15 def get(self, url, paramsNone): for retry in range(self.retry_count): try: resp self.session.get(url, paramsparams, timeoutself.timeout) if resp.status_code 200: return resp elif resp.status_code in (403, 429): # 403通常是触发了访问限制429是请求过于频繁 # 正确做法是退避等待而不是加大力度 time.sleep(random.uniform(5, 10)) continue except requests.RequestException as e: print(f请求异常: {url}, 错误: {e}, 第{retry1}次重试) time.sleep(2 * (retry 1)) return None代码里的几个细节说一下。第一Session对象会复用底层的TCP连接多页请求场景下比每次新建requests.get()效率高不少。第二UA直接用了Chrome的完整版本信息避免被基础反爬识别。第三重试策略区分了网络异常和状态码异常网络异常用指数退避403/429用固定长等待这个设计比较实用。4.3 解析模块与字段映射from parsel import Selector class Parser: def parse_list_page(self, html): sel Selector(texthtml) items [] for li in sel.css(.product-list li): item { item_id: li.css(.product-id::text).get(), title: li.css(.product-title::attr(title)).get(), price: li.css(.product-price::text).get(), sales: li.css(.product-sales::text).get(), detail_url: li.css(.product-title::attr(href)).get(), } if item[item_id]: items.append(item) return items def parse_detail_page(self, html): sel Selector(texthtml) data { description: sel.css(.detail-description::text).get(), image_urls: sel.css(.detail-gallery img::attr(src)).getall(), shop_name: sel.css(.shop-name::text).get(), category_path: sel.css(.breadcrumb a::text).getall(), } return dataCSS选择器解析时有个经验列表页尽量从每个商品条目的父节点开始定位避免跨商品边界选错元素。比如先选中li标签再在li的范围内继续找商品ID、标题、价格这样即使页面结构小幅调整解析代码的容错性也更高。这里也可以看到页面字段一般都带单位或货币符号比如价格字段可能是“¥199.00”入库前需要清洗。4.4 数据清洗与标准化清洗这块很容易被新手忽略但它决定了数据质量。以价格字段为例从页面上拿到的可能是“¥199.00”、“199.00元”、“199”等不同格式入库前统一转成浮点数。销量字段更乱淘宝可能显示“已售10万”拼多多显示“已拼1.2万件”京东显示“1000条评价”需要写一套归一化函数。import re def clean_price(price_str): if not price_str: return None num_str re.sub(r[^\d.], , price_str) try: return float(num_str) except ValueError: return None def clean_sales(sales_str): if not sales_str: return 0 num_match re.search(r([\d.]), sales_str) if not num_match: return 0 num float(num_match.group(1)) if 万 in sales_str or w in sales_str.lower(): num * 10000 return int(num)清洗函数的重点是兼容各种异常输入宁可返回None或0也不能让脏数据进库。实际项目中这一层的函数会在多个解析器之间共享所以测试用例要写详细把各种页面上能见到的格式都覆盖到。4.5 存储模块与去重逻辑import pymysql class Storage: def __init__(self, **db_config): self.conn pymysql.connect(**db_config) self.cursor self.conn.cursor() self.create_table() def create_table(self): sql CREATE TABLE IF NOT EXISTS products ( id BIGINT AUTO_INCREMENT PRIMARY KEY, platform VARCHAR(20) NOT NULL, item_id VARCHAR(50) NOT NULL, title VARCHAR(255), price DECIMAL(10,2), sales INT, shop_name VARCHAR(255), category_path VARCHAR(255), detail_url VARCHAR(500), description TEXT, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_platform_item (platform, item_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci self.cursor.execute(sql) self.conn.commit() def upsert_product(self, product): sql INSERT INTO products (platform, item_id, title, price, sales, shop_name, category_path, detail_url, description) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) ON DUPLICATE KEY UPDATE title VALUES(title), price VALUES(price), sales VALUES(sales), shop_name VALUES(shop_name), detail_url VALUES(detail_url), description VALUES(description) self.cursor.execute(sql, ( product[platform], product[item_id], product[title], product.get(price), product.get(sales), product.get(shop_name), ,.join(product.get(category_path, [])), product.get(detail_url), product.get(description) )) self.conn.commit()表结构设计上有几个点值得注意。platform item_id的联合唯一索引是实现去重的关键upsert操作能保证同一商品重复采集时直接更新字段不会产生重复记录。字符集用utf8mb4因为商品标题和描述里可能出现生僻字或特殊符号。sales字段用INT如果后续统计的销量超过21亿才需要考虑BIGINT现阶段INT足够了。4.6 主流程调度代码class Crawler: def __init__(self): self.downloader Downloader() self.parser Parser() self.storage Storage( host127.0.0.1, userroot, passwordyour_password, databasecrawler_db, charsetutf8mb4 ) def crawl_list(self, url, max_pages5): for page in range(1, max_pages 1): params {page: page} resp self.downloader.get(url, paramsparams) if not resp: continue items self.parser.parse_list_page(resp.text) if not items: break for item in items: item[platform] demo self.storage.upsert_product(item) print(f第{page}页完成, 本次采集{len(items)}条) time.sleep(random.uniform(1, 3)) # 限速是必须的 if __name__ __main__: spider Crawler() spider.crawl_list(https://example.com/products)主流程里的限速是很多人容易忽略的关键点。模拟正常用户的访问间隔既能降低对目标站点的压力也能减少触发反爬的概率。不要一口气把几百页请求瞬间发出去正常用户不会这么操作反爬系统一眼就能识别。5. 多平台扩展思路从单站点到五个平台5.1 平台差异与适配方案如果你完成了上面的单站点框架要扩展到淘宝、京东、拼多多、1688、京喜这几个平台核心工作主要集中在三块接口地址或页面地址不同、解析规则不同、反爬策略不同。以价格信息为例淘宝的商品价格可能在详情页的某个接口返回的JSON里京东的价格在页面渲染时通过JavaScript异步加载拼多多的价格数据内嵌在页面源码的一个JS变量中1688的价格则可能区分多个采购梯度京喜因为和京东同源很多接口结构类似但又略有差异。这些差异意味着你需要为每个平台编写独立的适配器。5.2 插件化架构设计多平台支持的正确姿势是插件化设计而不是把所有逻辑塞在一个文件里。我的做法是定义好基类每个平台写一个子类。class BaseSpider: platform base def parse_list(self, html): raise NotImplementedError def parse_detail(self, html): raise NotImplementedError class TaobaoSpider(BaseSpider): platform taobao def parse_list(self, html): # 淘宝专有解析逻辑 pass class JdSpider(BaseSpider): platform jd def parse_list(self, html): # 京东专有解析逻辑 pass这样设计之后新增一个平台只需要继承BaseSpider实现解析方法然后在工厂函数里注册即可其他模块完全不用动。5.3 接口优先原则在写真实平台爬虫之前强烈建议先研究一下各平台的开放平台能力。淘宝有淘宝开放平台京东有宙斯开放平台拼多多有拼多多开放平台1688也有对应的开放平台接口。开放平台提供的数据接口比页面爬取更规范、更稳定而且不会涉及合规风险。很多商品基础信息、价格、库存字段都能通过API拿到只是会有权限和调用配额限制。页面爬取应该定位为API的补充手段用于采集开放平台没有覆盖的字段或者是在没有开放平台权限的情况下的备选方案。6. 常见问题与排查技巧实录6.1 请求被拒绝或返回验证码这是遇到概率最高的问题。看到403状态码、验证码页面、或者“环境异常”之类的提示第一反应不是去和风控对抗而是先反思自己的请求行为是不是太像机器了。常见原因有三个请求频率过高短时间内的请求量超过阈值。请求头信息不完整比如缺少Referer、Accept等字段或者User-Agent是默认的python-requests。访问了需要登录才能查看的页面Cookie未携带或已过期。正确的处理顺序是先看目标平台的使用条款和robots协议再降低请求频率、补全请求头、确认是否需要用官方API。如果只是想学习技术换个公开接口或练习站点也是一样能练手。不要一上来就研究绕过验证码的方案那条路风险太高且违背做技术的初心。6.2 数据解析结果为空解析结果为空先别怀疑是页面变了按照下面的顺序排查用浏览器打开目标页面查看实际返回的HTML内容和代码里拿到的HTML是否一致。很多页面加载是异步的requests拿到的可能是空壳HTML真正的数据在后续的XHR请求里。检查搜索引擎是否禁用了CSS选择器路径直接在DevTools里复制selector拿到的是完整路径但页面结构调整后这个路径可能失效。用更短、更有标识性的class名会稳定很多。如果数据在script标签里JSON格式的用json.loads解析JavaScript变量的用正则提取。注意JavaScript对象字面量和标准JSON有区别比如key没有加双引号直接json.loads会报错需要先做转换。6.3 编码乱码问题中文乱码通常是因为请求返回的编码和requests自动检测的编码不一致。解决办法是在response上明确指定编码resp.encoding utf-8如果还是乱码就要看网页源码里的charset声明。有些老页面是gbk或gb2312编码这时候可以先用resp.apparent_encoding自动检测再手动指定。6.4 增量更新时的数据异常增量采集最怕遇到的情况是商品下架或ID失效。实际项目中一个商品可能在第一次采集中存在第二次增量采集时详情页返回404。这时候你需要一个下线标记机制而不是直接删除数据。比较通用的做法是在表中增加一个status字段0表示在售1表示已下架。增量采集时如果发现详情页不存在就把status更新为1这样既保留了历史数据也保证了数据状态的一致性。另外增量采集的时间窗口要设置合理。太短容易重复采集太长容易漏掉价格变动。我一般会设置成每6小时跑一次价格变动任务每天凌晨跑一次全量任务这样成本和时效性比较平衡。6.5 存储性能瓶颈与优化如果采集量达到百万级别数据库写入会逐渐变慢。优化手段是批量写入而不是逐条写入。逐条INSERT每条都要走一次网络往返和事务提交速度慢还占资源。改成批量INSERT后几百条一次提交写入速度能提升一个数量级。def batch_upsert(self, products): sql INSERT INTO products (platform, item_id, title, price, sales) VALUES (%s, %s, %s, %s, %s) ON DUPLICATE KEY UPDATE title VALUES(title), price VALUES(price), sales VALUES(sales) data [(p[platform], p[item_id], p[title], p.get(price), p.get(sales)) for p in products] self.cursor.executemany(sql, data) self.conn.commit()7. 从源码到工程的最后一公里代码能跑只是第一步真正把它变成一个工程化项目还需要补充一些细节。日志记录一定要有建议使用Python自带的logging模块按天滚动日志文件这样出了问题能回溯。任务状态持久化要做把未完成的任务记录到数据库里程序重启后能接着跑不用每次从零开始。错误告警机制也要考虑最简单的是在异常时发送邮件或企业微信通知复杂的项目可以接一套类似Sentry的服务。我在实际做这类项目时最深刻的体会是爬虫的代码量通常只占整个工程的30%剩下70%的工作都在处理异常情况、保证数据质量和维护系统的稳定性。很多人视野里爬虫就是抓数据但真正做久了就会发现能让数据持续稳定产出才是核心能力。如果你刚接触电商站点的采集我的建议是从小的公开站点练起把请求、解析、存储、去重这条链路跑通吃透再尝试迁移到真实平台并且优先考虑官方API接口。不要一上来就挑战最高难度的反爬对抗那既浪费时间也容易打打击信心。数据采集这条路上没有一劳永逸的方案保持好奇心和耐心比任何一份源码都重要。本文还有配套的精品资源点击获取
返回列表