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