8
Scrapy 框架:爬虫界的工程队
Scrapy: The Industrial Framework
写脚本爬几个页面用 requests 够了。但要爬整站、几千个页面、管队列、管去重、存数据库——手写脚本就乱了。Scrapy 就是爬虫界的工程队,异步高性能,帮你把"下载、解析、存储"流水线搭好。
安装与项目创建
pip install scrapy
scrapy startproject myspider
cd myspider
scrapy genspider blog example.com
# 自动生成项目结构:
# myspider/
# ├── scrapy.cfg
# └── myspider/
# ├── items.py 定义数据结构
# ├── pipelines.py 处理爬下来的数据(存数据库)
# ├── settings.py 配置
# └── spiders/ 爬虫代码放这
核心组件
| 组件 | 职责 |
|---|---|
| Spider | 你写的爬虫,定义从哪开始、怎么解析 |
| Engine | 引擎,调度一切 |
| Scheduler | 调度器,URL 队列(去重、排队) |
| Downloader | 下载器,发请求 |
| Pipeline | 管道,数据清洗、存数据库 |
| Middleware | 中间件,改请求/响应(加 UA、换代理) |
写一个 Spider
# spiders/blog_spider.py
import scrapy
class BlogSpider(scrapy.Spider):
name = "blog"
start_urls = ["https://example.com/blog?page=1"]
def parse(self, response):
# response 已经是 lxml 对象,直接 .css()/.xpath()
for post in response.css(".post"):
yield {
"title": post.css("h2::text").get(),
"url": post.css("a::attr(href)").get(),
}
# 翻页:yield 一个 Request,Scrapy 自动排队继续爬
next_page = response.css(".next a::attr(href)").get()
if next_page:
yield response.follow(next_page, callback=self.parse)
# 运行:scrapy crawl blog -o posts.csv
Item 与 ItemLoader
# items.py —— 定义爬的数据长什么样
import scrapy
class PostItem(scrapy.Item):
title = scrapy.Field()
url = scrapy.Field()
author = scrapy.Field()
created = scrapy.Field()
# Spider 里用 ItemLoader 自动清洗
from scrapy.loader import ItemLoader
def parse(self, response):
loader = ItemLoader(item=PostItem(), response=response)
loader.add_css("title", "h2::text")
loader.add_css("url", "a::attr(href)")
yield loader.load_item()
settings.py 关键配置详解
| 配置 | 干嘛用 |
|---|---|
ROBOTSTXT_OBEY | 遵守 robots.txt(生产建议 True) |
USER_AGENT | 默认 UA |
CONCURRENT_REQUESTS | 并发数(默认 16) |
DOWNLOAD_DELAY | 请求间隔秒数 |
ITEM_PIPELINES | 启用哪些 Pipeline(数字=优先级) |
DOWNLOADER_MIDDLEWARES | 启用哪些中间件 |
RETRY_ENABLED | 失败重试(默认 True) |
LOG_LEVEL | 日志级别(DEBUG/INFO/WARNING) |
ItemLoader 内置处理器
| 处理器 | 干嘛用 |
|---|---|
TakeFirst() | 取第一个非空值 |
Join() | 把列表拼成字符串 |
MapCompose() | 对每个值依次调用函数 |
Identity() | 原样返回 |
中间件:UA 池与代理
# middlewares.py —— 每个请求前加随机 UA
import random
USER_AGENTS = ["Mozilla/5.0 ...", "Mozilla/5.0 ..."]
class RandomUA:
def process_request(self, request, spider):
request.headers["User-Agent"] = random.choice(USER_AGENTS)
return None # None = 继续处理;return Response = 短路
meta 传参:翻页时带上上下文
# 从列表页进详情页,把列表页信息带过去
def parse_list(self, response):
for link in response.css(".post a::attr(href)").getall():
yield response.follow(
link,
callback=self.parse_detail,
meta={"list_page": response.url} # 带过去
)
def parse_detail(self, response):
list_url = response.meta["list_page"] # 拿回来
yield {"title": response.css("h1::text").get(),
"from_list": list_url}
Pipeline:存数据库
# pipelines.py
import pymysql
class MysqlPipeline:
def open_spider(self, spider):
self.conn = pymysql.connect(host="localhost", user="root",
password="123", database="spider")
self.cur = self.conn.cursor()
def process_item(self, item, spider):
sql = "INSERT IGNORE INTO posts (title, url) VALUES (%s, %s)"
self.cur.execute(sql, (item["title"], item["url"]))
self.conn.commit()
return item
def close_spider(self, spider):
self.cur.close()
self.conn.close()
settings.py 关键配置
ROBOTSTXT_OBEY = False # 遵守 robots.txt(生产建议 True)
USER_AGENT = "Mozilla/5.0 ..."
CONCURRENT_REQUESTS = 16 # 并发数
DOWNLOAD_DELAY = 1 # 每次请求间隔1秒
ITEM_PIPELINES = {
"myspider.pipelines.MysqlPipeline": 300,
}
# DOWNLOADER_MIDDLEWARES 加 UA 池/代理池
Scrapy 常用命令
| 命令 | 干嘛用 |
|---|---|
scrapy startproject mybot | 创建项目 |
scrapy genspider blog example.com | 生成爬虫文件 |
scrapy crawl blog | 运行爬虫 |
scrapy crawl blog -o out.json | 导出 JSON |
scrapy shell <url> | 交互式调试(REPL 里试选择器) |
scrapy list | 列出所有爬虫名 |
CrawlSpider:自动爬整站
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
class SiteSpider(CrawlSpider):
name = "site"
allowed_domains = ["example.com"]
start_urls = ["https://example.com/"]
rules = (
# 自动提取所有 /post/ 开头的链接,交给 parse_post 解析
Rule(LinkExtractor(allow=r"/post/\d+"), callback="parse_post", follow=True),
)
def parse_post(self, response):
yield {"title": response.css("h1::text").get()}
Scrapy 工作流程
论Scrapy 内部怎么转
① Spider 给 start_urls → ② Engine 交给 Scheduler 排队 → ③ Downloader 下载(中间件加UA/代理) → ④ 响应回 Engine → ⑤ 交给 Spider.parse 解析 → ⑥ yield item 给 Pipeline 存 → ⑦ yield Request 给 Scheduler 继续爬。整个流程自动跑,你只管写 parse。
运行 Scrapy
# 运行爬虫,结果存 CSV
scrapy crawl blog -o posts.csv
# 2026-09-16 10:00:00 [scrapy] INFO: Spider opened
# 2026-09-16 10:00:01 [scrapy] DEBUG: Crawled (200) <GET https://example.com/1>
# 2026-09-16 10:00:02 [scrapy] DEBUG: Crawled (200) <GET https://example.com/2>
# ...
# 2026-09-16 10:00:10 [scrapy] INFO: Closing spider (finished)
# 2026-09-16 10:00:10 [scrapy] INFO: Stored 50 items
# 交互式调试:scrapy shell <url>,在 REPL 里试 response.css()
本章面试题 · Scrapy
1.(概念题)Scrapy 和手写 requests 脚本比,强在哪?
查看答案
答案:① 异步并发,几千个请求自动调度;② 内置去重、重试、User-Agent 轮换;③ 流水线(Pipeline)分离存储逻辑;④ 中间件可插拔。手写脚本管队列和去重太痛苦。
2.(概念题)Scrapy 的 yield 是干嘛的?为什么不是 return?
查看答案
答案:parse() 里 yield item 是"把这个数据交给 Pipeline",yield Request 是"把这个 URL 交给调度器继续爬"。yield 是生成器,Scrapy 逐批取数据,不会一次占满内存。
3.(概念题)CrawlSpider 和普通 Spider 区别?
查看答案
答案:普通 Spider 手动 yield Request 翻页;CrawlSpider 用 Rule+LinkExtractor 自动按规则发现链接,适合爬整站。