Python爬虫-scrapy框架
前言
难走的路,从不拥挤
好走的路,人山人海
⚠️声明:本文所涉及的爬虫技术及代码仅用于学习、交流与技术研究目的,禁止用于任何商业用途或违反相关法律法规的行为。若因不当使用造成法律责任,概与作者无关。请尊重目标网站的
robots.txt协议及相关服务条款,共同维护良好的网络环境。
1.Scrapy框架
1.1简介
Scrapy 是一个开源的 Python 网络爬虫框架,用于从网站上抓取数据并进行处理。它可以高效、结构化地提取和存储网站信息。
Scrapy 的主要特点:
- 异步网络请求:Scrapy 基于 Twisted(Z一种异步网络库),能够同时处理多个 HTTP 请求,因此非常高效。
- 内置数据导出功能:Scrapy 支持将抓取的数据导出为多种格式,如 JSON、CSV、XML 等。
- 可定制的管道:可以通过自定义管道处理抓取的数据,例如清洗数据、存储到数据库等。
- 选择器:Scrapy 提供了简洁的选择器系统,可以通过 XPath 和 CSS 轻松提取网页中的信息。
- 用户代理伪装:Scrapy 支持模拟不同的用户代理,可以帮助绕过某些网站的反爬虫机制。
中文文档:https://docs.scrapy.net.cn/en/latest/
英文文档:https://docs.scrapy.org/en/latest/

1.2异步和非阻塞
异步 vs 非阻塞
| 对比点 | 异步(Asynchronous) | 非阻塞(Non-blocking) |
|---|---|---|
| 核心概念 | 任务的执行方式 | 调用的行为 |
| 执行模型 | 任务可以在等待结果时执行其他任务 | 调用立即返回,不等待结果 |
| 是否需要回调 | 需要(callback、future、async/await) | 可能需要轮询或回调 |
| 是否依赖事件驱动 | 是 | 不是必须 |
| 适用场景 | 适用于多任务并发,如爬虫、网络请求 | 适用于非阻塞 I/O,如非阻塞 socket |
关系
- 非阻塞 ≠ 异步,但异步通常是非阻塞的。
- 非阻塞 只是让调用立即返回,而异步 需要有一个调度机制(如事件循环)来管理任务的执行。
- 非阻塞 I/O 可以用于异步编程,但异步编程还涉及任务的调度和回调处理。
1.2.1异步
异步(Asynchronous)
异步指的是任务的执行方式,意味着一个任务可以在等待结果的同时去执行其他任务,而不需要同步等待结果返回。
- 任务的执行不按照顺序等待,而是由事件驱动的机制来决定何时执行。
- 任务可能会挂起(yield)并在未来的某个时间点恢复执行。
- 通常与回调(callback)、Promise/Future 或 async/await 机制结合使用。
代码
import asyncio
async def fetch_data():
print("开始获取数据")
await asyncio.sleep(2) # 模拟 IO 操作
print("数据获取完成")
return "数据"
async def main():
task = asyncio.create_task(fetch_data()) # 异步执行
print("做其他事情")
await task # 等待 fetch_data 结束
asyncio.run(main())
结果
开始获取数据
做其他事情
(等待2秒)
数据获取完成
在等待 fetch_data() 完成的同时,程序可以继续执行其他任务。
1.2.2非阻塞
非阻塞(Non-blocking)
非阻塞指的是调用方式,意味着调用一个函数时,它不会阻塞当前线程,而是立即返回,可以继续执行其他任务。
- 任务的执行不按照顺序等待,而是由事件驱动的机制来决定何时执行。
- 任务可能会挂起(yield)并在未来的某个时间点恢复执行。
- 通常与回调(callback)、Promise/Future 或 async/await 机制结合使用。
代码
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setblocking(False) # 设置非阻塞模式
try:
sock.connect(('www.example.com', 80))
except BlockingIOError:
print("非阻塞模式下,连接正在进行")
在非阻塞模式下,connect() 方法立即返回,而不会阻塞等待连接完成。
1.3Scrapy的工作流程
爬虫改的工作流程
使用队列的爬虫
Scrapy框架的执行流程

2.Scrapy框架的安装
使用miniconda创建scrapy环境并安装
# 创建 Scrapy 环境
conda create -n scrapy_py3.12 python=3.12
# 激活环境
conda activate scrapy_py3.12
# 安装 Scrapy
# 或者使用 Conda 安装:conda install -c conda-forge scrapy
pip install scrapy
# 验证安装,输出 Scrapy 版本
scrapy version
# 创建 Scrapy 爬虫项目
# 在你的项目存放目录下执行创建项目
scrapy startproject myspider
# 进入项目目录
cd myspider
# 创建爬虫
# scrapy genspider 爬虫名 允许爬取的域名
scrapy genspider baidu baidu.com
# 运行爬虫文件
scrapy crawl baidu
创建 Scrapy 环境

激活环境

安装 Scrapy

验证安装,输出 Scrapy 版本

创建 Scrapy 爬虫项目,我是进入到了我自己的项目目录下创建的

进入项目目录

创建爬虫

运行爬虫文件

使用Pycharm打开项目,目录结果如下

记得PyCharm设置解释器为刚才创建的scrapy_py3.12
3.数据提取
蜻蜓FM地址: https://m.qingting.fm/rank/
浏览器使用移动端模式打开

3.1创建项目
创建项目
# 创建项目目录
scrapy startproject fm
# 进入项目目录
cd fm
# 创建爬虫文件
scrapy genspider QingTingFM https://m.qingting.fm/rank/

3.2parse方法
我们发现爬虫脚本QingTingFMSpider继承了scrapy.Spider
并且parse的response的类型是scrapy.http.Response
parse还有一个参数**kwargs: Any

在Scrapy框架中可以使用cmdline.execute()方法来执行启动命令
if __name__ == '__main__':
# 默认启动方式
cmdline.execute("scrapy crawl QingTingFM".split())
# 忽略日志信息
# cmdline.execute("scrapy crawl QingTingFM --nolog".split())
3.3parse函数中的response参数
response 是 Scrapy 中的一个核心对象,它是从目标网站获取的 HTTP 响应,并且包含了网站返回的所有数据。这个对象包含许多有用的信息。以下是对 response 对象的详细解读:
response.url:返回请求的 URL 地址。response.status:HTTP 响应状态码。用于指示请求是否成功,或者发生了什么错误。response.headers:HTTP 响应头。它包含了许多与响应相关的元数据,例如内容类型、日期、服务器信息等。response.body:返回的响应体(即页面内容),它通常是 HTML 或其他类型的内容(如 JSON、XML、图片等)。response.request:返回原始请求的Request对象。可以用于访问原始的请求头、URL 等信息。response.meta:这是一个字典,通常用于存储和传递信息,比如在多个请求之间传递数据。它可以让你在爬虫的不同阶段传递数据,通常用于保存跨请求的信息。response.xpath():使用 XPath 表达式提取数据。返回的是一个SelectorList对象。response.css():使用 CSS 选择器提取数据。它是另一种从 HTML 页面中提取数据的方式,功能与xpath()类似。response.follow():用于追踪页面中的链接并发起新请求,通常用于爬取分页数据或者从页面中获取新的链接。response.json():如果响应体是 JSON 格式的数据,可以通过该方法将响应体解析为 Python 字典。response.encoding:响应内容的编码格式。如果Content-Type中包含了字符编码(如charset=UTF-8),则会根据该编码来解析响应体。response.request.url:请求地址。response.request.headers:请求头。

