Scrapy框架实战教程(上):从入门到实战,搭建你的第一个专业爬虫

其实我也用过不少爬虫方案,从最基础的requests+BeautifulSoup,到后来尝试的各种框架。但说实话,当你需要处理十万级、百万级数据的时候,Scrapy确实无可替代。

今天想用这篇教程,带你从零开始搭建一个专业的爬虫系统。我会用真实的电商数据采集案例,展示Scrapy的完整开发流程。

这是上篇,适合刚接触Scrapy的开发者。如果你已经有基础,可以直接看[下篇],那里有中间件、分布式、性能优化等高级内容。

为什么是Scrapy?

在开始之前,我想先聊聊为什么我最终选择了Scrapy,而不是其他方案。

我踩过的坑

最早我用requests+BeautifulSoup爬数据的时候,遇到过一个很典型的问题:爬取某电商平台1000个商品信息,需要40多分钟。为什么呢?因为它是同步请求,一个请求发完等响应回来再发下一个。

后来试了多线程,又遇到了新问题:线程管理太复杂,容易内存泄漏,而且反爬一上来,IP封得飞快。

Scrapy给我的感觉是:它把这些复杂的东西都封装好了。

Scrapy的三大核心优势

1. 异步并发,速度快到飞起

Scrapy基于Twisted异步框架,单台机器可以轻松达到每秒300+的请求量。我测下来,同样的1000个商品,Scrapy只需3-5分钟,比requests+BeautifulSoup快了8-10倍。

为什么这么快?

• 异步IO:不等待响应,发完请求立刻发下一个

• 连接复用:保持TCP长连接,减少握手开销

• 内存友好:请求队列在内存中,不阻塞主线程

2. 标准化流程,代码少写80%

Scrapy把爬虫开发做成了流水线:

复制代码
调度器 → 下载器 → 爬虫解析 → 管道存储

你只需要写两件事:

• 怎么找数据(爬虫逻辑)

• 怎么存数据(管道逻辑)

其他的(请求调度、去重、并发控制)框架都帮你搞定了。

3. 扩展性强,应对复杂反爬

遇到反爬怎么办?

• 需要随机UA?加个中间件

• 需要代理池?加个中间件

• 需要动态渲染?加个中间件

Scrapy的中间件机制让你可以灵活应对各种反爬策略。

Scrapy vs 其他方案

|--------|-----------------|------------------|---------------|
| 特性 | Scrapy | Requests+BS4 | Selenium |
| 并发能力 | 异步高并发(300+ QPS) | 同步单线程(10-20 QPS) | 单线程(5-10 QPS) |
| 学习曲线 | 中等 | 简单 | 简单 |
| 反爬应对 | 中间件+代理池 | 需手动实现 | 模拟真人,慢 |
| 资源占用 | 低(CPU<50%) | 低 | 高(内存大) |
| 适用场景 | 大规模爬取 | 小规模数据 | 动态页面 |

我个人的建议是:

• 如果是临时爬取几百条数据,requests+BeautifulSoup就够了

• 如果是长期项目、需要处理百万级数据,Scrapy是唯一选择

• 如果是动态渲染的页面,可以Selenium辅助,但不要作为主方案

环境准备

现在开始实战。先搭建开发环境。

安装Scrapy

Scrapy对Python版本有要求,需要Python 3.9+。

bash 复制代码
# 检查Python版本
python --version
# 推荐使用虚拟环境
python -m venv scrapy_env
source scrapy_env/bin/activate  # Windows: scrapy_env\Scripts\activate
# 安装Scrapy
pip install scrapy
# 验证安装
scrapy version

如果安装失败,常见问题:

• Windows:推荐用conda安装 conda install -c conda-forge scrapy

• Linux:需要安装依赖 sudo apt-get install python3-dev libxml2-dev libxslt1-dev zlib1g-dev

创建第一个项目

bash 复制代码
# 创建项目
scrapy startproject book_scraper
# 进入项目目录
cd book_scraper
# 创建爬虫
scrapy genspider books books.toscrape.com

项目结构是这样的:

bash 复制代码
book_scraper/
├── scrapy.cfg              # 部署配置
└── book_scraper/
    ├── __init__.py
    ├── items.py           # 数据模型
    ├── middlewares.py     # 中间件
    ├── pipelines.py       # 管道
    ├── settings.py        # 全局配置
    └── spiders/           # 爬虫目录
        ├── __init__.py
        └── books.py       # 爬虫逻辑

