我听见有人猜
你是敌人潜伏的内线
和你相知多年
我确信对你的了解
你舍命救我画面
一一在眼前浮现
司空见惯了鲜血
你忘记你本是娇娆的红颜
感觉你我彼此都那么依恋
🎵 许嵩《内线》
在 Web 爬虫中,深度优先搜索(DFS) 和 广度优先搜索(BFS) 是常见的网页采集策略。不同的策略适用于不同的爬取场景,例如,当我们想更快地抓取某个页面的所有关联内容时,广度优先可能更合适;而深度优先则更适合用于逐层深入网站结构,抓取嵌套得较深的网页内容。
在 Scrapy 框架中,默认的爬取方式是广度优先(BFS),但 Scrapy 也支持通过设置参数来切换到深度优先(DFS)爬取。本篇博客将详细介绍如何在 Scrapy 中设置深度优先和广度优先策略。
1. 什么是深度优先和广度优先?
广度优先(BFS):广度优先算法从起始点开始,优先访问与其直接相连的所有节点,然后再访问这些节点的相邻节点,依次展开。广度优先的特点是“先横向再纵向”,即会优先遍历当前层的所有页面,再继续遍历下一层。
深度优先(DFS):深度优先算法则是优先访问当前节点的子节点,然后依次深入。深度优先的特点是“先纵向再横向”,即会一直深入遍历下一级页面,直到达到某个终点才返回遍历下一个节点。
示例对比:
广度优先:Level 1 -> Level 2 -> Level 3 -> …
深度优先:Level 1 -> Level 1.1 -> Level 1.1.1 -> …
2. Scrapy 默认的采集策略
Scrapy 默认使用广度优先搜索(BFS)来进行页面爬取。这意味着它会从起始页面开始,依次抓取该页面上的所有链接,然后再逐层抓取下一层的页面。
在 Scrapy 中,这种采集策略是通过请求队列的管理实现的。Scrapy 使用一个优先级队列来存储要抓取的请求,而默认情况下,新发现的请求会被添加到队列的尾部,从而实现广度优先采集。
3. 切换到深度优先采集
为了在 Scrapy 中使用深度优先搜索(DFS),我们需要调整请求的优先级策略。通过为新的请求设置较高的优先级,Scrapy 会优先处理新发现的请求,从而达到深度优先的效果。
Scrapy 提供了 DEPTH_PRIORITY 和 SCHEDULER_DISK_QUEUE 设置来实现这一点。
3.1 修改 Scrapy 配置为深度优先
我们可以通过修改 Scrapy 项目的配置文件 settings.py 来设置深度优先采集:
# settings.py
# 设置为深度优先
DEPTH_PRIORITY = 1
# 优先从队列顶部抓取请求(即从深层先抓)
SCHEDULER_DISK_QUEUE = 'scrapy.squeues.PickleFifoDiskQueue'
SCHEDULER_MEMORY_QUEUE = 'scrapy.squeues.FifoMemoryQueue'
在这里:
DEPTH_PRIORITY = 1:设置深度优先采集策略,优先处理新发现的请求。
SCHEDULER_DISK_QUEUE 和 SCHEDULER_MEMORY_QUEUE 分别指定了磁盘队列和内存队列的处理方式。Fifo 表示 “First In, First Out”(先进先出),用以保证新请求先被处理,从而达到深度优先效果。
3.2 控制爬取深度
Scrapy 还提供了一个配置项 DEPTH_LIMIT 来限制爬取的最大深度。如果你想控制爬虫不爬得太深,可以设置这个参数。例如,限制爬虫最多爬取 3 层深度:
# 限制爬取深度为3
DEPTH_LIMIT = 3
当设置了 DEPTH_LIMIT 后,Scrapy 在到达指定深度后将不会继续抓取更深的页面。
4. 切换回广度优先采集
如果你想使用广度优先采集(BFS),实际上 Scrapy 默认就是使用这种策略。因此,回到默认的配置即可。
4.1 恢复广度优先的配置
广度优先采集的配置如下:
# settings.py
# 设置为广度优先
DEPTH_PRIORITY = -1
# 优先从队列尾部抓取请求(即从表层抓取)
SCHEDULER_DISK_QUEUE = 'scrapy.squeues.PickleLifoDiskQueue'
SCHEDULER_MEMORY_QUEUE = 'scrapy.squeues.LifoMemoryQueue'
在这里:
DEPTH_PRIORITY = -1:设置广度优先采集策略,优先处理先加入队列的请求。
SCHEDULER_DISK_QUEUE 和 SCHEDULER_MEMORY_QUEUE 分别指定了磁盘队列和内存队列的处理方式。Lifo 表示 “Last In, First Out”(后进先出),用以保证先发现的页面优先被处理。
5. 自定义请求优先级
除了全局的深度优先和广度优先配置外,Scrapy 还允许我们通过 Request 对象手动控制单个请求的优先级。
5.1 为请求设置优先级
在 Scrapy 中,priority 参数可以用于控制每个请求的优先级。优先级高的请求会被优先处理。默认情况下,所有请求的优先级都是 0。我们可以通过修改这个值来改变请求的处理顺序。
示例:手动控制某些请求的优先级,使其优先处理:
import scrapy
class MySpider(scrapy.Spider):
name = 'myspider'
def start_requests(self):
urls = [
'https://example.com/level1',
'https://example.com/level2'
]
for url in urls:
yield scrapy.Request(url=url, callback=self.parse, priority=10)
def parse(self, response):
# 对子页面设置更高的优先级
for href in response.css('a::attr(href)').getall():
yield scrapy.Request(url=response.urljoin(href), callback=self.parse, priority=20)
在这个示例中,起始页面的优先级设置为 10,而子页面的优先级设置为 20,子页面会被优先处理。
6. 完整示例:深度优先 vs 广度优先
以下是一个完整的示例,演示如何在 Scrapy 中通过设置来实现深度优先和广度优先爬取。
6.1 深度优先示例
# 深度优先爬虫配置 settings.py
DEPTH_PRIORITY = 1
SCHEDULER_DISK_QUEUE = 'scrapy.squeues.PickleFifoDiskQueue'
SCHEDULER_MEMORY_QUEUE = 'scrapy.squeues.FifoMemoryQueue'
DEPTH_LIMIT = 3 # 限制爬取深度为3
6.2 广度优先示例
# 广度优先爬虫配置 settings.py
DEPTH_PRIORITY = -1
SCHEDULER_DISK_QUEUE = 'scrapy.squeues.PickleLifoDiskQueue'
SCHEDULER_MEMORY_QUEUE = 'scrapy.squeues.LifoMemoryQueue'
6.3 爬虫代码
import scrapy
class MySpider(scrapy.Spider):
name = 'myspider'
start_urls = ['https://example.com']
def parse(self, response):
self.log(f"正在爬取:{response.url}")
for href in response.css('a::attr(href)').getall():
yield scrapy.Request(url=response.urljoin(href), callback=self.parse)
7. 总结
通过调整 Scrapy 的配置,我们可以灵活地在 深度优先 和 广度优先 爬取策略之间切换。深度优先适合深入探索网页的层次结构,而广度优先适合快速覆盖网站的广泛区域。
要实现深度优先,只需设置 DEPTH_PRIORITY = 1 并使用 Fifo 队列来优先处理新发现的请求;而广度优先可以通过默认配置或设置 DEPTH_PRIORITY = -1 并使用 Lifo 队列来实现。此外,通过 priority 参数,你还可以自定义不同请求的优先级,控制它们的处理顺序。
根据实际业务需求选择合适的爬取策略,可以让你的 Scrapy 爬虫更加高效、灵活地完成数据抓取任务。