3.4数据解析
3.4.1response.xpath()
使用response响应对象中提供的xpath方法提取数据
使用response.xpath方法所获取的数据是类似列表的数据集SelectorList,其中包含的是selector对象。
def parse(self, response):
"""
:param response:
:return:
此函数为回调函数,对当前start_urls进行请求后,会将请求完成的响应对象传递给此函数
response参数接收响应对象
# """
a_list = response.xpath('//div[@class="rank-list"]/a')
# print(a_list)
for a_temp in a_list:
rank_number = a_temp.xpath('./div[@class="badge"]/text()') # 排名
img_url = a_temp.xpath('./img/@src') # 图片地址
title = a_temp.xpath('./div[@class="content"]/div[@class="title"]/text()') # 标题
desc = a_temp.xpath('./div[@class="content"]/div[@class="desc"]/text()') # 描述
play_number = a_temp.xpath('.//div[@class="info-item"][1]/span/text()')
print('解析的数据:', rank_number, img_url, title, desc, play_number)

3.4.2extract()
extract()方法:返回一个包含有字符串的列表
def parse(self, response):
"""
:param response:
:return:
此函数为回调函数,对当前start_urls进行请求后,会将请求完成的响应对象传递给此函数
response参数接收响应对象
"""
a_list = response.xpath('//div[@class="rank-list"]/a')
# print(a_list)
for a_temp in a_list:
rank_number = a_temp.xpath("./div[@class='badge']//text()").extract()
img_url = a_temp.xpath("./img/@src").extract()
title = a_temp.xpath("./div[@class='content']/div[@class='title']/text()").extract()
desc = a_temp.xpath("./div[@class='content']/div[@class='desc']/text()").extract()
play_number = a_temp.xpath(".//div[@class='info-item'][1]/span/text()").extract()
print('解析的数据:', rank_number, img_url, title, desc, play_number)

3.4.3extract_first()
extract_first方法:返回列表中的第一个字符串,列表为空返回None

4.管道
4.1相关概念
在 Scrapy 中,管道(Pipeline)是数据处理的关键组件之一。Scrapy 的管道机制允许你在爬虫抓取数据并进行解析后,进一步处理这些数据。管道通常用于存储、清理、过滤或者修改抓取的数据。
Scrapy 管道概述
管道是 Scrapy 用来处理爬虫返回数据的机制。每个管道类负责处理特定的数据操作,所有管道类是按顺序依次执行的。
- 主要作用:管道用于处理 Scrapy 爬虫抓取的 Item 数据。
- 工作流程:每个管道接收并处理爬虫返回的 Item 数据,然后将它传递给下一个管道。如果管道执行成功,Item 会继续向下传递;如果有任何错误,管道可能会丢弃这个 Item,或者采取其他处理措施。
Scrapy 管道的工作流程
-
Item 通过 Spider 返回:Spider 返回的 Item 会依次通过管道进行处理。
-
管道执行顺序:Scrapy 会按照管道的顺序(在
settings.py中定义的顺序)逐一调用每个管道进行数据处理。 -
管道的处理:每个管道都会收到一个 Item,执行指定的操作,如数据清理、存储、过滤等。
-
管道的返回值:
-
如果管道成功处理了 Item,必须返回该 Item,表示继续将该数据传递给下一个管道。
-
如果管道处理失败或者决定丢弃该 Item,可以返回
None。
-
管道类的基本结构
一个简单的 Scrapy 管道类需要继承 scrapy.pipelines.Pipeline 类,并实现 process_item() 方法。以下是一个最基本的管道类结构:
process_item() 方法接收两个参数:
item:传递给管道的数据项,通常是一个字典或scrapy.Item对象。spider:爬虫实例,允许你在管道中访问爬虫的一些属性。

管道配置
管道的执行顺序可以在 settings.py 中通过 ITEM_PIPELINES 配置。每个管道类都有一个数字优先级,数字越小的管道优先执行。例如:
ITEM_PIPELINES = {
'myproject.pipelines.MyPipeline': 1,
'myproject.pipelines.AnotherPipeline': 2,
}
在上述配置中,MyPipeline 会在 AnotherPipeline 之前执行。管道的优先级顺序决定了处理顺序。数字越小的管道优先执行。

管道的常见用途
- 清理和过滤数据:对抓取的数据进行清理,去除无用信息,或者过滤掉不符合要求的内容。
- 数据存储(数据库、文件等):将抓取的数据保存到数据库、文件、Excel 等存储介质中。
- 去重处理:用于去重 Item,避免重复抓取和存储。
- 文件下载和存储:如果抓取的数据是文件(如图片、PDF 文件等),可以使用管道来处理文件的下载和存储。
管道的高级功能
异步管道
如果需要在管道中执行网络请求或其他异步操作,Scrapy 提供了异步管道的支持。管道类可以通过 defer 或 async 来执行异步任务,并在处理完成后继续管道的执行。
from twisted.internet.defer import Deferred
from scrapy import Request
class AsyncPipeline:
def process_item(self, item, spider):
d = Deferred()
spider.crawler.engine.downloader.download(Request(item['url']), spider)
d.addCallback(self.handle_response, item)
return d
def handle_response(self, response, item):
# 处理异步响应
return item
管道返回 None 或抛出 DropItem
如果不希望继续处理某个 Item,可以返回 None,或者抛出 DropItem 异常。
from scrapy.exceptions import DropItem
class DropInvalidItemPipeline:
def process_item(self, item, spider):
if 'invalid' in item['title']:
raise DropItem(f"Invalid item found: {item['title']}")
return item
管道与爬虫的协作
-
爬虫 (
Spider):用于抓取数据。 -
下载器中间件(Downloader Middleware):在请求和响应之间进行处理。
-
管道(Pipeline):在抓取的数据返回给爬虫后处理它们。
Scrapy 的管道为你提供了一个强大的数据处理机制,能够高效地进行数据清理、存储、去重、过滤和转换等任务。
总结
Scrapy 的管道是一个用于处理抓取到的 Item 数据的机制,管道通过对数据的清理、存储、过滤等操作,使得抓取的过程更加灵活。你可以根据需要自定义管道类,并根据 ITEM_PIPELINES 配置它们的执行顺序。管道在数据抓取和处理过程中起到了关键作用,帮助你把抓取到的数据转化为最终的存储格式。
4.2yield
在 Scrapy 中,yield 用于返回数据(或请求)是因为 Scrapy 采用的是协程(generator)模型,它通过生成器的方式异步地进行请求和处理数据。
使用 yield 具有以下几个优势:
- 提高性能和内存利用率:
yield是 Python 中生成器的关键字,它会暂停当前函数的执行,并将控制权返回给调用者。通过生成器,你可以按需生成数据,而不是一次性将所有数据返回,避免了将大量数据一次性加载到内存中,从而提高了爬虫的性能和内存利用率。 - 异步处理: Scrapy 使用
yield来处理请求和数据的返回。每当一个请求返回时,Scrapy 会“暂停”当前的爬取操作,并立即继续爬取其他页面或处理其他任务。这种异步处理方式能有效提高爬虫的速度和响应效率。 - 避免阻塞: 使用
yield不会阻塞其他请求的执行,它让 Scrapy 能够并发地处理多个请求,避免了等待某个请求完成后才开始下一个请求的情况,从而加速了整个抓取过程。 - 简化代码: 使用
yield可以让 Scrapy 自动处理请求的返回和调度,而不需要手动管理请求队列、延迟等细节。Scrapy 会自动调度新的请求和数据,开发者只需要关注数据的提取和请求的生成。
代码
import scrapy
class MySpider(scrapy.Spider):
name = 'my_spider'
start_urls = ['http://example.com']
def parse(self, response):
# 提取数据并返回
yield {
'title': response.xpath('//title/text()').get(),
}
# 发起新的请求并继续处理
next_page = response.xpath('//a[@class="next"]/@href').get()
if next_page:
yield response.follow(next_page, self.parse)
4.3管道的使用
在 Scrapy 中,Item Pipelines 是用来处理抓取到的 Item 数据的。你可以在 Item Pipeline 中执行诸如清洗数据、保存到数据库、导出文件等操作。你通过配置 ITEM_PIPELINES 来启用和配置不同的管道类。
-
ITEM_PIPELINES是 Scrapy 设置项,用于指定哪些管道类(pipelines)将处理抓取到的 Item。 -
"fm.pipelines.FmPipeline"是管道类的路径,表示该管道类位于fm项目的pipelines.py文件中。 -
300是优先级数字,Scrapy 会根据数字的大小来决定管道的执行顺序,数字越小的管道优先执行。

