【scrapy教程】掌握强大的网络数据采集框架

Scrapy 是什么?
Scrapy 是一个用 Python 编写的强大且高效的网络数据采集(也称为网络爬取或爬虫)框架。它被设计用来快速而优雅地从网站中提取结构化数据。与从头编写一个简单的脚本不同,Scrapy 提供了一整套工具和架构,帮助你处理网络请求、数据解析、数据存储、并发控制、错误处理等复杂任务。它是一个完整的框架,不仅仅是一个库。

为什么选择 Scrapy 而不是自己写脚本?

对于简单的数据采集任务,可能自己写几行 Python 代码加 requests 和 beautifulsoup 就可以搞定。但当任务变得复杂时,Scrapy 的优势就非常明显了:

  • 高效和并发: Scrapy 使用异步网络处理(基于 Twisted),可以同时发出多个请求,大大提高了采集速度,而无需你手动管理复杂的并发逻辑。
  • 结构清晰: Scrapy 提供了清晰的项目结构和组件,使得代码组织更加规范,易于维护和扩展。
  • 内置功能丰富: 它自带了请求调度、下载器、蜘蛛(Spiders)、管道(Pipelines)、中间件(Middlewares)等核心组件,开箱即用,省去了大量重复工作。
  • 强大的选择器: 内建对 CSS 和 XPath 选择器的良好支持,让你方便地从HTML或XML中提取数据。

  • 可扩展性: 通过中间件和管道,你可以轻松插入自定义逻辑,如处理 Cookie、修改请求头、自动重试、数据清洗、数据验证和存储。
  • 方便的命令行工具: 提供强大的命令行工具,用于创建项目、生成爬虫、运行爬虫、检查数据等。

简单来说,Scrapy 帮你解决了网络数据采集中的很多“脏活累活”,让你更专注于如何定义采集规则和处理数据。

Scrapy 的核心组件是什么?

理解 Scrapy 的架构有助于更好地使用它。其主要组件协同工作完成采集任务:

  • Scrapy Engine (引擎)

    引擎负责控制所有组件之间的数据流,并根据事件驱动系统触发相应的动作。它是整个框架的核心枢纽。

  • Scheduler (调度器)

    调度器从引擎接收 Request 对象,并将其放入队列中,等待引擎请求它们进行下载。它还负责过滤重复的请求。

  • Downloader (下载器)

    下载器负责获取网页内容。它接收引擎发来的 Request 对象,向互联网发送请求,并将 Responses 返回给引擎。

  • Spiders (蜘蛛)

    蜘蛛是你编写的类,定义了如何访问特定的网站(起始 URL、跟踪链接规则)以及如何从页面中提取结构化数据(解析 Response)。蜘蛛是用户自定义逻辑的主要实现地方。

  • Item Pipelines (数据管道)

    数据管道负责处理蜘蛛提取出的 Item(或字典)。通常用于清洗数据、验证数据、将数据持久化(存到数据库、文件等)。每个管道组件按特定顺序处理 Item。

  • Downloader Middlewares (下载器中间件)

    位于引擎和下载器之间,你可以通过它们在请求发送给下载器之前或响应到达引擎之前进行拦截和处理。常用于设置用户代理、处理 Cookie、引入代理、重试失败请求等。

  • Spider Middlewares (蜘蛛中间件)

    位于引擎和蜘蛛之间,用于处理蜘蛛的输入(Response)和输出(Request 或 Item)。常用于处理蜘蛛的回调函数输入、处理蜘蛛产生的请求等。

整个流程大致是:引擎从蜘蛛获取起始请求 -> 引擎将请求发给调度器 -> 调度器安排请求 -> 引擎从调度器获取请求 -> 引擎发请求给下载器 -> 下载器中间件处理请求 -> 下载器发送 HTTP 请求获取网页 -> 下载器将响应返回给引擎 -> 下载器中间件处理响应 -> 引擎将响应发给蜘蛛 -> 蜘蛛中间件处理响应 -> 蜘蛛解析响应生成 Item 或新的 Request -> 引擎将 Item 发给数据管道处理,将新的 Request 发给调度器。

如何开始使用 Scrapy?安装与创建项目

开始使用 Scrapy 非常简单,只需要几个步骤:

1. 安装 Scrapy

确保你的系统中安装了 Python 和 pip。然后打开终端或命令提示符,运行以下命令:

