初入xpath

写在前面

在获取到服务器响应(HTML源码)之后,我们可以通过正则来提取想要的信息,但是编写正则太过麻烦,也容易出错。然而强大的python有强大的解析库,可以供苦逼的码农食用,例如lxml, beautiful Soup. pyquery等,通过这些解析库,就可以根据网页的ID ,class等属性或者节点之间的层次关系来获取想要的数据。

这篇水文,写的是通过lxml库来实现用XPath来提取需要的信息。

#windows平台下安装
pip3 install lxml

XPath,全称XML path Language,即XML路径语言,它是一门在XML文档中查找信息的语言,但是它同样适用于HTML文档的查找信息。

1.初始化xpath对象

想要用XPath在HTML源码中提取想要的信息,需要用etree模块对HTML源码进行初始化构造XPath对象。

情景1 —— 对HTML字符串进行初始化

html = etree.HTML(text)

注释

etree.HTML():构造了一个XPath解析对象并对HTML文本进行自动修正。

demo

from lxml import etree
text = '''
<div>
    <ul>
         <li class="item-0"><a href="link1.html">first item</a></li>
         <li class="item-1"><a href="link2.html">second item</a></li>
         <li class="item-inactive"><a href="link3.html">third item</a></li>
         <li class="item-1"><a href="link4.html">fourth item</a></li>
         <li class="item-0"><a href="link5.html">fifth item</a>
     </ul>
 </div>
'''
#初始化一个xpath对象
html = etree.HTML(text)
#利用etree.tostring()方法进行转换称为字符串进行输出,不过却是tytes类型的,用decode转成str类型。
result = etree.tostring(html).decode('utf-8')
print(result)
""" 
输出 
<html><body><div>
    <ul>
         <li class="item-0"><a href="link1.html">first item</a></li>
         <li class="item-1"><a href="link2.html">second item</a></li>
         <li class="item-inactive"><a href="link3.html">third item</a></li>
         <li class="item-1"><a href="link4.html">fourth item</a></li>
         <li class="item-0"><a href="link5.html">fifth item</a>
     </li></ul>
 </div>
</body></html>
"""

情景2 —— 对HTML文档进行初始化

html = etree.parse('./text.html_path',etree.HTMLParser())

2.xpath常用规则

