第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>&copy; 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的高级用法,包括:

  1. 复杂CSS选择器的使用
  2. 节点关系操作(父节点、兄弟节点等)
  3. 内容提取的高级技巧
  4. 文档修改操作
  5. 特殊内容的处理
  6. 性能优化技巧
  7. 实际应用案例

这些高级技巧将帮助我们更高效、更灵活地从各种复杂的HTML文档中提取所需的数据。下一集我们将学习Scrapy框架的基础知识,进入更专业的爬虫开发领域。

« 上一篇 BeautifulSoup基础 下一篇 » Scrapy框架基础