返回博客
指南
Mihai MaximLast updated on May 13, 20263 min read

使用 Scrapy 进行网络抓取:2026 Playbook

使用 Scrapy 进行网络抓取:2026 Playbook
简而言之:这是一份关于2026年使用Scrapy进行网页抓取的、观点鲜明的端到端指南。你将安装Scrapy,在终端中编写选择器原型,构建一个多页面的电商爬虫,使用Item Loaders清理数据项,将数据持久化到数据库,强化配置以防范封禁,并为JavaScript渲染的页面集成Scrapy-Playwright。

十多年来,Scrapy 一直是专业 Python 爬虫的核心支柱,尽管涌现出一波波新的异步库,它依然稳居其位。如果你今天正在使用 Scrapy 进行网页抓取,你会得到一个立场鲜明的框架,它解决了那些枯燥的部分(请求调度、去重、重试、Item 管道),让你能够专注于真正容易出错的部分:选择器、反机器人和数据存储。

本指南围绕请求与响应的生命周期展开,而非按时间顺序逐步构建。每个章节都对应你在实际生产环境中会接触到的 Scrapy 组件,从引擎和下载器中间件,到 Item Loader 以及数据源导出。我们全程使用同一个目标站点——公共练习站点 books.toscrape.com,因此每个代码块都能融入统一的思维模型。

完成学习后,您将拥有一个可运行的蜘蛛程序:它能对商品目录进行分页,验证并清理项目,同时支持写入 JSON Lines 和 SQLite,在 429 ,并在页面需要 JavaScript 时回退到真实浏览器。我们还将标出框架中新手常误用的部分,并提供可直接复用的修复方案。

为何 Scrapy 在 2026 年仍主导生产环境爬取

虽然人们往往会倾向于使用 httpx 加上 selectolax ,然后就此作罢。 对于一次性脚本,这确实是正确的选择。但对于需要夜间运行、去除重复 URL、应对部分故障并写入两个目标的爬虫,你需要一个框架。截至本文撰写之时,Scrapy 仍是大规模数据提取的行业标准,原因很简单:它自带调度器、重复过滤器、重试中间件、限流、信号和数据流导出功能,且这些组件已预先集成。

与拼凑 requestsBeautifulSoup,Scrapy以一种实用且有主见的方式提供了解决方案。它运行在Twisted的事件循环之上,因此单个进程可以扇出式地处理数百个并发请求,而无需承担 async/await。您无需编写爬取循环,只需声明入口 URL 和解析逻辑,引擎便会自动处理队列。正是这种设计契约,使得 Scrapy 值得您克服其较陡峭的学习曲线。

Scrapy 网页抓取的工作原理:请求与响应的生命周期

在编写爬虫之前,请先理解其生命周期。Scrapy 的运行过程如下:

  1. 引擎Request 任务
  2. 请求依次经过下载器中间件(按优先级顺序)。在此阶段,将设置请求头、附加 Cookie、轮换代理并触发重试。
  3. 下载器发出 HTTP 请求并返回一个 Response.
  4. 响应在返回时会先经过下载器中间件,然后经过蜘蛛中间件,最后进入蜘蛛的回调函数(通常是 parse).
  5. 您的回调 yield会返回更多 Request 对象(这些对象将返回调度器)或 Item(这些将流入项目管道)。
  6. 管道会对每个项目进行验证、转换、丢弃或持久化。
  7. 任何幸存下来的项目都会被传递给 feed 导出器,该导出器会将其写入磁盘、S3 或 stdout。

回调中会出现两个术语:callback 是请求成功时 Scrapy 运行的函数,errback 是请求失败时运行的函数。爬虫通常以 Python 生成器形式编写,延迟生成请求和项目,以便引擎能够交错处理工作。

了解这个循环是区分“我的蜘蛛能运行”和“我的蜘蛛能扩展”的关键。当页面返回空结果时,问题几乎总出在下载器中间件层;当项目消失时,问题出在管道中;当分页功能失效时,问题则出在回调函数上。将症状与相应阶段对应起来,然后修复正确的组件。

