【高手过招第二期】解析 html 文件基础知识梳理 (RPA 内置 lxml)

验证 lxml 被 RPA 内置

要解析的 html 文件

【高手过招第二期】解析 html 文件基础知识梳理 (RPA 内置 lxml)

直接导入 etree,编写代码块即可

【高手过招第二期】解析 html 文件基础知识梳理 (RPA 内置 lxml)

Xpath

预备知识

HTML DOM 模型示例

HTML DOM 定义了访问和操作 HTML 文档的标准方法,以树型结构表示 HTML 文档。
【高手过招第二期】解析 html 文件基础知识梳理 (RPA 内置 lxml)

如何实现爬虫数据的解析?

1、定位 html 文档中的节点
2、提取指定节点的属性,比如 href,class 等
3、获取指定节点的文本,比如 a、p,div,span,div 等的文本

什么是 XPath?

XPath (XML Path Language) 是一门在 XML 文档中查找信息的语言,可用来在 XML
文档中对元素和属性迚行遍历。

XPath 可以先将 HTML 文件 转换成 XML 文档,然后用 XPath 查找 HTML 节点戒元素。

XPath 语法

选取节点

XPath 使用路径表达式来选取 XML 文档中的节点戒者节点集。这些路径表达式和我们
在常规的电脑文件系统中看到的表达式非常相似。
下面列出了最常用的路径表达式:
| 表达式 | 描述 |
|–|–|
|nodename | 选取此节点的所有子节点。|
| / | 从根节点选取。|
| // | 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。|
| . | 选取当前节点。 |
|.. | 选取当前节点的父节点。|
| @ | 选取属性。 |

一些路径表达式以及表达式的结果:
| 路径表达式 | 结果 |
|–|–|
|bookstore | 选取 bookstore 元素的所有子节点。|
|/bookstore| 选取根元素 bookstore。注释:假如路径起始于正斜杠 (/),则此路
径始终代表到某元素的绝对路径!|
|bookstore/book | 选取属于 bookstore 的子元素的所有 book 元素。|
|//book | 选取所有 book 子元素,而不管它们在文档中的位置。|
|bookstore//book | 选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于
bookstore 之下的什么位置。|
|//@lang | 选取名为 lang 的所有属性。|

谓语(Predicates)

谓语用来查找某个特定的节点戒者包含某个指定的值的节点,被嵌在方括号中。
在下面的表格中,我们列出了带有谓语的一些路径表达式,以及表达式的结果:
| 路径表达式 | 结果 |
|–|–|
|/bookstore/book[1] | 选取属于 bookstore 子元素的第一个 book 元素。|
|/bookstore/book[last()] | 选取属于 bookstore 子元素的最后一个 book 元素。|
|/bookstore/book[last()-1] | 选取属于 bookstore 子元素的倒数第二个 book 元素。|
|/bookstore/book[position() < 3] | 选取最前面的两个属于 bookstore 元素的子元素的 book 元素。|
|//title[@lang] | 选取所有拥有名为 lang 的属性的 title 元素。|
|//title[@lang=‘eng’]| 选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性。|
|/bookstore/book[price>35.00] | 选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00。|
|/bookstore/book[price>35.00]/title| 选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。|
|// title [contains(@lang,“e”)] | 选取所有 title 元素,且 lang 属性包含 e|

节点文本

| 路径表达式 | 结果 |
|–|–|
|//book/title/text() | 选取 book 元素的文本 。 |

选取未知节点

XPath 通配符可用来选取未知的 XML 元素。
| 通配符 | 描述 |
|–|–|
| * | 匹配任何元素节点。|
| @ * | 匹配任何属性节点。|
| node() | 匹配任何类型的节点。|

