第59课:进阶爬虫 - Scrapy 框架入门
学习目标
完成本课学习后,你将能够:
- 理解 Scrapy 框架的核心组件与工作流程。
- 熟练使用
scrapy startproject命令创建并组织 Scrapy 项目。 - 编写一个完整的 Spider 来爬取网页数据。
- 使用 Scrapy Shell 调试和测试选择器(Selectors)。
- 利用 Item Pipeline 对爬取的数据进行清洗和存储。
核心概念
在上一课中,我们使用 requests 和 BeautifulSoup 手动编写了爬虫。这种方式对于简单的、一次性的任务很方便,但当项目变得复杂(需要处理分页、登录、数据持久化、反爬策略)时,代码会变得难以维护。
Scrapy 是一个为了爬取网站数据、提取结构性数据而编写的应用框架。它就像一个功能完备的"爬虫工厂",内置了请求调度、下载、数据提取、数据处理流水线等一系列工具,让我们可以专注于定义"要爬取什么数据"和"如何提取数据",而无需重复造轮子。
项目结构: 一个标准的 Scrapy 项目目录如下:
myproject/
scrapy.cfg # 项目部署配置文件
myproject/ # 项目的 Python 模块
__init__.py
items.py # 定义要抓取的数据结构(Item)
middlewares.py # 下载器和Spider的中间件
pipelines.py # 数据处理管道(如清洗、存储)
settings.py # 项目设置(如并发数、下载延迟、管道开启)
spiders/ # 存放所有爬虫(Spider)的目录
__init__.py
核心组件:
- Spider:我们定义的爬虫类。它负责解析响应(
Response),提取数据(Item)和新的请求(Request)。这是最需要我们编写代码的部分。 - Selector:Scrapy 内置的选择器,基于
lxml库,支持 XPath 和 CSS 选择器,用于从 HTML/XML 中提取数据。 - Item:定义抓取数据的容器,类似于 Python 的字典,但更规范、有明确的字段。
- Item Pipeline:处理被 Spider 提取出来的 Item 的组件。典型应用包括:清洗数据、验证数据、存储到数据库或文件。
- Engine:框架的"大脑",负责控制数据流在系统中所有组件间的流动,并在发生动作时触发事件。
工作流程:
- Engine 从 Spider 获取初始请求(
Request)。 - Engine 将请求放入调度器(
Scheduler)并请求下一个待爬取的请求。 - 调度器将下一个请求返回给 Engine。
- Engine 通过下载器中间件(
Downloader Middlewares)将请求发送给下载器(Downloader)。 - 下载器完成下载,生成响应(
Response),并将其发送回 Engine。 - Engine 通过 Spider 中间件(
Spider Middlewares)将响应发送给 Spider。 - Spider 处理响应,提取出 Item 和新的 Request,并发送回 Engine。
- Engine 将提取的 Item 发送给 Item Pipeline,将新的 Request 发送给调度器。
- 重复步骤2-8,直到没有更多请求。
代码示例
我们将编写一个爬虫,从"名言网站" (quotes.toscrape.com) 抓取名言及其作者。
第一步:创建项目 在终端中运行:
scrapy startproject quotes_demo
cd quotes_demo
第二步:定义 Item
编辑 quotes_demo/items.py 文件,定义我们要抓取的数据字段。
# quotes_demo/items.py
import scrapy
class QuoteItem(scrapy.Item):
# 定义字段,相当于数据表的列
text = scrapy.Field() # 名言内容
author = scrapy.Field() # 作者
tags = scrapy.Field() # 标签,通常是一个列表
第三步:编写 Spider
在 quotes_demo/spiders/ 目录下创建一个新文件 quotes_spider.py。
# quotes_demo/spiders/quotes_spider.py
import scrapy
from quotes_demo.items import QuoteItem # 导入我们定义的Item
class QuotesSpider(scrapy.Spider):
name = 'quotes' # Spider的唯一标识符,运行爬虫时使用
allowed_domains = ['quotes.toscrape.com'] # 允许爬取的域名
start_urls = ['http://quotes.toscrape.com/'] # 爬虫启动时的URL列表
def parse(self, response):
"""
默认的回调方法,用于处理下载器返回的每个页面响应。
:param response: 网页的响应对象,包含页面内容
"""
# 使用CSS选择器提取所有名言块
quotes = response.css('div.quote')
for quote in quotes:
item = QuoteItem() # 实例化一个Item对象
# 提取数据,并通过Item的字段赋值
item['text'] = quote.css('span.text::text').get().strip()
item['author'] = quote.css('small.author::text').get().strip()
item['tags'] = quote.css('div.tags a.tag::text').getall()
yield item # 使用yield将Item发送给Item Pipeline
# 处理下一页:找到"下一页"按钮的链接
next_page = response.css('li.next a::attr(href)').get()
if next_page is not None:
# 构建完整的下一页URL,并生成一个新的请求,回调函数依然是parse
yield response.follow(next_page, callback=self.parse)
第四步:配置 Item Pipeline
编辑 quotes_demo/pipelines.py,实现一个简单的管道,将数据保存到 JSON 文件。
# quotes_demo/pipelines.py
import json
class SaveToPipeline:
def open_spider(self, spider):
"""爬虫启动时调用,初始化文件"""
self.file = open('quotes.json', 'w', encoding='utf-8')
self.file.write('[\n') # 写入JSON数组开头
self.first_item = True
def process_item(self, item, spider):
"""每个Item都会经过这个方法,进行处理(如清洗、存储)"""
# 将Item转换为字典,并写入JSON文件
line = json.dumps(dict(item), ensure_ascii=False) + ',\n'
if self.first_item:
self.file.write(line)
self.first_item = False
else:
# 在写入前,先写入一个逗号(因为JSON对象之间需要逗号分隔)
self.file.write(',' + line)
return item # 必须返回item,供后续管道使用
def close_spider(self, spider):
"""爬虫关闭时调用,收尾工作"""
self.file.write('\n]')
self.file.close()
第五步:启用 Pipeline 并运行
编辑 quotes_demo/settings.py 文件,找到 ITEM_PIPELINES 配置项并取消注释,或添加如下内容:
# quotes_demo/settings.py (部分)
# 开启我们的Pipeline,并设置优先级(数值越小,优先级越高)
ITEM_PIPELINES = {
'quotes_demo.pipelines.SaveToPipeline': 300,
}
# 设置下载延迟,体现友好性,避免给对方服务器造成过大压力
DOWNLOAD_DELAY = 2
运行爬虫:
在终端(项目根目录 quotes_demo 下)运行:
scrapy crawl quotes
运行结束后,你会在当前目录发现一个 quotes.json 文件,里面包含了所有抓取到的名言数据。
实践练习
练习一:基础数据提取
- 修改
quotes_spider.py,在提取text时,尝试使用 XPath 选择器代替 CSS 选择器。- 提示:XPath 表达式可以是
'//span[@class="text"]/text()'
- 提示:XPath 表达式可以是
- 运行爬虫,确保数据仍然能被正确提取。
练习二:增加数据字段
- 修改
QuoteItem类,增加一个birth_date字段(作者出生日期)。 - 修改
quotes_spider.py的parse方法,让它不仅爬取首页,还要进入每个作者的详情页,抓取其出生日期。- 提示:作者链接的选择器是
quote.css('small.author ~ a::attr(href)')'。你需要生成一个新的请求,使用新的回调函数来处理作者详情页。
- 提示:作者链接的选择器是
- 修改 Pipeline,将新字段也保存到
quotes.json中。
练习三:数据存储到 CSV
- 在
pipelines.py中新建一个SaveToCsvPipeline类。 - 实现将
Item数据保存到quotes.csv文件的功能。CSV 文件第一行为列名(text,author,tags,birth_date)。 - 在
settings.py的ITEM_PIPELINES中启用这个新的管道。- 提示:可以使用 Python 的
csv模块。
- 提示:可以使用 Python 的
常见错误
- 忘记在
settings.py中启用 Pipeline:即使写好了 Pipeline 类,如果不在ITEM_PIPELINES字典中注册,它也不会生效。 - 选择器(Selector)错误:这是最常见的问题。CSS 或 XPath 选择器可能写错,或者页面结构发生了变化。一定要善用 Scrapy Shell 来调试! 在终端中运行
scrapy shell 'http://quotes.toscrape.com/',然后交互式地测试你的选择器:response.css('your.selector')。 - 在 Pipeline 中未返回
item:process_item方法的最后必须返回item对象。如果返回None,那么这个 Item 将不会被后续的 Pipeline 处理,甚至可能丢失。 - 处理 JavaScript 动态加载的内容:Scrapy 默认无法执行 JavaScript。如果目标网站的内容是 JS 动态加载的,你需要使用
scrapy-splash或scrapy-playwright等扩展。 - 不遵守
robots.txt:Scrapy 默认遵守robots.txt规则。如果你在爬取一个禁止爬取的网站,可能会得到Forbidden错误。可以在settings.py中设置ROBOTSTXT_OBEY = False(但请谨慎使用,确保你的行为合法合规)。
小结
本节课我们踏入了 Scrapy 框架的大门:
- Scrapy 是一个强大、高效、可扩展的 Python 爬虫框架,适合中大型爬虫项目。
- 核心组件 包括
Spider(定义爬取逻辑)、Item(数据容器)、Pipeline(数据处理管道)、Selector(数据提取器)。 - 工作流程 清晰:Engine 协调 Spider、Scheduler、Downloader 和 Pipeline,形成数据流动的闭环。
- 开发步骤:
startproject-> 定义Item-> 编写Spider-> 配置Pipeline-> 在settings中启用相关功能。 - 调试利器:
scrapy shell是验证选择器和调试爬虫的必备工具。
掌握 Scrapy 意味着你拥有了一个强大的网络数据采集工具箱。下一课,我们将从数据采集转向 Web 应用开发,学习如何使用 Flask 框架来创建属于自己的网站。