网页数据的解析提取(XPath的使用----lxml库详解)

news/2024/7/19 11:44:26 标签: python, 爬虫

        在提取网页信息时,最基础的方法是使用正则表达式,但过程比较烦琐且容易出错。对于网页节点来说,可以定义id、class或其他属性,而且节点之间还有层次关系,在网页中可以通过XPath或CSS选择器来定位一个或多个节点。那么,在解析页面时,利用XPath或CSS选择器提取某个节点,然后调用相应方法获取该节点的正文内容或者属性,就可以提取我们想要的任意信息了。

        在Python中,如何实现上述操作呢?不用担心,相关解析库有非常多,其中比较强大的有lxml、Beautiful Soup,parsel、pyquery等。此帖介绍使用lxml库来定位网页源代码所需部分。(哇哦,Python也太强大了!又对Python深爱了一份!!!)

目录

XPath的使用

1. XPath概览

2. XPath常用规则

3.准备工作

4.实例引入

5、所有节点

6.子节点

7.父节点

8、属性匹配

 9、文本获取

10.属性获取

11.属性多值匹配

12.多属性匹配

13.按序选择


XPath的使用

XPath 的全称是 XML Path Language, 即XML 路径语言, 用来在 XML 文档中查找信息。它虽然最初是用来搜寻 XML 文档的,但同样适用于 HTML 文档的搜索。

所以在做爬虫时,我们完全可以使用XPath实现相应的信息抽取。本节我们就介绍一下 XPath的基本用法。

1. XPath概览

XPath的选择功能十分强大,它提供了非常简洁明了的路径选择表达式。另外,它还提供了 100多个内建函数,用于字符串、数值、时间的匹配以及节点、序列的处理等。几乎所有我们想要定位的节点, 都可以用XPath选择。

XPath于1999年11月16日成为 W3C标准,它被设计出来, 供XSLT、XPointer以及其他XML解析软件使用。

2. XPath常用规则

        下表列举了 XPath的几个常用规则。

表 达 式

nodename

选取此节点的所有子节点

/

从当前节点选取直接子节点

//

从当前节点选取子孙节点

.

选取当前节点

. .

选取当前节点的父节点

@

选取属性

        这里列出了 XPath 的一个常用匹配规则,如下:

python">//title[@lang='eng']

        它代表选择所有名称为 title,同时属性 lang 的值为 eng的节点。后面会通过 Python 的 lxml库, 利用XPath对HTML 进行解析。

3.准备工作

        使用lxml库之前,首先要确保其已安装好。可以使用 pip3 来安装:

python">pip3 install 1xml

更详细的安装说明可以参考:https://setup.scrape.center/lxml

        安装完成后,就可以进入接下来的学习了。

4.实例引入

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

python">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)
result = etree.tostring(html)
print(result.decode('utf-8'))

        这里首先导入 lxml库的etree模块,然后声明了一段HTML 文本,接着调用HTML 类进行初始化,这样就成功构造了一个 XPath 解析对象。此处需要注意一点,HTML 文本中的最后一个 li 节点是没有闭合的, 而 etree模块可以自动修正 HTML 文本。之后调用tostring方法即可输出修正后的 HTML 代码,但是结果是 bytes类型。于是利用 decode 方法将其转换成 str类型,结果如下:

python">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>
'''

        可以看到,经过处理之后的 li 节点标签得以补全,并且自动添加了 body、html节点。另外,也可以不声明,直接读取文本文件。 首先要将HTML文本新建一个html程序,然后采用调用的方式

test.html文件代码:(该html文本一定需要创建到与一下Python程序同一目录下,后面都是基于此html代码进行分析)

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>
'''

Python代码:

python">from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = etree.tostring(html)
print(result.decode('utf-8'))

         此次输出结果略有不同,多了一个DOCTYPE声明,不过对解析无任何影响,结果如下:

python"><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body><p>text = '''&#13;
</p><div>&#13;
    <ul>&#13;
    <li class="item-0"><a href="link1. html">first item</a></li>&#13;
    <li class="item-1"><a href="link2. html">second item</a></li>&#13;
    <li class="item-inactive"><a href="link3. html">third item</a></li>&#13;
    <li class="item-1"><a href="link4. html">fourth item</a></li>&#13;
    <li class="item-0"><a href="link5. html">fifth item</a>&#13;
    </li></ul>&#13;
