常见实践

本节介绍了使用 Scrapy 时的常见实践。这些实践涵盖了许多主题,通常不属于其他任何特定部分。

从脚本运行 Scrapy

你可以使用 API 从脚本运行 Scrapy,而不是通过 scrapy crawl 这种典型方式运行 Scrapy。

请记住,Scrapy 是基于 Twisted 异步网络库构建的,因此你需要将其在 Twisted reactor 内部运行。

你可以用来运行爬虫的第一个实用工具是 scrapy.crawler.AsyncCrawlerProcessscrapy.crawler.CrawlerProcess。这些类会为你启动一个 Twisted reactor,配置日志并设置关闭处理程序。这些类是所有 Scrapy 命令使用的类。它们功能相似,但在异步 API 风格上有所不同:AsyncCrawlerProcess 从其异步方法返回协程,而 CrawlerProcess 返回 Deferred 对象。

这是一个展示如何用它运行单个爬虫的示例。

import scrapy
from scrapy.crawler import AsyncCrawlerProcess


class MySpider(scrapy.Spider):
    # Your spider definition
    ...


process = AsyncCrawlerProcess(
    settings={
        "FEEDS": {
            "items.json": {"format": "json"},
        },
    }
)

process.crawl(MySpider)
process.start()  # the script will block here until the crawling is finished

你可以在传递给 AsyncCrawlerProcess 的字典中定义设置。请务必查阅 AsyncCrawlerProcess 文档以熟悉其使用详情。

如果你处于 Scrapy 项目中,有一些额外的助手可以用来在该项目内导入这些组件。你可以通过将爬虫名称传递给 AsyncCrawlerProcess,并使用 scrapy.utils.project.get_project_settings() 来获取一个包含你的项目设置的 Settings 实例。

以下是一个可行的示例,说明如何做到这一点,并以 testspiders 项目为例。

from scrapy.crawler import AsyncCrawlerProcess
from scrapy.utils.project import get_project_settings

process = AsyncCrawlerProcess(get_project_settings())

# 'followall' is the name of one of the spiders of the project.
process.crawl("followall", domain="scrapy.org")
process.start()  # the script will block here until the crawling is finished

还有另一个 Scrapy 实用工具,它能提供对爬取过程的更多控制:scrapy.crawler.AsyncCrawlerRunnerscrapy.crawler.CrawlerRunner。这些类是轻量级的包装器,封装了一些简单的辅助功能来运行多个爬虫,但它们不会以任何方式启动或干扰现有 reactor。就像 scrapy.crawler.AsyncCrawlerProcessscrapy.crawler.CrawlerProcess 一样,它们在异步 API 风格上有所不同。

使用这些类时,在调度完爬虫后应显式运行 reactor。如果你的应用程序已经在使用 Twisted 并且你想在同一个 reactor 中运行 Scrapy,建议你使用 AsyncCrawlerRunnerCrawlerRunner,而不是 AsyncCrawlerProcessCrawlerProcess

如果你想在爬虫完成之后立即停止 reactor 或运行任何其他代码,你可以在 AsyncCrawlerRunner.crawl() 返回的任务完成(或 CrawlerRunner.crawl() 返回的 Deferred 触发)后执行此操作。在最简单的情况下,你也可以使用 twisted.internet.task.react() 来启动和停止 reactor,尽管直接使用 AsyncCrawlerProcessCrawlerProcess 可能会更容易。

这是一个结合 AsyncCrawlerRunner 和简单 reactor 管理代码的示例

import scrapy
from scrapy.crawler import AsyncCrawlerRunner
from scrapy.utils.defer import deferred_f_from_coro_f
from scrapy.utils.log import configure_logging
from scrapy.utils.reactor import install_reactor
from twisted.internet.task import react


class MySpider(scrapy.Spider):
    # Your spider definition
    ...


async def crawl(_):
    configure_logging({"LOG_FORMAT": "%(levelname)s: %(message)s"})
    runner = AsyncCrawlerRunner()
    await runner.crawl(MySpider)  # completes when the spider finishes


install_reactor("twisted.internet.asyncioreactor.AsyncioSelectorReactor")
react(deferred_f_from_coro_f(crawl))

