scrapy 是一个纯 Python 编写的异步爬虫框架,具备以下特点:
优势 | 说明 |
---|---|
异步高效 | 基于 Twisted,非阻塞 IO |
模块化 | 各部分可灵活配置/替换 |
中间件机制 | 支持代理、UA、cookie 控制等 |
强大的解析 | 内置 XPath、CSS 提取器 |
自动去重 | Scheduler 内部维护请求 fingerprint |
可扩展 | 支持 Redis/MongoDB/Selenium 等集成 |
适合用于中大型项目,管理多个 Spider 和抓取流程。
一、Scrapy 架构图 & 核心原理
bash
┌────────────────────────────────────┐
│ Scrapy Engine 引擎 │
│ (调度、分发请求与响应的控制中枢) │
└────────────────────────────────────┘
▲ ▲
│ │
┌──────────────┘ └────────────────────┐
▼ ▼
┌──────────────┐ ┌─────────────────────┐
│ Scheduler │ │ Downloader │
│ 调度器 │ │ 下载器(发请求) │
│ 维护请求队列 │ └─────────────────────┘
└─────▲────────┘ ▲
│ │
│ ┌─────────────────────────────┐ │
└──────►│ Downloader Middlewares 下载中间件│◄──────┘
└─────────────────────────────┘
▲
│
┌─────┴───────┐
│ Request 请求│
└────────────┘
│
▼
┌─────────────┐
│ Response 响应│
└────┬────────┘
│
▼
┌────────────────────────────────────┐
│ Spider 爬虫类 │
│ (处理响应,提取数据和生成新请求) │
└────────────────┬───────────────────┘
│
▼
┌────────────────────┐
│ Item(提取数据) │
└───────┬────────────┘
▼
┌──────────────────────────┐
│ Item Pipeline 管道 │
│(保存数据到文件、数据库等)│
└──────────────────────────┘
>工作流程步骤解释
-
>1. Spider 生成初始 Request 请求
-
如**
start_urls = ['https://example.com']
** -
被送到引擎(Engine)
-
-
>2. Engine 把请求交给 Scheduler 调度器
- 调度器负责排队请求,避免重复(有去重功能)
-
>3. Engine 从 Scheduler 取一个请求交给 Downloader 下载器
- Downloader 通过 HTTP 请求抓网页(可带中间件,如代理)
-
>4. Downloader 下载完后返回 Response 给 Engine
-
>5. Engine 把 Response 给 Spider 的 parse() 方法
- 写的**
parse()
** 里会提取数据(Item)或继续发送新请求
- 写的**
-
>6. 提取的数据(Item)交给 Pipeline
- Pipeline 可以把它存储到:文件、MongoDB、MySQL、Elasticsearch 等
-
>7. Spider 产生的新请求(Request)再次送入 Scheduler → 重复上面过程
>模拟完整流程(豆瓣爬虫举例)
假设爬豆瓣 Top250 页:
-
>1. Spider:创建第一个请求**
Request("https://movie.douban.com/top250")
** -
**>2.**Scheduler:收下请求排队
-
**>3.**Downloader:请求网站,返回 HTML
-
**>4.**Spider.parse():用 CSS/XPath 抽取电影名、评分
-
>5. 生成 Item:
{'title': '肖申克的救赎', 'score': '9.7'}
-
**>6.**Pipeline:保存为 JSON
-
**>7.**如果页面有"下一页",parse() 再 yield 一个 Request(下一页链接),流程继续
二、Scrapy 项目结构
创建项目:
bash
scrapy startproject myspider
结构如下:
bash
myspider/ ← Scrapy 项目根目录
├── scrapy.cfg ← Scrapy 配置文件(全局入口)
├── myspider/ ← 项目 Python 包目录(真正的业务逻辑在这)
│ ├── __init__.py ← 表明这是一个包
│ ├── items.py ← 定义数据结构(Item 模型)
│ ├── middlewares.py ← 下载中间件定义(如加代理)
│ ├── pipelines.py ← 管道:保存数据(文件、数据库)
│ ├── settings.py ← 项目配置文件(如 headers、限速、并发数)
│ └── spiders/ ← 存放所有 Spider 的目录
│ └── example.py ← 一个 Spider 示例(爬虫脚本)
2.1 scrapy.cfg(项目运行配置文件)
位置 :根目录
作用:告诉 Scrapy 你要运行哪个项目的设置文件。
内容示例:
bash
[settings]
default = myspider.settings ← 指定 settings.py 的位置
[deploy]
# 用于部署到 scrapyd 时用,不影响本地运行
当运行 **scrapy crawl xxx
**命令时,它会先从这个文件找到项目配置。
2.2 myspider/(项目主模块目录)
这是 Scrapy 真正执行的业务模块,其中所有核心逻辑都写在这里。
1)init.py
让 Python 把 myspider/
识别为模块包,没别的逻辑。
2)items.py
:定义数据结构(类似数据表字段)
Scrapy 的数据提取不是直接用字典,而是专门定义一个**Item
** 类。
示例:
python
import scrapy
class MyspiderItem(scrapy.Item):
title = scrapy.Field()
author = scrapy.Field()
date = scrapy.Field()
在 Spider 中提取数据时用:
python
item = MyspiderItem()
item['title'] = ...
item['author'] = ...
yield item
3)middlewares.py
:下载中间件(拦截请求与响应)
Scrapy 支持在请求发出前、响应回来后做额外处理,例如:
-
修改请求头(如 User-Agent)
-
设置代理
-
自动重试
-
伪装成浏览器
示例:
python
class RandomUserAgentMiddleware:
def process_request(self, request, spider):
request.headers['User-Agent'] = 'YourUserAgent'
在 **settings.py
**中启用:
python
DOWNLOADER_MIDDLEWARES = {
'myspider.middlewares.RandomUserAgentMiddleware': 543,
}
4)pipelines.py
:数据管道(保存提取到的数据)
Scrapy 提取的数据通过**Item
** 传入管道中做进一步处理,比如:
-
保存到 JSON、CSV
-
存入数据库(MongoDB、MySQL)
-
图片下载
示例:
python
class JsonWriterPipeline:
def open_spider(self, spider):
self.file = open('items.json', 'w', encoding='utf-8')
def process_item(self, item, spider):
self.file.write(str(item) + "\n")
return item
def close_spider(self, spider):
self.file.close()
在**settings.py
** 启用:
python
ITEM_PIPELINES = {
'myspider.pipelines.JsonWriterPipeline': 300,
}
5)settings.py
:Scrapy 项目的配置中心
可以在这里设置:
配置项 | 作用 |
---|---|
ROBOTSTXT_OBEY |
是否遵守 robots 协议(开发建议设为 False) |
DOWNLOAD_DELAY |
下载延迟(防止被封 IP) |
CONCURRENT_REQUESTS |
最大并发请求数 |
DEFAULT_REQUEST_HEADERS |
请求头设置 |
ITEM_PIPELINES |
设置哪些 pipeline 被启用 |
DOWNLOADER_MIDDLEWARES |
设置中间件 |
示例片段:
python
BOT_NAME = 'myspider'
ROBOTSTXT_OBEY = False
DOWNLOAD_DELAY = 1
CONCURRENT_REQUESTS = 16
DEFAULT_REQUEST_HEADERS = {
'User-Agent': 'Mozilla/5.0',
}
ITEM_PIPELINES = {
'myspider.pipelines.JsonWriterPipeline': 300,
}
6)spiders/
:爬虫文件目录(每个爬虫类放一个 .py)
一个 Spider 类 = 一个网站的爬虫逻辑。
示例:
python
import scrapy
from myspider.items import MyspiderItem
class BookSpider(scrapy.Spider):
name = "books"
start_urls = ['https://books.example.com']
def parse(self, response):
for book in response.css('div.book'):
item = MyspiderItem()
item['title'] = book.css('h2::text').get()
item['author'] = book.css('.author::text').get()
yield item
运行爬虫:
bash
scrapy crawl books
2.3 各模块作用表
位置 | 作用 | 是否需要改 |
---|---|---|
scrapy.cfg |
项目入口配置 | 一般不改 |
myspider/__init__.py |
标识模块 | 不改 |
items.py |
定义数据字段 | 必改 |
middlewares.py |
拦截请求/响应 | 可选改 |
pipelines.py |
存储 Item | 可选改 |
settings.py |
设置并发、延迟等 | 常用配置项要改 |
spiders/ |
放爬虫脚本 | 主战场,必须写 |
三、项目举例
实战目标
-
抓取 某勾职位接口。
-
提取字段:
- 职位名、公司名、城市、薪资、学历、经验、公司规模
-
自动翻页(1~5页)
-
保存为 CSV 文件
第一步:创建项目
打开终端:
bash
scrapy startproject lagou_spider
cd lagou_spider
第二步:定义字段(items.py)
编辑 lagou_spider/lagou_spider/items.py
:
python
import scrapy
class LagouSpiderItem(scrapy.Item):
position = scrapy.Field()
company = scrapy.Field()
salary = scrapy.Field()
city = scrapy.Field()
exp = scrapy.Field()
edu = scrapy.Field()
company_size = scrapy.Field()
第三步:创建爬虫文件
bash
cd lagou_spider/lagou_spider/spiders
touch lagou.py # Windows 用户用编辑器创建 lagou.py 文件
编辑 lagou.py
:
python
import scrapy
import json
from lagou_spider.items import LagouSpiderItem
class LagouSpider(scrapy.Spider):
name = 'lagou'
allowed_domains = ['lagou.com']
start_urls = ['https://www.lagou.com/jobs/list_python']
def start_requests(self):
for page in range(1, 6): # 抓取前 5 页
url = 'https://www.lagou.com/jobs/positionAjax.json?needAddtionalResult=false'
headers = {
'Referer': 'https://www.lagou.com/jobs/list_python',
'User-Agent': 'Mozilla/5.0',
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
}
data = {
'first': 'true' if page == 1 else 'false',
'pn': str(page),
'kd': 'python'
}
yield scrapy.FormRequest(
url=url,
formdata=data,
headers=headers,
callback=self.parse
)
def parse(self, response):
data = json.loads(response.text)
jobs = data['content']['positionResult']['result']
for job in jobs:
item = LagouSpiderItem()
item['position'] = job['positionName']
item['company'] = job['companyFullName']
item['salary'] = job['salary']
item['city'] = job['city']
item['exp'] = job['workYear']
item['edu'] = job['education']
item['company_size'] = job['companySize']
yield item
第四步:配置 pipeline 保存 CSV
编辑**lagou_spider/lagou_spider/pipelines.py
:**
python
import csv
class CsvPipeline:
def open_spider(self, spider):
self.file = open('lagou_jobs.csv', 'w', newline='', encoding='utf-8-sig')
self.writer = csv.writer(self.file)
self.writer.writerow(['职位', '公司', '薪资', '城市', '经验', '学历', '公司规模'])
def process_item(self, item, spider):
self.writer.writerow([
item['position'],
item['company'],
item['salary'],
item['city'],
item['exp'],
item['edu'],
item['company_size']
])
return item
def close_spider(self, spider):
self.file.close()
第五步:修改配置 settings.py
编辑 lagou_spider/lagou_spider/settings.py
,添加或修改:
python
ROBOTSTXT_OBEY = False
DOWNLOAD_DELAY = 1.5 # 降低请求频率,防止封 IP
DEFAULT_REQUEST_HEADERS = {
'User-Agent': 'Mozilla/5.0'
}
ITEM_PIPELINES = {
'lagou_spider.pipelines.CsvPipeline': 300,
}
第六步:运行爬虫
在终端中运行:
bash
scrapy crawl lagou
第七步:查看结果(lagou_jobs.csv)
输出示例(CSV 文件):
职位 | 公司 | 薪资 | 城市 | 经验 | 学历 | 公司规模 |
---|---|---|---|---|---|---|
Python开发工程师 | 字节跳动 | 15k-30k | 北京 | 3-5年 | 本科 | 10000人以上 |
后端Python工程师 | 腾讯 | 20k-40k | 深圳 | 5-10年 | 本科 | 5000-10000人 |
项目结构参考
bash
lagou_spider/
├── scrapy.cfg
├── lagou_spider/
│ ├── __init__.py
│ ├── items.py
│ ├── pipelines.py
│ ├── settings.py
│ └── spiders/
│ └── lagou.py
注意事项
问题 | 说明 |
---|---|
某勾有反爬 | 添加 DOWNLOAD_DELAY ,5 页以内一般不封 |
接口变化 | 抓的是 JSON 接口,稳定性比页面 HTML 好 |
抓太多 | IP 会被封,建议加代理池后使用 |
数据为空 | 设置 Referer + UA + Content-Type 后就正常了 |
后续可拓展功能
功能 | 方法 |
---|---|
添加代理池 | 中间件 process_request() 设置 |
分布式 | 用 scrapy-redis 实现 |
存 MongoDB | 改 pipeline 用 pymongo 写入 |
接入前端展示 | 输出 JSON/数据库配合前端页面展示 |