Python 爬虫与HTML/XML分析必备lxml模块



lxml 简介

lxml库的功能与优势

解析能力

  • 全面支持:lxml是一个强大的Python解析库,不仅支持HTML,还支持XML的解析;
  • 高效性能:由于其基于C语言的实现,lxml在解析大型或复杂的文档时具有极高的效率;

XPath支持

  • 直观选择:XPath提供了简明的路径选择表达式,使得在XML或HTML文档中选择和提取信息变得直观和方便。
  • 丰富功能:XPath不仅用于节点选择,还提供了超过100个内建函数,这些函数可用于处理各种数据类型(如字符串、数值、时间等)和执行复杂的节点与序列操作。

补充特点

  • CSS选择器支持:除了XPath,lxml还提供了CSS选择器支持,为HTML解析提供了另一种灵活的选择方式。
  • 易于使用:lxml的API设计直观易用,即使对于初学者来说,也能快速上手并编写出高效的解析代码。
  • 广泛兼容性:lxml可以在多种操作系统和Python版本上运行,确保了其广泛的兼容性和实用性。

总结

lxml是一个功能强大、高效且易于使用的Python解析库。

它支持HTML和XML的解析,并通过XPath和CSS选择器提供了灵活的节点选择和数据提取能力。


lxml 运用

XPath 的核心理念是通过路径表达式来精确地定位和选择 XML 文档中的各个节点;

这里的“节点”不仅涵盖了元素节点,还包括了属性节点、文本节点等其他组成部分

XPath 提供了一种简洁、高效且灵活的方式来查询和操作 XML 数据,使得开发者可以轻松地提取、修改或删除文档中的信息。

XPath 的路径表达式由一系列节点测试、轴(axes)和谓语(predicates)组成;

它们共同定义了如何从文档的根节点开始,沿着树形结构遍历到目标节点。

节点测试用于确定节点的类型,如元素节点、属性节点等;

轴则定义了节点间的关系,如父子关系、兄弟关系等;

谓语则进一步对节点进行筛选,以满足特定的条件。

表达式描述
nodename选取此节点的所有子节点
/从当前节点选取直接子节点
//从当前节点选取子孙节点
.选取当前节点
选取当前节点的父节点
@选取属性
*通配符,选择所有元素节点与元素名
@*选取所有属性
[@attrib]选取具有给定属性的所有元素
[@attrib=‘value’]选取给定属性具有给定值的所有元素
[tag]选取所有具有指定元素的直接子节点

lxml 解析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'))

#etree会修复HTML文本节点
<class 'lxml.etree._Element'>
<class 'bytes'>
<html><body><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>
     </li></ul>
 </div>
</body></html>

lxml 解析HTML文件节点

from lxml import etree

html=etree.parse('test.html',etree.HTMLParser()) #指定解析器HTMLParser会根据文件修复HTML文件中缺失的如声明信息
result=etree.tostring(html)   #解析成字节
#result=etree.tostringlist(html) #解析成列表
print(type(html))
print(type(result))
print(result)

#输出
<class 'lxml.etree._ElementTree'>
<class 'bytes'>
b'<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">\n<html><body><div>&#13;\n    <ul>&#13;\n         <li class="item-0"><a href="link1.html">first item</a></li>
\n         <li class="item-1"><a href="link2.html">second item</a></li>
\n         <li class="item-inactive"><a href="link3.html">third item</a></li>
\n         <li class="item-1"><a href="link4.html">fourth item</a></li>
\n         <li class="item-0"><a href="link5.html">fifth item</a>&#13;\n     </li></ul>&#13;\n </div>&#13;\n</body></html>'

xpath 运用


获取所有节点
from lxml import etree

html=etree.parse('test',etree.HTMLParser())
result=html.xpath('//*')  #//代表获取子孙节点,*代表获取所有

print(type(html))
print(type(result))
print(result)

#
<class 'lxml.etree._ElementTree'>
<class 'list'>
[<Element html at 0x754b210048>, <Element body at 0x754b210108>, <Element div at 0x754b210148>, <Element ul at 0x754b210188>, <Element li at 0x754b2101c8>, <Element a at 0x754b210248>, <Element li at 0x754b210288>, <Element a at 0x754b2102c8>, <Element li at 0x754b210308>, <Element a at 0x754b210208>, <Element li at 0x754b210348>, <Element a at 0x754b210388>, <Element li at 0x754b2103c8>, <Element a at 0x754b210408>]