更详细的指南详见官方 Scrapy 架构文档,建议将其加入书签。

安装 Scrapy 并初始化项目

Scrapy 支持现代 Python 3(请查阅官方安装指南,了解安装时的最低版本要求)。文档强烈建议使用专用的虚拟环境,以避免 Scrapy 的固定依赖项与系统包发生冲突。

python -m venv .venv
source .venv/bin/activate     # Windows: .venv\Scripts\activate
pip install --upgrade pip
pip install scrapy
scrapy version

一旦 scrapy version 打印出版本字符串后,即可搭建项目:

scrapy startproject bookstore
cd bookstore

现在您已拥有一个项目结构树,其样式与地球上所有 Scrapy 代码库完全一致,这正是设计初衷。每当您加入新的 Scrapy 代码库时,您已清楚知道爬虫位于何处、配置文件存放于何处,以及哪个文件管理管道。这种可重复性正是使用框架的核心价值所在。请务必抵制简化结构的冲动:下游工具如 scrapydscrapy crawl 等下游工具都依赖于此。

Scrapy 项目内部:每个文件的作用

scrapy startproject 会生成五个文件和一个文件夹,你每天都会用到它们。

  • scrapy.cfg 是顶级项目配置文件。它为项目命名,并告知 scrapyd 设置模块的位置。
  • items.py 是模式层。您在此定义 Product, Article或任何你需要的类,每个类都继承自 scrapy.Item。请将其视为用于处理抓取结果的数据类。
  • pipelines.py 是提取项进行清理、验证、丢弃或写入数据库的地方。每个管道都是一个带有 process_item 方法。
  • middlewares.py 包含下载器和爬虫中间件。此文件用于轮换用户代理、附加代理,或通过受管抓取 API 路由请求。
  • settings.py 是核心配置对象:并发、限流、重试、管道、中间件以及数据源导出均在此配置。
  • spiders/ 是存放各个蜘蛛文件的文件夹。针对每个目标网站创建一个蜘蛛文件是一个合理的默认做法。

在 Scrapy Shell 中原型设计选择器

Scrapy 命令行是鲜少有人提及的秘密武器。在编写任何蜘蛛代码之前,请先在命令行中打开一个真实的 URL,并交互式地测试选择器。这能节省数小时的时间。

scrapy shell "https://books.toscrape.com/catalogue/page-1.html"

在 Shell 中,你会获得一个随页面预加载的实时 response 对象,其中已预加载了页面内容。有三个关键命令:

  • fetch("https://example.com") 可在不离开控制台的情况下切换新的响应。
  • view(response) 在默认浏览器中打开下载的 HTML,通过这种方式,你可以确认自己正在处理的是蜘蛛所看到的 DOM,而不是浏览器通常显示的渲染结果。
  • response.css(...) 以及 response.xpath(...) 允许你针对实时响应测试选择器。

请在练习站点上尝试以下操作:

>>> response.css("article.product_pod h3 a::attr(title)").getall()[:3]
['A Light in the Attic', 'Tipping the Velvet', 'Soumission']
>>> response.xpath("//article[@class='product_pod']//p[@class='price_color']/text()").get()
'£51.77'

反复测试直至两个选择器均返回有效数据。只有此时才将表达式移入你的蜘蛛程序中。在5分钟的爬取过程中调试一个失效的XPath所付出的代价,远高于一次终端会话的成本。

使用 Scrapy 编写您的第一个网络爬虫

针对目标域名生成一个蜘蛛程序框架:

scrapy genspider books books.toscrape.com

这将生成 spiders/books.py文件。将其内容替换为下面的蜘蛛代码。该代码会抓取商品目录首页,提取每本书的书名、价格和评分,然后为每本书生成一个 Python 字典。我们将在后续章节中将其升级为真正的 Items 对象。

import scrapy

