再端一碗 BeautifulSoup
在 上一章 里,我们快速演示了 BeautifulSoup 的安装与运行过程,同时也实现了每次选择一个对象的解析方法。
这一章将介绍通过属性查找标签的方法,标签组的使用。
基本上,你遇到的每个网站都有层叠样式表(cascading style sheet, CSS)。虽然你可能会认为,专门为了让浏览器和人类可以理解网站内容而设计一个展现样式的层,是一件愚蠢的事,但是 CSS 的发明却是网络爬虫的福音。 CSS 可以让 HTML 元素呈现出差异化,使那些具有完全相同修饰的元素呈现出不同的样式。比如,有的标签看起来是这样:
<span class="green"></span>
而另一些标签看起来是这样:
<span class="red"></span>
网络爬虫可以通过 class 属性的值,轻松的区分出两种不同的标签。例如,它们可以用 BeautifulSoup 爬取网页上的所有红色文字,而绿色文字一个都不抓,因为 CSS 通过属性准确的呈现网站的样式,所以你大可放心,大多数现代网站上的 class 和 id 属性资源都非常丰富。
下面让我们创建一个网络爬虫来爬取 http://www.pythonscraping.com/pages/warandpeace.html 这个页面。
在这个页面里,小说人物的对话内容都是红色的,人物名称都是绿色的。你可以看到网页源代码里的 span 标签引用了对应的 CSS 属性,如下所示:
我们可以使用以前类似的程序抓取整个页面,然后创建一个 BeautifulSoup 对象:
from urllib.request import urlopen
from bs4 import BeautifulSoup
html = urlopen('https://www.pythonscraping.com/pages/warandpeace.html')
bs = BeautifulSoup(html.read(), 'html.parser')
通过 BeautifulSoup 对象,我们可以用 find_all 函数提取只包含在 < span class=“green”> < /span> 标签里的文字,这样就会得到一个人物名称的 Python 列表(find_all 是一个非常灵活的函数,后面会经常用到它):
nameList = bs.findAll('span', {'class':'green'})
for name in nameList:
print(name.get_text())
代码执行后就会按照《战争与和平》中的人物出场顺序显示所有的人名。这是怎么实现的呢?之前,我们调用 bs.tagName 只能获取页面中指定的第一个标签。现在,调用 bs.find_all(tagName, tagAttributes) 可以获取页面中所有指定的标签,不再是第一个了。
获取人名列表后,程序遍历列表中所有的名字,然后打印 name.get_text(),就可以把标签中的内容分开显示了。
什么时候使用 get_text() ?什么时候应该保留标签?
.get_text() 会清除你正在处理的 HTML 文档中的所有标签,然后返回一个只包含文字的 Unicode 字符串。假如你正在处理一个包含许多超链接、段落和其他标签的大段文本,那么 .get_text() 会把这些超链接、段落和标签都清除掉,只剩下一串不带标签的文字。
用 BeautifulSoup 对象查找你想要的信息,比直接在 HTML 文本里查找信息要简单得多。通常在你准备打印、存储和操作最终数据时,应该最后才使用 .get_text() 。一般情况下,你应该尽可能地保留 HTML 文档的标签结构。
BeautifulSoup 的 find() 和 find_all()
BeautifulSoup 里的 find() 和 find_all() 可能是最常用的两个函数。借助它们,你可以通过标签的不同属性轻松地过滤 HTML 页面,查找需要的标签组或单个标签。
这两个函数非常相似,BeautifulSoup 文档里两者的定义就是这样:
find_all(tag, attributes, recursive, text, limit, keywords)
find(tag, attributes, recursive, text, keywords)
你可能会发现,自己在95%的时间里都只需要使用前两个参数:tag 和 attributes 。但是,我们还是应该仔细的看看所有的参数。
标签参数 tag 前面已经介绍过——你可以传递一个标签的名称或多个标签名称组成的 Python 列表做标签参数。例如,下面的代码将返回一个包含 HTML 文档中所有标题标签的列表:
.find_all(['h1','h2','h3','h4','h5','h6'])
属性参数 attributes 用一个 Python 字典封装一个标签的若干属性和对应的属性值。例如,下面这个函数会返回 HTML 文档里红色与绿色两种颜色的 span 标签:
.find_all('span', {'class':{'green', 'red'}})
递归参数 recursive 是一个布尔变量。你想抓取 HTML 文档标签结构里多少层的信息?如果 recursive 设置为 True,find_all 就会根据你的要求去查找标签参数的所有子标签,以及子标签的字标签。如果 recursive 设置为 False,find_all 就只查找文档的一级标签。find_all 默认是支持递归查找的(recursive 默认值是 True);一般情况下这个参数不需要设置,除非你真正了解自己需要哪些信息,而且抓取速度非常重要,那时你可以设置递归参数。
文本参数 text 有点不同,它是用标签的文本内容去匹配,而不是用标签的属性。假如我们想查找前面网页中包含“the prince”内容的标签数量,可以把之前的 find_all 方法换成下面的代码:
nameList = bs.find_all(text='the prince')
print(len(nameList))
输出结果为 7。
范围限制参数 limit 显然只适用于 find_all 方法。find 其实等价于 limit 等于 1 时的 find_all。
如果你想获取网页中的前 x 项结果,就可以设置它。但是要注意,设置这个参数之后,获取的前几项结果是按照网页上的顺序排列的,未必是你想要的那前几项。
还有一个关键词参数 keyword,可以让你选择那些具有指定属性的标签。例如:
title = bs.find_all(id='title', class_='text')
上述代码返回第一个在 class_ 属性中包含单词 text,并且在 id 属性中包含 title 的标签。需要注意的是,通常情况下,页面中每个 id 的属性值只能被使用一次。因此在实际情况中,上面的代码可能并不实用,而以下的代码可以达到相同的效果:
title = bs.find(id='title')
其他 BeautifulSoup 对象
看到这里,你已经见过 BeautifulSoup 库里的两种对象了。
BeautifulSoup 对象
前面代码示例中的 bs
标签 Tag 对象
BeautifulSoup 对象通过 find 和 find_all,或者直接调用子标签获取的一列对象或单个对象,就像:
bs.div.h1
但是,这个库还有另外两种对象,虽然不常用,却应该了解一下。
NavigableString 对象
用来表示标签里的文字,而不是标签本身(有些函数可以操作和生成 NavigableString 对象,而不是标签对象)。
Comment 对象
用来查找 HTML 文档里的注释标签。
<!-- 像这样 -->