Xpath简介
XPath (XML Path Language)是一门在 HTML\XML 文档中查找信息的语言,可用来在 HTML\XML 文档中对元素和属性进行遍历。在Python爬虫中, XPath 我们可以利用快速地定位 HTML\XML 响应中的特定元素以及获取节点的信息,并且通常情况下会比使用正则表达式提取更简单而且更高效。
Xpath常用规则
表达式 | 描述 |
---|---|
nodename | 选取此节点的所有子节点 |
/ | 从当前节点选取直接子节点 |
// | 从当前节点选取子孙节点 |
. | 选取当前节点 |
. . | 选取当前节点的父节点 |
text() | 选取文本 |
@ | 选取属性 |
* | 匹配任何元素节点 |
@* | 匹配任何属性节点 |
node() | 匹配任何类型的节点 |
请看以下示例:
表达式 | 描述 |
---|---|
bookstore | 选取bookstore元素的所有子节点 |
/bookstore | 选取根元素 bookstore。 |
bookstore/book | 选取属于 bookstore 的子元素的所有 book 元素。 |
//book | 选取所有 book 子元素,而不管它们在文档中的位置。 |
bookstore//book | 选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置。 |
//@lang | 选取名为 lang 的所有属性。 |
//title/text() | 选取所有 title 元素的文本。 |
/bookstore/* | 选取 bookstore 元素的所有子元素。 |
//* | 选取文档中的所有元素。 |
//title[@*] | 选取所有带有属性的 title 元素。 |
获取节点信息
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)
#从文件加载HTML
#html = etree.parse('test.html', etree.HTMLParser())
result = etree.tostring(html).decode('utf-8')
print(result)
ret = html.xpath('//li/a') #所有li的a节点
ret = html.xpath(' //a[@href="link1.html"]/../@class ') #通过父节点获取class属性
print(ret)
etree.HTML() 可以接收str或bytes类型数据,并将其转换为Elements对象,etree模块可以自动修正HTML文本,我们用tostring() 即可输出修正后的HTML代码,但结果是bytes类型,所以用decode() 将它转成str类型
属性匹配
我们在选取的时候,可以用@进行属性过滤,例如我们想要选取class为item-1的li节点时:
from lxml import etree
html = etree.HTML(text)
ret = html.xpath('//li[@class="item-1"]')
print(ret)
,通过中括号内加属性名和属性值的方式,过滤不要的东西,输出结果:
[<Element li at 0xf5b628>, <Element li at 0xf5b668>]
文本获取
利用xpath中的text() 方法可以获取节点中的文本
from lxml import etree
html = etree.HTML(text)
ret = html.xpath('text()')
print(ret)
输出结果:
[]
从结果可以看到。我们并没有获取到任何文本,只获取到了一个换行符,这是因为XPath中text()前面是/,而此处/的含义是选取直接子节点,很明显li的直接子节点都是a节点,文本都是在a节点内部的,所以这里匹配到的结果就是被修正的li节点内部的换行符,因为自动修正的li节点的尾标签换行了。
所以,我们还要其他方法,第一种:再加一层/a
from lxml import etree
html = etree.HTML(text)
ret = html.xpath('//li[@class="item-0"]/a/text()')
输出结果:
['first item', 'fifth item']
第二种:用// 选取所有子孙节点的文本,但结果中会有三个内容,前两个是a节点里的文本,另一个是最后一个li节点里的换行符
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', '\n ']
所以若想获得子孙节点内的所有文本,可以直接用//加text()的方法,这样可以保证获得最全的文本信息,只不过会包含一些换行符等字符。如果只想获取某些子孙节点下的文本,可以先选取子孙节点,在用text()的方法获取
属性获取
用@就可以获取想要的属性,假如我们要获取li节点下所有a节点的href属性:
from lxml import etree
html = tree.HTML(text)
ret = html.xpath('//li/a/@href')
print(ret)
输出结果:
['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html']
通过@href就可以获得节点属性,但这个和属性匹配的方法不同。属性匹配是这样的:如[@href=“link1.html”] ,把属性名和属性值给限定住, 而此处的@href指的是获取节点的某个属性,要做好区分
属性多值匹配
如果某些节点的某个属性有多个值,如下:
from lxml import etree
text = '<li class="li li-first"><a href="link.html">first item</a></li>'
html = etree.HTML(text)
ret = html.xpath('//li[@class="li"]/a/text()')
print(ret)
class属性有两个值li和li-first,按照这种办法无法输出想要的内容,此时需要用到contains()函数:
from lxml import etree
text = '<li class="li li-first"><a href="link.html">first item</a></li>'
html = etree.HTML(text)
ret = html.xpath('//li[contains(@class, "li")]/a/text()')
print(ret)
用contains() 时,第一个参数传入属性名,第二个参数传入属性值,只要此属性包含传入的属性值时,就可以完成匹配
输出结果:
['first item']
参数索引
当我们在选择的时候某些属性可能会匹配了多个节点,但我们只要其中的某个时,就可以用中括号传入索引的方法获取特定的节点:
from lxml import etree
html = etree.HTML(text)
html = etree.HTML(text)
ret = html.xpath('//li[1]/a/text()')
print(ret)
ret = html.xpath('//li[last()]/a/text()')
print(ret)
ret = html.xpath('//li[position()<3]/a/text()')
print(ret)
ret = html.xpath('//li[last()-2]/a/text()')
print(ret)
输出结果:
['first item']
['fifth item']
['first item', 'second item']
['third item']
实战
from lxml import etree
import requests
url = 'http://news.sina.com.cn/s/2018-05-09/doc-ihaichqz1009657.shtml'#网址
req = requests.get(url)
text = req.content.decode('utf-8')
html = etree.HTML(text)
node_title = html.xpath('/html/body//h1[@class="main-title"]//text()')#获得标题
print('==='*20)
print(node_title[0])
print('==='*20)
node_content = html.xpath('//div[@class="article-content-left"]//div[@id="article"]//text()')#获得正文
print("*".join(node_content))
输出结果:
============================================================
小学老师罚学生赤脚跑操场 官方:将按规定处理
============================================================
* 原标题:潼南一小学体育老师罚学生赤脚跑操场续:区教委向华龙网发来情况说明*
* 重庆客户端-华龙网5月9日消息,这两天,重庆潼南区朝阳小学二年级6班不少家长心疼不已,因为多个娃儿脚底被磨出了泡。一问才知道,是因为有些学生体育课上没穿运动鞋,被体育老师要求赤脚在操场上跑步。收到重庆网络问政平台这一投诉后,华龙网记者立即进行了调查。今(9)日,华龙网发布了*《重庆潼南一小学体育老师罚学生赤脚跑操场脚底磨出泡当地教委介入》*报道后,潼南教委高度重视并给华龙网传来官方的情况说明。*
* * [说明全文]*
* 关于家长在华龙网投诉教师上体育课体罚学生的情况说明*
* 潼南区朝阳小学体育教师邹老师于2018年5月7日上午上体育课时,发现该班有少部分名学生未按体育课的要求穿运动鞋。该教师认为,穿着凉鞋跑步对学生本人及他人存在安全隐患,塑胶跑道不会对学生光脚运动造成影响,于是就叫未穿运动鞋的学生,脱掉凉鞋进行随班热身跑步。当时邹老师未发现学生有异常情况,也未接到学生有异常情况的反映。后经家长反映到学校,有极少数光着脚跑步的学生有异常情况,学校庚即与部分家长进行了沟通,并及时调查了解了此事,并对该教师这种不恰当教学方法进行了批评教育,我们将按相关规定对该教师作出相应的处理。*
* 重庆市潼南区教育委员会*
* 2018年5月9日*
* 来源:华龙网*