杰瑞科技汇

Python解析HTMLParser,如何高效解析HTML?

html.parser 是 Python 标准库中的一个模块,它提供了一个简单的、基于事件的 HTML 解析器,这意味着它不会一次性将整个 HTML 文档加载到内存中并转换成一个复杂的树结构(像 lxmlBeautifulSoup 那样),而是逐个字符地扫描 HTML 文档,当遇到特定的标签、数据或事件时,会触发你预先定义好的处理函数。

Python解析HTMLParser,如何高效解析HTML?-图1
(图片来源网络,侵删)

为什么选择 html.parser

  • 无需安装:它是 Python 标准库的一部分,import html 即可使用,无需像 BeautifulSouplxml 那样通过 pip 安装。
  • 轻量级:对于简单的解析任务,它非常轻量,没有额外的依赖。
  • 易于理解:基于事件驱动,逻辑清晰,适合学习 HTML 解析的基本原理。

html.parser 的工作原理:事件驱动

HTMLParser 的工作核心是事件,当你解析一段 HTML 文本时,解析器会根据它遇到的内容,调用不同的方法,你需要创建一个 HTMLParser 的子类,并重写这些方法来处理你感兴趣的事件。

主要的事件方法包括:

事件方法 触发时机
handle_starttag(tag, attrs) 遇到开始标签,如 <div><a href='...'>
handle_endtag(tag) 遇到结束标签,如 </div>
handle_startendtag(tag, attrs) 遇到自闭合标签,如 <img src='...'><br/>
handle_data(data) 遇到标签内的文本数据
handle_comment(data) 遇到注释 <!-- ... -->
handle_decl(decl) 遇到文档类型声明 <!DOCTYPE ...>

实战演练:一个简单的例子

让我们从一个最简单的例子开始,目标是解析一段 HTML 并打印出所有标签和它们内部的文本。

目标 HTML:

Python解析HTMLParser,如何高效解析HTML?-图2
(图片来源网络,侵删)
<html>
  <head>一个简单的网页</title>
  </head>
  <body>
    <h1>欢迎!</h1>
    <p>这是一个段落。</p>
    <p>这是<b>另一个</b>段落。</p>
  </body>
</html>

Python 代码:

from html.parser import HTMLParser
# 1. 创建一个自定义的解析器类,继承自 HTMLParser
class MyHTMLParser(HTMLParser):
    # 2. 重写 handle_starttag 方法
    def handle_starttag(self, tag, attrs):
        print(f"遇到开始标签: <{tag}>")
        # attrs 是一个 (name, value) 元组的列表
        if attrs:
            print(f"  属性: {attrs}")
    # 3. 重写 handle_endtag 方法
    def handle_endtag(self, tag):
        print(f"遇到结束标签: </{tag}>")
    # 4. 重写 handle_data 方法
    def handle_data(self, data):
        # data 可能包含很多空白字符,我们只处理非空的
        if data.strip():
            print(f"遇到数据: '{data}'")
# --- 主程序 ---
if __name__ == "__main__":
    html_string = """
    <html>
      <head>
        <title>一个简单的网页</title>
      </head>
      <body>
        <h1>欢迎!</h1>
        <p>这是一个段落。</p>
        <p>这是<b>另一个</b>段落。</p>
      </body>
    </html>
    """
    # 5. 创建解析器实例
    parser = MyHTMLParser()
    # 6. 使用 feed() 方法喂入 HTML 字符串进行解析
    parser.feed(html_string)

输出结果:

遇到开始标签: <html>
遇到开始标签: <head>
遇到开始标签: <title>
遇到数据: '一个简单的网页'
遇到结束标签: </title>
遇到结束标签: </head>
遇到开始标签: <body>
遇到开始标签: <h1>
遇到数据: '欢迎!'
遇到结束标签: </h1>
遇到开始标签: <p>
遇到数据: '这是一个段落。'
遇到结束标签: </p>
遇到开始标签: <p>
遇到数据: '这是'
遇到开始标签: <b>
遇到数据: '另一个'
遇到结束标签: </b>
遇到数据: '段落。'
遇到结束标签: </p>
遇到结束标签: </body>
遇到结束标签: </html>

