BeautifulSoup将复杂的HTML文档解析成一个树形结构,这个树中的每个节点都是Python对象。所有对象可以分为4种类型:Tag、NavigableString、BeautifulSoup和Comment。理解这些对象类型是掌握BeautifulSoup的关键。
| 对象类型 | 描述 | 示例 | 常用属性/方法 |
|---|---|---|---|
| Tag | HTML/XML标签 | <div class="content"></div> |
name, attrs, find() |
| NavigableString | 标签内的文本 | "Hello World" |
string, text |
| BeautifulSoup | 整个文档对象 | 整个HTML文档 | find_all(), select() |
| Comment | 注释内容 | <!-- 这是注释 --> |
string |
Tag对象对应于HTML或XML文档中的标签(元素)。这是BeautifulSoup中最常用的对象类型。
from bs4 import BeautifulSoup
html = '''
<div class="article" id="main-article">
<h1>标题</h1>
<p>这是一个段落</p>
</div>
'''
soup = BeautifulSoup(html, 'lxml')
# 获取Tag对象
div_tag = soup.div
h1_tag = soup.h1
p_tag = soup.p
print(type(div_tag)) # <class 'bs4.element.Tag'>
print(div_tag) # <div class="article" id="main-article">...</div>
print(div_tag.name) # 'div'
print(h1_tag.name) # 'h1'
print(p_tag.name) # 'p'
print(div_tag.attrs)
# {'class': ['article'], 'id': 'main-article'}
# 获取特定属性
print(div_tag['class']) # ['article']
print(div_tag['id']) # 'main-article'
print(div_tag.get('class')) # ['article']
# 检查属性是否存在
print('id' in div_tag.attrs) # True
print('href' in div_tag.attrs) # False
# 修改现有属性
div_tag['id'] = 'new-article'
print(div_tag)
# <div class="article" id="new-article">...</div>
# 添加新属性
div_tag['data-custom'] = 'custom-value'
print(div_tag)
# <div class="article" id="new-article" data-custom="custom-value">...</div>
# 删除属性
del div_tag['data-custom']
print(div_tag)
# <div class="article" id="new-article">...</div>
# .contents:获取直接子节点的列表
print(div_tag.contents)
# ['\n', <h1>标题</h1>, '\n', <p>这是一个段落</p>, '\n']
# .children:获取直接子节点的生成器
for child in div_tag.children:
print(repr(child))
# .descendants:获取所有后代节点的生成器
for descendant in div_tag.descendants:
print(repr(descendant))
# .parent:获取父节点
print(h1_tag.parent.name) # 'div'
# .parents:获取所有祖先节点
for parent in h1_tag.parents:
print(parent.name if parent.name else type(parent))
# 输出: div, [document], None
NavigableString对象表示标签内的文本内容(字符串),它是Python字符串的子类,但添加了遍历文档树的方法。
from bs4 import BeautifulSoup
html = '<p>这是一个<b>加粗</b>的文本</p>'
soup = BeautifulSoup(html, 'lxml')
p_tag = soup.p
# 获取标签内的文本
print(p_tag.string) # None(因为包含多个子节点)
print(p_tag.text) # '这是一个加粗的文本'
# 获取b标签内的NavigableString
b_tag = soup.b
b_string = b_tag.string
print(type(b_string)) # <class 'bs4.element.NavigableString'>
print(b_string) # '加粗'
print(b_string.parent.name) # 'b'
# 将NavigableString转换为普通字符串
normal_string = str(b_string)
print(type(normal_string)) # <class 'str'>
print(normal_string) # '加粗'
# 替换文本
b_tag.string.replace_with('新的加粗文本')
print(p_tag) # <p>这是一个<b>新的加粗文本</b>的文本</p>
# 添加新文本(会替换原有内容)
b_tag.string = "更新的文本"
print(p_tag) # <p>这是一个<b>更新的文本</b>的文本</p>
# 使用append添加文本
b_tag.append(" 额外内容")
print(b_tag) # <b>更新的文本 额外内容</b>
BeautifulSoup对象表示整个解析后的文档。它继承自Tag对象,所以大部分Tag的方法都可以使用,但有一些特殊的属性。
from bs4 import BeautifulSoup
html = '''
<!DOCTYPE html>
<html>
<head>
<title>示例页面</title>
</head>
<body>
<h1>主标题</h1>
</body>
</html>
'''
soup = BeautifulSoup(html, 'lxml')
# BeautifulSoup对象的类型
print(type(soup)) # <class 'bs4.BeautifulSoup'>
# name属性总是为[document]
print(soup.name) # '[document]'
# 访问整个文档
print(soup.prettify()) # 格式化输出整个文档
# 访问文档类型声明
if soup.original_encoding:
print(f"文档编码: {soup.original_encoding}")
# 获取整个文档的文本内容
print(soup.get_text())
# 查找所有特定标签
all_h1 = soup.find_all('h1')
print(all_h1)
# 使用CSS选择器
title = soup.select_one('title')
print(title.string) # '示例页面'
Comment对象表示HTML文档中的注释。它是NavigableString的子类,但在输出时不会包含注释标记。
from bs4 import BeautifulSoup, Comment
html = '''
<div>
<!-- 这是一个注释 -->
<p>正文内容</p>
</div>
'''
soup = BeautifulSoup(html, 'lxml')
# 查找注释
comment = soup.find(text=lambda text: isinstance(text, Comment))
print(type(comment)) # <class 'bs4.element.Comment'>
print(comment) # ' 这是一个注释 '
print(comment.string) # ' 这是一个注释 '
# 或者直接查找
for element in soup.div.contents:
if isinstance(element, Comment):
print(f"找到注释: {element}")
# 创建新注释
new_comment = Comment("这是新添加的注释")
soup.div.append(new_comment)
print(soup.div.prettify())
在实际使用中,经常需要判断对象的类型并进行相应的处理。
from bs4 import BeautifulSoup, Tag, NavigableString, Comment
html = '''
<div>
<!-- 注释内容 -->
<h1>标题</h1>
<p>段落文本</p>
</div>
'''
soup = BeautifulSoup(html, 'lxml')
div = soup.div
# 遍历并判断类型
for element in div.contents:
if isinstance(element, Tag):
print(f"Tag: {element.name}")
elif isinstance(element, NavigableString):
if isinstance(element, Comment):
print(f"Comment: {element}")
else:
print(f"NavigableString: {repr(element)}")
# 实用函数:提取并清理文本
def extract_text(element):
"""安全提取元素的文本内容,忽略注释"""
if isinstance(element, Comment):
return ""
elif isinstance(element, NavigableString):
return str(element).strip()
elif isinstance(element, Tag):
texts = []
for child in element.children:
texts.append(extract_text(child))
return " ".join(filter(None, texts))
return ""
# 使用示例
print(extract_text(soup.div)) # '标题 段落文本'
下面是一个综合示例,演示如何结合使用不同对象类型来处理复杂的HTML文档。
from bs4 import BeautifulSoup
import re
# 复杂HTML文档
complex_html = '''
<article class="post" data-id="123">
<!-- 文章元数据 -->
<header>
<h1 class="title">BeautifulSoup教程</h1>
<div class="meta">
<span class="author">作者: 张三</span>
<span class="date">发布于: 2024-01-15</span>
</div>
</header>
<div class="content">
<p>这是第一段内容</p>
<p>这是第二段内容,包含<a href="/link">链接</a></p>
<div class="code-example">
<pre><code>print("Hello World")</code></pre>
</div>
</div>
<footer class="tags">
<ul>
<li>Python</li>
<li>BeautifulSoup</li>
<li>网页解析</li>
</ul>
</footer>
</article>
'''
soup = BeautifulSoup(complex_html, 'lxml')
article = soup.article
# 提取文章信息
def extract_article_info(article_tag):
"""提取文章结构化信息"""
info = {
'title': '',
'author': '',
'date': '',
'content': [],
'links': [],
'tags': []
}
# 标题(Tag对象)
title_tag = article_tag.find('h1', class_='title')
if title_tag:
info['title'] = title_tag.text.strip()
# 作者和日期(NavigableString对象)
author_span = article_tag.find('span', class_='author')
if author_span:
info['author'] = author_span.text.replace('作者:', '').strip()
date_span = article_tag.find('span', class_='date')
if date_span:
info['date'] = date_span.text.replace('发布于:', '').strip()
# 内容段落
content_div = article_tag.find('div', class_='content')
if content_div:
for p in content_div.find_all('p'):
info['content'].append(p.text.strip())
# 提取链接
for a in p.find_all('a'):
link_info = {
'text': a.text,
'href': a.get('href', '')
}
info['links'].append(link_info)
# 标签
tags_ul = article_tag.find('ul')
if tags_ul:
for li in tags_ul.find_all('li'):
info['tags'].append(li.text.strip())
return info
# 执行提取
article_info = extract_article_info(article)
print("文章标题:", article_info['title'])
print("作者:", article_info['author'])
print("发布日期:", article_info['date'])
print("内容段落数:", len(article_info['content']))
print("包含链接:", article_info['links'])
print("标签:", article_info['tags'])
.string:当标签只有一个子节点且为文本节点时使用.text:获取标签及其所有后代标签的文本内容<p>Hello <b>World</b></p>
p.string → None(因为包含多个子节点)p.text → "Hello World"b.string → "World"from bs4 import BeautifulSoup, Tag, NavigableString, Comment
# 方法1:使用isinstance()
element = soup.find('p')
if isinstance(element, Tag):
print("这是一个Tag对象")
# 方法2:使用type()
if type(element).__name__ == 'Tag':
print("这是一个Tag对象")
# 方法3:检查属性
if hasattr(element, 'name'):
print("可能是Tag或BeautifulSoup对象")