爬虫技术详解(一)- XPath

XPath简介

以下摘自维基百科

XPath (XML Path Language) is a query language for selecting nodes from an XML document. In addition, XPath may be used to compute values (e.g., strings, numbers, or Boolean values) from the content of an XML document. XPath was defined by the World Wide Web Consortium (W3C).

简单来说就是用于XML/HTML文档的查询语言。我们在爬虫中程序中使用XPath主要用于定位HTML文档中的节点。比如获取HTML文档中通过href属性标识的URL地址。更详细的介绍请参考XPath简介

XPath使用场景

在爬虫技术领域里为什么需要XPath呢?这是因为我们通过爬虫去网上爬取的很多都是html文档(比如爬知乎,爬微博),而我们通常需要对html数据进行解析,获取对我们有用的数据,然后再将这些处理后的数据存储起来。而解析HTML就需要用到XPath。(另外也可以通过css query来定位节点,但是一般其背后也是利用了XPath技术,而且XPath更加灵活和通用)

基本概念解释

在理解XPath之前我们首先要了解一些基本的概念:

  • 节点(Node):节点有七种,详见节点小节。
  • 基本值(Atomic Value):如下例中的属性值"en"或者文本2005
  • 项目(Item):基本值和节点都可以称之为项目。
    假设我们有如下XML文档:
<?xml version="1.0" encoding="ISO-8859-1"?>

<!--this is a xml demo-->
<bookstore>

<book>
  <title lang="en">Harry Potter</title>
  <author>J K. Rowling</author> 
  <year>2005</year>
  <price>29.99</price>
</book>
<book>
  <title lang="en">Black Swan</title>
  <author>Nassim Nicholas Taleb</author> 
  <year>2010</year>
  <price>12.23</price>
</book>

</bookstore>

节点(Node)

XML节点一共有七种,如下:

  • 文档节点或叫根节点(root node),
    <bookstore> ...</bookstore>就是根节点
  • 元素(element)
    <year>2005</year>
  • 属性(attribute)
    lang="en"为title元素的属性
  • 文本(text)
    即标签中间的文本,如J K. Rowling, 2005
  • 命名空间(namespace),用于避免标签的命名冲突,如下:
<table xmlns="http://www.w3.org/TR/html4/">
   <tr>
   <td>Apples</td>
   <td>Bananas</td>
   </tr>
</table>
  • 注释(comment), 使用包括起来的就是注释,xml解析时会忽略注释,如下:
<!--this is a xml demo-->
  • 处理指令(Processing Instructions),处理指令,允许文档中包含由应用程序来处理的指令。在XML文档中,有可能会包含一些非XML格式的数,这些数据XML处理器无法处理,我们就可以通过处理指令来通知其它应用程序来处理这些数据。如下:
    <?xml-stylesheet type="text/xsl" href="show_book.xsl"?>

节点之间的关系

节点之间的关系主要有以下几种:

  • 父子关系:<book>节点是<title>, <author>等节点的父节点(Parent),相应的,<title>等节点为<book>节点的子节点(Children)
  • 兄弟关系(Sibling):<title>, <author>互为兄弟关系
  • 先辈(Ancestor): 父节点,父的父都是先辈,比如<book>, <bookstore>是<author>的先辈
  • 后代(Descendant): 子节点,子的子都是后代,如<book>, <author>都是<bookstore>的后代

了解节点关系,主要用于后边路径表达式的轴,详见路径表达式小节。

XPath的路径表示

在进一步了解XPath的相关概念之前,我们最好可以通过实际的演练来看下XPath到底是什么。所以首先我们来配置一下测试的环境。

环境准备

安装scrapy。scrapy是一个爬虫框架,我们将通过scrapy提供的shell功能来对XPath做实验。

pip install -u scrapy

注意:在Mac环境下需要创建一个虚拟环境,否则无法安装成功

  • 启动scrapy shell。安装成功scrapy之后,通过如下命令可以发送一个get请求,并启动一个scrapy shell
> scrapy shell 'http://www.163.com'
2018-02-22 21:39:05 [scrapy.utils.log] INFO: Scrapy 1.5.0 started (bot: ifanrcrawler)
2018-02-22 21:39:05 [scrapy.utils.log] INFO: Versions: lxml 4.1.1.0, libxml2 2.9.7, cssselect 1.0.3, parsel 1.4.0, w3lib 1.19.0, Twisted 17.9.0, Python 2.7.13 (default, May  7 2017, 10:23:30) - [GCC 4.2.1 Compatible Apple LLVM 8.1.0 (clang-802.0.41)], pyOpenSSL 17.5.0 (OpenSSL 1.1.0g  2 Nov 2017), cryptography 2.1.4, Platform Darwin-17.4.0-x86_64-i386-64bit
2018-02-22 21:39:05 [scrapy.crawler] INFO: Overridden settings: {'NEWSPIDER_MODULE': 'ifanrcrawler.spiders', 'ROBOTSTXT_OBEY': True, 'DUPEFILTER_CLASS': 'scrapy.dupefilters.BaseDupeFilter', 'SPIDER_MODULES': ['ifanrcrawler.spiders'], 'BOT_NAME': 'ifanrcrawler', 'LOGSTATS_INTERVAL': 0, 'COOKIES_ENABLED': False}
2018-02-22 21:39:05 [scrapy.middleware] INFO: Enabled extensions:
['scrapy.extensions.memusage.MemoryUsage',
 'scrapy.extensions.telnet.TelnetConsole',
 'scrapy.extensions.corestats.CoreStats']
