查看全课大纲(第 17 / 101 节)
  1. 1.数据分析基本概念
  2. 2.学习数据分析的一般路线
  3. 3.数据分析的流程
  4. 4.数据类型
  5. 5.环境部署(1)
  6. 6.环境部署(2)
  7. 7.课程介绍
  8. 8.TXT文件操作
  9. 9.JSON文件操作
  10. 10.CSV文件操作
  11. 11.Excel文件操作
  12. 12.数据库及SQL常用语法
  13. 13.数据库基本操作
  14. 14.数据库多表连接
  15. 15.实战:欧洲职业足球数据库分析
  16. 16.爬虫简介
  17. 17.URL管理模块
  18. 18.网页下载模块
  19. 19.网页解析模块(1)
  20. 20.网页解析模块(2)
  21. 21.Scrapy简介
  22. 22.Scrapy使用步骤(1)
  23. 23.Scrapy使用步骤(2)
  24. 24.Scrapy使用步骤(3)
  25. 25.Scrapy使用步骤(4)
  26. 26.实战:获取国内城市空气质量指数数据
  27. 27.NumPy和SciPy介绍
  28. 28.多维数组
  29. 29.多维数组操作
  30. 30.NumPy的常用方法
  31. 31.向量化介绍
  32. 32.向量化及通用函数
  33. 33.实战:2016美国大选分析
  34. 34.数据结构-Series
  35. 35.数据结构-DataFrame
  36. 36.数据结构-Index
  37. 37.Series的索引操作
  38. 38.DataFrame的索引操作
  39. 39.索引操作总结
  40. 40.运算与对齐
  41. 41.函数应用操作(1) -- map
  42. 42.函数应用操作 (2) -- apply applymap
  43. 43.文件读写操作
  44. 44.排序操作
  45. 45.数据清洗--处理缺失数据
  46. 46.数据清洗--处理重复数据
  47. 47.数据清洗--替换数据
  48. 48.常用统计方法(1) -- describe quantile
  49. 49.常用统计方法(2) -- sum mean median count
  50. 50.常用统计方法(3) -- max min idxmax idxmin
  51. 51.常用统计方法(4) -- mad var std cumsum
  52. 52.实战:全球食品数据分析
  53. 53.层级索引
  54. 54.分组与聚合介绍
  55. 55.分组操作(1) -- GroupBy对象及常用聚合操作
  56. 56.分组操作(2) -- 自定义分组及聚合操作
  57. 57.透视表介绍
  58. 58.透视表操作
  59. 59.数据规整(1) -- 数据合并concat
  60. 60.数据规整(2) -- 数据连接merge
  61. 61.数据重构(3) -- 数据重构stack unstack
  62. 62.实战:互联网电影资料库分析
  63. 63.探索性数据分析EDA介绍
  64. 64.EDA的目的
  65. 65.EDA常用工具
  66. 66.Matplotlib绘图基本介绍
  67. 67.Matplotlib画布
  68. 68.散点图和柱状图的绘制
  69. 69.直方图的绘制
  70. 70.矩阵绘图
  71. 71.子图的使用
  72. 72.Matplotlib颜色、标记、线型
  73. 73.Matplotlib坐标刻度、标签、图例、标题
  74. 74.Seaborn介绍
  75. 75.数据集分布可视化(1) -- 单变量分布、双变量分布
  76. 76.数据集分布可视化(2) -- 变量关系可视化
  77. 77.类别数据可视化 -- 类别散布图、类别内数据分布、类别内统计图
  78. 78.交互式数据可视化工具Bokeh介绍
  79. 79.Bokeh绘制散点图、柱状图、盒子图、弦图
  80. 80.Bokeh绘制常用图形元素
  81. 81.3D绘图 -- mplot3d
  82. 82.3D曲线可视化
  83. 83.3D散点图可视化
  84. 84.3D柱状图可视化
  85. 85.Pandas绘图
  86. 86.实战:Lending Club借贷数据探索性分析及可视化
  87. 87.机器学习介绍及应用场景
  88. 88.机器学习建模介绍 (1) -- 分类
  89. 89.机器学习建模介绍 (2) -- 回归
  90. 90.机器学习建模介绍 (3) -- 聚类
  91. 91.机器学习分类
  92. 92.机器学习工具scikit-learn
  93. 93.使用scikit-learn的流程
  94. 94.数据集准备及划分
  95. 95.模型选择
  96. 96.数据预处理及特征工程
  97. 97.过拟合与欠拟合
  98. 98.模型调参介绍
  99. 99.模型调参方法
  100. 100.模型测试及评价
  101. 101.实战:通过移动设备行为数据预测性别和年龄

