Requests、Scrapy与Selenium在Python数据采集中的代码对比

月亮慢半拍
月亮慢半拍
发布时间:2026-11-06| 11人浏览过
原创

深入对比Requests、Scrapy与Selenium在Python数据采集中的核心代码实现、架构设计、执行流程与性能差异,通过同一基准任务的代码实操演示,帮助开发者厘清技术选型边界。

在Python数据采集任务中,Requests、Scrapy与Selenium分别代表了“协议层轻量同步请求”、“异步事件驱动工程框架”与“端到端浏览器仿真”三种完全不同的技术范式。选择哪一种工具,本质上取决于目标页面的动态渲染程度、数据采集的规模与并发需求,以及反爬防御的对抗边界。本文通过同一标准抓取任务的完整可运行代码,拆解三者的核心编写逻辑、运行机制与性能权衡。
Requests、Scrapy与Selenium在Python数据采集中的代码对比
图 0:Requests、Scrapy 与 Selenium 三大工具的核心定位与关键特性全貌对比

一、统一比对基准:多页图书信息采集任务

为了排除目标站点不同带来的代码干扰,我们设定一个标准的基准采集场景:爬取公开演练测试站点 http://books.toscrape.com/ 目录下的图书信息。任务要求遍历前2页列表,提取每本图书的书名(Title)、价格(Price)以及库存状态(Availability),并将结果以统一的数据字典结构产出。

该目标站点采用标准的服务端HTML渲染机制,结构稳定且分页明确。通过在同一业务需求下对照三种工具的实际实现,能够清晰展现各自的语法特征、组件分工和心智模型差异。

二、Requests:极简同步流与底层逻辑拼接

Requests是Python生态中最为广泛使用的HTTP客户端库。它的设计哲学是“为人类构建”,通过精炼的API隐藏了连接池建立、Cookie传递与重定向处理的复杂性。但需要注意:Requests自身仅负责网络传输,不具备任何HTML解析能力,必须与外部解析器(如 BeautifulSoup4 或 lxml)配合使用。

1. 完整采集代码示例

import re
import requests
from bs4 import BeautifulSoup

def scrape_with_requests(max_pages=2):
    base_url = "http://books.toscrape.com/catalogue/page-{}.html"
    results = []
    
    # 建立持久化会话对象,复用底层的 TCP 连接
    with requests.Session() as session:
        session.headers.update({
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
        })
        
        for page in range(1, max_pages + 1):
            url = base_url.format(page)
            try:
                # 阻塞式同步发起 HTTP GET 请求
                response = session.get(url, timeout=10)
                response.raise_for_status()
                response.encoding = response.apparent_encoding
            except requests.exceptions.RequestException as e:
                print(f"[Requests 错误] 请求第 {page} 页失败: {e}")
                continue

            # 使用 BeautifulSoup 将 HTML 文本解析为内存 DOM 树
            soup = BeautifulSoup(response.text, "html.parser")
            articles = soup.find_all("article", class_="product_pod")

            for article in articles:
                # 提取书名、价格及库存信息
                title_tag = article.find("h3").find("a")
                title = title_tag.get("title", "").strip() if title_tag else ""
                
                price_tag = article.find("p", class_="price_color")
                price = price_tag.text.strip() if price_tag else ""
                
                stock_tag = article.find("p", class_="instock availability")
                stock = re.sub(r"\s+", " ", stock_tag.text).strip() if stock_tag else ""

                results.append({
                    "title": title,
                    "price": price,
                    "stock": stock,
                    "source": "Requests"
                })
                
    return results

if __name__ == "__main__":
    items = scrape_with_requests()
    print(f"[Requests] 采集完成,共获取 {len(items)} 条记录。首条数据: {items[0] if items else None}")

2. 代码与运行特征评析

从代码中可以看出,Requests的控制流是纯线性的:循环构造URL -> 发起请求 -> 等待响应 -> 传入解析器 -> 提取字段 -> 放入列表。这种架构优势在于没有任何框架样板代码,调试时可以在任意行打断点或单步执行,异常捕获直截了当。但缺点同样明显:如果抓取1000个页面,单线程循环将处于漫长的网络I/O等待中;开发者必须自行手动编写多线程池(如 concurrent.futures)、失败重试循环以及持久化入库逻辑。

Requests、Scrapy与Selenium在Python数据采集中的代码对比
图 1:Requests、Scrapy 与 Selenium 的核心数据处理管道与生命周期阶段比对

三、Scrapy:工业级异步事件驱动框架

