Scrapling 通用 Spider 模板实战指南:从 CrawlSpider 规则、SitemapSpider 到 XML/CSV Feed 解析

发布时间:2026/9/7 3:32:16
Scrapling 通用 Spider 模板实战指南:从 CrawlSpider 规则、SitemapSpider 到 XML/CSV Feed 解析 Scrapling 通用 Spider 模板实战指南从 CrawlSpider 规则、SitemapSpider 到 XML/CSV Feed 解析【免费下载链接】Scrapling️ An adaptive Web Scraping framework that handles everything from a single request to a full-scale crawl!项目地址: https://gitcode.com/GitHub_Trending/sc/Scrapling本篇指南基于 Scrapling 官方文档与仓库源码系统讲解其通用爬虫模板体系CrawlSpider的声明式链接规则、SitemapSpider的 sitemap/robots.txt 种子发现、XMLFeedSpider与CSVFeedSpider的流式数据解析以及底层原语LinkExtractor的完整参数参考。读完后你既能直接复制可运行的模板代码完成按正则跟链和按 sitemap 全量抓取两类常见爬取任务也能理解每个模板在源码层的分派、过滤与容错逻辑把模板组合进自己的爬虫中。模板体系概览两种模式与一个原语大多数爬虫任务都会落入两种模式之一跟随匹配某个正则的链接或抓取站点 sitemap 中列出的所有 URL。Scrapling 为这两种模式各提供了一个模板类让你不必每次手写相同的parse()样板代码。所有这些模板都构建在LinkExtractor之上——它负责从Response中提取 URL或仅通过matches()对单个 URL 做过滤。SitemapSpider还会在内部解析 sitemap.xml / sitemap_index.xml 的正文无论是否 gzip 压缩。你完全可以在任何普通Spider.parse()中直接使用LinkExtractor模板只是替你省去了接线的部分。从 scrapling/spiders/init.py 的导出列表可以看到这些模板与ShopifySpider等专用模板同属公开 APIfrom scrapling.spiders import CrawlSpider, SitemapSpider, CrawlRule, XMLFeedSpider, CSVFeedSpider, LinkExtractorCrawlSpider基于声明式规则自动跟链CrawlSpider根据声明式的规则rules自动提取并跟随链接。你需要重写rules()方法返回一组CrawlRulefrom scrapling.spiders import CrawlSpider, CrawlRule, LinkExtractor class BlogCrawler(CrawlSpider): name blog start_urls [https://example.com] def rules(self): return [ CrawlRule(LinkExtractor(allowr/posts/), callbackself.parse_post), CrawlRule(LinkExtractor(allowr/page/\d/)), # 跟随分页无需 callback ] async def parse_post(self, response): yield { title: response.css(h1::text).get(), url: response.url, } result BlogCrawler().start()CrawlRule 的四个字段与默认 parse() 行为CrawlRule将一个LinkExtractor与以下可选项配对见 crawler.py 中的 dataclass 定义字段默认值说明link_extractor必填从每个Response中产出待抓取 URL 的LinkExtractorcallbackNone每个命中 URL 调用的 spider 绑定方法为空时回退到 spider 默认的parse()priorityNone覆盖分派出去的Request的优先级process_requestNone绑定方法在Request被 yield 前对其进行修改签名为(request, response) - request可用来加 headers、改优先级或过滤请求从源码看默认parse()的执行链路非常直白crawler.py遍历rules()对每条规则调用link_extractor.extract(response)对每个命中的 URL 执行response.follow(url, callbackrule.callback)再按需覆盖priority并交给process_request。测试用例 tests/spiders/test_templates.py 验证了关键行为规则为空时parse()不产出任何请求无 callback 的规则分派出的Request.callback为None引擎随后回退到 spider 的parse()——这正是无 callback 规则对分页场景的价值所在抽取下一页链接保持爬取继续而不需要单独的处理器response.follow()设置的referer头在规则路径下同样保留。规则与自定义逻辑组合重写parse()并在其中调用super().parse(response)即可同时获得规则行为与自己的产出class MySpider(CrawlSpider): def rules(self): return [CrawlRule(LinkExtractor(allowr/posts/), callbackself.parse_post)] async def parse(self, response): yield {page_url: response.url} async for req in super().parse(response): yield reqtests/spiders/test_templates.py 中test_user_can_compose_super_parse专门验证了这一组合模式自定义 dict 先产出随后是规则产生的Request序列。用 process_request 修改 Request在process_request中你可以修改返回的Request测试还验证了它可以返回一个全新的替换请求def add_priority(self, request, response): request.priority 10 return request def rules(self): return [CrawlRule( LinkExtractor(allowr/posts/), callbackself.parse_post, process_requestself.add_priority, )]一个实现细节值得注意CrawlSpider规则产生的Request携带的是 spider 的绑定方法 callback而 Python 的 bound method 本身不可直接 pickle。从源码与测试TestCrawlSpiderPickle可以看到Request.__getstate__会把绑定方法转换为方法名字符串_callback_name在检查点恢复时再由_restore_callback还原——这使得基于规则的爬虫可以安全参与 Scrapling 的断点续爬机制。SitemapSpider从 sitemap 播种的爬取SitemapSpider从 sitemap.xml 中的 URL 播种爬取。它使用与CrawlSpider相同的rules()API心智模型完全一致from scrapling.spiders import SitemapSpider, CrawlRule, LinkExtractor class MySitemap(SitemapSpider): name sm sitemap_urls [https://example.com/sitemap.xml] def rules(self): return [ CrawlRule(LinkExtractor(allowr/posts/), callbackself.parse_post), CrawlRule(LinkExtractor(allowr/products/), callbackself.parse_product), ] async def parse_post(self, response): yield {title: response.css(h1::text).get()} async def parse_product(self, response): yield {sku: response.css(.sku::text).get()} result MySitemap().start()注意与CrawlSpider的一个差异SitemapSpider的种子入口是类变量sitemap_urlsstart_requests()未配置它时会抛出RuntimeError而不是start_urls。URL 分派机制第一个匹配的规则获胜对 sitemap 中的每个 URLSitemapSpider会按顺序检查每条规则的LinkExtractor.matches(url)。第一条匹配的 rule 获胜随后用该规则的 callback 产出一个Request如果没有任何规则匹配且rules()非空该 URL 会被丢弃与 Scrapy 的行为一致。若rules()返回空列表则所有 URL 都会路由到 spider 的parse()方法——其默认实现抛出NotImplementedError需要你自行重写。源码中这一逻辑集中在_dispatch与_parse_sitemapsitemap.py# scrapling/spiders/templates/sitemap.py节选 for url in result.urls: req self._dispatch(response, url, rules) if req is not None: yield reqtests/spiders/test_sitemap.py 中的test_urlset_dispatched_through_rules与test_no_rules_means_all_urls_fall_through分别验证了这两条路径。Sitemap 索引sitemap of sitemaps当SitemapSpider遇到sitemapindexsitemap 的 sitemap时会自动递归下钻到每个子 sitemap。要过滤下钻哪些子 sitemap把sitemap_follow设置为一个LinkExtractorclass MySitemap(SitemapSpider): name sm sitemap_urls [https://example.com/sitemap.xml] sitemap_follow LinkExtractor(allowr/posts-sitemap-\d\.xml) # 只跟随文章 sitemaprobots.txt 支持把robots.txt的 URL 直接放进sitemap_urlsSitemapSpider会识别它提取其中每一条Sitemap:指令并逐一跟随class MySitemap(SitemapSpider): name sm sitemap_urls [https://example.com/robots.txt] # Sitemap: 指令将被自动发现从源码看sitemap.py 的_robots_bodyrobots.txt 正文是通过protego库解析的Protego.parse(text)之后取parser.sitemaps。如果 robots.txt 中没有任何 Sitemap 指令模板会记录一条 warning 而不是静默失败robots.txt 的解析失败也只告警、不中断爬取。多语言hreflangURL设置sitemap_alternate_links True后xhtml:link relalternate hreflang...中的 URL 也会与常规 URL 一起被送入 rules 分派。对应实现见 sitemap.py 的_extract_urls它在url子元素中除了读取loc还会在开关打开时收集link元素的href属性。健壮性gzip 与 64 MiB 解压上限sitemap 正文解析_sm_bodysitemap.py复用了 feed 模板共用的_decompress辅助函数templates/_utils.py当content-type含 gzip 或响应体前两个字节是 gzip 魔数\x1f\x8b时自动解压且输出上限为64 MiB以防止 gzip 炸弹。解压失败或 XML 语法错误都只记录 warning 并返回空结果——测试test_gzipped_sitemap_handled_via_magic_bytes验证了仅凭魔数即可触发解压。XMLFeedSpider逐节点迭代 XML FeedXMLFeedSpider迭代 XML 数据源RSS、Atom、商品 feed 等的节点。把itertag设为你要处理的节点名默认item并重写parse_node()——它会被每个匹配节点调用一次from scrapling.spiders import XMLFeedSpider class RSSSpider(XMLFeedSpider): name rss start_urls [https://example.com/feed.xml] itertag item async def parse_node(self, response, node): yield { title: node.findtext(title), link: node.findtext(link), date: node.findtext(pubDate), } result RSSSpider().start()与其他 callback 一样parse_node()也可以 yieldRequest对象例如response.follow(node.findtext(link), callbackself.parse_post)把爬取推进到 feed 指向的页面。节点如何匹配与解析命名空间剥离传给parse_node()的每个 node 都是一个命名空间被全部剥离的 lxml 元素因此node.findtext(title)、node.find(thumbnail).get(url)以及大小写敏感的node.xpath(...)都可以在任何 feed 上免命名空间映射地工作。源码中这是_strip_namespacesfeed.py用deepcopy复制节点后逐个重写 tag 与属性键实现的——原响应树不受影响。itertag的匹配语义分两种纯名称如entry按 localname 匹配忽略命名空间——这正是 Atom 及大多数带命名空间 feed 的用法带前缀的名称如media:thumbnail只匹配namespaces中该前缀所映射的命名空间前缀未定义时抛出ValueError。class ThumbnailSpider(XMLFeedSpider): name thumbs start_urls [https://example.com/feed.xml] itertag media:thumbnail namespaces ((media, http://search.yahoo.com/mrss/),) async def parse_node(self, response, node): yield {thumbnail: node.get(url)}gzip 压缩的 feed.xml.gz或以 gzip content-type 提供会被自动解压保护机制与 SitemapSpider 相同格式错误的 XML 会记录 warning 而不是让爬取崩溃见 feed.py 中对XMLSyntaxError的处理以及 tests/spiders/test_feed.py 中test_malformed_xml_logs_warning_and_yields_nothing。CSVFeedSpider逐行迭代 CSV FeedCSVFeedSpider迭代 CSV 数据源的每一行。重写parse_row()每一行以列名为键的字典形式传入from scrapling.spiders import CSVFeedSpider class PriceSpider(CSVFeedSpider): name prices start_urls [https://example.com/products.csv] async def parse_row(self, response, row): yield {product: row[title], price: float(row[price])} result PriceSpider().start()相关类变量默认值见 feed.py类变量默认值说明headersNone列名列表缺省时使用 feed 的第一行作为表头delimiter,字段分隔符quotechar包裹含特殊字符字段的引号字符没有表头行的 feed 手动指定headers非标准逗号格式用delimiter/quotechar适配class PriceSpider(CSVFeedSpider): name prices start_urls [https://example.com/products.csv] headers [title, price, url] delimiter ;实现上它用标准库csv.DictReader包装解码后的文本feed.py编码取response.encoding缺省 utf-8且errorsreplace因此非 UTF-8 字节不会让爬取崩溃测试test_non_utf8_bytes_do_not_crash覆盖了这一点。gzip 压缩的 CSV 同样自动解压。parse_row()与parse_node()一样可以 yieldRequest把行内 URL 继续扩展成爬取任务。在任意 Spider 中直接使用 LinkExtractor不一定要用模板。LinkExtractor在任何普通Spider中都能工作from scrapling.spiders import Spider, LinkExtractor class CustomSpider(Spider): name custom start_urls [https://example.com] def __init__(self): super().__init__() self._links LinkExtractor(allowr/posts/, deny_domainsads.example.com) async def parse(self, response): for url in self._links.extract(response): yield response.follow(url, callbackself.parse_post) async def parse_post(self, response): yield {title: response.css(h1::text).get()}LinkExtractor 完整参数参考参数默认值说明allow()要保留的 URL 模式。空表示全部匹配。可以是字符串、已编译的re.Pattern或二者的可迭代集合。deny()要丢弃的 URL 模式。始终优先于allow。allow_domains()要保留的主机名。子域名自动匹配example.com也匹配api.example.com。deny_domains()要丢弃的主机名。restrict_css()将 DOM 提取范围限定到某区域的 CSS 选择器。restrict_xpath()将 DOM 提取范围限定到某区域的 XPath 选择器。tags(a, area)在其中查找链接的元素标签。attrs(href,)从这些标签上读取 URL 的属性。canonicalizeTrue排序 query 参数并规范化路径。stripTrue去除提取出的 URL 中的空白。keep_fragmentFalse规范化时保留#fragment。deny_extensionsIGNORED_EXTENSIONS要丢弃的文件扩展名pdf、zip、图片、视频等。processNone可选 callable在过滤前应用于每个提取出的 URL返回假值即丢弃该 URL。LinkExtractor.extract(response)返回一个list[str]——绝对化、过滤并去重后的 URL 列表LinkExtractor.matches(url)返回bool——仅针对单个 URL 的过滤allow/deny/domain/extension供SitemapSpider在没有Response的情况下分派 URL。源码视角的过滤与提取细节阅读 links.py 可以看到几处比文档表格更具体的实现事实协议白名单_url_passes第一步就要求 URL scheme 属于{http, https, file}links.py因此mailto:、javascript:等链接天然被过滤扩展名判定支持复合后缀_url_extensions会把archive.tar.gz同时视为tar.gz和gz两种后缀参与匹配避免tar.gz这类多段扩展名漏网默认忽略清单相当激进IGNORED_EXTENSIONSlinks.py除了常见的 pdf、zip、图片、音视频还包含js、css、py、rss、exe、iso等爬取时若目标是静态资源页面可显式传入自定义集合提取与去重extract()先用restrict_css/restrict_xpath圈定作用域都为空时作用于整个响应再用 XPath 组合选择器.//tag/attr取出原始 URL随后依序做空白清洗、response.urljoin绝对化、process回调、canonicalize_url规范化与编码安全转换最后用dict.fromkeys去重——这比set的好处是保留链接在页面中的出现顺序links.pymatches 与 extract 的过滤一致性matches()复用同一套_url_passes判定只是额外在调用前对 URL 做规范化因此 SitemapSpider 的分派结果与页面内链接提取在 allow/deny 语义上保持一致。参考与延伸阅读官方文档原文agent-skill/Scrapling-Skill/references/spiders/generic-templates.md模板源码CrawlSpider、SitemapSpider、XMLFeedSpider / CSVFeedSpider、公共解压工具链接原语LinkExtractor对应测试tests/spiders/test_templates.py、tests/spiders/test_sitemap.py、tests/spiders/test_feed.py【免费下载链接】Scrapling️ An adaptive Web Scraping framework that handles everything from a single request to a full-scale crawl!项目地址: https://gitcode.com/GitHub_Trending/sc/Scrapling创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考