ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

python爬虫框架scrapy实战之爬取京东商城进阶篇

python爬虫框架scrapy实战之爬取京东商城进阶篇 前言标题里的「实战」需要先加一个前提。电商平台是最典型的「不欢迎被第三方批量抓取」的站点类型它要防的不仅是服务器压力还有价格被竞品实时监控、商品数据被整体搬运。因此在给任何具体代码之前先把合规立场摆明本文不提供针对京东商城的可直接运行抓取方案。我在核实时也没能从该站取得 robots.txtHTTPS 连接未成功所以更不会凭印象去描述它的规则——请你在动手前自行查阅目标站点的 robots.txt 与使用条款并以此为准。那「进阶篇」还讲什么讲电商抓取这套技术本身怎么判断数据来自 HTML 还是接口、怎么按商品标识做去重与增量、怎么理解反爬的成因并正确应对、以及怎么把抓到的脏数据洗干净。这些技术用在你拥有或获授权的站点上完全合法学它们才是这篇的价值。至于真实的大规模商品数据正规路径是京东开放平台商家服务与开放接口、官方数据合作或商业数据服务商而不是自己写爬虫硬扛。顺便处理一个老问题这类教程的老版本里经常是 Python 2 代码print语句、xrange、dict.iteritems()。Python 2.7 已于 2020 年 1 月 1 日停止维护这些写法现在都会直接报错请改成print(...)、range、.items()。一、电商站的信息结构电商页面通常分三层抓取策略完全不同层级典型内容抓取要点列表页搜索/分类结果几十条商品卡片分页规律、商品 ID、链接归一化详情页单个商品的完整信息与列表页字段的关系、缺失字段动态加载价格、库存、评价、推荐判断是随 HTML 返回还是异步请求一个常见的误区是「列表页里已经有价格抓列表就够了」。实际上电商的列表页价格常是「区间价」或「促销价」真正的成交价往往要进详情页而且会随用户、随活动变化。所以在设计字段时就要想清楚你要的是哪个层面的价格它的时效性要求是什么。想不清楚就直接开抓最后多半会得到一堆互相矛盾的数据。二、先确定数据从哪来这是「进阶」的第一个分水岭不要一上来就写选择器先用浏览器开发者工具的 Network 面板观察一次页面加载——数据是随 HTML 一起返回的还是在页面加载后由额外的请求取回来的这是排查手段不是绕过手段。如果数据在 HTML 里用response.css()/response.xpath()解析配合scrapy shell调规则。如果数据在后续请求里先看那个请求是否需要登录态、是否带签名参数。如果需要说明对方不希望你直接调用——这时候正确动作是停手改走开放平台或商务合作而不是去研究怎么伪造签名。这一步的判断直接决定了后面的工作量。判断错了你会花大量时间在 HTML 里找一个根本不存在的字段。三、去重与增量按商品 ID 收敛电商抓取的第二个重点是「不要重复劳动」。同一条商品可能通过多个入口(分类、搜索、活动页)被多次发现如果不去重数据会被重复落库如果是定时监控问题更严重。做法是把「业务主键」作为去重依据——通常是商品 IDSKU。下例用一个管道按 SKU 去重同时把价格文本归一化# 适用于 Python 3.8Scrapy 2.ximport refrom scrapy.exceptions import DropItemclass ProductPipeline:def __init__(self):self.seen_ids set()def open_spider(self, spider):# 爬虫启动时调用一次适合做初始化spider.logger.info(管道已启动开始去重)def close_spider(self, spider):spider.logger.info(本次共处理 %d 条商品, len(self.seen_ids))def process_item(self, item):# 注意较新版本的 Scrapy 已弃用给 process_item 传 spider 参数# 若你的版本较旧、签名是 process_item(self, item, spider)按老版本文档写即可sku item.get(sku)if not sku:raise DropItem(缺少商品 ID丢弃)if sku in self.seen_ids:raise DropItem(f商品 {sku} 已处理过)self.seen_ids.add(sku)# 价格归一化只对能确定的格式做转换拿不准的保留 Noneraw item.get(price)item[price] self._parse_price(raw)return itemstaticmethoddef _parse_price(text):if not text:return Nonenums re.findall(r\d\.?\d*, text)if not nums:return Nonereturn float(nums[0])然后在settings.py里启用管道数字越大越靠后执行# 适用于 Python 3.8Scrapy 2.xsettings.py 片段ITEM_PIPELINES {myproject.pipelines.ProductPipeline: 300,}两点必须注意process_item必须返回 item 或抛出DropItem不返回就等于把数据弄丢了另外这个seen_ids集合只在内存里进程重启就没了生产环境应当把它换成外部存储数据库唯一索引或 Redis 集合否则定时任务之间起不到去重作用。四、反爬先理解成因再谈应对电商站的反爬措施不是「为难你」而是它必须做的自我保护。理解这一点应对方式就清楚了——目标是「不给对方添麻烦」而不是「骗过对方」。你观察到的现象可能的原因正确的应对前几次正常之后开始返回空数据请求频率过高被限流降低并发与频率加DOWNLOAD_DELAY一直跳到登录/验证页面该内容需要登录态停手需要授权就走官方渠道返回的 HTML 结构与其他用户不同依据 UA / 地区做了差异化用真实、可识别的爬虫 UA不要伪装同一 URL 多次结果不一致个性化推荐 / 活动变量明确你要抓的稳定字段别抓个性化内容出现签名参数校验接口有调用凭证不伪造签名改走官方 API这些现象的共同应对原则只有一条降低强度、缩小范围、表明身份、必要时停止。研究「如何让爬虫看起来像真人」「如何破解验证码」这类方向既不可持续也越过了本文的边界。常见坑点1. 不看 robots 和条款就动手❌ 直接对着电商站写选择器跑起来才发现大量请求被拒。 ✅ 先查 robots.txt 与使用条款不允许就走开放平台或授权数据源。2. 把列表页价格当最终价格❌ 只抓列表页的「区间价」拿去做比价结论完全失真。 ✅ 明确字段语义需要成交价时去详情页并标注抓取时间。3. 价格直接存字符串❌item[price] 12.50 起后续无法做计算与比较。 ✅ 用清洗函数转成数值解析不出时返回None而不是 0。4. 管道里忘了return item❌process_item里改了字段却没返回数据在管道中途消失。 ✅ 必须return item要丢弃才raise DropItem(...)。5. 去重只放在内存里❌ 用进程内的 set 去重定时任务一重启就重复入库。 ✅ 用数据库唯一索引或外部存储做去重让它跨进程生效。6. 用登录态抓取需要权限的内容❌ 把自己账号的 Cookie 塞进爬虫批量抓取登录后才可见的数据。 ✅ 需要授权的数据走官方接口不要用个人凭证做批量抓取。7. 为了「像真人」而伪装请求头❌ 反复更换 UA、伪造设备指纹试图躲过识别。 ✅ 用能表明身份和联系方式的 UA被限制就降速或停止。8. 用 Python 2 的旧代码❌ 直接抄教程里的print语句、xrange。 ✅ 改成print(...)、range。总结环节进阶做法数据来源判断先用开发者工具确认 HTML 还是异步请求列表 vs 详情明确字段语义别把区间价当成交价去重以商品 ID 为业务主键落到外部存储增量按抓取时间打标只处理有变化的记录清洗能确定的格式才转换否则留None反爬应对降速、缩范围、表明身份、必要时停止正规路径开放平台 / 数据合作 / 授权数据源「进阶」这两个字在电商抓取里指向的从来不是「更巧妙地绕过防护」而是更准确地定义数据、更严谨地清洗、更克制地请求。前两件事决定了你的数据有没有用第三件事决定了你这条路能走多远。至于那些必须靠绕过限制才能拿到的数据答案不是「再想想办法」而是「换一条合规的路」。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进