获取父节点
from lxml import etree
from lxml.etree import HTMLParser
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>
     </ul>
 </div>
'''

html=etree.HTML(text,etree.HTMLParser())
result=html.xpath('//a[@href="link2.html"]/../@class')
result1=html.xpath('//a[@href="link2.html"]/parent::*/@class')
print(result)
print(result1)


#
['item-1']
['item-1']

属性匹配
from lxml import etree
from lxml.etree import HTMLParser
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>
     </ul>
 </div>
'''

html=etree.HTML(text,etree.HTMLParser())
result=html.xpath('//li[@class="item-1"]')
print(result)

文本获取
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>
     </ul>
 </div>
'''

html=etree.HTML(text,etree.HTMLParser())
result=html.xpath('//li[@class="item-1"]/a/text()') #获取a节点下的内容
result1=html.xpath('//li[@class="item-1"]//text()') #获取li下所有子孙节点的内容

print(result)
print(result1)

属性多值匹配
from lxml import etree

text1='''
<div>
    <ul>
         <li class="aaa item-0"><a href="link1.html">第一个</a></li>
         <li class="bbb item-1"><a href="link2.html">second item</a></li>
     </ul>
 </div>
'''

html=etree.HTML(text1,etree.HTMLParser())
result=html.xpath('//li[@class="aaa"]/a/text()')
result1=html.xpath('//li[contains(@class,"aaa")]/a/text()')

print(result)
print(result1)

#通过第一种方法没有取到值,通过contains()就能精确匹配到节点了
[]
['第一个']

多属性匹配
from lxml import etree

text1='''
<div>
    <ul>
         <li class="aaa" name="item"><a href="link1.html">第一个</a></li>
         <li class="aaa" name="fore"><a href="link2.html">second item</a></li>
     </ul>
 </div>
'''

html=etree.HTML(text1,etree.HTMLParser())
result=html.xpath('//li[@class="aaa" and @name="fore"]/a/text()')
result1=html.xpath('//li[contains(@class,"aaa") and @name="fore"]/a/text()')


print(result)
print(result1)


#
['second item']
['second item']

运算符
运算符描述实例返回值
orage=10 or age=20如果age等于10或者等于20则返回true反正返回false
andage>19 and age<21如果age等于20则返回true,否则返回false
mod取余5 mod 21
|取两个节点的集合//book | //cd返回所有拥有book和cd元素的节点集合
+5+49
-5-41
*5*420
div除法6 div 32
=等于age=10true
!=不等于age!=10true
<小于age<10true
<=小于或等于age<=10true
>大于age>10true
>=大于或等于age>=10true

按序选择
from lxml import etree

text1='''
<div>
    <ul>
         <li class="aaa" name="item"><a href="link1.html">第一个</a></li>
         <li class="aaa" name="item"><a href="link1.html">第二个</a></li>
         <li class="aaa" name="item"><a href="link1.html">第三个</a></li>
         <li class="aaa" name="item"><a href="link1.html">第四个</a></li> 
     </ul>
 </div>
'''

html=etree.HTML(text1,etree.HTMLParser())

result=html.xpath('//li[contains(@class,"aaa")]/a/text()') #获取所有li节点下a节点的内容
result1=html.xpath('//li[1][contains(@class,"aaa")]/a/text()') #获取第一个
result2=html.xpath('//li[last()][contains(@class,"aaa")]/a/text()') #获取最后一个
result3=html.xpath('//li[position()>2 and position()<4][contains(@class,"aaa")]/a/text()') #获取第一个
result4=html.xpath('//li[last()-2][contains(@class,"aaa")]/a/text()') #获取倒数第三个


print(result)
print(result1)
print(result2)
print(result3)
print(result4)


#
['第一个', '第二个', '第三个', '第四个']
['第一个']
['第四个']
['第三个']
['第二个']

节点轴选择
from lxml import etree

text1='''
<div>
    <ul>
         <li class="aaa" name="item"><a href="link1.html">第一个</a></li>
         <li class="aaa" name="item"><a href="link1.html">第二个</a></li>
         <li class="aaa" name="item"><a href="link1.html">第三个</a></li>
         <li class="aaa" name="item"><a href="link1.html">第四个</a></li> 
     </ul>
 </div>
