写在前面
在获取到服务器响应(HTML源码)之后,我们可以通过正则来提取想要的信息,但是编写正则太过麻烦,也容易出错。然而强大的python有强大的解析库,可以供苦逼的码农食用,例如lxml, beautiful Soup. pyquery等,通过这些解析库,就可以根据网页的ID ,class等属性或者节点之间的层次关系来获取想要的数据。
这篇水文,写的是通过lxml库来实现用XPath来提取需要的信息。
#windows平台下安装
pip3 install lxml
注
XPath,全称XML path Language,即XML路径语言,它是一门在XML文档中查找信息的语言,但是它同样适用于HTML文档的查找信息。
1.初始化xpath对象
想要用XPath在HTML源码中提取想要的信息,需要用etree模块对HTML源码进行初始化构造XPath对象。
情景1 —— 对HTML字符串进行初始化
html = etree.HTML(text)
注释
etree.HTML():构造了一个XPath解析对象并对HTML文本进行自动修正。
demo
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>
'''
#初始化一个xpath对象
html = etree.HTML(text)
#利用etree.tostring()方法进行转换称为字符串进行输出,不过却是tytes类型的,用decode转成str类型。
result = etree.tostring(html).decode('utf-8')
print(result)
"""
输出
<html><body><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>
</li></ul>
</div>
</body></html>
"""
情景2 —— 对HTML文档进行初始化
html = etree.parse('./text.html_path',etree.HTMLParser())
2.xpath常用规则
表达式 | 描述 |
---|---|
nodename | 选取此节点的所有子节点 |
/ | 选取当前节点的所有子节点 |
// | 选取当前节点的所有子孙节点 |
. | 选取当前节点 |
.. | 选取当前节点的父节点 |
@ | 选取属性 |
[@attrib_name='value‘] | 选取具有属性attrib_name属性并且值等于value属性的元素 |
3.XPath筛选数据基础
result = html.xpath('匹配规则')
注释
所有满足要求的元素对象以列表的形式返回。
(1)获取所有节点
result = html.xpath('//*')
demo
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)
element = html.xpath("//*") #单双引号都可以
print(len(element)) #输出14
print(element) #以列表的形式输出所有满足要求的元素
""" [<Element html at 0x2ab2b37c1c0>, <Element body at 0x2ab2b6a9b40>,
<Element div at 0x2ab2b6a9bc0>, <Element ul at 0x2ab2b6a9c80>,
<Element li at 0x2ab2b6a9cc0>, <Element a at 0x2ab2b6a9d40>,
<Element li at 0x2ab2b6a9d80>, <Element a at 0x2ab2b6a9dc0>,
<Element li at 0x2ab2b6a9e00>, <Element a at 0x2ab2b6a9d00>,
<Element li at 0x2ab2b6a9e40>, <Element a at 0x2ab2b6a9e80>,
<Element li at 0x2ab2b6a9ec0>, <Element a at 0x2ab2b6a9f00>]
"""
(2)获取指定元素
指定元素名来获取
result = html.xpath('//element_name')
demo —— 获取所有的li元素
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)
element = html.xpath("//li") #单双引号都可以
print(len(element)) #s输出 5
print(element)
"""
输出
[<Element li at 0x1d9476a9a40>,
<Element li at 0x1d9476a9ac0>,
<Element li at 0x1d9476a9b80>,
<Element li at 0x1d9476a9bc0>,
<Element li at 0x1d9476a9c00>]
"""
(3)获取子节点
通过/或着//即可查找元素的子节点或子孙节点。
demo —— 获取a节点
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)
#方法一 : 通过子节点的方式
element = html.xpath("//li/a") #单双引号都可以
print(len(element))
print(element)
#方法二: 通过孙节点的方式
element = html.xpath("//ul//a") #单双引号都可以
print(len(element))
print(element)
"""
都输出
5
[<Element a at 0x16ee8cb9b00>,
<Element a at 0x16ee8cb9b80>,
<Element a at 0x16ee8cb9c40>,
<Element a at 0x16ee8cb9c80>,
<Element a at 0x16ee8cb9cc0>]
"""
(4)属性匹配
在选取信息的时候可以用@符号和中括号[ ]进行属性的过滤。
demo —— 选取class属性等于item-inactive的li
```
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)
#方法一 : 通过子节点的方式
element = html.xpath("//li[@class='item-inactive']") #单双引号都可以
print(len(element))
print(element)
"""
输出
1
[<Element li at 0x211c9da9b00>]
"""
(5)获取文本
用XPath中的text()方法获取节点中的文本。
demo
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)
element = html.xpath("//li[@class='item-inactive']/a/text()")
print(element)
"""
输出
['third item']
"""
注
用XPath中text()方法来获取文本,可能会包括一些不需要的其他文本,比如换行符( )
(6)获取属性
前面提到了,用关键字@来进行class属性筛选,这里属性的获取也是用关键字@。
demo
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)
element = html.xpath("//li/a/@href")
print(element)
"""
输出
['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html']
"""
注
属性获取和属性筛选的对比
名称 | 描述 |
---|---|
属性获取 | 属性获取直接是@加属性名称 |
属性筛选 | 属性筛选用中括号来限制怕[@class='value'] |
4.XPath筛选数据进阶
(1)属性多值匹配
有时候,某些节点某个属性可能会有多个值例如
<li class="li li-first"><a href="link.html">first item</a></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[@class='li']//text()")
print(result)#输出 []
这时候就要用到contains(@属性名称,值)函数,代码改写如下:
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')]//text()")
print(result)#输出 [['first item']]
等效于
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 li-first"]//text()')
print(result)#输出 [['first item']]
(2)多属性的匹配
有时候还会遇到多个属性确定一个节点的情况,例如
<!-- 同时具有class属性和name属性 -->
<li class="li li-first" name="item"><a href="link.html">first item</a></li>
这个时候就要用到运算符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"]//text()')
print(result)#输出 ['first item']
其他XPath运算符
运算符 | 描述 |
---|---|
or | 或 |
amd | 与 |
mod | 取余 |
| | 交集 |
+ | 数值加法 |
- | 数值减法 |
* | 乘法 |
div | 除法 |
= | 等于 |
!= | 不等于 |
< | 小于 |
> | 大于 |
>= | 大于等于 |
(3)按序选择
有时候,我们在选择的时候某些属性可能同时匹配了多个节点,但是只想要其中的某个节点,如第二个节点或者最后一个节点,可以利用中括号传入索引的方法获取特定次序的节点。
from lxml import etree
text = text = '''
<div>
<ul>
<li class="item-0"><a href="link1.html">one</a></li>
<li class="item-1"><a href="link2.html">two</a></li>
<li class="item-inactive"><a href="link3.html">three</a></li>
<li class="item-1"><a href="link4.html"> four</a></li>
<li class="item-0"><a href="link5.html">five</a>
</ul>
</div>
'''
html = etree.HTML(text)
#返回第一个li里的a的文本
one = html.xpath('//li[1]//a/text()')
print(one) #输出 ['one']
#选取前三个
two=html.xpath('//li[position()<=3]/a/text()')
print(two) #输出 ['one', 'two', 'three']
#选取最后一个
last = html.xpath('//li[last()]/a/text()')
print(last) #输出['five']
#选取倒数第二个
four = html.xpath('//li[last()-1]/a/text()')
print(four) #输出[' four']
注
括号里面的数字是从1开始的。
(4)节点轴选择
XPath提供了很多节点轴选择方法,包括获取子元素,兄弟元素,父元素,祖先元素等。
from lxml import etree
text = text = '''
<div>
<ul>
<li class="item-0" name='one'><a href="link1.html">one</a></li>
<li class="item-1"><a href="link2.html">two</a></li>
<li class="item-inactive"><a href="link3.html">three</a></li>
<li class="item-1"><a href="link4.html"> four</a></li>
<li class="item-0"><a href="link5.html">five</a>
</ul>
</div>
'''
html = etree.HTML(text)
#选取第一个li的所有祖先节点
result = html.xpath('//li[1]/ancestor::*')
print(result)
'''
[<Element html at 0x28c1a0dd140>,
<Element body at 0x28c1a3e99c0>,
<Element div at 0x28c1a3e9a80>,
<Element ul at 0x28c1a3e9ac0>]
'''
#选取第一个li的div祖先
result = html.xpath('//li[1]/ancestor::div')
print(result) #输出 [<Element div at 0x2e75ee89bc0>]
#选取当前节点的所有属性值
result = html.xpath('//li[1]/attribute::*')
print(result) #输出 ['item-0', 'one']
#获取子孙节点中的a节点
result = html.xpath('//li[1]/child::a//text()')
print(result) #输出 ['one']
摘自【Python 3网络爬虫开发实战 ,崔庆才著 】
内容有所改动