python3 使用 lxml 库解析 HTML

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解析
  • test_lxml.html HTML 文件
<head><title>The Dormouse's story</title></head>

<p class="story">
	this is P label
	<a href="http://www.baidu.com" class="baidu" id="link1"><span>baidu</span></a><span>this is span</span>
	<a href="http://www.cnblogs.com" class="cnblogs" id="link2"><span>cnblogs</span></a>
</p>
<div>
	<ul>
		<li class="item-0 li" name="item0"><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>
		<li class="aaa li-aaa"><a href="link6.html">aaaaa item</a></li>
		<li class="li li-first" name="item6"><a href="link6.html"><span>six item</span></a></li>
		<li class="li li-first" name="item7"><a href="link7.html"><span>seven item</span></a></li>
	</ul>
	<ul class="ul2">
		<li class="item-10 li" name="item10"><a href="link10.html">10 item</a></li>
		<li class="item-11 li" name="item11"><a href="link11.html">11 item</a></li>
		<li class="item-12 li" name="item12"><a href="link12.html">12 item</a></li>
		<li class="item-13 li" name="item13"><a href="link13.html">13 item</a></li>
		<li class="item-14 li" name="item14"><a href="link14.html">14 item</a></li>
		<li class="item-15 li" name="item15"><a href="link15.html">15 item</a></li>
		<li class="item-16 li" name="item16"><a href="link16.html">16 item</a></li>
	</ul>
</div>
  • 读取文本解析节点
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}')
原文地址:https://www.cnblogs.com/gxfaxe/p/15255468.html