首页
/ Scrapling 平台爬虫模板详解:ShopifySpider 如何免写 HTML 解析抓取整店商品

Scrapling 平台爬虫模板详解:ShopifySpider 如何免写 HTML 解析抓取整店商品

2026-09-06 18:06:54作者:农烁颖Land

本文围绕 Scrapling 的"平台型爬虫模板"(Platform Spider Templates)展开:它面向 Shopify 这类"统一数据结构、跨大量独立站点"的平台,只需用 ShopifySpider 指定一个店铺域名,即可通过平台的 JSON API 抓取全部商品与变体数据。读完本文,你将掌握平台模板与通用模板的本质区别、ShopifySpider 的分页抓取流程、全部 item 字段的来源与取值规则、target_website 的域名解析逻辑,以及如何通过覆写 _process_product() 定制输出结构,并能结合仓库源码验证每一个行为细节。

平台模板 vs 通用模板:按"平台"而非按"模式"复用

Scrapling 内置的爬虫模板分为两类,理解两者的定位是理解本文的前提:

  • 通用模板CrawlSpider、SitemapSpider、XMLFeedSpider、CSVFeedSpider)覆盖的是爬取的模式:跟踪匹配某模式的链接、遍历站点 sitemap、迭代 XML/CSV 数据源。它们需要你针对具体站点的 HTML 结构自己写解析逻辑。
  • 平台模板覆盖的是平台:网站构建器(site builder)会在它托管的众多独立网站上暴露出相同的机器可读结构,所以爬虫天生知道"数据在哪里"——你只需把爬虫指向某个具体域名。

从源码结构看,这一划分直接体现在模板包的组织方式上:scrapling/spiders/templates/init.py 将通用模板(CrawlSpiderSitemapSpiderXMLFeedSpiderCSVFeedSpider)与平台模板(ShopifySpider)统一导出,而 scrapling/spiders/init.py 再将其提升到 scrapling.spiders 顶层,因此可以直接 from scrapling.spiders import ShopifySpider

当前仓库中唯一的平台模板就是 ShopifySpider,它是本文的绝对主角。

ShopifySpider:最小可用示例

ShopifySpider 通过 Shopify 店铺的 JSON API 提取任意 Shopify 驱动商店的全部商品,全程不接触网站的 HTML。最小用法如下:

from scrapling.spiders import ShopifySpider

class MyStore(ShopifySpider):
    target_website = "example.com"

result = MyStore().start()
print(result.items[0])

target_website 设置为商店域名即可,爬虫会自行完成 collections 与 products 两层的分页遍历,result.items 中是逐变体展开的商品条目。Spider 基类的其余能力全部继承可用:并发配置、下载延迟、robots.txt 遵循、检查点(checkpoint)以及生命周期钩子——这套异步调度、并发控制与断点续爬的完整机制,可参见 Spider 架构文档

目标域名解析:三级回退与自动归一化

target_website 并非唯一指定目标的方式。从 shopify.py__init__ 可以看到,域名的确定遵循明确的三级回退顺序:

  1. 优先使用 target_website
  2. 为空时,回退到 start_urls 的第一个条目;
  3. 仍为空时,回退到 allowed_domains 的第一个条目;
  4. 三者全部为空则抛出 ValueError,错误信息明确要求设置其中任意一项。