class BooksSpider(scrapy.Spider):
    name = "books"
    allowed_domains = ["books.toscrape.com"]
    start_urls = ["https://books.toscrape.com/catalogue/page-1.html"]

    def parse(self, response):
        for card in response.css("article.product_pod"):
            yield {
                "title": card.css("h3 a::attr(title)").get(),
                "price": card.css("p.price_color::text").get(),
                "rating": card.css("p.star-rating::attr(class)").get(),
                "url": response.urljoin(card.css("h3 a::attr(href)").get()),
            }

在项目根目录下运行:

scrapy crawl books -o books.jsonl

您应能看到 Scrapy 记录对第 1 页的请求,刮取了 20 个项目,随后正常关闭。打开 books.jsonl 并确认每行对应一个 JSON 对象。

有几点需要注意。 start_urls 是入口点,引擎会自动调度每个 URL。 parse 是默认回调函数。 response.urljoin 会根据当前页面解析一个相对 href ,以避免出现断链。 rating 字段仍包含诸如 "star-rating Three",这正是 Item Loaders 稍后将处理的清理类型。

生产环境提示:使用 -o 参数运行仅适用于快速测试,切勿在定时任务中依赖此设置。请配置 FEEDS 设置, settings.py 中配置,以便将输出目标、格式和覆盖行为纳入版本控制。我们将在持久化部分将其与数据库管道结合使用。请将 CLI 标志视为开发捷径,而非部署产物。

CSS 与 XPath:选择不会导致程序崩溃的选择器

Scrapy 默认包含这两种选择器引擎,且二者均基于同一解析树运行。请根据具体任务选择更简洁清晰的那种。经验法则是:基于类和结构的查询更适合使用 CSS,而当需要按文本内容、同级元素或祖先元素遍历树时,XPath 则更具优势。

# CSS: short, idiomatic, fast to write
response.css("article.product_pod p.price_color::text").get()

# XPath equivalent
response.xpath("//article[@class='product_pod']//p[@class='price_color']/text()").get()

当 CSS 无法表达您的需求时,XPath 就派上用场了:

# "Find the <td> that follows the <th> whose text is 'Stock'"
response.xpath("//th[normalize-space()='Stock']/following-sibling::td/text()").get()

# "Find all links whose visible text contains 'Next'"
response.xpath("//a[contains(., 'Next')]/@href").getall()

保持选择器稳定的几个习惯:优先使用属性选择器而非易出错的位置选择器(nth-child(3) 最终会失效),比较文本时规范化空格(normalize-space()),以及将 .get().getall() 来生成列表,切勿盲目对 .getall() 。若想深入了解何时选择哪种选择器更合适,我们的《XPath 与 CSS 选择器对比指南》是极佳的参考读物。

生产环境提示:当选择器返回 None ,但在命令行中却能正常工作,这很可能是页面采用了 JavaScript 渲染。请先通过 view(response) 进行确认,再归咎于选择器。

项与项加载器:可复用的清理模式

对于十行代码来说,返回普通字典是可以的。但在大规模场景下,你需要一个类型化的模式,这样字段名的拼写错误就能快速报错,而不是默默生成垃圾行。在 items.py:

import scrapy
from itemloaders.processors import MapCompose, TakeFirst, Join

def to_float(value):
    return float(value.replace("£", "").replace("$", "").strip())

def normalize_rating(value):
    # "star-rating Three" -> "Three"
    parts = value.split()
    return parts[1] if len(parts) > 1 else value

class ProductItem(scrapy.Item):
    title = scrapy.Field(input_processor=MapCompose(str.strip), output_processor=TakeFirst())
    price = scrapy.Field(input_processor=MapCompose(str.strip, to_float), output_processor=TakeFirst())
    rating = scrapy.Field(input_processor=MapCompose(normalize_rating), output_processor=TakeFirst())
    description = scrapy.Field(input_processor=MapCompose(str.strip), output_processor=Join(" "))

MapCompose 链式转换器中, TakeFirst 将匹配列表压缩为单一值, Join 将多个段落合并为一个。在爬虫中使用加载器,以保持爬虫代码的可读性:

from scrapy.loader import ItemLoader
from bookstore.items import ProductItem