'''

html=etree.HTML(text1,etree.HTMLParser())
result=html.xpath('//li[1]/ancestor::*')  #获取所有祖先节点
result1=html.xpath('//li[1]/ancestor::div')  #获取div祖先节点
result2=html.xpath('//li[1]/attribute::*')  #获取所有属性值
result3=html.xpath('//li[1]/child::*')  #获取所有直接子节点
result4=html.xpath('//li[1]/descendant::a')  #获取所有子孙节点的a节点
result5=html.xpath('//li[1]/following::*')  #获取当前子节之后的所有节点
result6=html.xpath('//li[1]/following-sibling::*')  #获取当前节点的所有同级节点


#
[<Element html at 0x3ca6b960c8>, <Element body at 0x3ca6b96088>, <Element div at 0x3ca6b96188>, <Element ul at 0x3ca6b961c8>]
[<Element div at 0x3ca6b96188>]
['aaa', 'item']
[<Element a at 0x3ca6b96248>]
[<Element a at 0x3ca6b96248>]
[<Element li at 0x3ca6b96308>, <Element a at 0x3ca6b96348>, <Element li at 0x3ca6b96388>, <Element a at 0x3ca6b963c8>, <Element li at 0x3ca6b96408>, <Element a at 0x3ca6b96488>]
[<Element li at 0x3ca6b96308>, <Element li at 0x3ca6b96388>, <Element li at 0x3ca6b96408>]

自定义函数
from lxml import etree

#定义函数
def ends_with(context, s1, s2):
    return s1[0].endswith(s2)
if __name__ == '__main__':
    doc='''
        <div>
            <ul class='ul items'>
                 <li class="item-0 active"><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>
        '''
    html = etree.XML(doc)
    ns = etree.FunctionNamespace(None)  
    ns['ends-with'] = ends_with #将ends_with方法注册到方法命名空间中
    print(html.xpath("//li[ends-with(@class,'active')]"))
    print(html.xpath("//li[ends-with(@class,'active')]/a/text()"))
  • 形参s1会传入xpath中的第一个参数@class,但这里注意@class是个列表
  • 形参s2会传入xpath中的第二个参数'active''active'是个字符串

xpath支持的内建函数

last()

  • 用途:返回当前上下文中兄弟节点的数量;
  • 含义:在谓语中使用,表示当前节点在其父节点的子节点集合中的位置是最后一个。

position()

  • 用途:返回当前节点在其父节点的子节点集合中的位置。
  • 含义:在谓语中使用,表示当前节点是父节点的第几个子节点。

count(node-set)

  • 用途:返回node-set中节点的数量。
  • 含义:node-set是一个节点集合,该函数计算并返回这个集合中节点的个数。

id(“foo”)

  • 用途:返回文档中ID属性值为"foo"的元素。
  • 含义:在XML文档中,每个元素的ID应该是唯一的。此函数用于选择具有特定ID值的元素。

local-name(node-set)

  • 用途:返回节点名的本地部分。
  • 含义:对于元素或属性节点,该函数返回不包含命名空间的节点名。

name(node-set)

  • 用途:返回节点的完全限定名。
  • 含义:对于元素或属性节点,该函数返回包括命名空间的节点名。

namespace-uri(node-set)

  • 用途:返回节点的命名空间URI。
  • 含义:对于元素或属性节点,该函数返回其命名空间的URI。

concat(string, string, string)

  • 用途:连接两个或多个字符串。
  • 含义:将给定的字符串参数连接成一个单一的字符串。

starts-with(string, string)

  • 用途:检查字符串是否以指定的前缀开始。
  • 含义:如果第一个字符串参数以第二个字符串参数开始,则返回true;否则返回false。

contains(string, string)

  • 用途:检查字符串是否包含另一个字符串。
  • 含义:如果第一个字符串参数包含第二个字符串参数,则返回true;否则返回false。

substring-before(string, string)

  • 用途:返回字符串中第一个子串,该子串在指定分隔符之前。
  • 含义:找到第一个出现的分隔符,并返回其之前的所有字符。

substring-after(string, string)

  • 用途:返回字符串中第一个子串,该子串在指定分隔符之后。
  • 含义:找到第一个出现的分隔符,并返回其之后的所有字符。

substring(string, number, number?)

  • 用途:返回字符串的子串。
  • 含义:从指定的开始位置提取字符串,并根据提供的长度(如果提供)来确定子串的结束位置。

string-length(string)

  • 用途:返回字符串的长度。
  • 含义:计算并返回字符串中的字符数。

normalize-space(string)

  • 用途:删除字符串中的前导和尾随空白,以及字符串中的多余空白。
  • 含义:规范化字符串中的空白字符,使其只包含必要的空格。

sum(node-set)

  • 用途:返回节点集中所有数值的总和。
  • 含义:对node-set中的所有数值节点进行求和。

floor(number)

  • 用途:返回不大于给定数字的最大整数。
  • 含义:对数字进行下取整。

ceiling(number)

  • 用途:返回不小于给定数字的最小整数。
  • 含义:对数字进行上取整。

round(number)

  • 用途:返回最接近给定数字的整数。
  • 含义:对数字进行四舍五入。

这些函数在XPath表达式中非常有用,可以帮助开发者进行更精确的节点选择和数据处理,尤其是在处理复杂或大型的XML或HTML文档时。这些函数增强了XPath的查询能力,使其能够满足多种不同的数据处理需求。


xpath使用工具

Chrome生成XPath表达式
Chrome浏览器提供了一个非常实用的功能,允许开发者快速生成选定元素的XPath表达式。
这对于那些需要定位特定元素进行自动化测试、爬虫开发或前端调试的用户来说非常有用。


要使用这个功能,可以按照以下步骤操作:

  1. 打开Chrome浏览器,并导航到想要检查的网页。
  2. 使用快捷键Ctrl + Shift + I(Windows/Linux)或Cmd + Opt + I(Mac)打开开发者工具。
  3. 切换到“Elements”选项卡,这里会显示网页的DOM结构。
  4. 使用快捷键Ctrl + Shift + C(Windows/Linux)或Cmd + Shift + C(Mac)进入审查元素模式,此时您可以点击页面上的任何元素,它会在Elements选项卡中高亮显示。
  5. 右键点击选中的元素,选择“Copy” > “Copy XPath”选项。这样,您就可以将当前元素的XPath表达式复制到剪贴板中,供后续使用。

生成的XPath表达式通常会包括从根节点到选定元素的所有父级元素和当前元素本身的信息,以确保唯一性。
这使得开发者能够精确地定位到网页上的任何元素,无论是用于自动化测试、数据抓取还是前端调试,都非常方便。
请注意,由于网页的DOM结构可能会随着页面的加载和交互而动态变化,因此生成的XPath表达式可能需要在特定的上下文和状态下才有效。如果网页结构发生变化,可能需要重新生成XPath表达式。
此外,虽然XPath是一种非常强大的工具,但有时候它可能不是定位元素的最佳方法。例如,在某些情况下,使用CSS选择器可能更加简洁和高效。因此,开发者在选择定位策略时应根据具体需求和场景做出决策。

img


xpath插件

安装XPath Helper插件可以验证XPath表达式是否正确

首先,需要在浏览器中安装lanternFQ或Astar VPN等VPN服务,以便访问Google服务器上的插件资源。

然后,在Chrome浏览器右上角点击插件的图标,打开插件的黑色界面。

接下来,编辑好您的XPath表达式并将其选中元素标记为黄色。

这样,就可以轻松地检测您的XPath表达式的有效性了。

通过这种方式,可以确保XPath表达式能够准确地定位所需节点并提取所需数据,从而提高开发效率和代码质量。

总结

lxml是Python中一个功能强大的XML和HTML处理库,它提供了对XML和HTML文档的高效解析和修改能力。

lxml基于C语言库libxml2和libxslt构建,因此具有出色的性能和稳定性。

它支持XPath和XSLT等高级特性,允许用户轻松地查询和转换XML和HTML文档。

此外,lxml还提供了丰富的API,包括ElementTree、SAX和DOM等,可以满足不同的需求。

无论是数据抓取、网页解析还是文档处理,lxml都是一个非常实用的工具。

  • 24
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

需要休息的KK.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值