Scrapy 是 Python 生态中最强大的分布式爬虫框架,专为高效爬取网页数据设计,支持异步请求、自动去重、断点续爬、数据持久化等核心功能,广泛应用于电商数据采集、舆情监控、行业数据分析等场景。
一、Scrapy 核心优势与适用场景
1.1 核心优势
-
异步高效:基于 Twisted 异步框架,单线程可处理千级并发请求,爬取速度远超 Requests+BeautifulSoup 组合;
-
功能完备:内置数据解析、请求调度、去重、限速、代理池等模块,无需重复造轮子;
-
可扩展性强:支持自定义中间件、管道、扩展,轻松应对反爬、分布式爬取等复杂场景;
-
数据处理便捷:内置 Item、Pipeline 机制,支持将数据直接存储到 MySQL、MongoDB、CSV 等格式;
-
跨平台兼容:Windows、macOS、Linux 均可运行,支持 Docker 部署。
1.2 典型适用场景
| 应用场景 | 实际需求 | 举例说明 |
|---|---|---|
| 电商数据爬取 | 批量获取商品价格、评价、销量 | 爬取京东 / 淘宝商品列表、用户评论 |
| 资讯 / 舆情采集 | 实时抓取新闻、博客、社交媒体内容 | 爬取行业资讯、微博热点话题 |
| 招聘 / 房产数据采集 | 收集岗位信息、房源数据 | 爬取 BOSS 直聘岗位、链家二手房信息 |
| 数据挖掘预处理 | 为机器学习提供训练数据 | 爬取图片、文本、结构化数据 |
二、前置准备:环境搭建(Python 版)
Scrapy 依赖 Python 环境,推荐使用 Python 3.8~3.11(兼容性最佳),以下是 Windows/macOS/Linux 通用搭建步骤:
2.1 安装 Python 环境
-
安装注意:Windows 勾选 "Add Python to PATH",macOS/Linux 默认已配置环境变量;
-
验证:CMD / 终端输入
python --version(Windows)或python3 --version(macOS/Linux),显示版本号即成功。
2.2 安装 Scrapy 库
打开 CMD / 终端,执行 pip 命令安装(建议使用国内镜像源加速):
\# 国内镜像源(豆瓣)加速安装,避免超时
pip install scrapy -i https://pypi.douban.com/simple
-
验证:输入
scrapy version,显示 Scrapy 版本号(如 2.11.0)即安装成功; -
常见问题:
三、核心流程:Scrapy 爬虫开发四步走
Scrapy 爬虫开发遵循 "创建项目→定义 Item→编写爬虫→配置 Pipeline" 的固定流程,以下以 "爬取豆瓣电影 Top250" 为例,详细拆解每一步操作。
3.1 第一步:创建 Scrapy 项目
打开 CMD / 终端,进入想要存放项目的目录,执行以下命令创建项目:
\# scrapy startproject + 项目名
scrapy startproject douban\_movie
项目创建后,目录结构如下(核心文件已标注):
douban\_movie/
├── douban\_movie/ # 项目核心目录
│ ├── \_\_init\_\_.py
│ ├── items.py # 定义数据结构(核心)
│ ├── middlewares.py # 中间件(反爬、代理等)
│ ├── pipelines.py # 数据存储(核心)
│ ├── settings.py # 项目配置(核心)
│ └── spiders/ # 爬虫脚本目录(核心)
│ └── \_\_init\_\_.py
└── scrapy.cfg # 项目配置文件(无需修改)
3.2 第二步:定义 Item(数据结构)
Item 用于规范爬取的数据字段,避免数据混乱,修改douban_movie/``items.py:
import scrapy
class DoubanMovieItem(scrapy.Item):
  \# 电影名称
  title = scrapy.Field()
  \# 导演+主演
  director\_actor = scrapy.Field()
  \# 上映年份+国家+类型
  year\_country\_genre = scrapy.Field()
  \# 评分
  rating = scrapy.Field()
  \# 评价人数
  comment\_count = scrapy.Field()
  \# 电影简介
  intro = scrapy.Field()