def parse(self, response):
    for card in response.css("article.product_pod"):
        loader = ItemLoader(item=ProductItem(), selector=card)
        loader.add_css("title", "h3 a::attr(title)")
        loader.add_css("price", "p.price_color::text")
        loader.add_css("rating", "p.star-rating::attr(class)")
        yield loader.load_item()

其优势在于可复用性。一旦 to_float 驻留在 items.py后,每个爬虫中的所有带价格的元素都能调用它。清理逻辑不再需要在回调函数之间复制粘贴。

跟随链接:手动分页 vs CrawlSpider

在 Scrapy 中爬取多页内容有两种惯用方法。请根据链接结构的可预测性进行选择。

当仅需跟随单个“下一页”链接时,手动分页是正确的选择。在 parse:

next_page = response.css("li.next a::attr(href)").get()
if next_page:
    yield response.follow(next_page, callback=self.parse)

response.follow 可处理相对 URL 并复用同一回调,这正是目录式分页所需的。当最后一个页面上的“下一页”链接消失时,爬取会自然停止。

若需通过匹配 URL 模式遍历整个网站,CrawlSpider 是更合适的选择。它使用 RuleLinkExtractor 来自动发现并跟随链接:

from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor

class BooksCrawl(CrawlSpider):
    name = "books_crawl"
    allowed_domains = ["books.toscrape.com"]
    start_urls = ["https://books.toscrape.com/"]
    rules = (
        Rule(LinkExtractor(restrict_css=".pager a")),  # follow pagination
        Rule(LinkExtractor(restrict_css="h3 a"), callback="parse_book"),
    )

    def parse_book(self, response):
        yield {
            "title": response.css("h1::text").get(),
            "price": response.css("p.price_color::text").get(),
        }

Scrapy 的内置 RFPDupeFilter 确保同一 URL 不会被重复加入队列,因此您无需自行追踪已访问的链接。将 DEPTH_LIMITsettings.py 当您爬取结构深层网站且希望强制停止时。

制作说明:对于支持站点地图的网站, SitemapSpider 操作更为简便。它会直接读取 /sitemap.xml ,并允许您使用 sitemap_rules.

结果持久化:FEEDS 与数据库管道

使用 Scrapy 进行网页抓取时,系统提供两层数据持久化方案,通常建议同时使用。FEEDS 设置可免费处理结构化导出,而管道则负责管理关系型数据库等自定义目标。

settings.py。请查阅 Scrapy feed 导出文档了解当前语法,但现代配置大致如下:

FEEDS = {
    "data/books.jsonl": {
        "format": "jsonlines",
        "encoding": "utf-8",
        "overwrite": True,
    },
    "data/books.csv.gz": {
        "format": "csv",
        "postprocessing": ["scrapy.extensions.postprocessing.GzipPlugin"],
    },
}

JSON Lines 是理想的默认选项:支持流式处理、便于追加数据,且易于导入 Pandas 或数据仓库。带 gzip 压缩的 CSV 则适合交付给分析师。这两种格式均无法处理关系型查询,此时便需要管道来解决。

一个在验证器之后运行的 SQLite 管道:

# pipelines.py
import sqlite3
from itemadapter import ItemAdapter

class SqlitePipeline:
    def open_spider(self, spider):
        self.conn = sqlite3.connect("data/books.db")
        self.conn.execute(
            "CREATE TABLE IF NOT EXISTS products (title TEXT, price REAL, rating TEXT)"
        )

    def close_spider(self, spider):
        self.conn.commit()
        self.conn.close()

    def process_item(self, item, spider):
        a = ItemAdapter(item)
        self.conn.execute(
            "INSERT INTO products(title, price, rating) VALUES (?, ?, ?)",
            (a["title"], a["price"], a["rating"]),
        )
        return item

为其注册优先级。数值越低执行越早,因此优先级为 100 的验证器会在优先级为 200 的数据库写入器之前触发:

ITEM_PIPELINES = {
    "bookstore.pipelines.PriceRangeValidator": 100,
    "bookstore.pipelines.SqlitePipeline": 200,
}

