ZVVQ代理分享网

使用Scrapy进行Web抓取:Python教程 - 终极指南

作者:zvvq博客网
导读本终极指南详细介绍了如何使用Python Scrapy框架进行Web抓取。从环境搭建、项目结构、Spider编写到数据提取(XPath/CSS)、Item Pipeline处理与存储,再到高级配置与反爬策略,全面解析Scrap

Scrapy

在当今信息爆炸的时代,互联网成为了一个巨大的数据宝库。从市场趋势分析到竞争对手情报收集,从学术研究到个人兴趣探索,对海量网络数据的需求日益增长。然而,手动收集这些数据不仅效率低下,而且几乎不可能实现。这时,Web抓取(Web Scraping),也称为网络爬虫,应运而生,它是一种自动化从网站提取结构化数据的方法。
Web抓取通过编写程序模拟人类浏览器的行为,访问网页,解析其内容,并从中提取所需信息。这些信息可以是商品价格、新闻文章、用户评论、联系方式等等。Web抓取技术广泛应用于数据分析、SEO优化、价格监控、舆情分析等多个领域,为个人和企业提供了强大的数据支持。
在众多Web抓取工具和框架中,Scrapy脱颖而出,成为Python社区中最受欢迎、功能最强大的爬虫框架之一。Scrapy是一个快速、高层次的Web抓取和数据提取框架,它提供了一整套工具和组件,帮助开发者高效地构建和运行爬虫。Scrapy的优势在于其异步处理能力、灵活的Selector机制、可扩展的Item Pipeline以及强大的中间件系统,这些特性使得Scrapy能够轻松应对复杂的抓取任务,包括处理动态内容、管理会话、应对反爬机制等。无论是初学者还是经验丰富的开发者,Scrapy都能提供一个高效且可维护的解决方案,让Web抓取变得更加简单和高效。

Scrapy环境搭建

在开始使用Scrapy构建您的第一个爬虫之前,您需要确保Python环境已正确配置,并安装Scrapy及其相关依赖。以下是详细的步骤指南:

1. Python环境准备

Scrapy是一个基于Python的框架,因此您需要先安装Python。推荐使用Python 3.6或更高版本。如果您尚未安装Python,可以从Python官方网站下载并安装最新版本。在安装过程中,请务必勾选“Add Python to PATH”选项,这样可以方便地在命令行中直接使用Python命令。
为了更好地管理Python项目和依赖,强烈建议使用虚拟环境(Virtual Environment)。虚拟环境可以为每个项目创建一个独立的Python运行环境,避免不同项目之间的依赖冲突。常用的虚拟环境工具有venv(Python 3.3+ 内置)和conda(Anaconda发行版)。
使用venv创建虚拟环境:
打开命令行或终端,导航到您希望创建项目的目录,然后执行以下命令:

python -m venv my_scrapy_env


这将在当前目录下创建一个名为my_scrapy_env的虚拟环境。激活虚拟环境的命令因操作系统而异:
Windows:
macOS/Linux:
激活虚拟环境后,您的命令行提示符前会显示虚拟环境的名称(例如 (my_scrapy_env)),表示您当前正处于虚拟环境中。所有后续安装的Python包都将安装到这个独立的虚拟环境中,而不会影响系统全局的Python环境。

2. Scrapy安装步骤

在激活虚拟环境后,您可以使用Python的包管理工具pip来安装Scrapy。Scrapy的安装通常会同时安装其所有必要的依赖项,包括Twistedlxml等。
执行以下命令安装Scrapy:

pip install Scrapy


如果您在中国大陆地区,由于网络原因,直接从PyPI(Python Package Index)安装可能会比较慢或失败。您可以考虑使用国内的镜像源来加速安装过程,例如清华大学的镜像源:

pip install Scrapy -i https://pypi.tuna.tsinghua.edu.cn/simple
 
安装完成后,您可以通过运行以下命令来验证Scrapy是否成功安装:
Bash
scrapy version

如果命令成功执行并显示Scrapy的版本信息,则表示Scrapy已成功安装并准备就绪。现在,您已经为Web抓取之旅打下了坚实的基础,可以开始创建您的第一个Scrapy项目了。

Scrapy项目结构解析

成功安装Scrapy后,下一步是创建一个Scrapy项目。Scrapy项目是组织爬虫代码和相关配置的容器。通过使用Scrapy提供的命令行工具,您可以快速生成一个标准的项目骨架,这大大简化了开发流程。

scrapy startproject 命令

要创建一个新的Scrapy项目,您只需在命令行中导航到您希望创建项目的目录,然后执行scrapy startproject命令,后面跟着您的项目名称。例如,要创建一个名为my_scraper的项目:
Bash
scrapy startproject my_scraper

