第153集 BeautifulSoup高级用法
1 复杂选择器的使用
1.1 CSS选择器高级技巧
BeautifulSoup的select()方法支持几乎所有的CSS选择器语法,这使得我们可以进行非常精确的元素选择。
from bs4 import BeautifulSoup
html = """
<div id="container">
<h1 class="title">网站标题</h1>
<div class="content">
<p>第一段内容</p>
<p>第二段内容</p>
</div>
<div class="sidebar">
<ul class="nav">
<li><a href="/home">首页</a></li>
<li><a href="/about">关于我们</a></li>
<li><a href="/contact">联系我们</a></li>
</ul>
</div>
</div>
"""
soup = BeautifulSoup(html, 'lxml')
# ID选择器
container = soup.select('#container')
print(f"ID选择器结果: {len(container)}")
# 类选择器
content_divs = soup.select('.content')
print(f"类选择器结果: {len(content_divs)}")
# 后代选择器
nav_links = soup.select('.sidebar .nav a')
print("后代选择器找到的链接:")
for link in nav_links:
print(f"- {link.get_text()}: {link['href']}")
# 子选择器
nav_items = soup.select('.nav > li')
print(f"子选择器找到的列表项: {len(nav_items)}")
# 相邻兄弟选择器
next_element = soup.select('.title + div')
print(f"相邻兄弟选择器结果: {next_element[0].get_text().strip()}")
# 属性选择器
about_link = soup.select('a[href="/about"]')
print(f"属性选择器找到的链接: {about_link[0].get_text()}")1.2 组合选择器
我们可以将多个选择器组合使用,增强选择的灵活性。
# 组合标签和类选择器
content_paragraphs = soup.select('div.content p')
print(f"组合选择器找到的段落: {len(content_paragraphs)}")
# 多个选择器
all_elements = soup.select('h1, div, a')
print(f"多个选择器找到的元素: {len(all_elements)}")2 节点关系操作
2.1 父节点访问
# 获取父节点
h1_tag = soup.find('h1')
parent_div = h1_tag.parent
print(f"h1的父节点: {parent_div.name}")
print(f"父节点的ID: {parent_div.get('id')}")
# 获取所有祖先节点
print("h1的所有祖先节点:")
for ancestor in h1_tag.parents:
if ancestor.name:
print(f"- {ancestor.name}")2.2 兄弟节点访问
# 获取下一个兄弟节点
first_p = soup.find('p')
next_p = first_p.next_sibling
while next_p and next_p.name is None:
next_p = next_p.next_sibling
print(f"第一个p的下一个兄弟节点: {next_p.name if next_p else 'None'}")
# 获取上一个兄弟节点
last_li = soup.find_all('li')[-1]
prev_li = last_li.previous_sibling
while prev_li and prev_li.name is None:
prev_li = prev_li.previous_sibling
print(f"最后一个li的上一个兄弟节点: {prev_li.find('a').get_text()}")
# 获取所有后续兄弟节点
print("第一个p的所有后续兄弟节点:")
for sibling in first_p.next_siblings:
if sibling.name:
print(f"- {sibling.name}")3 内容提取高级技巧
3.1 文本内容提取
# 获取标签内的所有文本
content_div = soup.find('div', class_='content')
all_text = content_div.get_text()
print(f"div内的所有文本: {all_text.strip()}")
# 获取标签内的文本,包括子标签
all_text_with_children = content_div.text
print(f"div内的所有文本(包括子标签): {all_text_with_children.strip()}")
# 获取特定部分的文本
title_text = soup.title.string if soup.title else "No title"
print(f"title文本: {title_text}")3.2 属性提取高级用法
# 提取多个属性
links = soup.find_all('a')
print("所有链接的属性:")
for link in links:
href = link.get('href', 'No href')
target = link.get('target', 'No target')
print(f"文本: {link.get_text()}, href: {href}, target: {target}")
# 提取自定义属性
custom_html = """
<div data-id="123" data-type="product">产品信息</div>
<div data-id="456" data-type="category">分类信息</div>
"""
custom_soup = BeautifulSoup(custom_html, 'lxml')
divs = custom_soup.find_all('div')
print("自定义属性提取:")
for div in divs:
data_id = div.get('data-id')
data_type = div.get('data-type')
print(f"内容: {div.get_text()}, data-id: {data_id}, data-type: {data_type}")4 文档修改操作
4.1 修改标签内容
# 修改标签文本
content_div = soup.find('div', class_='content')
content_div.string = "新的内容"
print(f"修改后的div内容: {content_div}")
# 追加内容
content_div.append(" <strong>追加的内容</strong>")
print(f"追加后的div内容: {content_div}")4.2 修改标签属性
# 修改属性
about_link = soup.find('a', href='/about')
about_link['href'] = 'https://example.com/about'
about_link['target'] = '_blank'
print(f"修改后的链接: {about_link}")
# 删除属性
about_link.pop('target')
print(f"删除target属性后的链接: {about_link}")4.3 添加和删除标签
from bs4 import Tag
# 创建新标签
new_paragraph = Tag(soup, name='p')
new_paragraph.string = "这是一个新段落"
# 添加新标签
content_div = soup.find('div', class_='content')
content_div.append(new_paragraph)
print(f"添加新段落后的div: {content_div}")
# 删除标签
first_p = soup.find('p')
first_p.decompose()
print(f"删除第一个p标签后的div: {content_div}")5 处理特殊内容
5.1 处理注释
html_with_comments = """
<div>
<!-- 这是一个注释 -->
<p>正常内容</p>
<!-- 另一个注释 -->
</div>
"""
comment_soup = BeautifulSoup(html_with_comments, 'lxml')
comments = comment_soup.find_all(string=lambda text: isinstance(text, Comment))
print("找到的注释:")
for comment in comments:
print(f"- {comment.strip()}")
# 替换注释
for comment in comments:
comment.replace_with("<p>注释已被替换</p>")
print(f"替换后的HTML: {comment_soup.prettify()}")5.2 处理换行和空白
# 标准化空白
html_with_whitespace = """
<div>
<p> 段落内容 </p>
<p>
多行
内容
</p>
</div>
"""
whitespace_soup = BeautifulSoup(html_with_whitespace, 'lxml')
# 获取去除空白的文本
p_tags = whitespace_soup.find_all('p')
for i, p in enumerate(p_tags):
print(f"段落 {i+1} 原始文本: '{p.text}'")
print(f"段落 {i+1} 去除空白: '{p.get_text(strip=True)}'")6 性能优化技巧
6.1 限制搜索范围
# 先定位到特定区域,再搜索
content_div = soup.find('div', class_='content')
paragraphs_in_content = content_div.find_all('p')
print(f"在content div内找到的段落: {len(paragraphs_in_content)}")6.2 使用更高效的解析器
# lxml解析器比html.parser更快
# 确保已经安装lxml: pip install lxml
import time
test_html = "<div><p>" + "test " * 1000 + "</p></div>" * 100
# 使用html.parser
start_time = time.time()
soup1 = BeautifulSoup(test_html, 'html.parser')
end_time = time.time()
print(f"html.parser耗时: {end_time - start_time:.4f}秒")
# 使用lxml
start_time = time.time()
soup2 = BeautifulSoup(test_html, 'lxml')
end_time = time.time()
print(f"lxml耗时: {end_time - start_time:.4f}秒")6.3 避免重复解析
# 解析一次,多次使用
html_content = """...大量HTML内容..."""
soup = BeautifulSoup(html_content, 'lxml')
# 多次使用同一个soup对象
result1 = soup.find_all('div')
result2 = soup.find_all('a')
result3 = soup.select('.important')7 实际应用案例
7.1 解析复杂网页结构
complex_html = """
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>复杂页面示例</title>
</head>
<body>
<header class="site-header">
<h1>网站标题</h1>
<nav class="main-nav">
<ul>
<li><a href="/home">首页</a></li>
<li><a href="/news">新闻</a></li>
<li><a href="/products">产品</a></li>
<li><a href="/contact">联系</a></li>
</ul>
</nav>
</header>
<main class="main-content">
<article class="news-item">
<h2 class="news-title">新闻标题1</h2>
<p class="news-summary">这是新闻摘要内容...</p>
<a href="/news/1" class="read-more">阅读更多</a>
</article>
<article class="news-item">
<h2 class="news-title">新闻标题2</h2>
<p class="news-summary">这是另一条新闻摘要...</p>
<a href="/news/2" class="read-more">阅读更多</a>
</article>
</main>
<footer class="site-footer">
<p>© 2024 网站版权所有</p>
</footer>
</body>
</html>
"""
soup = BeautifulSoup(complex_html, 'lxml')
# 提取新闻列表
news_items = soup.select('.news-item')
print(f"找到的新闻条目: {len(news_items)}")
# 提取每条新闻的信息
print("\n新闻详情:")
for i, item in enumerate(news_items):
title = item.select_one('.news-title').get_text()
summary = item.select_one('.news-summary').get_text()
link = item.select_one('.read-more')['href']
print(f"\n新闻 {i+1}:")
print(f"标题: {title}")
print(f"摘要: {summary}")
print(f"链接: {link}")
# 提取导航菜单
nav_menu = soup.select('.main-nav ul li a')
print("\n导航菜单:")
for link in nav_menu:
print(f"- {link.get_text()}: {link['href']}")7.2 处理动态加载内容
注意:BeautifulSoup只能处理静态HTML内容,对于动态加载的内容(如通过JavaScript生成的),需要结合其他工具如Selenium或Requests-HTML。
# 使用Requests-HTML处理动态内容
# 安装: pip install requests-html
from requests_html import HTMLSession
# 这里只是示例,实际使用时需要替换为真实URL
session = HTMLSession()
# r = session.get('https://example.com/dynamic-page')
# r.html.render() # 渲染JavaScript
# soup = BeautifulSoup(r.html.html, 'lxml')8 总结
通过本集的学习,我们掌握了BeautifulSoup的高级用法,包括:
- 复杂CSS选择器的使用
- 节点关系操作(父节点、兄弟节点等)
- 内容提取的高级技巧
- 文档修改操作
- 特殊内容的处理
- 性能优化技巧
- 实际应用案例
这些高级技巧将帮助我们更高效、更灵活地从各种复杂的HTML文档中提取所需的数据。下一集我们将学习Scrapy框架的基础知识,进入更专业的爬虫开发领域。