《Python网络数据采集》第 2 章 复杂HTML解析

当米开朗基罗被问及如何完成《大卫》这样匠心独具的雕刻作品时,他有一段著名的回答:“很简单,你只要用锤子把石头上不像大卫的地方敲掉就行了。”
虽然网络数据采集和大理石雕刻大相径庭,但是当我们从复杂的网页中寻觅信息时,也必须持有类似的态度。在我们找到目标信息之前,有很多技巧可以帮我们“敲掉”网页上那些不需要的信息。这一章我们将介绍解析复杂的 HTML 页面的方法,从中抽取出我们需要的信息。

2.1 不是一直都要用锤子

面对页面解析难题(Gordian Knot)的时候,不假思索地直接写几行语句来抽取信息是非常直接的做法。但是,像这样鲁莽放纵地使用技术,只会让程序变得难以调试或脆弱不堪,甚至二者兼具。在开始解析网页之前,让我们看一些在解析复杂的 HTML 页面时需要避免的问题。
假如你已经确定了目标内容,可能是采集一个名字、一组统计数据,或者一段文字。你的目标内容可能隐藏在一个 HTML “烂泥堆”的第 20 层标签里,带有许多没用的标签或HTML 属性。假如你不经考虑地直接写出下面这样一行代码来抽取内容:
bsObj.findAll("table")[4].findAll("tr")[2].find("td").findAll("div")[1].find("a")
虽然也可以达到目标,但这样看起来并不是很好。除了代码欠缺美感之外,还有一个问题是,当网站管理员对网站稍作修改之后,这行代码就会失效,甚至可能会毁掉整个网络爬 虫。那么你应该怎么做呢?
1.寻找“打印此页”的链接,或者看看网站有没有 HTML 样式更友好的移动版(把自己的请求头设置成处于移动设备的状态,然后接收网站移动版,更多内容在第 12 章介绍)。
2.寻找隐藏在 JavaScript 文件里的信息。要实现这一点,你可能需要查看网页加载的JavaScript 文件。我曾经要把一个网站上的街道地址(以经度和纬度呈现的)整理成格式整洁的数组时,查看过内嵌谷歌地图的 JavaScript 文件,里面有每个地址的标记点。
3. 虽然网页标题经常会用到,但是这个信息也许可以从网页的 URL 链接里获取。
4.如果你要找的信息只存在于一个网站上,别处没有,那你确实是运气不佳。如果不只限于这个网站,那么你可以找找其他数据源。有没有其他网站也显示了同样的数据?网站上显示的数据是不是从其他网站上抓取后攒出来的?
尤其是在面对埋藏很深或格式不友好的数据时,千万不要不经思考就写代码,一定要三思
而后行。如果你确定自己不能另辟蹊径,那么本章后面的内容就是为你准备的。

2.2 再端一碗BeautifulSoup

在第 1 章里,我们快速演示了 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 属性,如下所示:
"<span class="red">Heavens! what a virulent attack!</span>" replied <span class= 
"green">the prince</span>, not in the least disconcerted by this reception.
我们可以抓出整个页面,然后创建一个 BeautifulSoup 对象,和第 1 章里使用的程序类似:
from urllib.request import urlopen
from bs4 import BeautifulSoup
html = urlopen("http://www.pythonscraping.com/pages/warandpeace.html")
bsObj = BeautifulSoup(html)
通过 BeautifulSoup 对象,我们可以用 findAll 函数抽取只包含在 <span class="green"></span> 标签里的文字,这样就会得到一个人物名称的 Python 列表( findAll 是一个非常灵活的函数,我们后面会经常用到它):
nameList = bsObj.findAll("span", {"class":"green"})
for name in nameList: 
    print(name.get_text())
代码执行以后就会按照《战争与和平》中的人物出场顺序显示所有的人名。这是怎么实现的呢?之前,我们调用 bsObj.tagName 只能获取页面中的第一个指定的标签。现在,我们调用 bsObj.findAll(tagName, tagAttributes) 可以获取页面中所有指定的标签,不再只是第一个了。
获取人名列表之后,程序遍历列表中所有的名字,然后打印 name.get_text(),就可以把标签中的内容分开显示了。

小tips 

什么时候使用 get_text() 与什么时候应该保留标签
.get_text() 会把你正在处理的 HTML 文档中所有的标签都清除,然后返回一个只包含文字的字符串。假如你正在处理一个包含许多超链接、段落和标签的大段源代码,那么 .get_text() 会把这些超链接、段落和标签都清除掉,只剩下一串不带标签的文字。
BeautifulSoup 对象查找你想要的信息,比直接在 HTML 文本里查找信息要简单得多。通常在你准备打印、存储和操作数据时,应该最后才使用 .get_text() 。一般情况下,你应该尽可能地保留 HTML 文档的标签结构。

2.2.1 BeautifulSoupfind()findAll()

BeautifulSoup 里的 find() findAll() 可能是你最常用的两个函数。借助它们,你可以通过标签的不同属性轻松地过滤 HTML 页面,查找需要的标签组或单个标签。
这两个函数非常相似, BeautifulSoup 文档里两者的定义就是这样:
findAll(tag, attributes, recursive, text, limit, keywords)
find(tag, attributes, recursive, text, keywords)
很可能你会发现,自己在 95% 的时间里都只需要使用前两个参数: tag attributes。但是,我们还是应该仔细地观察所有的参数。

标签参数 tag

前面已经介绍过——你可以传一个标签的名称或多个标签名称组成的 Python列表做标签参数。例如,下面的代码将返回一个包含 HTML 文档中所有标题标签的列表:

.findAll({"h1","h2","h3","h4","h5","h6"})

属性参数 attributes

是用一个 Python 字典封装一个标签的若干属性和对应的属性值。例如,下面这个函数会返回 HTML 文档里红色与绿色两种颜色的 span 标签:
.findAll("span", {"class":{"green", "red"}})

递归参数 recursive

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值