</div>&#13;
'''</body></html>

5、所有节点

        一般以//开头的XPath规则,来选取所有符合要点的节点。这里还是以第一个实例中的HTML文本为例,选取其中所有节点。
Python代码:

python">import pprint
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//*')
pprint.pprint(result)

结果如下:

python">[<Element html at 0x2a9cf235280>,
 <Element body at 0x2a9cf4ea780>,
 <Element p at 0x2a9cf4ea7c0>,
 <Element div at 0x2a9cf4ea040>,
 <Element ul at 0x2a9cf4ea200>,
 <Element li at 0x2a9cf4ea680>,
 <Element a at 0x2a9cf4ea700>,
 <Element li at 0x2a9cf4ea740>,
 <Element a at 0x2a9cf4ea8c0>,
 <Element li at 0x2a9cf4ea540>,
 <Element a at 0x2a9cf4eaa40>,
 <Element li at 0x2a9cf4eaa00>,
 <Element a at 0x2a9cf4ea840>,
 <Element li at 0x2a9cf4eab80>,
 <Element a at 0x2a9cf4eabc0>]

        这里使用*代表匹配所有节点,也就是获取整个HTML 文本中的所有节点。从运行结果可以看到返回形式是一个列表,其中每个元素是Element类型,类型后面跟着节点的名称,如html、body、div ul、li、a等,所有节点都包含在了列表中。当然,此处匹配也可以指定节点名称。例如想获取所有 li 节点,实例如下:

python">import pprint
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li')
pprint.pprint(result)
pprint.pprint(result[0])

        这里选取所有li 节点,可以使用//,然后直接加上节点名称,调用时使用xpath方法即可。

运行结果如下:

python">[<Element li at 0x1c5211da700>,
 <Element li at 0x1c5211da740>,
 <Element li at 0x1c5211d9fc0>,
 <Element li at 0x1c5211da180>,
 <Element li at 0x1c5211da4c0>]
<Element li at 0x1c5211da700>

        可以看到,提取结果也是一个列表,其中每个元素都是 Element类型。要是想取出其中一个对象可以直接用中括号加索引获取,如[0]。

6.子节点

通过/ 或//即可查找元素的子节点或子孙节点。假如现在想选择 li 节点的所有直接子节点a,可以这样实现:

python">import pprint
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li/a')
pprint.pprint(result)

        这里通过追加/a的方式,选择了li节点的所有直接子节点a。其中//li用于选中所有li节点,/a用于选中li节点的所有直接子节点a。

运行结果如下 :

python">[<Element a at 0x1c663e9a780>,
 <Element a at 0x1c663e9a7c0>,
 <Element a at 0x1c663e9a040>,
 <Element a at 0x1c663e9a200>,
 <Element a at 0x1c663e9a540>]

        上面的/用于选取节点的直接子节点,如果要获取节点的所有孙子节点,可以使用//。例如:要获取ul节点下的所有子孙节点a,可以这样实现:

python">import pprint
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//ul//a')
pprint.pprint(result)

        运行结果与上面相同。如果这里用//ul/a,就无法获取结果了。因为/用于获取直接子节点,二ul节点下没有直接的a子节点,只有li子节点。因此要注意/和//的区别,前者用于获取直接子节点,后者用于获取子孙节点。

7.父节点

        通过连续的 /或 //可以查找子节点或子孙节点,那么假如知道了子节点,怎样查找父节点呢?这可以用..实现。例如, 首先选中 href属性为 link4. html的a节点, 然后获取其父节点,再获取父节点的 class 属性,相关代码如下:

python">import pprint
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//a[@href="link4.html"]/../@class')
pprint.pprint(result)

运行结果如下:

python">['item-1']

        检查一下结果发现,这正是我们获取的目标li节点的 class 属性。

也可以通过 parent::获取父节点,代码如下:

python">import pprint
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//a[@href="link4.html"]/parent::*/@class')
pprint.pprint(result)

8、属性匹配

        在选取节点的时候,还可以使用@符号实现属性过滤。例如,要选取class属性为item-0的li节点,可以这样实现:

python">import pprint
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]')
pprint.pprint(result)

结果如下:

python">[<Element li at 0x1cf3f68a7c0>, <Element li at 0x1cf3f68a040>]

 9、文本获取

        用XPath中的text方法可以获取节点中的文本,接下来尝试获取前面li节点中的文本,代码如下:

python">import pprint
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]/text()')
pprint.pprint(result)

结果如下:

python">['\r\n    ']

        奇怪的是,我们没有获取任何文本,只获取了一个换行符,这是为什么呢? 因为xpath中text方法的前面是/,而/的含义是选取直接子节点,很明显li的直接子节点都是 a 节点,文本都是在 a节点内部的,所以这里匹配到的结果就是被修正的 li 节点内部的换行符,因为自动修正的 li 节点的尾标签换行了。

即选中的是这两个节点:

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

        其中一个节点因为自动修正,li 节点的尾标签在添加的时候换行了,所以提取文本得到的唯一结果就是 li节点的尾标签和a节点的尾标签之间的换行符。因此,如果想获取 li 节点内部的文本,就有两种方式,一种是先选取 a 节点再获取文本,另一种是使用//。接下来,我们看下两种方式的区别。

先选取a节点,再获取文本的代码如下:

python">import pprint
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]/a/text()')
pprint.pprint(result)

结果如下:

python">['first item', 'fifth item']

        可以看到,这里有两个返回值,内容都是 class 属性为 item-0 的 1i 节点的文本,这也印证了前面属性匹配的结果是正确的。这种方式下,我们是逐层选取的,先选取li节点,然后利用/选取其直接子节点a,再选取节点a的文本,得到的两个结果恰好是符合我们预期的。这种方式下,我们是逐层选取的,先选li节点,然后利用/选取其直接子节点a,再选取节点a 的文本,得到的两个结果恰好是符合我们预期的。再来看一下使用//能够获取什么样的结果,代码如下:

python">import pprint
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]//text()')
pprint.pprint(result)

运行结果如下:

python">['first item', 'fifth item', '\r\n    ']

        不出所料,这里的返回结果是三个。可想而知,这里选取的是所有子孙节点的文本,其中前两个是 li 的子节点 a 内部的文本,另外一个是最后一个 li 节点内部的文本,即换行符。由此,要想获取子孙节点内部的所有文本,可以直接使用//加text 方法的方式,这样能够保证获取最全面的文本信息,但是可能会夹杂一些换行符等特殊字符。如果想获取某些特定子孙节点下的所有文本,则可以先选取特定的子孙节点,再调用 text方法获取其内部的文本,这样可以保证获取的结果是整洁的。

10.属性获取

        我们已经可以用text方法获取节点内部文本,那么节点属性该怎样获取呢? 其实依然可以用@符号。例如,通过如下代码获取所有 li 节点下所有 a 节点的 href属性:

python">import pprint
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li/a/@href')
pprint.pprint(result)

        这里通过@href获取节点的 href属性。注意,此处和属性匹配的方法不同,属性匹配是用中括号加属性名和值来限定某个属性, 如[@href="link1.html"], 此处的@href是指获取节点的某个属性,二者需要做好区分。结果如下:

python">['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html']

         可以看到,我们成功获取了所有 li 节点下 a 节点的 href 属性,并以列表形式返回了它们。

11.属性多值匹配

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

python">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"]/a/text()')
print(result)

这里 HTML 文本中 li 节点的 class 属性就有两个值: li 和li-first。此时如果还用之前的属性匹配获取节点, 就无法进行了, 运行结果如下:

python">[]

        这种情况需要用到 contains 方法, 于是代码可以改写如下:

python">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")]/a/text()')
print(result)

        上面使用了contains方法,给其第一个参数传入属性名称,第二个参数传入属性值,只要传入的属性包含传入的属性值,就可以完成匹配。

运行结果为:

python">['first item']

        contains方法经常在某个节点的某个属性有多个值用到。

12.多属性匹配

        我们还可能遇到一种情况, 就是根据多个属性确定一个节点, 这时需要同时匹配多个属性。运算符and用于连接多个属性, 实例如下:

python">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"]/a/text()')
print(result)

        这里的 li 节点又增加了一个属性name。因此要确定 li 节点, 需要同时考察 class 和 name 属性,一个条件是class 属性里面包含li字符串, 另一个条件是 name 属性为item字符串, 这二者同时得到满足, 才是 li 节点。class 和 name 属性需要用 and 运算符相连, 相连之后置于中括号内进行条件筛选。运行结果如下:

python">['first item']

        这里的 and其实是 XPath中的运算符。除了它,还有很多其他运算符,如or、mod等,在此总结为下表。

  

描述

实例

返 回 值

or

age=19 or age=20

如果 age 是 19, 则返回true。

and

age>19 and age<21

如果 age 是 20, 则返回true。如果age 是18, 则返回false

mod

计算除法的余数

5 mod 2

1

|

计算两个节点集

//book|//cd

返回所有拥有 book 和cd元素的节点集

+

加法

6 + 4

10

-

减法

6 - 4

2

*

乘法

6

* 4

24

div

除法

8 div

4

2

=

等于

age=19

如果 age 是 19, 则返回true。

!=

不等于

age!=19

如果 age 是 18, 则返回true。如果age 是 19, 则返回false

<

小于

age<19

如果 age 是 18, 则返回 true。如果age 是 19, 则返回 false

<=

小于或等于

<=19

如果 age 是 19, 则返回 true。如果age 是 20, 则返回false

>

大于

age>19

如果 age 是 20, 则返回true。如果age 是 19, 则返回 false

>=

大于或等于

age>=19

如果age 是 19, 则返回true。如果age 是18, 则返回false

13.按序选择

        在选择节点时, 某些属性可能同时匹配了多个节点, 但我们只想要其中的某一个,如第二个或者最后一个, 这时该怎么办呢?可以使用往中括号中传入索引的方法获取特定次序的节点, 实例如下:

python">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)
result = html.xpath('//li[1]/a/text()')
print(result)
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)

        上述代码中, 第一次选择时选取了第一个 li 节点, 往中括号中传入数字1 即可实现。注意,这里和写代码不同, 序号以1开头, 而非0。第二次选择时, 选取了最后一个 li 节点, 在中括号中调用last 方法即可实现。第三次选择时, 选取了位置小于3 的 li 节点, 也就是位置序号为1 和2 的节点, 得到的结果就是前两个 li节点。第四次选择时, 选取了倒数第三个 li 节点, 在中括号中调用last 方法再减去2即可实现。因为last 方法代表最后一个, 在此基础上减2 得到的就是倒数第三个。
运行结果如下:

python">['first item']
['fifth item']
['first item', 'second item']
['third item']

        在这个实例中, 我们使用了 last、position等方法。XPath 提供了 100多个方法, 包括存取、数值、字符串、逻辑、节点、序列等处理功能。

        XPath还有一个节点轴的选择方法,但由于很少使用,故在此不在介绍!!!

注:今天,又是深爱Python的一天!!!


http://www.niftyadmin.cn/n/5387575.html

相关文章

3、安装插件

以下插件请按需安装 Mask Passwords 使用此插件可以将在console中出现的password加密&#xff0c;以防止密码泄露。 Job Import Plugin 支持从其他的Jenkins上远程导入job Extended E-mail Notification 在job构建后发送邮件 Python Adds the ability to execute python scrip…

ORA-600 kclchkblk_4和2662故障---惜分飞

有客户恢复请求:由于未知原因导致aix环境的rac两台主机同时重启之后数据库无法正常启动,初步判断是由于写丢失导致故障&#xff08;ORA-00742 ORA-00353&#xff09; Wed Feb 21 09:23:06 2024 ALTER DATABASE OPEN This instance was first to open Abort recovery for domain…

”戏说“ 交换机 与 路由器

一般意义上说 老哥 这文章发表 的 东一榔头 西一锤 呵呵&#xff0c; 想到哪里就啰嗦到哪里 。 交换机&#xff1a; 其实就是在通道交换 路由器&#xff1a; 不光是在通道交换还要在协议上交换 下图你看懂了吗&#xff1f; &#xff08;仅仅数据交换-交换机 协议…

Docker部署开源白板工具Excalidraw并结合内网穿透远程访问

最近&#xff0c;我发现了一个超级强大的人工智能学习网站。它以通俗易懂的方式呈现复杂的概念&#xff0c;而且内容风趣幽默。我觉得它对大家可能会有所帮助&#xff0c;所以我在此分享。点击这里跳转到网站。 文章目录 1. 安装Docker2. 使用Docker拉取Excalidraw镜像3. 创建…

算法| 977.有序数组的平方 209.长度最小的子数组 59.螺旋矩阵II

977.有序数组的平方 简单题 /*** param {number[]} nums* return {number[]}*/ var sortedSquares function(nums) {const arr []for(let i 0; i < nums.length;i){arr[i] nums[i]*nums[i]}return arr.sort((a,b)> a-b) };209.长度最小的子数组 考察&#xff1a; 不…

06 内存管理

目录 c/c内存分布c语言中动态内存管理方式c中动态内存管理方式operator new与operator delete函数new和delete的实现原理定位new表达式(placement-new)常见题 1. c/c内存分布 看一段代码 int globalVar 1; static int staticGlobalVar 1; void Test() {static int staticV…

小程序API能力汇总——基础容器API(二)

ty.preDownloadMiniApp 预下载智能小程序&#xff0c;此接口仅供提供预下载普通智能小程序调用&#xff0c;面板小程序的预下载需要使用另外的接口。 需引入MiniKit&#xff0c;且在>2.3.0版本才可使用 参数 Object object 属性类型默认值必填说明miniAppIdstring是小程序…

在Shopee 平台上销售露营用品的策略指南

在当今数字化时代&#xff0c;电商平台成为了许多商家推广产品的首选渠道。对于想要在 Shopee 平台上销售露营用品的卖家来说&#xff0c;制定有效的选品策略至关重要。通过市场调研、热销品类分析、竞品分析、产品差异化等一系列策略&#xff0c;卖家可以提高产品的竞争力和销…