基于R语言的自动数据收集:网络抓取和文本挖掘实用指南【2.0】

张开发
2026/4/13 16:58:28 15 分钟阅读

分享文章

基于R语言的自动数据收集:网络抓取和文本挖掘实用指南【2.0】
4.2.3.1 数字谓语XPath能够利用文档中隐含的数字属性如计数或位置。有几个谓语可以返回数字属性它们可以用来创建条件语句。节点的位置是一个我们很容易实现的重要的数字特性。让我们采集出现在第一个位置的p那些节点这里用到的谓语是position和等号的组合。[3]该语句返回两个节点。这个位置谓语用来判定的并不是文档中所有的p节点里哪个处于第一的位置而是在每个相对于其父节点的节点子集中哪个是第一个。如果要选定某节点集的最后一个且无须提前知道节点子集里的节点数我们就可以使用last操作符数字谓语产生的输出可以进一步用数学操作符进行处理。要选定倒数第二个p节点我们可以对前面的语句进行如下扩展计数是另一种我们可以用作节点选取条件的数字属性。最常用的计数之一是根据其子节点数量选取节点。这种逻辑的一个实现例子如下所示该语句可以分步理解如下一开始在文档中选取所有的div节点//div。然后通过使用count谓语来改进选取条件这个谓语可以把我们需要计数的东西作为参数。在本例中我们需要计数的是选中的div节点后的a节点的数量.//a。其中的.元素是用来表示把之前的选取结果作为前提条件。从内部原理而言这样会产生另一个节点集然后把它传递给count函数。配合使用操作符和值0我们请求的是文档中那些有至少一个a子节点的div节点。除了节点我们也可以设定一个节点里属性数的条件其中元素用于获取选定节点的属性。在这个例子里./*表达式会从当前选中的节点返回所有的属性不管它们的属性名是什么。我们把这些属性传递给计数函数并判断属性的个数是否大于2。只有那些让该函数返回TRUE的节点才会被选中。某个元素中内容的字符数是另一种计数我们能获取它并用于有条件地选取节点。当我们对于要提取的目标节点所知甚少只知道它包含一些大篇幅的文字时这种计数尤其有用。它的实现如下所示我们首先获取包含文档中所有节点的节点集//*。对于这个节点集我们引入一个条件让这些节点中的内容由text返回必须包含多于50个字符。有时候把节点选取逻辑反过来得到能让谓语不返回TRUE的所有节点也是有用的。XPath含有一些让查询中能运用布尔逻辑的函数。要表达对节点集的反选可以使用not布尔函数来选取所有不被查询条件选中的节点。例如要选取所有不多于2个属性的div节点我们可以这样写4.2.3.2 文本谓语因为HTML/XML文件或任何它们的变体都是纯文本文件所以文档的文本属性对于节点选取也是有用的谓语。如果我们需要根据它们的名字、内容、属性或属性值中的文本选取节点这种谓语就能派得上用场了。除了完全匹配对于字符串的处理往往需要子串的部分匹配工具。尽管XPath 1.0在这方面已经很强了2.0版实现的关于正则表达式谓语的完整库是一个巨大的提高字符串操作相关技术的介绍请参见第8章。不过XPath 1.0在大部分情况下都很好用所以转到其他的XPath实现版本并无必要。首先让我们来探讨一下进行字符串完全匹配的方法。我们已经介绍了用于判断数值是否相等的等于操作符而它也可以用于字符串的完全匹配。要选取文档中所有包含了写于2011年10月的语录的div节点也就是说这些节点包含的date属性值为October/2011我们可以这样写首先选取文档中所有的div节点然后再从上一步的选取结果中选取那些date属性值为October/2011的节点。在很多情况下等号字符串表达的完全匹配是一种极其严格的操作。更宽松的一种方式是对字符串进行部分匹配。这种方法的一般应用是这样的 string_methodtext1text2其中text1对应文档中的某个文本元素text2则对应我们希望匹配的一个字符串。要选取文档中所有值里包含了magic这个单词的节点我们可以构造如下的语句在上述语句中我们首先选取文档中的所有节点再利用contains函数对这个节点集引入条件判断text返回的值是否包含magic这个单词。请注意所有的部分匹配函数都是对大小写敏感的因此首字母大写的Magic关键词就会匹配不上。要从字符串起始位置开始匹配一个特征可以使用starts_with函数。下面的代码片段说明了这个函数的应用方法它可以选取所有具有id属性且属性值以字母R开头的div节点ends_with则用来从字符串尾部开始匹配字符串。在进行匹配操作之前对节点字符串进行预处理常常是有用的。这个步骤的用处是把节点值、属性和属性值规范化例如去掉首字母大写或替换某些子串。让我们尝试提取那些发表于2003年的语录。正如我们在源代码中所见div节点包含了一个date属性其中存放了语录发表的年份信息。要把选取条件设定到这个值上我们可以发布下列表达式让我们一段一段地分析上面的语句。首先是选取文档中所有的div节点//div。选取结果进一步由从位于返回的属性值进行条件过滤。在谓语里我们首先获取所有选中节点的date属性值./date。这样会得到如下向量June/2003October/2011。然后这些值会传递给substring-after函数在该函数里这些值会按第二个参数指定的/字符分开。从内部看该函数会输出20032011。然后我们对属性值2003进行完全匹配就选取了我们查找的那个div节点。最后我们通过在表达式里加入//i操作顺藤摸瓜找到它下面的i节点。[1] 这两步也可以相互分开进行。你可以使用getNodeSet()来应用XPath。然后可以通过循环结构或apply()系列函数对得到的节点集进行后续处理并重构其中的信息。[2 ] 当在实际网络抓取的场景中运用XPath时通常不能依赖类似于图4-1的对节点关系的视觉化表达。这类信息必须直接从网页源代码中读取。这往往也是使用XPath进行信息提取工作中最吃力的部分。[3] 请注意一种对上述查询更简洁的表达方式是//div/p[1]。4.3 提取节点元素到目前为止我们已经运用xpathSApply来返回匹配特定XPath语句的节点。该函数会返回一个列表对象其中包含节点的名字、值和属性值如果有。我们通常不关心节点的整体而只是需要从中提取特定的信息例如它的值。幸运的是这个任务实施起来直截了当。我们只要在函数调用里把一个提取函数传递给fun参数就可以了。XML组件提供了大量的这类函数可以用来选取我们感兴趣的信息片段。对所有提取函数的完整概述见表4-4。例如为了提取title节点的值可以简单地编写如下语句不同于含有完整节点信息的列表xpathSApply这次返回的是一个向量对象其中仅仅包含匹配其XPath语句的节点的值。对于没有值信息的节点该函数会返回一个NA值。除了值我们还可以从属性里提取信息。把xmlAttrs传递给fun参数可以选取选中节点里的所有属性在大部分应用中我们感兴趣的只是特定的属性而不是所有的节点属性。要从一个节点选取特定的属性可以用xmlGetAttr并加入属性名4.3.1 扩展fun参数对从XPath返回节点集的处理在4.2节介绍的单纯的特性提取基础上还可以轻松地向外扩展。不同于从节点提取信息我们可以采用fun参数对节点元素进行任何可用的数值或文本操作。我们可以为具体目的创建新的函数或为具体需求修改已有的提取函数并将它们传递给xpathSApply。这些后续处理的目标可以是整理节点的数值或文本内容也可以是为了应对提取失败情况的某些异常处理。为了在第一个应用里讲解相关概念我们尝试提取文档中所有的语录并在提取过程中把所有符号转化为小写。我们可以使用R的基础函数tolower它会把字符串转换为小写。首先我们编写一个叫作lowerCaseFun的函数。在该函数中我们就简单地把节点值的信息传递给tolower函数并返回转换后的文本把该函数加到xpathSApply的fun参数里就可以得到现在返回的向量由所有转换后的节点值组成这样就省去了提取操作完成后的额外后续处理步骤。第二个更复杂的后续处理函数会包括某些基本字符串操作这些操作可以运用stringr组件里的功能。同样我们可以先编写一个函数用它加载stringr组件、采集数据并提取年份信息[1]把该函数传递给xpathSApply的fun参数就可以得到我们也可以利用fun参数处理XPath语句返回一个空节点集的情况。在XML的DOM里NULL对象用来表示一个不存在的节点。我们可以应用一个定制的函数它包括对NULL对象的检测以及针对这个检测的真假判断结果进行的后续处理在上面的定制函数中第一行代码把节点的id属性值保存到一个新的对象id里。根据该属性值是否为NULL的条件我们在第二行代码中要么返回not specified要么返回该id值。如果要查看结果就把该函数传递给xpathSApply在XPath表达式里使用变量前面的例子都足够简单所以用一个固定的XPath表达式就可以查询到所有信息。不过有时候不可避免地需要把XPath表达式本身看作提取程序的变量部分。数据分析师经常会发现特定的信息类型在不同文档中的编码方式并不统一因此要给所有文档创建一个合法的XPath表达式恐怕就不可能了特别是在网站的未来版本可能改变的情况下。为了描述这种情况可以参考从XML文件technology.xml中提取信息该文件在3.5.1节介绍过。之前我们从该文件里提取了Apple的股票信息不过我们现在要面对提取所有公司的股票信息的问题。但问题在于作为提取目标的收盘信息close是封装在具有不同名字Apple、Google、IBM的父节点中的。我们可以通过使用sprintf函数创建灵活的XPath表达式来解决这个问题而不是为每个公司单独创建查询函数。首先我们重新解析该文档并创建一个带有相关公司名的字符向量下一步使用sprintf来创建查询。在该函数内部我们设置了XPath表达式的基本模板。字符串%s就是用来表示变量部分在这里s代表一个字符串变量。companies对象则指示了我们希望替换%s的元素我们可以照前例进行处理首先安排一个提取函数然后把这个提取函数传递给xpathSApply。在这里还进一步把输出转换为更方便的数据框格式并改变了向量的类型4.3.2 XML命名空间在第3章介绍XML技术时我们讲解了命名空间它是用来在网络文档里创建可唯一识别的节点的一个特性。当在单个文档内部用了不同的标记词汇时命名空间就会成为XML不可或缺的一部分。这种情况可能是把两个不同的XML文件合并为一个文档的结果。如果其中的XML文件采用了相似的词汇命名空间就有助于解决不确定性问题并防止名字互相冲突。单独的命名空间也给我们迄今为止用到的XPath语句带来了一个问题XPath通常只考虑缺省命名空间。在本节会学习如何指定特定节点集所在的命名空间并由此提取出感兴趣的元素。让我们回到介绍XML命名空间时3.4节所用到的例子。文件books.xml不仅包含了一个HTML标题还包含在XML节点内的有关一本书的信息。我们一开始先用xmlParse解析该文档并将其内容输出到屏幕对于这个例子假定我们对从提取title节点提取信息感兴趣其中含有文本字符串JavaScriptThe Good Parts。一开始我们可以发布一个对文档中所有title节点的调用并获取它们的值很明显上面的调用会返回一个空列表。这里的关键问题在于文档中的两个title节点都没有定义在缺省命名空间下而标准的XPath是在缺省命名空间下操作的。特定的命名空间可以在root节点的xmlns声明中进行检测。这个例子里声明了两个单独的命名空间分别用字母h和t引用。跳过单独命名空间的一种方法是构造一个直接指向我们感兴趣的本地名称的查询在这里首先选择文档中所有的节点然后再从中选取带有本地名title的所有节点。要对文档进行能针对命名空间的XPath查询我们可以扩展上述函数在xpathSApply函数里使用namespaces参数来指示第二个title节点定义时所在的特定命名空间。我们知道命名空间信息出现在root节点里。我们可以把第二个命名空间字符串传递给xpathSApply函数的namespaces参数类似地如果我们感兴趣的是从第一个命名空间下的title节点里提取信息那么可以直接修改命名空间的URI这些方法要求预先知道我们感兴趣的节点的声明所在的命名空间。如果我们知道命名空间在文档中的定义所在的位置就可以避免用硬编码方式来指定命名空间对应的URI。命名空间永远作为一个XML元素的属性值进行声明。对于上面的例子文件该信息出现在root节点的xmlns属性中。利用这个知识我们就可以通过使用xmlNamespaceDefinitions函数提取第二个命名空间的URI把这个信息保存到一个新的对象中之后该命名空间的URI就可以传递给XPath查询以从该命名空间下的title节点提取信息4.3.3 XPath的辅助性小工具和XPath的多样性相伴的一个代价是它比较陡的学习曲线。初学者和有经验的XPath用户都会发现下面的工具对于校验和构建用于数据提取工作的合法语句是很有帮助的SelectorGadget SelectorGadgethttp://selectorgadget.com是一个开源的书签小工具它能通过鼠标点击的方法对创建适当XPath语句的过程进行简化。要利用它的功能请访问SelectorGadget网站并创建该页面的一个书签。在你感兴趣的网站上可以通过点击该书签来激活SelectorGadget。一旦在左下角出现一个工具条SelectorGadget就被激活了会在鼠标移过网页时高亮显示该页面的DOM元素。点击某个元素就会把它加入需要抓取的节点列表里。根据这些选择SelectorGadget会产生一个通用语句我们可以通过点击XPath按钮来获取它。然后该XPath表达式可以传递给xpathSApply的path参数这里你需要注意用来包住XPath表达式的引号类型不能是在表达式内部使用过的例如用于属性名。否则就需要用双引号或单引号来替换它们。Web Developer Tools 很多现代浏览器都包含了一套开发者工具用来帮助检查网页中的元素并产生可以传递给XML节点查找函数的合法XPath语句。除了当前DOM的信息开发者工具也能够追踪动态网页里DOM元素的变化。我们会在6.4节利用这些工具。[1] 字符串操作的介绍请参见第8章。需要特别指出的是定制的提取函数中的str_extract()函数会通过所谓的正则表达式采集四个连续的数字。正则表达式的概念和细节也会在第8章进行讲解。小结本章针对用于查询XML文档的XPath语言进行了广泛的介绍。我们想表明XPath对于希望能高效地处理网络数据的数据分析师而言是一笔不可或缺的投资。借助于本章最后介绍的一些工具很多数据提取中的问题甚至只需通过简单点击元素和粘贴返回的表达式就能迎刃而解。尽管它们很有帮助但是这些工具对于一些错综复杂的提取问题也有可能失效因此了解如何从头开始创建表达式仍然是必要的技能。我们也要表明创建适用的XPath语句很少是一锤子买卖而是一个需要反复渐进的学习过程。这个过程可以用三个步骤的循环来表示。在创建阶段我们拼装一个有希望返回正确信息的XPath语句。在测试阶段我们应用该XPath语句观察返回的节点集或错误信息然后发现返回的节点集可能是过于广泛还是过于局限。在XPath查询失败的情况下学习阶段就是一个必要的阶段。从这种失败中学习我们就会推导出一个更合适的XPath表达式例如通过把它改得更严格或更宽松以此来获取那些恰好是我们需要的信息。返回第一步我们运用改善过的XPath来检查它是否获得了正确的节点集。对于很多提取问题我们发现反复经历这个循环是很有必要的它有利于巩固对于自动化采集程序的健壮性的信心。我们会在9.2.2节中再次详细讲解XPath的数据抓取策略。当用XPath代码处理某个从没见过的网页实例时如提取代码是要每天自动执行的参见11.2节XPath健壮性的问题就更为严重。不可避免地网站会经历结构的变化元素会被删除或转移新的特性会被实现视觉表现会被修改这些变化最终也会影响网页的内容。这对于流行的网站来说尤其如此。但是我们会看到在XPath语句和辅助代码的设计中可以进行具体的部署以便提高代码的健壮性并在提取失败时警告分析师。当要从文档中提取文本信息时有一种可能性是依赖于文本谓语。给查询代码增加实质性的信息也会让代码增加必要的健壮性。延伸阅读对XPath和XML组件的完整探讨超出了本章的范围。要深入了解XML组件的全貌感兴趣的读者可以参阅Nolan和Temple Lang2014的文章。对该组件更简洁的介绍是由Temple Lang2013c提供的。Tennison提供了一个XPath 1.0的完整概述Tennison 2001。另一个对XPath 1.0和2.0的方法有帮助的概述可以在Holzner2003的著作中找到。如需关于包括XPath在内的网络技术的优秀在线文档请访问Mozilla Developer Network2013。习题1.XPath成为一门特定域语言的原因2.XPath是XML Path语言但它对于HTML网页也有效。试解释其中的原因。3.回到fortunes1.html文件考虑如下XPath表达式//a[text[contains.R-help]]§。替换§获取值为“Robert Gentleman”的h1节点。4.用适当的字符串函数创建一个谓语判断语录中的月份是否是10月October。5.考虑下列两个用来从HTML文件里提取段落节点的XPath语句。1.//div//p2.//p。判断这两个语句中哪一个产生了让结果的范围更小的请求。解释你这么判断的理由。6.对于从fortunes.html中提取语录的任务核实XPath表达式//i不会返回正确的结果。解释它失效的原因。7.XML文件potus.xml包含了美国总统的传记信息。把该文件解析到R会话的一个对象中。a提取所有总统的姓名。b从第40任总统开始提取所有总统的姓名。c提取所有共和党总统的occupation节点的值。d提取所有也是浸礼会教徒Baptist的共和党总统的occupation节点的值。eoccupation节点包含了开头和结尾都带有多余空格的字符串。通过扩展提取函数删除这些空格。f从education节点提取信息把所有“No formal education”的实例替换为NA。g提取所有任期起始于1960年及其之后的总统的name节点。8.Delaware州保管了一套该州政府信息中心及其他州立机构发布的数据集存档。查看一下Naturalizations.xml包括在本章的材料中见http://www.r-datacollection.com。该文件包含了来自最高法院的入籍naturalization记录信息。把这些数据转换为R数据框。9.英联邦战争公墓委员会的数据库包含了墓地的地理位置和为在第一次世界大战中丧生的人们举办的纪念仪式的相关信息。这些数据被重构为KML文档这是一种XML类的数据结构。查看一下cwgc-uk.kml包含在本章的材料中。解析该数据并根据名字和坐标信息创建数据框。在一个地图中标出它们的分布情况。10.查看SelectorGadget参见4.3.3节。访问http://planning.maryland.gov/Redistricting/2010/legiDist.shtml并用SelectorGadget找出适合于提取右下角名为Maryland 2012Legislative District Mapswith Precincts的表格中所有链接的XPath表达式。

更多文章