爬虫技术详解(一)- 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 Syntax Abbreviated Syntax Notes
    ancestor
    ancestor-or-self
    attribute @ @abc is short for attribute::abc
    child xyz is 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 + 4 10
- 减法 6 - 4 2
* 乘法 6 * 4 24
div 除法 8 div 4 2
= 等于 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。
or price=9.80 or price=9.70 如果 price 是 9.80,则返回 true。如果 price 是 9.50,则返回 false。
and price>9.00 and price<9.90 如果 price 是 9.80,则返回 true。如果 price 是 8.50,则返回 false。
mod 计算除法的余数 5 mod 2 1

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

Reference

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容