表达式 描述
nodename 选取此节点的所有子节点
/ 选取当前节点的所有子节点
// 选取当前节点的所有子孙节点
. 选取当前节点
.. 选取当前节点的父节点
@ 选取属性
[@attrib_name='value‘] 选取具有属性attrib_name属性并且值等于value属性的元素

3.XPath筛选数据基础

result = html.xpath('匹配规则')

注释

所有满足要求的元素对象以列表的形式返回。

(1)获取所有节点

result = html.xpath('//*')

demo

from lxml import etree
text = '''
<div>
    <ul>
         <li class="item-0"><a href="link1.html">first item</a></li>
         <li class="item-1"><a href="link2.html">second item</a></li>
         <li class="item-inactive"><a href="link3.html">third item</a></li>
         <li class="item-1"><a href="link4.html">fourth item</a></li>
         <li class="item-0"><a href="link5.html">fifth item</a>
     </ul>
 </div>
'''
html = etree.HTML(text)
element = html.xpath("//*")  #单双引号都可以
print(len(element)) #输出14
print(element) #以列表的形式输出所有满足要求的元素

""" [<Element html at 0x2ab2b37c1c0>, <Element body at 0x2ab2b6a9b40>,
 <Element div at 0x2ab2b6a9bc0>, <Element ul at 0x2ab2b6a9c80>,
  <Element li at 0x2ab2b6a9cc0>, <Element a at 0x2ab2b6a9d40>,
   <Element li at 0x2ab2b6a9d80>, <Element a at 0x2ab2b6a9dc0>,
    <Element li at 0x2ab2b6a9e00>, <Element a at 0x2ab2b6a9d00>,
 <Element li at 0x2ab2b6a9e40>, <Element a at 0x2ab2b6a9e80>, 
 <Element li at 0x2ab2b6a9ec0>, <Element a at 0x2ab2b6a9f00>] 
 """

(2)获取指定元素

指定元素名来获取

result = html.xpath('//element_name')

demo —— 获取所有的li元素

from lxml import etree
text = '''
<div>
    <ul>
         <li class="item-0"><a href="link1.html">first item</a></li>
         <li class="item-1"><a href="link2.html">second item</a></li>
         <li class="item-inactive"><a href="link3.html">third item</a></li>
         <li class="item-1"><a href="link4.html">fourth item</a></li>
         <li class="item-0"><a href="link5.html">fifth item</a>
     </ul>
 </div>
'''

html = etree.HTML(text)
element = html.xpath("//li")  #单双引号都可以
print(len(element))   #s输出 5
print(element)
"""
输出

 [<Element li at 0x1d9476a9a40>,
 <Element li at 0x1d9476a9ac0>, 
 <Element li at 0x1d9476a9b80>, 
 <Element li at 0x1d9476a9bc0>,
<Element li at 0x1d9476a9c00>] 
  """

(3)获取子节点

通过/或着//即可查找元素的子节点或子孙节点。

demo —— 获取a节点

from lxml import etree
text = '''
<div>
    <ul>
         <li class="item-0"><a href="link1.html">first item</a></li>
         <li class="item-1"><a href="link2.html">second item</a></li>
         <li class="item-inactive"><a href="link3.html">third item</a></li>
         <li class="item-1"><a href="link4.html">fourth item</a></li>
         <li class="item-0"><a href="link5.html">fifth item</a>
     </ul>
 </div>
'''
html = etree.HTML(text)

#方法一 : 通过子节点的方式
element = html.xpath("//li/a")  #单双引号都可以
print(len(element)) 
print(element)

#方法二: 通过孙节点的方式
element = html.xpath("//ul//a")  #单双引号都可以
print(len(element))  
print(element)

""" 
都输出
5
[<Element a at 0x16ee8cb9b00>, 
<Element a at 0x16ee8cb9b80>,
 <Element a at 0x16ee8cb9c40>, 
 <Element a at 0x16ee8cb9c80>, 
 <Element a at 0x16ee8cb9cc0>]
 """

(4)属性匹配

在选取信息的时候可以用@符号和中括号[ ]进行属性的过滤。

demo —— 选取class属性等于item-inactive的li

​```
from lxml import etree
text = '''
<div>
    <ul>
         <li class="item-0"><a href="link1.html">first item</a></li>
         <li class="item-1"><a href="link2.html">second item</a></li>
         <li class="item-inactive"><a href="link3.html">third item</a></li>
         <li class="item-1"><a href="link4.html">fourth item</a></li>
         <li class="item-0"><a href="link5.html">fifth item</a>
     </ul>
 </div>
'''
html = etree.HTML(text)

#方法一 : 通过子节点的方式
element = html.xpath("//li[@class='item-inactive']")  #单双引号都可以
print(len(element)) 
print(element)
""" 
输出
1
[<Element li at 0x211c9da9b00>]
"""

(5)获取文本

用XPath中的text()方法获取节点中的文本。

demo

from lxml import etree
text = '''
<div>
    <ul>
         <li class="item-0"><a href="link1.html">first item</a></li>
         <li class="item-1"><a href="link2.html">second item</a></li>
         <li class="item-inactive"><a href="link3.html">third item</a></li>
         <li class="item-1"><a href="link4.html">fourth item</a></li>
         <li class="item-0"><a href="link5.html">fifth item</a>
     </ul>
 </div>
'''
html = etree.HTML(text)
element = html.xpath("//li[@class='item-inactive']/a/text()")  
print(element)
""" 
输出
['third item']
 """

用XPath中text()方法来获取文本,可能会包括一些不需要的其他文本,比如换行符( )

(6)获取属性

前面提到了,用关键字@来进行class属性筛选,这里属性的获取也是用关键字@。

demo

from lxml import etree
text = '''
<div>
    <ul>
         <li class="item-0"><a href="link1.html">first item</a></li>
         <li class="item-1"><a href="link2.html">second item</a></li>
         <li class="item-inactive"><a href="link3.html">third item</a></li>
         <li class="item-1"><a href="link4.html">fourth item</a></li>
         <li class="item-0"><a href="link5.html">fifth item</a>
     </ul>
 </div>
'''
html = etree.HTML(text)
element = html.xpath("//li/a/@href")  
print(element)
""" 
输出
['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html']
 """

属性获取和属性筛选的对比

名称 描述
属性获取 属性获取直接是@加属性名称
属性筛选 属性筛选用中括号来限制怕[@class='value']

4.XPath筛选数据进阶

(1)属性多值匹配

有时候,某些节点某个属性可能会有多个值例如

<li class="li li-first"><a href="link.html">first item</a></li>

如果用之前筛选属性的方法来筛选的话会解析不到该节点。

from lxml import etree
text = '<li class="li li-first"><a href="link.html">first item</a></li>'
html = etree.HTML(text)
result = html.xpath("//li[@class='li']//text()")
print(result)#输出 []

这时候就要用到contains(@属性名称,值)函数,代码改写如下:

from lxml import etree
text = '<li class="li li-first"><a href="link.html">first item</a></li>'
html = etree.HTML(text)
result = html.xpath("//li[contains(@class,'li')]//text()")
print(result)#输出 [['first item']]

等效于

from lxml import etree
text = '<li class="li li-first"><a href="link.html">first item</a></li>'
html = etree.HTML(text)
#把属性的多个值都写上
result = html.xpath('//li[@class="li li-first"]//text()')
print(result)#输出 [['first item']]

(2)多属性的匹配

有时候还会遇到多个属性确定一个节点的情况,例如

<!-- 同时具有class属性和name属性 -->
<li class="li li-first" name="item"><a href="link.html">first item</a></li>

这个时候就要用到运算符and来连接两个条件

from lxml import etree
text = '<li class="li li-first" name="item"><a href="link.html">first item</a></li>'
html = etree.HTML(text)
result = html.xpath('//li[contains(@class,"li")and@name="item"]//text()')
print(result)#输出 ['first item']

其他XPath运算符

运算符 描述
or
amd
mod 取余
| 交集
+ 数值加法
- 数值减法
* 乘法
div 除法
= 等于
!= 不等于
< 小于
> 大于
>= 大于等于

(3)按序选择

有时候,我们在选择的时候某些属性可能同时匹配了多个节点,但是只想要其中的某个节点,如第二个节点或者最后一个节点,可以利用中括号传入索引的方法获取特定次序的节点。

from lxml import etree
text = text = '''
<div>
    <ul>
         <li class="item-0"><a href="link1.html">one</a></li>
         <li class="item-1"><a href="link2.html">two</a></li>
         <li class="item-inactive"><a href="link3.html">three</a></li>
         <li class="item-1"><a href="link4.html"> four</a></li>
         <li class="item-0"><a href="link5.html">five</a>
     </ul>
 </div>
'''
html = etree.HTML(text)
#返回第一个li里的a的文本
one = html.xpath('//li[1]//a/text()')
print(one) #输出 ['one']
#选取前三个
two=html.xpath('//li[position()<=3]/a/text()')
print(two) #输出 ['one', 'two', 'three']
#选取最后一个
last = html.xpath('//li[last()]/a/text()')
print(last) #输出['five']
#选取倒数第二个
four = html.xpath('//li[last()-1]/a/text()')
print(four) #输出[' four']

括号里面的数字是从1开始的。

(4)节点轴选择

XPath提供了很多节点轴选择方法,包括获取子元素,兄弟元素,父元素,祖先元素等。

from lxml import etree
text = text = '''
<div>
    <ul>
         <li class="item-0" name='one'><a href="link1.html">one</a></li>
         <li class="item-1"><a href="link2.html">two</a></li>
         <li class="item-inactive"><a href="link3.html">three</a></li>
         <li class="item-1"><a href="link4.html"> four</a></li>
         <li class="item-0"><a href="link5.html">five</a>
     </ul>
 </div>
'''
html = etree.HTML(text)
#选取第一个li的所有祖先节点
result = html.xpath('//li[1]/ancestor::*')
print(result) 
'''
[<Element html at 0x28c1a0dd140>,
 <Element body at 0x28c1a3e99c0>, 
 <Element div at 0x28c1a3e9a80>, 
 <Element ul at 0x28c1a3e9ac0>]
'''
#选取第一个li的div祖先
result = html.xpath('//li[1]/ancestor::div')
print(result) #输出 [<Element div at 0x2e75ee89bc0>]

#选取当前节点的所有属性值
result = html.xpath('//li[1]/attribute::*')
print(result) #输出 ['item-0', 'one']
#获取子孙节点中的a节点
result = html.xpath('//li[1]/child::a//text()')
print(result) #输出 ['one']

更多轴用法

摘自【Python 3网络爬虫开发实战 ,崔庆才著 】

内容有所改动

原文地址:https://www.cnblogs.com/lc-snail/p/13232920.html