第64天: XPath 和 lxml

2019/11/20

by 某某白米饭

XPath 和 lxml

XPath 全称为 Xml Path Language,即 Xml 路径语言,是一种在 Xml 文档中查找信息的语言。它提供了非常简洁的路径选择表达式,几乎所有的节点定位都可以用它来选择。

XPath 可以用于 Xml 和 Html,在爬虫中经常使用 XPath 获取 Html 文档内容。

lxml 是 Python 语言用 Xpath 解析 XML、Html文档功能最丰富的、最容易的功能模块。

XPath 术语

节点

在 XPath 中有七种节点分别是元素、属性、文本、文档、命名空间、处理指令、注释,前3种节点为常用节点

请看下面的 Html 例子,(注:这个例子全文都需要使用)

<!DOCTYPE html>
<html>
    <body>
        <div>
            <!-- 这里是注释 -->
            <h4>手机品牌商<span style="margin-left:10px">4</span></h4>
            <ul>
               <li>小米</li>
               <li>华为</li> 
               <li class='blank'> OPPO </li>
               <li>苹果</li>
            </ul>
        </div>
        <div>
            <h4>电脑品牌商<span style="margin-left:10px">3</span></h4>
            <ul class="ul" style="color:red">
                <li>戴尔</li> 
                <li>机械革命</li> 
                <li>ThinkPad</li>
            </ul>
        </div>
    </body>
</html>

在上面的例子中

<html> 为文档节点
<li>小米</li> 为元素节点
class='blank' 为属性节点
<!-- 这里是注释 --> 为注释节点

节点关系

在 XPath中有多中节点关系分别是父节点、子节点、同胞节点、先辈节点、后代节点

在上面的例子中

  1. 父节点:每个元素以及属性都有一个父节点,如:div 节点的父节点是 body 节点
  2. 子节点:元素节点可有零个、一个或多个子节点,如:第一个 ul 节点的子节点是4个 li 节点
  3. 同胞节点:拥有相同的父节点的节点,如:两个 div 节点是同胞节点
  4. 先辈节点:某节点的父节点、父节点的父节点…,如:ul 节点的先辈节点有 div 节点、body 节点
  5. 后代节点:某个节点的子节点,子节点的子节点…,如:body 节点的后代节点有div 节点、ul 节点、li 节点

XPath 语法

基本语法

表达式 描述
nodeName 选择nodeName节点的所有子节点
/ 从根节点开始
// 从匹配的节点开始选择节点
. 选择当前节点
.. 选择当前节点的父节点
@ 选择元素
* 匹配任意元素节点
@* 匹配任意属性节点

用上面的 Html 文档举个例子

路径表达式 描述
body 选取 body 的所有子节点
/html 选取 html 节点
//div 选取所有 div 节点
//div/./h4 div 节点下的 h4 节点
../div 选取当前节点的父节点下的所有 div 节点
//@class 所有带有 class 元素的节点
//* 选择所有节点
//@* 选择所有属性节点

常用函数

表达式 描述
position() 返回节点的 index 位置
last() 返回节点的个数
contains(string1,string2) string1 是否包含 string2
text() 返回文本节点
comment() 返回注释节点
normalize-space(string) 去除首位空格,中间多个空格用一个空格代替
substring(string,start,len) 返回从 start 位置开始的指定长度的子字符串,第一个字符下标为1
substring-before(string1,string2) 返回string1中位于第一个string2之前的部分
substring-after(string1,string2) 返回string1中位于第一个string2之后的部分

同样用上面的Html文档举个例子