从这个输出中,你可以清晰地看到解析器是如何一步步处理 HTML 的。

更复杂的例子:提取所有链接

现在我们来一个更实用的例子:从一个 HTML 页面中提取出所有 <a> 标签的 href 属性。

Python解析HTMLParser,如何高效解析HTML?-图3
(图片来源网络,侵删)

目标 HTML:

<html>
  <body>
    <h1>我的网站</h1>
    <p>请访问我的<a href="https://www.example.com">主页</a>。</p>
    <p>或者看看这个<a class="external" href="https://www.python.org">Python官网</a>。</p>
    <img src="image.png" />
  </body>
</html>

Python 代码:

from html.parser import HTMLParser
class LinkParser(HTMLParser):
    def __init__(self):
        super().__init__()
        self.links = [] # 用来存储找到的链接
    def handle_starttag(self, tag, attrs):
        # 我们只关心 <a> 标签
        if tag == 'a':
            # attrs 是一个列表,我们可以把它转换成字典方便查找
            attributes = dict(attrs)
            # 检查是否存在 'href' 属性
            if 'href' in attributes:
                self.links.append(attributes['href'])
    def get_links(self):
        return self.links
# --- 主程序 ---
if __name__ == "__main__":
    html_string = """
    <html>
      <body>
        <h1>我的网站</h1>
        <p>请访问我的<a href="https://www.example.com">主页</a>。</p>
        <p>或者看看这个<a class="external" href="https://www.python.org">Python官网</a>。</p>
        <img src="image.png" />
      </body>
    </html>
    """
    parser = LinkParser()
    parser.feed(html_string)
    print("找到的链接:")
    for link in parser.get_links():
        print(link)

输出结果:

找到的链接:
https://www.example.com
https://www.python.org

html.parser 的局限性

虽然 html.parser 很方便,但它也有一些明显的局限性,这也是为什么在实际项目中,人们更倾向于使用 BeautifulSouplxml 的原因:

  1. 容错性差html.parser 对格式错误的 HTML 非常敏感,HTML 代码有哪怕一点点语法错误(比如标签未闭合),解析器可能会抛出异常或直接停止解析。
  2. 功能有限:它只提供基础的解析功能,无法方便地进行复杂的导航,找到第三个 <div> 下的所有 <span>”或“根据 CSS 类名查找元素”,这些操作在 BeautifulSoup 中非常简单。
  3. 性能一般:相比于用 C 语言编写的 lxml,纯 Python 实现的 html.parser 性能要慢一些。

html.parserBeautifulSoup 的关系

BeautifulSoup 实际上是一个“包装器”或“适配器”,它可以使用不同的解析器作为其后端,其中就包括 Python 内置的 html.parser

当你这样使用 BeautifulSoup 时:

from bs4 import BeautifulSoup
soup = BeautifulSoup(html_string, 'html.parser')

BeautifulSoup 会帮你处理所有复杂的解析工作,并提供一个简单、强大的 API 来遍历和搜索 HTML 文档。html.parser 解析失败,你甚至可以轻松切换到更强大的 lxml

soup = BeautifulSoup(html_string, 'lxml') # 只需改一个参数
特性 html.parser BeautifulSoup / lxml
安装 Python 标准库,无需安装 需要通过 pip 安装
易用性 较低,需要手动处理事件 非常高,提供简洁的 API
容错性 较差,对错误 HTML 敏感 非常好,能修复和解析不规范的 HTML
功能 基础,仅解析功能 强大,支持 CSS 选择器、XPath、修改文档等
性能 一般 lxml 很快,BeautifulSoup (用 lxml 作后端) 也很快

如何选择?

  • 如果你只需要快速写一个简单的脚本,处理一小段格式良好的 HTML,并且不想安装任何第三方库html.parser 是一个不错的选择。
  • 如果你在进行任何严肃的 Web 爬虫或数据处理项目,或者需要处理来源不明的、可能格式混乱的 HTML,强烈推荐使用 BeautifulSoup 并搭配 lxml 解析器,它能让你事半功倍,代码也更健壮、更易读。
分享:
扫描分享到社交APP
上一篇
下一篇