在提取网页信息时,最基础的方法是使用正则表达式,但过程比较烦琐且容易出错。对于网页节点来说,可以定义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 的一个常用匹配规则,如下:
//title[@lang='eng']
它代表选择所有名称为 title,同时属性 lang 的值为 eng的节点。后面会通过 Python 的 lxml库, 利用XPath对HTML 进行解析。
3.准备工作
使用lxml库之前,首先要确保其已安装好。可以使用 pip3 来安装:
pip3 install 1xml
更详细的安装说明可以参考:https://setup.scrape.center/lxml
安装完成后,就可以进入接下来的学习了。
4.实例引入
下面通过实例感受一下使用XPath对网页进行解析的过程,相关代码如下:
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类型,结果如下:
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代码:
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = etree.tostring(html)
print(result.decode('utf-8'))
此次输出结果略有不同,多了一个DOCTYPE声明,不过对解析无任何影响,结果如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body><p>text = '''
</p><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>
5、所有节点
一般以//开头的XPath规则,来选取所有符合要点的节点。这里还是以第一个实例中的HTML文本为例,选取其中所有节点。
Python代码:
import pprint
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//*')
pprint.pprint(result)
结果如下:
[<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 节点,实例如下:
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方法即可。
运行结果如下:
[<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,可以这样实现:
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。
运行结果如下 :
[<Element a at 0x1c663e9a780>,
<Element a at 0x1c663e9a7c0>,
<Element a at 0x1c663e9a040>,
<Element a at 0x1c663e9a200>,
<Element a at 0x1c663e9a540>]
上面的/用于选取节点的直接子节点,如果要获取节点的所有孙子节点,可以使用//。例如:要获取ul节点下的所有子孙节点a,可以这样实现:
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 属性,相关代码如下:
import pprint
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//a[@href="link4.html"]/../@class')
pprint.pprint(result)
运行结果如下:
['item-1']
检查一下结果发现,这正是我们获取的目标li节点的 class 属性。
也可以通过 parent::获取父节点,代码如下:
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节点,可以这样实现:
import pprint
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]')
pprint.pprint(result)
结果如下:
[<Element li at 0x1cf3f68a7c0>, <Element li at 0x1cf3f68a040>]
9、文本获取
用XPath中的text方法可以获取节点中的文本,接下来尝试获取前面li节点中的文本,代码如下:
import pprint
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]/text()')
pprint.pprint(result)
结果如下:
['\r\n ']
奇怪的是,我们没有获取任何文本,只获取了一个换行符,这是为什么呢? 因为xpath中text方法的前面是/,而/的含义是选取直接子节点,很明显li的直接子节点都是 a 节点,文本都是在 a节点内部的,所以这里匹配到的结果就是被修正的 li 节点内部的换行符,因为自动修正的 li 节点的尾标签换行了。
即选中的是这两个节点:
<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节点,再获取文本的代码如下:
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)
结果如下:
['first item', 'fifth item']
可以看到,这里有两个返回值,内容都是 class 属性为 item-0 的 1i 节点的文本,这也印证了前面属性匹配的结果是正确的。这种方式下,我们是逐层选取的,先选取li节点,然后利用/选取其直接子节点a,再选取节点a的文本,得到的两个结果恰好是符合我们预期的。这种方式下,我们是逐层选取的,先选li节点,然后利用/选取其直接子节点a,再选取节点a 的文本,得到的两个结果恰好是符合我们预期的。再来看一下使用//能够获取什么样的结果,代码如下:
import pprint
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]//text()')
pprint.pprint(result)
运行结果如下:
['first item', 'fifth item', '\r\n ']
不出所料,这里的返回结果是三个。可想而知,这里选取的是所有子孙节点的文本,其中前两个是 li 的子节点 a 内部的文本,另外一个是最后一个 li 节点内部的文本,即换行符。由此,要想获取子孙节点内部的所有文本,可以直接使用//加text 方法的方式,这样能够保证获取最全面的文本信息,但是可能会夹杂一些换行符等特殊字符。如果想获取某些特定子孙节点下的所有文本,则可以先选取特定的子孙节点,再调用 text方法获取其内部的文本,这样可以保证获取的结果是整洁的。
10.属性获取
我们已经可以用text方法获取节点内部文本,那么节点属性该怎样获取呢? 其实依然可以用@符号。例如,通过如下代码获取所有 li 节点下所有 a 节点的 href属性:
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是指获取节点的某个属性,二者需要做好区分。结果如下:
['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html']
可以看到,我们成功获取了所有 li 节点下 a 节点的 href 属性,并以列表形式返回了它们。
11.属性多值匹配
有时候,某些节点的某个属性可能有多个值,例如:
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。此时如果还用之前的属性匹配获取节点, 就无法进行了, 运行结果如下:
[]
这种情况需要用到 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")]/a/text()')
print(result)
上面使用了contains方法,给其第一个参数传入属性名称,第二个参数传入属性值,只要传入的属性包含传入的属性值,就可以完成匹配。
运行结果为:
['first item']
contains方法经常在某个节点的某个属性有多个值用到。
12.多属性匹配
我们还可能遇到一种情况, 就是根据多个属性确定一个节点, 这时需要同时匹配多个属性。运算符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"]/a/text()')
print(result)
这里的 li 节点又增加了一个属性name。因此要确定 li 节点, 需要同时考察 class 和 name 属性,一个条件是class 属性里面包含li字符串, 另一个条件是 name 属性为item字符串, 这二者同时得到满足, 才是 li 节点。class 和 name 属性需要用 and 运算符相连, 相连之后置于中括号内进行条件筛选。运行结果如下:
['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.按序选择
在选择节点时, 某些属性可能同时匹配了多个节点, 但我们只想要其中的某一个,如第二个或者最后一个, 这时该怎么办呢?可以使用往中括号中传入索引的方法获取特定次序的节点, 实例如下:
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 得到的就是倒数第三个。
运行结果如下:
['first item']
['fifth item']
['first item', 'second item']
['third item']
在这个实例中, 我们使用了 last、position等方法。XPath 提供了 100多个方法, 包括存取、数值、字符串、逻辑、节点、序列等处理功能。
XPath还有一个节点轴的选择方法,但由于很少使用,故在此不在介绍!!!文章来源:https://www.toymoban.com/news/detail-834903.html
注:今天,又是深爱Python的一天!!!文章来源地址https://www.toymoban.com/news/detail-834903.html
到了这里,关于网页数据的解析提取(XPath的使用----lxml库详解)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!