现在,无效的价格会在触及数据库之前就被丢弃。

强化 settings.py:AutoThrottle、重试和缓存

默认设置在开发环境中可行,但在生产环境中会导致被封禁。以下几项是最关键的配置。请根据您安装的 Scrapy 版本核对确切的默认值。

# settings.py
ROBOTSTXT_OBEY = True            # respect the site's policy unless you have a contract
CONCURRENT_REQUESTS = 8          # global cap; lower for fragile sites
CONCURRENT_REQUESTS_PER_DOMAIN = 4
DOWNLOAD_DELAY = 0.5             # base delay; AutoThrottle adjusts dynamically

AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_TARGET_CONCURRENCY = 2.0
AUTOTHROTTLE_START_DELAY = 1.0
AUTOTHROTTLE_MAX_DELAY = 30.0

RETRY_ENABLED = True
RETRY_TIMES = 5
RETRY_HTTP_CODES = [429, 500, 502, 503, 504, 408, 522, 524]

HTTPCACHE_ENABLED = True         # huge time-saver during development
HTTPCACHE_EXPIRATION_SECS = 3600
HTTPCACHE_IGNORE_HTTP_CODES = [429, 500, 502, 503, 504]

AutoThrottle 是此处的杀手级功能。它不再需要猜测 DOWNLOAD_DELAY,而是直接设定目标并发数,当延迟上升时 Scrapy 会自动降速。仅此一项就能防止在低速网站上发生大多数意外的 DDoS 情况。

HTTPCACHE_ENABLED 是一项提升开发体验的设置:在遍历选择器时,系统会从磁盘读取已处理过的请求,从而避免对目标网站进行重复请求。请在生产环境中将其关闭。

若要应对真正的反机器人压力,仅靠配置是不够的,我们的《爬虫为何被封锁》指南深入解析了背后的机制。无论如何,下一层是中间件。

Downloader 中间件:头部信息、代理和托管 API

当网站开始返回 403,解决方案几乎总是在下载器中间件中。其核心结构很简单:

# middlewares.py
import random

class RandomUserAgentMiddleware:
    UAS = [
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...",
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_4) AppleWebKit/605.1.15 ...",
    ]
    def process_request(self, request, spider):
        request.headers["User-Agent"] = random.choice(self.UAS)

settings.py中。Scrapy 自带预置的默认中间件栈(开箱即用时已启用十余个),中间件优先级通常以文档中规定的整数范围表示。社区建议将自定义反机器人中间件置于内置的 RetryMiddleware之前,后者的默认优先级为 550,这样重试时就能使用轮换后的身份。

DOWNLOADER_MIDDLEWARES = {
    "bookstore.middlewares.RandomUserAgentMiddleware": 400,
    "scrapy.downloadermiddlewares.useragent.UserAgentMiddleware": None,  # disable default
}

对于代理轮换,请设置 request.meta["proxy"]process_request中。社区中既有用于代理轮换的插件,也有用于随机化用户代理的插件(以及用于分布式爬取、持久缓存和监控的插件),但在使用 Scrapy 进行大规模网页抓取并将其投入生产环境之前,请务必检查每个项目的当前维护状态。

坦诚的权衡:到了某个阶段,自行处理头部信息、住宅IP和验证码识别会变成一个副业。这就是托管式Scraper API能完美接入的地方。只需实现一个中间件,将 request.url 以指向 API 端点,并将您的 API 密钥作为头部字段添加,这样您的爬虫其余部分无需任何改动。

Scrapy-Playwright:JavaScript 的逃生通道

Scrapy 本身无法执行 JavaScript,因此使用 Angular、React 或任何客户端框架构建的网站返回的只是 HTML 骨架,而非你在浏览器中看到的数据。在 2026 年,针对动态页面使用 Scrapy 进行网络爬取的最干净解决方案是 scrapy-playwright,当你按请求启用该功能时,它会将默认下载器替换为真正的无头 Chromium。

安装时请参照 scrapy-playwright 的 README 文件,验证当前的处理程序注册语法:

# settings.py
DOWNLOAD_HANDLERS = {
    "http": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
    "https": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
}
TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"

通过设置 meta:

def start_requests(self):
    yield scrapy.Request(
        "https://example-spa.com/products",
        meta={
            "playwright": True,
            "playwright_page_methods": [
                ("wait_for_selector", "article.product"),
            ],
        },
    )

仅对真正需要浏览器的 URL 进行标记。每个 Playwright 请求在 CPU 消耗和延迟方面都比普通的 Scrapy 抓取请求昂贵得多,因此混合式爬虫(列表页面使用 HTML,产品详情页使用 Playwright)通常是最佳方案。如果您希望获得更深入的指导或与旧版 Splash 后端的对比,我们的 Scrapy-Playwright 教程详细介绍了这些模式。

日志记录、契约与部署

使用 Scrapy 进行生产级网页抓取需要三个教程通常忽略的要素。

日志记录。设置 LOG_LEVEL = "INFO" in settings.py 用于常规运行, "DEBUG" 仅在出现异常时启用。使用 LOG_FILE 或将其流式传输到结构化的后端。

爬虫契约。在回调函数中添加文档字符串契约,并在 scrapy check 在 CI 中运行。典型的契约会锁定 URL、预期字段和最小项目数量,因此网站的无声变更会导致构建失败,而非数据集。

def parse(self, response):
    """
    @url https://books.toscrape.com/
    @returns items 20 20
    @scrapes title price rating
    """

计划与部署。 scrapyd 将您的项目作为长期运行的守护进程运行,您可以通过 scrapyd-client。对于基于容器的架构,请构建一个包含您项目的精简 Docker 镜像,并 scrapy crawl 在 cron 计划(或 Kubernetes CronJob)中运行。无论哪种方式,请将输出持久化到持久化存储中,而非容器文件系统。

常见陷阱及调试方法

  • 空选择器。选择器在 shell 中有效,但在 None 。几乎总是由 JavaScript 渲染的。请使用 view(response) 并切换至 scrapy-playwright
  • 403429 storms。您的指纹显而易见。添加一个随机 User-Agent 中间件,降低 CONCURRENT_REQUESTS_PER_DOMAIN,提高 AUTOTHROTTLE_START_DELAY,并确认 RETRY_HTTP_CODES 包含 429。
  • 无限分页循环。 "next" 选择器在最后一页也会匹配。将其锚定在最后会消失的 CSS 类上,或设置 DEPTH_LIMIT.
  • 项被静默丢弃。管道抛出 DropItem ,而你却毫无察觉。更新 LOG_LEVELDEBUG,在日志中搜索 Dropped:,并验证你的范围检查。
  • 重复的 URL 悄然混入。 RFPDupeFilter 通过指纹进行匹配,因此仅查询字符串顺序不同的 URL 可能会混入其中。在处理请求前请规范化 URL。

关键要点

  • 当您需要开箱即用的调度、去重、重试、限流和管道集成时,使用 Scrapy 进行网络爬取才物有所值;而如果仅需 20 行脚本即可完成,则无需使用 Scrapy。
  • 将每个异常症状映射到生命周期阶段:阻塞问题源于下载器中间件,缺失项源于管道,而选择器匹配失败通常指向 JavaScript 渲染。
  • 使用 MapCompose, TakeFirst,以及 Join ,可让清理逻辑在不同爬虫间复用,而非在回调中重复复制粘贴。
  • 使用 FEEDS 用于可移植格式,并使用自定义管道进行关系型存储。两者结合使用,通过管道优先级将验证操作排在数据库写入器之前。
  • AutoThrottle、重试代码以及托管抓取 API 视为抵御封禁的分层防御机制。仅当 scrapy-playwright 仅当 HTML 内容确实为空时才使用。

常见问题

与较新的异步库相比,2026年学习Scrapy还有价值吗?

