詹德江 發表於 2020-10-7 11:08:00

python使用xpath(超详细)

<blockquote>
<p>使用时先安装 lxml包</p>
</blockquote>
<h2 id="开始使用">开始使用</h2>
<p>和beautifulsoup类似,首先我们需要得到一个文档树</p>
<ul>
<li>把文本转换成一个文档树对象</li>
</ul>
<pre><code>from lxml import etree
if __name__ == '__main__':
    doc='''
      &lt;div&gt;
            &lt;ul&gt;
               &lt;li class="item-0"&gt;&lt;a href="link1.html"&gt;first item&lt;/a&gt;&lt;/li&gt;
               &lt;li class="item-1"&gt;&lt;a href="link2.html"&gt;second item&lt;/a&gt;&lt;/li&gt;
               &lt;li class="item-inactive"&gt;&lt;a href="link3.html"&gt;third item&lt;/a&gt;&lt;/li&gt;
               &lt;li class="item-1"&gt;&lt;a href="link4.html"&gt;fourth item&lt;/a&gt;&lt;/li&gt;
               &lt;li class="item-0"&gt;&lt;a href="link5.html"&gt;fifth item&lt;/a&gt; # 注意,此处缺少一个 &lt;/li&gt; 闭合标签
             &lt;/ul&gt;
         &lt;/div&gt;
      '''

    html = etree.HTML(doc)
    result = etree.tostring(html)
    print(str(result,'utf-8'))
</code></pre>
<ul>
<li>把文件转换成一个文档树对象</li>
</ul>
<pre><code>from lxml import etree

# 读取外部文件 index.html
html = etree.parse('./index.html')
result = etree.tostring(html, pretty_print=True)    #pretty_print=True 会格式化输出
print(result)
</code></pre>
<blockquote>
<p>均会打印出文档内容</p>
</blockquote>
<h2 id="节点元素属性内容">节点、元素、属性、内容</h2>
<p><strong>xpath 的思想是通过 路径表达 去寻找节点。节点包括<code>元素</code>,<code>属性</code>,和<code>内容</code></strong></p>
<ul>
<li>元素举例</li>
</ul>
<pre><code>html ---&gt; &lt;html&gt; ...&lt;/html&gt;
div ---&gt; &lt;div&gt; ...&lt;/div&gt;
a---&gt; &lt;a&gt; ...&lt;/a&gt;
</code></pre>
<p>这里我们可以看到,这里的<code>元素</code>和html中的<code>标签</code>一个意思。单独的元素是无法表达一个路径的,所以单独的元素不能独立使用</p>
<h2 id="路径表达式">路径表达式</h2>
<pre><code class="language-xpath">    /   根节点,节点分隔符,
    //任意位置
    .   当前节点
    ..父级节点
    @   属性
</code></pre>
<h2 id="通配符">通配符</h2>
<pre><code class="language-xpath">    *   任意元素
    @*任意属性
    node()任意子节点(元素,属性,内容)
</code></pre>
<h2 id="谓语">谓语</h2>
<p><strong>使用中括号来限定元素,称为谓语</strong></p>
<pre><code class="language-xpath">    //a n为大于零的整数,代表子元素排在第n个位置的&lt;a&gt;元素
    //a   last()代表子元素排在最后个位置的&lt;a&gt;元素
    //a和上面同理,代表倒数第二个
    //a 位置序号小于3,也就是前两个,这里我们可以看出xpath中的序列是从1开始
    //a[@href]    拥有href的&lt;a&gt;元素
    //a[@href='www.baidu.com']    href属性值为'www.baidu.com'的&lt;a&gt;元素
    //book[@price&gt;2]   price值大于2的&lt;book&gt;元素
</code></pre>
<h2 id="多个路径">多个路径</h2>
<p>用<code>|</code> 连接两个表达式,可以进行 <code>或</code>匹配</p>
<pre><code>//book/title | //book/price
</code></pre>
<h2 id="函数">函数</h2>
<p><strong>xpath内置很多函数。更多函数查看https://www.w3school.com.cn/xpath/xpath_functions.asp</strong></p>
<ul>
<li>contains(string1,string2)</li>
<li>starts-with(string1,string2)</li>
<li>ends-with(string1,string2)    #不支持</li>
<li>upper-case(string)    #不支持</li>
<li>text()</li>
<li>last()</li>
<li>position()</li>
<li>node()</li>
</ul>
<p><strong>可以看到last()也是个函数,在前面我们在谓语中已经提到过了</strong></p>
<h2 id="案例">案例</h2>
<h3 id="定位元素">定位元素</h3>
<p><strong>匹配多个元素,返回列表</strong></p>
<pre><code class="language-python">from lxml import etree

