1866 字
9 分钟
Scrapy框架讲解
2025-12-22

诉苦#

最近是我的期末实验课周

连续五天的早八四节连上,感觉有一点死,正好课上是做爬虫相关的东西我就来记录一下吧

下面是我的课表

期末课表

期末周的爬虫还是挺简单的,都是爬取一些电影评论,简介这些。 基本的爬虫都大差不差,一个模板就能处理大部分的东西了,访问,cookie,html,取数据,保存数据。

scrapy#

Scrapy 是一个异步、高性能的 Python 爬虫框架,其实我认为这些小打小闹的爬虫用scrapy太大材小用了

架构#

┌─────────────────────────────────────────────────────────┐
│ Scrapy Engine │
├─────────────────────────────────────────────────────────┤
│ 调度器 (Scheduler) ←─→ 下载器 (Downloader) │
└─────────────────────────────────────────────────────────┘
↓ ↓
┌─────────────────┐ ┌─────────────────────────┐
│ Spider │ │ Downloader Middlewares│
│ (爬虫/解析器) │ │ (下载器中间件) │
└─────────────────┘ └─────────────────────────┘
┌─────────────────┐ ┌─────────────────────────┐
│ Item Pipeline │ │ Spider Middlewares │
│ (项目管道) │ │ (爬虫中间件) │
└─────────────────┘ └─────────────────────────┘
  • 引擎(Engine)

    • 控制所有模块之间的数据流,并在条件触发时触发事件。
  • 调度器(Scheduler)

    • 接收引擎发过来的请求,并将其入队,以便在引擎请求时提供给引擎。
  • 下载器(Downloader)

    • 负责下载网页内容,并将内容返回给蜘蛛。
  • Spider类

    • 用户自定义的类,用于解析响应并提取Item(即数据)或额外的请求。
  • Item Pipeline

    • 负责处理爬虫提取的Item,典型的任务包括清理、验证和持久化(例如存储到数据库)。
  • 下载器中间件(Downloader Middlewares)

    • 位于引擎和下载器之间的钩子框架,主要用于处理请求和响应。
  • 爬虫中间件(Spider Middlewares)

    • 位于引擎和爬虫之间的钩子框架,能够处理爬虫的输入(响应)和输出(Items和请求)。

基本项目结构#

myproject/
├── scrapy.cfg # 项目配置文件
└── myproject/ # 项目Python模块
├── __init__.py
├── items.py # 定义Item数据结构
├── middlewares.py # 中间件定义
├── pipelines.py # 管道处理 数据保存
├── settings.py # 项目设置
└── spiders/ # 爬虫目录
├── __init__.py
└── myspider.py # 爬虫实现

案例讲解#

这里展示一个爬取当当网书籍信息,我会在这里面体现scrapy的异步,高性能,高解耦度

安装#
Terminal window
pip install scrapy
创建#
Terminal window
# 创建一个项目
scrapy startproject dangdang_spider
# 进入
cd dangdang_spider
# 生成爬虫 这里会在spider.py文件里生成一些相关的代码
scrapy genspider dangdang e.dangdang.com

以上都执行后会生成这样的项目结构

项目结构

