理解基本的HTML解析

在用HTMLParser模块解析之前,一般需要定义一个子类HTMLParser.HTMLParser,并添加用来处理不同标签的函数。例子:

#!/usr/bin/env python#-*-coding:utf-8-*-import sysfrom HTMLParser import HTMLParserclass TitleParser(HTMLParser):    def __init__(self):        self.title=''        self.readingtitle=0        HTMLParser.__init__(self)#初始化和重置实例        def handle_starttag(self,tag,attrs):        if tag=='title':            self.readingtitle=1    def handle_data(self,data):        if self.readingtitle:            self.title+=data    def handle_endtag(self,tag):        if tag=='title':            self.readingtitle=0        def gettitle(self):        return self.titlefd=open(sys.argv[1])tp=TitleParser()tp.feed(fd.read())print 'Title is:',tp.gettitle()tp.close()

HTMLParser的feed()方法会适当地调用handle_starttag()、handle_data()和handle_endtag()方法。按照我的理解,HTMLParser会解析html文档中的每一层,若遇到的是<...>,则将括号内的内容交给handle_starttag()处理,若是</...>,则交给handle_endtag()处理,若是两者之间的,则交给handle_data()处理。

除此以外,真是的html中会有实体。实体表示正规的字符,例如:&amp;表示“&”。对于实体的处理会调用handle_entityref()方法。除了实体外,还有字符参考,看上去类似&#174;这样的字符参考是用来嵌入那些不能打印的字符的。对于字符参考的处理会调用handle_charref()函数处理。

对于HTML代码来说,一个令人讨厌的问题是不均衡的标签。因为在HTML中,有些标签是不要求结束的。而XHTML要求左右的标签必须有结束部分。mxTidy和uTidylib库可以用来自动修复写得不号的HTML代码。例子:

Document Title & Intro®</HEAD>This is my text.
  • First list item
  • Second list item
  • Third list item
#!/usr/bin/env pythonfrom htmlentitydefs import entitydefsfrom HTMLParser import HTMLParserimport sys,reclass TitleParser(HTMLParser):    def __init__(self):        self.taglevels=[]        self.handledtags=['title','ul','li']        self.processing=None        HTMLParser.__init__(self)        def handle_starttag(self,tag,attrs):        if len(self.taglevels) and self.taglevels[-1]==tag:            #Processing a previous version of this tag.Close it out             #and then start a new on this one            self.handle_endtag(tag)            #Note that we're now processing this tag        self.taglevels.append(tag)                if tag in self.handledtags:            self.data=''            self.processing=tag            if tag=='ul':                print 'List started.'    def handle_data(self,data):        if self.processing:            self.data+=data        def handle_endtag(self,tag):        if not tag in self.taglevels:            return        while len(self.taglevels):            starttag=self.taglevels.pop()            if starttag in self.handledtags:                self.finishprocessing(starttag)            if starttag==tag:                break    def cleanse(self):        self.data=re.sub('\s+',' ',self.data)    def finishprocessing(self,tag):        self.cleanse()        if tag=='title' and tag==self.processing:            print 'Document Title:',self.data        elif tag=='ul':            print 'List ended.'        elif tag=='li' and tag==self.processing:            print 'List item:',self.data        self.processing=None    def handle_entityref(self,name):        if entitydefs.has_key(name):            self.handle_data(entitydefs[name])        else:            self.handle_data('&'+name+';')        def handle_charref(self,name):        try:            charnum=int(name)        except ValueError:            return        if charnum<1 or charnum>255:            return        self.handle_data(chr(charnum))        def gettitle(self):        return self.titlefd=open(sys.argv[1])tp=TitleParser()tp.feed(fd.read())

在handle_starttag()函数中,无论何时只要出现了一个开始标签,系统就会在self.taglevels中记录下来。若标签是程序处理的三种之一,也会在self.processing设置标记来通知系统开始记录数据。在handle_endtag()中,首先检查在查询中是否存在一个和结束标签对应的起始标签。若没有就会略过。若有就会找到最近的哪个。self.finishprocessing()函数会去掉数据字符串中的空格,并打印除合适的消息。tag==self.processing确保相同的数据不会被使用两次。