if __name__ == '__main__':
    doc='''
      &lt;div&gt;
            &lt;ul&gt;
               &lt;li class="item-0"&gt;&lt;a href="link1.html"&gt;first item&lt;/a&gt;&lt;/li&gt;
               &lt;li class="item-1"&gt;&lt;a href="link2.html"&gt;second item&lt;/a&gt;&lt;/li&gt;
               &lt;li class="item-inactive"&gt;&lt;a href="link3.html"&gt;third item&lt;/a&gt;&lt;/li&gt;
               &lt;li class="item-1"&gt;&lt;a href="link4.html"&gt;fourth item&lt;/a&gt;&lt;/li&gt;
               &lt;li class="item-0"&gt;&lt;a href="link5.html"&gt;fifth item&lt;/a&gt; # 注意,此处缺少一个 &lt;/li&gt; 闭合标签
             &lt;/ul&gt;
         &lt;/div&gt;
      '''

    html = etree.HTML(doc)
    print(html.xpath("//li"))
    print(html.xpath("//p"))
</code></pre>
<p><strong>【结果为】</strong></p>
<pre><code>[&lt;Element li at 0x2b41b749848&gt;, &lt;Element li at 0x2b41b749808&gt;, &lt;Element li at 0x2b41b749908&gt;, &lt;Element li at 0x2b41b749948&gt;, &lt;Element li at 0x2b41b749988&gt;]
[]#没找到p元素
</code></pre>
<pre><code>html = etree.HTML(doc)
print(etree.tostring(html.xpath("//li[@class='item-inactive']")))
print(html.xpath("//li[@class='item-inactive']").text)
print(html.xpath("//li[@class='item-inactive']/a").text)
print(html.xpath("//li[@class='item-inactive']/a/text()"))
print(html.xpath("//li[@class='item-inactive']/.."))
print(html.xpath("//li[@class='item-inactive']/../li[@class='item-0']"))
</code></pre>
<p><strong>【结果为】</strong></p>
<pre><code>b'&lt;li class="item-inactive"&gt;&lt;a href="link3.html"&gt;third item&lt;/a&gt;&lt;/li&gt;\n               '
None    #因为第三个li下面没有直接text,None
third item#
['third item']
[&lt;Element ul at 0x19cd8c4c848&gt;]
[&lt;Element li at 0x15ea3c5b848&gt;, &lt;Element li at 0x15ea3c5b6c8&gt;]
</code></pre>
<h3 id="使用函数">使用函数</h3>
<h4 id="contains">contains</h4>
<p>有的时候,class作为选择条件的时候不合适<code>@class='....'</code> 这个是完全匹配,当网页样式发生变化时,class或许会增加或减少像<code>active</code>的<code>class</code>。用contains就能很方便</p>
<pre><code>from lxml import etree
if __name__ == '__main__':
    doc='''
      &lt;div&gt;
            &lt;ul&gt;
               &lt;p class="item-0 active"&gt;&lt;a href="link1.html"&gt;first item&lt;/a&gt;&lt;/p&gt;
               &lt;li class="item-1"&gt;&lt;a href="link2.html"&gt;second item&lt;/a&gt;&lt;/li&gt;
               &lt;li class="item-inactive"&gt;&lt;a href="link3.html"&gt;third item&lt;/a&gt;&lt;/li&gt;
               &lt;li class="item-1"&gt;&lt;a href="link4.html"&gt;fourth item&lt;/a&gt;&lt;/li&gt;
               &lt;li class="item-0"&gt;&lt;a href="link5.html"&gt;fifth item&lt;/a&gt; # 注意,此处缺少一个 &lt;/li&gt; 闭合标签
             &lt;/ul&gt;
         &lt;/div&gt;
      '''

    html = etree.HTML(doc)
    print(html.xpath("//*"))
</code></pre>
<p><strong>【结果为】</strong></p>
<pre><code>[&lt;Element p at 0x23f4a9d12c8&gt;, &lt;Element li at 0x23f4a9d13c8&gt;, &lt;Element li at 0x23f4a9d1408&gt;, &lt;Element li at 0x23f4a9d1448&gt;, &lt;Element li at 0x23f4a9d1488&gt;]
</code></pre>
<h4 id="starts-with">starts-with</h4>
<pre><code>
from lxml import etree
if __name__ == '__main__':
    doc='''
      &lt;div&gt;
            &lt;ul class='ul items'&gt;
               &lt;p class="item-0 active"&gt;&lt;a href="link1.html"&gt;first item&lt;/a&gt;&lt;/p&gt;
               &lt;li class="item-1"&gt;&lt;a href="link2.html"&gt;second item&lt;/a&gt;&lt;/li&gt;
               &lt;li class="item-inactive"&gt;&lt;a href="link3.html"&gt;third item&lt;/a&gt;&lt;/li&gt;
               &lt;li class="item-1"&gt;&lt;a href="link4.html"&gt;fourth item&lt;/a&gt;&lt;/li&gt;
               &lt;li class="item-0"&gt;&lt;a href="link5.html"&gt;fifth item&lt;/a&gt; # 注意,此处缺少一个 &lt;/li&gt; 闭合标签
             &lt;/ul&gt;
         &lt;/div&gt;
      '''

    html = etree.HTML(doc)
    print(html.xpath("//*"))
    print(html.xpath("//*"))
</code></pre>
<p><strong>【结果为】</strong></p>
<pre><code>[&lt;Element ul at 0x23384e51148&gt;, &lt;Element p at 0x23384e51248&gt;, &lt;Element li at 0x23384e51288&gt;, &lt;Element li at 0x23384e512c8&gt;, &lt;Element li at 0x23384e51308&gt;, &lt;Element li at 0x23384e51388&gt;]
[&lt;Element ul at 0x23384e51148&gt;]
</code></pre>
<h4 id="ends-with">ends-with</h4>
<pre><code>print(html.xpath("//*"))
</code></pre>
<p><strong>【结果为】</strong></p>
<pre><code>Traceback (most recent call last):
File "F:/OneDrive/pprojects/shoes-show-spider/test/xp5_test.py", line 18, in &lt;module&gt;
    print(html.xpath("//*"))
File "src\lxml\etree.pyx", line 1582, in lxml.etree._Element.xpath
File "src\lxml\xpath.pxi", line 305, in lxml.etree.XPathElementEvaluator.__call__
File "src\lxml\xpath.pxi", line 225, in lxml.etree._XPathEvaluatorBase._handle_result
lxml.etree.XPathEvalError: Unregistered function
</code></pre>
<p><strong>看来python的lxml并不支持有的xpath函数列表</strong></p>
<h4 id="upper-case">upper-case</h4>
<p><strong>和ends-with函数一样,也不支持。同样报错<code>lxml.etree.XPathEvalError: Unregistered function</code></strong></p>
<pre><code>print(html.xpath("//a"))
</code></pre>
<h4 id="textlast">text、last</h4>
<pre><code>#最后一个li被限定了
print(html.xpath("//li/a/text()"))

#会得到所有的`&lt;a&gt;`元素的内容,因为每个&lt;a&gt;标签都是各自父元素的最后一个元素。
#本来每个li就只有一个&lt;a&gt;子元素,所以都是最后一个
print(html.xpath("//li/a/text()"))

print(html.xpath("//li/a"))
</code></pre>
<p><strong>【结果为】</strong></p>
<pre><code>['fifth item']
['second item', 'third item', 'fourth item', 'fifth item']
[&lt;Element a at 0x26ab7bd1308&gt;]
</code></pre>
<h4 id="position">position</h4>
<pre><code>print(html.xpath("//li/a/text()"))
#结果为['third item']
</code></pre>
<p><strong>上面这个例子我们之前以及讲解过了</strong><br>
*<em>这里有个疑问,就是<code>position()</code>函数能不能像<code>text()</code>那样用呢</em></p>
<pre><code>print(html.xpath("//li/a/position()"))
#结果lxml.etree.XPathEvalError: Unregistered function
</code></pre>
<p><em>这里我们得到一个结论,函数不是随意放在哪里都能得到自己想要的结果</em></p>
<h4 id="node">node</h4>
<p><strong>返回所有子节点,不管这个子节点是什么类型(熟悉,元素,内容)</strong></p>
<pre><code>print(html.xpath("//ul/li[@class='item-inactive']/node()"))
print(html.xpath("//ul/node()"))
</code></pre>
<p><strong>【结果为】</strong></p>
<pre><code>[&lt;Element a at 0x239a0d197c8&gt;]
['\n               ', &lt;Element li at 0x239a0d19788&gt;, '\n               ', &lt;Element li at 0x239a0d19888&gt;, '\n               ', &lt;Element li at 0x239a0d19908&gt;, '\n               ', &lt;Element li at 0x239a0d19948&gt;, '\n               ', &lt;Element li at 0x239a0d198c8&gt;, ' 闭合标签\n             ']
</code></pre>
<h3 id="获取内容">获取内容</h3>
<p>刚刚已经提到过,可以使用<code>.text</code>和<code>text()</code>的方式来获取元素的内容</p>
<pre><code>

from lxml import etree
if __name__ == '__main__':
    doc='''
      &lt;div&gt;
            &lt;ul class='ul items'&gt;
               &lt;li class="item-0 active"&gt;&lt;a href="link1.html"&gt;first item&lt;/a&gt;&lt;/li&gt;
               &lt;li class="item-1"&gt;&lt;a href="link2.html"&gt;second item&lt;/a&gt;&lt;/li&gt;
               &lt;li class="item-inactive"&gt;&lt;a href="link3.html"&gt;third item&lt;/a&gt;&lt;/li&gt;
               &lt;li class="item-1"&gt;&lt;a href="link4.html"&gt;fourth item&lt;/a&gt;&lt;/li&gt;
               &lt;li class="item-0"&gt;&lt;a href="link5.html"&gt;fifth item&lt;/a&gt; # 注意,此处缺少一个 &lt;/li&gt; 闭合标签
             &lt;/ul&gt;
         &lt;/div&gt;
      '''
    html = etree.XML(doc)
    print(html.xpath("//a/text()"))
    print(html.xpath("//a").text)
    print(html.xpath("//ul").text)
    print(len(html.xpath("//ul").text))
    print(html.xpath("//ul/text()"))
</code></pre>
<p><strong>【结果为】</strong></p>
<pre><code>['first item', 'second item', 'third item', 'fourth item', 'fifth item']
first item

               
18
['\n               ', '\n               ', '\n               ', '\n               ', '\n               ', ' 闭合标签\n             ']
</code></pre>
<p><strong>看到这里,我们观察到<code>text()</code>和<code>.text</code>的区别。自己总结吧。不太好表达,就不表达了</strong></p>
<h3 id="获取属性">获取属性</h3>
<pre><code>print(html.xpath("//a/@href"))
print(html.xpath("//li/@class"))
</code></pre>
<p><strong>【结果为】</strong></p>
<pre><code>['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html']
['item-0 active', 'item-1', 'item-inactive', 'item-1', 'item-0']
</code></pre>
<h2 id="自定义函数">自定义函数</h2>
<p>我们从使用函数的过程中得到结论,就是有的函数不支持,有的支持,那问题来了,到底哪些函数支持呢。我们在lxml官网找到了答案。https://lxml.de/xpathxslt.html。lxml 支持XPath 1.0,想使用其他扩展,使用libxml2,和libxslt的标准兼容的方式。XPath 1.0官方文档以及其他版本的XPath文档 https://www.w3.org/TR/xpath/</p>
<pre><code>lxml supports XPath 1.0, XSLT 1.0 and the EXSLT extensions through libxml2 and libxslt in a standards compliant way.
</code></pre>
<p>除此之外,lxml还提供了自定义函数的方式来扩展xpath的支持度 https://lxml.de/extensions.html</p>
<pre><code>
from lxml import etree

#定义函数
def ends_with(context,s1,s2):
    return s1.endswith(s2)
if __name__ == '__main__':
    doc='''
      &lt;div&gt;
            &lt;ul class='ul items'&gt;
               &lt;li class="item-0 active"&gt;&lt;a href="link1.html"&gt;first item&lt;/a&gt;&lt;/li&gt;
               &lt;li class="item-1"&gt;&lt;a href="link2.html"&gt;second item&lt;/a&gt;&lt;/li&gt;
               &lt;li class="item-inactive"&gt;&lt;a href="link3.html"&gt;third item&lt;/a&gt;&lt;/li&gt;
               &lt;li class="item-1"&gt;&lt;a href="link4.html"&gt;fourth item&lt;/a&gt;&lt;/li&gt;
               &lt;li class="item-0"&gt;&lt;a href="link5.html"&gt;fifth item&lt;/a&gt; # 注意,此处缺少一个 &lt;/li&gt; 闭合标签
             &lt;/ul&gt;
         &lt;/div&gt;
      '''
    html = etree.XML(doc)
    ns = etree.FunctionNamespace(None)
    ns['ends-with'] = ends_with #将ends_with方法注册到方法命名空间中
    print(html.xpath("//li"))
    print(html.xpath("//li/a/text()"))
</code></pre>
<p><strong>【结果为】</strong></p>
<pre><code>[&lt;Element li at 0x2816ed30548&gt;, &lt;Element li at 0x2816ed30508&gt;]
['first item', 'third item']
</code></pre>
<ul>
<li><strong>形参<code>s1</code>会传入xpath中的第一个参数<code>@class</code>,但这里注意@class是个列表</strong></li>
<li><strong>形参<code>s2</code>会传入xpath中的第二个参数<code>'active'</code>,<code>'active'</code>是个字符串</strong></li>
</ul>
<p>官网例子https://lxml.de/extensions.html</p>
<pre><code>def hello(context, a):
    return "Hello %s" % a

from lxml import etree
ns = etree.FunctionNamespace(None)
ns['hello'] = hello
root = etree.XML('&lt;a&gt;&lt;b&gt;Haegar&lt;/b&gt;&lt;/a&gt;')
print(root.xpath("hello('Dr. Falken')"))
# 结果为 Hello Dr. Falken
</code></pre>
<h2 id="xpath10支持的函数列表">xpath1.0支持的函数列表</h2>
<ul>
<li>number last() 谓语中返回在兄弟元素中排在最末尾的</li>
<li>numberposition() 谓语中,返回该元素在兄弟中的排名</li>
<li>number count(node-set) 返回子节点的个数,<code>node-set</code>代表子节点的表达式</li>
</ul>
<pre><code class="language-xpath">//*    属性只有一个的元素(因为元素的属性本身就是子节点)
//*    任意一种属性如果为1,就满足
//* 子元素中有一个`&lt;a&gt;`的元素
</code></pre>
<ul>
<li>node-set id("foo") 返回唯一id为foo的元素。尝试了多次,未尝试出来,这里的唯一id是基于xml的,有下面这样一句话。所以确实没测试出来</li>
</ul>
<pre><code>If a document does not have a DTD, then no element in the document will have a unique ID.
</code></pre>
<ul>
<li>
<p>string local-name(node-set?)</p>
</li>
<li>
<p>string name(node-set?)</p>
</li>
<li>
<p>string namespace-uri(node-set?)</p>
</li>
<li>
<p>stringconcat(string, string, string*)</p>
</li>
<li>
<p>boolean starts-with(string, string)</p>
</li>
<li>
<p>boolean contains(string, string)</p>
</li>
<li>
<p>string substring-before(string, string)</p>
</li>
<li>
<p>string substring-after(string, string)</p>
</li>
<li>
<p>string substring(string, number, number?)</p>
</li>
<li>
<p>number string-length(string?)</p>
</li>
<li>
<p>string normalize-space(string?)</p>
</li>
<li>
<p>number sum(node-set)</p>
</li>
<li>
<p>number floor(number)</p>
</li>
<li>
<p>number ceiling(number)</p>
</li>
<li>
<p>number round(number)</p>
</li>
</ul>
<h2 id="xpath使用工具">xpath使用工具</h2>
<h3 id="chome生成xpath表达式">chome生成xpath表达式</h3>
<p>经常使用chome的小伙伴的都应该知道这个功能,在 <code>审查</code> 状态下(快捷键ctrl+shift+i,F12),定位到元素(快捷键ctrl+shift+c) ,在Elements选项卡中,右键元素Copy-&gt;Copy xpath,就能得到该元素的xpath了<br>
<img src="https://img2020.cnblogs.com/blog/587830/202010/587830-20201008163455880-637000496.png" alt="" loading="lazy"></p>
<h3 id="xpath-helper插件">XPath Helper插件</h3>
<p>为chome装上XPath Helper就可以很轻松的检验自己的xpath是否正确了。安装插件需要kxsw(使用lanternFQ,或者Astar VPN),安装好插件后,在chrome右上角点插件的图标,调出插件的黑色界面,编辑好xpath表达式,表达式选中的元素被标记为黄色<br>
<img src="https://img2020.cnblogs.com/blog/587830/202010/587830-20201008164444409-405038214.png" alt="" loading="lazy"></p><br><br>
来源:https://www.cnblogs.com/mxjhaima/p/13775844.html
頁: [1]
查看完整版本: python使用xpath(超详细)