路径表达式 描述
//div[position()>1] 选择第二个 div 节点
//div[last()] 选择最后一个 div 节点
contains(//h4[2],’手机’) 第二个 h4 标签是否包含手机字符串
//li/text() li 节点中的文本内容
//div/comment() div 节点下的 html 注释
normalize-space(//li[@class=’blank’]) li 节点下 class属性为 blank 的文本去掉空格
substring(//h4[1],1,2) 第一个 h4 节点的前2个字
substring-before(//h4[1],’品牌商’) 第一个 h4 节点的品牌商字符串之前的字符串
substring-after(//h4[1],’品牌商’) 第一个 h4 节点的品牌商字符串之后的字符串

谓语

XPath 中的谓语就是删选表达式,相当于 SQL 中的 Where 条件,谓语被嵌在 [ ] 中

路径表达式 描述
//div[1] 选择第一个 div 节点
//div[2]/ul/li[last()] 选择第二个 div 节点下的最后一个 li 节点
//div[2]/ul/li[position()>3] 选择第二个 div 节点下的前两个 li 节点
//ul[@class] 选择所有带 class 属性的 ul 节点
//ul[@class=’computer’] 选择 class 属性为 computer 的 ul 节点
//h4[span = 4] 选择 h4 节点下 span 值等于4的节点

Xpath 结语

以上内容介绍了 XPath 的基本语法,下面将介绍 XPath 如何在 Python 中使用。

lxml 模块

安装

sudo pip3 install lxml==4.4.1

解析 HTML 文档

lxml.etree 一个强大的 Xml 处理模块,etree 中的 ElementTree 类是一个主要的类,用于对XPath的解析、增加、删除和修改节点。

from lxml import etree

etree.parse() 函数可以解析一个网页文件还可以解析字符串, 在网页中下载的数据一般都是字符串形式的,使用 parse(StringIO(str)) 将整个页面内容解析加载构建一个 ElementTree 对象,ElementTree 可以使用 XPath 语法精准找到需要的数据。

1.加载页面到内存

from lxml import etree
from io import StringIO

test_html = '''
<html>
    <body>
        <div>
            <!-- 这里是注释 -->
            <h4>手机品牌商<span style="margin-left:10px">4</span></h4>
            <ul>
               <li>小米</li>
               <li>华为</li>
               <li class='blank'> OPPO </li>
               <li>苹果</li>
            </ul>
        </div>
        <div>
            <h4>电脑品牌商<span style="margin-left:10px">3</span></h4>
            <ul class="ul" style="color:red">
                <li>戴尔</li>
                <li>机械革命</li>
                <li>ThinkPad</li>
            </ul>
        </div>
    </body>
</html>'''

html = etree.parse(StringIO(test_html))
print(html)

结果:

<lxml.etree._ElementTree object at 0x10bd6b948>

2.获取所有 li 标签数据

li_list = html.xpath('//li')

print("类型:")
print(type(li_list))

print("值:")
print(li_list)

print("个数:")
print(len(li_list))


for l in li_list:
    print("li文本为:" + l.text)

结果:

类型:
<class 'list'>
值:
[<Element li at 0x10543c9c8>, <Element li at 0x10543ca08>, <Element li at 0x10543ca48>, <Element li at 0x10543ca88>, <Element li at 0x10543cac8>, <Element li at 0x10543cb48>, <Element li at 0x10543cb88>]
个数:
7
li文本为:小米
li文本为:华为
li文本为: OPPO 
li文本为:苹果
li文本为:戴尔
li文本为:机械革命
li文本为:ThinkPad

3.获取带 class=’blank’ 属性数据

blank_li_list = html.xpath('//li[@class="blank"]')

print("类型:")
print(type(blank_li_list))

print("值:")
print(blank_li_list)

print("个数:")
print(len(blank_li_list))


for l in blank_li_list:
    print("li文本为:" + l.text)

结果:

类型:
<class 'list'>
值:
[<Element li at 0x105253a48>]
个数:
1
li文本为: OPPO 

4.属性操作

ul = html.xpath('//ul')[1]
#遍历属性
for name, value in ul.attrib.items():
    print('{0}="{1}"'.format(name, value))

#添加新的属性
ul.set("new_attr", "true")
# 获取单个属性
new_attr = ul.get('new_attr')
print(new_attr)

结果:

class="ul"
style="color:red"
true

5.获取最后一个div标签数据

last_div = html.xpath('//div[last()]')
print("TAG:")
print(last_div.tag)
print("值:")
print(last_div.text)

结果

div
值:

   

6.添加子节点

child = etree.Element("child")
child.text = "这里是新的子元素"
last_div.append(child)
# 在最后一个 div 标签查找新的子元素
clild_text = last_div.find("child").text
print(clild_text)

7.删除子元素

# 查找并设置第一个查询到的元素
first_ul = html.find("//ul")
ul_li = first_ul.xpath("li")
for li in ul_li:
    # 删除元素
    first_ul.remove(li)

ul_li = first_ul.xpath("li")
if len(ul_li) == 0:
    print("元素被删除了")

8.遍历元素后代

body = html.find("body")
for sub in body.iter():
    print(sub.tag)
    print(sub.text)

结果

body

        
div

            
<cyfunction Comment at 0x10c374b10>
 这里是注释 
h4
手机品牌商
span
4
ul
...

工具

  1. 在 google 浏览器开发者模式下,Elements 界面选择元素后右键 Copy,可以 Copy 元素的 XPath 路径
  2. XPath Helper 是一个 google 浏览器插件,可以验证 XPath 是否正确

总结

  1. 学习了 XPAth 的知识,可以快速匹配单个或多个元素节点和属性,在工作中大大加快了工作的效率。
  2. lxml 是一个 Python 中强大的 Xml 和 Html 处理模块,结合 XPath 的使用在程序中快速、便捷的分析、修改网页内容。

示例代码:Python-100-days-day064


微信扫描二维码,关注我的公众号

(关注后回复 1024 获取程序员专属精华资料!

点击了解:全网唯二以程序员为主题的漫画公众号

Show Disqus Comments

Post Directory

扫码关注公众号:纯洁的微笑
发送 290992
即可立即永久解锁本站全部文章