诉苦
最近是我的期末实验课周
连续五天的早八四节连上,感觉有一点死,正好课上是做爬虫相关的东西我就来记录一下吧
下面是我的课表

期末周的爬虫还是挺简单的,都是爬取一些电影评论,简介这些。 基本的爬虫都大差不差,一个模板就能处理大部分的东西了,访问,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的异步,高性能,高解耦度
安装
pip install scrapy创建
# 创建一个项目scrapy startproject dangdang_spider# 进入cd dangdang_spider# 生成爬虫 这里会在spider.py文件里生成一些相关的代码scrapy genspider dangdang e.dangdang.com以上都执行后会生成这样的项目结构

定义数据结构
# 文件在 item.pyimport 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.pyimport scrapyimport jsonfrom 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)是一种在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页内容的技术。)
上面代码我们输入的关键词为“经管”
这样的话我们就可以伪造 ajax 的 url 就是上面代码的 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'''
这里的 start 和 end 参数表示该次请求的范围,我的代码里是仿造该网的格式
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()') # 传给pipelinesyield { '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 = 1RANDOMIZE_DOWNLOAD_DELAY = TrueCONCURRENT_REQUESTS = 8CONCURRENT_REQUESTS_PER_DOMAIN = 4CONCURRENT_REQUESTS_PER_IP = 4CONCURRENT_REQUESTS_PER_DOMAIN = 1DOWNLOAD_DELAY = 1COOKIES_ENABLED = TrueFEED_EXPORT_ENCODING = "utf-8"ITEM_PIPELINES = { "dangdang_spider.pipelines.DangdangSpiderPipeline": 300,}
EXTENSIONS = { 'scrapy.extensions.telnet.TelnetConsole': None,}运行
scrapy crawl dangdang在终端执行,需要有相关的环境,需要额外下载 lxml,pip 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的作用远远不止如此,middlewares和pipeline还有很多可以玩的,可以去看看源码里是怎么实现的