URL管理模块

约 3 分钟

📺 正在播放小象学院官方高清录播(支持倍速与清晰度调节)

小象实战讲义 · Python数据分析实战

在网络爬虫的流程中,URL管理模块扮演着“调度中心”的角色。它负责记录哪些链接已经爬取过,哪些链接等待爬取,从而有效避免重复劳动和陷入无限循环。本节我们将学习URL管理模块的核心功能与两种典型的实现思路。

💡 核心导读

  • 核心问题:理解为何需要URL管理模块,以及它要解决的“重复爬取”和“循环指向”问题。
  • 核心功能:掌握URL管理模块的三大功能:添加新URL、管理已爬取与未爬取URL、获取待爬取URL。
  • 实现方式:学习两种实现方式——使用Python集合(set)的简单方案,以及使用数据库(标志位)的复杂方案。

为什么需要URL管理模块?

想象一下,你正在编写一个爬虫程序,从一个电商网站的首页开始爬取。首页上通常包含许多链接,例如商品列表页、分类页,甚至可能还包含指向首页自身的链接(如Logo链接)。

如果你没有对已爬取的URL进行记录和管理,那么爬虫在解析首页时,可能会再次发现首页的链接,并误以为这是一个新的、未爬取过的页面,从而再次发起请求。这会导致两个严重问题:

  1. 重复爬取:同一个页面被反复下载和解析,浪费网络带宽和计算资源,也可能对目标网站造成不必要的压力。
  2. 循环指向:如果页面间的链接形成了环(A链向B,B链向C,C又链回A),爬虫可能会在这个环里无限循环,永远无法结束。

URL管理模块就是为了解决这些问题而设计的。它像一个“任务清单”和“完成记录本”,确保每个链接只被处理一次,并引导爬虫有序地探索新的页面。

URL管理模块的核心功能

一个基本的URL管理模块通常需要实现以下三个功能:

  1. 添加新的URL:当爬虫在解析一个网页时,会发现许多新的链接。这些链接需要被添加到管理模块中,等待后续爬取。
  2. 管理已爬取和未爬取的URL:模块需要清晰地区分哪些URL已经处理过(已爬取),哪些还在等待队列中(未爬取)。对于已爬取的URL,未来不再进行爬取。
  3. 获取待爬取的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创建一条记录,并添加一个“标志位”字段。

核心思想:

  1. 在数据库表中,为每个URL存储其状态。例如,可以有一个字段 crawled(或 status)。
  2. 当URL未被爬取时,crawled = 0(或 status = 'pending')。
  3. 当URL被爬取后,将其标志位更新为 crawled = 1(或 status = 'done')。
  4. 每当发现一个新链接,首先去数据库中查询该链接的记录:
    • 如果记录存在且 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去重与调度组件。

📝 动手练一练

  1. 概念辨析:如果使用Python列表(list)来代替集合(set)管理待爬取URL,可能会遇到什么问题?请具体说明。
👉 点击查看参考答案

使用列表(list)管理待爬取URL的主要问题是无法自动去重。列表允许存储重复的元素。当同一个URL被多次发现并添加到列表中时,它会在列表中出现多次。这会导致爬虫重复下载和解析同一个页面,造成资源浪费,并可能触发目标网站的反爬机制。而集合(set)的天然去重特性正好解决了这个问题。

  1. 代码实践:观察上面“集合方案”的示例代码,种子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 数据库
    下载