解析完数据后,通过yield返回到下个管道处理数据保存
class FmPipeline:
def process_item(self, item, spider):
# print('pipline中的数据:', item)
mongo_client = pymongo.MongoClient()
collection = mongo_client['py_spider']['qingtingFM']
collection.insert_one(item)
print('数据插入成功:', item)
# return item

mongodb中数据已经保存完成

5.Scrapy框架执行流程
相关概念
Scrapy Engine:这是 Scrapy 的核心,它负责协调整个爬虫流程,管理各个组件的协作。
Scheduler(调度器):调度器负责接收爬虫生成的请求(Requests),并按优先级将它们加入到队列中。请求按顺序被发送到下载器进行下载。
Downloader(下载器):下载器负责从互联网下载网页并返回响应。它会根据需要将请求传递给下载器中间件(Downloader Middlewares)进行进一步处理。
Requests 和 Responses:请求(Requests)是从爬虫生成的,响应(Responses)是下载器获取网页内容后返回的。这些请求和响应在整个过程中流动,传递数据。
Spiders(爬虫):爬虫是 Scrapy 框架的核心,负责从起始 URL 开始抓取页面,处理响应并提取数据(Items),然后生成新的请求。
Item Pipeline(数据管道):当数据(Items)被提取后,它会被传递到数据管道进行进一步处理,例如存储数据或清洗数据。
Middlewares(中间件):Scrapy 使用中间件来处理请求和响应的流程,例如处理请求的延迟、修改请求头、处理重定向等。
主要流程总结
- 爬虫 提取数据并生成请求。
- 请求通过 调度器 排队。
- 请求被 下载器 发送到目标服务器。
- 服务器返回 响应,并通过下载器传递回来。
- 爬虫 解析响应并提取数据。
- 数据通过 数据管道 进行处理。
- 如有需要,爬虫会生成更多的请求,继续抓取。

6.蜻蜓FM爬虫
6.1创建项目
命令
# 创建项目
scrapy startproject qtfmspider
# 打开项目目录
cd qtfmspider
# 创建爬虫脚本
scrapy genspider qingtingfm qingting.fm/rank

6.2项目配置
6.2.1可访问域名
在 Scrapy 中,allowed_domains 是一个用于限制爬虫访问的域名列表。这个设置确保爬虫只会抓取来自指定域名的页面,避免爬取其他不相关的站点。
allowed_domains = ["qingting.fm", "pic.qtfm.cn"]

这表示爬虫只会从 qingting.fm 和 pic.qtfm.cn 这两个域名抓取数据。这样做有助于防止爬虫意外访问外部站点,并且能提高抓取的精确度。
注意事项:
qingting.fm和pic.qtfm.cn是完整的主域名。通常,allowed_domains中的域名应该包括主域名,并且不包括http://或https://。pic.qtfm.cn是qingting.fm的子域名,所以可以同时添加这两个域名,确保能够抓取来自主域名和子域名的内容。- 如果你只想抓取
qingting.fm这个站点的内容,而不希望爬取pic.qtfm.cn上的图片,建议只添加qingting.fm,这样爬虫就不会请求pic.qtfm.cn上的资源。
这样配置之后,爬虫只会抓取 qingting.fm 和 pic.qtfm.cn 的页面和资源,确保数据抓取的集中性和安全性。
我们需要访问图片,从而下载图片,所以需要配置图片的域名

6.2.2数据管道
ITEM_PIPELINES 是 Scrapy 中用来指定和启用数据管道(Item Pipelines)的设置。 ITEM_PIPELINES 表示启用了 qtfmspider.pipelines.QtfmspiderPipeline 这个管道,并且给它指定了优先级 300。
在 Scrapy 中,ITEM_PIPELINES 是一个字典,键是管道的路径,值是一个整数,表示管道的执行顺序。数字越小,优先级越高,越先执行。
ITEM_PIPELINES = {
"qtfmspider.pipelines.QtfmspiderPipeline": 300,
}
qtfmspider.pipelines.QtfmspiderPipeline是管道类的路径。管道类会在爬虫抓取完一项数据(Item)后处理数据。你需要确保QtfmspiderPipeline类存在于pipelines.py文件中。300是管道的优先级。默认情况下,优先级是按照数字顺序执行的,数字小的先执行。