执行此命令后,Scrapy将自动创建一个包含以下目录和文件的项目结构:

my_scraper/
├── my_scraper/
│   ├── __init__.py
│   ├── items.py
│   ├── middlewares.py
│   ├── pipelines.py
│   ├── settings.py
│   └── spiders/
│       └── __init__.py
└── scrapy.cfg

各文件作用
让我们逐一了解这些文件和目录在Scrapy项目中的作用:
scrapy.cfg: 这是项目的配置文件,位于项目的根目录。它定义了项目的名称,并指向项目的Python模块。当您在项目根目录运行Scrapy命令时,Scrapy会查找这个文件来识别项目。它通常包含以下内容:
my_scraper/ (项目模块目录): 这是项目的Python模块,包含了项目的核心代码。所有与爬虫相关的Python文件都将存放在这个目录下。
my_scraper/__init__.py: 一个空的Python文件,表示my_scraper目录是一个Python包。
my_scraper/items.py: 这个文件用于定义您希望从网页中抓取的数据结构。在Scrapy中,抓取到的数据通常以Item对象的形式表示。Item类似于Python字典,但提供了额外的保护,防止拼写错误。通过定义Item,您可以清晰地规划需要提取哪些字段的数据。例如:
my_scraper/middlewares.py: 中间件(Middleware)是Scrapy框架中非常重要的组件,它允许您在Scrapy处理请求(Request)和响应(Response)的各个阶段插入自定义逻辑。Scrapy有两种类型的中间件:
下载器中间件(Downloader Middleware):在请求发送到下载器之前和响应到达爬虫之前处理请求和响应。常用于处理User-Agent轮换、代理设置、Cookie管理、重试机制等反爬策略。
爬虫中间件(Spider Middleware):在爬虫处理输入响应和生成输出结果(Item和Request)时处理它们。常用于处理爬虫的输入和输出。 您可以在此文件中编写自定义中间件,并在settings.py中启用它们。
my_scraper/pipelines.py: Item Pipeline是Scrapy处理抓取到的Item的组件。当一个Item被爬虫抓取后,它会被发送到Item Pipeline进行一系列的处理,例如:
数据清洗:去除HTML标签、多余空格等。
数据验证:检查数据是否符合预期格式或范围。
数据去重:防止重复抓取相同的数据。
数据存储:将数据保存到数据库(如MySQL, MongoDB)、文件(如JSON, CSV)或其他存储介质中。 您可以在此文件中编写自定义的Pipeline类,并在settings.py中启用它们。
my_scraper/settings.py: 这是Scrapy项目的核心配置文件,包含了项目的各种设置,用于控制Scrapy各个组件的行为。您可以在这里配置下载延迟、并发请求数、用户代理、启用/禁用中间件和Pipeline等。例如:
my_scraper/spiders/ 目录: 这个目录是存放所有爬虫(Spider)文件的地方。每个爬虫都是一个独立的Python文件,负责定义如何从特定网站抓取数据。通常,一个网站对应一个或多个Spider。
my_scraper/spiders/__init__.py: 一个空的Python文件,表示spiders目录是一个Python包。
理解Scrapy的项目结构是高效开发爬虫的第一步。每个组件各司其职,共同协作完成Web抓取任务。在接下来的章节中,我们将深入探讨如何编写Spider来定义抓取逻辑,以及如何使用Selector提取数据,并利用Item Pipeline处理和存储数据。

编写第一个Scrapy爬虫(Spider)

Spider是Scrapy框架的核心,它定义了如何从特定网站(或一组网站)抓取数据。每个Spider都是一个Python类,继承自scrapy.Spider,并包含了一系列用于定义抓取行为和数据提取逻辑的属性和方法。

Spider的核心组件

一个基本的Scrapy Spider通常包含以下几个核心组件:
name: 这是Spider的唯一标识符。在Scrapy项目中,每个Spider都必须有一个唯一的名称。您将使用这个名称来运行您的爬虫。例如:name = 'quotes'
allowed_domains: 这是一个可选的列表,包含了Spider允许抓取的域名。如果请求的URL不在这个列表中,该请求将被过滤掉。这有助于防止爬虫离开目标网站,避免抓取不相关的页面。例如:allowed_domains = ['quotes.toscrape.com']
start_urls: 这是一个URL列表,包含了Spider开始抓取的起始URL。Scrapy将从这些URL开始发送请求,并调用parse方法处理响应。例如:start_urls = ['http://quotes.toscrape.com/']
parse 方法: 这是Scrapy Spider中最重要的回调方法。当Scrapy下载完start_urls中的页面并生成响应(Response)对象后,会自动调用parse方法来处理这些响应。parse方法接收一个Response对象作为参数,您可以在这个方法中:
使用选择器(XPath或CSS)从响应中提取数据。
生成新的请求(Request)对象,用于抓取更多页面(例如,跟踪分页链接或详情页链接)。
将提取到的数据封装成Item对象,并将其传递给Item Pipeline进行后续处理。

