pyquery与XPath的使用记录

hresh 475 0

pyquery与XPath的使用记录

概述

在爬虫工作中,对页面的解析工作是不可避免的,因此如何准确高效的匹配出目标信息,对于数据的提取尤为重要。对于网页的节点来说,它可以定义 id、class 或其他属性。而且节点之间还有层次关系,在网页中可以通过 XPath 或 CSS 选择器来定位一个或多个节点。

在 Python 中,除了可以继续使用正则表达式外,还提供了一系列解析库,其中比较强大的库有 lxml、Beautiful Soup、pyquery 等,在实际工作中,根据个人习惯选择适合自己的便捷解析方式,不管是通过 Xpath 和 CSS 选择器来定位解析数据,对于一个懂点前端知识的人来说,都是非常合适。

XPath

XPath,全称 XML Path Language,即 XML 路径语言,它是一门在 XML 文档中查找信息的语言。它最初是用来搜寻 XML 文档的,但是它同样适用于 HTML 文档的搜索。在 Python 中通过 lxml 库来实现 XPath 解析。

pyquery

pyquery:一个类似 jquery 的 python 库。pyquery 允许您对 xml 文档进行 jquery 查询。API 尽可能与 jquery 类似。

实战分析

XPath 的使用

XPath 的几个常见规则

XPath使用规则

比较常用的组合方式如下:

//div[@class="xxx"]/ul  #全局搜索class名称为xxx的div模块,然后再在该div下搜索它的子节点ul,返回结果如:[<Element ul at 0x2b16b9d2c88>]
//div[@id="xxx"]/ul #全局搜索id名称为xxx的div模块

现在通过实例来感受一下使用 XPath 来对网页进行解析的过程,相关代码如下:

输出文本

from lxml import etree

text= '''
<div>
    <ul class="list">
         <li class="item-0">first item</li>
         <li class="item-1"><a href="link2.html">second item</a></li>
         <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
         <li class="item-1 active"><a href="link4.html">fourth item</a></li>
         <li class="item-0"><a href="link5.html">fifth item</a></li>
     </ul>
 </div>
 ‘’‘
html = etree.HTML(text) #使用该模块对字符串进行处理,返回符合HTML格式的文本
result = etree.tostring(html)   #输出HTML文本,格式为byte类型
print(result.decode('utf-8'))   #byte转str

上述代码是为了输出即将要解析的文本内容。

子节点

1、当节点没有属性名称时,需要按照节点嵌套顺序进行定位

html = etree.HTML(text)
result = html.xpath('//ul/li')
#result = html.xpath('//ul/a')  #由于ul节点下的直接节点为li,无法定位到a节点,a节点是li下的直接节点
print(result)

执行结果为:

[<Element li at 0x2b16b9d2d08>, <Element li at 0x2b16b9d2d48>, <Element li at 0x2b16b9d2d88>, <Element li at 0x2b16b9d2dc8>, <Element li at 0x2b16b9d2e08>]

2、当节点有属性名称时,可以通过属性进行精确定位

html = etree.HTML(text)
result = html.xpath('//ul[@class="list"]//span[@class="bold"]')
print(result)

执行结果为:

[<Element span at 0x26f74552b88>]

在以上的结果可以看出提取结果是一个列表形式,其中每个元素都是一个 Element 对象。如果要取出其中一个对象,可以直接用中括号加索引,如[0]。

文本获取

html = etree.HTML(text)

result = html.xpath('//ul[@class="list"]')
print([a.text for a in result[0].xpath('li')])
print(result[0].xpath('li/a/text()'))
print(result[0].xpath('li//text()'))

result = html.xpath('//ul[@class="list"]//span[@class="bold"]/text()')
print(result)

执行结果为:

['first item', None, None, None, None]
['second item', 'fourth item', 'fifth item']
['first item', 'second item', 'third item', 'fourth item', 'fifth item']
['third item']

根据结果进行分析,如果想获取 li 节点内部的文本,就有两种方式,一种是按照嵌套顺序定位到直接与文本相接的节点,或通过属性进行定位,另一种就是使用 //。

属性获取

html = etree.HTML(text)
result = html.xpath('//li/a/@href')
print(result)

result = html.xpath('//li/a')[0].attrib['href']
print(result)

执行结果为:

['link2.html', 'link3.html', 'link4.html', 'link5.html']
link2.html

通过 @href 即可获取节点的 href 属性,或者是通过 attrib() 方法进行获取。如果是别的名称的属性,同理进行操作即可。

属性多值匹配

html = etree.HTML(text)

result = html.xpath('//li[@class="item-0"]//text()')
print(result)
result = html.xpath('//li[contains(@class,"item-0")]//text()')
print(result)

执行结果为:

['first item', 'fifth item']
['first item', 'third item', 'fifth item']

根据属性模糊匹配的时候,需要用到 contains() 方法,第一个参数传入属性名称,第二个参数传入属性值,只要此属性包含所传入的属性值,就可以完成匹配了。

按序选择

html = '''
<div class="wrap">
    <div id="container">
        <ul class="list">
             <li class="item-0">first item</li>
             <li class="item-1"><a href="link2.html">second item</a></li>
             <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
             <li class="item-1 active"><a href="link4.html">fourth item</a></li>
             <li class="item-0"><a href="link5.html">fifth item</a></li>
         </ul>
     </div>
 </div>
