BeautifulSoup 解析 HTML:文档遍历与搜索
BeautifulSoup 解析 HTML:文档遍历与搜索
BeautifulSoup 是一个强大的 Python 库,用于从 HTML 和 XML 文档中提取数据。无论你是想抓取网页信息,还是处理本地 HTML 文件,掌握文档的遍历与搜索是高效解析的关键。本教程将带你从创建解析对象开始,逐步深入,学会使用 BeautifulSoup 精确定位和提取所需内容。
环境准备与解析器选择
使用前请确保已安装 BeautifulSoup 和解析器:
pip install beautifulsoup4 lxml
推荐配合 lxml 使用,因为它速度快且容错能力强。在代码中导入并解析文档:
from bs4 import BeautifulSoup
html_doc = """
<html><head><title>示例页面</title></head>
<body>
<p class="title"><b>我的博客</b></p>
<p class="story">从前有三只小猪...</p>
<a href="http://example.com/page1" id="link1">第一页</a>
</body></html>
"""
soup = BeautifulSoup(html_doc, 'lxml') # 或使用 'html.parser'
BeautifulSoup 对象就代表了整个文档,接下来的一切操作都基于它。
文档遍历:在节点树中穿行
HTML 文档可以看作一个标签树。BeautifulSoup 提供了丰富的属性让我们在树中上下移动。
向下遍历:访问子节点
-
通过标签名直接获取
使用.标签名可以快速拿到第一个匹配的标签。soup.head # <head><title>示例页面</title></head> soup.title # <title>示例页面</title> soup.p # <p class="title"><b>我的博客</b></p> (仅第一个 p) -
.contents与.children
前者返回子节点列表(包括换行符等 NavigableString),后者返回可迭代对象。head_tag = soup.head head_tag.contents # [<title>示例页面</title>] for child in soup.p.children: print(child) # <b>我的博客</b> -
.descendants
递归遍历所有子孙节点。与.children不同,它会深入多层。for descendant in soup.html.descendants: print(descendant.name if descendant.name else repr(descendant))
向上遍历:访问父节点与祖先
.parent获取直接父节点。soup.title.parent # <head>...</head>.parents迭代所有祖先,直到<html>甚至整个文档。for parent in soup.title.parents: print(parent.name) # head, html, [document]
横向遍历:兄弟节点
.next_sibling和.previous_sibling
注意:很多情况下下一个兄弟可能是换行符或空白文本。通常需要多次调用或结合条件判断。.next_siblings和.previous_siblings返回迭代器,用于遍历所有同级节点。
p_tag = soup.p
p_tag.next_sibling # 可能是 '\n'
p_tag.next_sibling.next_sibling # <p class="story">...
文档搜索:精准定位所需数据
遍历有时显得繁琐,BeautifulSoup 真正的威力在于搜索方法,它们让你能以类似 CSS 选择器或自定义规则快速匹配标签。
find() 与 find_all()
find_all() 是最常用的搜索方法,它会递归搜索所有匹配的标签;find() 只返回第一个匹配结果。
基本用法:按标签名查找
soup.find_all('p') # 找到所有 <p> 标签
soup.find('a') # 返回第一个 <a> 标签
限制返回数量:soup.find_all('p', limit=2)
按属性值过滤
将属性以关键字参数传递,class 是 Python 保留字,需写作 class_。
# 查找 class 为 'story' 的 p 标签
soup.find_all('p', class_='story')
# 查找 id 为 'link1' 的标签
soup.find_all(id='link1')
# 查找 href 属性包含 'page1' 的链接
soup.find_all('a', href='http://example.com/page1')
多值属性:HTML 的 class 可以有多值,BeautifulSoup 会将它们视为列表。匹配时只要任一值相同即可找到。
soup.find_all('p', class_=['title', 'story']) # 匹配 class 含 title 或 story
使用正则表达式与函数
搜索条件不局限于字符串,你还可以传入:
- 正则表达式对象(
re.compile):匹配标签名或属性值。 - 自定义函数:接收标签对象,返回
True/False。
import re
# 查找所有包含数字 id 的标签
soup.find_all(id=re.compile(r'\d+'))
# 查找所有有 href 属性的标签
soup.find_all(href=True)
# 自定义函数:查找包含文字 "小猪" 的标签
def has_xiaozhu(tag):
return tag.name == 'p' and '小猪' in tag.get_text()
soup.find_all(has_xiaozhu)
按文本内容搜索:string 参数
有时我们需要根据标签内包含的文本进行过滤。使用 string 参数(注意不是 text,虽然老版本支持,但推荐 string)。
soup.find_all('b', string='我的博客') # 精确匹配
soup.find_all('p', string=re.compile('小猪')) # 正则匹配
组合搜索与层级关系
BeautifulSoup 允许你指定搜索的上下文,而不是从整个文档搜索。
# 先在 body 下搜索
body = soup.find('body')
body.find_all('p')
# 或一次性链式调用(注意 find_all 返回的是列表,故需循环)
for p in soup.find_all('p'):
link = p.find('a')
if link:
print(link['href'])
CSS 选择器:用 select() 高效提取
如果你熟悉前端开发,select() 方法会让你倍感亲切。它支持几乎全部的 CSS 选择器语法,返回匹配的标签列表。
# 类选择器
soup.select('.story')
# ID 选择器
soup.select('#link1')
# 层级选择器
soup.select('html head title') # 空格分隔后代
soup.select('p > b') # 直接子元素
# 属性选择器
soup.select('a[href]') # 包含 href 的 a 标签
soup.select('a[href$="page1"]') # href 以 page1 结尾
soup.select('p[class~="title"]') # class 中包含 title 的 p
# 组合与伪类
soup.select('p.story, p.title') # 同时选择两类 p
配合 select_one() 快速获取第一个匹配项。
提取关键信息:内容与属性
找到目标标签后,下一步就是提取我们需要的数据。
-
获取标签内文本
.string:仅当标签内只有一个NavigableString时有效,否则返回None。.get_text()/.text:获取所有子节点拼接后的文本,可指定分隔符。
soup.title.string # '示例页面' soup.body.get_text() # 返回可见文本,包含换行 soup.body.get_text(strip=True) # 去除多余空白 -
获取属性值
使用字典式访问或.get()方法避免键不存在报错。link = soup.find('a') link['href'] # 'http://example.com/page1' link.get('id') # 'link1' link.get('target', '') # 提供默认值
实战案例:提取一个简单网页的链接与标题
将以上知识串联起来,假设我们要提取页面中所有故事段落和对应链接。
from bs4 import BeautifulSoup
import requests
url = 'https://example-blog.com/stories'
response = requests.get(url)
soup = BeautifulSoup(response.text, 'lxml')
# 获取页面主标题
title = soup.select_one('h1.entry-title').get_text(strip=True)
# 提取所有文章卡片
cards = soup.select('div.post-card')
for card in cards:
headline_tag = card.select_one('h2 > a')
if headline_tag:
headline = headline_tag.get_text(strip=True)
link = headline_tag['href']
print(f'标题:{headline}\n链接:{link}\n---')
此模式可轻易扩展到各类结构化数据抓取场景。
处理不规范 HTML 的注意事项
BeautifulSoup 内置的解析器 html.parser 较宽容,但处理破损 HTML 时推荐使用 lxml 或 html5lib。若发现解析结果不符合预期,可检查:
- 是否使用了正确的解析器(如
lxml对嵌套标签修复更好)。 - 原始 HTML 中是否有未闭合的标签,尝试用浏览器的“检查”功能核实结构。
- 使用
soup.prettify()输出格式化文档,帮助调试。
小结
- 遍历 用于在已知文档结构时,通过父子兄弟关系移动,适合小范围导航。
- 搜索 方法(
find,find_all,select)是提取数据的核心,配合字符串、正则或函数实现灵活匹配。 - 提取信息务必调用
.get_text()获取文本、用字典操作获取属性,注意区分.string和.text的差异。 - 结合
lxml解析器能显著提升解析速度和容错能力。
掌握这些基础后,你就拥有了解析绝大多数网页的能力。多练习不同的网站结构,逐渐你会自然发现最适合当前场景的解析策略。