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