xpath的基础使用
一.xpath简介

XPath 是一门在 XML 文档中查找信息的语言。XPath 用于在 XML 文档中通过元素和属性进行导航。
- XPath 使用路径表达式在 XML 文档中进行导航
- XPath 包含一个标准函数库
- XPath 是 XSLT 中的主要元素
- XPath 是一个 W3C 标准
`节点
在 XPath 中,有七种类型的节点:元素、属性、文本、命名空间、处理指令、注释以及文档(根)节点。XML 文档是被作为节点树来对待的。
xpath比美丽汤更通用,在各语言的xpath中都可以使用,scrapy框架也是内置了一个xpath,而美丽汤只能在python中使用.

二、xpath语法
1.选取节点
| 表达式 | 描述 | 实例 | |
| nodename | 选取nodename节点的所有子节点 | xpath('//div') | 选取了div节点的所有子节点 |
| / | 从根节点选取 | xpath('/div') | 从根节点上选取div节点 |
| // | 选取所有的当前节点,不考虑他们的位置 | xpath('//div') | 选取所有的div节点 |
| . | 选取当前节点 | xpath('./div') | 选取当前节点下的div节点 |
| .. | 选取当前节点的父节点 | xpath('..') | 回到上一个节点 |
| @ | 选取属性 | xpath('//@calss') | 选取所有的class属性 |
2、谓语
谓语被嵌在方括号内,用来查找某个特定的节点或包含某个特定的值的节点,因为所有xpath解析返回值为一个列表
xpath的索引从1开始
| 表达式 | 结果 |
| xpath('/body/div[1]') | 选取body下的第一个div节点 |
| xpath('/body/div[last()]') | 选取body下最后一个div节点 |
| xpath('/body/div[last()-1]') | 选取body下倒数第二个div节点 |
| xpath('/body/div[positon()<3]') | 选取body下前两个div节点 |
| xpath('/body/div[@class]') | 选取body下带有class属性的div节点 |
| xpath('/body/div[@class="main"]') | 选取body下class属性为main的div节点 |
| xpath('/body/div[price>35.00]') | 选取body下price元素值大于35的div节点 |
3、通配符
Xpath通过通配符来选取未知的XML元素
| 表达式 | 结果 |
| xpath('/div/*') | 选取div下的所有子节点 |
| xpath('/div[@*]') | 选取所有带属性的div节点 |
4、逻辑运算
使用“|”或者and运算符可以选取多个路径
| 表达式 | 结果 |
| xpath('//div|//table') | 选取所有的div和table节点 |
| xpath(//a[@href="" and @class="du"]) | 选取同时具href="",class='du'的a标签 |
5、Xpath轴
轴可以定义相对于当前节点的节点集
| 轴名称 | 表达式 | 描述 |
| ancestor | xpath('./ancestor::*') | 选取当前节点的所有先辈节点(父、祖父) |
| ancestor-or-self | xpath('./ancestor-or-self::*') | 选取当前节点的所有先辈节点以及节点本身 |
| attribute | xpath('./attribute::*') | 选取当前节点的所有属性 |
| child | xpath('./child::*') | 返回当前节点的所有子节点 |
| descendant | xpath('./descendant::*') | 返回当前节点的所有后代节点(子节点、孙节点) |
| following | xpath('./following::*') | 选取文档中当前节点结束标签后的所有节点 |
| following-sibing | xpath('./following-sibling::*') | 选取当前节点之后的兄弟节点 |
| parent | xpath('./parent::*') | 选取当前节点的父节点 |
| preceding | xpath('./preceding::*') | 选取文档中当前节点开始标签前的 |
| preceding-sibling | xpath('./preceding-sibling::*') | 选取当前节点之前的兄弟节点 |
| self | xpath('./self::*') | 选取当前节点 |
6、功能函数(模糊匹配)
使用功能函数能够更好的进行模糊搜索
| 函数 | 用法 | 解释 |
| starts-with | xpath('//div[starts-with(@id,"ma")]') | 选取id值以ma开头的div节点 |
| contains | xpath('//div[contains(@id,"ma")]') | 选取id值包含ma的div节点 |
| and | xpath('//div[contains(@id,"ma") and contains(@id,"in")]') | 选取id值包含ma和in的div节点 |
| text() | xpath('//div[contains(text(),"ma")]') | 选取节点文本包含ma的div节点 |
7.取属性和文本
| @ | //div[@class="tang"]//li[2]/a/@href | 取标签的的href属性值 |
| text() | //div[@class="song"]/p[1]/text() | 取p[1]的所有属性值 |
| text() | //div[@class="tang"]//text() | 取div标签的所有文本值,包括子标签的 |
8.通过text()确定标签
选取文本是'>'的a标签
xpath('//a[text()=">"]')
9.string(.)提取所有文本

html3 = '''
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="test3">
我左青龙,
<span id="tiger">
右白虎,
<ul>上朱雀,
<li>下玄武。</li>
</ul>
老牛在当中,
</span>
龙头在胸口。
</div>
</body>
</html>
'''
#如果使用一般的办法,就会出现获取到的数据不完整的情况
selector = lxml.html.fromstring(html3)
# content_1 = selector.xpath('//div[@id="test3"]/text()')
# for each in content_1:
# print(each)
# 使用string(.)就可以把数据获取完整
data = selector.xpath('//div[@id="test3"]')[0]
info = data.xpath('string(.)')
print(info)
#####或者使用这种情况
t=etree.HTML(html3)
res = t.xpath("//div[@id='test3']//text()")
print(res)

10.text_content()

最开始对html文本使用 etree.HTML(html)解析,得到Element对象。
from lxml import etree
str="""
<div>
<a href="xxxx">123</a>
<a href="xxxx">45</a>
<div>
"""
root= etree.HTML(str)
root.xpath("//div//text()")
#发现并没有直接获取12345文本方法
#后来网上调查发现lxml操作html有一个专门的html模块html,然后找到了解决该问题的关键方法text_content(),这个方法在上面的写法中是不存在的于是解决方案如下。
from lxml import html
root = html.fromstring('''<div><a href="xxxx">123</a><a href="xxxx">45</a><div>''')
root.xpath("//div").text_content()

三.使用流程

xpath数据解析流程:
1.pip install lxml
2.导包:from lxml import etree
3.实例化一个etree对象(将页面数据加载到该对象)
-
- 本地文件:tree = etree.parse(文件名) tree.xpath("xpath表达式") ===>本地文件指的是包打开的网页另存为到本地的文件
- 网络数据:tree = etree.HTML(网页内容字符串) tree.xpath("xpath表达式")
4.调用etree中的xpath函数结合着xpath表达式进行数据解析操作
补充:
# 使用lxml.etree.parse()解析html文件,该方法默认使用的是“XML”解析器,所以如果碰到不规范的html文件时就会解析错误
# lxml.etree.XMLSyntaxError: Opening and ending tag mismatch: meta line 3 and head, line 3, column 87
# 创建html解析器,增加parser参数
parser = etree.HTMLParser(encoding="utf-8")
tree = etree.parse('exe_file/xpath.html', parser=parser)

看一个例子(爬取58)

import requests
from lxml import etree
headers={
'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36 LBBROWSER'
}
url='https://sz.58.com/ershoufang/?PGTID=0d200001-0000-4229-cd25-6936558931c6&ClickID=1'
#拿到页面源码数据的text
page_text=requests.get(url=url,headers=headers).text
#把页面源码数据作为参数实例一个etree对象
tree=etree.HTML(page_text)
#用xpath进行解析
#xpath表达式的作用:xpath表达式如果作用到页面源码中,可以将页面源码中指定的标签进行定位.
#所有xpath解析返回值为一个列表
#可以对局部对象在进行xpath,但是路径要记得要变成./
li_list=tree.xpath('//ul[@class="house-list-wrap"]/li')
f=open('./xpath-58.txt','w',encoding='utf-8')
for li in li_list:
title=li.xpath('./div[2]/h2/a/text()')[0]#xpath返回的是一个列表,即使解析对象只有一个
total_price=li.xpath('./div[3]/p[1]//text()')#返回两个值直接返回
total_price=''.join(total_price)
f.write(title+':'+total_price+'\n')
f.close()
#这边只爬取了第一页,如想要爬取其他页面,只要去改一下页码参数,进行一个循环即可

主要几点:

1.获取文本
- a/text() 获取a下文本
- a//text() 获取a下面的所有文本
- a[text()='下一页']
2.@符号
- a/@href
- div[@class='detail']
- div[@id='content']
3.//
- 任意位置开始选择

四.一些高级用法
1.count:统计
tree.xpath('count(//li[@data])') #节点统计
2.concat:字符串连接
tree.xpath('concat(//li[@data="one"]/text(),//li[@data="three"]/text())')
3.string:解析当前节点下的字符
#string只能解析匹配到的第一个节点下的值,也就是作用于list时只匹配第一个
tree.xpath('string(//li)')
4. local-name:解析节点名称
tree.xpath('local-name(//*[@id="testid"])') #local-name解析节点名称
5.not 布尔值(否)
tree.xpath('count(//li[not(@data)])') #不包含data属性的li标签统计
#多用于表格中匹配中不包含表头信息的数据
xpath('//table/tr[not(@class="tbhead")]'
6.string-length:返回指定字符串的长度
#string-length函数+local-name函数定位节点名长度小于2的元素
tree.xpath('//*[string-length(local-name())<2]/text()')[0]
7.or:多条件匹配
tree.xpath('//li[@data="one" or @code="84"]/text()') #or匹配多个条件
tree.xpath('//li[@data="one"]/text() | //li[@code="84"]/text()') #|匹配多个条件
8.<:小于
tree.xpath('//li[@code<200]/text()')
9.div
tree.xpath('//div[@id="testid"]/ul/li[3]/@code div //div[@id="testid"]/ul/li[1]/@code')
10.根据节点下的某一节点数量定位
tree.xpath('//ul[count(li)>5]/li/text()')
11.将对象还原为字符串

>>> s = tree.xpath('//*[@id="testid"]')[0] #使用xpath定位一个节点
>>> s
<Element div at 0x2b6ffc8>
>>> s2 = etree.tostring(s).decode() #还原这个对象为html字符串
>>> s2
'<div id="testid">\n\t\t<h2>ÕâÀïÊǸöС±êÌâ</h2>\n\t\t<ol>\n\t\t\t<li data="one">1</li>\n\t\t\t<li data="two">2</li>\n\t\t\t<li data="three">3</li>\n\t\t</ol>\n\t\t<ul>\n\t\t\t<li code="84">84</li>\n\t\t\t<li code="104">104</li>\n\t\t\t<li code="223">223</li>\n\t\t</ul>\n\t</div>\n\t'

注:s2 = etree.tostring(s,encoding=‘utf-8’)
12. and
#匹配所有的tr中不包含 tbhead 属性 和包含 head 的tr标签
xpath('//table/tr[not(@class="tbhead") and @class="head"]')
13. or
匹配包含class="speedbar" 或者 class="content-wrap" 的标签 xpath('//div[@class="speedbar" or @class="content-wrap"]')
14.取货所有的属性
//div/@*



















