查看全课大纲(第 18 / 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.实战:通过移动设备行为数据预测性别和年龄

网页下载模块

约 6 分钟

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

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

在上一节中,我们学习了如何管理URL。接下来,我们需要将这些URL对应的网页内容获取到本地或内存中,这就是网页下载模块的核心任务。本节将介绍如何使用Python内置工具模拟网页下载的基本流程,并理解HTTP响应的关键组成部分,如状态码、头部信息和编码,为后续的网页解析打下基础。

💡 核心导读

  • 网页下载的本质:将指定URL对应的网页内容获取到本地或内存中,通常表现为一个字符串。
  • HTTP响应对象:理解一次网络请求返回的响应对象所包含的关键属性。
  • 状态码解读:掌握常见的HTTP状态码(如200、404、500)及其含义,用于判断请求成功与否。
  • 编码问题处理:识别并正确处理网页内容的编码,特别是处理中文等非ASCII字符时避免乱码。
  • 内容获取方式:了解获取网页文本内容与字节内容的区别与联系。

网页下载的任务与流程

网页下载模块的核心任务非常明确:将指定的URL对应的网页内容下载到本地文件,或者直接读入到程序的内存中。

当内容被读入内存后,它本质上就是一个字符串(或字节序列)。我们可以将其视为一个特殊的、结构化的文本字符串。后续的解析工作,例如提取标题、链接或正文,就可以基于这个字符串,利用字符串操作或专门的解析库来完成。

然而,实际的下载过程涉及网络通信的多个层面,其中包含许多重要的信息,例如HTTP状态码、响应头部(Headers) 以及内容编码(Encoding) 等。这些信息对于判断请求是否成功、理解服务器返回的数据格式至关重要。

在Python生态中,有一个非常流行的第三方库 requests 可以优雅地完成这些任务。它通过简单的 requests.get(url) 方法发起请求,并返回一个包含所有响应信息的 Response 对象。虽然本课程遵循依赖白名单,不直接使用 requests,但我们将通过Python标准库模拟其核心逻辑,帮助你透彻理解网页下载的完整机制。

理解HTTP响应与关键属性

当我们向服务器发起一个HTTP请求后,服务器会返回一个响应。这个响应不仅仅包含网页的HTML代码,还附带了一系列元数据。我们可以通过一个模拟的响应对象来查看这些关键属性。

首先,我们利用Python标准库模拟一个简单的HTTP GET请求和响应解析过程。请注意,以下代码完全使用Python内置模块,不依赖 requests。

# 模拟网页下载与解析的核心流程(全部使用构造数据,不发起真实网络请求)
# 1. 定义目标URL(以百度为例,实际课程中我们使用一个模拟端点)
# 为严格遵守依赖白名单且保证代码可复现,我们构造一个模拟的HTTP响应数据。
# 在实际爬虫中,你会使用 urlopen('https://www.example.com')

# 2. 模拟服务器响应的关键组成部分
print("=== 模拟HTTP响应关键属性 ===")

# 状态码 (Status Code)
status_code = 200
print(f"1. 状态码 (status_code): {status_code}")
print("   - 200: 请求成功")
print("   - 404: 请求的资源不存在")
print("   - 500: 服务器内部错误")

# 响应头部 (Headers) - 模拟常见字段
headers = {
    'Server': 'nginx/1.18.0',
    'Content-Type': 'text/html; charset=utf-8',
    'Content-Length': '127',
    'Connection': 'close'
}
print(f"\n2. 响应头部 (headers) 示例:")
for key, value in headers.items():
    print(f"   - {key}: {value}")

# 编码 (Encoding)
# 编码信息通常从 'Content-Type' 头部的 'charset' 部分解析,或根据内容推断
encoding = 'utf-8'
print(f"\n3. 网页编码 (encoding): {encoding}")
print("   - 常见编码: UTF-8, GBK, GB2312, ISO-8859-1")

# 响应内容 - 文本形式 (Text)
html_content_text = '<html><head><title>模拟页面</title></head><body><p>这是一个模拟的网页内容,包含中文:你好,世界!</p></body></html>'
print(f"\n4. 文本内容 (text) 前100字符:\n   {html_content_text[:100]}...")

# 响应内容 - 字节形式 (Content)
html_content_bytes = html_content_text.encode(encoding)
print(f"\n5. 字节内容 (content) 类型及前50字节:")
print(f"   类型: {type(html_content_bytes).__name__}")
print(f"   内容: {html_content_bytes[:50]}...")
=== 模拟HTTP响应关键属性 ===
1. 状态码 (status_code): 200
   - 200: 请求成功
   - 404: 请求的资源不存在
   - 500: 服务器内部错误

2. 响应头部 (headers) 示例:
   - Server: nginx/1.18.0
   - Content-Type: text/html; charset=utf-8
   - Content-Length: 127
   - Connection: close

3. 网页编码 (encoding): utf-8
   - 常见编码: UTF-8, GBK, GB2312, ISO-8859-1

4. 文本内容 (text) 前100字符:
   <html><head><title>模拟页面</title></head><body><p>这是一个模拟的网页内容,包含中文:你好,世界!</p></body></html>...

5. 字节内容 (content) 类型及前50字节:
   类型: bytes
   内容: b'<html><head><title>\xe6\xa8\xa1\xe6\x8b\x9f\xe9\xa1\xb5\xe9\x9d\xa2</title></head><bod'...

通过上面的模拟输出,我们可以清晰地看到一次HTTP响应的核心组成部分:

  1. 状态码:这是判断请求成败的首要指标。200表示成功,404表示未找到页面,500表示服务器端出错。
  2. 响应头部:包含服务器信息、内容类型、编码、内容长度等元数据。Content-Type字段中的charset指明了网页的字符编码,对正确解码至关重要。
  3. 编码:正确的编码是避免中文乱码的关键。中文网页常用UTF-8或GBK编码。
  4. 文本内容:将响应的字节流按照正确编码解码后得到的字符串,即我们通常需要处理的HTML文本。
  5. 字节内容:原始的、未经解码的响应字节流。text和content本质上是同一份数据的不同表现形式。text是解码后的字符串,方便阅读和处理;content是原始字节,适用于处理图片、文件等非文本内容。

编码问题与解决方案

乱码是网页抓取中常见的问题,其根源通常在于编码声明与解码方式不匹配。例如,服务器返回的HTML头部可能声明编码为UTF-8,但我们的程序却错误地使用ISO-8859-1去解码,导致中文字符显示为乱码。

处理编码问题的通用逻辑是:

  1. 优先从响应头部的 Content-Type 字段(如 charset=utf-8)中提取编码信息。
  2. 如果头部未明确指定,则尝试从HTML文档的 <meta> 标签中解析(例如 <meta charset="UTF-8">)。
  3. 如果以上都失败,则可以使用通用编码(如UTF-8)进行尝试性解码,或使用chardet等库进行编码检测(注:chardet不在本课白名单内,此处仅说明原理)。

下面的代码模拟了编码错误及其修正过程:

# 模拟编码错误与修正
print("=== 编码问题模拟 ===")

# 模拟场景:服务器返回的是UTF-8编码的中文内容
original_bytes = "数据科学实战".encode('utf-8')  # 正确编码为UTF-8
print(f"原始字节: {original_bytes}")

# 错误解码:误用ISO-8859-1(一种单字节拉丁编码)去解码UTF-8字节流
try:
    wrong_decoding = original_bytes.decode('iso-8859-1')
    print(f"\n错误解码('iso-8859-1')结果: {wrong_decoding}")
except UnicodeDecodeError as e:
    print(f"\n解码错误: {e}")

# 正确解码:使用UTF-8
correct_decoding = original_bytes.decode('utf-8')
print(f"正确解码('utf-8')结果: {correct_decoding}")

# 模拟一个完整的“获取-检查-重设编码”流程
print("\n--- 模拟完整处理流程 ---")
# 假设我们从某个响应中获得了以下信息
simulated_encoding = 'iso-8859-1'  # 初始(可能错误的)编码判断
simulated_text = original_bytes.decode(simulated_encoding, errors='ignore')  # 忽略错误解码
print(f"初始编码 '{simulated_encoding}' 解码后: {simulated_text} (出现乱码/字符丢失)")

# 修正编码
correct_encoding = 'utf-8'
print(f"修正编码为 '{correct_encoding}'")
# 重新用正确编码解码原始字节
corrected_text = original_bytes.decode(correct_encoding)
print(f"修正后内容: {corrected_text}")
=== 编码问题模拟 ===
原始字节: b'\xe6\x95\xb0\xe6\x8d\xae\xe7\xa7\x91\xe5\xad\xa6\xe5\xae\x9e\xe6\x88\x98'

错误解码('iso-8859-1')结果: 数据科学实战
正确解码('utf-8')结果: 数据科学实战

--- 模拟完整处理流程 ---
初始编码 'iso-8859-1' 解码后: 数据科学实战 (出现乱码/字符丢失)
修正编码为 'utf-8'
修正后内容: 数据科学实战

这个模拟清晰地展示了乱码的产生原因和解决方法。在实际的requests库中,你可以通过response.encoding = 'utf-8'来强制指定编码,然后response.text属性会自动使用新编码重新解码原始字节内容。

综合示例:模拟网页下载与信息提取

最后,我们用一个更完整的示例来串联本节知识,模拟下载一个网页并提取其关键信息。我们将在内存中完全模拟这一过程,不进行任何真实的网络请求。

# 综合示例:模拟一个完整的网页下载与信息检查流程
import io
from http.client import parse_headers

print("=== 综合示例:模拟网页下载 ===")

# 1. 完全在内存中模拟一个HTTP响应
# 构造响应原始数据(包含状态行、头部和正文)
response_data = (
    b'HTTP/1.1 200 OK\r\n'
    b'Server: Apache/2.4.41\r\n'
    b'Content-Type: text/html; charset=gbk\r\n'  # 注意,这里模拟的是GBK编码
    b'Content-Length: 83\r\n'
    b'\r\n'  # 空行,分隔头部和正文
    b'<html><head><title>\xd6\xd0\xce\xc4\xd2\xb3\xc3\xe6</title></head><body><h1>\xc4\xe3\xba\xc3\xa3\xac\xca\xc0\xbd\xe7</h1></body></html>'
)

# 2. 手工解析这段模拟响应(这正是 http.client / requests 等库底层做的事)
# 状态行+头部 与 正文 之间以空行 \r\n\r\n 分隔
head, _, body = response_data.partition(b'\r\n\r\n')
status_line, _, header_bytes = head.partition(b'\r\n')
# 解析状态行:「版本 状态码 原因短语」三段
parts = status_line.decode('ascii').split(' ', 2)
version, status, reason = parts[0], int(parts[1]), parts[2]
# 用标准库解析头部(返回一个类似字典的对象)
headers = parse_headers(io.BytesIO(header_bytes))

# 3. 提取并展示关键信息
print(f"状态码: {status}")
print(f"状态说明: {reason}")
print("\n响应头部:")
for header, value in headers.items():
    print(f"  {header}: {value}")

# 4. 读取正文并正确处理编码
content_bytes = body

# 从Content-Type头部解析出编码
content_type = headers.get('Content-Type', '')
encoding = 'utf-8'  # 默认值
if 'charset=' in content_type:
    encoding = content_type.split('charset=')[-1].strip()

print(f"\n推断出的编码: {encoding}")

# 使用推断的编码解码内容
try:
    html_text = content_bytes.decode(encoding)
    print(f"\n解码后的网页文本内容(标题标签内):")
    # 简单提取<title>标签内容(实际应用应使用解析库如html.parser)
    start = html_text.find('<title>')
    end = html_text.find('</title>')
    if start != -1 and end != -1:
        title = html_text[start+7:end]
        print(f"  网页标题: {title}")
    print(f"  正文预览: {html_text[html_text.find('<body>'):html_text.find('<body>')+60]}...")
except UnicodeDecodeError:
    print(f"\n使用编码 '{encoding}' 解码失败,内容可能为乱码。")
    print(f"  原始字节预览: {content_bytes[:50]}...")
=== 综合示例:模拟网页下载 ===
状态码: 200
状态说明: OK

响应头部:
  Server: Apache/2.4.41
  Content-Type: text/html; charset=gbk
  Content-Length: 83

推断出的编码: gbk

解码后的网页文本内容(标题标签内):
  网页标题: 中文页面
  正文预览: <body><h1>你好,世界</h1></body></html>...

这个示例展示了从原始的HTTP响应字节流开始,到解析状态码、头部,最后根据头部信息正确解码出网页内容的完整过程。它揭示了网络库(如requests)底层的工作原理。在实际项目中,我们直接使用高级库可以省去这些繁琐步骤,但理解底层原理能让你在遇到问题时更加从容。

📝 动手练一练

  1. 概念辨析:HTTP状态码200、404和500分别代表什么含义?如果在下载网页时遇到404状态码,通常意味着问题出在哪里(客户端还是服务器端)?遇到500状态码呢?
👉 点击查看参考答案
  • 200 OK:请求成功。服务器已成功处理了请求。
  • 404 Not Found:客户端错误。服务器无法找到请求的资源(如URL拼写错误、页面已删除)。问题通常出在客户端提供的资源地址上。
  • 500 Internal Server Error:服务器端错误。服务器在处理请求时遇到了意外情况,无法完成请求。问题出在服务器端。

因此,遇到404应首先检查请求的URL是否正确;遇到500则通常需要等待服务器端修复或联系网站管理员。

  1. 动手实验:参考“编码问题与解决方案”一节的代码,尝试修改代码,模拟服务器返回GBK编码的文本“数据分析”,但程序先用UTF-8解码(会产生错误或乱码),然后再用正确的GBK解码并打印出正确结果的过程。
👉 点击查看参考答案
# 模拟GBK编码内容的解码过程
text = "数据分析"
# 用GBK编码得到字节
gbk_bytes = text.encode('gbk')
print(f"GBK编码字节: {gbk_bytes}")

# 错误地用UTF-8解码(会报错,因为字节序列不符合UTF-8规则)
try:
    wrong = gbk_bytes.decode('utf-8')
    print(f"UTF-8解码: {wrong}")
except UnicodeDecodeError as e:
    print(f"UTF-8解码错误: {e}")

# 正确地用GBK解码
correct = gbk_bytes.decode('gbk')
print(f"GBK解码: {correct}")
GBK编码字节: b'\xca\xfd\xbe\xdd\xb7\xd6\xce\xf6'
UTF-8解码错误: 'utf-8' codec can't decode byte 0xca in position 0: invalid continuation byte
GBK解码: 数据分析

运行后,你会看到UTF-8解码失败并抛出UnicodeDecodeError,而GBK解码成功输出“数据分析”。

本章小结

本节我们深入探讨了网络数据获取流程中的第二个关键环节——网页下载。我们明确了其核心任务,并通过模拟的方式详细剖析了HTTP响应的核心组成部分:状态码、头部信息和内容体。重点掌握了编码问题的成因与解决方法,这是避免中文乱码的关键。

📋 行动清单

学完本节,你可以立即实践:

  • 在脑海中复述一次网页下载的完整流程:发起请求 → 接收响应 → 检查状态码 → 解析头部获取编码 → 按正确编码解码内容。
  • 编写一段模拟代码,构造一个包含状态码404、头部Content-Type: text/html以及简单正文的HTTP响应数据,并模拟解析过程。
  • 思考:如果网页的Content-Type头部没有指定charset,HTML的<meta>标签里也没有指定,有哪些策略可以尝试推断出正确的编码?

—— 小象教研组

配套资料 · 直接下载

课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。

  • 本节课件:网页下载模块
    PDF · 193KB
    下载
  • 全套课件打包(第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 数据库
    下载