其实我也用过不少爬虫方案,从最基础的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)
关键点说明:
- CSS选择器 :
◦ response.css('article.product_pod') - 找所有商品卡片
◦ response.css('h3 a::attr(title)') - 提取属性值
◦ response.css('p.price_color::text') - 提取文本
-
response.urljoin() - 自动拼接相对路径为绝对URL
-
yield item - 将数据传给Pipeline处理
-
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}")
关键点说明:
-
process_item() - 每条数据都会调用此方法
-
DropItem - 抛出此异常会丢弃该数据
-
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)
• 动态页面处理
• 性能优化技巧
• 反爬策略应对
• 监控和告警