首页 > 深入Python > HTML 处理 | << >> | ||||
diveintopython.org Python for experienced programmers |
目录
我经常在comp.lang.python上看到关于象“怎么才能从我的 HTML 文档中列出所有的[头|图像|链接]呢?怎么才能[分析|解释|munge]我的HTML文档的文本,但是不要标记呢?怎么才能一次给我所有的HTML标记[增加|删除|加引号]属性呢?”本章将回答所有这些问题。
以下是一个完整的,可工作的Python程序,它分为两部分。第一部分,BaseHTMLProcessor.py,是一个通用工具,它可以帮助你处理 HTML 文件,通过扫描标记和文本块。第二部分,dialect.py,是一个例子,演示了如何使用 BaseHTMLProcessor.py 来转化 HTML 文档文本,但是去掉了标记。阅读文档字符串(doc string)和注释来了解将要发生事情的概况。大部分内容看上去象巫术,因为任一个这些类的方法是如何调用的不是很清楚。不要紧,所有内容都会按进度被展示出来。
如果你还没有运行过,可以下载本例或本书用到的其它的例子。
from sgmllib import SGMLParser import htmlentitydefs class BaseHTMLProcessor(SGMLParser): def reset(self): # 扩展 (可以通过调用 SGMLParser.__init__) self.pieces = [] SGMLParser.reset(self) def unknown_starttag(self, tag, attrs): # 对每一个开始标记进行调用 # attrs 是(attr, value)元组的一个列表 # 例如,对于 <pre class="screen">标记,tag="pre",attrs=[("class", "screen")] # 理想地,我们愿意重构初始的标记和属性,但是我们可能由于在源文档中没对属性 # 值加引号而终止,或我们可能修改了在属性值前后的引号类型(单引号到双引号)。 # 注意不正确地嵌入非HTML代码(象客户端的Javascript)可能被父类分析得不正确, # 从而引发运行时脚本错误。 # 所有非HTML代码必须被包括在HTML注释标记之中(<!-- code -->),来保证非HTML # 代码可以不被修改地通过这个分析器(在 handle_comment中处理)。 strattrs = "".join([' %s="%s"' % (key, value) for key, value in attrs]) self.pieces.append("<%(tag)s%(strattrs)s>" % locals()) def unknown_endtag(self, tag): # 对每一个结束标记进行调用,例如,对于 </pre>, 标记将是 "pre"。 # 重构原始结束标记。 self.pieces.append("</%(tag)s>" % locals()) def handle_charref(self, ref): # 对每一个字符引用进行调用,例如,对于" ", ref 将是"160"。 # 重构原始字符引用。 self.pieces.append("&#%(ref)s;" % locals()) def handle_entityref(self, ref): # 对每一个实体引用进行调用,例如,对于"©", ref 将是"copy"。 # 重构原始实体引用。 self.pieces.append("&%(ref)s;" % locals()) # 标准的 HTML 实体以一个分号结束;其它的实体则不是这样 if htmlentitydefs.entitydefs.has_key(ref): self.pieces.append(";") def handle_data(self, text): # 对每一块无格式文本进行调用,也就是在任何标记之外的,不包含任何字符或 # 实体引用的文本 # 保存原始的文本逐字复制。 self.pieces.append(text) def handle_comment(self, text): # 对每一个HTML注释进行调用,例如,<!-- insert Javascript code here --> # 重构原始注释。 # 当源文档中将客户端代码(如Javascript)包括在注释中时这一点非常重要, # 这样这些代码才可以不受影响地通过处理器;参见在unknown_starttag中的 # 注释来了解更多细节。 self.pieces.append("<!--%(text)s-->" % locals()) def handle_pi(self, text): # 对每一个处理指令进行调用,例如 <?instruction> # 重构原始处理指令。 self.pieces.append("<?%(text)s>" % locals()) def handle_decl(self, text): # 对DOCTYPE进行调用, 如果存在的话,例如 # <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" # "http://www.w3.org/TR/html4/loose.dtd"> # 重构原始DOCTYPE self.pieces.append("<!%(text)s>" % locals()) def output(self): """返回处理后的HTML作为单个字符串""" return "".join(self.pieces)from sgmllib import SGMLParser
import re from BaseHTMLProcessor import BaseHTMLProcessor class Dialectizer(BaseHTMLProcessor): subs = () def reset(self): # 扩展 (从父类中的 __init__ 进行调用) # 重置所有数据属性 self.verbatim = 0 BaseHTMLProcessor.reset(self) def start_pre(self, attrs): # 对HTML源代码中的每个<pre>标记进行调用 # 增加verbatim模式计数,然后象正常方式处理标记 self.verbatim += 1 self.unknown_starttag("pre", attrs) def end_pre(self): # 对HTML源代码中的每个</pre>标记进行调用 # 减少verbatim模式计数 self.unknown_endtag("pre") self.verbatim -= 1 def handle_data(self, text): # 覆盖 # 对HTML源代码中的每个文本块进行调用 # 如果处于verbatim模式,保存未修改过的文本; # 否则用一系列的替换来处理文本 self.pieces.append(self.verbatim and text or self.process(text)) def process(self, text): # 从 handle_data 中进行调用 # 通过执行一系列的正则表达式替换处理文本块(真正的替换定义在子类中) for fromPattern, toPattern in self.subs: text = re.sub(fromPattern, toPattern, text) return text class ChefDialectizer(Dialectizer): """转换HTML为瑞典厨师用语(Swedish Chef-speak) 基于著名的chef.x, 版权所有 (c) 1992, 1993 John Hagerman """ subs = ((r'a([nu])', r'u\1'), (r'A([nu])', r'U\1'), (r'a\B', r'e'), (r'A\B', r'E'), (r'en\b', r'ee'), (r'\Bew', r'oo'), (r'\Be\b', r'e-a'), (r'\be', r'i'), (r'\bE', r'I'), (r'\Bf', r'ff'), (r'\Bir', r'ur'), (r'(\w*?)i(\w*?)$', r'\1ee\2'), (r'\bow', r'oo'), (r'\bo', r'oo'), (r'\bO', r'Oo'), (r'the', r'zee'), (r'The', r'Zee'), (r'th\b', r't'), (r'\Btion', r'shun'), (r'\Bu', r'oo'), (r'\BU', r'Oo'), (r'v', r'f'), (r'V', r'F'), (r'w', r'w'), (r'W', r'W'), (r'([a-z])[.]', r'\1. Bork Bork Bork!')) class FuddDialectizer(Dialectizer): """转换HTML为埃尔默唠叨者用语(Elmer Fudd-speak)""" subs = ((r'[rl]', r'w'), (r'qu', r'qw'), (r'th\b', r'f'), (r'th', r'd'), (r'n[.]', r'n, uh-hah-hah-hah.')) class OldeDialectizer(Dialectizer): """转换HTML为模仿中世纪英语(mock Middle English)""" subs = ((r'i([bcdfghjklmnpqrstvwxyz])e\b', r'y\1'), (r'i([bcdfghjklmnpqrstvwxyz])e', r'y\1\1e'), (r'ick\b', r'yk'), (r'ia([bcdfghjklmnpqrstvwxyz])', r'e\1e'), (r'e[ea]([bcdfghjklmnpqrstvwxyz])', r'e\1e'), (r'([bcdfghjklmnpqrstvwxyz])y', r'\1ee'), (r'([bcdfghjklmnpqrstvwxyz])er', r'\1re'), (r'([aeiou])re\b', r'\1r'), (r'ia([bcdfghjklmnpqrstvwxyz])', r'i\1e'), (r'tion\b', r'cioun'), (r'ion\b', r'ioun'), (r'aid', r'ayde'), (r'ai', r'ey'), (r'ay\b', r'y'), (r'ay', r'ey'), (r'ant', r'aunt'), (r'ea', r'ee'), (r'oa', r'oo'), (r'ue', r'e'), (r'oe', r'o'), (r'ou', r'ow'), (r'ow', r'ou'), (r'\bhe', r'hi'), (r've\b', r'veth'), (r'se\b', r'e'), (r"'s\b", r'es'), (r'ic\b', r'ick'), (r'ics\b', r'icc'), (r'ical\b', r'ick'), (r'tle\b', r'til'), (r'll\b', r'l'), (r'ould\b', r'olde'), (r'own\b', r'oune'), (r'un\b', r'onne'), (r'rry\b', r'rye'), (r'est\b', r'este'), (r'pt\b', r'pte'), (r'th\b', r'the'), (r'ch\b', r'che'), (r'ss\b', r'sse'), (r'([wybdp])\b', r'\1e'), (r'([rnt])\b', r'\1\1e'), (r'from', r'fro'), (r'when', r'whan')) def translate(url, dialectName="chef"): """取得URL并且使用dialect进行转换 dialect in ("chef", "fudd", "olde")""" import urllib sock = urllib.urlopen(url) htmlSource = sock.read() sock.close() parserName = "%sDialectizer" % dialectName.capitalize() parserClass = globals()[parserName] parser = parserClass() parser.feed(htmlSource) parser.close() return parser.output() def test(url): """测试对于URL的所有言转换器""" for dialect in ("chef", "fudd", "olde"): outfile = "%s.html" % dialect fsock = open(outfile, "wb") fsock.write(translate(url, dialect)) fsock.close() import webbrowser webbrowser.open_new(outfile) if __name__ == "__main__": test("http://diveintopython.org/odbchelper_list.html")
运行这个脚本会将列表 101转换成模仿瑞典厨师用语(mock Swedish Chef-speak) (来自The Muppets), 模仿埃尔默唠叨者用语(mock Elmer Fudd-speak) (来自 Bugs Bunny 卡通画), 和模仿中世纪英语(mock Middle English) (零散地来源于乔叟的《坎特伯雷故事集》). 如果你查看输出页面的HTML源代码,你会发现所有的HTML标记和属性没有改动,但是在标记之间的文本被转换成模仿语言了。如果你观查得更仔细些,你会发现,实际上,仅有标题和段落被转换了;代码列表和屏幕例子没有改动。
小结 |
1 2 3 4 5 6 7 8 9 10 11 |
sgmllib.py 介绍 >> |