Scrapy并不是一个简单的库,而是一个完备的采集工程框架。它基于Twisted事件驱动引擎构建,将网络下载、请求去重、页面解析、数据管道清洗与入库彻底解耦。在Scrapy中,开发者编写的代码主要是声明式的解析规则,调度与并发完全交由框架内核管理。

1. 完整采集代码示例

import scrapy
from scrapy.crawler import CrawlerProcess

# 1. 声明数据模型
class BookItem(scrapy.Item):
    title = scrapy.Field()
    price = scrapy.Field()
    stock = scrapy.Field()
    source = scrapy.Field()

# 2. 声明爬虫逻辑
class BooksSpider(scrapy.Spider):
    name = "books_spider"
    allowed_domains = ["books.toscrape.com"]
    start_urls = ["http://books.toscrape.com/catalogue/page-1.html"]
    max_pages = 2
    current_page = 1

    def parse(self, response):
        # 原生使用 XPath 或 CSS 选择器高效提取节点
        for article in response.css("article.product_pod"):
            item = BookItem()
            item["title"] = article.css("h3 a::attr(title)").get("").strip()
            item["price"] = article.css("p.price_color::text").get("").strip()
            raw_stock = article.css("p.instock.availability::text").getall()
            item["stock"] = " ".join("".join(raw_stock).split())
            item["source"] = "Scrapy"
            yield item

        # 处理翻页逻辑
        self.current_page += 1
        if self.current_page <= self.max_pages:
            next_url = f"http://books.toscrape.com/catalogue/page-{self.current_page}.html"
            # 异步将新 URL yield 给引擎调度器
            yield response.follow(next_url, callback=self.parse)

# 3. 独立入口与框架配置运行
if __name__ == "__main__":
    collected_items = []

    # 自定义管道收集数据
    class MemoryPipeline:
        def process_item(self, item, spider):
            collected_items.append(dict(item))
            return item

    settings = {
        "LOG_LEVEL": "ERROR",
        "CONCURRENT_REQUESTS": 16,
        "DOWNLOAD_DELAY": 0.25,
        "ITEM_PIPELINES": {MemoryPipeline: 300},
        "USER_AGENT": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
    }

    process = CrawlerProcess(settings=settings)
    process.crawl(BooksSpider)
    process.start() # 启动 Twisted Reactor 循环

    print(f"[Scrapy] 采集完成,共获取 {len(collected_items)} 条记录。首条数据: {collected_items[0] if collected_items else None}")

2. 代码与运行特征评析

Scrapy的代码形态与Requests有根本不同:parse 方法是一个Python生成器,通过 yield item 交付数据,通过 yield response.follow() 交付后续抓取任务。开发者不需要写 for 循环去阻塞请求每一页,Twisted会在后台维持一个高效的非阻塞Socket连接池。此外,内置的选择器直接在C语言层级的 lxml 树上求值,内存利用率极高,天然适合数十万级页面的高吞吐量采集。

Requests、Scrapy与Selenium在Python数据采集中的代码对比
图 2:三类采集方案的代码组织形态与核心模块架构对照

四、Selenium:真实浏览器内核驱动与动态仿真

Selenium最初是为Web应用程序的端到端自动化测试而诞生的。它不直接向服务器发送裸HTTP报文,而是通过WebDriver接口向底层的无头浏览器(如 Google Chrome 或 Firefox)下发指令。浏览器会执行所有外部JavaScript代码、渲染CSS样式表、发起AJAX子请求,并构建完整的实时DOM树。

1. 完整采集代码示例

import re
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

def scrape_with_selenium(max_pages=2):
    chrome_options = Options()
    # 启用无头模式,避免弹出图形界面窗口
    chrome_options.add_argument("--headless=new")
    chrome_options.add_argument("--disable-gpu")
    chrome_options.add_argument("--no-sandbox")
    chrome_options.add_argument("--disable-dev-shm-usage")
    chrome_options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")

    driver = webdriver.Chrome(options=chrome_options)
    results = []

    try:
        base_url = "http://books.toscrape.com/catalogue/page-{}.html"
        
        for page in range(1, max_pages + 1):
            url = base_url.format(page)
            driver.get(url)

            # 显式等待:确保图书列表节点已经挂载到 DOM 中(至多等待 10 秒)
            wait = WebDriverWait(driver, 10)
            wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "article.product_pod")))

            articles = driver.find_elements(By.CSS_SELECTOR, "article.product_pod")
            for article in articles:
                # 定位当前节点下的子元素
                title_elem = article.find_element(By.CSS_SELECTOR, "h3 a")
                title = title_elem.get_attribute("title") or title_elem.text

                price_elem = article.find_element(By.CSS_SELECTOR, "p.price_color")
                price = price_elem.text.strip()

                stock_elem = article.find_element(By.CSS_SELECTOR, "p.instock.availability")
                stock = re.sub(r"\s+", " ", stock_elem.text).strip()

                results.append({
                    "title": title,
                    "price": price,
                    "stock": stock,
                    "source": "Selenium"
                })
                
    finally:
        # 必须显式关闭 driver 进程,防止僵尸 Chrome 进程耗尽内存
        driver.quit()

    return results