2018-02-22 21:39:05 [scrapy.middleware] INFO: Enabled downloader middlewares:
['scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware',
 'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware',
 'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware',
 'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware',
 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware',
 'scrapy.downloadermiddlewares.retry.RetryMiddleware',
 'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware',
 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware',
 'scrapy.downloadermiddlewares.redirect.RedirectMiddleware',
 'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware',
 'scrapy.downloadermiddlewares.stats.DownloaderStats']
2018-02-22 21:39:05 [scrapy.middleware] INFO: Enabled spider middlewares:
['scrapy.spidermiddlewares.httperror.HttpErrorMiddleware',
 'scrapy.spidermiddlewares.offsite.OffsiteMiddleware',
 'scrapy.spidermiddlewares.referer.RefererMiddleware',
 'scrapy.spidermiddlewares.urllength.UrlLengthMiddleware',
 'scrapy.spidermiddlewares.depth.DepthMiddleware']
2018-02-22 21:39:05 [scrapy.middleware] INFO: Enabled item pipelines:
[]
2018-02-22 21:39:05 [scrapy.extensions.telnet] DEBUG: Telnet console listening on 127.0.0.1:6023
2018-02-22 21:39:05 [scrapy.core.engine] INFO: Spider opened
2018-02-22 21:39:05 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://www.163.com/robots.txt> (referer: None)
2018-02-22 21:39:05 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://www.163.com> (referer: None)
[s] Available Scrapy objects:
[s]   scrapy     scrapy module (contains scrapy.Request, scrapy.Selector, etc)
[s]   crawler    <scrapy.crawler.Crawler object at 0x10f5269d0>
[s]   item       {}
[s]   request    <GET http://www.163.com>
[s]   response   <200 http://www.163.com>
[s]   settings   <scrapy.settings.Settings object at 0x10f526a50>
[s]   spider     <DefaultSpider 'default' at 0x10f9a6f90>
[s] Useful shortcuts:
[s]   fetch(url[, redirect=True]) Fetch URL and update local objects (by default, redirects are followed)
[s]   fetch(req)                  Fetch a scrapy.Request and update local objects
[s]   shelp()           Shell help (print this help)
[s]   view(response)    View response in a browser
>>> 
  • 在mac shell程序中输入scrapy shell 'http://www.163.com',scrapy会发送针对163网站的get请求,并进入scrapy shell中。
  • 在scrapy成功返回response,并进入scrapy shell之后,就会看到提示的scrapy shell的命令的相关描述,命令包括request, response, view, fetch等
  • 此时我们就可以通过response.xpath来定位response中html文档的节点,如下所示:
>>> title = response.xpath('//title').extract_first()
>>> title
u'<title>\u7f51\u6613</title>'
>>> title = response.xpath('//title/text()').extract_first()
>>> title
u'\u7f51\u6613'
>>> print title
网易

路径表达式

XPath通过路径表达式来定位节点。

