2.1 Spider:从 start_requests 出发


文档摘要

2.1 Spider:从 startrequests 出发 本节摘要:Spider 是请求的出生地:框架把 starturls 逐条转成 Request 交给引擎,响应回来按 callback 找到解析回调。本节讲清 Spider 类的生命周期钩子、起始请求的三种写法、多回调链的编排方式,让你能驾驭列表页加详情页的多层抓取。 第 1 章你站在车站外看全景,从这一节起你走进 Spider 车间。这里发生的是旅程的第一次交接:你把地址交给引擎,引擎替你跑完全程再把响应送回来。写好 Spider 的关键,是想明白"哪些时刻框架会来调用你"。

2.1 Spider:从 start_requests 出发

本节摘要:Spider 是请求的出生地:框架把 start_urls 逐条转成 Request 交给引擎,响应回来按 callback 找到解析回调。本节讲清 Spider 类的生命周期钩子、起始请求的三种写法、多回调链的编排方式,让你能驾驭列表页加详情页的多层抓取。

第 1 章你站在车站外看全景,从这一节起你走进 Spider 车间。这里发生的是旅程的第一次交接:你把地址交给引擎,引擎替你跑完全程再把响应送回来。写好 Spider 的关键,是想明白"哪些时刻框架会来调用你"。

生命周期:框架在哪些时刻找你

一个 Spider 类里,框架只会在固定的时刻调固定名字的方法:

import scrapy class BookSpider(scrapy.Spider): name = "books" # 运行标识,必须唯一 allowed_domains = ["books.example.com"] # 域名白名单,越界请求会被拦 start_urls = ["https://books.example.com/list"] # 起始地址,最简的出生方式 def start_requests(self): # 框架启动时逐条迭代 start_urls,包成 Request; # 需要自定义起始行为时重写本方法 for page in range(1, 4): yield scrapy.Request( f"https://books.example.com/list?page={page}", callback=self.parse_list, meta={"page": page}, ) def parse_list(self, response): # 到站第一站:列表页拆包 for href in response.css("a.book-link::attr(href)").getall(): yield response.follow(href, callback=self.parse_detail) def parse_detail(self, response): # 到站第二站:详情页拆包,接住列表页传来的话 yield { "title": response.css("h1::text").get(default="").strip(), "page": response.meta["page"], # 上一程塞进 meta 的话 }

三个钩子的分工:start_requests 管"第一批票怎么印",parse 或自定义回调管"到站怎么拆",而类属性 name 与 allowed_domains 是身份与边界。运行后从日志能看出回调链的推进:

2026-08-29 11:02:11 [scrapy.core.engine] DEBUG: Crawled (200) books.example.com/list?page=1 2026-08-29 11:02:12 [scrapy.core.engine] DEBUG: Crawled (200) books.example.com/book/101 2026-08-29 11:02:12 [scrapy.core.engine] DEBUG: Crawled (200) books.example.com/book/102 ... 'item_scraped_count': 60, 'downloader/request_method_count/GET': 13,

60 条 Item 来自 12 个详情页、3 个列表页的接力——回调链就是靠 yield 一个个请求"续"起来的。

起始请求的三种写法怎么选

写法一,直接给 start_urls:零样板,适合纯 GET、无需附加信息的场景,框架默认 callback 是 parse。写法二,重写 start_requests:需要批量生成带规律的起始地址、或首请求就要带特殊头与 meta 时用,注意它必须是生成器,逐个 yield。写法三,从外部拿起始清单:地址存在文件或数据库里,同样在 start_requests 里读取后 yield。

def start_requests(self): # 写法三示例:从本地文件读取起始清单,逐行出票 with open("seeds.txt", encoding="utf-8") as fh: for line in fh: url = line.strip() if not url: continue yield scrapy.Request(url, callback=self.parse, errback=self.log_failure)

⚠️ 常见坑:start_requests 里忘了 yield 而是 return 列表中的单个 Request,后续地址全部静默丢失。它必须是生成器,逐个交票。

多回调链的编排:让传话走 meta

列表页加详情页是爬虫最常见的形态。回调之间的"传话"靠 meta 字典:它是 Request 自带的行李舱,响应对象原样带回。除了自定义数据,Scrapy 还有几个保留键,比如 proxy、download_timeout——第 4 章会再碰到它们。传话的另一半是 errback:请求失败时响应对象不存在,errback 收到的是 Failure,可以在里面做记录或补偿。

def parse_list(self, response): for href in response.css("a.book-link::attr(href)").getall(): yield response.follow( href, callback=self.parse_detail, meta={"source_page": response.url, "category": response.css("h2.category::text").get()}, errback=self.on_error, ) def on_error(self, failure): # 到不了站的票:记下票据信息,交给统计与补偿逻辑 yield {"failed_url": failure.request.url, "error": str(failure.value)}

response.follow 接收相对地址也自动拼域名,还直接接受选择器对象,比手工 urljoin 少踩很多坑。

Spider 里的判断力:何时收手

新手常犯的错是"抓得越多越好"。工程上 Spider 应当有明确的收敛条件:翻页到尽头自然结束、列表去重后无新链接自然结束、或主动 close。无界爬虫对目标站点是负担,对自己是不可控的账单。给翻页循环加边界、给详情页数量设上限,是编写 Spider 时的基本功——第 3 章的去重机制会再从框架层面兜一遍底。

💡 关键直觉:Spider 代码只回答三个问题——第一批票怎么印、到站怎么拆、新票怎么续。任何超出这三个问题范围而写进 Spider 的逻辑(比如登录凭证管理、数据清洗),几乎都有更合适的岗位在等着它。

本节要点回顾

  • 框架按钩子调用你:start_requests 管出生,callback 管到站,errback 管失联;
  • 起始请求三种写法:清单直给、重写生成、外部读取,按需选择且必须逐个 yield;
  • meta 是回调间行李舱:跨页传话全靠它,注意与框架保留键的名字冲突;
  • follow 优于手拼:相对地址、选择器对象都能接,省去拼接错误。

车票印出来了,可票面上到底印了什么?下一节把 Request 与 Response 这对双程车票摊开细看。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U