if __name__ == "__main__":
    items = scrape_with_selenium()
    print(f"[Selenium] 采集完成,共获取 {len(items)} 条记录。首条数据: {items[0] if items else None}")

2. 代码与运行特征评析

Selenium的代码围绕 WebDriver 实例展开。它的核心开销在于驱动真实的浏览器子进程:每一个页面跳转都涉及HTML解析、样式计算、JS脚本引擎执行及重绘过程。正因如此,代码中必须严谨使用 WebDriverWait 进行显式同步等待,否则极易发生 NoSuchElementException 或 StaleElementReferenceException 异常。在任务结束时,必须在 finally 块中调用 driver.quit() 释放进程资源。

Requests、Scrapy与Selenium在Python数据采集中的代码对比
图 3:单机部署环境下三种工具的内存基线与吞吐性能对照

五、多维度技术差异对照

为了直观评估三者在工程落地中的适配性,我们从编程接口、执行机制、资源占用与维护成本四个核心维度进行系统性对比:

比对维度 Requests Scrapy Selenium
本质定位 协议层同步 HTTP 传输客户端 分布式就绪的异步采集框架 自动化测试与浏览器驱动平台
JavaScript 执行 不支持(只能获取初始字节流) 原生不支持(需集成无头中间件) 原生完全支持(V8/Blink 引擎执行)
并发模型 单线程同步阻塞(需自建多线程) Twisted 事件循环非阻塞 I/O 依赖多浏览器进程池(开销巨大)
内存开销 (单节点) 极小(15MB ~ 30MB) 低至中等(50MB ~ 100MB) 极高(每个标签页/实例 300MB+)
网络吞吐性能 中等(受制于网络延迟往返) 极高(数千请求/分钟级别) 较低(每分钟数十页为常见上限)
代码与调试成本 极低,单脚本即写即测 需遵循项目规范与组件声明 中等,需处理环境驱动与元素等待
反爬防御对抗 易被基于指纹、TLS及JS的屏障拦截 支持自定义中间件伪装请求特征 具备真实浏览器指纹,天然克制JS混淆
Requests、Scrapy与Selenium在Python数据采集中的代码对比
图 4:基于目标站点特征与业务规模的采集工具选型决策路径

六、工程实践中的选型原则与混合架构建议

在实际生产研发中,切忌盲目依赖单一工具。合理的选型应当建立在充分分析目标站协议特征的基础之上:

原则一:优先逆向内部 API,慎用浏览器仿真
现代单页应用(SPA)虽然在前端使用复杂的 Vue 或 React 框架渲染,但其底层数据绝大多数来自干净的 RESTful JSON 接口。在启动 Selenium 之前,首先应打开浏览器的开发者工具(F12),切换至 Network 标签页观察 XHR/Fetch 请求。如果能直接捕获其接口规律并构造请求头,使用 Requests 或 Scrapy 直接读取 JSON 数据的效率比无头浏览器高出数十倍。
原则二:轻量脚本用 Requests,集群采集上 Scrapy
如果只是临时采集几百个网页或单次分析,编写一个包含几十行代码的 Requests 脚本是最快交付的选择。而一旦目标页面上升至数十万、几百万量级,需要自动去重、失败重试、自动限速防封、断点续爬以及直接对接 MySQL/MongoDB 时,Scrapy 提供的成熟工程脚手架能够大幅减少系统维护的复杂度。
原则三:混合架构(Scrapy + Headless Middleware)应对复杂站点
当大型采集任务中仅有 5% 的关键页面(如滑块验证码页面、动态签名计算逻辑)需要执行 JavaScript 时,不要将整个项目切换为纯 Selenium。最佳实践是将 Selenium(或现代的 Playwright)作为 Downloader Middleware 嵌入 Scrapy 框架:大部分静态页面由 Scrapy 高速下载,仅对特定 URL 交由无头浏览器中间件拦截渲染,从而实现吞吐效率与渲染能力的最佳平衡。

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。