在下面的表格中列出了一些路径表达式,以及这些表达式的结果:
| 路径表达式 | 结果 |
|–|–|
|/bookstore/* | 选取 bookstore 元素的所有子元素。|
|//* | 选取文档中的所有元素。|
|//title[@*] | 选取所有带有属性的 title 元素。|

Xpath 轴

轴可以定义相对于当前节点的节点集
| 轴名称 | 表达式 | 描述 |
|–|–|–|
|ancestor| xpath(‘./ancestor: : *’) | 选取当前节点的所有先辈节点(父、祖父)|
|ancestor-or-self |xpath(‘./ancestor-or-self: : *’) | 选取当前节点的所有先辈节点以及节点本身 |
|attribute |xpath(‘./attribute:: *’) | 选取当前节点的所有属性 |
|child |xpath(‘./child:: *’)| 返回当前节点的所有子节点 |
|descendant |xpath(‘./descendant:: *’) | 返回当前节点的所有后代节点(子节点、孙节点)|
|following |xpath(‘./following:: *’) | 选取文档中当前节点结束标签后的所有节点 |
|==following-sibling== |==xpath(‘./following-sibling:: *’)==| == 选取当前节点之后的兄弟节点 ==|
|==parent==| ==xpath(‘./parent:: *’) == |== 选取当前节点的父节点 ==|
|preceding| xpath(‘./preceding:: *’)| 选取文档中当前节点开始标签前的所有节点 |
|==preceding-sibling== |== xpath(‘./preceding-sibling:: *’) ==|== 选取当前节点之前的兄弟节点 ==|
|self |xpath(‘./self:: *’) | 选取当前节点 |

选取若干路径

通过在路径表达式中使用“|”运算符,可以选取若干个路径。
| 路径表达式 | 结果 |
|–|–|
|//book/title | //book/price 选取 book 元素的所有 title 和 price 元素。|
|//title | //price 选取文档中的所有 title 和 price 元素。|
|/bookstore/book/title //price| 选取属于 bookstore 元素的 book 元素的所有 title 元素,以及文档中所有的 price 元素。|

XPath 的运算符

下面列出了可用在 XPath 表达式中的运算符:
【高手过招第二期】解析 html 文件基础知识梳理 (RPA 内置 lxml)

lxml 库

lxml 是一款高性能的 Python HTML/XML 的解析器,用 C 实现的,主要的功能是利用
XPath 语法解析和提取 HTML/XML 数据,来快速的定位特定元素以及节点信息。
lxml python 官方文档:http://lxml.de/index.html

安装

pip install lxml

html 文件解析

hello.html 文件:

<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"><span class="bold">third
item</span></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>

#lxml_parse.py

from lxml import etree
#读取外部文件 hello.html
html = etree.parse('./hello.html')
result = etree.tostring(html, pretty_print=True).decode()
print(result)

常见问题

lxml 解析在使用 parse 加载本地的 html 文件的时候出现报错:
lxml.etree.XMLSyntaxError: xmlParseEntityRef: no name, line 18, column 258
原因
html 代码书写不规范,不符合 xml 解析器的使用规范
解决的办法:

使用 parse 方法的 parser 参数:

parser = etree.HTMLParser(encoding="utf-8")
selector = etree.parse('./data/lol.html',parser=parser)
result=etree.tostring(selector)
print(result)

解析 HTML 代码

#使用 lxml 的 etree 库

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> # 注意,此处缺少一个 </li>
闭合标签
 </ul>
</div>
'''
#利用 etree.HTML,将字符串解析为 HTML 文档
html = etree.HTML(text)
# 按字符串序列化 HTML 文档
result = etree.tostring(html).decode()
print(result)

lxml 可以自动修正 html 代码,例子里不仅补全了 li 标签,还添加了 body,html 标签。

XPath 实例测试

获取所有的 < li > 标签

from lxml import etree
html = etree.parse('hello.html')
print(type(html)) # 显示 etree.parse() 返回类型
result = html.xpath('//li')
print(result) # 打印<li>标签的元素集合
print(len(result))
print(type(result))
print(type(result[0]))

获取 < li > 标签下的所有 < span > 标签

from lxml import etree
html = etree.parse('hello.html')
#result = html.xpath('//li/span')
#注意这么写是不对的:
#因为 / 是用来获取子元素的,而 <span> 并不是 <li> 的子元素,所以,要用双斜杠
result = html.xpath('//li//span')
print(result)

获取 < li > 标签下 href 为 link1.html 的 < a > 标签

from lxml import etree
html = etree.parse('hello.html')
result = html.xpath('//li/a[@href="link1.html"]')
print(result)

获取 < li > 标签的所有 class 属性

#xpath_li.py

from lxml import etree

html = etree.parse('hello.html')
result = html.xpath('//li/@class')
print(result)

运行结果
[<Element a at 0x10ffaae18>]

获取 < li > 标签下的 < a > 标签里的所有 class 属性

#xpath_li.py

from lxml import etree
html = etree.parse('hello.html')
result = html.xpath('//li/a//@class')
print(result)

获取最后一个 < li > 的 < a > 的 href

#xpath_li.py

from lxml import etree
html = etree.parse('hello.html')
result = html.xpath('//li[last()]/a/@href')
#谓语 [last()] 可以找到最后一个元素
print(result)

获取倒数第二个元素的内容

#xpath_li.py

from lxml import etree
html = etree.parse('hello.html')
result = html.xpath('//li[last()-1]/a')
print('通过 text 属性获取元素的内容:')
#text 方法可以获取元素内容
print(result[0].text)
print('通过 text()路径表达式获取元素的内容:')
result = html.xpath('//li[last()-1]/a/text()')
print(result)

获取 class 值为 bold 的标签名

from lxml import etree
html = etree.parse('hello.html')
result = html.xpath('//*[@class="bold"]')


#tag 方法可以获取标签名
print(result[0].tag)
  1. 选取当前节点之前的兄弟节点
elem = html.xpath('//li[3]')[0]
print(elem.tag)
ls = elem.xpath('./preceding-sibling::*')
print('len:',len(ls))
#tag 方法可以获取标签名
for item in ls:
 print(item.xpath('.//text()')[0])

选取当前节点之后的兄弟节点

elem = html.xpath('//li[2]')[0]
print(elem.tag)
ls = elem.xpath('./following-sibling::*')
print('len:',len(ls))
# tag 方法可以获取标签名
for item in ls:
 print(item.xpath('.//text()')[0])

思考题

1、Python 中单引号,双引号,3 个单引号及 3 个双引号的区别

当你用单引号’ ’定义字符串的时候,它就会认为你字符串里面的双引号 " " 是普通字符,从而
不需要转义。反之当你用双引号定义字符串的时候,就会认为你字符串里面的单引号是普通
字符无需转义。3 个引号实现多行输出效果戒者加注释

2、xpath 中 / 和 // 的区别

/ 用来获取子元素,// 用来获取子孙后代的元素

3、如何获取节点内容?

用 text 选取文本内容。

4、如何获取节点属性?

用 @选取属性。

活动链接 【高手过招】第 2 期:实施 RPA 项目中,你遇到了哪些棘手的问题?