无论来源是裸域名(example.com)还是完整 URL(https://example.com/collections/all),最终都会被归一化为纯域名(netloc):没有 :// 的字符串会先补上 https:// 前缀再交给 urlparsenetloc

这套行为在 tests/spiders/test_shopify.pyTestDomainResolution 中有逐项验证:从 target_website 取域名、从完整 URL 归一化出域名、从 start_urls 回退(含 www. 子域保留)、从 allowed_domains 回退,以及三源皆空时抛出 ValueError,五个用例与源码实现一一对应。

抓取流程:collections.json → products.json → 变体级条目

ShopifySpider 的抓取逻辑由三个类属性 URL 模板驱动(shopify.py#L33-L37):

name = "shopify"
target_website = ""
collections_url = "https://{website}/collections.json?page={page}&limit=250"
products_url = "https://{website}/collections/{handle}/products.json?page={page}&limit=250"
product_url = "https://{website}/collections/{handle}/products/{product_handle}"

其中 limit=250 是 Shopify 平台的单页上限。完整流程分三步:

第一步:遍历 collections.json 分页

start_requests() 只产出第一个请求:https://<store>/collections.json?page=1&limit=250,并携带 meta={"page": 1}parse() 处理该响应时做两件事(shopify.py#L54-L70):

  • 对当前页中每一个 products_count 非零的 collection,派发一个指向 /collections/<handle>/products.json?page=1 的请求,meta 中记录 handlepage
  • 只要当前页的 collections 列表非空,就继续派发下一页(page + 1)的 collections.json 请求。空页即终止,不再产生后续请求。

products_count 在这里只被当作"非零就抓这个 collection"的信号,从不作为期望总数使用——原因见下文"注意事项与限制"。

第二步:遍历每个 collection 的 products.json 分页

parse_collection()shopify.py#L96-L112)对每个 collection 的响应执行:

  • products 列表中的每个产品,调用 _process_product() 逐变体产出条目;
  • 只要当前页 products 非空,就派发该 collection 的下一页产品请求;
  • 空页表示该 collection 已抓完,记录一条 debug 日志("Extracted all products from collection ")并停止。

测试用例 test_parse_dispatches_collections_and_next_pagetest_parse_stops_on_empty_pagetest_parse_collection_stops_on_empty_pagetest_shopify.py)精确覆盖了这套分页边界:2 个 collection(含一个空 collection 被跳过)时派发 2 个请求、空 collections 页返回 0 个请求、空 products 页返回 0 个请求。

第三步:按变体产出条目并跨 collection 去重

去重状态存放在实例属性 self.collected_ids(一个 set,在 __init__ 中初始化)。每个变体以 variant["id"] 作为键:已见过的变体 id 直接跳过。由于同一个产品变体可能出现在多个 collection 中,这一机制保证每个变体只产出一次条目。测试用例 test_variants_deduplicated_across_collections 验证了同一产品在 lipsticksbestsellers 两个 collection 中只产出一次,test_dedup_state_is_per_instance 则确认去重状态是实例级的——不同 Spider 实例之间互不干扰。

条目字段详解:每个字段的来源与取值规则

ShopifySpider 默认每个变体产出一个 dict 条目,字段结构与底层数据来源如下(逐项对应 _process_product() 的实现):

字段 来源与规则
name 产品标题;当变体标题不是 Default Title 时,追加 " - <变体标题>"
price 变体价格,字符串,保持 Shopify 返回的原样(如 "9.00"
category 所属 collection 的 handle,做 title-case 化(summer-sale 变成 Summer Sale
brand 产品的 vendor 字段
identifier 变体 id(variant["id"]
sku 变体 SKU;店铺未设置时为 ""None 也被归一为空串)
stock 变体有货为 None,缺货为 0
image_url 产品第一张图的 src;无图为 ""
url 产品在所属 collection 内的商品页 URL(product_url 模板拼出)
description 产品 body_htmlremove_tags(来自 w3lib.html)剥离 HTML 标签后的纯文本
old_price compare_at_price,但仅当它是真实预售价(非零)时才保留,否则为 ""
barcode 变体 barcode,无则为 ""(大多数店铺不在这两个端点暴露该字段)

测试数据(test_shopify.py#L19-L59PRODUCTS_PAGE)恰好覆盖了所有边界分支,TestItemProcessing 的断言可以直接当作"字段规则 → 期望输出"的验收清单,例如:

  • 变体标题为 Red(非默认)→ name"Matte Lipstick - Red",标题为 Default Titlename 保持产品原标题;
  • sku: None → 归一为 ""available: Falsestock0
  • compare_at_price: "0.00"old_price"""12.00" → 原样保留;
  • body_html: Nonedescription""images: []image_url""

自定义输出结构:覆写 _process_product()

上述字段不是强制契约。如果你想改变条目结构、或从产品数据中提取其他字段,只需在子类中覆写 _process_product()——它的签名是 _process_product(self, product: Dict, collection_handle: str) -> Generator[Dict, None, None],接收单个产品 dict 与所属 collection 的 handle,以生成器形式逐条 yield 结果。覆写后,parse_collection() 的分页、去重之外的调用链保持不变(注意:跨 collection 的变体去重发生在 _process_product() 内部,如果你完全重写该方法,需要自行决定是否保留 self.collected_ids 去重逻辑)。

注意事项与限制

使用 ShopifySpider 前必须了解以下边界(来自 platform-templates.md 与源码行为):

  • JSON 端点的可见性限制/collections.json/products.json 只暴露已发布到 online-store 渠道的商品,因此一个 collection 报告的 products_count 可能大于实际能抓到的数量。源码中(shopify.py#L58if collection["products_count"]: 的判断印证了这一点——该计数只被用作"是否抓取"的开关,而不是断点校验或完整性预期。
  • 受保护店铺大概率不可用:位于额外防护或密码保护(Shopify 店铺密码页)后面的商店,该模板很可能无法工作;即使勉强可用,也需要你进行大量覆写。
  • Spider 基类能力照常生效robots_txt_obeyconcurrent_requests、下载延迟、crawldir 检查点(暂停/恢复)与生命周期钩子等 Spider 系统 提供的一切机制对平台模板同样适用。

什么样的平台有资格成为平台模板

仓库文档给出了明确的准入标准:平台模板只接受"在大量独立网站上暴露统一、机器可读结构"的平台,Shopify 的 collections/products JSON 端点即典型例子。反过来,针对某一个特定网站的爬虫不属于库的收录范围——无论该网站有多受欢迎。从源码结构看,这一约束也反映在实现方式上:ShopifySpider 的全部逻辑都建立在平台级 URL 模板(collections_url/products_url/product_url 类属性)之上,没有任何站点专属的 HTML 选择器或硬编码路径——这正是"平台模板"与"单站爬虫"的结构性差异。

小结

Scrapling 的平台模板把"数据在哪里"的知识封装进了库:ShopifySpider 用约 110 行源码(scrapling/spiders/templates/shopify.py)实现了三级域名回退、双层分页遍历、变体级去重和 12 个规范化字段,并留出 _process_product() 作为唯一的定制点。对任意一家 Shopify 店铺,你只需要定义一个两行的子类并调用 start();而 docs/spiders/generic-templates.md 中的通用模板则继续负责链接跟踪、sitemap 与数据源迭代这些"模式级"的复用。两者叠加,覆盖了从单页抓取到整站乃至整平台规模抓取的大部分场景。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391