如果我们要编写一个搜索引擎,第一步是用爬虫把目标网站的页面抓下来,第二步就是解析该HTML页面,看看里面的内容到底是新闻、图片还是视频。
假设第一步已经完成了,第二步应该如何解析HTML呢?
HTML本质上是XML的子集,但是HTML的语法没有XML那么严格,所以不能用标准的DOM或SAX来解析HTML。
好在Python提供了HTMLParser来非常方便地解析HTML,只需简单几行代码:
from HTMLParser import HTMLParser
from htmlentitydefs import name2codepoint
class MyHTMLParser(HTMLParser):
def handle_starttag(self, tag, attrs):
print('<%s>' % tag)
def handle_endtag(self, tag):
print('%s>' % tag)
def handle_startendtag(self, tag, attrs):
print('<%s/>' % tag)
def handle_data(self, data):
print('data')
def handle_comment(self, data):
print('')
def handle_entityref(self, name):
print('&%s;' % name)
def handle_charref(self, name):
print('%s;' % name)
parser = MyHTMLParser()
parser.feed('Some html tutorial...
END
')
feed()方法可以多次调用,也就是不一定一次把整个HTML字符串都塞进去,可以一部分一部分塞进去。
特殊字符有两种,一种是英文表示的 ,一种是数字表示的Ӓ,这两种字符都可以通过Parser解析出来。
小结
找一个网页,例如https://www.python.org/events/python-events/,用浏览器查看源码并复制,然后尝试解析一下HTML,输出Python官网发布的会议时间、名称和地点。
html.parser — 简单的HTML和XHTML解析器
源代码: Lib/html/parser.py
该模块定义了一个类HTMLParser,它用作解析HTML(超文本标记语言)和XHTML格式文本文件的基础。
class html.parser.HTMLParser(*, convert_charrefs=True)
创建一个能够解析无效标记的解析器实例。
如果convert_charrefs为True(默认值),则所有字符引用(script/style元素中的字符引用除外)将自动转换为相应的Unicode字符。
当遇到开始标记、结束标记、文本、注释和其它标记元素时,HTMLParser实例将被提供HTML数据并调用处理程序方法。 用户应该子类化HTMLParser并覆盖其方法以实现所需的行为。
此解析器不检查结束标记是否与开始标记匹配,或者通过关闭外部元素来隐式关闭的元素调用结束标记处理程序。
Python版本3.4中的修改:已添加convert_charrefs关键字参数。
Python版本3.5中的修改:参数convert_charrefs的值现在默认为True。
from html.parser import HTMLParser
class MyHTMLParser(HTMLParser):
def handle_starttag(self, tag, attrs):
print("Encountered a start tag:", tag)
def handle_endtag(self, tag):
print("Encountered an end tag :", tag)
def handle_data(self, data):
print("Encountered some data :", data)
parser = MyHTMLParser()
parser.feed('Test '
'Parse me!
')
输出将是:
Encountered a start tag: html
Encountered a start tag: head
Encountered a start tag: title
Encountered some data : Test
Encountered an end tag : title
Encountered an end tag : head
Encountered a start tag: body
Encountered a start tag: h1
Encountered some data : Parse me!
Encountered an end tag : h1
Encountered an end tag : body
Encountered an end tag : html
2. HTMLParser方法
HTMLParser实例具有以下方法:
HTMLParser.feed(data)
将一些文本提供给解析器。 只要它由完整的元素组成,它就被处理; 缓存不完整的数据,直到输入更多数据或调用close()。 数据必须是str。
HTMLParser.close()
强制处理所有缓冲数据,就好像它后跟一个文件结束标记一样。 此方法可以由派生类重新定义,以在输入的末尾定义附加处理,但重新定义的版本应始终调用HTMLParser基类方法close()。
HTMLParser.reset()
重置实例。 丢失所有未处理的数据。 这在实例化时隐式调用。
HTMLParser.getpos()
返回当前行号和偏移量。
HTMLParser.get_starttag_text()
返回最近打开的开始标记的文本。 结构化处理通常不需要这样做,但在处理HTML“部署”或重新生成具有最小变化的输入(可以保留属性之间的空白等)时可能是有用的。
遇到数据或标记元素时会调用以下方法,并且要在子类中重写它们。 基类实现什么都不做(handle_startendtag()除外):
HTMLParser.handle_starttag(tag, attrs)
调用此方法来处理标记的开始(例如
from html.parser import HTMLParser
from html.entities import name2codepoint
class MyHTMLParser(HTMLParser):
def handle_starttag(self, tag, attrs):
print("Start tag:", tag)
for attr in attrs:
print(" attr:", attr)
def handle_endtag(self, tag):
print("End tag :", tag)
def handle_data(self, data):
print("Data :", data)
def handle_comment(self, data):
print("Comment :", data)
def handle_entityref(self, name):
c = chr(name2codepoint[name])
print("Named ent:", c)
def handle_charref(self, name):
if name.startswith('x'):
c = chr(int(name[1:], 16))
else:
c = chr(int(name))
print("Num ent :", c)
def handle_decl(self, data):
print("Decl :", data)
parser = MyHTMLParser()
解析doctype:
>>> parser.feed('')
Decl : DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"
解析具有一些属性和标题的元素:
>>> parser.feed('')
Start tag: img
attr: ('src', 'python-logo.png')
attr: ('alt', 'The Python logo')
>>>
>>> parser.feed('Python
')
Start tag: h1
Data : Python
End tag : h1
script和style元素的内容按原样返回,无需进一步解析:
>>> parser.feed('')
Start tag: style
attr: ('type', 'text/css')
Data : #python { color: green }
End tag : style
>>> parser.feed('')
Start tag: script
attr: ('type', 'text/javascript')
Data : alert("hello!");
End tag : script
解析注释:
>>> parser.feed(''
... '')
Comment : a comment
Comment : [if IE 9]>IE-specific content
解析命名和数字字符引用并将它们转换为正确的char(注意:这3个引用都等同于’>’):
>>> parser.feed('>>>')
Named ent: >
Num ent : >
Num ent : >
将不完整的块提供给feed()可以工作,但handle_data()可能会被多次调用(除非convert_charrefs设置为True):
>> for chunk in ['buff', 'ered ', 'text']:
... parser.feed(chunk)
...
Start tag: span
Data : buff
Data : ered
Data : text
End tag : span
解析不规范的HTML(例如,未引用的属性)也有效:
>>> parser.feed('')
Start tag: p
Start tag: a
attr: ('class', 'link')
attr: ('href', '#main')
Data : tag soup
End tag : p
End tag : a