专项练习 · 8 分钟阅读

Python HTMLParser 练习:链接、文本与属性

把解析器拆成小型处理器,更容易信任。这些练习每次只增加一个职责:收集标签、读取属性、捕获文本,最后组合成记录。

从标签收集器开始

重写 handle_starttag,把标签名加入列表。类要小到能在一屏内读完。只做一件事的解析器,比一次提取所有内容的解析器更容易测试。

from html.parser import HTMLParser

class Tags(HTMLParser):
    def __init__(self):
        super().__init__()
        self.names = []
    def handle_starttag(self, tag, attrs):
        self.names.append(tag)

p = Tags()
p.feed("<article><h1>Title</h1><p>Text</p></article>")
print(p.names)

一个处理器,一个职责

读取属性但不要崩溃

attrs 是键值对列表,有些值可能是 None。先转换成字典,再用 get。导航标记里缺少 href 很正常,应跳过,而不是把 None 加入结果。

from html.parser import HTMLParser

class Links(HTMLParser):
    def __init__(self):
        super().__init__()
        self.links = []
    def handle_starttag(self, tag, attrs):
        values = dict(attrs)
        if tag == "a" and values.get("href"):
            self.links.append(values["href"])

p = Links()
p.feed('<a href="/one">One</a><a>No link</a><a href="/two">Two</a>')
print(p.links)

跳过缺失值

捕获标签之间的文本

handle_data 接收的是一段段文本,不是完整句子。逐段去掉首尾空白,忽略空字符串,再决定如何连接。如果空白有意义,要有意识地保留,而不是依赖浏览器。

from html.parser import HTMLParser

class Text(HTMLParser):
    def __init__(self):
        super().__init__()
        self.parts = []
    def handle_data(self, data):
        text = data.strip()
        if text:
            self.parts.append(text)

p = Text()
p.feed("<h1>Title</h1><p>First sentence.</p>")
print(" ".join(p.parts))

清理后的文本节点

解码实体并保留来源

HTMLParser 会解码 &amp; 这类字符引用。保存提取文本时,把原始 URL 和简短来源一起保留。解析器提供结构,来源信息让人可以核对结果。

from html import unescape

raw = "Tom &amp; Jerry"
clean = unescape(raw)
print(clean)
print(raw)

解码并保留原值

把多个处理器组合成记录

一个类同时收集链接和文本时,要显式管理状态,并为每个页面创建新实例。除非明确清空所有列表,否则不要跨文档复用解析器;残留状态是重复记录的常见来源。

from html.parser import HTMLParser

class Page(HTMLParser):
    def __init__(self):
        super().__init__()
        self.links = []
        self.text = []
    def handle_starttag(self, tag, attrs):
        href = dict(attrs).get("href")
        if tag == "a" and href:
            self.links.append(href)
    def handle_data(self, data):
        text = data.strip()
        if text:
            self.text.append(text)

p = Page()
p.feed('<h1>Docs</h1><a href="/docs">Read</a>')
print(p.links)
print(p.text)

两个列表,一次遍历

你的练习清单

常见问题

HTMLParser 是完整浏览器吗?

不是。它是 Python 代码里的宽容分词器,不执行 JavaScript,也不构建完整 DOM。

为什么不用正则?

正则不理解嵌套和带引号的属性。解析器遵循文档结构,也更容易维护。

可以用 BeautifulSoup 吗?

可以,前提是能安装第三方包。标准库 HTMLParser 足以处理简单、结构良好的页面,而且没有依赖。

练习 HTML 解析

12 道题,覆盖标签、属性、文本节点、实体和解析器状态。

打开练习

继续阅读: