新手Python入门之网络机器人
·
Xpath是什么?
XPath 是 XML/HTML 文档的查询语言,爬虫里经常用到,lxml、Scrapy、Selenium 都支持,常用来解析网页。
一、基本概念
XPath 把html文档看成一棵树,用路径定位节点。
<html>
<body>
<div class="box">
<p id="t1">hello</p>
</div>
</body>
</html>
- 元素:
div、p - 属性:
class="box"、id="t1" - 文本:
hello
二、路径表达式
| 符号 | 含义 | 例子 |
|---|---|---|
/ | 绝对路径,从根开始 | /html/body/div |
// | 任意位置的子孙节点 | //p |
. | 当前节点 | ./div |
.. | 父节点 | ../p |
@ | 属性 | //div[@class="box"] |
* | 通配,任意节点 | //div/* |
@* | 任意属性 | //div/@* |
选择元素
/html/body/div # 绝对路径
//p # 所有 p 元素
//div/p # div 下的直接子 p
//div//p # div 下的所有后代 p
选择属性
//a/@href # 所有 a 的 href 属性
//div/@class # 所有 div 的 class
选择文本
//p/text() # p 的直接文本
//div//text() # div 下所有文本
三、谓词(条件)
用 [] 加条件筛选节点。
按位置
//p[1] # 每个父节点下第一个 p
//p[last()] # 最后一个 p
//p[position()<3] # 前两个 p(position()函数意味着第几个位置,<3则说明有1,2)
//p[last()-1] # 倒数第二个
注意://p[1] 是"每个父节点下的第一个 p",不是"整个文档第一个 p"。
要拿整个文档第一个,用 (//p)[1]。注意优先级
按属性
//p[@id] # 有 id 属性的 p
//div[@class="box"] # 所有class 等于 box 的 div
//a[@href="http://x.com"] # href 精确匹配
按文本
//p[text()="hello"] # 文本精确等于 hello
//p[contains(text(),"ell")] # 文本包含 ell
//p[starts-with(text(),"he")] # 文本以 he 开头
组合条件
//div[@class="box"][@id="main"] # 两个属性都满足等价and
//p[@id="a" or @id="b"] # 满足一个条件就行
//p[@id and @class] # 同一
四、通配符
| 写法 | 含义 |
|---|---|
* | 任意元素 |
@* | 任意属性 |
node() | 任意节点(元素、文本、注释) |
text() | 文本节点 |
//div/* # div 下所有子元素
//div/@* # div 的所有属性
//div/node() # div 下所有节点
五、轴(axes)
轴用来沿某个方向选择节点。
| 轴 | 含义 |
|---|---|
child:: | 子节点(默认轴) |
parent:: | 父节点 |
ancestor:: | 所有祖先 |
descendant:: | 所有后代 |
following-sibling:: | 后面的兄弟 |
preceding-sibling:: | 前面的兄弟 |
following:: | 文档中之后的所有节点 |
preceding:: | 文档中之前的所有节点 |
self:: | 自身 |
attribute:: | 属性 |
//p/ancestor::div # p 的所有祖先 div
//p/following-sibling::p # p 后面的兄弟 p
//div/descendant::a # div 下所有后代 a
//p/parent::div # p 的父 div
六、运算符
| 运算符 | 含义 | 例子 |
|---|---|---|
+ - * div mod | 算术 | //p[position() mod 2 = 1] |
= != | 相等 / 不等 | //p[@id="a"] |
< <= > >= | 比较 | //p[count(*) > 2] |
and | 与 | //p[@id and @class] |
or | 或 | //p[@id="a" or @id="b"] |
| ` | ` | 并集 |
注意 div 既可以是"除法"运算符,也可以是标签名,看上下文。
因为/要作为路径,所以div是除法,同理mod就是取余啦。
七、常用函数
字符串函数
contains(s, t) # s 是否包含 t
starts-with(s, t) # s 是否以 t 开头
concat(s1, s2, ...) # 拼接
string-length(s) # 长度
normalize-space(s) # 去首尾空白 + 合并中间空格
substring(s, start, len) # 截取(从 1 开始)
位置函数
position() # 当前位置(从 1 开始)
last() # 最后一个位置
count(nodeset) # 节点个数
数值函数
number(x) # 转数字
sum(nodeset) # 求和
floor(x) / ceiling(x) # 取整
round(x) # 四舍五入
逻辑函数
not(expr) # 取反
true() / false() # 布尔值
示例
//a[contains(@href, "login")] # href 包含 login 的 a
//p[normalize-space(text())="hello"] # 去空白后匹配文本
//div[count(p) > 3] # 有 3 个以上 p 的 div
//p[not(@class)] # 没有 class 属性的 p
八、实战示例
假设 HTML 如下:
<div class="list">
<ul>
<li class="item"><a href="/a">A</a></li>
<li class="item"><a href="/b">B</a></li>
<li class="item"><a href="/c">C</a></li>
</ul>
</div>
常用定位
//div[@class="list"]//a # 所有链接
//div[@class="list"]//a/@href # 所有链接的 href
//li[2]/a/text() # 第二个 li 的文本
//a[contains(@href, "/b")] # href 包含 /b 的 a
//li[last()] # 最后一个 li
//li[last()-1] # 倒数第二个
//div[@class="list"]//text() # 所有文本
//li/parent::ul # li 的父节点
九、lxml 中的写法
Python 里用 lxml:
from lxml import etree
html = '''
<div class="list">
<ul>
<li class="item"><a href="/a">A</a></li>
<li class="item"><a href="/b">B</a></li>
</ul>
</div>
'''
tree = etree.HTML(html)
# 取 href
hrefs = tree.xpath('//div[@class="list"]//a/@href')
print(hrefs) # ['/a', '/b']
# 取文本
texts = tree.xpath('//li/a/text()')
print(texts) # ['A', 'B']
# 取节点对象
nodes = tree.xpath('//li')
for node in nodes:
print(node.xpath('./a/text()')) # 相对路径
几点注意:
- 返回的列表可能是字符串(
@href、text())或节点对象 - 用
./表示相对当前节点 //从整个文档开始找,不依赖上下文- 文本里如果有 HTML 实体,用
etree.tostring(node, encoding='unicode')还原
十、常见坑
1. //p[1] 和 (//p)[1] 的区别
//p[1] # 每个父节点下的第一个 p
(//p)[1] # 整个文档第一个 p
2. 文本不在 text() 里的情况
<p>hello <b>world</b></p>
//p/text()→['hello '](不含子元素里的文本)//p//text()→['hello ', 'world']
要取全部文本,可以用:
''.join(node.xpath('.//text()'))
3. 属性值含空格
//div[@class="box green"] # 精确匹配整个 class 字符串
//div[contains(@class, "box")] # class 里含 box
4. @href 取不到时返回空列表
tree.xpath('//a[@href="x"]/@href') # 没匹配可以返回 []
别假设一定有一条,要判空。
5. lxml 会自动补全 html/body
tree.xpath('/html/body/div') # 可能不是你想的那样
tree.xpath('//div') # 用 // 更稳妥
十一、速查表
| 需求 | 写法 |
|---|---|
| 所有 p | //p |
| 直接子元素 | //div/p |
| 所有后代 | //div//p |
| 属性 | //a/@href |
| 文本 | //p/text() |
| 第一个 | (//p)[1] |
| 最后一个 | (//p)[last()] |
| 按属性筛选 | //div[@class="box"] |
| 属性包含 | //a[contains(@href, "login")] |
| 文本包含 | //p[contains(text(), "hello")] |
| 多条件 | //div[@id and @class] |
| 父节点 | //p/.. |
| 后面的兄弟 | //p/following-sibling::p |
| 节点数量 | count(//p) |
十二、总结一下
XPath 的核心就几样:
- 路径:
/绝对、//任意、.当前、..父 - 谓词:
[]里写条件 - 属性:
@、文本text() - 函数:
contains、starts-with、normalize-space、count
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)