示例代码:抓取一个简单网页

让我们通过一个简单的例子来演示如何编写一个Scrapy Spider。我们将抓取http://quotes.toscrape.com/网站上的名言、作者和标签。
首先,在您的Scrapy项目(例如my_scraper)的spiders目录下创建一个新的Python文件,例如quotes_spider.py。然后,将以下代码复制到该文件中:

import scrapy
 
class QuotesSpider(scrapy.Spider):
    name = 'quotes'
    allowed_domains = ['quotes.toscrape.com']
    start_urls = ['http://quotes.toscrape.com/']
 
    def parse(self, response):
        # 提取名言、作者和标签
        # 使用CSS选择器定位每个名言块
        for quote in response.css('div.quote'):
            yield {
                'text': quote.css('span.text::text').get(),
                'author': quote.css('small.author::text').get(),
                'tags': quote.css('div.tags a.tag::text').getall(),
            }
 
        # 查找下一页的链接并继续抓取
        next_page = response.css('li.next a::attr(href)').get()
        if next_page is not None:
            yield response.follow(next_page, callback=self.parse)
 
代码解释:
1.import scrapy:导入Scrapy库。
2.class QuotesSpider(scrapy.Spider)::定义一个名为QuotesSpider的类,它继承自scrapy.Spider
3.name = 'quotes':设置Spider的名称为quotes
4.allowed_domains = ['quotes.toscrape.com']:限制Spider只抓取quotes.toscrape.com域名下的页面。
5.start_urls = ['http://quotes.toscrape.com/']:指定爬虫的起始URL。
6.parse(self, response) 方法
response.css('div.quote'):使用CSS选择器选择页面中所有class为quotediv元素,每个div代表一个名言块。
quote.css('span.text::text').get():在每个名言块中,使用CSS选择器提取class为textspan元素中的文本内容。.get()方法返回第一个匹配项的文本。
quote.css('small.author::text').get():提取作者姓名。
quote.css('div.tags a.tag::text').getall():提取所有标签。.getall()方法返回所有匹配项的文本列表。
yield {}:将提取到的数据以字典的形式返回。Scrapy会将这些字典转换为Item对象,并将其发送到Item Pipeline。
response.css('li.next a::attr(href)').get():查找下一页链接的href属性值。
response.follow(next_page, callback=self.parse):如果存在下一页链接,则生成一个新的请求,并指定回调函数仍为self.parse,从而实现递归抓取。

运行您的第一个爬虫

保存quotes_spider.py文件后,打开命令行或终端,导航到您的Scrapy项目的根目录(即包含scrapy.cfg文件的目录),然后执行以下命令来运行您的爬虫:
Bash
scrapy crawl quotes


Scrapy将开始抓取页面,并在命令行中输出抓取到的数据。您会看到类似以下内容的输出:
...
2023-10-27 10:00:00 [scrapy.core.engine] INFO: Spider opened
2023-10-27 10:00:00 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2023-10-27 10:00:00 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://quotes.toscrape.com/> (referer: None)
2023-10-27 10:00:00 [scrapy.core.scraper] DEBUG: Scraped from <200 http://quotes.toscrape.com/>
{'text': '“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”', 'author': 'Albert Einstein', 'tags': ['change', 'deep-thoughts', 'thinking', 'world']}
...

这表明您的第一个Scrapy爬虫已经成功运行并提取到了数据。在下一节中,我们将深入探讨Scrapy强大的数据提取机制——选择器(Selectors)。

数据提取:Selectors(选择器)详解

在Scrapy中,从下载的网页内容中提取数据是爬虫的核心任务之一。Scrapy提供了强大的选择器(Selectors)机制,支持XPath和CSS两种常用的选择器语法,让数据提取变得高效而灵活。Scrapy的选择器是基于parsel库构建的,它提供了Selector对象来封装响应内容,并提供xpath()css()get()getall()等方法来方便地提取数据。

Scrapy中的Selector对象

在Spider的parse方法中,response对象(scrapy.http.Response的实例)本身就带有一个selector属性,可以直接调用response.xpath()response.css()方法来执行选择器查询。这使得数据提取非常直观。

XPath选择器:基本语法与使用

