您当前位置:首页 > 深入 Python > HTML 处理 > 介绍 BaseHTMLProcessor.py | << >> | ||||
深入 Python从 Python 新手到专家 |
SGMLParser 本身不会产生任何输出。它会不断地解析,并在找到每个感兴趣的内容时调用一个方法,但这些方法本身不做任何事情。SGMLParser 是一个 HTML 消费者:它接收 HTML 并将其分解成小的、结构化的片段。正如您在上一节中所见,您可以继承 SGMLParser 来定义捕获特定标签并生成有用内容的类,例如网页上所有链接的列表。现在,您将更进一步,定义一个类来捕获 SGMLParser 抛出的所有内容,并重建完整的 HTML 文档。用技术术语来说,这个类将是一个 HTML 生产者。
BaseHTMLProcessor 继承自 SGMLParser 并提供了所有 8 个基本处理程序方法:unknown_starttag、unknown_endtag、handle_charref、handle_entityref、handle_comment、handle_pi、handle_decl 和 handle_data。
class BaseHTMLProcessor(SGMLParser): def reset(self):self.pieces = [] SGMLParser.reset(self) def unknown_starttag(self, tag, attrs):
strattrs = "".join([' %s="%s"' % (key, value) for key, value in attrs]) self.pieces.append("<%(tag)s%(strattrs)s>" % locals()) def unknown_endtag(self, tag):
self.pieces.append("</%(tag)s>" % locals()) def handle_charref(self, ref):
self.pieces.append("&#%(ref)s;" % locals()) def handle_entityref(self, ref):
self.pieces.append("&%(ref)s" % locals()) if htmlentitydefs.entitydefs.has_key(ref): self.pieces.append(";") def handle_data(self, text):
self.pieces.append(text) def handle_comment(self, text):
self.pieces.append("<!--%(text)s-->" % locals()) def handle_pi(self, text):
self.pieces.append("<?%(text)s>" % locals()) def handle_decl(self, text): self.pieces.append("<!%(text)s>" % locals())
![]() |
reset 由 SGMLParser.__init__ 调用,在调用祖先方法之前将 self.pieces 初始化为空列表。self.pieces 是一个数据属性,它将保存您正在构建的 HTML 文档的片段。每个处理程序方法都将重建 SGMLParser 解析的 HTML,并将该字符串追加到 self.pieces 中。请注意,self.pieces 是一个列表。您可能会试图将其定义为字符串,并将每个片段追加到该字符串中。这可行,但 Python 处理列表的效率要高得多。[2] |
![]() |
由于 BaseHTMLProcessor 没有为特定标签定义任何方法(例如 URLLister 中的 start_a 方法),因此 SGMLParser 会为每个开始标签调用 unknown_starttag。此方法接收标签(tag)和属性名称/值对列表(attrs),重建原始 HTML,并将其追加到 self.pieces 中。这里的字符串格式化有点奇怪;您将在本章后面解开这个谜团(以及看起来很奇怪的 locals 函数)。 |
![]() |
重建结束标签要简单得多;只需获取标签名称并将其括在 </...> 括号中即可。 |
![]() |
当 SGMLParser 找到字符引用时,它会使用原始引用调用 handle_charref。如果 HTML 文档包含引用  ,则 ref 将为 160。重建原始的完整字符引用只需将 ref 括在 &#...; 字符中即可。 |
![]() |
实体引用类似于字符引用,但没有井号。重建原始实体引用需要将 ref 括在 &...; 字符中。(实际上,正如一位博学的读者向我指出的那样,它比这稍微复杂一些。只有某些标准 HTML 实体以分号结尾;其他看起来相似的实体则没有。幸运的是,标准 HTML 实体集定义在 Python 模块 htmlentitydefs 的字典中。因此,这里有一个额外的 if 语句。) |
![]() |
文本块直接追加到 self.pieces 中,不做任何更改。 |
![]() |
HTML 注释括在 <!--...--> 字符中。 |
![]() |
处理指令括在 <?...> 字符中。 |
![]() |
|
HTML 规范要求所有非 HTML 代码(如客户端 JavaScript)都必须包含在 HTML 注释中,但并非所有网页都能正确执行此操作(而且所有现代 Web 浏览器对此都很宽容)。BaseHTMLProcessor 则不宽容;如果脚本嵌入不正确,它将被解析为 HTML。例如,如果脚本包含小于号和等于号,SGMLParser 可能会错误地认为它找到了标签和属性。SGMLParser 始终将标签和属性名称转换为小写,这可能会破坏脚本,而 BaseHTMLProcessor 始终将属性值括在双引号中(即使原始 HTML 文档使用单引号或不使用引号),这肯定会破坏脚本。始终将您的客户端脚本保护在 HTML 注释中。 |
def output(self):"""Return processed HTML as a single string""" return "".join(self.pieces)
[2] Python 处理列表比处理字符串更好的原因是列表是可变的,而字符串是不可变的。这意味着向列表追加元素只会添加元素并更新索引。由于字符串在创建后不能更改,因此像 s = s + newpiece 这样的代码会从原始字符串和新片段的串联创建一个全新的字符串,然后丢弃原始字符串。这涉及大量的内存管理开销,并且随着字符串变长,所需的工作量也会增加,因此在循环中执行 s = s + newpiece 是非常低效的。用技术术语来说,向列表追加 n 个元素的时间复杂度为 O(n),而向字符串追加 n 个元素的时间复杂度为 O(n2)。
<< 从 HTML 文档中提取数据 |
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | |
locals 和 globals >> |