BeautifulSoup 解析 HTML:文档遍历与搜索

FreeGuideOnline 最新 2026-06-16

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 时推荐使用 lxmlhtml5lib。若发现解析结果不符合预期,可检查:

  • 是否使用了正确的解析器(如 lxml 对嵌套标签修复更好)。
  • 原始 HTML 中是否有未闭合的标签,尝试用浏览器的“检查”功能核实结构。
  • 使用 soup.prettify() 输出格式化文档,帮助调试。

小结

  • 遍历 用于在已知文档结构时,通过父子兄弟关系移动,适合小范围导航。
  • 搜索 方法(find,find_all,select)是提取数据的核心,配合字符串、正则或函数实现灵活匹配。
  • 提取信息务必调用 .get_text() 获取文本、用字典操作获取属性,注意区分 .string.text 的差异。
  • 结合 lxml 解析器能显著提升解析速度和容错能力。

掌握这些基础后,你就拥有了解析绝大多数网页的能力。多练习不同的网站结构,逐渐你会自然发现最适合当前场景的解析策略。