定义
XPath,全称 XML Path Language,即 XML 路径语言,它是一门在XML文档中查找信息的语言,是一个文档解析库。XPath 最初设计是用来搜寻XML文档的,但是它同样适用于 HTML 文档的搜索。
所以在做爬虫时,可以使用 XPath 来做相应的信息抽取,定位节点。XPath有特定的语法、函数、运算符去定位节点、获取节点信息。
常用规则
表达式 | 描述 |
---|---|
nodename | 选取此节点的所有子节点 |
/ | 从当前节点选取直接子节点 |
// | 从当前节点选取子孙节点 |
. | 选取当前节点 |
.. | 选取当前节点的父节点 |
@ | 选取属性 |
1. lxml的etree模块可以对HTML文档进行自动修正
直接解析文本
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>
</ul>
</div>
'''
html = etree.HTML(text)
result = etree.tostring(html)
print(result.decode('utf-8'))
读取文件,再解析
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = etree.tostring(html)
print(result.decode('utf-8'))
test.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">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>
</ul>
</div>
2. 选取所有节点
选取所有节点:XPath语法://*
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//*')
print(result)
只选取所有li节点:XPath语法://li
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li')
print(result)
print(result[0])
3. 获取孩子节点(/)和孙子节点(//)
孩子节点
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li/a')
print(result)
孙子节点
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//ul//a')
print(result)
这个例子中,运行结果是相同的。因为ul里面只有一个a。
4. 获取父节点
XPath语法:/..或者parent::
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//a[@href="link4.html"]/../@class')
print(result)
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//a[@href="link4.html"]/parent::*/@class')
print(result)
5. 获取有相应属性的节点(属性匹配)
选取 class 为 item-1 的 li 节点
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-1"]')
print(result)
6. 获取节点的文本
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]/text()')
print(result)
# 运行结果:['
']
匹配到的结果就是被修正的 li 节点内部的换行符,因为自动修正的li节点的尾标签换行了
即选中的是这两个节点:
<li class="item-0"><a href="link1.html">first item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a>
</li>
其中一个节点因为自动修正,li 节点的尾标签添加的时候换行了,所以提取文本得到的唯一结果就是 li 节点的尾标签和 a 节点的尾标签之间的换行符
如果我们想获取 li 节点内部的文本就有两种方式,一种是选取到 a 节点再获取文本,另一种就是使用 //
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]/a/text()')
print(result)
# 运行结果:['first item', 'fifth item']
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]//text()')
print(result)
# 运行结果:['first item', 'fifth item', '
']
先选取到特定的子孙节点,然后再调用 text() 方法获取其内部文本,这样可以保证获取的结果是整洁的
7. 获取属性值
获取所有 li 节点下所有 a 节点的 href 属性
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li/a/@href')
print(result)
通过 @href 即可获取节点的 href 属性,注意此处和属性匹配的方法不同,属性匹配是中括号加属性名和值来限定某个属性去获取节点,如 a[@href="link1.html"],而此处的 @href 指的是获取节点的某个属性,二者需要做好区分。
8. 属性多值匹配
from lxml import etree
text = '''
<li class="li li-first"><a href="link.html">first item</a></li>
'''
html = etree.HTML(text)
result = html.xpath('//li[@class="li"]/a/text()')
print(result)
# 运行结果 []
有多个值就需要用 contains() 函数,因为//li[@class="li"]代表精确匹配
from lxml import etree
text = '''
<li class="li li-first"><a href="link.html">first item</a></li>
'''
html = etree.HTML(text)
result = html.xpath('//li[contains(@class, "li")]/a/text()')
print(result)
# 运行结果 ['first item']
9. 多属性匹配
可能需要根据多个属性才能确定一个节点,这是就需要同时匹配多个属性才可以,那么这里可以使用运算符 and 来连接
from lxml import etree
text = '''
<li class="li li-first" name="item"><a href="link.html">first item</a></li>
'''
html = etree.HTML(text)
result = html.xpath('//li[contains(@class, "li") and @name="item"]/a/text()')
print(result)
and是XPath的运算符,参考地址http://www.w3school.com.cn/xpath/xpath_operators.asp
10. 按序选择节点
有时候在选择的时候可能某些属性同时匹配了多个节点,但是我们只想要其中的某个节点,如第二个节点,或者最后一个节点。可以利用中括号传入索引的方法获取特定次序的节点。
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>
</ul>
</div>
'''
html = etree.HTML(text)
result = html.xpath('//li[1]/a/text()') # 注意这里和代码中不同,序号是以 1 开头的,不是 0 开头的。result是0开头的,因为result是一个列表。
print(result)
result = html.xpath('//li[last()]/a/text()') # 返回最后一个li节点
print(result)
result = html.xpath('//li[position()<3]/a/text()') # 选取了位置小于 3 的 li 节点,也就是位置序号为 1 和 2 的节点。即得到了前两个节点
print(result)
result = html.xpath('//li[last()-2]/a/text()') # 中括号中传入 last()-2即可,因为 last() 是最后一个,所以 last()-2 就是倒数第三个
print(result)
XPath 中提供了 100 多个函数,包括存取、数值、字符串、逻辑、节点、序列等处理功能,具体所有的函数作用可以参考:http://www.w3school.com.cn/xpath/xpath_functions.asp
11. 节点轴选择
XPath 提供了很多节点轴选择方法,英文叫做 XPath Axes,包括获取子元素、兄弟元素、父元素、祖先元素等等,在一定情况下使用它可以方便地完成节点的选择
from lxml import etree
text = '''
<div>
<ul>
<li class="item-0"><a href="link1.html"><span>first item</span></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>
</ul>
</div>
'''
html = etree.HTML(text)
result = html.xpath('//li[1]/ancestor::*')
print(result)
result = html.xpath('//li[1]/ancestor::div')
print(result)
result = html.xpath('//li[1]/attribute::*')
print(result)
result = html.xpath('//li[1]/child::a[@href="link1.html"]')
print(result)
result = html.xpath('//li[1]/descendant::span')
print(result)
result = html.xpath('//li[1]/following::*[2]')
print(result)
result = html.xpath('//li[1]/following-sibling::*')
print(result)
参考:http://www.w3school.com.cn/xpath/xpath_axes.asp
总结
XPath ,熟练使用之后可以大大提升 HTML(文档) 信息的提取效率