查看全课大纲(第 20 / 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.实战:通过移动设备行为数据预测性别和年龄
网页解析模块(2)
约 10 分钟
小象实战讲义 · Python数据分析实战
在上一节中,我们学习了如何使用 BeautifulSoup 查找网页中的节点。找到节点只是第一步,更重要的是如何从节点中提取出我们需要的信息,以及如何利用节点之间的层级关系来定位目标。本节我们将深入讲解如何获取节点的详细信息(标签名、属性、文本),并掌握如何通过“家族关系”(父子、兄弟、子孙)来获取其他相关节点,从而构建灵活、强大的网页解析能力。
💡 核心导读
- 节点信息的提取:理解 Tag 对象,掌握如何获取节点的标签名、属性和文本内容。
- 节点的层级关系:理解 DOM 树结构,学会使用
.children、.descendants、.next_sibling、.parent等属性遍历节点。 - 关系定位的实战意义:当无法直接定位目标节点时,如何通过已知节点的关系间接获取。
- 代码实践:通过模拟 HTML 代码,动手练习获取节点信息与关系遍历。
获取节点信息
当我们使用 find() 或 find_all() 查找到一个节点后,返回的是一个 Tag 对象。这个对象包含了该节点的所有信息,我们可以通过它的属性来访问。
一个 Tag 对象主要包含三部分信息:
- 标签名称:节点本身的标签名,如
'a'、'div'、'table'。 - 属性:标签内的属性,如
href、class、id等,以字典形式存储。 - 文本:标签所包含的文本内容。
下面我们通过一个简单的例子来演示。假设我们有一段 HTML 代码,包含一个链接:
# 示例:获取节点信息
# 注意:本课为演示原理,使用模拟HTML。实际项目中,BeautifulSoup常用于解析requests获取的网页内容。
# 由于环境限制,我们使用标准库html.parser进行解析。
from html.parser import HTMLParser
import re
# 模拟一段包含链接的HTML
html_doc = """
<html>
<body>
<a class="mnav" href="http://news.baidu.com" name="tj_trnews">新闻</a>
</body>
</html>
"""
# 为了演示,我们手动解析并模拟Tag对象的行为。
# 在实际BeautifulSoup中,操作是类似的。
class SimpleTag:
def __init__(self, tag, attrs, text):
self.name = tag
self.attrs = dict(attrs) if attrs else {}
self.text = text
def __getitem__(self, key):
return self.attrs.get(key)
def __repr__(self):
return f"<{self.name} {self.attrs}> {self.text}"
# 简单解析逻辑(仅用于本示例)
def parse_simple_html(html):
# 使用正则简单提取第一个<a>标签(仅用于教学演示)
pattern = r'<a\s+([^>]+)>([^<]+)</a>'
match = re.search(pattern, html, re.IGNORECASE)
if match:
attr_str, text = match.groups()
# 解析属性
attrs = []
for attr in re.findall(r'(\w+)="([^"]*)"', attr_str):
attrs.append(attr)
return SimpleTag('a', attrs, text.strip())
return None
# 解析并获取Tag对象
link_tag = parse_simple_html(html_doc)
print("1. 完整的Tag对象:")
print(f" {link_tag}")
print()
print("2. 节点标签名称 (Tag.name):")
print(f" {link_tag.name}")
print()
print("3. 节点的所有属性 (Tag.attrs):")
print(f" {link_tag.attrs}")
print()
print("4. 获取特定属性,如href (Tag['href']):")
print(f" {link_tag['href']}")
print()
print("5. 节点的文本内容 (Tag.text):")
print(f" {link_tag.text}")1. 完整的Tag对象:
<a {'class': 'mnav', 'href': 'http://news.baidu.com', 'name': 'tj_trnews'}> 新闻
2. 节点标签名称 (Tag.name):
a
3. 节点的所有属性 (Tag.attrs):
{'class': 'mnav', 'href': 'http://news.baidu.com', 'name': 'tj_trnews'}
4. 获取特定属性,如href (Tag['href']):
http://news.baidu.com
5. 节点的文本内容 (Tag.text):
新闻关键点回顾:
Tag.name返回标签名(字符串)。Tag.attrs返回包含所有属性的字典。Tag['属性名']可以获取特定属性的值。Tag.text返回节点内的纯文本(会拼接所有子孙文本)。
理解节点的层级关系
HTML 文档是一个树形结构(DOM 树)。节点之间存在着家族式的层级关系:父子、兄弟、子孙。理解这些关系,能让我们在无法直接定位目标节点时,通过已知节点“顺藤摸瓜”。
常用的关系属性有:
.children:返回节点的直接子节点(生成器)。只包含下一级。.descendants:返回节点的所有子孙节点(生成器)。递归包含所有后代。.next_sibling/.previous_sibling:返回下一个或上一个兄弟节点。.parent:返回节点的父节点。
我们以一个简单的商品表格为例来理解这些关系:
# 示例:理解节点的层级关系
# 我们构造一个简单的表格HTML,并模拟遍历其关系。
table_html = """
<table border="1" id="product-table">
<tr>
<th>Item</th>
<th>Title</th>
<th>Cost</th>
</tr>
<tr>
<td>001</td>
<td>Apple</td>
<td>$1.2</td>
</tr>
<tr>
<td>002</td>
<td>Banana</td>
<td>$0.8</td>
</tr>
</table>
"""
# 为了清晰演示,我们定义一个简单的节点遍历函数
def demonstrate_relationships(html):
lines = html.strip().split('\n')
print("原始HTML结构:")
for i, line in enumerate(lines):
print(f"{i:2}: {line}")
print("\n--- 关系解读 ---")
print("1. 整个 <table> 是根节点。")
print("2. <table> 的直接子节点 (.children) 中有三个 <tr> 行;")
print(" 注意:由于标签之间存在换行,.children 也会把换行形成的空白文本节点一并返回,")
print(" 只取标签可写:[c for c in table_tag.children if getattr(c, 'name', None)]。")
print("3. <table> 的子孙节点 (.descendants) 包括:")
print(" - 三个 <tr>")
print(" - 第一行里的 <th>Item</th>, <th>Title</th>, <th>Cost</th>")
print(" - 第二行里的 <td>001</td>, <td>Apple</td>, <td>$1.2</td>")
print(" - 第三行里的 <td>002</td>, <td>Banana</td>, <td>$0.8</td>")
print(" - 以及标签内的文本和标签间的空白文本节点。")
print("4. 第一行 <tr> 的下一个兄弟节点 (.next_sibling) 是标签间的换行文本节点,")
print(" 下一个 <tr> 要用 .find_next_sibling('tr') 来获取。")
print("5. 第二行 <tr> 的父节点 (.parent) 是 <table>。")
demonstrate_relationships(table_html)原始HTML结构:
0: <table border="1" id="product-table">
1: <tr>
2: <th>Item</th>
3: <th>Title</th>
4: <th>Cost</th>
5: </tr>
6: <tr>
7: <td>001</td>
8: <td>Apple</td>
9: <td>$1.2</td>
10: </tr>
11: <tr>
12: <td>002</td>
13: <td>Banana</td>
14: <td>$0.8</td>
15: </tr>
16: </table>
--- 关系解读 ---
1. 整个 <table> 是根节点。
2. <table> 的直接子节点 (.children) 中有三个 <tr> 行;
注意:由于标签之间存在换行,.children 也会把换行形成的空白文本节点一并返回,
只取标签可写:[c for c in table_tag.children if getattr(c, 'name', None)]。
3. <table> 的子孙节点 (.descendants) 包括:
- 三个 <tr>
- 第一行里的 <th>Item</th>, <th>Title</th>, <th>Cost</th>
- 第二行里的 <td>001</td>, <td>Apple</td>, <td>$1.2</td>
- 第三行里的 <td>002</td>, <td>Banana</td>, <td>$0.8</td>
- 以及标签内的文本和标签间的空白文本节点。
4. 第一行 <tr> 的下一个兄弟节点 (.next_sibling) 是标签间的换行文本节点,
下一个 <tr> 要用 .find_next_sibling('tr') 来获取。
5. 第二行 <tr> 的父节点 (.parent) 是 <table>。实战:利用关系定位节点
在实际解析中,我们经常遇到无法直接用 find 精准定位的情况。例如,网页结构复杂,目标标签没有独特的 id 或 class,但我们可以先定位到它的父节点、兄弟节点或子节点,再进一步获取目标。
场景模拟:假设我们需要从一个产品列表中提取每个产品的名称和价格,但产品名称和价格被包裹在不同的标签里,且整个列表结构规整。
# 实战:模拟利用兄弟关系提取结构化数据
# 我们模拟一个产品列表的HTML片段。
products_html = """
<div class="product-list">
<div class="product">
<h3 class="name">笔记本电脑</h3>
<span class="price">¥5999</span>
</div>
<div class="product">
<h3 class="name">无线鼠标</h3>
<span class="price">¥129</span>
</div>
<div class="product">
<h3 class="name">机械键盘</h3>
<span class="price">¥399</span>
</div>
</div>
"""
# 解析思路演示(非执行代码,展示逻辑)
print("【数据提取逻辑】")
print("1. 使用 find_all('div', class_='product') 找到所有产品块。")
print("2. 对每个产品块(product_tag):")
print(" a) 使用 product_tag.find('h3', class_='name').text 获取产品名。")
print(" b) 使用 product_tag.find('span', class_='price').text 获取价格。")
print("3. 将提取的结果存储到列表或字典中。")
print()
print("【模拟提取结果】")
print("产品列表:")
print("- 笔记本电脑 : ¥5999")
print("- 无线鼠标 : ¥129")
print("- 机械键盘 : ¥399")【数据提取逻辑】
1. 使用 find_all('div', class_='product') 找到所有产品块。
2. 对每个产品块(product_tag):
a) 使用 product_tag.find('h3', class_='name').text 获取产品名。
b) 使用 product_tag.find('span', class_='price').text 获取价格。
3. 将提取的结果存储到列表或字典中。
【模拟提取结果】
产品列表:
- 笔记本电脑 : ¥5999
- 无线鼠标 : ¥129
- 机械键盘 : ¥399这种“先定位容器,再内部挖掘”的模式,是网页数据抓取中最常用、最稳健的方法之一。
📝 动手练一练
- 概念辨析:
.children和.descendants属性返回的节点范围有何本质区别?请用“家族关系”来比喻。 - 场景假设:假设你已定位到一个
<td>单元格标签,但你需要获取它所在表格的标题行(第一个<tr>)中对应列的标题文本(<th>内容)。请描述你的查找思路(至少使用两种节点关系属性)。
👉 点击查看参考答案
.children与.descendants的区别:.children只返回节点的直接子节点,相当于“亲儿子”这一代。.descendants返回节点的所有子孙节点,包括儿子、孙子、曾孙……等所有后代。- 比喻:如果节点是一位父亲,
.children就是他亲生的几个孩子;.descendants则是他的整个家族谱系,包括孩子、孩子的孩子,一直延续下去。
获取对应列标题的思路: 思路一(通过父节点和索引):
- 通过
td.parent找到当前单元格所在的行(<tr>)。 - 通过
td.parent.parent找到表格(<table>)。 - 通过
table.find('tr')找到标题行(假设第一个<tr>是标题)。 - 获取标题行中所有
<th>标签,形成一个列表headers。 - 确定当前
<td>在其父行中的索引位置(例如,是第几个<td>)。 - 使用该索引从
headers列表中取出对应的<th>标签,再通过.text获取标题文本。
思路二(通过兄弟节点计数):
- 在当前
<td>节点之前,使用td.find_previous_siblings('td')获取它前面的所有兄弟<td>单元格。 - 前面兄弟单元格的数量
n,就是当前单元格在行中的列索引(从0开始)。 - 找到表格的标题行(同上)。
- 获取标题行中所有
<th>标签列表headers。 - 使用索引
n从headers中取出对应的标题文本。
- 通过
本章小结
本节我们深入学习了 BeautifulSoup 网页解析的核心技能之一:获取节点信息与利用层级关系。你不仅学会了如何从单个 Tag 对象中提取标签名、属性和文本,更重要的是理解了 DOM 树的家族式结构,并掌握了通过 .children、.descendants、.next_sibling、.parent 等属性在节点间导航的方法。这为你处理复杂、嵌套的网页结构,精准定位并提取所需数据,打下了坚实的基础。
📋 行动清单
- 尝试修改实战部分的模拟 HTML,增加或减少一个产品,并手动推演提取逻辑会得到什么结果。
- 在脑海中构建一个三层嵌套的 HTML 结构(如
div > ul > li > span),并口头描述从最内层span如何找到最外层div的路径。 - 回顾本节的两个代码示例,确保你理解每一行打印输出对应的节点关系或信息是什么。
—— 小象教研组
配套资料 · 直接下载
课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。
- 下载本节课件:网页解析模块(2)PDF · 325KB
- 下载全套课件打包(第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 数据库