python3 lxml

python 库安装 lxml
  • windows系统下的安装:
#pip安装
pip3 install lxml

#wheel安装
#下载对应系统版本的wheel文件:http://www.lfd.uci.edu/~gohlke/pythonlibs/#lxml
pip3 install lxml-4.2.1-cp36-cp36m-win_amd64.whl


  • linux下安装:
yum install -y epel-release libxslt-devel libxml2-devel openssl-devel

pip3 install lxml


xpath 常用规则

表达式

描述

nodename

选取此节点的所有子节点

/

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

//

从当前节点选取子孙节点

.

选取当前节点

..

选取当前节点的父节点

@

选取属性

*

通配符,选择所有元素节点与元素名

@*

选取所有属性

[@attrib]

选取具有给定属性的所有元素

[@attrib='value']

选取给定属性具有给定值的所有元素

[tag]

选取所有具有指定元素的直接子节点

[tag='text']

选取所有具有指定元素并且文本内容是text节点

xpath 中的运算符

运算符

描述

实例

返回值

or

age=19 or age = 20

如果 age 等于 19 或者等于 20 则返回 True, 否则返回 False

an

age > 19 and age < 22

如果 age 大于 19 小于 22 则返回 True, 否则返回 False

mod

取余

5 mod 2

取 5 除以 2 的余数 1

丨【管道符】

取两个节点的集合

//book 丨【管道符】 //cd

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

+, -, *, div

加, 减, 乘, 除

8 加/减/乘/除 4

12/4/32/2

=, !=, <, <=, >, >=

等于, 不等于, 小于, 小于等于, 大于, 大于等于

age =/ != / < / <= / > / >= 19

age 等于 / 不等于 / 小于 / 小于等于 / 大于 / 大于等于 19, 则返回 True, 否则返回 False

HTML解析
  • 读取文本解析节点
from lxml import etree

text = '''
<div>
<ul>
<li class="item-0"><a href="link1.html">第一个</a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-0"><a href="link5.html">a属性</a>
</ul>
</div>
'''
html = etree.HTML(text) # 初始化生成一个XPath解析对象
result = etree.tostring(html, encoding='utf-8') # 解析对象输出代码
print(type(html))
print(type(result))
print(result.decode('utf-8'))


  • 读取 HTML 文件进行解析
from lxml import etree
html_path = os.path.join(os.path.dirname(os.getcwd()), 'html_dir', 'test_lxml.html')
html = etree.parse(html_path, etree.HTMLParser()) #指定解析器HTMLParser会根据文件修复HTML文件中缺失的如声明信息
result = etree.tostring(html) # 解析成字节
# result=etree.tostringlist(html) # 解析成列表
print(type(html))
print(type(result))
print(result)


获取节点
  • 获取所有子孙节点
html = etree.parse(html_path, etree.HTMLParser())
# 从 HTML 节点选取所有的子孙节点
result = html.xpath('//*')
print(f'选择 HTML 节点及所有子孙节点 返回的数据类型: {type(result)}, 返回节点长度: {len(result)}')
print(f'选择 HTML 节点及所有子孙节点: {result}')


  • 获取所有直接子节点
html = etree.parse(html_path, etree.HTMLParser())
# 从 li 节点选取所有的直接子节点 a 节点
result = html.xpath('//li/a')
print(f'选择所有 li 节点下的 a 节点 返回的数据类型: {type(result)}, 返回节点长度: {len(result)}')
print(f'选择所有 li 节点下的 a 节点 返回的: {result}')


  • 获取所有的直接父节点
html = etree.parse(html_path, etree.HTMLParser())
# 获取所有 li 节点节点的直接父节点
result = html.xpath('//li/..')
print(f'获取所有 li 节点的直接父节点 返回的数据类型: {type(result)}, 返回节点长度: {len(result)}')
print(f'获取所有 li 节点的直接父节点 返回的: {result}')


  • 查找属性符合条件的节点
  • 在选取节点的时候我们还可以通过节点的属性进行匹配
html = etree.parse(html_path, etree.HTMLParser())
# 获取所有 属性class='item-1' 的所有 li 节点
result = html.xpath("//li[@class='item-1']")
print(f"获取所有 属性 class='item-1' 的所有 li 节点 返回的: {result}")

# 获取所有 属性name='item0' 的所有 li 节点
result = html.xpath("//li[@name='item0']")
print(f"获取所有 属性 name='item0' 的所有 li 节点 返回的: {result}")


获取文本或属性
  • 文本获取
html = etree.parse(html_path, etree.HTMLParser())
# 获取 p 节点文本
result = html.xpath("//p[@class='story']/text()")
print(f'属性 class="story" 的 p 节点的文本: {result}')


  • 属性获取
  • 使用 @ 符号即可获取节点的属性