是的,对于超过几百页的爬取任务。像 httpx plus selectolax 等非常适合一次性脚本,但 Scrapy 集成了调度器、重复项过滤器、重试中间件、信号机制以及数据流导出功能,这些功能你原本需要自己编写。对于需要长期运行的生产环境爬虫,这种“开箱即用”的设计在维护成本上依然更具优势。

Scrapy 能否独立抓取 JavaScript 渲染的页面,还是需要 Playwright 或 Splash?

仅靠 Scrapy 本身无法做到。Scrapy 获取的是原始 HTML 且不执行 JavaScript,因此单页应用会返回空壳标记。当前的最佳方案是 scrapy-playwright,它会为每个请求将下载器替换为真正的无头 Chromium 浏览器。 scrapy-splash 对某些团队而言仍可行,但 Playwright 具有更广泛的浏览器支持且维护活跃。

对于不同规模的项目,Scrapy 与 Beautiful Soup 及 Selenium 相比如何?

Beautiful Soup 是一个解析器,而非爬虫,它与 requests 配合使用,适用于小型静态抓取任务。Selenium驱动完整浏览器,最适合处理具有状态的交互式流程,例如登录后的仪表盘。Scrapy则介于两者之间:它是一个可处理数百至数百万页面的高吞吐量爬取框架,并在需要时通过 scrapy-playwright 在需要时附加浏览器渲染功能。

如何将 Scrapy 蜘蛛部署到生产环境中按计划运行?

有三种常见模式。将 scrapyd 作为守护进程,并通过其 HTTP API 触发任务。构建包含项目的 Docker 镜像,并安排 scrapy crawl <name> 通过 cron 或 Kubernetes CronJob 进行调度。或者使用托管式抓取平台,由其为您托管蜘蛛。无论哪种情况,请将输出持久化到 S3 或数据库等持久化存储中,切勿存储在容器文件系统中。

如何防止 Scrapy 蜘蛛被封锁或 IP 被封禁?

分层部署防护措施。启用 AutoThrottle、通过下载器中间件随机化 User-Agent ,通过下载器中间件随机化 429RETRY_HTTP_CODES,并降低 CONCURRENT_REQUESTS_PER_DOMAIN。对于安全性更高的网站,请通过住宅代理或托管式爬虫 API 路由请求,该 API 可在单一端点后处理代理轮换和验证码破解。在可能的情况下,请遵守 robots.txt 速率限制。

总结

使用 Scrapy 进行网页抓取的意义并不在于你编写的代码比 requests 加上 BeautifulSoup相比写得更少。通常第一天你写的代码反而更多。关键在于,你在第一天编写的代码在第90天依然有效,因为底层的引擎、调度器、重复过滤器、限流、重试层和管道契约始终保持不变。你为自己构建了一个稳定的基础架构,随后再针对每个目标网站对爬虫、数据项和中间件进行定制。

若要从本指南中领悟一个核心概念,那就是请求与响应的生命周期。你遇到的每一个 Scrapy 错误都发生在该循环的特定阶段,而确定具体阶段就等于解决了问题的一半。选择器在回调中失败,Item 在管道中消失,封禁发生在下载器中,分页在回调逻辑中无限循环。只要将症状与生命周期阶段对应起来,解决方案便显而易见。

当反机器人压力超出您在 middlewares.py,那就是将请求层卸载出去的恰当时机。在 WebScrapingAPI,我们构建 Scraper API 正是为了实现这种交接:保留你的 Scrapy 蜘蛛、你的 Items 和你的管道,让一个托管端点来处理代理、验证码破解和 JavaScript 渲染。你的蜘蛛依然是 Scrapy。那些棘手的问题则由他人来解决。

关于作者
Mihai Maxim, 全栈开发工程师 @ WebScrapingAPI
Mihai Maxim全栈开发工程师

米海·马克西姆(Mihai Maxim)是 WebScrapingAPI 的全栈开发工程师,他在产品各领域均有贡献,并协助为该平台构建可靠的工具和功能。

开始构建

准备好扩展您的数据收集规模了吗?

加入2,000多家企业,使用WebScrapingAPI在无需任何基础设施开销的情况下,以企业级规模提取网络数据。