定义数据结构#
# 文件在 item.py
import scrapy
class DangdangSpiderItem(scrapy.Item):
book_name = scrapy.Field()
author = scrapy.Field()
book_price = scrapy.Field()
book_profile = scrapy.Field()
book_coverpic = scrapy.Field()
book_contribution = scrapy.Field()
catalogue = scrapy.Field()
point_num = scrapy.Field()
编写主要得逻辑代码#
# 文件为 spiders/dangdang.py
import scrapy
import json
from lxml import etree
class DangdangSpider(scrapy.Spider):
name = "dangdang"
allowed_domains = ["e.dangdang.com"]
heasers = {
"user-agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)"
}
def start_requests(self):
'''设置翻页逻辑'''
baseurl = '''https://e.dangdang.com/media/api.go?action=mediaCategoryLeaf&promotionType=1&deviceSerialNo=html5&macAddr=html5&channelType=html5&permanentId=20251224082506162502645867115466839&returnType=json&channelId=70000&clientVersionNo=6.8.0&platformSource=DDDS-P&fromPlatform=106&deviceType=pconline&token=&start={}&end={}&category=GLX&dimension=dd_sale&order=0'''
for i in range(0,100,21):
url = baseurl.format(i,i+20)
yield scrapy.Request(url=url, callback=self.parse)
def second_url_func(self, response):
'''处理详情页面,获取更多信息'''
# 处理html
data_list = response.meta['data_list']
res = response.text
html = etree.HTML(res)
# 使用xpath提取数据
book_contribution = html.xpath('//div[@class="explain_box"]/p[2]/span/a/text()')
book_contribution = book_contribution[0].strip() if book_contribution else ''
catalogue = html.xpath('//div[@class="txt"]/p/text()')
# 传给pipelines
yield {
'book_name': data_list['book_name'],
'author': data_list['author'],
'book_price': data_list['book_price'],
'book_profile': data_list['book_profile'],
'book_coverpic': data_list['book_coverpic'],
'book_contribution': book_contribution,
'catalogue': catalogue,
}
def parse(self, response):
'''解析数据'''
# 基本设置,这里的响应是json格式的数据
second_url = "https://e.dangdang.com/products/{}.html"
res = response.text
data = json.loads(res)
thing = data['data']
salelist = thing['saleList']
# 得到数据
for i,book in enumerate(salelist):
# point_num = count + i
inter_msg = book['mediaList']
data_list = {
"inter_msg" : book['mediaList'],
"book_name" : inter_msg[0]['title'],
"author" : inter_msg[0]['authorPenname'],
"book_price" : inter_msg[0]['salePrice'],
"book_profile" : inter_msg[0]['descs'],
"book_coverpic" : inter_msg[0]['coverPic'],
}
# 拿到id构造详情页url
saleid = book['saleId']
yield scrapy.Request(
url=second_url.format(saleid), # 详情页URL
callback=self.second_url_func, # 指定详情页处理函数
meta={'data_list': data_list}, # 传递已获取的数据
headers=self.heasers
)
Ajax 访问#

这里我来详细讲解一下逻辑,在当当网上的书籍信息访问是使用的Ajax(AJAX(Asynchronous JavaScript and XML)是一种在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页内容的技术。)

上面代码我们输入的关键词为“经管”

这样的话我们就可以伪造 ajaxurl 就是上面代码的 baseurl,调试界面找到的url如下

baseurl = '''https://e.dangdang.com/media/api.go?action=mediaCategoryLeaf&promotionType=1&deviceSerialNo=html5&macAddr=html5&channelType=html5&permanentId=20251224082506162502645867115466839&returnType=json&channelId=70000&clientVersionNo=6.8.0&platformSource=DDDS-P&fromPlatform=106&deviceType=pconline&token=&start={}&end={}&category=GLX&dimension=dd_sale&order=0'''

ajax请求

这里的 startend 参数表示该次请求的范围,我的代码里是仿造该网的格式

for i in range(0,100,21):
url = baseurl.format(i,i+20)
yield scrapy.Request(url=url, callback=self.parse)
信息提取#

该访问的响应如图

信息

我们提取相关信息

res = response.text
data = json.loads(res)
thing = data['data']
salelist = thing['saleList']
# 得到数据
for i,book in enumerate(salelist):
# point_num = count + i
inter_msg = book['mediaList']
data_list = {
"inter_msg" : book['mediaList'],
"book_name" : inter_msg[0]['title'],
"author" : inter_msg[0]['authorPenname'],
"book_price" : inter_msg[0]['salePrice'],
"book_profile" : inter_msg[0]['descs'],
"book_coverpic" : inter_msg[0]['coverPic'],
}
进一步提取和保存#

然后这里我我想要书籍的 出版社和目录信息 ,但是上面的请求没有,所以我做了一个新的url拼接和跳转

saleid = book['saleId']
yield scrapy.Request(
url=second_url.format(saleid), # 详情页URL
callback=self.second_url_func, # 指定详情页处理函数
meta={'data_list': data_list}, # 传递已获取的数据
headers=self.heasers
)

上面传输已经拿到的数据,在新的页面也拿到出版社和目录信息的信息并将信息传给pipelines

