背景

最近因为工作需要写爬虫,以前用过BeautifulSoup,所以很自然的无脑上BeautifulSoup了,不过使用过程中发现BeautifulSoup有一个致命的缺陷,就是不能支持XPath。XPath可以快速在结构化的文档(如XML,HTML)中查找、访问元素的语言,语法比正则表达式还要简单,非常容易使用。

在浏览器中其中可以方便地获取任何目标元素的XPath,简单来说XPath和文件路径很像,通过文件路径可以快速定位文件,通过XPath可以快速定位网页中的元素。这里网页和元素的关系类似文件系统和文件的关系。当然XPath提供了更多的能力。下面就是本文的重点——lxml了。lxml提供了很多功能强大的子库,本文主要用到xlml.etree(支持XPath!支持XPath!支持XPath!,重要的事说三遍)。

先看一个简单的小例子

#!env python3
from urllib.request import urlopen
from lxml import etree

sHtml = """
    <!DOCTYPE html>
    <html>
        <head>
            <title>Hello World</title>
        </head>
        <body>
            <div>
                <p>This is the first paragraph</p>
            </div>
            <div>
                <p>This is the second paragraph</p>
                <p>This is the third paragraph</p>
            </div>
        </body>
    </html>
"""

#解析html
eleRoot = etree.HTML(sHtml)
#使用xpath获取元素
listP = eleRoot.xpath("/html/body/div/p")
#打印title信息
for eleP in listP:
    print(eleP.text)

运行结果如下:

This is the first paragraph
This is the second paragraph
This is the third paragraph

eleRoot是lxml.etree._Element类型,该类型提供了众多接口: |方法或者属性|功能| |—-|—-| |_Element.text|获取标签的文本| |_Element.tag |获取标签名字(如head,div)| |_Element.xpath(path)|根据XPath获取元素对象|

详情参照官方文档

xpath()返回的是一个列表,列表中包含所有符合XPath的元素。上面的例子给定的XPath是”/html/body/div/p”。

可以看到两个div的共3个p标签都符合这个路径,所以最后返回了三个p标签元素,如果想获得指定的标签,可以在给定XPath的时候添加索引,比如上图想获得second paragraph,那么path应该为”/html/body/div[2]/p[1]“,没错XPath的索引是从1开始的,而不是0。这样返回的是一个只有一个_Element元素的列表。

xpath()除了可以指定绝对路径(以”/“开头的路径),还可以在元素上使用相对路径。修改上面的代码中获取和打印元素的代码为如下:

#获取body元素,然后通过相对路径访问子元素
eleBody = eleRoot.xpath("/html/body")[0]
eleP = eleBody.xpath("div[2]/p[1]")[0]
print(eleP.text)

首先获取body元素,然后通过xpath获取body元素的第二个div的第一个p,运行结果如下:

This is the second paragraph

调用etree.HTML()获得的元素为根元素,对根元素只能使用绝对路径,不能使用相对路径,其他元素均可以使用相对路径调用xpath()。

如何快速获取网页中元素的XPath

在Chrome浏览器中(其他浏览器应该也都支持这个功能),鼠标放在目标元素的上面右键 -> 点击”检查元素“,就会跳转到指定元素的标签代码上了,对选中的标签右键 -> 点击Copy -> 点击”Copy full XPath”,即可获得该元素的XPath了。

XPath语法示例

注意:返回的是etree._Element的list,一个元素也会以list的格式返回

获得所有xxx元素

eleRoot.xpath("//xxx")

获得/html/body/div的所有子元素

eleRoot.xpath("html/body/div/*")

获取class为yyy的标签

eleRoot.xpath("//*[contains(@class, "yyy")]"

获取属性zzz的值为yyy的标签

eleRoot.xpath("//*[contains(@zzz, "yyy")])

如果解析中文网页出现乱码乱码的问题

请看移步另一篇文章 —— 使用lxml.etree解析中文网页时出现乱码问题的解决办法