pip install scrapy

Scrapy 依赖于一些库,如 Twisted、lxml 等,pip 会自动处理这些依赖的安装。

2. 创建 Scrapy 项目

找到你想要创建项目的目录,然后在终端中运行:

scrapy startproject myproject

myproject 替换为你希望的项目名称。执行完毕后,会生成一个如下结构的项目目录:

myproject/
scrapy.cfg # 项目的配置文件
myproject/
__init__.py
items.py # 定义你的数据结构 Item
middlewares.py # 定义中间件
pipelines.py # 定义数据管道
settings.py # 项目设置
spiders/ # 存放你的爬虫文件
__init__.py

这个结构为你的爬虫代码提供了良好的组织方式。

编写第一个 Scrapy 爬虫 (Spider)

爬虫是 Scrapy 项目的核心,它定义了如何从特定网站提取数据。

1. 创建一个蜘蛛文件

进入你的项目目录(比如 myproject),然后使用 Scrapy 的命令行工具创建一个新的蜘蛛:

cd myproject
scrapy genspider example example.com

这会在 myproject/spiders 目录下生成一个名为 example.py 的文件,内容大致如下:

import scrapy

class ExampleSpider(scrapy.Spider):
name = “example”
allowed_domains = [“example.com”]
start_urls = [“http://example.com”]

def parse(self, response):
pass # TODO: parse the response

其中:

  • name: 爬虫的唯一名称。
  • allowed_domains: 可选列表,限制爬虫只爬取这些域名下的页面,防止爬出站外。
  • start_urls: 一个 URL 列表,爬虫将从这些 URL 开始爬取。
  • parse(self, response): 这是一个默认的回调方法。当下载器下载了一个起始 URL 的页面后,会将 Response 对象传递给这个方法进行处理。你需要在这里编写解析逻辑。

2. 编写解析逻辑

parse 方法中,你会接收到一个 response 对象。这个对象包含了页面内容以及方便的数据提取工具(选择器)。Scrapy 支持 CSS 和 XPath 选择器。

假设你想从页面中提取标题和一个段落的文本。你可以修改 parse 方法如下:

def parse(self, response):
# 使用 CSS 选择器提取标题
# 这里的 ‘h1::text’ 表示选择所有的 h1 标签的文本内容
title = response.css(‘h1::text’).get()

# 使用 XPath 选择器提取第一个 p 标签的文本
# 这里的 ‘//p[1]/text()’ 表示选择文档中第一个 p 标签下的文本内容
paragraph = response.xpath(‘//p[1]/text()’).get()

# 打印提取到的数据
print(f”Title: {title}”)
print(f”Paragraph: {paragraph}”)

# 或者将数据作为字典或 Item 对象返回
yield {
‘title’: title,
‘paragraph’: paragraph,
}

这里使用了 .get() 方法来获取选择器匹配到的第一个结果的文本内容。如果你想要获取所有匹配项的列表,可以使用 .getall() 方法。

你也可以使用 response.css(...).extract_first() / .extract()response.xpath(...).extract_first() / .extract(),它们的行为与 .get() / .getall() 类似,只是命名不同,新版本更推荐使用 .get() / .getall()

如何处理分页与跟踪链接?

大多数网站都不是单页的,你需要跟踪链接去访问更多的页面。Scrapy 使得这个过程非常简单。

parse 或其他回调方法中,你可以找到页面上的链接,并生成新的 Request 对象,然后 yield 这些请求。Scrapy 引擎会自动调度这些新的请求进行下载。

例如,在一个列表页上找到下一页的链接:

def parse(self, response):
# 提取当前页的数据
# … (数据提取逻辑) …
yield {
‘item1’: ‘value1’,
# … 其他数据 …
}

# 找到下一页的链接 (假设下一页链接在 class 为 ‘next-page’ 的链接中)
next_page_url = response.css(‘a.next-page::attr(href)’).get()

# 如果找到了下一页链接且它是绝对路径或者可以拼接成绝对路径
if next_page_url is not None:
# 使用 response.urljoin() 处理相对或绝对路径
next_page_full_url = response.urljoin(next_page_url)

# 生成一个新的 Request 对象,并指定解析这个新页面的回调函数
yield scrapy.Request(url=next_page_full_url, callback=self.parse) # 再次调用 parse 方法解析下一页

在这个例子中,callback=self.parse 表示下载了下一页后,仍然使用当前的 parse 方法来处理响应。你也可以指定其他的回调方法来处理不同类型的页面。

如果你需要将当前页的一些数据传递给下一个页面(例如,从列表页传递一个分类信息到详情页),可以使用 cb_kwargs 参数:

detail_page_url = response.css(‘a.detail-link::attr(href)’).get()
category_info = “Electronics”

if detail_page_url:
yield scrapy.Request(
url=response.urljoin(detail_page_url),
callback=self.parse_detail,
cb_kwargs={‘category’: category_info}
)

然后在 parse_detail 方法中接收这个参数:

def parse_detail(self, response, category):
# category 参数就包含了从上一页传递过来的 ‘Electronics’
print(f”Processing detail page for category: {category}”)
# … 解析详情页数据 …
yield {
‘category’: category,
‘product_name’: response.css(‘h1::text’).get(),
# … 其他数据 …
}

如何保存爬取的数据? (Item 与 Pipeline)

蜘蛛提取到的数据通常以字典或 scrapy.Item 对象的形式返回。为了对这些数据进行后续处理和持久化,Scrapy 引入了数据管道 (Item Pipelines)。

1. 定义 Item (可选但推荐)

虽然可以直接返回字典,但定义一个 Item 类能提供更好的结构化和清晰度。编辑项目中的 items.py 文件:

import scrapy

class MyprojectItem(scrapy.Item):
# define the fields for your item here like:
title = scrapy.Field()
paragraph = scrapy.Field()
# 添加更多字段…
pass

然后在蜘蛛中导入并使用它:

from ..items import MyprojectItem

def parse(self, response):
item = MyprojectItem()
item[‘title’] = response.css(‘h1::text’).get()
item[‘paragraph’] = response.xpath(‘//p[1]/text()’).get()
# 填充其他字段

yield item

2. 编写数据管道 (Pipeline)

数据管道是一系列按顺序执行的类。编辑项目中的 pipelines.py 文件。一个典型的管道类至少包含 process_item(self, item, spider) 方法:

# -*- coding: utf-8 -*-

# Define your item pipelines here
#
# Don’t forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html

class MyprojectPipeline:
def process_item(self, item, spider):
# 这里可以对 item 进行清洗、验证等操作
# 例如,检查某个字段是否存在或格式是否正确

# 最后,必须返回 item,以便下一个管道处理,或者返回 DropItem 抛弃该 item
return item

# 示例:一个简单的将数据保存到 JSON 文件的管道
import json

class JsonWriterPipeline:
def open_spider(self, spider):
# 爬虫开启时执行,打开文件
self.file = open(‘items.jsonl’, ‘w’, encoding=’utf-8′)
# 使用 .jsonl 格式,每行一个 JSON 对象

def close_spider(self, spider):
# 爬虫关闭时执行,关闭文件
self.file.close()

def process_item(self, item, spider):
# 处理每个 item,将其写入文件
line = json.dumps(dict(item), ensure_ascii=False) + “\n”
self.file.write(line)
return item # 继续将 item 传递给下一个可能的管道

3. 启用数据管道

在项目的 settings.py 文件中,找到 ITEM_PIPELINES 设置,并取消注释或添加你的管道类及其优先级(数字越小优先级越高):

ITEM_PIPELINES = {
‘myproject.pipelines.MyprojectPipeline’: 300,
‘myproject.pipelines.JsonWriterPipeline’: 400,
# 添加更多管道…
}

这样,当蜘蛛生成 Item 时,它会依次通过 MyprojectPipelineJsonWriterPipeline 进行处理。

快速保存数据到文件: 对于简单的保存需求,你也可以不写 Pipeline,直接在运行时指定输出格式和文件名,Scrapy 内置了多种输出器:

scrapy crawl example -o items.json
scrapy crawl example -o items.csv
scrapy crawl example -o items.xml

这会在爬虫运行完毕后,自动将提取到的所有 Item 保存到指定的文件中。

如何运行你的爬虫?

在项目根目录下(包含 scrapy.cfg 的目录),打开终端,运行以下命令:

scrapy crawl your_spider_name

your_spider_name 替换为你蜘蛛类中定义的 name 属性的值(例如上面的 example)。Scrapy 引擎会启动,找到对应的蜘蛛,开始爬取。

运行时会看到详细的日志输出,包括请求的数量、状态码、提取到的 Item 数量、警告和错误等。

Scrapy 常用进阶操作有哪些?

掌握了基础,你可以利用 Scrapy 的更多功能处理复杂的场景:

  • 使用设置 (Settings) 控制行为

    编辑 settings.py 文件可以全局或按蜘蛛配置各种行为,如:

    • DOWNLOAD_DELAY: 设置下载间隔,避免对网站造成过大压力。
    • CONCURRENT_REQUESTS: 设置并发请求数。
    • USER_AGENT: 设置请求头中的 User-Agent。
    • ROBOTSTXT_OBEY: 是否遵守网站的 robots.txt 协议。
    • DEFAULT_REQUEST_HEADERS: 设置默认请求头。
    • ITEM_PIPELINES: 启用和配置数据管道。
    • DOWNLOADER_MIDDLEWARES: 启用和配置下载器中间件。
    • SPIDER_MIDDLEWARES: 启用和配置蜘蛛中间件。
  • 编写中间件 (Middlewares)

    自定义下载器中间件可以实现诸如自动更换 User-Agent、使用代理 IP 池、处理重定向、处理 Cookie、自动重试等功能。自定义蜘蛛中间件可以处理蜘蛛的输入输出。这是 Scrapy 强大灵活性的关键。

  • 处理登录和 Session

    对于需要登录的网站,你可以在起始请求中模拟登录的 POST 请求,并在后续请求中维护 Cookie 或 Session 信息。下载器中间件可以帮你自动处理 Cookie。

  • 处理动态加载内容 (JavaScript/AJAX)

    Scrapy 本身不执行 JavaScript。对于依赖 JavaScript 动态加载内容的网站,可以结合外部工具,如 Selenium、Puppeteer 或专门为 Scrapy 设计的插件如 scrapy-splash 或 scrapy-playwright,让这些工具负责渲染页面,Scrapy 再从渲染后的页面中提取数据。

  • 错误处理和日志

    Scrapy 提供详细的日志输出,可以帮助你调试问题。你也可以在代码中捕获异常或检查 Response 的状态码来进行错误处理。

  • 使用 Scrapy Shell

    这是一个交互式环境,可以在不运行整个爬虫的情况下测试选择器和提取逻辑,非常方便调试。在项目目录下运行 scrapy shell "要测试的URL" 即可进入。

在哪里找到更多学习资源?掌握 Scrapy 需要多少时间?

在哪里学习?

学习 Scrapy 最权威和详细的资源是:

  • 官方文档 (Official Scrapy Documentation): 这是最全面、最准确的学习资料。虽然初看可能觉得内容多,但它涵盖了 Scrapy 的所有功能和用法,强烈推荐作为主要参考。
  • 在线教程和博客文章: 有很多开发者分享的 Scrapy 入门和进阶教程,可以通过网络方便地找到。它们通常会结合具体例子,有助于快速理解。
  • 社区和论坛: Stack Overflow 等技术问答社区有很多关于 Scrapy 的问题和解答。当遇到具体问题时,这里通常能找到解决方案。
  • 在线课程: 一些在线学习平台也提供 Scrapy 的视频课程,适合喜欢系统学习的人。

需要多少时间掌握?

掌握 Scrapy 所需的时间因人而异,取决于你的 Python 基础、学习能力以及投入的时间。

  • 入门 (编写简单爬虫、提取数据、跟踪链接): 如果有 Python 基础,通常几天到一周时间就可以学会编写简单的 Scrapy 爬虫,能够爬取结构规范的网站。
  • 熟练使用 (利用中间件、管道、处理复杂网站): 这需要更多的时间和实践。理解并灵活运用中间件和管道,处理登录、JS 渲染、反爬机制等,可能需要几周到几个月不等的练习和项目经验。
  • 精通 (深入理解架构、性能优化、大规模部署): 成为 Scrapy 专家需要对框架有深入的理解,能够进行高级定制和优化,处理超大规模的采集任务。这通常需要长时间的实践和项目经验积累。

总的来说,学习 Scrapy 是一个循序渐进的过程。先从简单的例子开始,逐步深入学习其组件和高级功能,并通过实际项目来巩固和提升技能。不要期望一蹴而就,持续的练习和解决问题是掌握 Scrapy 的关键。