'''

html = etree.HTML(html)
print(html.xpath('//ul/li[2]//text()'))
result = html.xpath('//li[last()]/a/text()')
print(result)
result = html.xpath('//li[position()<3]/a/text()')
print(result)
result = html.xpath('//li[last()-2]/a//text()')
print(result)

执行结果为:

['second item']
['fifth item']
['second item']
['third item']

在选择的时候某些属性可能同时匹配了多个节点,但是只想要其中的某个节点,如第二个节点或者最后一个节点,又或者没法通过属性进行匹配的时候。利用中括号传入索引的方法获取特定次序的节点。注意,中括号传入的数值和代码中不同,序号是以 1 开头的,不是以 0 开头。

遍历不同子节点

from lxml import etree

html = '''
<div class="wrap">
    <div id="container">
        <ul class="list">
             <li class="item-0">first item</li>
             <li class="item-1"><a href="link2.html">second item</a></li>
             <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
             <li class="item-1 active"><a href="link4.html">fourth item</a></li>
             <li class="item-0"><a href="link5.html">fifth item</a></li>
         </ul>
         <ol class="list">
             <li class="item-0">first item</li>
             <li class="item-1"><a href="link2.html">second item</a></li>
             <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
             <li class="item-1 active"><a href="link4.html">fourth item</a></li>
             <li class="item-0"><a href="link5.html">fifth item</a></li>
         </ol>
     </div>
 </div>
'''
html = etree.HTML(html)
result = html.xpath('//div[@id="container"]/child::*')
for elem in result:
    if elem.tag == 'ul':
        print('ul--------result:')
        for e in elem.xpath('li//text()'):
            print(e)
    if elem.tag == 'ol':
        print('ol--------result:')
        for e in elem.xpath('li[@class="item-0"]//text()'):
            print(e)

执行结果为:

ul--------result:
first item
second item
third item
fourth item
fifth item
ol--------result:
first item
fifth item

pyquery 的使用

输出文本

from pyquery import PyQuery as pq

html = '''
<div>
    <ul class="list">
         <li class="item-0">first item</li>
         <li class="item-1"><a href="link2.html">second item</a></li>
         <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
         <li class="item-1 active"><a href="link4.html">fourth item</a></li>
         <li class="item-0"><a href="link5.html">fifth item</a></li>
     </ul>
 </div>