6.2.3robots协议
地址:https://m.qingting.fm/robots.txt
蜻蜓FM的robots协议
-
User-agent: *表示适用于所有爬虫 -
Disallow: /*表示禁止访问所有包含问号(?)的 URL。 -
Disallow: /?*也有类似的效果,禁止访问以问号开头的 URL。 -
Disallow:后面没有指定路径时,表示允许访问所有页面。

在 Scrapy 中,ROBOTSTXT_OBEY 是一个配置项,用来决定是否遵守网站的 robots.txt 文件的规则。如果将其设置为 False,Scrapy 就会忽略 robots.txt 中的限制,允许爬取任何页面。
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
-
ROBOTSTXT_OBEY = False:表示 Scrapy 会忽略robots.txt文件中的规则,允许抓取网站上所有的页面,尽管robots.txt中可能会指定某些页面不允许抓取。 -
ROBOTSTXT_OBEY = True:表示 Scrapy 会遵守robots.txt文件中的规定,只有允许的页面会被爬取。

不配置会出现以下错误

6.2.4type字段
在 Scrapy 中,yield 用来生成一个 Item 或者一个字典(dict)来传递抓取到的数据给爬虫的管道(Item Pipeline)。通过 yield 语句,Scrapy 逐步处理并返回抓取到的结果,而不是将所有结果一次性返回。Scrapy 会根据这些 yield 返回的结果来执行后续的处理,比如数据存储、去重、或者进一步的抓取。
在 Scrapy 中,type 字段是一个自定义字段,用于标识当前 Item 的类型。它可以帮助你在数据处理和存储过程中进行分类和管理。通过 yield 语句,Scrapy 将抓取到的结果传递给后续的处理阶段(如 Item Pipeline),并通过 type 字段进行不同类型的处理。这种方式有助于在复杂爬虫中有效管理不同类型的数据。
save_img告诉管道此数据需要进行图片保存
save_info告诉管道此数据进行数据保存

6.3代码
parse方法
# 创建发送图片请求
yield scrapy.Request(img_url, callback=self.parse_image, cb_kwargs={"img_name": title})
parse_image方法
def parse_image(self, response, img_name):
"""
解析图片
:param response:
:param image_name:
:return:
"""
# print('图片解析方法:', response.url)
# print(image_name)
# print(response.body)
yield {
'type': 'save_img',
"img_name": img_name + ".png",
"img_content": response.body
}
process_item方法
class QtfmspiderPipeline:
def process_item(self, item, spider):
# 获取返回的数据类型
type_ = item.get('type')
if type_ == 'save_img':
# getcwd(): 用于获取当前工作目录(Current Working Directory)的路径
download_path = os.getcwd() + '/download/'
if not os.path.exists(download_path):
os.mkdir(download_path)
# 图片保存
img_name = re.sub(r'[\\/*?:"<>|]', "", item.get("img_name"))
image_content = item.get("img_content")
with open(download_path + img_name, "wb") as f:
f.write(image_content)
print("图片保存成功: ", img_name)
elif type_ == 'save_info':
mongo_client = pymongo.MongoClient()
collection = mongo_client['py_spider']['qingtingfm']
collection.insert_one(item)
print('数据插入成功:', item.get('title'))
else:
print('数据类型不符合规定...')

7.豆瓣Top250爬虫
7.1创建项目
命令
# 创建项目
scrapy startproject douban
# 打开项目目录
cd douban
# 创建爬虫脚本
scrapy genspider doubantop250 "https://movie.douban.com/top250?start=0&filter="

7.2项目配置
7.2.1robots协议
豆瓣robots协议地址:https://movie.douban.com/robots.txt

配置豆瓣项目关闭rebots.txt验证。

7.2.2配置User-Agent
有以下2中方式配置User-Agent
DEFAULT_REQUEST_HEADERS
在 Scrapy 中,DEFAULT_REQUEST_HEADERS 是一个配置项,用于定义默认的 HTTP 请求头。这些请求头会在你发起 HTTP 请求时自动附加到请求中,帮助你模拟浏览器行为,以便绕过一些简单的反爬机制(例如,检查 User-Agent,Referer 等)。配置请求头可以让你在进行网络爬取时更加灵活和隐蔽。
# 也可以在headers中配置
# Override the default request headers:
DEFAULT_REQUEST_HEADERS = {
# "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
# "Accept-Language": "en",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36"
}
User-Agent
在 Scrapy 中,User-Agent 配置是用来模拟浏览器或客户端发送 HTTP 请求时的一种方式。User-Agent 是 HTTP 请求头(headers)中的一个字段,用于告知服务器请求是来自哪种操作系统、浏览器或设备。配置这个字段在爬虫开发中非常重要,主要用于绕过反爬虫机制和模拟不同的客户端环境。
在配置文件中设置的请求头是固定的, 如果发送的请求过多也可能造成当前请求头失效。
所以需要在请求的过程中要对请求头进行随机变换,想要完成这种功能需要借助中间件完成。
# 配置 USER_AGENT
# USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36"

7.2.4分页
检查页面上是否存在“下一页”的链接。如果存在,就抓取这个链接并递归地调用 parse 方法,以抓取下一页的数据。
- 首先,代码通过
response.xpath("//span[@class='next']/a/@href")获取“下一页”链接的相对 URL。如果该链接存在(即存在.next类的分页元素),说明还有下一页。 - 使用
response.urljoin(next_url)将相对 URL 转换为绝对 URL。 - 然后,发起请求
scrapy.Request(url=next_url, callback=self.parse)来抓取下一页的数据,并指定回调函数parse继续处理抓取到的页面。 - 如果不存在下一页(即没有
.next类的元素),表示分页已经结束,打印爬取成功...。

7.2.3其他配置
这些配置上面都写过了,这里不详解了
添加图片的域名

开启数据管道

type参数
cb_kwargs的参数名向和回调函数callback的参数名一致

7.3代码
Doubantop250Spider
import scrapy
from scrapy import cmdline
class Doubantop250Spider(scrapy.Spider):
name = "doubantop250"
allowed_domains = ["movie.douban.com", 'doubanio.com']
start_urls = ["https://movie.douban.com/top250?start=0&filter="]
def parse(self, response):
li_list = response.xpath("//ol[@class='grid_view']/li")
for li in li_list:
img_url = li.xpath(".//img/@src").extract_first()
title = li.xpath(".//span[@class='title'][1]/text()").extract_first()
rating_num = li.xpath(".//span[@class='rating_num']/text()").extract_first()
star = li.xpath(".//div[@class='star']/span[4]/text()").extract_first()
# 数据保存
yield {
'type': 'save_info',
'image_url': img_url,
'title': title,
'rating_num': rating_num,
'star': star
}
# 请求图片链接,进行下载图片
yield scrapy.Request(url=img_url, callback=self.parse_img, cb_kwargs={'img_name': title})
# 翻页
if response.xpath("//span[@class='next']/a/@href"):
next_url = response.urljoin(response.xpath("//span[@class='next']/a/@href").extract_first())
print('开始抓取下一页:', next_url)
yield scrapy.Request(url=next_url, callback=self.parse)
else:
print('爬取成功...')
@staticmethod
def parse_img(response, img_name):
yield {
'type': 'save_img',
'img_name': img_name + '.jpg',
'img_content': response.body
}
if __name__ == '__main__':
cmdline.execute('scrapy crawl doubantop250'.split())
DoubanPipeline
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
import os
import pymongo
# useful for handling different item types with a single interface
from itemadapter import ItemAdapter
class DoubanPipeline:
def process_item(self, item, spider):
type_ = item.get('type')
if type_ == 'save_info':
mongo_client = pymongo.MongoClient()
collection = mongo_client['py_spider']['doubantop250']
collection.insert_one(item)
print('添加成功:', item.get('title'))
elif type_ == 'save_img':
download_path = os.getcwd() + '/download/'
if not os.path.exists(download_path):
os.mkdir(download_path)
img_name = item.get('img_name')
image_content = item.get('img_content')
with open(download_path + img_name, 'wb') as f:
f.write(image_content)
print('下载成功:', img_name)
else:
print(f'type参数有误:{type_}')
return item
分页爬取了1次,IP就被封了

8.Scrapy中间件
8.1概念
在 Scrapy 中,中间件(Middleware)是用于处理请求和响应的组件,可以在 Scrapy 的请求处理过程中进行各种操作,比如修改请求、响应,或者执行一些额外的逻辑。Scrapy 中间件分为两类:
- 请求中间件(Request Middleware):处理发送到网站的请求。
- 响应中间件(Response Middleware):处理从网站返回的响应。
Scrapy 中的中间件非常强大,允许你在请求和响应的过程中进行自定义操作。通过灵活配置中间件,你可以控制请求的重试、延迟、设置 headers、处理 cookies 等,增强爬虫的稳定性和功能。
根据Scrapy运行流程中所在位置不同分为:
-
下载中间件
-
爬虫中间件
中间件的工作流程
在 Scrapy 中,流程大致如下:
- 请求首先通过 请求中间件,可以修改请求或执行一些操作(如设置 User-Agent,重试请求等)。
- 请求会被发送到目标网站,等待响应。
- 返回的响应会通过 响应中间件,可以修改响应内容或者执行一些额外的操作(如数据清洗,修改响应的编码等)。
- 最终响应会传递给爬虫进行解析。
常见中间件
Scrapy 提供了几个内置的中间件,常用的包括:
- UserAgentMiddleware:为每个请求添加一个
User-Agent。 - RetryMiddleware:如果请求失败(如超时、HTTP 错误等),则自动重试。
- HttpCacheMiddleware:启用 HTTP 缓存,使得相同的请求在短时间内不会被重新发送。
- RedirectMiddleware:处理 HTTP 重定向(3xx)。
- CookiesMiddleware:处理请求中的 cookies。
但在Scrapy默认的情况下,两种中间件都在middlewares.py一个文件中。爬虫中间件使用方法和下载中间件相同,且功能重复,常使用下载中间件。
8.2process_request
在 Scrapy 中,process_request 是 请求中间件 中的方法之一,它用于在请求发送之前进行处理。你可以在 process_request 方法中对请求对象 (request) 进行修改,例如设置请求头、代理、添加一些元数据,或者做一些额外的操作,比如日志记录。
process_request 方法的定义如下:
def process_request(self, request, spider):
# 处理请求
return None
参数解释:
request:当前正在处理的请求对象,包含了该请求的 URL、headers、cookies 等信息。spider:当前爬虫对象,包含爬虫的配置信息。
返回值:
- 如果中间件没有修改请求,返回
None,请求会继续传递到下一个中间件或者最终发送到目标网站。 - 如果你希望中止请求流程(比如直接返回一个响应),可以在
process_request方法中返回一个Response对象或者一个Request对象来替代原本的请求。这样会立刻停止对该请求的处理,跳过后续的中间件处理。
8.3process_response
在 Scrapy 中,process_response 是 响应中间件 中的方法之一,它在响应返回到 Scrapy 框架时被调用,允许你对响应进行修改或者执行其他的操作,比如清洗数据、修改响应的内容、处理编码问题等。
process_response 方法的定义如下:
def process_response(self, request, response, spider):
# 处理响应
return response
参数解释:
request:返回响应的请求对象。这个请求对象包含了发送该请求时的 URL、headers、meta 数据等信息。response:从目标网站返回的响应对象。通常是一个scrapy.http.Response实例,包含了网页内容、状态码等信息。spider:当前爬虫对象,包含爬虫的配置信息。
返回值:
- 修改响应:如果你希望对响应做一些修改,可以在
process_response方法中对response进行修改,并返回修改后的response。 - 中止响应处理:如果你希望中止后续的中间件处理(比如直接返回一个替代响应),可以返回一个
Response对象,Scrapy 会停止后续的中间件处理。
8.4实现随机User-Agent
8.4.1创建项目
命令
# 创建项目
scrapy startproject middwaredemo
# 打开项目目录
cd middwaredemo1
# 创建爬虫脚本
scrapy genspider doubantop250 "https://movie.douban.com/top250?start=0&filter="

8.4.2配置
配置下载中间件

然后关闭robots

暂时关闭分页,容易导致IP封禁

8.4.3实现UserAgentDownloaderMiddleware
class UserAgentDownloaderMiddleware:
USER_AGENTS_LIST = [
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)",
"Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)",
"Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)",
"Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0",
"Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5"
]
def process_request(self, request, spider):
print("------UA中间件----")
# 随机选择UA
user_agent = random.choice(self.USER_AGENTS_LIST)
request.headers['User-Agent'] = user_agent
# 不写return
"""
如果返回None, 表示当前的response提交下一个权重低的process_request。
如果传递到最后一个process_request,则传递给下载器进行下载。
"""

8.5DOWNLOAD_DELAY(请求延迟)
地址:https://docs.scrapy.net.cn/en/latest/topics/settings.html#std-setting-DOWNLOAD_DELAY

在 Scrapy 中,DOWNLOAD_DELAY 是一个非常重要的配置项,它用于控制请求之间的间隔时间(即每个请求发送之间的延迟)。设置这个参数可以帮助你遵守目标网站的抓取规则,减少因频繁请求而导致被封锁的风险,同时也有助于减轻目标网站的负载。
DOWNLOAD_DELAY 用来指定 Scrapy 在两个连续请求之间的最小延迟时间(单位:秒)。它可以帮助你调整爬虫的请求频率,从而避免目标网站的反爬虫机制(如频繁请求导致 IP 被封禁)。
默认值:
默认情况下,DOWNLOAD_DELAY 为 0,即没有延迟,Scrapy 会尽可能地快速发送请求。
语法:
DOWNLOAD_DELAY = 0
工作原理
当 Scrapy 发送请求时,它会在每个请求之间插入一个延迟时间。具体延迟的时间是由 DOWNLOAD_DELAY 设置的值决定的。该值是爬虫每发送一个请求后暂停的时间,防止目标网站被过度负载。
例如:
- 如果
DOWNLOAD_DELAY = 1.0,那么每个请求之间会有 1 秒的延迟。 - 如果
DOWNLOAD_DELAY = 2.5,那么每个请求之间会有 2.5 秒的延迟。

8.6start_requests(分页)
之前分页我们使用urljoin拼接分页链接进行请求,因为该网站是个静态网站,我们可以获取页面链接。
对于动态网站,我们则需要通过请求api的方式请求分页,这里就是用到了start_requests

在 Scrapy 中,start_requests 方法是爬虫开始抓取的入口点之一。它是 Scrapy 爬虫类中的一个默认方法,可以用来生成初始请求并开始爬取数据。通常,start_requests 方法用来生成爬虫启动时需要抓取的第一个请求列表,之后 Scrapy 会根据这些请求开始抓取网页。
start_requests 是一个生成器函数,用来返回初始的 Request 对象。在 Scrapy 中,yield 语句用于返回一个请求 (Request),它会将该请求交给 Scrapy 引擎处理,然后继续执行后面的代码。当 Scrapy 处理完当前请求之后,会继续调用该方法生成下一个请求。
简单理解,就是生成所有的分页链接,然后通过 scrapy.Request进行请求
yield 语句会返回一个 scrapy.Request 对象,它表示一个 HTTP 请求,Scrapy 引擎会将这个请求加入到调度队列中,然后发起请求。scrapy.Request(url) 会将构建好的 URL 作为请求发送到目标网站。
Scrapy 会通过中间件处理这个请求,并在请求响应回来后,调用爬虫类中定义的回调方法(通常是 parse 方法),然后继续抓取数据。
def start_requests(self):
for page in range(0, 3):
url = f'https://movie.douban.com/top250?start={page * 25}&filter='
yield scrapy.Request(url)
这里记得修改DOWNLOAD_DELAY=3,我这里保险起见只爬取3页,防止IP封禁
还有这几个配置

爬取完成

8.6设置IP代理
8.6.1免费代理
def process_request(self, request, spider):
print("------UA中间件------")
# 随机选择UA
user_agent = random.choice(self.USER_AGENTS_LIST)
request.headers['User-Agent'] = user_agent
# 设置免费代理: 有些代理在发送请求时会失败
request.meta['proxy'] = 'http://127.0.0.1:10809'
# print(request.meta)
return None
# 不写return
"""
如果返回None, 表示当前的response提交下一个权重低的process_request。
如果传递到最后一个process_request,则传递给下载器进行下载。
"""
def process_response(self, request, response, spider):
print('------process_response------')
# response.status = 302
if response.status != 200:
# raise TimeoutError
request.dont_filter = True # 关闭过滤, 可以发送之前发送过的请求
return request
return response
8.6.2付费代理
快代理文档:https://www.kuaidaili.com/doc/dev/sdk_http/#proxy_python-scrapy
大致也是通过中间件实现

8.7在Scrapy中使用Selenium
爬取腾讯招聘网站:https://careers.tencent.com/search.html

8.7.1创建项目
命令
# 创建项目
scrapy startproject TxJob
# 打开项目目录
cd TxJob
# 创建爬虫脚本
scrapy genspider TxJobSpider https://careers.tencent.com/search.html

8.7.2分析页面
地址:https://careers.tencent.com/search.html?index=2
分页会根据地址栏参数index获取每页数据
页面比较简单,之前也爬取过,获取每个职位的div,然后再获响应的数据即可
//div[@class='correlation-degree']//div[@class='recruit-list']

8.7.3Spider
爬虫模块:
- 获取要爬取的分页地址
- 解析页面
TxjobPipeline管道获取每个职位信息后进行打印
def start_requests(self):
url = 'https://careers.tencent.com/search.html?index={}'
# 方便测试 只爬3页
for page in range(1, 4):
yield Request(url=url.format(page))
def parse(self, response):
div_list = response.xpath("//div[@class='correlation-degree']//div[@class='recruit-list']")
for div in div_list:
item = dict()
item['title'] = div.xpath('./a//span[@class="job-recruit-title"]/text()').extract_first()
item['department'] = div.xpath('./a/p[1]/span[1]/text()').extract_first()
item['address'] = div.xpath('./a//span[2]/text()').extract_first()
item['post'] = div.xpath('./a/p[1]/span[3]/text()').extract_first()
item['date'] = div.xpath('./a/p[1]/span[last()]/text()').extract_first()
item['recruit_data'] = div.xpath('./a/p[2]/text()').extract_first()
yield item
if __name__ == '__main__':
cmdline.execute('scrapy crawl TxJobSpider'.split())

8.7.4Selenium
8.7.4.1使用Selenium的方式
在 Scrapy Spider 中使用 Selenium 和在 Middleware 中使用 Selenium 各有优缺点,主要的区别在于 灵活性 和 代码结构。下面我们详细对比它们的区别,帮助你选择合适的方式。
| 方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 在 Spider 里用 Selenium | 只需部分页面使用 Selenium,或者需要模拟用户交互(如点击、滚动、输入) | 灵活、可控,适合复杂页面 | 代码耦合,影响 Scrapy 性能 |
| 在 Middleware 里用 Selenium | 所有页面都依赖 Selenium 渲染 | 代码整洁,多个 Spider 共享 WebDriver | 影响 Scrapy 并发,无法灵活控制 Selenium 交互 |
总结
- 如果只有少量页面需要 Selenium,在 Spider 里用 Selenium,更灵活,性能更高。
- 如果所有页面都要 Selenium 解析,可以在 Middleware 里用 Selenium,代码清晰,但性能较低。
我们这里是静态网站,所以需要在Middleware 里用 Selenium
8.7.4.2安装scrapy-selenium
首先需要安装scrapy-selenium
pip install scrapy-selenium

8.7.4.3代码
主要用于:
- 处理传递给
Spider的Response - 处理
Spider生成的Request或Item
webdriver.Chrome():在 __init__ 方法中,创建了Chrome WebDriver实例。
from_crawler 是 Scrapy 组件生命周期管理的工厂方法,用于实例化 SeleniumDownloaderMiddleware 中间件。Scrapy 启动爬虫时,会调用 from_crawler 方法创建中间件实例 s。
- 它允许访问 Scrapy 的
crawler对象 - 获取全局配置(settings),如果需要读取 Scrapy 配置项,可以通过
crawler.settings访问。 - 绑定 Scrapy 的信号(signals),在爬虫关闭时执行
spider_closed方法,防止 WebDriver 进程残留。
crawler.signals.connect(s.spider_closed, signal=signals.spider_closed):
- 监听 Scrapy 的
spider_closed信号,当爬虫关闭时,调用spider_closed方法来关闭 WebDriver,防止资源泄露。
process_request 是 Scrapy 下载中间件的核心方法,用于拦截请求,并让 Selenium 加载网页、执行 JavaScript,然后返回 HTML 内容。
class SeleniumDownloaderMiddleware:
def __init__(self):
self.browser = webdriver.Chrome()
# 检测爬虫状态
@classmethod
def from_crawler(cls, crawler):
s = cls()
crawler.signals.connect(s.spider_closed, signal=signals.spider_closed)
return s
def process_request(self, request, spider):
self.browser.get(request.url)
wait = WebDriverWait(self.browser, 10)
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.recruit-list')))
body = self.browser.page_source
return HtmlResponse(url=request.url, body=body, encoding='utf-8')
def spider_closed(self):
self.browser.quit()
配置settings.py
ROBOTSTXT_OBEY = False # 关闭robots一些
DOWNLOAD_DELAY = 1 # 请求延时1s 防止ip封禁
# 下载中间件 使用selenium功能
DOWNLOADER_MIDDLEWARES = {
# "TxJob.middlewares.TxjobDownloaderMiddleware": 543,
"TxJob.middlewares.SeleniumDownloaderMiddleware": 543
}
# 开启管道 用于处理数据
ITEM_PIPELINES = {
"TxJob.pipelines.TxjobPipeline": 300,
}

启动爬虫,爬取了3页,每页10条

9.多管道使用
Scrapy 允许使用多个管道(Pipelines)来处理不同的任务。多个管道可以按照特定的顺序处理数据,每个管道都能对数据进行独立的处理或修改。
流程:
- 数据从爬虫传递到管道:每个 item 被爬虫返回后会被传递到管道进行处理。
- 多个管道执行:Scrapy 按照管道的顺序处理数据,直到所有的管道都执行完毕。
- 管道的返回值:
- 如果管道返回
None,Scrapy 会跳过当前管道,进入下一个管道。 - 如果管道返回修改过的 item,Scrapy 会继续传递该 item 到下一个管道。
- 如果管道返回
- 配置多个管道:在
settings.py中,配置ITEM_PIPELINES字典,其中管道的值是管道的类路径,优先级是一个整数,数值越小,优先级越高。Scrapy 会按照优先级的顺序执行管道。
ITEM_PIPELINES = {
'myproject.pipelines.PipelineOne': 1, # 优先级 1
'myproject.pipelines.PipelineTwo': 2, # 优先级 2
'myproject.pipelines.PipelineThree': 3, # 优先级 3
}
创建TxJobFilePipeline和TxJobMongoDBPipeline用于将数据保存文件和保存到MongoDB
根据settings.py配置先执行TxJobFilePipeline在执行TxJobMongoDBPipeline
open_spider 方法是 Scrapy 管道(Pipeline)中的一个钩子方法,在爬虫启动时只执行 一次,用于进行一些初始化操作。
class TxJobFilePipeline:
"""
保存json文件
"""
def open_spider(self, spider):
# 爬虫开启时仅运行一次
if spider.name == 'TxJobSpider':
self.file_obj = open('tx_json_data.txt', 'a', encoding='utf-8')
def process_item(self, item, spider):
if spider.name == 'TxJobSpider':
print('TxJobFilePipeline被执行...')
self.file_obj.write(json.dumps(item, ensure_ascii=False, indent=4) + '\n')
return item # 将数据写入完成之后把原数据传递给下一个pipeline
def close_spider(self, spider):
# 爬虫关闭时仅运行一次
if spider.name == 'TxJobSpider':
self.file_obj.close()
class TxJobMongoDBPipeline:
"""
保存MongoDB
"""
def open_spider(self, spider):
# 爬虫开启时仅运行一次
if spider.name == 'TxJobSpider':
print('open_spider被执行...')
self.mongo_client = pymongo.MongoClient()
self.collection = self.mongo_client['py_spider']['tx_job_info']
def process_item(self, item, spider):
if spider.name == 'TxJobSpider':
print('TxJobMongoDBPipeline被执行...')
self.collection.insert_one(item)
print('数据插入成功:', item)
# 没有管道了 可以不用return
# return item # 将数据写入完成之后把原数据传递给下一个pipeline
def close_spider(self, spider):
# 爬虫关闭时仅运行一次
if spider.name == 'TxJobSpider':
print('close_spider被执行...')
self.mongo_client.close()

文件和Mongodb都保存成功

10.数据去重
10.1数据去重
该管道的作用是去重,防止爬取的重复数据进入数据库或文件存储。它使用 Redis + MD5 哈希 来进行数据去重。
-
使用 Redis 作为存储介质
-
对 Item 进行 MD5 哈希生成唯一标识
-
利用 Redis 存储已处理的 Item 哈希值 进行去重
class TxJobCheckPipeline:
def open_spider(self, spider):
if spider.name == 'TxJobSpider':
self.redis_client = redis.Redis()
def process_item(self, item, spider):
if spider.name == 'TxJobSpider':
# 将传递过来的item数据转为字符串并加密成md5数据
item_str = json.dumps(item, ensure_ascii=False, indent=4)
md5_hash = hashlib.md5()
md5_hash.update(item_str.encode('utf-8'))
hash_value = md5_hash.hexdigest()
# 判断hash值是否存在于redis中
if self.redis_client.get(f'tx_job_item_filter:{hash_value}'):
# 如果存在则抛出异常停止管道传递数据
raise DropItem('数据已存在...')
else:
# 如果不存在则将hash保存到redis中
# tx_work_filter:前缀会在redis中创建文件夹, 便于管理
self.redis_client.set(f'tx_job_item_filter:{hash_value}', item_str)
return item
def close_spider(self, spider):
if spider.name == 'TxJobSpider':
self.redis_client.close()

10.2地址去重
通过生成 URL 的 MD5 值并使用 Redis 来记录已经访问过的 URL,避免对相同页面进行重复抓取。
-
生成分页 URL;
-
使用 MD5 对每个 URL 进行哈希;
-
检查 URL 是否已经抓取过(通过 Redis 存储哈希值);
-
如果未抓取过,则发起请求,并将 URL 的哈希值存入 Redis。
def start_requests(self):
url = 'https://careers.tencent.com/search.html?index={}'
# 方便测试 只爬3页
for page in range(1, 4):
md5_hash = hashlib.md5()
md5_hash.update(url.format(page).encode('utf-8'))
url_md5_value = md5_hash.hexdigest()
if self.redis_client.get(f'txjob_url_filter:{url_md5_value}'):
print(f"地址重复:{url.format(page)}")
continue
else:
self.redis_client.set(f'txjob_url_filter:{url_md5_value}', url.format(page))
yield Request(url=url.format(page))

11.增量爬虫
这条命令启动名为 MySpider 的爬虫,并设置爬虫状态存储目录为 crawls/my_spider-1。如果爬虫中途因故停止,它可以从该目录中的状态信息继续爬取,不必重新开始爬取。
-
scrapy crawl是 Scrapy 的命令行接口中的一个指令,用于启动一个爬虫并执行抓取任务。 -
MySpider爬虫的名称,Scrapy 会根据该名称找到相应的爬虫类并开始爬取。 -
-s是 Scrapy 中的一个参数,用于设置自定义配置。 -
JOBDIR是 Scrapy 的一个配置项,它指定了爬虫的 "job directory"(作业目录),用于存储爬虫的状态信息。- Scrapy 的爬虫会在每次执行时,保存抓取过程中的一些信息,例如已经爬取过的 URL 列表、请求的状态等。这样,如果爬虫由于某些原因中断(比如机器重启、网络问题等),它可以恢复之前的工作,而不是从头开始抓取所有数据。
JOBDIR=crawls/my_spider-1指定了存储状态信息的目录路径,在本例中,该目录是crawls/my_spider-1。目录crawls/my_spider-1中会存储该爬虫运行时的状态信息。- 这个目录的名字
my_spider-1可以帮助区分不同的爬虫任务,避免不同爬虫的状态信息混在一起。如果需要恢复爬虫,可以指定相应的JOBDIR目录来加载爬虫的状态。
# 例子:scrapy crawl TxJobSpider -s JOBDIR=crawls/TxJobSpider-1
scrapy crawl MySpider -s JOBDIR=crawls/my_spider-1
第一次使用会启动爬虫

在终端启动爬虫之后,只需要按下ctrl + c就可以让爬虫暂停
注意点:ctrl + c不能执行两次,只需一次即可

再次执行,爬虫会继续运行
启动爬虫和回复爬虫指令一样
scrapy crawl TxJobSpider -s JOBDIR=crawls/TxJobSpider-1

12.dont_filter
在 Scrapy 中,dont_filter 是一个用于控制请求去重机制的参数。默认情况下,Scrapy 会自动去重请求,以避免重复抓取相同的页面。dont_filter 允许你在特定情况下跳过请求去重检查,强制爬虫发出请求,即使该请求已经存在于去重队列中。
默认请求去重机制
Scrapy 在发出请求时会使用去重机制(通过请求的 url 和 meta 信息)来防止重复请求。如果 Scrapy 检测到已经请求过某个 URL,它会跳过该请求并不会再次发送。
dont_filter参数
dont_filter是 Scrapy 中Request对象的一个布尔值参数。当你发出请求时,如果将dont_filter=True,Scrapy 就不会对该请求进行去重处理,确保该请求被执行。- 如果
dont_filter=False(默认值),Scrapy 会对该请求进行去重检查,防止重复请求。 - 如果
dont_filter设置为True,该请求将被强制发出,即使它之前已经被访问过。
使用场景:强制重新抓取相同页面、避免去重对特定请求的影响等。
scrapy.Request初始化方法部分源码:

13.start_requests
start_requests 是 Scrapy 爬虫中一个重要的方法,它在爬虫启动时被调用,用于生成并发起初始请求。Scrapy 爬虫通常会在 start_requests 方法中定义抓取的起始页面(或一系列页面),然后根据这些起始请求来进行后续的页面抓取。
start_requests 方法的主要作用是生成爬虫开始抓取的初始请求。默认情况下,Scrapy 会使用爬虫类中的 start_urls 属性作为初始请求的 URL 列表,但是你也可以自定义 start_requests 方法以便更灵活地控制请求的生成和处理。
start_requests 的工作原理
- Scrapy 启动时调用:当你运行
scrapy crawl <spider_name>命令时,Scrapy 会在爬虫启动时调用start_requests方法,生成并发起初始请求。 - 返回
Request对象:start_requests方法需要返回一个或多个Request对象,Scrapy 会调度并发送这些请求。 - 请求的回调:每个请求都应该有一个回调方法,通常是
parse,Scrapy 会在请求完成时调用该方法来处理响应。
14.Post请求
目标站点:http://www.cninfo.com.cn/new/commonUrl?url=disclosure/list/notice#szse

14.1创建项目
命令
# 创建项目
scrapy startproject CNInfo
# 打开项目目录
cd CNInfo
# 创建爬虫脚本
scrapy genspider CNInfoSS http://www.cninfo.com.cn/new/disclosure

14.2设置源代码根目录
将项目目录设置为源代码根目录

要不然相对路径导入包的时候会报错


14.3Scrapy.Item
在 Scrapy 中,scrapy.Item 是用于定义爬虫抓取的数据结构,它相当于一个容器,用来存储爬虫在抓取过程中的数据。通过 scrapy.Item 定义的字段是我们在爬虫中提取和存储的数据。具体来说,scrapy.Field() 是用来定义每个字段的数据类型,它是一个简单的容器,存储着爬虫抓取的数据。
scrapy.Item:
scrapy.Item是 Scrapy 中用于存储抓取到的数据的容器类。你可以定义一个类继承自scrapy.Item,然后通过scrapy.Field()来为该类定义具体的数据字段。- 每一个继承自
scrapy.Item的类都是一个数据结构,用于存储不同字段的数据。 - Scrapy 会在爬取的过程中自动创建该类的实例,并通过各个字段来存储抓取到的数据。
scrapy.Field:
scrapy.Field()是用来定义每个数据字段的,实际上它是一个容器,它允许 Scrapy 存储和操作该字段的数据。scrapy.Field()没有特别的功能,它只是一个字典类型的占位符,用来给Item类中的每个字段定义名称。Field本身是一个容器,并不存储数据,而是让 Scrapy 在抓取到数据后存放在这个字段里。
14.4Request
Scrapy 提供了多种请求类型来适应不同的抓取需求,常见的有:
Request: 基本的 HTTP 请求,用于普通的抓取操作。FormRequest: 用于表单提交,适合登录、搜索等操作。JsonRequest: 用于发送 JSON 数据请求,常见于与 API 交互。XMLRequest: 用于发送 XML 数据请求,适合与 XML 格式的数据交互。SeleniumRequest: 用于抓取动态内容,结合 Selenium 使用。CurlRequest: 用于执行 curl 命令,适用于复杂请求场景。RedirectRequest: 用于处理 HTTP 重定向,自动跟踪重定向链接。
14.5代码
CNInfoItem
class CNInfoItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
announcementTitle = scrapy.Field()
announcementTypeName = scrapy.Field()
batchNum = scrapy.Field()
secName = scrapy.Field()
adjunctType = scrapy.Field()
CninfossSpider
import json
import scrapy
from scrapy import cmdline
from ..items import CNInfoItem
# from CNInfo.items import CNInfoItem
class CninfossSpider(scrapy.Spider):
name = "CNInfoSS"
allowed_domains = ["www.cninfo.com.cn"]
start_urls = ["http://www.cninfo.com.cn/new/disclosure"]
def start_requests(self):
url = 'http://www.cninfo.com.cn/new/disclosure'
# 表单数据
for page in range(1, 3):
data = {
'column': 'szse_latest',
'pageNum': str(page),
'pageSize': '30',
'sortName': '',
'sortType': '',
'clusterFlag': 'true',
}
yield scrapy.FormRequest(url=url, formdata=data, callback=self.parse, dont_filter=False)
# yield scrapy.Request(url=url, body=json.dumps(data), method='POST', callback=self.parse)
def parse(self, response, **kwargs):
for info_list in response.json()['classifiedAnnouncements']:
for info in info_list:
item = CNInfoItem()
item['announcementTitle'] = info['announcementTitle']
item['announcementTypeName'] = info['announcementTypeName']
item['batchNum'] = info['batchNum']
item['secName'] = info['secName']
item['adjunctType'] = info['adjunctType']
yield item
if __name__ == '__main__':
cmdline.execute('scrapy crawl CNInfoSS'.split())
CNInfoMongoPipeline
class CNInfoMongoPipeline:
def open_spider(self, spider):
if spider.name == 'CNInfoSS':
self.mongo_client = pymongo.MongoClient()
self.collection = self.mongo_client['py_spider']['cn_info']
def process_item(self, item, spider):
if spider.name == 'CNInfoSS':
self.collection.insert_one(dict(item))
return item
def close_spider(self, spider):
if spider.name == 'CNInfoSS':
self.mongo_client.close()
MongoDB

📌 创作不易,感谢支持!
每一篇内容都凝聚了心血与热情,如果我的内容对您有帮助,欢迎请我喝杯咖啡☕,您的支持是我持续分享的最大动力!

浙公网安备 33010602011771号