- 说明:每个字段用
scrapy.Field()定义,后续爬虫爬取的数据将按此结构存储。
3.3 第三步:编写爬虫脚本(核心)
在douban_movie/spiders目录下创建爬虫文件movie_spider.py,执行爬取逻辑:
import scrapy
from douban\_movie.items import DoubanMovieItem
class MovieSpider(scrapy.Spider):
  \# 爬虫名称(唯一,用于启动爬虫)
  name = "douban\_top250"
  \# 允许爬取的域名(防止爬取到其他网站)
  allowed\_domains = \["douban.com"]
  \# 起始URL(爬取的入口页面)
  start\_urls = \["https://movie.douban.com/top250"]
  def parse(self, response):
  \# 1. 解析当前页面的电影数据
  movie\_list = response.xpath('//ol\[@class="grid\_view"]/li')
  for movie in movie\_list:
  item = DoubanMovieItem()
  \# 电影名称(提取text(),strip()去除空格)
  item\["title"] = movie.xpath('.//span\[@class="title"]\[1]/text()').extract\_first().strip()
  \# 导演+主演(split()分割字符串,去除多余空格)
  item\["director\_actor"] = movie.xpath('.//div\[@class="bd"]/p\[1]/text()').extract\_first().strip().split('\n')\[0].strip()
  \# 上映年份+国家+类型
  year\_country\_genre = movie.xpath('.//div\[@class="bd"]/p\[1]/text()').extract\_first().strip().split('\n')\[1].strip()
  item\["year\_country\_genre"] = year\_country\_genre
  \# 评分
  item\["rating"] = movie.xpath('.//span\[@class="rating\_num"]/text()').extract\_first().strip()
  \# 评价人数
  item\["comment\_count"] = movie.xpath('.//div\[@class="star"]/span\[4]/text()').extract\_first().strip()
  \# 电影简介(若不存在则返回空字符串)
  intro = movie.xpath('.//span\[@class="inq"]/text()').extract\_first()
  item\["intro"] = intro.strip() if intro else ""
  \# 提交Item到Pipeline(后续存储数据)
  yield item
  \# 2. 实现翻页爬取(找到下一页URL)
  next\_page = response.xpath('//span\[@class="next"]/a/@href')
  if next\_page:
  \# 拼接下一页完整URL(response.urljoin自动补全域名)
  next\_page\_url = response.urljoin(next\_page.extract\_first())
  \# 发起下一页请求,回调parse函数继续解析
  yield scrapy.Request(next\_page\_url, callback=self.parse)
