《数据科学R语言实践:面向计算推理与问题求解的案例研究法》一一2.7 从网上抓取比赛结果

本节书摘来自华章计算机《数据科学R语言实践:面向计算推理与问题求解的案例研究法》一书中的第2章,第2.7节,作者:[美] 德博拉·诺兰(Deborah Nolan)  邓肯·坦普·朗(Duncan Temple Lang)  更多章节内容可以访问云栖社区“华章计算机”公众号查看。

2.7 从网上抓取比赛结果

樱花10英里赛的比赛结果可以从http://www.cherryblossom.org获得。图2-1显示了从该主页上的截屏,其中包含每一年比赛结果的链接。2012年男子组比赛结果的截屏为图2-2和图2-20。可以看到,数据被简单地格式化为一组以固定宽度的列进行组织的普通文本。我们可以通过查看网页的源码来确定是否属于这种情形。比如可以在谷歌的Chrome浏览器中通过点击View->Developer->View Source的方式进行查看。通过源码我们可以看到表本身没有HTML标记,它在文档内部插入了

节点(图2-2网页的HTML源码显示于图2-20)。那么我们可以很容易地从HTML中提取这张表,然后做进一步的处理。

图2-20 2012樱花公路赛男子组比赛结果的页面源码截屏。截屏显示的是2012樱花公路赛男子组结果网页的HTML源码,它和2011年女子组比赛结果(见图2-21)的格式不是很相似,但两者都是在HTML文档中通过

节点插入的普通文本
我们通过查看另一年的网页内容来确定它们的格式是否相同。当查看2011年女子组的比赛结果页面时,我们看到其基本格式是一样的。图2-21中显示了2011年女子组比赛结果页面的源码,只是它们的列不完全相同。在2011年的网页中,净时间和终场时间都进行了记录,并且在配速列的后面有一个列标签S,对前几位参赛选手用感叹号进行了标记,而对其余的参赛选手没有标记。我们现在的任务就是要简单地提取文本表,只需要定位表的位置,然后将它作为一个文本块进行提取。2.3节中的函数功能就是要将信息列转换成变量。
使用XML包中的htmlParse()函数[6]从网站中抓取2012年男子组比赛的页面。

《数据科学R语言实践:面向计算推理与问题求解的案例研究法》一一2.7 从网上抓取比赛结果

图2-21 2011年樱花赛女子组结果页面的源码截图。该图是2011年樱花公路赛女子组结果的HTML源码截屏。注意给出的时间是比赛中点处(5 Mile)和两个比赛完成时间(Time和Net Tim)。还要注意最右边的列标记为S。尽管和2012年男子组比赛结果的格式不同,但是他们都是在HTML文档中通过

节点插入的普通文本
我们从HTML的源码中看到了要提取的
节点的文本内容。可以通过简单的XPath表达式,//pre,访问文档中所有的
节点。代码如下:

getNodeSet()函数返回一个列表,其中的每个元素都对应文档中的一个

节点。在我们的例子中,只有一个这样的节点。接下来,使用xmlValue()函数提取该节点中的文本内容如下:

下面检查txt中的内容。我们首先确定其中包含多少字符,然后检查它的开始和结束位置。代码如下:
《数据科学R语言实践:面向计算推理与问题求解的案例研究法》一一2.7 从网上抓取比赛结果

结果显示我们成功地从Web页面中提取出了信息。我们还看到以rn结尾的单独行,可以利用这些字符将690 904个字符分割成独立的字符串,每个字符串与表中的行对应。方法如下:
《数据科学R语言实践:面向计算推理与问题求解的案例研究法》一一2.7 从网上抓取比赛结果

《数据科学R语言实践:面向计算推理与问题求解的案例研究法》一一2.7 从网上抓取比赛结果

现在已经成功地提取了表中的行作为一个字符向量中的元素。
下面让我们把代码整理成函数,使其可以应用到所有的28个Web网页(从1999年到2012年的所有男子组和女子组页面)。函数以Web页面的URL作为输入,返回一个字符向量,其中每一行为一个元素,包括头部行和表的结果行。将前面的代码组织成一个函数:

《数据科学R语言实践:面向计算推理与问题求解的案例研究法》一一2.7 从网上抓取比赛结果

函数的提取结果和之前的一样。现在我们可以将它应用到跨这些年度的所有男子组的比赛结果中。
假设有一个包含所有URL的向量,那么就可以将我们的函数简单地应用于该向量。我们通过把基本的URL与特定年份的信息结合在一起,创建这样的URL向量:
《数据科学R语言实践:面向计算推理与问题求解的案例研究法》一一2.7 从网上抓取比赛结果

这里有一个错误,提示preNode有问题。
为了找到关于错误原因的更多信息,我们通过设置recover()函数对象的error选项,把错误处理的功能打开,这样当有错误发生时recover()函数就会被调用。该函数允许我们访问活动的调用框,以便检查对象并查看结果是否如我们所预料。下面设置options(),并再次调用extractResTable()函数:

当错误发生时,以上内容显示了当前实际的函数调用集合的简单视图。这就是“调用栈”。第一条是对lapply()的顶层调用。第二条是对extractResTable()函数的实际调用。lapply()调用了extractResTable()函数,但是是使用名字FUN进行调用,这是因为它是lapply()中的参数名,它包含了我们想要使用lapply()第一个参量中的所有元素而调用的这个函数。extractRetTable()调用了几个函数,但是错误发生在第三个表达式xmlValue(preNode[[ 1]]),它是当前调用栈的第三个也是最后一个元素。
recover()函数允许我们选择想要检查的调用。在Selection提示符处,输入调用编号(如2)并按下回车键,那么将直接在指定调用的调用框中执行该函数。我们将可以检查(和更新)当前参数与局部变量的值,且可以在该位置执行任意代码。

由于调用栈中的第二个元素是对extractResTable()函数的调用,因此我们选择检查该调用。操作如下:

《数据科学R语言实践:面向计算推理与问题求解的案例研究法》一一2.7 从网上抓取比赛结果

结果显示在1999年的比赛结果页面中没有

节点。
下面让我们通过访问网站来进行检查。将URL地址http: //www.cherryblossom.org/ results/ 1999/ 1999cucb10m-m.htm粘贴到Web浏览器中,发现被带到了图2-1显示的主页,而不是我们期望进入的页面。当使用主页上的导航系统进入1999年男子组比赛的结果页面时,我们发现了问题所在。它的URL不是我们上面预期的地址,而是http://www.cherryblossom.org/cb99m.htm。它与我们基于2011和2012年的URL创建的地址格式有很大不同。这提醒我们需要利用Web站点的导航系统确认所有的28个URL地址。我们可以通过编程实现地址的创建,但是在这里我们只简单收集男子组比赛结果的URL地址,生成一个名为menURLs的字符向量,即

以上只是包括了在URL中基地址http://www.cherryblossom.org/后面的部分。
我们重新创建urls向量,使其包含正确的Web地址。我们将URL地址合并到一起:
《数据科学R语言实践:面向计算推理与问题求解的案例研究法》一一2.7 从网上抓取比赛结果

这回我们没有收到任何错误消息。当然,这只是因为没有运行错误,并不意味着已经能够正确地提取数据。我们需要检查结果集以查看其中是否包含我们所期望的信息。
首先检查每一个字符向量的长度。从网站上我们看到每年有几千名参赛选手完成比赛,所以我们期望在向量中有几千个元素。
《数据科学R语言实践:面向计算推理与问题求解的案例研究法》一一2.7 从网上抓取比赛结果

但是,2000年和2009年的向量提取结果中只有一个元素。
这两年数据的文件名是正确的,因此需要进一步发掘原因。我们浏览2000年网页的源码,检查其格式是否符合我们预期。下面是2000年文档的前几行信息:
《数据科学R语言实践:面向计算推理与问题求解的案例研究法》一一2.7 从网上抓取比赛结果

让我们重新组织HTML标签,利用缩进来检查是否有文件格式问题。下面是对于相同的内容以更直观的方式展现的结果:
《数据科学R语言实践:面向计算推理与问题求解的案例研究法》一一2.7 从网上抓取比赛结果

这个文档不是规范的HTML文档。虽然htmlParse()函数可以修复很多畸形文档问题,比如,关闭
标签,根据标签名匹配实例等。然而,这个函数只能做到这些。注意上面文档中

标签没有正确地被嵌套,并且结束标签

出现在
标签之后也是有问题的。如果htmlParse()关闭
标签,使得文档中的标签可以正确地被嵌套,这样
节点就不再包含比赛结果表。
我们可以通过编程来编辑HTML,使其符合良好的规范。另一种方式是,我们可以尝试使用另一个表达式XPath来定位特定文件的内容。这里我们选择第二种方式,使用XPath进行处理,并将第一种方法留作练习。
如果我们想对每一年的数据都使用不同的方式处理,那么我们需要一种方法来区分不同的处理方法。一种方式是在函数定义中添加另一个参数来表示我们处理的是哪一年的数据。然后可以用代码检查年份,如果是2000年,我们可以以不同的方式提取表中的内容。可以向year提供一个缺省值,这样即使我们没有指定该参数的值,函数也可以采用缺省的方式进行提取。修改后的extacktResTable()函数如下:

《数据科学R语言实践:面向计算推理与问题求解的案例研究法》一一2.7 从网上抓取比赛结果

以上我们已经解决了2000年的问题,但是2009年的问题仍然存在。在此将这个问题留作练习,修改extractResTable()函数以处理该特殊情况。一旦修改了代码,便会发现2009年的表中有6659行。
既然已经有了可以处理男子组比赛结果页面的函数,可以尝试把它应用到女子组页面上。在操作时,我们发现除了2009年之外其他页面都能处理得很好。对于这种情况,不需要对女子组该年份的结果做任何特殊的处理。我们可以再次修改函数,使2009年女子组的结果能够得到缺省处理,而不是像2009年男子组结果那样需要特殊处理,在此我们将它留作练习。
我们保存数据以便进一步处理:
《数据科学R语言实践:面向计算推理与问题求解的案例研究法》一一2.7 从网上抓取比赛结果

最后,采用R数据格式保存字符向量列表的另一种方式是将字符向量写到普通的文本文件中。我们可以使用writeLines()来完成上述工作。事实上,可以修改extractResTable()函数以接受一个file参量。如果提供了file参量,函数就把结果写到相应的文件中;如果file参量为NULL,那么函数就返回一个字符向量。同样,在此我们把这个关于改进的问题留作练习。

上一篇:【R数据科学读书笔记】R语言的数据结构原来可以这样理解


下一篇:Java微信公众号开发