核心配置(settings.py)

先修改几个关键配置:

python 复制代码
# 机器人协议,测试环境可以关闭
ROBOTSTXT_OBEY = False
# 请求头,模拟真实浏览器
DEFAULT_REQUEST_HEADERS = {
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
# 并发控制
CONCURRENT_REQUESTS = 32           # 全局并发数
CONCURRENT_REQUESTS_PER_DOMAIN = 16  # 单域名并发
DOWNLOAD_DELAY = 1                   # 下载延迟(秒)
# 启用管道
ITEM_PIPELINES = {
    'book_scraper.pipelines.BookPipeline': 300,
}

核心概念讲解

在开始写代码之前,先理解Scrapy的核心组件。这很重要,因为你后续的所有操作都是围绕这些组件展开的。

Scrapy的六大组件

python 复制代码
┌─────────────────────────────────────────────┐
│           引擎(Engine)                      │
│  (协调所有组件,数据流的核心)                │
└─────────────────────────────────────────────┘
            ↑              ↓              ↑
            │              │              │
┌───────────┴───┐  ┌──────┴───────┐  ┌─┴─────────┐
│  调度器      │  │  下载器      │  │  爬虫      │
│  Scheduler   │  │  Downloader  │  │  Spider    │
│  (请求队列)  │  │  (下载页面)  │  │  (解析数据)│
└─────────────┘  └──────────────┘  └───────────┘
                                    ↓
                            ┌───────────────┐
                            │  管道 Pipeline │
                            │  (数据处理)    │
                            └───────────────┘

1. 引擎(Engine)

整个系统的"大脑",负责调度各个组件之间的数据流。你不用直接操作它。

2. 调度器(Scheduler)

负责管理请求队列:

• 接收引擎传来的请求

• 按优先级排序

• 去重(避免重复请求)

• 逐个发给下载器

3. 下载器(Downloader)

负责下载网页:

• 发起HTTP请求

• 处理响应(支持重试、超时)

• 通过中间件扩展功能

4. 爬虫(Spider)

最核心的组件,你需要自己写:

• 定义起始URL

• 解析响应数据

• 提取目标字段(传给Pipeline)

• 提取新URL(传给调度器)

5. 数据容器(Item)

定义数据结构,比如:

python 复制代码
# items.py
import scrapy
class BookItem(scrapy.Item):
    title = scrapy.Field()    # 书名
    price = scrapy.Field()    # 价格
    rating = scrapy.Field()   # 评分
    stock = scrapy.Field()    # 库存
    url = scrapy.Field()      # 详情链接

6. 管道(Pipeline)

处理Item对象:

• 数据清洗(去空、格式转换)

• 数据验证(字段完整性检查)

• 数据存储(文件/数据库)

• 去重(避免重复数据)

数据流详解

一个完整的爬取流程是这样的:

python 复制代码
1. 爬虫生成初始请求(Start Requests)
   ↓
2. 引擎交给调度器(入队)
   ↓
3. 调度器取出请求(出队)
   ↓
4. 引擎交给下载器(下载页面)
   ↓
5. 下载器返回响应(Response)
   ↓
6. 引擎交给爬虫(解析数据)
   ↓
7. 爬虫提取数据(Item) → 管道处理
   ↓
8. 爬虫提取新URL(Request) → 调度器入队
   ↓
9. 循环步骤3-8,直到队列为空

实战案例:电商商品数据采集

现在用一个真实案例,带你完整走一遍Scrapy的开发流程。

我们的目标是爬取Books to Scrape(一个专门用于练习的电商网站)的商品数据,包括书名、价格、评分、库存等信息。

第一步:定义数据模型

先定义我们要抓取哪些数据:

python 复制代码
# items.py
import scrapy
class BookItem(scrapy.Item):
    """商品数据模型"""
    title = scrapy.Field()       # 书名
    price = scrapy.Field()       # 价格
    rating = scrapy.Field()      # 评分(如Four Stars)
    stock = scrapy.Field()       # 库存状态
    category = scrapy.Field()    # 分类
    url = scrapy.Field()         # 详情链接
    crawl_time = scrapy.Field()  # 爬取时间

第二步:编写爬虫逻辑

这是核心部分,需要分析网页结构。

分析页面结构

打开 http://books.toscrape.com/ ,按F12打开开发者工具:

html 复制代码
<article class="product_pod">
  <div class="image_container">
    <a href="catalogue/a-light-in-the-attic_1000/index.html">
      <img src="../media/cache/2c/da/2cdad674...jpg" alt="A Light in the Attic">
    </a>
  </div>
  <h3>
    <a href="catalogue/a-light-in-the-attic_1000/index.html"
       title="A Light in the Attic">A Light in the ...</a>
  </h3>
  <div class="product_price">
    <p class="price_color">£51.77</p>
    <p class="instock availability">
      <i class="icon-ok"></i>
      In stock
    </p>
  </div>
</article>

编写爬虫代码

python 复制代码
# spiders/books.py
import scrapy
from book_scraper.items import BookItem
class BooksSpider(scrapy.Spider):
    name = 'books'
    allowed_domains = ['books.toscrape.com']
    start_urls = ['http://books.toscrape.com/']
    def parse(self, response):
        """解析列表页"""
        # 提取所有商品卡片
        books = response.css('article.product_pod')
        for book in books:
            item = BookItem()
            # 书名(从title属性提取完整标题)
            item['title'] = book.css('h3 a::attr(title)').get()
            # 价格(去掉£符号)
            price_text = book.css('p.price_color::text').get()
            item['price'] = price_text.replace('£', '') if price_text else None
            # 评分(从class提取,如"star-rating Three" → "Three")
            rating_class = book.css('p.star-rating::attr(class)').get()
            item['rating'] = rating_class.split()[-1] if rating_class else None
            # 库存状态
            stock_text = book.css('p.instock availability::text').re_first(r'\w+.*')
            item['stock'] = stock_text.strip() if stock_text else None
            # 详情链接
            detail_url = book.css('h3 a::attr(href)').get()
            item['url'] = response.urljoin(detail_url)
            # 爬取时间
            import datetime
            item['crawl_time'] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            yield item
        # 处理下一页
        next_page = response.css('li.next a::attr(href)').get()
        if next_page:
            next_url = response.urljoin(next_page)
            yield scrapy.Request(next_url, callback=self.parse)

关键点说明:

  1. CSS选择器 :

◦ response.css('article.product_pod') - 找所有商品卡片

◦ response.css('h3 a::attr(title)') - 提取属性值

◦ response.css('p.price_color::text') - 提取文本

  1. response.urljoin() - 自动拼接相对路径为绝对URL

  2. yield item - 将数据传给Pipeline处理

  3. yield Request() - 将新URL传给调度器

第三步:实现数据管道

管道负责数据的后处理,这里我们实现三个功能:

• 数据清洗

• 数据去重

• 保存到文件

python 复制代码
# pipelines.py
import json
import os
class BookPipeline:
    """数据处理管道"""
    def __init__(self):
        # 初始化
        self.seen_titles = set()  # 用于去重
        self.items = []          # 暂存所有数据
    def process_item(self, item, spider):
        """处理每条数据"""
        # 1. 数据清洗
        # 空值处理
        if not item.get('title'):
            raise scrapy.exceptions.DropItem(f"Missing title: {item}")
        # 价格转为浮点数
        try:
            item['price'] = float(item['price'])
        except (ValueError, TypeError):
            item['price'] = 0.0

        # 2. 数据去重(按书名)
        title = item['title']
        if title in self.seen_titles:
            raise scrapy.exceptions.DropItem(f"Duplicate item: {title}")
        self.seen_titles.add(title)
        # 3. 暂存数据
        self.items.append(dict(item))
        spider.logger.info(f"Processed: {title}")
        return item
    def close_spider(self, spider):
        """爬虫结束时执行"""
        # 保存到JSON文件
        output_file = 'books_data.json'
        with open(output_file, 'w', encoding='utf-8') as f:
            json.dump(self.items, f, ensure_ascii=False, indent=2)
        spider.logger.info(f"Saved {len(self.items)} items to {output_file}")

关键点说明:

  1. process_item() - 每条数据都会调用此方法

  2. DropItem - 抛出此异常会丢弃该数据

  3. close_spider() - 爬虫结束时自动调用,适合做数据持久化

第四步:运行爬虫

所有代码写完了,现在运行爬虫:

python 复制代码
# 基本运行
scrapy crawl books
# 指定输出格式(不经过Pipeline)
scrapy crawl books -o books.csv
# 指定日志级别
scrapy crawl books -L INFO
# 限制爬取数量
scrapy crawl books -s CLOSESPIDER_ITEMCOUNT=100

运行后,会在项目根目录生成books_data.json文件,内容如下:

python 复制代码
[
  {
    "title": "A Light in the Attic",
    "price": 51.77,
    "rating": "Three",
    "stock": "In stock",
    "url": "http://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html",
    "crawl_time": "2026-02-02 11:30:00"
  },
  ...
]

调试技巧

写爬虫难免会遇到问题,这里分享一些实用的调试技巧。

使用Scrapy Shell

Scrapy Shell是一个交互式调试工具,可以快速测试选择器。

bash 复制代码
# 启动Shell
scrapy shell "http://books.toscrape.com/"
# 测试选择器
>>> response.css('article.product_pod').getall()  # 获取所有商品卡片
>>> response.css('h3 a::attr(title)').getall()   # 获取所有书名
>>> response.css('p.price_color::text').getall() # 获取所有价格
# 查看响应内容
>>> print(response.text)  # 查看HTML内容
>>> response.status       # 查看状态码

查看日志

Scrapy提供了详细的日志信息,帮助你定位问题:

bash 复制代码
# 调整日志级别
scrapy crawl books -L DEBUG   # 显示所有日志
scrapy crawl books -L INFO    # 只显示重要信息
scrapy crawl books -L WARNING # 只显示警告和错误
# 保存日志到文件
scrapy crawl books --logfile=spider.log

常见问题排查

问题1:选择器提取不到数据

可能原因:

• 选择器写错

• 页面动态加载

• 被反爬拦截

解决方法:

python 复制代码
# 在爬虫中添加调试输出
def parse(self, response):
    print(response.text)  # 查看实际收到的HTML
    print(response.status)  # 查看状态码

问题2:爬虫速度太慢

可能原因:

• 并发数太低

• 下载延迟太大

• 网络问题

解决方法:

python 复制代码
# settings.py
CONCURRENT_REQUESTS = 64  # 提高并发
DOWNLOAD_DELAY = 0.1      # 降低延迟

问题3:数据重复

可能原因:

• 去重逻辑有问题

• URL没有去重

解决方法:

python 复制代码
# 在Pipeline中检查
def process_item(self, item, spider):
    if item['title'] in self.seen_titles:
        raise DropItem(f"Duplicate: {item['title']}")
    self.seen_titles.add(item['title'])
    return item

小结

上篇到这里就结束了。我们覆盖了:

• Scrapy的核心概念和优势

• 环境搭建和项目创建

• 核心组件详解

• 完整的实战案例(从0到1)

• 基础调试技巧

如果你是第一次接触Scrapy,建议先实践上篇的案例,确保能独立运行一个爬虫。

下篇会深入讲解:

• 中间件的高级用法

• 管道的高级用法(多存储、批量处理)

• 分布式爬虫(Scrapy-Redis)

• 动态页面处理

• 性能优化技巧

• 反爬策略应对

• 监控和告警

相关推荐
Bruk.Liu2 小时前
(LangChain实战4):LangChain消息模版PromptTemplate
人工智能·python·langchain
SunnyRivers2 小时前
Asyncio 提速秘籍:用 run_in_executor 与 to_thread 巧解同步阻塞难题
python·asyncio·to_thread·run_in_executor
亚林瓜子2 小时前
pyspark分组计数
python·spark·pyspark·分组统计
查无此人byebye2 小时前
从零解读CLIP核心源码:PyTorch实现版逐行解析
人工智能·pytorch·python·深度学习·机器学习·自然语言处理·音视频
chao_7892 小时前
双设备全栈开发最佳实践[mac系统]
git·python·macos·docker·vue·全栈
筷乐老六喝旺仔2 小时前
使用PyQt5创建现代化的桌面应用程序
jvm·数据库·python
叫我辉哥e12 小时前
Libvio.link爬虫技术解析大纲
爬虫
LilySesy2 小时前
【SAP-MOM项目】二、接口对接(中)
开发语言·python·pandas·restful·sap·abap
零度@2 小时前
专为 Java 开发者 整理的《Python编程:从入门到实践》前8章核心内容
java·开发语言·windows·python