查看全课大纲(第 17 / 101 节)
- 1.数据分析基本概念
- 2.学习数据分析的一般路线
- 3.数据分析的流程
- 4.数据类型
- 5.环境部署(1)
- 6.环境部署(2)
- 7.课程介绍
- 8.TXT文件操作
- 9.JSON文件操作
- 10.CSV文件操作
- 11.Excel文件操作
- 12.数据库及SQL常用语法
- 13.数据库基本操作
- 14.数据库多表连接
- 15.实战:欧洲职业足球数据库分析
- 16.爬虫简介
- 17.URL管理模块
- 18.网页下载模块
- 19.网页解析模块(1)
- 20.网页解析模块(2)
- 21.Scrapy简介
- 22.Scrapy使用步骤(1)
- 23.Scrapy使用步骤(2)
- 24.Scrapy使用步骤(3)
- 25.Scrapy使用步骤(4)
- 26.实战:获取国内城市空气质量指数数据
- 27.NumPy和SciPy介绍
- 28.多维数组
- 29.多维数组操作
- 30.NumPy的常用方法
- 31.向量化介绍
- 32.向量化及通用函数
- 33.实战:2016美国大选分析
- 34.数据结构-Series
- 35.数据结构-DataFrame
- 36.数据结构-Index
- 37.Series的索引操作
- 38.DataFrame的索引操作
- 39.索引操作总结
- 40.运算与对齐
- 41.函数应用操作(1) -- map
- 42.函数应用操作 (2) -- apply applymap
- 43.文件读写操作
- 44.排序操作
- 45.数据清洗--处理缺失数据
- 46.数据清洗--处理重复数据
- 47.数据清洗--替换数据
- 48.常用统计方法(1) -- describe quantile
- 49.常用统计方法(2) -- sum mean median count
- 50.常用统计方法(3) -- max min idxmax idxmin
- 51.常用统计方法(4) -- mad var std cumsum
- 52.实战:全球食品数据分析
- 53.层级索引
- 54.分组与聚合介绍
- 55.分组操作(1) -- GroupBy对象及常用聚合操作
- 56.分组操作(2) -- 自定义分组及聚合操作
- 57.透视表介绍
- 58.透视表操作
- 59.数据规整(1) -- 数据合并concat
- 60.数据规整(2) -- 数据连接merge
- 61.数据重构(3) -- 数据重构stack unstack
- 62.实战:互联网电影资料库分析
- 63.探索性数据分析EDA介绍
- 64.EDA的目的
- 65.EDA常用工具
- 66.Matplotlib绘图基本介绍
- 67.Matplotlib画布
- 68.散点图和柱状图的绘制
- 69.直方图的绘制
- 70.矩阵绘图
- 71.子图的使用
- 72.Matplotlib颜色、标记、线型
- 73.Matplotlib坐标刻度、标签、图例、标题
- 74.Seaborn介绍
- 75.数据集分布可视化(1) -- 单变量分布、双变量分布
- 76.数据集分布可视化(2) -- 变量关系可视化
- 77.类别数据可视化 -- 类别散布图、类别内数据分布、类别内统计图
- 78.交互式数据可视化工具Bokeh介绍
- 79.Bokeh绘制散点图、柱状图、盒子图、弦图
- 80.Bokeh绘制常用图形元素
- 81.3D绘图 -- mplot3d
- 82.3D曲线可视化
- 83.3D散点图可视化
- 84.3D柱状图可视化
- 85.Pandas绘图
- 86.实战:Lending Club借贷数据探索性分析及可视化
- 87.机器学习介绍及应用场景
- 88.机器学习建模介绍 (1) -- 分类
- 89.机器学习建模介绍 (2) -- 回归
- 90.机器学习建模介绍 (3) -- 聚类
- 91.机器学习分类
- 92.机器学习工具scikit-learn
- 93.使用scikit-learn的流程
- 94.数据集准备及划分
- 95.模型选择
- 96.数据预处理及特征工程
- 97.过拟合与欠拟合
- 98.模型调参介绍
- 99.模型调参方法
- 100.模型测试及评价
- 101.实战:通过移动设备行为数据预测性别和年龄
URL管理模块
约 3 分钟
小象实战讲义 · Python数据分析实战
在网络爬虫的流程中,URL管理模块扮演着“调度中心”的角色。它负责记录哪些链接已经爬取过,哪些链接等待爬取,从而有效避免重复劳动和陷入无限循环。本节我们将学习URL管理模块的核心功能与两种典型的实现思路。
💡 核心导读
- 核心问题:理解为何需要URL管理模块,以及它要解决的“重复爬取”和“循环指向”问题。
- 核心功能:掌握URL管理模块的三大功能:添加新URL、管理已爬取与未爬取URL、获取待爬取URL。
- 实现方式:学习两种实现方式——使用Python集合(set)的简单方案,以及使用数据库(标志位)的复杂方案。
为什么需要URL管理模块?
想象一下,你正在编写一个爬虫程序,从一个电商网站的首页开始爬取。首页上通常包含许多链接,例如商品列表页、分类页,甚至可能还包含指向首页自身的链接(如Logo链接)。
如果你没有对已爬取的URL进行记录和管理,那么爬虫在解析首页时,可能会再次发现首页的链接,并误以为这是一个新的、未爬取过的页面,从而再次发起请求。这会导致两个严重问题:
- 重复爬取:同一个页面被反复下载和解析,浪费网络带宽和计算资源,也可能对目标网站造成不必要的压力。
- 循环指向:如果页面间的链接形成了环(A链向B,B链向C,C又链回A),爬虫可能会在这个环里无限循环,永远无法结束。
URL管理模块就是为了解决这些问题而设计的。它像一个“任务清单”和“完成记录本”,确保每个链接只被处理一次,并引导爬虫有序地探索新的页面。
URL管理模块的核心功能
一个基本的URL管理模块通常需要实现以下三个功能:
- 添加新的URL:当爬虫在解析一个网页时,会发现许多新的链接。这些链接需要被添加到管理模块中,等待后续爬取。
- 管理已爬取和未爬取的URL:模块需要清晰地区分哪些URL已经处理过(已爬取),哪些还在等待队列中(未爬取)。对于已爬取的URL,未来不再进行爬取。
- 获取待爬取的URL:爬虫需要一个接口来从管理模块中获取下一个要爬取的URL,通常是按照某种策略(如先进先出)从“未爬取”队列中取出。
实现方式一:使用Python集合(set)
对于小规模或简单的爬虫任务,使用Python内置的set(集合)数据结构是一种非常简洁高效的实现方式。
集合(set)的特性:集合中的元素是唯一且无序的。这意味着,如果你尝试向集合中添加一个已经存在的元素,这个操作不会产生任何效果,集合的内容不会改变。
这个特性恰好可以用来防止重复添加相同的URL。
# 示例:使用集合模拟一个简单的URL管理器
# 初始化两个集合:一个存放待爬取的URL,一个存放已爬取的URL
urls_to_crawl = set() # 待爬取集合
urls_crawled = set() # 已爬取集合
# 1. 添加新的URL(模拟从种子页面开始)
seed_urls = ['https://example.com', 'https://example.com/about']
for url in seed_urls:
# 只有当URL既不在待爬取集合,也不在已爬取集合时,才添加
if url not in urls_to_crawl and url not in urls_crawled:
urls_to_crawl.add(url)
print(f"添加种子URL: {url}")
# 2. 模拟爬取过程
print("\n开始模拟爬虫流程(使用集合管理):")
print("-" * 40)
# 假设我们有一个简单的“爬取”函数,它会返回当前页面上的链接
def simulate_parse_page(current_url):
"""模拟解析页面,返回该页面上的链接列表"""
# 这里硬编码一些链接来模拟不同页面的链接结构
link_mockup = {
'https://example.com': ['https://example.com/page1', 'https://example.com/page2', 'https://example.com'],
'https://example.com/page2': ['https://example.com/page1', 'https://example.com', 'https://example.com/page1', 'https://example.com/page3'],
'https://example.com/page3': ['https://example.com/page1', 'https://example.com/page2', 'https://example.com'],
'https://example.com/page1': ['https://example.com/page2', 'https://example.com', 'https://example.com/page1', 'https://example.com/page3'],
'https://example.com/about': [] # about页面没有外链
}
return link_mockup.get(current_url, [])
# 循环爬取,直到没有待爬取的URL
while urls_to_crawl:
# 3. 获取一个待爬取的URL
# 注:集合本身无序(pop 的顺序依赖哈希,每次运行可能不同),
# 这里按字典序取最小值,保证演示输出稳定;实际工程常用先进先出队列
current_url = sorted(urls_to_crawl)[0]
urls_to_crawl.remove(current_url)
# 先把当前URL标记为已爬取,防止解析页面时又把它当作“新链接”加回来
urls_crawled.add(current_url)
print(f"正在爬取: {current_url}")
# 模拟解析页面,发现新链接
new_links = simulate_parse_page(current_url)
for link in new_links:
# 检查链接是否为新链接(既未爬取,也不在待爬取队列)
if link not in urls_crawled and link not in urls_to_crawl:
urls_to_crawl.add(link)
print(f" 发现新URL,加入待爬取列表: {link}")
else:
print(f" URL已存在,跳过: {link}")
print(f" 待爬取数量: {len(urls_to_crawl)}, 已爬取数量: {len(urls_crawled)}")
print("-" * 40)
print("模拟爬取结束。")
print(f"\n最终状态 - 已爬取URL: {sorted(urls_crawled)}")添加种子URL: https://example.com
添加种子URL: https://example.com/about
开始模拟爬虫流程(使用集合管理):
----------------------------------------
正在爬取: https://example.com
发现新URL,加入待爬取列表: https://example.com/page1
发现新URL,加入待爬取列表: https://example.com/page2
URL已存在,跳过: https://example.com
待爬取数量: 3, 已爬取数量: 1
----------------------------------------
正在爬取: https://example.com/about
待爬取数量: 2, 已爬取数量: 2
----------------------------------------
正在爬取: https://example.com/page1
URL已存在,跳过: https://example.com/page2
URL已存在,跳过: https://example.com
URL已存在,跳过: https://example.com/page1
发现新URL,加入待爬取列表: https://example.com/page3
待爬取数量: 2, 已爬取数量: 3
----------------------------------------
正在爬取: https://example.com/page2
URL已存在,跳过: https://example.com/page1
URL已存在,跳过: https://example.com
URL已存在,跳过: https://example.com/page1
URL已存在,跳过: https://example.com/page3
待爬取数量: 1, 已爬取数量: 4
----------------------------------------
正在爬取: https://example.com/page3
URL已存在,跳过: https://example.com/page1
URL已存在,跳过: https://example.com/page2
URL已存在,跳过: https://example.com
待爬取数量: 0, 已爬取数量: 5
----------------------------------------
模拟爬取结束。
最终状态 - 已爬取URL: ['https://example.com', 'https://example.com/about', 'https://example.com/page1', 'https://example.com/page2', 'https://example.com/page3']代码解读:
- 我们使用两个集合
urls_to_crawl和urls_crawled分别管理待爬取和已爬取的URL。 simulate_parse_page函数模拟了爬虫解析页面并提取链接的过程,其中硬编码了链接关系,用于演示重复链接的出现。- 在添加新链接时,我们检查该链接是否已存在于任何一个集合中,以此实现去重。
- 最终,所有唯一的URL都被成功爬取了一次,没有发生重复或循环。
实现方式二:使用数据库(标志位)
对于大规模、需要持久化或分布式爬虫项目,将URL信息存储在数据库中是一种更专业的做法。常见的实现思路是为每个URL创建一条记录,并添加一个“标志位”字段。
核心思想:
- 在数据库表中,为每个URL存储其状态。例如,可以有一个字段
crawled(或status)。 - 当URL未被爬取时,
crawled = 0(或status = 'pending')。 - 当URL被爬取后,将其标志位更新为
crawled = 1(或status = 'done')。 - 每当发现一个新链接,首先去数据库中查询该链接的记录:
- 如果记录存在且
crawled = 1,说明已爬取,跳过。 - 如果记录存在且
crawled = 0,说明已在待爬取队列中,跳过。 - 如果记录不存在,则插入一条新记录,
crawled = 0,并将其加入待爬取队列。
- 如果记录存在且
# 示例:使用Python字典模拟一个简单的“数据库表”来实现URL管理
# 注意:实际项目中会使用SQLite、MySQL等真实数据库
# 初始化一个“模拟数据库”,用字典存储,键为URL,值为状态(0未爬取,1已爬取)
url_database = {}
# 初始化待爬取队列(这里用列表模拟,实际可能用消息队列)
url_queue = []
# 1. 添加种子URL
seed_urls = ['https://example.com/product/1', 'https://example.com/product/2']
for url in seed_urls:
if url not in url_database: # 模拟“查询数据库,记录不存在”
url_database[url] = 0 # 状态设为0(未爬取)
url_queue.append(url) # 加入待爬取队列
print(f"[DB] 插入新记录: {url}, 状态: 0")
# 2. 模拟爬取过程
print("\n开始模拟爬虫流程(使用数据库标志位):")
print("-" * 40)
def simulate_parse_page_db(current_url):
"""模拟解析页面,返回链接"""
link_mockup = {
'https://example.com/product/1': ['https://example.com/product/2', 'https://example.com/product/3'],
'https://example.com/product/2': ['https://example.com/product/1', 'https://example.com/product/4'],
'https://example.com/product/3': [],
'https://example.com/product/4': [],
}
return link_mockup.get(current_url, [])
while url_queue:
# 3. 获取一个待爬取的URL(从队列头部取)
current_url = url_queue.pop(0)
print(f"正在爬取: {current_url} (状态: {url_database[current_url]})")
# 模拟解析页面
new_links = simulate_parse_page_db(current_url)
for link in new_links:
# 查询“数据库”
status = url_database.get(link)
if status is None:
# 记录不存在,是新URL
url_database[link] = 0
url_queue.append(link)
print(f" [新URL] 添加到数据库和队列: {link}")
elif status == 0:
# 记录存在,但状态为0(已在队列中)
print(f" [已存在] URL已在待爬取队列: {link}")
else: # status == 1
# 记录存在,且状态为1(已爬取)
print(f" [已爬取] 跳过已处理URL: {link}")
# 4. 更新当前URL在“数据库”中的状态为“已爬取”
url_database[current_url] = 1
print(f" 更新状态为已爬取。")
print(f" 待爬取队列长度: {len(url_queue)}, 数据库总记录数: {len(url_database)}")
print("-" * 40)
print("模拟爬取结束。")
print(f"\n最终数据库状态:")
for url, status in sorted(url_database.items()):
print(f" {url} -> {'已爬取' if status == 1 else '未爬取'}")[DB] 插入新记录: https://example.com/product/1, 状态: 0
[DB] 插入新记录: https://example.com/product/2, 状态: 0
开始模拟爬虫流程(使用数据库标志位):
----------------------------------------
正在爬取: https://example.com/product/1 (状态: 0)
[已存在] URL已在待爬取队列: https://example.com/product/2
[新URL] 添加到数据库和队列: https://example.com/product/3
更新状态为已爬取。
待爬取队列长度: 2, 数据库总记录数: 3
----------------------------------------
正在爬取: https://example.com/product/2 (状态: 0)
[已爬取] 跳过已处理URL: https://example.com/product/1
[新URL] 添加到数据库和队列: https://example.com/product/4
更新状态为已爬取。
待爬取队列长度: 2, 数据库总记录数: 4
----------------------------------------
正在爬取: https://example.com/product/3 (状态: 0)
更新状态为已爬取。
待爬取队列长度: 1, 数据库总记录数: 4
----------------------------------------
正在爬取: https://example.com/product/4 (状态: 0)
更新状态为已爬取。
待爬取队列长度: 0, 数据库总记录数: 4
----------------------------------------
模拟爬取结束。
最终数据库状态:
https://example.com/product/1 -> 已爬取
https://example.com/product/2 -> 已爬取
https://example.com/product/3 -> 已爬取
https://example.com/product/4 -> 已爬取方案对比与选择:
- 集合(set)方案:实现简单、内存操作速度快,适合数据量不大、单机运行的爬虫。缺点是程序关闭后状态丢失(除非自己序列化到文件),且难以在多个爬虫进程间共享状态。
- 数据库方案:状态持久化,可以支持分布式爬虫(多个爬虫节点共享同一个数据库)。功能也更强大,可以方便地添加更多元数据(如爬取时间、HTTP状态码、页面内容哈希等)。缺点是引入了外部依赖,架构更复杂。
在实际项目中,你可以根据爬虫的规模、复杂度和运维要求来选择合适的方案。许多成熟的爬虫框架(如Scrapy)已经内置了强大且可扩展的URL去重与调度组件。
📝 动手练一练
- 概念辨析:如果使用Python列表(list)来代替集合(set)管理待爬取URL,可能会遇到什么问题?请具体说明。
👉 点击查看参考答案
使用列表(list)管理待爬取URL的主要问题是无法自动去重。列表允许存储重复的元素。当同一个URL被多次发现并添加到列表中时,它会在列表中出现多次。这会导致爬虫重复下载和解析同一个页面,造成资源浪费,并可能触发目标网站的反爬机制。而集合(set)的天然去重特性正好解决了这个问题。
- 代码实践:观察上面“集合方案”的示例代码,种子URL中包含了
https://example.com/about,但模拟解析函数simulate_parse_page中并没有为它定义外链。请修改simulate_parse_page函数,为about页面也硬编码添加1-2个外链(例如https://example.com/contact和https://example.com/faq),然后重新运行代码块,观察爬取流程和最终结果有何变化。
👉 点击查看参考答案
修改 simulate_parse_page 函数中的 link_mockup 字典,为 'https://example.com/about' 键添加外链列表。
# ... 其他代码不变 ...
link_mockup = {
'https://example.com': ['https://example.com/page1', 'https://example.com/page2', 'https://example.com'],
'https://example.com/page2': ['https://example.com/page1', 'https://example.com', 'https://example.com/page1', 'https://example.com/page3'],
'https://example.com/page3': ['https://example.com/page1', 'https://example.com/page2', 'https://example.com'],
'https://example.com/page1': ['https://example.com/page2', 'https://example.com', 'https://example.com/page1', 'https://example.com/page3'],
'https://example.com/about': ['https://example.com/contact', 'https://example.com/faq'] # 修改这里
}
# ... 后续代码不变 ...重新运行后,爬虫在爬取 about 页面时会发现两个新链接 contact 和 faq,并将它们加入待爬取集合。由于我们的模拟解析函数没有为这两个新链接定义进一步的外链,它们被爬取后流程便会结束。最终,已爬取集合中会包含这两个新增的URL。这个练习帮助你理解URL管理模块如何动态地接收和处理新发现的链接。
本章小结
本节我们深入探讨了网络爬虫中一个基础但至关重要的组件——URL管理模块。理解它的工作原理是构建一个健壮、高效爬虫的基石。
📋 行动清单
- 理解核心价值:明确URL管理模块解决“重复爬取”和“循环指向”问题的核心价值。
- 掌握两种实现:能够阐述使用Python集合和数据库标志位两种实现方式的原理、优缺点及适用场景。
- 尝试简单实现:动手在本地编写一个使用集合来管理URL的微型爬虫模拟程序,加深对去重和调度流程的理解。
—— 小象教研组
配套资料 · 直接下载
课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。
- 下载本节课件:URL管理模块PDF · 176KB
- 下载全套课件打包(第1-5章)ZIP · 12.8MB
- 下载全套课件打包(第6-8章)ZIP · 15MB
- 下载实战数据集:AppleStore 应用商城分析ZIP · 329KB
- 下载实战数据集:女性服装电商分析ZIP · 2.8MB
- 下载Python 数据分析环境搭建指南PDF · 2MB
- 下载Scrapy 安装教程PDF · 12.7MB
- 下载附加实战项目:AppleStore 应用商城数据分析ZIP · 0.3MB · ipynb + CSV 数据
- 下载附加实战项目:银行电话营销数据分析ZIP · 0.4MB · ipynb + CSV 数据
- 下载附加实战项目:女性服装电商评论数据分析ZIP · 2.7MB · ipynb + CSV 数据
- 下载附加实战项目:美国化学学会杂志数据分析ZIP · 34.2MB · ipynb + SQLite 数据库