前言
先划清一个前提:本文说的Spider类不是 Python 标准库的一部分,它是第三方爬虫框架 Scrapy 里最基础的类。本机没有安装 Scrapy,也没有 Python 解释器,所以下面的代码是结构性示例,用来讲清类的组织方式,具体属性名、参数与调用细节一律以 Scrapy 官方文档为准。
讲 Scrapy 的Spider,最容易产生的误解是把它当成"一个能自动爬遍全网的对象"。它不是。Spider只是一个定义了"从哪些地址开始、拿到响应后怎么解析"的骨架;真正发起请求、调度、去重、限速的是框架的引擎和各个 Downloader Middleware。你写的子类只负责回答两个问题:请求哪些 URL,以及从响应里提取什么。
另外必须把合规写在前头:爬虫只能对着允许爬取的内容用。本文只讲合规侧的做法——遵守robots.txt、限速、使用真实可辨识的 User-Agent、不绕过登录墙与付费墙。
一、Spider 类的骨架
在 Scrapy 里,自定义爬虫就是继承Spider并实现约定的成员。最小骨架包含三样东西:name(必填)、入口 URL、以及解析方法。
# 示例基于 Scrapy 2.x(第三方库,未在本机安装);API 以 Scrapy 官方文档为准
import scrapy
class ExampleSpider(scrapy.Spider):
name = "example" # 必填,且全局唯一
allowed_domains = ["example.com"] # 可选,限定抓取范围
start_urls = ["https://example.com/"]
def parse(self, response):
# response 是 TextResponse,封装了这次请求的返回内容
title = response.css("title::text").get()
yield {"title": title}name是框架用来在scrapy crawl example时找到这个爬虫的键,必须唯一,且不能与项目里其它爬虫重名。
二、几个核心类属性
| 属性 | 是否必需 | 作用 |
|---|
name | 必需 | 爬虫的唯一标识,命令行用它启动 |
start_urls | 常用 | 起始 URL 列表,默认入口 |
allowed_domains | 可选 | 限定域名,越界链接会被过滤 |
custom_settings | 可选 | 为这个爬虫覆盖项目级配置 |
start_requests() | 可覆写 | 自定义起始请求的生成方式 |
parse() | 通常必需 | 默认的响应解析回调 |
start_urls的默认行为是:框架按它逐个生成请求,把响应交给parse()。allowed_domains是范围控制,不是安全边界——它只保证不会顺着链接跑到别的站点,不等于对方允许你爬。
三、start_requests() 与 parse()
默认的起始请求逻辑可以用start_requests()覆写。在较新的 Scrapy 里,这个方法仍是同步生成器;如果你需要更细的控制(比如带请求头、控制顺序),就在这里写。
# 示例基于 Scrapy 2.x(第三方库,未在本机安装);API 以 Scrapy 官方文档为准
import scrapy
class BlogSpider(scrapy.Spider):
name = "blog"
allowed_domains = ["example.com"]
def start_requests(self):
# 覆写入口:自己构造请求,而不是用 start_urls
for page in range(1, 4):
url = f"https://example.com/list?p={page}"
yield scrapy.Request(url, callback=self.parse_list)
def parse_list(self, response):
# 从列表页提取详情页链接,交回框架继续跟进
for href in response.css("a.detail::attr(href)").getall():
yield response.follow(href, callback=self.parse_detail)
def parse_detail(self, response):
yield {
"title": response.css("h1::text").get(),
"url": response.url,
}要点:回调函数靠yield把结果交还框架——yield字典表示一条数据项,yield请求表示"再抓一页"。不要在回调里自己开线程或直接写文件,那会绕过框架的调度与限速。
四、新版入口:async def start()
Scrapy 2.13 引入了async def start(self)作为起始请求的新写法:它是一个异步生成器,逐个产出初始请求。官方说明它的默认实现与读取start_urls的行为等价。
# 示例基于 Scrapy 2.13+(第三方库,未在本机安装);API 以 Scrapy 官方文档为准
from scrapy import Request, Spider
class ModernSpider(Spider):
name = "modern"
async def start(self):
yield Request("https://example.com/")写新项目时请先确认你用的 Scrapy 版本支持哪种入口写法——以 Scrapy 官方文档为准。旧版只有start_requests(),新版两者都提供但推荐新写法。代码里不要同时重写两个入口,否则行为取决于框架版本,容易踩坑。
五、合规:写爬虫之前先想清楚
技术骨架之外,真正决定爬虫能不能长期跑下去的是这几条:
- 先读
robots.txt:目标站的robots.txt会声明哪些路径不允许抓。Scrapy 有对应的 robots 中间件,默认遵循该协议;若有人为了"快"关掉它,那是违规的开始。 - 限速:在配置里设置下载延迟并发上限,别把对方站点打挂。宁可慢一点、跑久一点。
- 真实可辨识的 User-Agent:用能表明身份与用途的 UA,方便对方在出问题时联系你;伪造浏览器 UA 去骗过校验不是合规做法。
- 不绕登录墙 / 付费墙:需要登录才能看的内容,不在"公开可抓"范围内,不能拿爬虫去绕。
- 遵守站点条款与当地法律:爬取的是公开数据,也要看对方是否明示禁止。
# 示例基于 Scrapy 2.x(第三方库,未在本机安装);配置项以 Scrapy 官方文档为准
# settings 片段:放慢节奏、标明身份(写在项目配置里,不是写在 Spider 里)
DOWNLOAD_DELAY = 1 # 每次请求之间的延迟(秒)
CONCURRENT_REQUESTS = 4 # 并发上限,别开太大
USER_AGENT = "MyResearchBot/1.0 (+contact: admin@example.com)"常见坑点
坑 1:name重复或缺失。
❌ 两个爬虫都叫"spider"—— 命令行启动时报"已存在同名爬虫"。
✅ 每个爬虫给唯一name,并保持简短可读。
坑 2:在parse里直接return数据而不yield。
❌def parse(self, response): return response.text—— 框架拿不到结构化数据项。
✅ 用yield {"field": value}逐个产出;多个结果就多次yield。
坑 3:自己在回调里写文件或开线程。
❌ 在parse里open("out.txt", "a").write(...)或起线程并发抓取。
✅ 让框架负责输出(Feed Export)和调度,自己只产出数据项与请求。
坑 4:把allowed_domains当安全边界。
❌ 以为限制了域名就等于"对方允许我爬"。
✅ 它只是防跑偏的范围控制;合规与否看robots.txt、条款与限速。
坑 5:为了速度关掉 robots 中间件、把并发拉满。
❌ 这类"优化"常把目标站压慢甚至打挂,属于违规且不道德。
✅ 保留 robots 遵循,设置合理延迟与并发。
坑 6:新老入口写法混用。
❌ 同时重写start_requests()和async def start()—— 行为依赖框架版本。
✅ 按所用 Scrapy 版本的官方文档二选一。
坑 7:把response.text与response.body搞混。
❌ 想拿文本却用了body(bytes),后面做字符串处理报类型错误。
✅ 文本用response.text,原始字节用response.body;编码猜测以官方文档为准。
总结
| 要素 | 说明 |
|---|
Spider是什么 | Scrapy 第三方框架里的爬虫基类,非标准库 |
| 必需成员 | name |
| 起始地址 | start_urls,或用start_requests()/async start()自定义 |
| 解析入口 | parse()等回调,用yield产出数据项与请求 |
| 范围控制 | allowed_domains,只防跑偏不等于授权 |
| 合规底线 | 遵守robots.txt、限速、真实 UA、不绕登录与付费墙 |
| 版本差异 | async def start()自 Scrapy 2.13 起提供,以官方文档为准 |
一句话总结:Spider类只负责"请求哪些 URL、怎么解析响应"这两件事,调度与限速由框架承担;把它用好一半靠写对name、start_urls、parse这套骨架,另一半靠合规——先读robots.txt、放慢节奏、如实标注身份,才能既拿到数据又不给目标站添麻烦。