'''

doc = pq(html)
print(doc)

子节点

doc = pq(html)
print(doc('#idxx li'))
print(type(doc('.list li')))
items = doc('.list li').items()
print(type(items))
for item in items:
    print(item)

items = doc('.list')
lis = items.find('li')
print(lis)

执行结果为:

<li class="item-0">first item</li>
         <li class="item-1"><a href="link2.html">second item</a></li>
         <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
         <li class="item-1 active"><a href="link4.html">fourth item</a></li>
         <li class="item-0"><a href="link5.html">fifth item</a></li>

<class 'pyquery.pyquery.PyQuery'>
<class 'generator'>
<li class="item-0">first item</li>

<li class="item-1"><a href="link2.html">second item</a></li>

<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>

<li class="item-1 active"><a href="link4.html">fourth item</a></li>

<li class="item-0"><a href="link5.html">fifth item</a></li>

<li class="item-0">first item</li>
         <li class="item-1"><a href="link2.html">second item</a></li>
         <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
         <li class="item-1 active"><a href="link4.html">fourth item</a></li>
         <li class="item-0"><a href="link5.html">fifth item</a></li>

通过结果可以看出,CSS 选择器定位的时候,class 属性一般用 .属性值来识别,id 属性用 #属性值匹配,不必严格按照节点嵌套顺序来写。返回结果类型为 PyQuery 类型,不同于 XPath 返回结果为列表类型,如果需要遍历结果,则需要调用 items() 方法。

文本获取

doc = pq(html)
items = doc('.list li').items()

print([item.text() for item in items])
print(doc('li').text())

执行结果为:

['first item', 'second item', 'third item', 'fourth item', 'fifth item']
first item second item third item fourth item fifth item

pyquery 相对于 XPath 来说,获取文本相对比较简单。text() 则返回了节点内部的纯文本,不需要定位到与文本直接相连的节点。如果返回的文本有多个,则用空格分开,为字符串类型,无法遍历。

属性获取

doc = pq(html)

a = doc('.item-0.active a')
print(a.attr('href'))

a = doc('.active a')
print(a.attr('href'))

a_items = doc('a').items()
for item in a_items:
    print(item.attr('href'))

执行结果为:

link3.html
link3.html
link2.html
link3.html
link4.html
link5.html

调用 attr()方法。在这个方法中传入属性的名称,就可以得到相应节点的属性值。当选中的是节点返回多个元素时,则需要进行遍历。

属性多值匹配

doc = pq(html)

a = doc('.item-0.active a')
print(a.attr('href'))

a = doc('.active a')
print(a.attr('href'))

执行结果为:

link3.html
link3.html

当属性较长含有空格时,可以分别用 .属性值相连的方式进行匹配,或者单独匹配特有的一段属性值。

伪类选择器

CSS 选择器之所以强大,还有一个很重要的原因,那就是它支持多种多样的伪类选择器,例如选择第一个节点、最后一个节点、奇偶数节点、包含某一文本的节点等。

html = '''
<div class="wrap">
    <div id="container">
        <ul class="list">
             <li class="item-0">first item</li>
             <li class="item-1"><a href="link2.html">second item</a></li>
             <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
             <li class="item-1 active"><a href="link4.html">fourth item</a></li>
             <li class="item-0"><a href="link5.html">fifth item</a></li>
         </ul>
     </div>
 </div>
'''
from pyquery import PyQuery as pq
doc = pq(html)
li = doc('li:first-child')
print(li)
li = doc('li:last-child')
print(li)
li = doc('li:nth-child(2)')
print(li)
li = doc('li:gt(2)')
print(li)
li = doc('li:nth-child(2n)')
print(li)

执行结果为:

<li class="item-0">first item</li>

<li class="item-0"><a href="link5.html">fifth item</a></li>

<li class="item-1"><a href="link2.html">second item</a></li>

<li class="item-1 active"><a href="link4.html">fourth item</a></li>
             <li class="item-0"><a href="link5.html">fifth item</a></li>

<li class="item-1"><a href="link2.html">second item</a></li>
             <li class="item-1 active"><a href="link4.html">fourth item</a></li>

在实际提取网页数据的过程中,有些情况下是无法通过属性进行匹配的,为了准确定位到某一节点,就需要通过伪类选择器来进行点位。

总结

XPath 和 pyquery 都比较高效便捷,具体选择哪种使用方法,根据个人习惯进行选择,两者在功能上基本都一致,如果对 CSS 样式有所了解的话,选择 pyquery 比较容易一些。

发表评论 取消回复
表情 图片 链接 代码

分享