关键代码解读
-
爬虫类继承 :必须继承
scrapy.Spider,并重写name(爬虫唯一标识)、allowed_domains(域名限制)、start_urls(起始 URL); -
解析函数 parse :Scrapy 自动发起
start_urls的请求,响应结果传入 parse 函数,用于解析数据; -
XPath 解析:Scrapy 支持 XPath 和 CSS 选择器,XPath 更灵活(推荐),常用语法:
-
//:从根节点查找所有匹配元素; -
.//:从当前节点查找子元素; -
@属性名:提取元素属性(如 href、src); -
extract_first():提取第一个匹配结果(避免列表);
- 翻页逻辑 :找到下一页链接,用
scrapy.Request发起新请求,回调parse函数循环解析。
3.4 第四步:配置 Pipeline(数据存储)
Pipeline 用于处理爬虫爬取的 Item 数据,支持存储到 CSV、MySQL、MongoDB 等,以下以 "存储到 CSV 文件" 和 "存储到 MySQL" 为例:
3.4.1 存储到 CSV 文件(简单高效,适合快速导出)
无需修改pipelines.py,直接在启动爬虫时指定输出格式:
scrapy crawl douban\_top250 -o douban\_top250.csv
3.4.2 存储到 MySQL(工业级场景,适合长期存储)
-
安装 MySQL 驱动:
pip install pymysql
-
修改
douban_movie/``pipelines.py,编写 MySQL 存储逻辑:import pymysql
class DoubanMoviePipeline:
# 爬虫启动时执行(连接数据库)
def open_spider(self, spider):
self.conn = pymysql.connect(
host="localhost", # 数据库地址
user="root", # 数据库用户名
password="123456", # 数据库密码
database="scrapy_db", # 数据库名(需提前创建)
charset="utf8mb4" # 支持中文
)
self.cursor = self.conn.cursor()
# 创建电影表(若不存在)
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS douban_top250 (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
director_actor VARCHAR(255),
year_country_genre VARCHAR(255),
rating FLOAT,
comment_count VARCHAR(50),
intro TEXT
)
''')
self.conn.commit()
# 处理每一个Item(插入数据库)
def process_item(self, item, spider):
try:
self.cursor.execute('''
INSERT INTO douban_top250 (title, director_actor, year_country_genre, rating, comment_count, intro)
VALUES (%s, %s, %s, %s, %s, %s)
''', (
item["title"],
item["director_actor"],
item["year_country_genre"],
item["rating"],
item["comment_count"],
item["intro"]
))
self.conn.commit()
except Exception as e:
print(f"插入数据失败:{e}")
self.conn.rollback()
return item # 必须返回Item,否则后续Pipeline无法处理
# 爬虫关闭时执行(关闭数据库连接)
def close_spider(self, spider):
self.cursor.close()
self.conn.close()
-
启用 Pipeline:修改
douban_movie/``settings.py,取消ITEM_PIPELINES的注释并配置:ITEM_PIPELINES = {
"douban_movie.pipelines.DoubanMoviePipeline": 300, # 300为优先级(越小越先执行)
}
3.5 启动爬虫
进入项目根目录(douban_movie目录),执行以下命令启动爬虫:
\# scrapy crawl + 爬虫名称(name字段定义的名称)
scrapy crawl douban\_top250
- 运行效果:Scrapy 会自动发起请求、解析数据、存储到 MySQL,终端会显示爬取进度(请求数、Item 数、耗时等)。
四、核心配置:settings.py 关键参数(优化爬取效果)
settings.py是 Scrapy 项目的核心配置文件,合理配置可提升爬取效率、规避反爬:
\# 1. 爬虫名称(可选)
BOT\_NAME = "douban\_movie"
\# 2. 爬虫模块路径(无需修改)
SPIDER\_MODULES = \["douban\_movie.spiders"]
NEWSPIDER\_MODULE = "douban\_movie.spiders"
\# 3. 遵守robots.txt协议(默认True,反爬友好;爬取自己的网站可设为False)
ROBOTSTXT\_OBEY = True
\# 4. 并发请求数(默认16,根据目标网站抗压能力调整,避免被封IP)
CONCURRENT\_REQUESTS = 8
\# 5. 下载延迟(单位:秒,默认0,建议设置1\~3秒,模拟人工访问)
DOWNLOAD\_DELAY = 2
\# 6. 禁用Cookie(默认False,反爬场景可设为True)
\# COOKIES\_ENABLED = False
\# 7. 设置请求头(模拟浏览器,避免被识别为爬虫)
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 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
}
\# 8. 启用Pipeline(已配置,见3.4.2)
ITEM\_PIPELINES = {
  "douban\_movie.pipelines.DoubanMoviePipeline": 300,
}
\# 9. 下载超时时间(单位:秒,默认180,避免长时间阻塞)
DOWNLOAD\_TIMEOUT = 30
\# 10. 重试次数(默认2,网络波动时自动重试)
RETRY\_TIMES = 3
五、进阶技巧:解决实际爬取问题
5.1 处理动态加载页面(JavaScript 渲染)
Scrapy 默认不支持解析 JS 动态加载的数据(如滑动加载、点击加载),解决方案:
-
分析接口:F12 打开开发者工具→Network→XHR,找到动态加载的 API 接口,直接爬取接口数据(推荐,效率最高);
-
集成 Selenium:通过 Scrapy 中间件将 Selenium 与 Scrapy 结合,模拟浏览器渲染:
# 安装依赖
pip install scrapy-selenium selenium webdriver-manager
修改settings.py添加中间件:
DOWNLOADER\_MIDDLEWARES = {
  "scrapy\_selenium.SeleniumMiddleware": 800,
}
\# Selenium配置
SELENIUM\_DRIVER\_NAME = "chrome"
SELENIUM\_DRIVER\_EXECUTABLE\_PATH = None # 自动下载驱动
SELENIUM\_DRIVER\_ARGUMENTS = \["--headless=new"] # 无头模式
5.2 配置代理 IP(规避 IP 封禁)
反爬严格的网站会封禁频繁请求的 IP,需配置代理池,修改middlewares.py:
class ProxyMiddleware:
  def process\_request(self, request, spider):
  # 代理IP列表(可替换为自己的代理池)
  proxies = \[
  "http://123.45.67.89:8080",
  "http://98.76.54.32:8888",
  ]
  # 随机选择一个代理
  proxy = random.choice(proxies)
  request.meta\["proxy"] = proxy
启用中间件(settings.py):
DOWNLOADER\_MIDDLEWARES = {
  "douban\_movie.middlewares.ProxyMiddleware": 750,
}
5.3 断点续爬(避免重复爬取)
爬取大量数据时中断后,可通过JOBDIR参数实现断点续爬:
scrapy crawl douban\_top250 -s JOBDIR=./job\_info
- 说明:
./job_info为存储爬取状态的目录,再次执行该命令会从上次中断的位置继续爬取。
5.4 数据去重(避免重复存储)
通过 Pipeline 实现数据去重(以 MySQL 为例),修改process_item方法:
def process\_item(self, item, spider):
  # 先查询数据库是否已存在该电影
  self.cursor.execute('SELECT id FROM douban\_top250 WHERE title = %s', (item\["title"],))
  if self.cursor.fetchone():
  print(f"电影《{item\['title']}》已存在,跳过")
  return item
  # 不存在则插入
  try:
  self.cursor.execute('''INSERT INTO ...''', (...))
  self.conn.commit()
  except Exception as e:
  self.conn.rollback()
  return item
六、常见坑与避坑技巧
6.1 爬取速度过慢
-
原因 1:
DOWNLOAD_DELAY设置过大 → 适当减小(如 1 秒); -
原因 2:
CONCURRENT_REQUESTS设置过小 → 调整为 16~32(根据网站抗压能力); -
原因 3:未禁用图片加载 → 配置禁用图片:
DOWNLOADER_MIDDLEWARES = {
"scrapy.downloadermiddlewares.images.ImagesPipeline": None,
}
6.2 被目标网站封禁 IP
-
解决方案 1:设置
DOWNLOAD_DELAY(1~3 秒),模拟人工访问; -
解决方案 2:配置代理 IP 池(推荐使用付费代理,稳定性更高);
-
解决方案 3:更换
User-Agent,避免单一请求头; -
解决方案 4:启用 Cookie 池,模拟登录状态爬取。
6.3 数据解析失败(XPath/CSS 选择器错误)
-
排查步骤 1:在终端用
scrapy shell调试,验证选择器是否正确:scrapy shell "https://movie.douban.com/top250"
# 进入交互环境后,执行XPath验证
response.xpath('//ol[@class="grid_view"]/li')
-
排查步骤 2:检查网页源码(F12),确认元素 class/id 是否动态变化(如带随机字符串);
-
解决方案:改用更稳定的父元素定位,或使用 contains 匹配部分属性值:
# 匹配class包含"grid_view"的ol元素
response.xpath('//ol[contains(@class, "grid_view")]/li')
6.4 数据库插入失败
-
原因 1:中文编码问题 → 数据库 charset 设为
utf8mb4; -
原因 2:字段长度不足 → 调整 VARCHAR 长度(如 title 设为 255);
-
原因 3:SQL 语法错误 → 用
print(sql)输出 SQL 语句,手动验证语法。
七、进阶方向:Scrapy 分布式爬取
当需要爬取千万级数据时,单台机器效率过低,可通过scrapy-redis实现分布式爬取:
-
安装依赖:
pip install scrapy-redis
-
修改
settings.py配置 Redis:# 启用Redis调度器(分布式去重、任务调度)
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 启用Redis去重
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# Redis连接配置
REDIS_URL = "redis://localhost:6379/0"
-
启动 Redis 服务,多台机器运行同一爬虫,即可实现任务分发和数据共享。