XPath(XML Path Language)是一种在XML文档中查找信息的语言。由于HTML文档可以被视为一种特殊的XML文档,因此XPath也广泛应用于HTML解析。XPath通过路径表达式来选择节点或节点集。
常用XPath语法:
//tagname:选择文档中所有tagname元素。
//tagname[@attribute=\'value\']:选择所有tagname元素,其attribute属性的值为value
//tagname/childtag:选择tagname元素下的所有childtag子元素。
//tagname//descendanttag:选择tagname元素下的所有descendanttag后代元素。
text():提取元素的文本内容。
@attribute:提取元素的属性值。
contains(@attribute, \'value\'):选择attribute属性值包含value的元素。
starts-with(@attribute, \'value\'):选择attribute属性值以value开头的元素。
XPath使用示例:
假设我们有以下HTML片段:

<div class="product-info">
    <h2 class="title">商品A</h2>
    <span class="price">$19.99</span>
    <ul class="features">
        <li>特点1</li>
        <li>特点2</li>
    </ul>
</div>
 
在Scrapy Spider中,我们可以这样使用XPath提取数据:

# 提取商品标题
title = response.xpath("//div[@class=\'product-info\']/h2[@class=\'title\']/text()").get()
# 结果: "商品A"
 
# 提取商品价格
price = response.xpath("//div[@class=\'product-info\']/span[@class=\'price\']/text()").get()
# 结果: "$19.99"
 
# 提取所有特点
features = response.xpath("//div[@class=\'product-info\']/ul[@class=\'features\']/li/text()").getall()
# 结果: ["特点1", "特点2"]
 
# 提取某个属性值,例如一个链接的href属性
# <a href="/next_page">下一页</a>
# next_page_link = response.xpath("//a[text()=\'下一页\']/@href").get()

CSS选择器:基本语法与使用

CSS选择器是Web开发中用于选择HTML元素的一种简洁方式,Scrapy也对其提供了良好的支持。对于熟悉前端开发的开发者来说,CSS选择器可能更易于上手。
常用CSS选择器语法:
tagname:选择所有tagname元素。
.classname:选择所有class为classname的元素。
#id:选择id为id的元素。
tagname.classname:选择所有class为classnametagname元素。
tagname#id:选择id为idtagname元素。
parent > child:选择parent元素的直接child子元素。
ancestor descendant:选择ancestor元素内的所有descendant后代元素。
[attribute=\'value\']:选择attribute属性值为value的元素。
::text:提取元素的文本内容。
::attr(attribute):提取元素的指定属性值。
CSS使用示例:
使用与XPath示例相同的HTML片段:

<div class="product-info">
    <h2 class="title">商品A</h2>
    <span class="price">$19.99</span>
    <ul class="features">
        <li>特点1</li>
        <li>特点2</li>
    </ul>
</div>
 
在Scrapy Spider中,我们可以这样使用CSS选择器提取数据:

# 提取商品标题
title = response.css("div.product-info h2.title::text").get()
# 结果: "商品A"
 
# 提取商品价格
price = response.css("div.product-info span.price::text").get()
# 结果: "$19.99"
 
# 提取所有特点
features = response.css("div.product-info ul.features li::text").getall()
# 结果: ["特点1", "特点2"]
 
# 提取某个属性值,例如一个链接的href属性
# <a href="/next_page">下一页</a>
# next_page_link = response.css("a::attr(href)").get()
 

get()getall() 方法

在Scrapy的选择器链的末尾,您会经常使用到get()getall()这两个方法来最终提取数据:
get(): 返回选择器匹配到的第一个元素的文本内容或属性值。如果没有匹配到任何元素,则返回None。当您确定某个元素只会出现一次,或者只需要第一个匹配项时,使用get()
getall(): 返回选择器匹配到的所有元素的文本内容或属性值列表。如果没有匹配到任何元素,则返回一个空列表。当您需要提取多个相同类型的元素时,例如列表项、表格行等,使用getall()
选择器链式调用:
Scrapy的选择器支持链式调用,这意味着您可以在一个选择器表达式的结果上继续应用另一个选择器,从而实现更精确的数据定位。例如:

# 先选择商品信息块,再在该块内选择标题
product_block = response.css("div.product-info")
title = product_block.css("h2.title::text").get()
熟练掌握XPath和CSS选择器是Scrapy数据提取的关键。在实际项目中,您可能需要结合使用这两种选择器,根据网页结构和个人偏好选择最适合的提取方式。在下一节中,我们将探讨如何使用Item和Item Pipeline来处理和存储这些提取到的数据。

数据处理与存储:Item和Item Pipeline

在Scrapy中,当爬虫成功从网页中提取到数据后,这些数据并不会直接保存到文件或数据库中。Scrapy引入了ItemItem Pipeline的概念,以提供一个清晰、可扩展的数据处理流程。Item用于定义数据的结构,而Item Pipeline则负责对这些结构化数据进行后续的处理和存储。

定义Item:items.py的作用

Item是Scrapy中用于收集抓取数据的容器。它类似于Python字典,但提供了额外的优势,例如通过预定义字段来避免拼写错误,并更好地组织数据。在Scrapy项目的items.py文件中,您可以定义一个或多个Item类,每个类代表一种您希望抓取的数据类型。
例如,对于我们之前抓取的名言网站,我们可以定义一个QuoteItem来存储名言的文本、作者和标签:

import scrapy
 
class MyScraperItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    pass
 
class QuoteItem(scrapy.Item):
    text = scrapy.Field()
    author = scrapy.Field()
    tags = scrapy.Field()
 
在Spider中,当您提取到数据后,可以将其封装成QuoteItem的实例并yield出去:

# ... 在Spider的parse方法中 ...
from my_scraper.items import QuoteItem
 
# ...
 
        for quote in response.css("div.quote"):
            item = QuoteItem()
            item["text"] = quote.css("span.text::text").get()
            item["author"] = quote.css("small.author::text").get()
            item["tags"] = quote.css("div.tags a.tag::text").getall()
            yield item
# ...
当Spider yield出一个Item实例时,这个Item就会被发送到Item Pipeline进行处理。

Item Pipeline的作用

Item Pipeline是Scrapy处理抓取到的Item的组件。它由一系列按顺序执行的独立处理组件组成,每个组件都负责对Item执行特定的操作。Item Pipeline的主要作用包括:
数据清洗(Cleaning Data):去除HTML标签、多余的空格、特殊字符等,使数据更规范。
数据验证(Validating Data):检查数据是否符合预期的格式、类型或范围。例如,确保价格是数字,日期是有效格式。
数据去重(Duplicating Data):防止重复存储相同的Item,特别是在抓取过程中可能遇到重复的URL或内容时。
数据存储(Storing Data):将处理后的Item保存到各种存储介质中,如数据库(MySQL, PostgreSQL, MongoDB)、文件(JSON, CSV, XML)或其他自定义存储系统。

编写自定义Pipeline:示例代码

要创建一个自定义的Item Pipeline,您需要在pipelines.py文件中定义一个类,并实现process_item方法。process_item方法接收三个参数:item(被处理的Item实例)、spider(处理该Item的Spider实例)和一个return值。process_item方法必须返回一个Item或抛出DropItem异常。
让我们创建一个简单的Pipeline,将抓取到的名言数据保存到JSON文件中。在pipelines.py中添加以下代码:

# pipelines.py
 
import json
 
class JsonWriterPipeline:
    def open_spider(self, spider):
        self.file = open("quotes.json", "w", encoding="utf-8")
        self.file.write("[")
        self.first_item = True
 
    def close_spider(self, spider):
        self.file.write("]")
        self.file.close()
 
    def process_item(self, item, spider):
        if self.first_item:
            self.first_item = False
        else:
            self.file.write(",\n")
        line = json.dumps(dict(item), ensure_ascii=False)
        self.file.write(line)
        return item
 
class DuplicatesPipeline:
    def __init__(self):
        self.ids_seen = set()
 
    def process_item(self, item, spider):
        if item["text"] in self.ids_seen:
            raise DropItem(f"Duplicate item found: {item}")
        else:
            self.ids_seen.add(item["text"])
            return item
 
代码解释:
JsonWriterPipeline
open_spider(self, spider):当Spider打开时被调用。在这里,我们打开一个名为quotes.json的文件,并写入JSON数组的起始方括号[
close_spider(self, spider):当Spider关闭时被调用。在这里,我们写入JSON数组的结束方括号]并关闭文件。
process_item(self, item, spider):核心方法。它将Item转换为字典,然后使用json.dumps将其序列化为JSON字符串,并写入文件。为了生成有效的JSON数组,我们处理了第一个Item和后续Item之间的逗号。
DuplicatesPipeline
__init__(self):初始化一个集合ids_seen,用于存储已处理的Item的唯一标识(这里使用名言文本作为唯一标识)。
process_item(self, item, spider):检查当前Item的文本是否已存在于ids_seen集合中。如果存在,则抛出DropItem异常,Scrapy将丢弃该Item。否则,将Item的文本添加到集合中并返回Item,使其继续传递给下一个Pipeline。

启用Item Pipeline

编写完Pipeline后,您需要在settings.py文件中启用它们。在ITEM_PIPELINES设置中,您可以指定Pipeline的类路径和它们的执行顺序(数字越小,优先级越高)。
 
# settings.py
 
# Enable or disable item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
    'my_scraper.pipelines.DuplicatesPipeline': 200,
    'my_scraper.pipelines.JsonWriterPipeline': 300,
}

在这个配置中,DuplicatesPipeline的优先级是200,JsonWriterPipeline的优先级是300。这意味着DuplicatesPipeline会先于JsonWriterPipeline执行。通常,数据清洗和去重类的Pipeline会放在前面,而数据存储类的Pipeline会放在后面。
通过Item和Item Pipeline,Scrapy提供了一个强大且灵活的机制来处理和存储抓取到的数据,使得数据管理变得有序和高效。在下一节中,我们将探讨Scrapy的一些高级配置和如何应对常见的反爬策略。

Scrapy高级配置与反爬策略

在实际的Web抓取任务中,网站通常会采取各种反爬机制来阻止自动化程序的访问。Scrapy作为一个强大的框架,提供了丰富的配置选项和灵活的扩展机制,帮助我们应对这些挑战。本节将深入探讨Scrapy的一些高级配置以及如何利用它们来绕过常见的反爬策略。

settings.py常用配置

settings.py文件是Scrapy项目的核心配置文件,通过修改其中的参数,可以极大地影响爬虫的行为和性能。以下是一些常用的配置项及其作用:
USER_AGENT: 用户代理(User-Agent)是HTTP请求头中的一个字段,用于标识发出请求的客户端类型(例如浏览器、操作系统等)。许多网站会根据User-Agent来判断请求是否来自真实的浏览器。默认情况下,Scrapy会使用自己的User-Agent,这很容易被网站识别为爬虫。为了模拟真实用户的访问,您应该设置一个常见的浏览器User-Agent。例如:
ROBOTSTXT_OBEYrobots.txt是一个网站用来告知搜索引擎爬虫哪些页面可以抓取、哪些页面不能抓取的协议文件。Scrapy默认会遵守robots.txt的规则(ROBOTSTXT_OBEY = True)。在某些情况下,如果您明确知道自己有权抓取被robots.txt禁止的页面,或者目标网站没有robots.txt文件,您可以将其设置为False。但请注意,不遵守robots.txt可能会导致法律或道德问题,请谨慎使用。
DOWNLOAD_DELAY: 下载延迟是指Scrapy在两次连续请求之间等待的时间(单位:秒)。设置合适的下载延迟可以降低对目标网站的访问频率,模拟人类用户的行为,从而减少被封禁的风险。默认情况下,DOWNLOAD_DELAY为0。您可以根据目标网站的响应速度和反爬强度进行调整。例如,设置为1秒:
CONCURRENT_REQUESTS: 并发请求数是指Scrapy同时发出的最大请求数量。默认值为16。增加并发请求数可以提高抓取效率,但也会增加对目标网站的压力,更容易触发反爬机制。您需要根据服务器的负载能力和自身的网络条件进行权衡。

代理(Proxies)设置:通过中间件实现代理轮换

当网站通过IP地址限制访问频率或进行地理位置限制时,使用代理IP是常见的反爬手段。Scrapy可以通过下载器中间件来集成代理。为了实现代理的随机轮换,您需要编写一个自定义的下载器中间件。
步骤1:在settings.py中启用代理中间件

# settings.py
 
DOWNLOADER_MIDDLEWARES = {
    'my_scraper.middlewares.ProxyMiddleware': 543, # 启用自定义代理中间件
    # 'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 100, # 如果使用Scrapy自带的代理中间件,可以启用此项
}
 
步骤2:在middlewares.py中编写代理中间件

# middlewares.py
 
import random
 
class ProxyMiddleware:
    def process_request(self, request, spider):
        # 假设您有一个代理列表
        proxies = [
            'http://user1:pass1@proxy1.com:8000',
            'http://user2:pass2@proxy2.com:8000',
            'https://user3:pass3@proxy3.com:8000',
            'socks5://user4:pass4@proxy4.com:8000',
        ]
        proxy = random.choice(proxies)
        request.meta['proxy'] = proxy
        spider.logger.debug(f"Using proxy: {proxy}")
        return None # 返回None表示Scrapy将继续处理该请求
process_request方法中,我们从一个代理列表中随机选择一个代理,并将其赋值给request.meta['proxy']。Scrapy的下载器会自动使用request.meta['proxy']中指定的代理发送请求。

User-Agent轮换

与代理轮换类似,User-Agent轮换也可以通过下载器中间件实现。这有助于模拟来自不同浏览器和操作系统的请求,进一步降低被识别为爬虫的风险。
步骤1:在settings.py中启用User-Agent中间件
python

# settings.py
 
DOWNLOADER_MIDDLEWARES = {
    'my_scraper.middlewares.RandomUserAgentMiddleware': 400, # 启用自定义User-Agent中间件
    # 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None, # 禁用Scrapy自带的User-Agent中间件
}

步骤2:在middlewares.py中编写User-Agent中间件

python
# middlewares.py
 
from fake_useragent import UserAgent # 需要安装:pip install fake-useragent
 
class RandomUserAgentMiddleware:
    def __init__(self, user_agent=''):
        self.user_agent = user_agent
        self.ua = UserAgent()
 
    @classmethod
    def from_crawler(cls, crawler):
        return cls(crawler.settings.get('USER_AGENT'))
 
    def process_request(self, request, spider):
        random_user_agent = self.ua.random
        if random_user_agent:
            request.headers.setdefault('User-Agent', random_user_agent)
            spider.logger.debug(f"Using User-Agent: {random_user_agent}")
这里我们使用了fake_useragent库来生成随机的User-Agent。在process_request方法中,我们为每个请求设置一个随机的User-Agent。

处理Cookie和Session

许多网站使用Cookie来维护用户会话状态。Scrapy默认会处理Cookie,但有时您可能需要更精细地控制Cookie。例如,您可以禁用Cookie,或者在请求中手动设置Cookie。
禁用Cookie:在settings.py中设置COOKIES_ENABLED = False
手动设置Cookie:在Request对象中通过cookies参数传递字典形式的Cookie。

应对验证码和JavaScript渲染页面

验证码(CAPTCHA): 验证码是常见的反爬机制。对于简单的验证码,可以尝试使用OCR(光学字符识别)技术进行识别。对于复杂的验证码(如滑动验证、点选验证),通常需要集成第三方打码平台或使用机器学习模型进行识别。Scrapy本身不提供验证码识别功能,需要结合外部库或服务。
JavaScript渲染页面: 现代网站大量使用JavaScript来动态加载内容,这意味着直接请求HTML可能无法获取到完整的数据。Scrapy本身是一个异步请求框架,不执行JavaScript。为了抓取JavaScript渲染的内容,通常有以下几种方法:
分析XHR请求:检查浏览器开发者工具中的网络请求,看是否有XHR(XMLHttpRequest)请求直接返回了所需的数据。如果有,可以直接模拟这些XHR请求。
集成无头浏览器:将Scrapy与无头浏览器(如Selenium、Playwright)或JavaScript渲染服务(如Splash)结合使用。这些工具可以在后台运行一个真实的浏览器,执行JavaScript并渲染页面,然后将渲染后的HTML返回给Scrapy进行解析。
Scrapy-Splash:Scrapy官方推荐的集成方案,Splash是一个轻量级的JavaScript渲染服务,可以通过HTTP API与Scrapy通信。它允许您发送请求到Splash,由Splash渲染页面并返回结果。
Scrapy-Selenium/Playwright:通过集成Selenium或Playwright,可以在Scrapy中控制真实的浏览器,实现更复杂的交互,如点击、填写表单、等待元素加载等。这通常通过自定义下载器中间件来实现。
应对反爬策略是一个持续的挑战,需要根据目标网站的具体情况灵活调整。Scrapy的模块化设计使得集成各种反爬工具和技术变得相对容易。在下一节中,我们将讨论Scrapy开发过程中可能遇到的常见问题和故障排除技巧。

Scrapy常见问题与故障排除

在使用Scrapy进行Web抓取时,开发者可能会遇到各种问题,从简单的配置错误到复杂的反爬机制。了解这些常见问题及其解决方案,将有助于您更高效地进行故障排除,确保爬虫的稳定运行。

1. 连接超时(TimeoutError)

当Scrapy尝试连接目标网站但长时间未收到响应时,会发生连接超时。这可能是由以下原因造成的:
网络问题:您的网络连接不稳定或目标网站服务器响应缓慢。
目标网站限制:网站可能对单个IP的连接数或请求频率进行了限制。
代理问题:如果您使用了代理,代理服务器可能不稳定或速度过慢。
解决方案:
增加下载超时时间:在settings.py中调整DOWNLOAD_TIMEOUT参数,例如设置为15秒:
降低并发请求数和增加下载延迟:在settings.py中降低CONCURRENT_REQUESTS,并增加DOWNLOAD_DELAY,以减轻对目标网站的压力。
更换高质量代理:如果使用代理,确保代理的稳定性和速度。
检查网络连接:确保您的本地网络连接正常。

2. IP被封禁(HTTP 403 Forbidden, 429 Too Many Requests等)

这是爬虫开发者最常遇到的问题之一。当您的IP地址被目标网站识别为爬虫并被阻止访问时,会收到HTTP 403或429等错误码。
解决方案:
使用代理IP:通过代理IP轮换来隐藏您的真实IP,并模拟来自不同地理位置的请求。结合代理中间件实现随机代理。
User-Agent轮换:使用不同的User-Agent来模拟不同的浏览器,降低被识别的风险。
增加下载延迟:放慢请求速度,模拟人类浏览行为。
设置随机下载延迟:在settings.py中设置RANDOMIZE_DOWNLOAD_DELAY = True,并结合DOWNLOAD_DELAY,使每次请求的延迟时间在一定范围内随机变化。
禁用Cookie:某些网站会通过Cookie追踪用户行为,尝试在settings.py中设置COOKIES_ENABLED = False
处理Referer:设置正确的Referer头,模拟从其他页面跳转过来的请求。
模拟登录/会话:对于需要登录才能访问的网站,需要模拟登录过程并维护会话。

3. 数据提取错误(Selector not found, empty data)

当爬虫无法正确提取到所需数据时,通常是由于选择器(XPath或CSS)编写错误或网页结构发生变化。
解决方案:
检查选择器:仔细检查您的XPath或CSS选择器是否正确。可以使用浏览器开发者工具(如Chrome的Elements面板)来测试选择器,确保它们能够准确地定位到目标元素。
检查网页结构变化:网站的HTML结构可能会不定期更新,导致您原有的选择器失效。定期检查目标网页的HTML结构,并相应地更新您的选择器。
使用Scrapy Shell调试:Scrapy Shell是一个交互式环境,允许您在不运行整个爬虫的情况下测试选择器。在命令行中运行scrapy shell <URL>,然后可以在Shell中测试response.xpath()response.css()

4. 编码问题(UnicodeDecodeError)

当处理网页内容时,如果编码不匹配,可能会出现UnicodeDecodeError。这通常发生在Scrapy尝试将字节流解码为字符串时。
解决方案:
指定响应编码:Scrapy通常会自动检测网页编码,但有时可能会出错。您可以在Spider的parse方法中,通过response.encoding属性来查看或强制指定编码:
检查HTTP响应头:网站通常会在HTTP响应头中指定Content-Type,其中包含charset信息。Scrapy会尝试使用这个信息来解码。如果网站返回的编码信息不正确,则需要手动指定。

5. 爬虫运行缓慢或内存占用过高

长时间运行的爬虫可能会遇到性能问题,例如运行缓慢或内存占用不断增加。
解决方案:
优化选择器:复杂的选择器会增加解析时间。尽量使用简洁高效的选择器。
减少不必要的请求:避免抓取不需要的页面或资源(如图片、CSS、JS文件)。可以通过ROBOTSTXT_OBEYDOWNLOAD_DELAYallowed_domains等设置来控制。
使用Item Loader:对于复杂的Item构建,使用Item Loader可以提高代码的可读性和维护性,但对性能影响不大。
优化Item Pipeline:如果Pipeline中包含耗时操作(如数据库写入),考虑使用异步操作或批量写入。
限制并发请求:适当降低CONCURRENT_REQUESTSCONCURRENT_REQUESTS_PER_DOMAIN
内存管理:对于抓取大量数据的爬虫,注意内存使用。Scrapy默认会缓存一些数据,可以通过settings.py中的HTTPCACHE_ENABLED等设置进行调整。
通过掌握这些故障排除技巧,您将能够更自信地应对Scrapy爬虫开发中的各种挑战,并构建出更加健壮和高效的Web抓取系统。

总结

Web抓取作为从互联网获取海量结构化数据的重要手段,在当今数据驱动的世界中扮演着越来越关键的角色。而Scrapy,作为Python生态系统中一款卓越的Web抓取框架,凭借其高效、灵活和可扩展的特性,成为了无数开发者构建强大爬虫的首选工具。
本教程从Scrapy的环境搭建、项目结构解析入手,详细介绍了如何编写第一个Scrapy爬虫,并深入探讨了数据提取的核心——XPath和CSS选择器。我们还学习了如何利用Item和Item Pipeline对抓取到的数据进行处理和存储,以及如何通过调整Scrapy的高级配置和运用各种反爬策略来应对网站的防御机制。最后,我们总结了Scrapy开发中常见的故障和相应的排除方法,旨在帮助读者在实践中少走弯路。
Scrapy的强大之处不仅在于其丰富的功能模块,更在于其高度模块化的设计理念,使得开发者可以根据具体需求灵活组合和扩展各个组件。无论是进行简单的数据收集,还是构建复杂的分布式爬虫系统,Scrapy都能提供坚实的基础和无限的可能性。
Web抓取的世界充满挑战,但同时也蕴藏着巨大的机遇。掌握Scrapy,您就拥有了一把开启互联网数据宝库的钥匙。我们鼓励您将所学知识付诸实践,从简单的项目开始,逐步挑战更复杂的抓取任务。在不断尝试和解决问题的过程中,您将不仅提升技术能力,更能深刻理解数据背后的价值。愿这篇终极指南能成为您Scrapy学习之旅的得力助手,助您在数据海洋中乘风破浪,收获丰硕!