相同示例,但使用 CrawlerRunner 和不同的 reactor(AsyncCrawlerRunner 仅适用于 AsyncioSelectorReactor

import scrapy
from scrapy.crawler import CrawlerRunner
from scrapy.utils.log import configure_logging
from scrapy.utils.reactor import install_reactor
from twisted.internet.task import react


class MySpider(scrapy.Spider):
    custom_settings = {
        "TWISTED_REACTOR": "twisted.internet.epollreactor.EPollReactor",
    }
    # Your spider definition
    ...


def crawl(_):
    configure_logging({"LOG_FORMAT": "%(levelname)s: %(message)s"})
    runner = CrawlerRunner()
    d = runner.crawl(MySpider)
    return d  # this Deferred fires when the spider finishes


install_reactor("twisted.internet.epollreactor.EPollReactor")
react(crawl)

另请参阅

Reactor 概述

以下是使用这些类并将 TWISTED_REACTOR_ENABLED 设置为 False 的示例。

AsyncCrawlerProcess 的简单用法

import scrapy
from scrapy.crawler import AsyncCrawlerProcess


class MySpider(scrapy.Spider):
    # Your spider definition
    ...


process = AsyncCrawlerProcess(
    settings={
        "TWISTED_REACTOR_ENABLED": False,
    }
)

process.crawl(MySpider)
process.start()  # the script will block here until the crawling is finished

TWISTED_REACTOR_ENABLED=False 时,你可以在同一进程中使用多个 AsyncCrawlerProcess 实例

import scrapy
from scrapy.crawler import AsyncCrawlerProcess


class MySpider(scrapy.Spider):
    # Your spider definition
    ...


process1 = AsyncCrawlerProcess(
    settings={
        "TWISTED_REACTOR_ENABLED": False,
    }
)
process1.crawl(MySpider)
process1.start()

process2 = AsyncCrawlerProcess(
    settings={
        "TWISTED_REACTOR_ENABLED": False,
    }
)
process2.crawl(MySpider)
process2.start()

AsyncCrawlerRunnerasyncio.run() 结合使用

import asyncio

import scrapy
from scrapy.crawler import AsyncCrawlerRunner
from scrapy.utils.log import configure_logging


class MySpider(scrapy.Spider):
    # Your spider definition
    ...


async def main():
    configure_logging({"LOG_FORMAT": "%(levelname)s: %(message)s"})
    runner = AsyncCrawlerRunner(settings={"TWISTED_REACTOR_ENABLED": False})
    await runner.crawl(MySpider)  # completes when the spider finishes


asyncio.run(main())

在同一进程中运行多个爬虫

默认情况下,当你运行 scrapy crawl 时,Scrapy 会在每个进程中运行一个爬虫。然而,Scrapy 支持使用内部 API 在每个进程中运行多个爬虫。

这是一个同时运行多个爬虫的示例

import scrapy
from scrapy.crawler import AsyncCrawlerProcess
from scrapy.utils.project import get_project_settings


class MySpider1(scrapy.Spider):
    # Your first spider definition
    ...


class MySpider2(scrapy.Spider):
    # Your second spider definition
    ...


settings = get_project_settings()
process = AsyncCrawlerProcess(settings)
process.crawl(MySpider1)
process.crawl(MySpider2)
process.start()  # the script will block here until all crawling jobs are finished

使用 AsyncCrawlerRunner 的相同示例

import scrapy
from scrapy.crawler import AsyncCrawlerRunner
from scrapy.utils.defer import deferred_f_from_coro_f
from scrapy.utils.log import configure_logging
from scrapy.utils.reactor import install_reactor
from twisted.internet.task import react


class MySpider1(scrapy.Spider):
    # Your first spider definition
    ...


class MySpider2(scrapy.Spider):
    # Your second spider definition
    ...


async def crawl(_):
    configure_logging({"LOG_FORMAT": "%(levelname)s: %(message)s"})
    runner = AsyncCrawlerRunner()
    runner.crawl(MySpider1)
    runner.crawl(MySpider2)
    await runner.join()  # completes when both spiders finish


install_reactor("twisted.internet.asyncioreactor.AsyncioSelectorReactor")
react(deferred_f_from_coro_f(crawl))

相同示例,但通过等待每个爬虫完成再启动下一个来顺序运行爬虫

import scrapy
from scrapy.crawler import AsyncCrawlerRunner
from scrapy.utils.defer import deferred_f_from_coro_f
from scrapy.utils.log import configure_logging
from scrapy.utils.reactor import install_reactor
from twisted.internet.task import react


class MySpider1(scrapy.Spider):
    # Your first spider definition
    ...


class MySpider2(scrapy.Spider):
    # Your second spider definition
    ...


async def crawl(_):
    configure_logging({"LOG_FORMAT": "%(levelname)s: %(message)s"})
    runner = AsyncCrawlerRunner()
    await runner.crawl(MySpider1)
    await runner.crawl(MySpider2)


install_reactor("twisted.internet.asyncioreactor.AsyncioSelectorReactor")
react(deferred_f_from_coro_f(crawl))

注意

在同一进程中运行多个爬虫时,日志设置reactor 设置对于每个爬虫不应有不同的值,并且预爬虫设置无法为每个爬虫单独定义。

另请参阅

从脚本运行 Scrapy.

分布式爬取

Scrapy 不提供任何内置工具来以分布式(多服务器)方式运行爬取。但是,有一些方法可以分发爬取,具体取决于你计划如何分发它们。

如果你有许多爬虫,分发负载的明显方法是设置多个 Scrapyd 实例,并将爬虫运行任务分配给这些实例。

如果你希望通过多台机器运行一个(大型)爬虫,通常的做法是划分要爬取的 URL 并将其发送给每个单独的爬虫。这是一个具体的示例

首先,你准备要爬取的 URL 列表,并将它们放入单独的文件/URL 中

http://somedomain.com/urls-to-crawl/spider1/part1.list
http://somedomain.com/urls-to-crawl/spider1/part2.list
http://somedomain.com/urls-to-crawl/spider1/part3.list

然后你在 3 个不同的 Scrapyd 服务器上启动一个爬虫运行。该爬虫将接收一个(爬虫)参数 part,其中包含要爬取的分区号

curl http://scrapy1.mycompany.com:6800/schedule.json -d project=myproject -d spider=spider1 -d part=1
curl http://scrapy2.mycompany.com:6800/schedule.json -d project=myproject -d spider=spider1 -d part=2
curl http://scrapy3.mycompany.com:6800/schedule.json -d project=myproject -d spider=spider1 -d part=3

减少大型项目的启动时间

当使用 scrapy crawl 运行爬虫时,Scrapy 会加载 SPIDER_MODULES 中列出的所有模块以查找目标爬虫。在拥有许多爬虫的大型项目中,这会显著增加启动时间和内存使用量。

为了避免加载每个爬虫模块,请在命令行中覆盖 SPIDER_MODULES,使其仅指向包含你要运行的爬虫的模块

scrapy crawl myspider -s SPIDER_MODULES=myproject.spiders.myspider

因为 SPIDER_MODULES 是一个列表设置,你可以通过逗号分隔来包含多个模块。

避免被封禁

一些网站会采取某些措施来阻止机器人爬取它们,其复杂程度各不相同。绕过这些措施可能很困难和棘手,有时可能需要特殊的 инфраструкure。如有疑问,请考虑联系商业支持

在处理这类网站时,请记住以下一些提示

  • 从浏览器中已知用户代理池中轮换使用(通过 Google 搜索获取列表)

  • 禁用 cookie(参见 COOKIES_ENABLED),因为某些网站可能会使用 cookie 来识别机器人行为

  • 使用下载延迟(2 或更高)。参见 DOWNLOAD_DELAY 设置。

  • 如果可能,使用 Common Crawl 来抓取页面,而不是直接访问网站

  • 使用轮换 IP 池。例如,免费的 Tor 项目或付费服务如 ProxyMesh。一个开源替代方案是 scrapoxy,这是一个你可以附加自己代理的超级代理。

  • 对于 HTTPS 网站,如果封禁似乎与 TLS 行为有关,请考虑调整 DOWNLOAD_TLS_MIN_VERSIONDOWNLOAD_TLS_MAX_VERSION 设置,因为某些网站可能会根据客户端使用的 TLS 方法作出不同响应。

  • 使用防封禁服务,例如 Zyte API,它提供了一个 Scrapy 插件和额外功能,例如 AI 网络爬取

如果你仍然无法阻止机器人被封禁,请考虑联系商业支持

静态分析

考虑使用 scrapy-lint,一个用于 Scrapy 项目的 linter,它可以检测常见错误和反模式。