常见实践
本节介绍了使用 Scrapy 时的常见实践。这些实践涵盖了许多主题,通常不属于其他任何特定部分。
从脚本运行 Scrapy
你可以使用 API 从脚本运行 Scrapy,而不是通过 scrapy crawl 这种典型方式运行 Scrapy。
请记住,Scrapy 是基于 Twisted 异步网络库构建的,因此你需要将其在 Twisted reactor 内部运行。
你可以用来运行爬虫的第一个实用工具是 scrapy.crawler.AsyncCrawlerProcess 或 scrapy.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.AsyncCrawlerRunner 或 scrapy.crawler.CrawlerRunner。这些类是轻量级的包装器,封装了一些简单的辅助功能来运行多个爬虫,但它们不会以任何方式启动或干扰现有 reactor。就像 scrapy.crawler.AsyncCrawlerProcess 和 scrapy.crawler.CrawlerProcess 一样,它们在异步 API 风格上有所不同。
使用这些类时,在调度完爬虫后应显式运行 reactor。如果你的应用程序已经在使用 Twisted 并且你想在同一个 reactor 中运行 Scrapy,建议你使用 AsyncCrawlerRunner 或 CrawlerRunner,而不是 AsyncCrawlerProcess 或 CrawlerProcess。
如果你想在爬虫完成之后立即停止 reactor 或运行任何其他代码,你可以在 AsyncCrawlerRunner.crawl() 返回的任务完成(或 CrawlerRunner.crawl() 返回的 Deferred 触发)后执行此操作。在最简单的情况下,你也可以使用 twisted.internet.task.react() 来启动和停止 reactor,尽管直接使用 AsyncCrawlerProcess 或 CrawlerProcess 可能会更容易。
这是一个结合 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)
另请参阅
以下是使用这些类并将 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()
将 AsyncCrawlerRunner 与 asyncio.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 不提供任何内置工具来以分布式(多服务器)方式运行爬取。但是,有一些方法可以分发爬取,具体取决于你计划如何分发它们。
如果你有许多爬虫,分发负载的明显方法是设置多个 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_VERSION和DOWNLOAD_TLS_MAX_VERSION设置,因为某些网站可能会根据客户端使用的 TLS 方法作出不同响应。
如果你仍然无法阻止机器人被封禁,请考虑联系商业支持。
静态分析
考虑使用 scrapy-lint,一个用于 Scrapy 项目的 linter,它可以检测常见错误和反模式。