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

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

<HTML>
<HEAD>
<TITLE>Document Title &amp; Intro&#174;
</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确保相同的数据不会被使用两次。