
1. 题目考点
这道题主要考:
- HTMLParser 的使用
- 类的继承
- 重写方法
- 字符串判断
- 多行输入拼接
- 按题目格式输出
这道题不是让我们自己手写 HTML 解析器,而是让我们使用 Python 已经提供好的工具:html.parser.HTMLParser。
2. 审题
题目给你一段 HTML 代码,一共有 N 行。
你需要从这段 HTML 中找出三类内容:
| 内容类型 | 示例 | 输出标题 |
|---|---|---|
| 单行注释 | <!-- comment --> | >>> Single-line Comment |
| 多行注释 | <!-- line1\nline2 --> | >>> Multi-line Comment |
| 普通数据 | <div>Hello</div> 里面的 Hello | >>> Data |
注意题目要求:
Do not print data if data == '\n'
意思是:
如果 HTMLParser 读到的数据只是一个换行符 '\n',不要输出它。
3. 思路提示
这道题最关键的点是:
你不需要自己去判断哪里是标签、哪里是注释、哪里是数据。
Python 的 HTMLParser 会帮你解析 HTML。你只需要写一个类,继承它,然后重写对应的方法。
HTMLParser 在解析时会自动调用这些方法:
handle_comment(self, data)
遇到注释时自动调用。
handle_data(self, data)
遇到普通文本数据时自动调用。
所以整体方向是:
第一步,导入 HTMLParser。
第二步,定义一个自己的解析器类,继承 HTMLParser。
第三步,重写 handle_comment(),判断注释是单行还是多行。
第四步,重写 handle_data(),输出普通数据。
第五步,把输入的 N 行 HTML 拼成一个完整字符串,然后交给解析器处理。
4. 完整设计思路
第一步:导入工具
这道题要用:
from html.parser import HTMLParser
HTMLParser 是 Python 内置的 HTML 解析工具。
第二步:定义自己的解析器类
我们可以写:
class MyHTMLParser(HTMLParser):
...
意思是:
我创建一个自己的 HTML 解析器,它继承自 Python 已经写好的 HTMLParser。
第三步:处理注释
HTML 注释长这样:
<!-- comment -->
当 HTMLParser 读到注释时,会自动调用:
handle_comment(self, data)
其中 data 就是注释里面的内容。
例如:
<!-- hello -->
传进来的 data 是:
hello
如果注释里面有换行符 \n,说明是多行注释。
所以可以这样判断:
if '\n' in data:
print(">>> Multi-line Comment")
else:
print(">>> Single-line Comment")
然后再打印注释内容:
print(data)
第四步:处理普通数据
普通数据就是标签之间的文字,例如:
<div>Welcome to HackerRank</div>
其中:
Welcome to HackerRank
就是 data。
当 HTMLParser 读到普通文本时,会自动调用:
handle_data(self, data)
但是题目说,如果 data == '\n',不要打印。
所以写成:
if data != '\n':
print(">>> Data")
print(data)
第五步:读取输入并拼接 HTML
输入第一行是整数 N。
后面 N 行是 HTML 代码。
注意这里有一个很容易错的地方:
我们不能直接把每一行连起来,否则多行注释中的换行会丢失。
例如多行注释原本是:
<!-- line1
line2 -->
如果不加 '\n',就会变成:
<!-- line1line2 -->
这样程序就无法判断它是多行注释了。
所以每读取一行,都要加上换行符:
html += input() + '\n'
5. 代码实现
from html.parser import HTMLParser
class MyHTMLParser(HTMLParser):
def handle_comment(self, data):
if '\n' in data:
print(">>> Multi-line Comment")
else:
print(">>> Single-line Comment")
print(data)
def handle_data(self, data):
if data != '\n':
print(">>> Data")
print(data)
n = int(input())
html = ""
for _ in range(n):
html += input() + '\n'
parser = MyHTMLParser()
parser.feed(html)
6. 运行演示
假设输入是:
4
<!--[if IE 9]>IE9-specific content
<![endif]-->
<div>Welcome to HackerRank</div>
<!--[if IE 9]>IE9-specific content<![endif]-->
程序先把这 4 行拼成一个完整 HTML 字符串。
遇到:
<!--[if IE 9]>IE9-specific content
<![endif]-->
里面有换行符,所以输出:
>>> Multi-line Comment
[if IE 9]>IE9-specific content
<![endif]
遇到:
<div>Welcome to HackerRank</div>
其中 Welcome to HackerRank 是普通数据,所以输出:
>>> Data
Welcome to HackerRank
遇到:
<!--[if IE 9]>IE9-specific content<![endif]-->
里面没有换行符,所以输出:
>>> Single-line Comment
[if IE 9]>IE9-specific content<![endif]
7. 方法总结
这一类题的关键不是自己从头解析字符串,而是识别题目想让你用哪个工具。
看到题目要求处理 HTML,并且要分别处理 comment、data、tag 这些内容时,应该马上想到:
HTMLParser
然后根据题目需要,重写对应的方法:
| 题目要处理的内容 | 应该重写的方法 |
|---|---|
| 开始标签 | handle_starttag() |
| 结束标签 | handle_endtag() |
| 空标签 | handle_startendtag() |
| 注释 | handle_comment() |
| 普通数据 | handle_data() |
本题只需要处理:
handle_comment()
handle_data()
所以代码并不长,难点主要在审题和理解 HTMLParser 的工作方式。
8. 小练习
请你尝试完成一个轻微变形题:
输入若干行 HTML 代码,要求打印所有普通数据 Data,但不打印注释。
示例输入:
3
<div>Hello</div>
<!-- hidden comment -->
<p>Python</p>
期望输出:
>>> Data
Hello
>>> Data
Python
提示:
只需要重写 handle_data(),不需要写 handle_comment()。
我们引入了到底哪些工具,我们是怎么使用的?,我看这个代码,他好像并没有使用到?
这个疑问非常关键:代码里确实没有直接写“提取注释”或“提取 data”的逻辑,因为这件事不是我们手动做的,而是交给了 HTMLParser 这个工具去做。
我们引入的工具只有一个:
from html.parser import HTMLParser
这里的意思是:
从 Python 内置模块 html.parser 里面,导入一个叫 HTMLParser 的类。
它的作用是:帮我们解析 HTML 代码,并且在遇到不同内容时,自动调用对应的方法。
1. 我们到底引入了什么?
这句代码:
from html.parser import HTMLParser
可以拆开理解:
| 部分 | 含义 |
|---|---|
html.parser | Python 内置的 HTML 解析模块 |
HTMLParser | 模块里面提供的 HTML 解析器类 |
from ... import ... | 从某个模块中导入某个工具 |
所以我们引入的是:
HTMLParser
它不是一个普通函数,而是一个类。
你可以把它理解成 Python 官方已经写好的一个“HTML 分析机器”。
2. 为什么看起来好像没有用到?
其实用到了,在这里:
class MyHTMLParser(HTMLParser):
这一行就是在使用 HTMLParser。
它的意思是:
我们自己定义一个类 MyHTMLParser,它继承 Python 官方提供的 HTMLParser。
换句话说:
class MyHTMLParser(HTMLParser):
可以理解成:
我基于官方的 HTML 解析器,做一个自己的解析器。
官方的 HTMLParser 已经有“读取 HTML、识别标签、识别注释、识别文本”的能力。
我们自己写的 MyHTMLParser 只负责告诉它:
遇到注释时,我要怎么输出;
遇到普通数据时,我要怎么输出。
3. 真正启动解析的是哪一行?
真正让解析器开始工作的,是最后这一行:
parser.feed(html)
完整过程是:
parser = MyHTMLParser()
parser.feed(html)
第一行:
parser = MyHTMLParser()
意思是创建一个我们自己的 HTML 解析器对象。
第二行:
parser.feed(html)
意思是把 HTML 字符串喂给解析器,让它开始分析。
这里的 feed 可以理解为“喂入数据”。
4. 为什么 handle_comment() 和 handle_data() 没有被我们手动调用?
这是这道题最容易困惑的地方。
代码里没有写:
parser.handle_comment(...)
parser.handle_data(...)
因为这两个方法不是我们主动调用的,而是 HTMLParser 在解析过程中自动调用的。
例如 HTML 是:
<div>Hello</div>
<!-- test -->
当执行:
parser.feed(html)
时,HTMLParser 会自动分析这段内容。
它看到:
<div>
知道这是开始标签。
它看到:
Hello
知道这是普通数据,于是自动调用:
handle_data("Hello")
它看到:
<!-- test -->
知道这是注释,于是自动调用:
handle_comment("test")
所以我们写的这两个方法,本质上是“回调方法”。
也就是说:
我们提前定义好规则,等 HTMLParser 遇到对应内容时,它会自动帮我们调用。
5. 这段代码每一部分到底在干什么?
我们把代码拆开看:
from html.parser import HTMLParser
导入官方 HTML 解析器。
class MyHTMLParser(HTMLParser):
定义自己的解析器,并继承官方解析器的能力。
def handle_comment(self, data):
告诉解析器:
当你遇到 HTML 注释时,请执行这里的代码。
比如遇到:
<!-- hello -->
这里的 data 就是:
hello
if '\n' in data:
print(">>> Multi-line Comment")
else:
print(">>> Single-line Comment")
判断这个注释里面有没有换行符。
有换行符,就是多行注释。
没有换行符,就是单行注释。
def handle_data(self, data):
告诉解析器:
当你遇到普通文本数据时,请执行这里的代码。
比如遇到:
<div>Hello</div>
这里的 data 就是:
Hello
if data != '\n':
题目要求如果数据只是换行符,就不要打印。
所以我们要过滤掉单独的 '\n'。
parser = MyHTMLParser()
parser.feed(html)
创建解析器,并把 HTML 内容交给它处理。
从这一刻开始,HTMLParser 就会自动扫描 HTML,并自动调用我们写好的方法。
6. 用一个生活类比理解
你可以把 HTMLParser 想象成一个快递分拣机器。
HTML 内容里面有很多东西:
标签、注释、普通文字、换行
HTMLParser 负责识别它们属于哪一类。
我们写的代码相当于提前告诉机器:
如果你发现“注释”,就执行 handle_comment();
如果你发现“普通文字”,就执行 handle_data()。
所以我们不需要自己写复杂的字符串查找规则。
7. 最核心的一句话
这道题的关键是:
HTMLParser 负责“识别 HTML 结构”,我们负责“定义遇到注释和数据时怎么输出”。
所以工具确实用到了,只是它不是像普通函数那样直接调用,而是通过:
class MyHTMLParser(HTMLParser)
和:
parser.feed(html)
间接发挥作用。
8. 小练习
请你判断下面代码中,哪一行真正开始让解析器工作?
from html.parser import HTMLParser
class MyHTMLParser(HTMLParser):
def handle_data(self, data):
print(data)
parser = MyHTMLParser()
parser.feed("<p>Hello</p>")
提示:不是 class 那一行,也不是 def handle_data 那一行。