book_contribution = html.xpath('//div[@class="explain_box"]/p[2]/span/a/text()')
book_contribution = book_contribution[0].strip() if book_contribution else ''
catalogue = html.xpath('//div[@class="txt"]/p/text()')
# 传给pipelines
yield {
'book_name': data_list['book_name'],
'author': data_list['author'],
'book_price': data_list['book_price'],
'book_profile': data_list['book_profile'],
'book_coverpic': data_list['book_coverpic'],
'book_contribution': book_contribution,
'catalogue': catalogue,
}
pipelines保存#
import csv
class DangdangSpiderPipeline:
def __init__(self):
self.filename = 'dangdang_books.csv'
self.file = None
self.writer = None
def open_spider(self, spider):
"""爬虫开始时调用"""
# 创建文件并写入表头
self.file = open(self.filename, 'w', newline='', encoding='utf-8-sig')
# 定义CSV表头
fieldnames = [
'书名', '作者', '价格', '简介',
'封面图', '出版社', '目录', #'序号'
]
self.writer = csv.DictWriter(self.file, fieldnames=fieldnames)
self.writer.writeheader()
def process_item(self, item, spider):
"""处理每个item"""
try:
# 转换为字典
book_data = dict(item)
# 准备写入CSV的数据
csv_data = {
'书名': book_data.get('book_name', ''),
'作者': book_data.get('author', ''),
'价格': book_data.get('book_price', ''),
'简介': book_data.get('book_profile', ''),
'封面图': book_data.get('book_coverpic', ''),
'出版社': book_data.get('book_contribution', ''),
'目录': self._format_catalogue(book_data.get('catalogue', '')),
# '序号': book_data.get('point_num', ''),
}
# 写入CSV
self.writer.writerow(csv_data)
except Exception as e:
spider.logger.error(f'写入数据时出错: {e}')
return item
def _format_catalogue(self, catalogue):
"""格式化目录数据"""
if isinstance(catalogue, list):
return ' | '.join([str(item) for item in catalogue])
return str(catalogue)
def close_spider(self, spider):
"""爬虫结束时调用"""
if self.file:
self.file.close()
spider.logger.info(f'数据已保存到 {self.filename}')

这里保存csv文件里

setting#

这里可能每个人都不一样 我的配置是

BOT_NAME = "dangdang_spider"
SPIDER_MODULES = ["dangdang_spider.spiders"]
NEWSPIDER_MODULE = "dangdang_spider.spiders"
ADDONS = {}
DOWNLOAD_DELAY = 1
RANDOMIZE_DOWNLOAD_DELAY = True
CONCURRENT_REQUESTS = 8
CONCURRENT_REQUESTS_PER_DOMAIN = 4
CONCURRENT_REQUESTS_PER_IP = 4
CONCURRENT_REQUESTS_PER_DOMAIN = 1
DOWNLOAD_DELAY = 1
COOKIES_ENABLED = True
FEED_EXPORT_ENCODING = "utf-8"
ITEM_PIPELINES = {
"dangdang_spider.pipelines.DangdangSpiderPipeline": 300,
}
EXTENSIONS = {
'scrapy.extensions.telnet.TelnetConsole': None,
}
运行#
Terminal window
scrapy crawl dangdang

在终端执行,需要有相关的环境,需要额外下载 lxmlpip install lxml

一些细节#

具体的执行过程 start_requests ==> parse ==> second_url_func ==> piplines

整个过程是异步执行,高效,分工明确,解耦度高

数据流过程

┌─────────────┐ 请求 ┌─────────────┐ 请求 ┌─────────────┐
│ 列表页API │ ──────> │ Spider │ ──────> │ 详情页HTML │
│ (start_urls)│ │ (parse()) │ │(second_url_func())
└─────────────┘ └─────────────┘ └─────────────┘
↑ ↑ ↑
│ JSON响应 │ HTML响应 │
└───────────────────────┴───────────────────────┘
┌─────────────┐
│ 提取合并数据 │
yield item │
└─────────────┘
┌─────────────┐
│Item Pipeline│
│ (清洗/存储) │
└─────────────┘

小结#

这里的案例需要一些爬虫的基础,其实还有很多可以优化的,简单了解一下scrapy到是够了,等我后面有时间再写一篇爬虫的吧

上面的案例体现了scrapy的高性能,scrapy的作用远远不止如此,middlewarespipeline还有很多可以玩的,可以去看看源码里是怎么实现的

Scrapy框架讲解
https://fuwari.vercel.app/posts/scrapy_try/scrapy/
作者
SiestaRem
发布于
2025-12-22
许可协议
CC BY-NC-SA 4.0