Scrapy 框架基础知识
Scrapy 是一个适合处理结构化爬取任务的 Python 爬虫框架。相比手写 requests 循环请求,Scrapy 内置了请求调度、并发控制、失败重试、数据管道和中间件机制,更适合需要持续抓取、批量抓取或长期维护的项目。
这篇文章用一个公开练习站点做示例:抓取名言、作者和标签,并处理分页。目标是先掌握 Scrapy 的基础项目结构,而不是一上来处理登录、验证码、风控这些复杂问题。
Scrapy 适合下面这些场景:
- 需要抓取大量页面,并希望自动管理请求队列。
- 需要处理并发、重试、下载延迟等稳定性问题。
- 需要把解析逻辑、数据清洗、数据保存拆开维护。
- 需要把抓取结果写入 CSV、JSON、MySQL、MongoDB 等存储。
- 需要后续扩展代理、Cookies、请求头、中间件或分布式队列。
如果只是抓取一两个页面,requests 或 httpx 更轻量;如果项目会持续增长,Scrapy 的工程结构会更稳。
Scrapy 常见模块可以先这样理解:
- Spider:定义从哪里开始抓、怎么解析页面、怎么继续发请求。
- Request / Response:请求和响应对象,Scrapy 负责调度与下载。
- Item:定义要提取的数据字段。
- Pipeline:处理清洗、校验、保存数据。
- Settings:配置并发、延迟、请求头、Pipeline 开关等。
- Middleware:扩展请求和响应处理流程,例如代理、Cookies、重试策略。
最开始学习时,优先掌握 Spider、Item、Pipeline 和 Settings 就够了。
本教程抓取练习站点:
https://quotes.toscrape.com/需要提取三类字段:
text:名言内容。author:作者。tags:标签列表。
同时处理分页,把所有页面的数据都抓下来。
安装 Scrapy
Section titled “安装 Scrapy”pip install scrapyscrapy startproject quotes_crawler进入项目目录:
cd quotes_crawler创建爬虫:
scrapy genspider quotes quotes.toscrape.com生成后的目录大致如下:
文件夹quotes_crawler/
- scrapy.cfg
文件夹quotes_crawler/
__init__.py- items.py
- middlewares.py
- pipelines.py
- settings.py
文件夹spiders/
__init__.py- quotes.py
定义 Item
Section titled “定义 Item”先在 quotes_crawler/items.py 中定义要保存的数据结构。
import scrapy
class QuoteItem(scrapy.Item): text = scrapy.Field() author = scrapy.Field() tags = scrapy.Field()Item 的作用是让数据字段更明确。虽然直接 yield dict 也可以,但使用 Item 更适合后续维护和 Pipeline 处理。
编写 Spider
Section titled “编写 Spider”打开 quotes_crawler/spiders/quotes.py,写入下面的爬虫逻辑。
import scrapy
from quotes_crawler.items import QuoteItem
class QuotesSpider(scrapy.Spider): name = "quotes" allowed_domains = ["quotes.toscrape.com"] start_urls = ["https://quotes.toscrape.com/"]
def parse(self, response): quote_blocks = response.css(".quote")
for quote in quote_blocks: item = QuoteItem() item["text"] = quote.css(".text::text").get() item["author"] = quote.css(".author::text").get() item["tags"] = quote.css(".tags .tag::text").getall() yield item
next_page = response.css("li.next a::attr(href)").get() if next_page: yield response.follow(next_page, callback=self.parse)这段代码做了几件事:
- 通过
.quote找到每条名言所在的元素。 - 从每条名言中提取正文、作者和标签。
yield item把数据交给 Scrapy 后续处理。- 找到下一页链接后,使用
response.follow()继续请求。
CSS 选择器说明
Section titled “CSS 选择器说明”Scrapy 支持 CSS 选择器和 XPath。上面的代码主要用了 CSS 选择器:
response.css(".quote")表示选择页面里所有 class="quote" 的元素。
quote.css(".text::text").get()表示获取当前 quote 内部 .text 元素的文本。
quote.css(".tags .tag::text").getall()表示获取当前 quote 内所有标签文本,并返回列表。
常用方法:
.get():取第一个结果。.getall():取全部结果。::text:取文本内容。::attr(href):取属性值。
在项目根目录运行:
scrapy crawl quotes如果想直接导出 JSON 文件:
scrapy crawl quotes -O quotes.json导出 CSV:
scrapy crawl quotes -O quotes.csv-O 会覆盖已有文件。如果想追加写入,可以使用 -o。
使用 Pipeline 保存 JSONL
Section titled “使用 Pipeline 保存 JSONL”如果想更接近真实项目,可以使用 Pipeline 自己控制保存逻辑。
编辑 quotes_crawler/pipelines.py:
import json
class JsonLinesPipeline: def open_spider(self, spider): self.file = open("quotes.jl", "w", encoding="utf-8")
def close_spider(self, spider): self.file.close()
def process_item(self, item, spider): line = json.dumps(dict(item), ensure_ascii=False) self.file.write(line + "\n") return item然后在 quotes_crawler/settings.py 中开启 Pipeline:
ITEM_PIPELINES = { "quotes_crawler.pipelines.JsonLinesPipeline": 300,}再次运行:
scrapy crawl quotes运行结束后会生成:
quotes.jlJSON Lines 的特点是每一行都是一条 JSON 数据,适合大规模数据逐行写入。
Scrapy 默认支持并发请求。如果需要控制请求速度,可以在 settings.py 中配置:
# 遵守 robots.txt,练习阶段建议开启ROBOTSTXT_OBEY = True
# 下载延迟,避免请求过于密集DOWNLOAD_DELAY = 0.5
# 最大并发请求数CONCURRENT_REQUESTS = 16
# 单个域名的并发请求数CONCURRENT_REQUESTS_PER_DOMAIN = 8
# 请求头,可以按需要补充DEFAULT_REQUEST_HEADERS = { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",}并发不是越高越好。真实项目中需要结合目标站点响应速度、机器资源和站点规则逐步调整。
页面没有解析出数据
Section titled “页面没有解析出数据”可以先保存响应内容,确认请求到的页面是否和浏览器看到的一样。
def parse(self, response): self.logger.info("Current URL: %s", response.url) self.logger.info("Status: %s", response.status)如果页面依赖 JavaScript 渲染,Scrapy 直接请求 HTML 可能拿不到最终数据。这时需要寻找接口数据,或使用 Playwright、Selenium 等方案。
分页没有继续抓取
Section titled “分页没有继续抓取”检查下一页选择器是否正确:
next_page = response.css("li.next a::attr(href)").get()如果 next_page 是相对路径,使用 response.follow() 会自动补全 URL。
导出的中文乱码
Section titled “导出的中文乱码”JSON 导出可以指定编码:
scrapy crawl quotes -O quotes.json -s FEED_EXPORT_ENCODING=utf-8Pipeline 中写文件时也要指定:
open("quotes.jl", "w", encoding="utf-8")可以在 Pipeline 中统一清理文本:
def process_item(self, item, spider): item["text"] = item["text"].strip("“”") item["author"] = item["author"].strip() return item如果担心重复数据,可以用 text + author 作为去重键:
class DuplicatesPipeline: def open_spider(self, spider): self.seen = set()
def process_item(self, item, spider): key = (item["text"], item["author"]) if key in self.seen: raise DropItem(f"Duplicate item: {item['text']}") self.seen.add(key) return item使用 DropItem 时需要导入:
from scrapy.exceptions import DropItem当数据量增大后,可以把 Pipeline 改成写入数据库:
- MySQL:适合结构化数据和后续查询。
- MongoDB:适合字段变化较多的数据。
- Elasticsearch:适合全文检索。
Spider 仍然只负责解析,Pipeline 负责保存,这样结构会比较清晰。
- 优先选择公开、允许抓取或专门用于练习的网站。
- 控制请求频率,不要对目标站点造成压力。
- 遵守目标站点的 robots、服务条款和相关法律要求。
- 不要把账号、密码、Token 等敏感信息写进代码。
- 对需要登录、验证码、风控的网站,要单独评估合法性和技术复杂度。
Scrapy 的核心价值不是单次请求,而是提供了一套可维护的爬虫工程结构。
一个基础 Scrapy 项目通常包含:
- Spider 负责请求和解析。
- Item 定义数据字段。
- Pipeline 负责清洗和保存。
- Settings 控制并发、延迟和扩展功能。
掌握这条链路后,再去处理登录、代理、分布式队列或动态页面,会更容易扩展。