拓冰建站拓冰建站
首页 / 资讯中心 / 正文

希音Web采集实战:从商品解析到价格监控的完整爬虫方案

做电商数据的人应该对Shein希音不陌生。这家快时尚品牌这几年的增长速度有多夸张行业里都有目共睹。而围绕希音的“web采集”需求这两年也越来越多——有人想做价格监测有人想跟踪新品上架节奏有人想分析品类分布还有人想给自家的选品系统喂数据。无论目的是什么本质都是同一件事把希音网站上的商品信息、价格变动、评价内容等公开数据用程序化的方式持续地、结构化地抓取下来。这篇文章就把我实际做过的希音web采集项目完整拆开讲一遍。从最开始的方案选型到请求构造、数据解析、存储设计再到跑起来之后遇到的各种坑都会覆盖到。如果你正准备做类似的电商数据采集或者已经在做了但遇到瓶颈这篇应该能帮你省掉不少试错时间。1. 项目整体思路为什么绕不开Web采集这条路很多人第一反应是找官方API。但希音并没有面向第三方开放商品数据接口市面上能看到的数据服务商要么是自己在持续采集要么是走了某个灰色渠道。对于绝大多数需要数据的团队或个人来说web采集是唯一现实可行的手段。1.1 核心需求拆解采集希音到底要拿什么数据动手之前先把需求定义清楚。不同业务方向采集的侧重点完全不同。最常见的几类需求是这样的价格监控与调价策略需要采集商品详情页的售价、划线价、折扣率按SKU维度记录价格变化历史。这类需求要求采集频率高、字段精确对价格字段的实时性要求很严格。新品上新跟踪需要采集新品列表、上新时间、首图、销量趋势。这类需求更关注列表页和分类页需要能识别“哪些是最近7天上架的”。竞品品类分析需要采集某一大类下的商品结构、价格带分布、属性分布。这类需求对单品的细节要求不高但需要覆盖全要能分页拉取完整列表。评价与口碑分析需要采集商品评论内容、评分分布、评论时间。这类需求对翻页和去重的要求很高评论多时容易产生大量重复请求。我做的这个项目主打的是综合型采集以价格监测为切入点兼顾新品跟踪。所以核心目标就从“把商品页抓下来”变成了“把商品主键、SKU信息、价格体系、上下架状态这四类字段稳定地拿回来”。需要明确一点采集的目标不是把整个网站镜像下来。存储成本、带宽成本、解析成本都摆在那里采集前先画清楚字段边界能帮你省掉后面大量的麻烦。1.2 方案选型自建采集框架还是用现成爬虫工具确定要采以后下一个问题就是用什么实现。我见过有人用现成的图形化采集工具比如后羿采集器、八爪鱼这类也有人用Python自己写。两种方式各有各的适用场景。图形化采集器的优势是上手快不需要写代码配置选择器之后就能跑。劣势也很明显网站前端一改版配置就得全部重新做复杂的分页和动态加载处理起来很吃力而且并发控制和反爬策略基本没有可调空间。自己写采集程序前期投入大一点但后面所有环节都可控。请求频率、重试机制、代理切换、数据落库方式、增量更新的判断逻辑全部按项目需求定制。对于一个长期要跑的数据采集项目我强烈建议用代码实现。语言就选Python生态最成熟requests、parsel、pandas这些库随手就能用后期如果要做数据分析也不用换语言。框架层面我没用Scrapy直接上了requests parsel sqlite的组合。原因很简单这个项目的目标站点相对单一不需要Scrapy那种分布式调度能力requests的代码路径更直接调试起来心智负担低。如果你要采几十个网站Scrapy有价值但单一站点用轻量方案效率更高。2. 采集环节的技术拆解从请求构造到数据落库这一部分是整个项目的核心。我会按实际工作流的顺序把每个环节的做法和思考讲清楚。2.1 页面分析与请求定位搞清数据到底藏在哪打开希音的官网随便点进一个商品详情页会看到页面结构非常复杂。图片懒加载、首屏直出、滚动动态加载、脚本动态渲染——看起来数据好像是前端Ajax请求动态拉取的对吧其实不全是。这里有个关键发现希音的商品详情页很多核心数据是直接内嵌在首屏HTML里的。具体来说在页面的script标签里有一段JSON数据里面包含了商品的名称、价格、SKU属性、图片地址等信息。这意味着只要拿到详情页的HTML源码不需要执行任何JavaScript就能解析出绝大部分关键字段。这个发现对采集方案的影响是决定性的。它意味着不需要渲染JavaScript也就省了Selenium、Playwright这类重型工具不需要处理接口签名和加密参数这类动态请求通常会有token之类的校验直接对静态HTML做解析就能拿到结构化数据所以真正要做的工作被拆成了两步第一步定位到包含数据的那段JSON在HTML中的位置第二步写一个稳健的解析逻辑把它提取出来。实际操作中可以在浏览器里打开开发者工具选Sources或者Elements面板按关键词比如价格字段、商品ID搜索页面源码很快就能定位到那段脚本。说白了就是找到数据所在的锚点然后用正则或者parsel提取即可。2.2 请求构造Header、Cookie与基础参数处理拿到页面之后要构造出能正常返回200的请求。这一步有几个细节需要注意。首先请求头必须模拟真实浏览器的行为。UAUser-Agent是基础中的基础但光有UA还不够。我实测下来以下请求头字段对希音站点的响应率有明显影响Header字段推荐值说明User-AgentChrome最新版UA不可缺失Accepttext/html,application/xhtmlxml,...让服务端知道你要的是HTMLAccept-Languagezh-CN,zh;q0.9,en;q0.8影响页面返回的语言版本Accept-Encodinggzip, deflate, br服务端会返回压缩内容需让requests自动解压Connectionkeep-alive保持长连接减少握手开销Referer对应分类页URL部分页面会校验来源页Cookie的问题要单独聊一下。希音的商城分很多区域站点美国站、欧洲站、中东站等不同站点的语言、货币、商品价格体系都是独立的。首次请求时服务端会种下区域识别相关的Cookie后续请求带上这些Cookie才能返回正确区域的内容。实践下来最稳的方式是用requests.Session()保持会话先手动请求一次首页拿到初始Cookie再带上这个Session去请求详情页。这样避免了自己手工拼接Cookie串的麻烦也能让服务端认为你是连续操作的正常用户。一个容易踩的误区不要以为Cookie字符串固定不变就写死在代码里。希音的Cookie中有几个关键值是有时效性的过期之后再用老Cookie请求会被直接拒绝或强制跳转到验证页面。用Session自动管理Cookie是最省心的方案。2.3 数据解析从HTML中稳定提取JSON数据拿到HTML源码之后解析策略非常关键。我采用的方法是先定位包含数据的大块script片段再从片段中抽离JSON最后用json库解析成字典。定位脚本片段的锚点我在项目里用的是window.__PRELOADED_STATE__这个变量名不同的站点版本变量名可能不同需要自行确认。经验是优先搜索类似__INITIAL_STATE__、__PRELOADED_STATE__、window.__data这些常见命名哪个存在用哪个。这里有个关键点这段脚本里的JSON并不是严格的JSON格式。因为JavaScript的对象字面量允许key不带引号虽然在现代代码中很少这么写而且可能包含undefined这样的非JSON值。所以直接用json.loads解析大概率会失败。我的处理方式是这样的先用正则把window.__PRELOADED_STATE__ {...};这段整体提取出来用demjson3或者json5这类宽松JSON解析库去解析而不是直接用标准json库解析失败时用html.unescape处理掉HTML实体比如quot;、\u003d这类转义解析成功之后数据是一个多层的嵌套字典。商品详情页里核心字段通常藏在类似product、goods这样的子节点下。需要做的就是从嵌套结构中找到正确的路径然后一层层取出来。举个例子价格字段在我当时采集的版本里路径大概是data.product.price.salePrice这样的结构但不同站点版本有差异。笔者的建议是第一次解析时把整个JSON结构递归打印出来人工查看一遍字段路径然后再写提取逻辑。不要凭感觉猜路径因为猜错的代价是后面要反复修。2.4 数据清洗与字段映射原始数据不能直接入库从JSON里提取出来的数据是网站前端的原始数据结构直接入库是有问题的。原因有三个字段命名不规范前端喜欢用简写、格式不统一有的价格是字符串有的已经是数字、存在大量冗余很多渲染用的配置字段对分析完全没用。所以我在提取之后加了一层清洗和映射逻辑。核心工作包括把前端字段名统一映射成自己业务定义的字段名比如prodId-product_id把价格字段统一转换成Decimal类型避免浮点精度问题把时间字段统一成datetime格式方便后续按时间排序、计算把图片URL里的尺寸参数替换掉希音的图片URL通常会带_thumbnail之类的后缀需要换成原图参数把SKU列表展开成一行一行的SKU记录而不是塞在一个字段里这层清洗逻辑的价值在于原始采集程序只需要管“抓取和解析”业务数据层只需要管“消费和分析”中间的转换规则独立维护任一侧变更时另一侧不受影响。3. 实操过程复盘一次完整的采集任务是怎么跑通的这一节我按实际项目的操作顺序把从零到有、从单页面到批量跑通的过程完整复盘一遍。你跟着这个流程走一遍基本就能跑通自己的采集。3.1 环境准备依赖清单与工程结构我的Python环境是3.10版本。第三方库用得不多核心就这几个pip install requests parsel json5 pandas openpyxlrequests发HTTP请求parsel解析HTML底层是lxmljson5解析宽松JSON处理前端变量赋值时的非严格JSONpandas openpyxl数据清洗和导出Excel用工程结构上我按功能模块拆分了文件而不是把所有代码放在一个脚本里shein_crawler/ ├── config.py # 配置目标URL、请求头、频率控制参数 ├── fetcher.py # 请求模块负责发送请求、重试、错误处理 ├── parser.py # 解析模块定位script块提取JSON ├── cleaner.py # 清洗模块字段映射、类型转换、去重 ├── storage.py # 存储模块负责写入sqlite ├── scheduler.py # 调度模块控制采集频率和增量更新 └── main.py # 入口编排整个采集流程模块化的好处后面会体现得很明显。比如希音网站改版导致解析逻辑需要调整时只需要改parser.py其他模块完全不用动。如果你把全部逻辑都写在一个脚本里改一处就要整体回归测试。3.2 请求模块的代码实现带重试和频率控制的请求封装请求模块是整个采集程序的基石。我封装了一个带重试机制和限速控制的请求函数核心逻辑如下import time import random import requests from requests.adapters import HTTPAdapter class Fetcher: def __init__(self, sessionNone, max_retries3): self.session session or requests.Session() # 挂载HTTP适配器设置连接池大小和重试策略 adapter HTTPAdapter(pool_connections10, pool_maxsize10, max_retriesmax_retries) self.session.mount(https://, adapter) self.session.mount(http://, adapter) # 默认请求头 self.session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.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, }) def get_html(self, url, paramsNone): for attempt in range(3): try: resp self.session.get(url, paramsparams, timeout15) if resp.status_code 200: return resp.text elif resp.status_code in (403, 429): # 被限流或拒绝等待后重试 wait_time 10 * (attempt 1) random.uniform(1, 3) time.sleep(wait_time) continue else: resp.raise_for_status() except requests.RequestException as e: if attempt 2: raise time.sleep(2 * (attempt 1)) return None这里有几个设计点值得说明重试策略。网络请求不是每次都成功超时、连接重置、服务端5xx都时有发生。重试次数设为3次每次重试的等待时间递增避免刚失败完立刻又去请求导致更大的压力。频率控制。这个是重点。我见过很多人写采集程序for循环里直接调请求几百个页面几秒钟就发完了结果IP很快被封。我项目里的节奏是每两个请求之间至少随机间隔2到5秒。看起来好像很慢但稳定性和持久性远比瞬时速度重要。一次性把IP打没后面要等很久才能解封。随机间隔的意义在于固定间隔比如每3秒一次其实也是可以被规则识别的。用随机数把间隔打散让请求时间序列更接近人为操作。3.3 解析模块的代码实现定位脚本块并提取JSON解析模块的代码核心是提取script块里的大JSON。这里的关键是要处理“非严格JSON”的问题。import re import json5 from parsel import Selector class Parser: # 定义变量名到JSON的匹配模式 PATTERN_JSON re.compile(rwindow\.__PRELOADED_STATE__\s*\s*(\{.*?\});, re.S) def parse_product(self, html): sel Selector(texthtml) scripts sel.xpath(//script/text()).getall() json_text None for script in scripts: # 优先找包含核心数据的script块 if __PRELOADED_STATE__ in script: m self.PATTERN_JSON.search(script) if m: json_text m.group(1) break if not json_text: return None # 处理HTML实体转义 json_text bytes(json_text, utf-8).decode(unicode_escape) # 用json5解析非严格JSON data json5.loads(json_text) return data有几点要特别注意正则匹配的惰性匹配问题。上面示例里用(\{.*?\});是非贪婪模式如果这段JSON里本身就包含};这种字符串就会提前截断。实际项目中我一般不用正则直接匹配整个JSON而是用字符串查找的方式定位起始位置和结束位置起始位置是变量名后的第一个{结束位置是与之配对的最后一个}。要写一个简单的括号计数器来配平。这样更稳。decode的顺序。如果先做json5.loads再做unicode_escape可能会出问题。先解码转义再解析JSON顺序不能乱。前端变量名的稳定性。希音的代码在改版时有时候变量名会变。如果某天解析突然返回None优先去页面源码里搜一下window.开头的变量看看有没有改名。这个检查可以写成日志告警方便第一时间发现。3.4 数据存储与增量更新SQLite起步避免全量重复抓取存储方案我用的是SQLite。项目数据量在几十万量级以内时SQLite完全够用还免去了部署维护数据库服务的负担。表结构设计上按业务需要拆了两个核心表CREATE TABLE products ( product_id TEXT PRIMARY KEY, title TEXT, category TEXT, sale_price DECIMAL(10,2), original_price DECIMAL(10,2), discount_rate DECIMAL(5,2), product_url TEXT, first_seen_at DATETIME, last_seen_at DATETIME, is_active INTEGER DEFAULT 1 ); CREATE TABLE price_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, product_id TEXT NOT NULL, sale_price DECIMAL(10,2), original_price DECIMAL(10,2), check_at DATETIME NOT NULL, UNIQUE(product_id, check_at) );增量更新逻辑是这样的products表存商品当前状态product_id为主键每次采集时INSERT OR REPLACEprice_history表记录的是每次价格检查的快照用于追溯价格变化历史每次采集前先从products表里读出当前商品的product_id集合再跟本次采集到的product_id做差集。差集里的商品置为is_active 0表示已下架。这样就能追踪上下架状态变化。这个设计看起来简单但实际用起来很有价值。比如做价格监控时想知道某个商品最近一个月涨过几次价直接查price_history表即可不需要去第三方平台买数据。4. 高频问题与排查记录实测中踩过的坑跑采集项目的时间一长各种奇奇怪怪的问题都会冒出来。这一节把我在项目里实际遇到的高频问题整理成清单并附上排查思路和解决方案。4.1 频繁请求导致IP被限流怎么办这个恐怕是做采集都会遇到的第一个坎。现象是程序跑着跑着突然连续出现403状态码或者页面返回的内容变成了一段验证JavaScript的代码要求浏览器环境验证。这说明服务端已经检测到你来自同一IP的访问频率异常。我的处理方式分三层第一层调低请求频率。把间隔从最短1秒调整到5秒以上观察是否恢复。多数情况下单纯降低频率就能让问题消失前提是IP还没被拉黑。如果已经返回验证码说明IP已经被标记了要等待一段时间自然解封。第二层更换IP出口。如果业务对采集量有较高要求单IP不够用就需要引入代理IP池。这里要说明的是今天的商用代理服务质量参差不齐很多便宜的代理IP本身就属于高风险IP段用上之后反而更容易触发风控。我的经验是优先选住宅代理或者高质量数据中心代理频率控制在每个IP每分钟不超过10次请求。第三层行为模拟。在请求序列中模拟人类操作的特征比如访问详情页之前先访问这个商品的列表页或分类页请求间隔用随机分布每天的访问时间段控制在北京时间白天到晚上。这些行为序列的模拟能在一定程度上降低风控识别的概率。但我必须提醒一句任何绕开风控的手段都有合规边界。采集的初衷如果是价格监测、学术研究这类合理用途就尽量保持低姿态如果业务本身就对数据实时性要求极高且需要绕过各种防护那就需要认真评估法律风险。4.2 解析出来的价格字段偶尔为空或异常运行一段时间后会发现极少数商品的价格字段解析出来是null或者0。去页面源码一看发现这些商品要么是“已售罄”要么是“即将上架”页面里根本没有渲染价格节点。这种情况要做的不是修解析逻辑而是处理边界场景售罄商品价格可能还在也可能被替换成“补货提醒”之类的文本。采集时如果解析不到价格就记录为null同时标记商品状态为out_of_stock预售商品价格存在但可能显示的是预售价需要在字段里增加一个price_type标识多规格商品不同SKU的价格不同页面展示的是价格区间比如“$12.99 - $15.99”。解析时如果遇到区间价格需要把它拆成min_price和max_price两个字段而不是直接存字符串这种字段级的数据质量细节决定了你后面做分析时要不要花时间清数。我在项目里专门加了一层数据校验规则凡是不符合预设类型和范围的数据统一丢弃并写入错误日志而不是让它混入主数据表。4.3 站点改版导致解析逻辑失效怎么办希音的站点更新频率不算低前端布局一改最脆弱的环节就是CSS选择器和JSON路径。我遇到过一次典型情况某天例行任务跑完后检查日志发现成功解析率为0。排查后发现页面里的变量名从__PRELOADED_STATE__改成了__NEXT_DATA__。改一行正则的匹配规则就恢复了。这个事件给我的启发是一定要做采集成功率监控。每天任务跑完后统计当天成功解析的商品数与预期数量的比值低于阈值就告警解析逻辑要与HTML解耦。不要在产品代码里到处散落着CSS选择器而是收敛到专门的解析配置模块里定期人工抽检页面源码。每周抽几个商品页人工查看页面结构是否发生变化。这个动作花不了多少时间但能让你在站点改版的早期就发现问题而不是等到大量数据缺失才察觉4.4 采集频率越来越慢如何提升整体吞吐量出现这种情况通常是因为增量更新逻辑没做好。很多初学者的做法是每次全量抓一遍所有商品页。商品数量少时没问题但当商品数量达到几十万级别后全量抓取的耗时会从几十分钟逐渐变成几天最终不可持续。解决思路是分层调度高优先级商品比如自己关注的竞品或最近有调价的商品每1~2小时抓一次中优先级商品正常在售商品每6~12小时抓一次低优先级商品下架商品或长期无变化的商品每24~48小时抓一次这种分层的核心价值是在有限的请求预算内把采集资源集中在最有信息增量的事件上。价格变动通常发生在一段时间的集中调价期新品上新集中在某几个时间点这些都是高优先级的采集时机不需要均匀地扫全量。5. 合规与风险控制采集项目的底线思考做数据采集绕不开合规这个话题。我现在做采集项目第一步不是写代码而是先把合规边界画清楚。5.1 明确数据使用边界希音网站上的商品数据本质上是公开可浏览的信息任何人都能在浏览器里看到。但“公开可见”不等于“可以随意使用”。用这些数据做价格监测、市场分析这类研究性用途风险相对可控但如果是大规模抓取后用于商业转售、用于训练竞对模型、或者在自营平台上直接搬运他人商品信息法律风险就会显著上升。在做项目之前建议先想清楚这三个问题采集的数据是用于内部决策分析还是对外提供数据服务采集的频率是否会影响目标网站的正常运营采集的数据是否包含个人信息比如用户昵称、评论者的ID第三个问题尤其敏感。个人信息有专门的法律保护体系采集包含个人信息的数据需要格外谨慎。如果业务确实需要评论数据建议做脱敏处理只保留内容文本和评级不保留用户标识。5.2 技术上的风险控制策略技术上我习惯遵循几条底线单IP请求频率严格控制在人类浏览水平每秒不超过1次避开业务高峰时段大批量采集减少对目标站点的负载影响不尝试绕过登录、验证码等主动防护机制这类行为性质完全不同不对目标网站发起超出正常浏览行为之外的请求花样比如并发测试、接口探测等这几点做下来采集项目能在相当长的时间里稳定运行同时又不会给目标站点带来明显的负担。数据采集这个事追求的不是“跑得多快”而是“跑得多稳、能跑多久”。慢一点、稳一点细水长流地持续更新比短时间暴力抓取然后被封IP要高效得多。5.3 数据质量的持续保障机制最后再分享一个我认为每个采集项目都必须有的设计数据质量看板。不需要很复杂一个简单的统计脚本就够了。每天自动汇总以下指标指标正常范围触发预警的条件商品详情页成功率98%以上低于95%新增商品数量每日50~500不等为0或异常突增10倍以上价格变动商品数每日10~200不等为0或异常突增下架商品数量每日0~500不等连续3天为0解析空字段率低于2%超过5%这些指标一旦有异动第一时间去看日志定位问题而不是等数据积累到不可收拾再返工。采集项目的日常维护80%的工作量其实都是在处理“数据质量变差”这件事剩下20%才是写新代码。我实际跑了一段时间后最大的感受是单次采集成功并不难难的是让这个采集程序在一个月、一个季度、甚至更长时间里保持稳定输出。做到这一点的关键不在代码多高效而在于你对这个站点的变化有没有感知、对数据质量有没有监控、对异常情况有没有预案。如果你正准备做希音的web采集建议先小范围地跑通一条商品链路的完整流程确认字段解析稳定、存储逻辑无误之后再逐步扩大商品覆盖范围。一上来就想全量抓大概率会在早期被各种边界问题拖住进度。稳扎稳打先跑通再跑宽这个节奏对大多数采集项目来说都是最省力的。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门