前面用 requests + BeautifulSoup 写了那么多爬虫,已经能解决大部分需求了。但如果遇到需要大规模采集、数据量上万页的场景,就该上 Scrapy 了。
Scrapy 是 Python 最强大的爬虫框架,内置了多线程调度、请求去重、数据管道、自动限速等功能,让你写更少的代码,爬更多的数据。
一、安装与创建项目
bash
pip install scrapy
# 创建爬虫项目
scrapy startproject myspider
# 目录结构
myspider/
├── scrapy.cfg
└── myspider/
├── __init__.py
├── items.py # 定义数据字段
├── middlewares.py # 中间件(代理、UA等)
├── pipelines.py # 数据管道(存数据库/文件)
├── settings.py # 配置文件
└── spiders/ # 爬虫代码目录
└── __init__.py
# 创建爬虫
cd myspider
scrapy genspider quotes quotes.toscrape.com
二、核心组件速览
URLs → 调度器(Scheduler) → 下载器(Downloader) → 爬虫(Spider) → 管道(Pipeline)
↑ ↓
去重过滤器(DupeFilter) 中间件(Middleware)
各组件作用:
| 组件 | 职责 |
|---|---|
| Spider | 解析响应、提取数据、生成新请求 |
| Scheduler | 管理待爬取的请求队列 |
| Downloader | 下载网页内容 |
| Pipeline | 清洗、验证、存储数据 |
| Middleware | 处理请求/响应的钩子(UA切换、代理等) |
三、实战:爬取名言网站
以 quotes.toscrape.com 为例,爬取名言、作者和标签。
1. 定义数据模型
python
# items.py
import scrapy
class QuoteItem(scrapy.Item):
text = scrapy.Field() # 名言内容
author = scrapy.Field() # 作者
tags = scrapy.Field() # 标签列表
author_url = scrapy.Field() # 作者详情页URL
2. 编写爬虫
python
# spiders/quotes.py
import scrapy
from myspider.items import QuoteItem
class QuotesSpider(scrapy.Spider):
name = "quotes"
allowed_domains = ["quotes.toscrape.com"]
start_urls = ["https://quotes.toscrape.com"]
def parse(self, response):
"""解析列表页"""
# 提取当前页所有名言
for quote in response.css("div.quote"):
item = QuoteItem()
item["text"] = quote.css("span.text::text").get()
item["author"] = quote.css("small.author::text").get()
item["tags"] = quote.css("div.tags a.tag::text").getall()
# 提取作者详情页URL
item["author_url"] = quote.css("a::attr(href)").get()
yield item
# 翻页:提取下一页链接并继续爬取
next_page = response.css("li.next a::attr(href)").get()
if next_page:
yield response.follow(next_page, callback=self.parse)
3. 数据存储------Pipeline
python
# pipelines.py
import json
from itemadapter import ItemAdapter
class JsonPipeline:
"""保存为JSON文件"""
def open_spider(self, spider):
self.file = open("quotes.json", "w", encoding="utf-8")
self.file.write("[\n")
self.first = True
def close_spider(self, spider):
self.file.write("\n]")
self.file.close()
def process_item(self, item, spider):
line = json.dumps(dict(item), ensure_ascii=False)
if not self.first:
self.file.write(",\n")
self.first = False
self.file.write(" " + line)
return item
class MongoPipeline:
"""保存到MongoDB"""
def open_spider(self, spider):
self.client = pymongo.MongoClient("localhost", 27017)
self.db = self.client["quotes_db"]
def close_spider(self, spider):
self.client.close()
def process_item(self, item, spider):
self.db["quotes"].insert_one(dict(item))
return item
4. 启用 Pipeline
python
# settings.py
ITEM_PIPELINES = {
"myspider.pipelines.JsonPipeline": 300, # 数字越小优先级越高
# "myspider.pipelines.MongoPipeline": 400,
}
5. 运行
bash
scrapy crawl quotes -o quotes.json # 内置方式,最简单
# 或者
scrapy crawl quotes # 走自定义Pipeline
-o quotes.json 是 Scrapy 内置的导出方式,一行命令搞定。
四、中间件------随机User-Agent
python
# middlewares.py
import random
class RandomUserAgentMiddleware:
"""随机切换 User-Agent"""
user_agents = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/120.0.0.0",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) Gecko/20100101 Firefox/121.0",
]
def process_request(self, request, spider):
request.headers["User-Agent"] = random.choice(self.user_agents)
return None # 返回 None 继续处理
python
# settings.py 中启用
DOWNLOADER_MIDDLEWARES = {
"myspider.middlewares.RandomUserAgentMiddleware": 543,
}
五、爬虫配置优化
python
# settings.py ------ 常用配置项
# 遵守 robots.txt(开发时建议设为 False)
ROBOTSTXT_OBEY = False
# 下载延迟(秒),防止被封
DOWNLOAD_DELAY = 1.5 # 每两个请求间隔 1.5 秒
# 并发请求数
CONCURRENT_REQUESTS = 16 # 总并发
CONCURRENT_REQUESTS_PER_DOMAIN = 8 # 每个域名并发
# 请求超时
DOWNLOAD_TIMEOUT = 15
# 是否启用cookies
COOKIES_ENABLED = False
# 自动限流扩展(智能调节爬取速度)
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_START_DELAY = 5.0
AUTOTHROTTLE_MAX_DELAY = 60.0
# 重试配置
RETRY_ENABLED = True
RETRY_TIMES = 3
RETRY_HTTP_CODES = [500, 502, 503, 504, 403, 408]
# 日志级别
LOG_LEVEL = "INFO"
六、实战:爬取翻页详情页
很多网站是列表页 → 详情页的结构,两步完成:
python
import scrapy
from myspider.items import BookItem
class BooksSpider(scrapy.Spider):
name = "books"
start_urls = ["https://books.toscrape.com/"]
def parse(self, response):
"""第一步:提取列表页中的书籍链接"""
for book in response.css("article.product_pod"):
detail_url = book.css("h3 a::attr(href)").get()
yield response.follow(detail_url, callback=self.parse_book)
# 翻页
next_page = response.css("li.next a::attr(href)").get()
if next_page:
yield response.follow(next_page, callback=self.parse)
def parse_book(self, response):
"""第二步:解析详情页"""
item = BookItem()
item["title"] = response.css("h1::text").get()
item["price"] = response.css("p.price_color::text").get()
item["description"] = response.css(
"#product_description ~ p::text"
).get()
# 提取分类
breadcrumb = response.css("ul.breadcrumb li a::text").getall()
item["category"] = breadcrumb[2] if len(breadcrumb) > 2 else ""
yield item
七、如何断点续爬
Scrapy 支持暂停和恢复:
bash
# 启动时生成状态文件
scrapy crawl quotes -s JOBDIR=quotes_state
# 再次运行会自动恢复(Ctrl+C 中断后)
scrapy crawl quotes -s JOBDIR=quotes_state
适合大规模采集怕中断的场景。
八、Scrapy vs requests 怎么选?
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 爬几十页数据 | requests + BS4 | 简单灵活,没必要上框架 |
| 爬几百页数据 | Scrapy | 多线程自动调度,速度快 |
| 爬上万页数据 | Scrapy + 分布式 | Scrapy-Redis 实现分布式 |
| 需要登录/复杂交互 | Scrapy + Selenium | 配合中间件处理动态内容 |
建议路线: requests 入门 → Scrapy 进阶 → Scrapy-Redis 分布式
总结
Scrapy 真正强的地方在于:你只管写解析逻辑,调度、并发、重试、限速、去重这些脏活累活框架全包了。对做数据采集来说,Scrapy 是值得投入时间学习的利器。
💡 觉得有用的话,点赞 + 关注【张老师技术栈】吧!每周更新 Java/Python/爬虫 实战干货,不让你白来。