语法
  • 相对路径和绝对路径
    位置路径可以是绝对的,也可以是相对的。
    绝对路径起始于正斜杠( / ),而相对路径不会这样。在两种情况中,位置路径均包括一个或多个步,每个步均被斜杠分割:
    绝对位置路径:

    /step/step/...
    

    相对位置路径:
    每个步均根据当前节点集之中的节点来进行计算

    step/step/...
    
  • 步(step)的组成
    步的语法结构如下:

    轴名称::节点测试[谓语]
    

    示例:

    # 获取当前节点的后代节点中所有价格大于20的book节点
    descendant::book[price>20]
    
  • 路径表达式
    常用路径表达式语法如下:

    表达式描述
    nodename选取此节点的所有子节点。
    /从根节点选取。
    //从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。
    .选取当前节点。
    ..选取当前节点的父节点。
    @选取属性。
    谓语

    通过示例来看下其含义:

    路径表达式结果
    /bookstore选取根元素 bookstore。注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径!
    //book选取所有 book 子元素,而不管它们在文档中的位置。
    /bookstore//book选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置。
    //@lang选取名为 lang 的所有属性。

    我们可以将前述的例子保存为bookstore.xml文件,保存在当前目录,然后进行如下操作进入scrapy shell:

    scrapy shell './bookstore.xml'
    

    之后在scrapy shell中测试如下:

    >>> response.xpath('/bookstore').extract()
    [u'<bookstore>\n\n<book>\n  <title lang="en">Harry Potter</title>\n  <author>J K. Rowling</author> \n  <year>2005</year>\n  <price>29.99</price>\n</book>\n<book>\n  <title lang="en">Black Swan</title>\n  <author>Nassim Nicholas Taleb</author> \n  <year>2010</year>\n  <price>12.23</price>\n</book>\n\n</bookstore>']
    >>> response.xpath('/bookstore//book').extract()
    [u'<book>\n  <title lang="en">Harry Potter</title>\n  <author>J K. Rowling</author> \n  <year>2005</year>\n  <price>29.99</price>\n</book>', u'<book>\n  <title lang="en">Black Swan</title>\n  <author>Nassim Nicholas Taleb</author> \n  <year>2010</year>\n  <price>12.23</price>\n</book>']
    >>>
    

    注意所有XPath路径表达式均需要以’/’或者’//’开头,否则无法定位到具体的节点。

  • 路径表达式的轴(axis)
    此处的轴即为前面在“步”中提到的轴名称

    Full SyntaxAbbreviated SyntaxNotes
    ancestor
    ancestor-or-self
    attribute@@abc is short for attribute::abc
    childxyzis short for child::xyz
    descendant
    descendant-or-self//// is short for /descendant-or-self::node()/
    following
    following-sibling
    namespace
    parent.... is short for parent::node()
    preceding
    preceding-sibling
    self.. is short for self::node()
  • 节点测试
    节点测试部分分为三种情况:

    1. 使用节点名称。
    2. 使用*
    3. 使用内置函数。

    下面结合轴和节点测试举一些例子:

    例子结果
    child::book选取所有属于当前节点的子元素的 book 节点。
    attribute::lang选取当前节点的 lang 属性。
    child::*选取当前节点的所有子元素。
    attribute::*选取当前节点的所有属性。
    child::text()选取当前节点的所有文本子节点。
    child::node()选取当前节点的所有子节点。
    descendant::book选取当前节点的所有 book 后代。
    ancestor::book选择当前节点的所有 book 先辈。
    ancestor-or-self::book选取当前节点的所有 book 先辈以及当前节点(如果此节点是 book 节点)
    child::*/child::price选取当前节点的所有 price 孙节点。
  • 谓语
    谓语是用于选取符合某种条件或在特定位置的节点。谓语被嵌在方括号中。

    路径表达式结果
    /bookstore/book[1]选取属于 bookstore 子元素的第一个 book 元素。
    /bookstore/book[last()]选取属于 bookstore 子元素的最后一个 book 元素。
    /bookstore/book[last()-1]选取属于 bookstore 子元素的倒数第二个 book 元素。
    /bookstore/book[position()<3]选取最前面的两个属于 bookstore 元素的子元素的 book 元素。
    //title[@lang]选取所有拥有名为 lang 的属性的 title 元素。
    //title[@lang=’eng’]选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性。
    /bookstore/book[price>35.00]选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00。
    /bookstore/book[price>35.00]/title选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。
XPath运算符

可用的运算符如下:

运算符描述实例返回值
|计算两个节点集//book | //cd返回所有拥有 book 和 cd 元素的节点集
+加法6 + 410
减法6 – 42
*乘法6 * 424
div除法8 div 42
=等于price=9.80如果 price 是 9.80,则返回 true。如果 price 是 9.90,则返回 false。
!=不等于price!=9.80如果 price 是 9.90,则返回 true。如果 price 是 9.80,则返回 false。
<小于price<9.80如果 price 是 9.00,则返回 true。如果 price 是 9.90,则返回 false。
<=小于或等于price<=9.80如果 price 是 9.00,则返回 true。如果 price 是 9.90,则返回 false。
>大于price>9.80如果 price 是 9.90,则返回 true。如果 price 是 9.80,则返回 false。
>=大于或等于price>=9.80如果 price 是 9.90,则返回 true。如果 price 是 9.70,则返回 false。
orprice=9.80 or price=9.70如果 price 是 9.80,则返回 true。如果 price 是 9.50,则返回 false。
andprice>9.00 and price<9.90如果 price 是 9.80,则返回 true。如果 price 是 8.50,则返回 false。
mod计算除法的余数5 mod 21

通过运算符我们可以选取符合特定条件的节点。

Reference

    原文作者:geekpy
    原文地址: https://www.jianshu.com/p/4e61c64108a4
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