虽然近年来随着 REST API 的不断发展,越来越多的网站/APP都提供 REST 接口给开发者使用,从而我们可以很方便得使用自己喜欢熟悉的编程语言对我们常用的或者感兴趣的数据进行操作。但是,有一些没有对拥有者来说比较重要的数据,可能 ta 就不会提供 API 给我们了,但是,我们是可以登录进去查看的,这个时候,如果我们需要使用我们熟悉的编程语言获取到感兴趣的数据,那么就需要使用爬虫或者类似爬虫的机制去抓取数据了。
说到抓取数据,有一个环节必不可少,那就是解析 HTML 文档了,因为大多数情况下我们抓取到的都是 HTML 原文本,有时候甚至抓取到的是 JS 代码,而页面都是通过 JS 渲染出来的。这篇文章要讨论的是抓取到的是标准的 HTML 文档,而不考虑 JS 动态渲染的情况,所以重点在于如何使用 HTMLParser 这个 Python 自带的原生库。
HTMLParser 库重要函数介绍
对于 HTMLParser 来说,我个人觉得有三个函数是比较重要的,因为 HTMLParser 是基于流式的处理库,所以我们需要关注的函数有:
- handle_starttag
- handle_endtag
- handle_data
其实就是 开始标签,结束标签和标签之间的内容,下面就简单介绍一下这三个函数:
- HTMLParser.handle_starttag(tag, attrs)
这个函数会在 HTMLParser 解析遇到标签开始的时候调用,例如遇到
<h3 class="chapter-head">
这样的标签的时候被调用,它包含两个参数,一个是 tag,一个是 attrs:
- tag: 就是这个标签的名称,例如这里应该就是 "h3"
attrs: 是这个标签的属性,需要注意的是这个参数是元组的列表,也就是说它是个列表,而列表里面的元素是元组。例如,这里应该就是:[('class', 'chapter-head')]
HTMLParser.handle_endtag(tag)
这个函数会在 HTMLParser 解析遇到标签结束的时候调用,例如遇到
</h3>
这样的标签的时候被调用,它只有一个参数,就是这个标签的名称,那这里就是 "h3" 了
- HTMLParser.handle_data(data)
这个函数会在 HTMLParser 解析遇到标签内部的内容是被调用,那么什么是内容了,加入一个完整的 HTML 标签对是:
<h3 class="chapter-head"> HTMLParser 示例 </h3>
<h3 class="chapter-head">
我们知道是对应
handle_starttag
的,而
</h3>
是对应
handle_endtag
了,那么中间的
HTMLParser 示例
就是对应我们这里的
handle_data
了。
HTMLParser 示例
对于我们使用 HTMLParser 解析 HTML,我觉得使用状态机来对应是非常恰当的,例如我要解析一个统计某个指标的页面的时候,因为指标被分维度了,例如分为:按浏览器来分每个版本的浏览器有多少访问用户使用,按国家来分每个国家分别有多少访问用户等等,这些在 HTML 文档中应该都是按块来编写的,所以我就画了一个状态图来转换:
INIT_STATE ---> SUMMARY ---> RS
|
|
|
v
TERM <---- PF <---- TP <--- CT
然后解析 HTML 的代码就可以这样编写了,需要注意的是,我这里只使用了
handle_data
:
# create a subclass and override the handler methods
class MyHTMLParser(HTMLParser):
def __init__(self):
HTMLParser.__init__(self)
self.status = Status.INIT
def transfer_status(self, content):
pass
def handle_data(self, data):
content = data.strip()
if content:
self.transfer_status(content)
self.status_processer[self.status](content)
# instantiate the parser and fed it some HTML
parser = MyHTMLParser()
parser.feed(data)
如果你有兴趣查看完整的代码,我已经将代码放入 Github 中,你可以【点此】 阅读完整代码。