理解基本的HTML解析
在用HTMLParser模块解析之前,一般需要定义一个子类HTMLParser.HTMLParser,并添加用来处理不同标签的函数。例子:
#!/usr/bin/env python #-*-coding:utf-8-*- import sys from HTMLParser import HTMLParser class 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.title fd=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中会有实体。实体表示正规的字符,例如:&;表示“&”。对于实体的处理会调用handle_entityref()方法。除了实体外,还有字符参考,看上去类似®;这样的字符参考是用来嵌入那些不能打印的字符的。对于字符参考的处理会调用handle_charref()函数处理。
对于HTML代码来说,一个令人讨厌的问题是不均衡的标签。因为在HTML中,有些标签是不要求结束的。而XHTML要求左右的标签必须有结束部分。mxTidy和uTidylib库可以用来自动修复写得不号的HTML代码。例子:
<HTML> <HEAD> <TITLE>Document Title & Intro® </HEAD> <BODY> This is my text. <UL> <LI>First list item <LI>Second list item</LI> <LI>Third list item </BODY> </HTML>
#!/usr/bin/env python from htmlentitydefs import entitydefs from HTMLParser import HTMLParser import sys,re class 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.title fd=open(sys.argv[1]) tp=TitleParser() tp.feed(fd.read())
在handle_starttag()函数中,无论何时只要出现了一个开始标签,系统就会在self.taglevels中记录下来。若标签是程序处理的三种之一,也会在self.processing设置标记来通知系统开始记录数据。在handle_endtag()中,首先检查在查询中是否存在一个和结束标签对应的起始标签。若没有就会略过。若有就会找到最近的哪个。self.finishprocessing()函数会去掉数据字符串中的空格,并打印除合适的消息。tag==self.processing确保相同的数据不会被使用两次。