
题目考点
这道题主要考察:
- 输入输出处理
- 第一行输入整数
N - 接下来读取
N行 HTML 代码
- 第一行输入整数
- HTML 解析
- 识别 HTML 标签,例如
<head>、<object>、<param /> - 识别标签中的属性,例如
type、width - 识别属性值,例如
"application/x-flash"、"0"
- 识别 HTML 标签,例如
- 类与方法重写
- 使用 Python 自带的
HTMLParser - 自己定义一个类,继承
HTMLParser - 重写处理标签的方法
- 使用 Python 自带的
- 忽略注释
- 注释中的内容不能被检测
- 例如:
<!-- <param name="movie" value="your-file.swf" /> -->
这一行里面虽然有 <param>,但是它在注释里,所以不能输出。
审题
题目给我们一个 HTML 代码片段,要我们按照出现顺序打印:
标签名
-> 属性名 > 属性值
如果一个标签没有属性,只打印标签名。
例如:
<object type="application/x-flash" width="0">
应该输出:
object
-> type > application/x-flash
-> width > 0
需要注意的是,题目只关心:
<开始标签>
<自闭合标签 />
不需要输出结束标签,例如:
</head>
</object>
这些结束标签不用打印。
思路提示
这道题不要一开始就想着自己手写正则表达式去匹配 HTML。HTML 的情况比较复杂,比如属性可能换行,注释可能跨多行。如果用正则,初学者很容易漏情况。
Python 已经提供了一个专门解析 HTML 的工具:
from html.parser import HTMLParser
我们可以把思路拆成三步:
第一步,定义一个自己的解析器类,让它继承 HTMLParser。
第二步,重写两个方法:
handle_starttag()
handle_startendtag()
其中:
handle_starttag()
负责处理普通开始标签,例如:
<object>
而:
handle_startendtag()
负责处理自闭合标签,例如:
<param />
第三步,在这两个方法里打印标签名和属性。
完整设计思路
第一步:读取输入
题目第一行是整数 N,表示后面有多少行 HTML。
所以可以这样读:
n = int(input())
然后读取接下来的 N 行:
html_code = ""
for _ in range(n):
html_code += input() + "\n"
这里把所有行拼接成一个完整字符串,是因为 HTML 注释可能跨多行。如果一行一行处理,有时不方便判断注释范围。
第二步:创建 HTML 解析器类
我们定义一个类:
class MyHTMLParser(HTMLParser):
它继承自 Python 自带的 HTMLParser。
HTMLParser 会帮我们分析 HTML 结构。当它遇到不同内容时,会自动调用不同方法。
比如遇到开始标签:
<head>
它会调用:
handle_starttag()
遇到自闭合标签:
<param name="quality" value="high"/>
它会调用:
handle_startendtag()
第三步:处理普通开始标签
普通开始标签包括:
<head>
<title>
<object type="application/x-flash">
我们重写:
def handle_starttag(self, tag, attrs):
其中:
tag
表示标签名。
attrs
表示属性列表。
例如:
<object type="application/x-flash" width="0">
解析后大致是:
tag = "object"
attrs = [
("type", "application/x-flash"),
("width", "0")
]
所以我们先打印标签名:
print(tag)
然后遍历属性:
for name, value in attrs:
print(f"-> {name} > {value}")
第四步:处理自闭合标签
自闭合标签例如:
<param name="quality" value="high"/>
它也要按照同样格式输出。
所以 handle_startendtag() 里的代码和 handle_starttag() 很像。
代码实现
完整代码如下:
from html.parser import HTMLParser
class MyHTMLParser(HTMLParser):
def handle_starttag(self, tag, attrs):
print(tag)
for name, value in attrs:
print(f"-> {name} > {value}")
def handle_startendtag(self, tag, attrs):
print(tag)
for name, value in attrs:
print(f"-> {name} > {value}")
n = int(input())
html_code = ""
for _ in range(n):
html_code += input() + "\n"
parser = MyHTMLParser()
parser.feed(html_code)
代码逐段解释
1. 导入工具
from html.parser import HTMLParser
这里引入的是 Python 自带的 HTML 解析工具。
它的作用是:帮我们识别 HTML 里的标签、属性、注释、文本等内容。
这道题中,我们主要用它来识别:
开始标签
自闭合标签
属性
属性值
2. 定义自己的解析器
class MyHTMLParser(HTMLParser):
这表示我们创建了一个自己的解析器类。
它继承了 HTMLParser 的能力,但我们可以重写里面的方法,让它按照题目要求打印内容。
3. 处理开始标签
def handle_starttag(self, tag, attrs):
print(tag)
for name, value in attrs:
print(f"-> {name} > {value}")
当解析器遇到这种标签时:
<object type="application/x-flash" width="0">
会自动调用这个方法。
其中:
tag
是标签名:
object
attrs
是属性列表:
[("type", "application/x-flash"), ("width", "0")]
所以这段代码会输出:
object
-> type > application/x-flash
-> width > 0
4. 处理自闭合标签
def handle_startendtag(self, tag, attrs):
print(tag)
for name, value in attrs:
print(f"-> {name} > {value}")
自闭合标签长这样:
<param name="quality" value="high"/>
它不是普通开始标签,而是开始和结束写在一起。
所以需要单独重写 handle_startendtag()。
运行演示
题目样例输入大概是:
9
<head>
<title>HTML</title>
</head>
<object type="application/x-flash"
data="your-file.swf"
width="0" height="0">
<!-- <param name="movie" value="your-file.swf" /> -->
<param name="quality" value="high"/>
</object>
程序读取完之后,交给:
parser.feed(html_code)
解析器开始分析 HTML。
首先遇到:
<head>
输出:
head
接着遇到:
<title>
输出:
title
遇到:
</title>
这是结束标签,不输出。
遇到:
</head>
还是结束标签,不输出。
然后遇到:
<object type="application/x-flash"
data="your-file.swf"
width="0" height="0">
这是一个开始标签,标签名是 object,有四个属性,所以输出:
object
-> type > application/x-flash
-> data > your-file.swf
-> width > 0
-> height > 0
然后遇到:
<!-- <param name="movie" value="your-file.swf" /> -->
这是注释,里面的 <param> 不算,所以不输出。
最后遇到:
<param name="quality" value="high"/>
这是自闭合标签,输出:
param
-> name > quality
-> value > high
所以最终输出是:
head
title
object
-> type > application/x-flash
-> data > your-file.swf
-> width > 0
-> height > 0
param
-> name > quality
-> value > high
这道题的关键理解
这道题表面上像是字符串处理题,但更准确地说,它是一个 HTML 解析题。
你需要识别的不是普通字符串,而是 HTML 结构:
标签
属性
属性值
注释
结束标签
所以更适合使用专门的解析器,而不是自己硬拆字符串。
方法总结
以后看到类似题目,可以这样判断:
如果题目要求你从 HTML 中提取标签、属性、文本内容,优先考虑:
from html.parser import HTMLParser
然后根据题目要求重写不同的方法:
| 方法 | 作用 |
|---|---|
handle_starttag() | 处理开始标签,例如 <div> |
handle_startendtag() | 处理自闭合标签,例如 <br /> |
handle_endtag() | 处理结束标签,例如 </div> |
handle_comment() | 处理注释 |
handle_data() | 处理标签之间的文本 |
这道题只需要输出开始标签和自闭合标签,所以只重写:
handle_starttag()
handle_startendtag()
不用处理结束标签,也不用处理注释。
小练习
请你尝试处理下面这个输入:
5
<html>
<body class="main">
<img src="logo.png" alt="logo"/>
<!-- <a href="test.html">test</a> -->
<a href="home.html">Home</a>
提示:
<html>没有属性,只输出标签名。<body class="main">有一个属性。<img ... />是自闭合标签。- 注释中的
<a>不能输出。 - 最后一行的
<a href="home.html">要输出标签和属性。
这种题目是要求我们继承类,然后重写方法吗?
那attrs的数据结构到底是什么形式?本质上解析是靠的那个被继承类的方法,我们写的方法,只不过是为了将这个类里面处理好的数据进行打印出来,能不能这样子理解
可以这样理解,而且你的理解已经接近本质了。
这类题的核心不是让我们从零写一个 HTML 解析器,而是让我们继承 Python 已经写好的 HTMLParser 类,然后重写它在解析过程中会自动调用的方法。
1. 这道题是不是要求继承类、重写方法?
是的,这道题的标准思路基本就是:
from html.parser import HTMLParser
然后自己写一个类:
class MyHTMLParser(HTMLParser):
...
这里的意思是:
我不重新发明 HTML 解析器,而是使用 Python 已经提供好的 HTMLParser。
我只负责告诉它:当你解析到标签时,我希望你怎么处理。
所以我们要重写这些方法:
def handle_starttag(self, tag, attrs):
...
def handle_startendtag(self, tag, attrs):
...
这两个方法不是我们随便起的名字,而是 HTMLParser 规定好的方法名。
当解析器遇到普通开始标签时,它会自动调用:
handle_starttag()
当解析器遇到自闭合标签时,它会自动调用:
handle_startendtag()
例如:
<object type="application/x-flash" width="0">
会触发:
handle_starttag(self, tag, attrs)
而:
<param name="quality" value="high"/>
会触发:
handle_startendtag(self, tag, attrs)
2. attrs 的数据结构到底是什么?
attrs 的本质是一个列表 list,列表里面的每一项是一个二元组 tuple。
形式是:
[
("属性名1", "属性值1"),
("属性名2", "属性值2"),
("属性名3", "属性值3")
]
也就是说,attrs 的结构可以理解成:
attrs = [
(attribute_name, attribute_value),
(attribute_name, attribute_value),
...
]
举个例子。
如果 HTML 是:
<object type="application/x-flash" width="0" height="0">
那么解析器传给我们的数据大概是:
tag = "object"
attrs = [
("type", "application/x-flash"),
("width", "0"),
("height", "0")
]
所以我们可以这样遍历:
for name, value in attrs:
print(name, value)
这里的 name 会依次拿到:
type
width
height
value 会依次拿到:
application/x-flash
0
0
所以题目要求输出:
-> type > application/x-flash
-> width > 0
-> height > 0
代码就是:
for name, value in attrs:
print(f"-> {name} > {value}")
3. 你的理解是否正确?
你说的这句话:
本质上解析是靠的那个被继承类的方法,我们写的方法,只不过是为了将这个类里面处理好的数据进行打印出来。
这个理解是对的。
更准确地说,可以这样表述:
HTMLParser负责真正的 HTML 解析工作,比如识别哪里是标签、哪里是属性、哪里是注释、哪里是结束标签。
我们继承它之后,重写指定的handle_...方法,相当于定义“解析器遇到某类内容时应该做什么”。
在这道题里,我们做的事情很简单,就是把它已经解析好的tag和attrs按题目格式打印出来。
也就是说:
parser.feed(html_code)
这一句才是真正启动解析的地方。
它会扫描整个 HTML 字符串。扫描过程中,如果遇到开始标签,就自动调用你写的:
handle_starttag()
如果遇到自闭合标签,就自动调用你写的:
handle_startendtag()
所以可以把整个过程理解成:
HTML 字符串
↓
parser.feed(html_code)
↓
HTMLParser 内部开始解析
↓
发现开始标签
↓
自动调用 handle_starttag(tag, attrs)
↓
我们在方法里打印 tag 和 attrs
4. 为什么我们不用自己写解析逻辑?
因为 HTML 的结构不只是简单的字符串切割。
比如这个标签跨了多行:
<object type="application/x-flash"
data="your-file.swf"
width="0" height="0">
如果你自己用字符串切割,就要考虑:
标签从哪里开始?
标签到哪里结束?
属性之间怎么分隔?
属性值里的引号怎么处理?
注释里的标签要不要忽略?
这些事情都很麻烦。
但是 HTMLParser 已经帮你处理好了。
它最终直接把结果整理成:
tag = "object"
attrs = [
("type", "application/x-flash"),
("data", "your-file.swf"),
("width", "0"),
("height", "0")
]
我们只需要输出。
5. 用一个简单例子看 attrs
假设输入里有一行:
<a href="https://example.com" target="_blank">Example</a>
解析器遇到 <a ...> 时,会调用:
handle_starttag("a", attrs)
其中:
tag = "a"
attrs = [
("href", "https://example.com"),
("target", "_blank")
]
所以这段代码:
def handle_starttag(self, tag, attrs):
print(tag)
for name, value in attrs:
print(f"-> {name} > {value}")
会输出:
a
-> href > https://example.com
-> target > _blank
6. 这类题的本质套路
以后你看到题目说:
检测 HTML 标签
检测 HTML 属性
检测 HTML 注释
检测 HTML 文本内容
就要想到:
from html.parser import HTMLParser
然后看题目要你处理哪种内容。
常见方法是:
handle_starttag() # 开始标签
handle_endtag() # 结束标签
handle_startendtag() # 自闭合标签
handle_data() # 标签中间的文本
handle_comment() # 注释
这道题只要求打印开始标签和自闭合标签,所以我们主要重写:
handle_starttag()
handle_startendtag()
7. 一个关键类比
可以把 HTMLParser 想象成一个已经写好的扫描机器。
它负责扫描:
<object type="text" width="100">
然后告诉你:
我发现了一个开始标签
标签名是 object
属性有 type=text,width=100
你写的 handle_starttag() 就是接收这个通知的地方。
所以不是你主动去拆 HTML,而是:
解析器发现东西
↓
解析器自动调用你的方法
↓
你在方法里处理结果
这就是“继承 + 重写方法”的用途。
小练习
判断下面这个标签被解析后,tag 和 attrs 分别是什么:
<img src="logo.png" alt="site logo" width="200"/>
提示:
tag是标签名。attrs是一个列表。- 列表中的每一项是一个二元组:
("属性名", "属性值")。



