bigywater.tech
Scrapy · 32

SitemapSpider:sitemap 索引的递归抓取

SitemapSpider 怎么从 robots.txt 找到 sitemap、怎么处理索引和压缩、怎么用正则分流到不同回调?哪些响应会被认定为 sitemap?这篇在一个带索引、gz、备用链接的假站点上逐项验证。

6 个
从 robots.txt 出发经索引递归后抓到的页面数(3 个子 sitemap)
3 → 6
sitemap_alternate_links 从 False 变 True 时的页面数
text/plain
内容合法但会被忽略的 sitemap 的 Content-Type(URL 也不以 .xml 结尾)

发现 sitemap

实测 · 本机执行输出
  服务器收到(按顺序):['/robots.txt', '/sitemap_index.xml', '/extra.html', '/sm/products.xml', '/sm/blog.xml.gz', '/sm/news.xml', '/p/1', '/p/2', '/p/3', '/blog/a', '/blog/b', '/news/1']
  parse 回调的页面:['/blog/a', '/blog/b', '/news/1', '/p/1', '/p/2', '/p/3']
  警告:[scrapy.spiders.sitemap] WARNING: Ignoring invalid sitemap: <200 http://127.0.0.1:9960/extra.html>

sitemap_urls 可以直接是 sitemap,也可以是 robots.txt:后者读取其中的 Sitemap: 行。sitemapindex 里的子 sitemap 会递归请求,.xml.gz 和即时压缩的响应都能解开。

回调分流

实测 · 本机执行输出
  规则 [(/p/, parse_product), (/blog/, parse_blog)]:回调 [('blog', 'a'), ('blog', 'b'), ('product', '1'), ('product', '2'), ('product', '3')];服务器收到页面请求 ['/blog/a', '/blog/b', '/p/1', '/p/2', '/p/3'](/news/1 没有规则匹配,没有请求)

sitemap_rules 按顺序取第一个匹配的正则,没匹配的 URL 不请求。

sitemap_follow

实测 · 本机执行输出
  sitemap_follow=["products","news"]:服务器收到 sitemap 请求 ['/sitemap_index.xml', '/sm/products.xml', '/sm/news.xml'];parse 页面 ['/news/1', '/p/1', '/p/2', '/p/3'](blog.xml.gz 没有被请求)

sitemap_follow 决定索引里哪些子 sitemap 要跟,默认全部。

sitemap_filter

实测 · 本机执行输出
  entry p/1        lastmod=2025-01-01 键=['alternate', 'lastmod', 'loc']
  entry p/2        lastmod=2025-02-01 键=['alternate', 'lastmod', 'loc']
  entry p/3        lastmod=2025-03-01 键=['alternate', 'lastmod', 'loc']
  entry blog/a     lastmod=2024-01-01 键=['lastmod', 'loc']
  entry blog/b     lastmod=2025-06-01 键=['lastmod', 'loc']
  entry news/1     lastmod=2025-09-01 键=['lastmod', 'loc']
  只保留 lastmod ≥ 2025-02 的:parse 页面 ['/blog/b', '/news/1', '/p/2', '/p/3']

重写 sitemap_filter 可以按 lastmod 等字段筛选条目。

备用链接

实测 · 本机执行输出
  False(默认):parse 页面 ['/p/1', '/p/2', '/p/3']
  True:parse 页面 ['/p/1', '/p/1?lang=en', '/p/2', '/p/2?lang=en', '/p/3', '/p/3?lang=en']

sitemap_alternate_links = True 会把 xhtml:link 的备用地址也当作页面。

识别规则

实测 · 本机执行输出
  /sm/products.xml    → parse 页面 ['/p/1', '/p/2', '/p/3']
  /sitemap.php        → parse 页面 ['/y/1']
  /sm/plain-ct        → parse 页面 []  [警告:Ignoring invalid sitemap]
  /sm/news.xml        → parse 页面 ['/news/1']
  /sm/blog.xml.gz     → parse 页面 ['/blog/a', '/blog/b']

响应必须是 XmlResponse、gzip 或 URL 以 .xml/.xml.gz 结尾,否则被当作无效 sitemap 忽略。服务器发 text/plain 的 sitemap 要特别留意。

分步演示

sitemap
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.8 / Scrapy 2.19.0 / Twisted 26.4.0 / lxml 6.1.3 / parsel 1.12.1(临时虚拟环境);所有请求只发给本机自己起的测试服务器(127.0.0.1,端口 9960 以上,不访问任何真实网站);服务器可以按实验需要制造延迟、重定向、压缩、断连等行为,并记录收到的原始请求;每个实验在临时目录里新建 Scrapy 项目或单文件 spider,命令输出来自真实的 scrapy 进程;计时用 time.perf_counter,数字每次运行会有小幅抖动。站点:robots.txt 里一个相对路径和一个绝对路径的 Sitemap: 行;sitemapindex 指向 products.xml、blog.xml.gz(真 gzip 文件)、news.xml(Content-Encoding: gzip);并发 1,先进先出队列
    • 没有验证:超大 sitemap(超过 50,000 条或 50 MB)的内存占用;DOWNLOAD_MAXSIZE 对 sitemap 解压的限制(源码里有,未实测);sitemap 里 、 扩展标签;lastmod 的各种日期格式;同一个 URL 出现在多个 sitemap 里的去重(靠调度器的去重,未单独测);sitemap 与 CrawlSpider 的混用
    京ICP备2026067502号-1京公网安备11010502063487号