html = etree.parse(html_path, etree.HTMLParser())
# 获取属性 class=story 的节点 p 下面所有节点 a 的 id 属性
result = html.xpath("//p[@class='story']/a/@id")
print(f'获取属性 class=story 的节点 p 下面所有节点 a 的 id 属性: {result}')


  • 属性多值匹配
  • 如果某个属性的值有多个时,我们可以使用contains()函数来获取
  • contains 方法单独使用时只会匹配到第一个节点, 如果有多个不会继续匹配后续的节点
html = etree.parse(html_path, etree.HTMLParser())
# 匹配 class 属性中包含 aaa 的 li 节点下的子节点 a 的文本
result = html.xpath("//li[@class='aaa']/a/text()") # 没有匹配到值
print(f'匹配 class 属性中包含 aaa 的 li 节点下的子节点 a 的文本: {result}')

result = html.xpath("//li[contains(@class, 'aaa')]/a/text()") # 可以匹配到值
print(f'匹配 class 属性中包含 aaa 的 li 节点下的子节点 a 的文本: {result}')


  • 多属性匹配
  • 我们还会遇到根据多个属性确定一个节点,这时就需要同时匹配多个属性,此时可用运用and运算符来连接使用
html = etree.parse(html_path, etree.HTMLParser())
# 匹配 class 属性中包含 li-first 且 name=item7 的 li 节点下的子节点 aa/span 的文本
result = html.xpath("//li[contains(@class, 'li-first') and @name='item7']/a/span/text()")
print(f'匹配 class 属性中包含 li-first 且 name=item7 的 li 节点下的子节点 a/span文本: {result}')


  • 按序选择
  • 有时候,我们在选择的时候某些属性可能同时匹配多个节点,但我们只想要其中的某个节点,如第二个节点或者最后一个节点,这时可以利用中括号引入索引的方法获取特定次序的节点
  • 在 xpath 中索引位置从1开始, 和 python 的索引起始位置不同
  • last(): 最后一个节点, 如果后面跟了子节点, 只有一个节点; 如果没有子节点, 而且有多个符合条件的节点, 返回多个节点
  • position(): 返回所有的节点, 不写效果一样
  • position() < 3: 返回索引位置小于 3 的节点, 即节点位置为 1 和 2
  • [index]: 使用这种方式时, 有可能会有多个元素;
  • 例如: 有两个同级节点 ul, 下面都有10的 li 子节点, 此时通过索引会同时取到两个 ul 下符合条件的 li 节点
html = etree.parse(html_path, etree.HTMLParser())
result = html.xpath('//li[1]/a/text()')
print(f'两个符合条件的 ul 都匹配到了, 获取第一个 li 节点下 a 节点文本: {result}')
result = html.xpath('//li[3]/a/text()')
print(f'两个符合条件的 ul 都匹配到了, 获取第三个 li 节点下 a 节点文本: {result}')
result = html.xpath('//li[last()]/a/text()')
print(f'只匹配到了 li 最多的 ul , 获取最后一个 li 节点下 a 节点文本: {result}')
result = html.xpath('//li[position() < 3]/a/text()')
print(f'两个符合条件的 ul 都匹配到了, 获取位置小于3的 li 节点下 a 节点文本: {result}')
result = html.xpath('//li[position()]/a/text()')
print(f'两个符合条件的 ul 都匹配到了, 获取位置小于3的 li 节点下 a 节点文本: {result}')


  • 节点轴选择
print('获取祖先节点')
html = etree.parse(html_path, etree.HTMLParser())
result = html.xpath('//li[1]/ancestor::*')
print(f'获取 第一个 li 的所有祖先节点: {result}')
result = html.xpath('//li[last()]/ancestor::*')
print(f'获取 最后一个 li 所有祖先节点: {result}')
result = html.xpath('//li[last()]/ancestor::div')
print(f'获取 最后一个 li 所有 div 祖先节点: {result}')

print('获取属性值')
# 2个 ul 都被匹配到了
result = html.xpath('//li[1]/attribute::*')
print(f'获取 第一个 li 的所有属性值: {result}')
# 2个 ul 都被匹配到了
result = html.xpath('//li[last()]/attribute::*')
print(f'获取 第一个 li 的所有属性值: {result}')

print('获取所有的直接子节点')
result = html.xpath('//li[1]/child::*')
print(f'li[index] 获取所有的直接子节点: {result}')
result = html.xpath('//li[last()]/child::*')
print(f'li[last() ]获取所有的直接子节点: {result}')

result = html.xpath('//li[1]/descendant::a/text()')
print(f'第一个 li 节点, 获取所有的子孙节点的 a 节点的文本: {result}')
result = html.xpath('//li[1]/following::*')
print(f'获取当前子节点之后的所有节点【与当前节点同级并且包括其子节点】: {result}')
result = html.xpath('//li[1]/following-sibling::*')
print(f'获取当前子节点之后的所有节点【与当前节点同级不包括其子节点】: {result}')