美文网首页
爬虫 - XPath

爬虫 - XPath

作者: Vector_Wan | 来源:发表于2019-08-17 10:42 被阅读0次

    哎,还是要整理一下这枯燥的基础知识,,,没办法,逃不掉喽

    我们回顾一下网页爬虫的整个思路:

    • 先爬取整个网页,也就是将网页的源代码给获取下来
    • 爬取下来的网页再通过文本解析提取,找到我们需要的信息,可以是图片或者文字

    之前我们学的 requests 库可以获取到我们想要的网页的 HTML 代码,我们待会儿要介绍的 lxml 库和 XPath 语法可以帮助我们完成信息的提取。

    实际上解析文档有很多种方式,比如最通用的 正则表达式方法,但是这种方法有时候写起来会比较困难,所以人们开发出了基于 XPath 语法、 CSS 选择器语法、和 jquery语法的解析库 ,包括 lxml、beautiful-soup、pyquery。

    我们这篇文章先来说一说 XPath 。

    Xpath 在使用的时候需要借助一个模型叫 etree,给人的感觉总像是 JavaScript 里面的 DOM ,他们确实有很多相似之处,比如都是树结构。

    在使用 XPath 解析文本之前,需要将文档先创建成一个 etree 对象。

    1. 建立 etree 的两种方式

    有两种建立 etree 对象的方式,一种是直接是 str 类型的导入,一种是写在文件里面了,需要把 html 文件导入。

    先看看第一种:

    字符串导入
    text = '''
    <div>
        <ul>
            <li class='item-0'><a href="link1.html">1 item</a></li>
            <li class='item-1'><a href="link1.html">2 item</a></li>
            <li class='item-inactive'><a href="link1.html">3 item</a></li>
            <li class='item-1'><a href="link1.html">4 item</a></li>
            <li class='item-4'><a href="link1.html">5 item</a>
        </ul>
    </div>
    '''# 我们删去了最后一个li 标签
    
    htmlcontent = etree.HTML(text)  # 通过文本构建 etree 实例
    result = etree.tostring(htmlcontent)  # 返回的是 bytes 类型,需要转化为 str 类型
    print(result.decode('utf-8'))  # 经过处理后的 html 代码会被自动修复 添加了 <html><body></li>
    

    看到上面的例子你应该就知道啥叫字符串的形式导入,这种方式是直接在内存中读取,我们爬下来的 response.text 就可以直接扔进去解析,

    下面我给大家隆重介绍一下导入三连;
    首先是 etree.HTML() 这个方法可以使用 str 创建一个 etree 实例,可以理解为一个树模型,这个方法好呀,可以自动修复 html 代码,然后添加 <html><body>

    照理儿说这个,应该就可以直接解析了,找我们想要的就完了,但是我们非得想要查看一下,导入了啥,不确定一下不痛快,ok,没关系
    etree.tostring() 参数是一个 etree 对象,顾名思义,to字符串,ok 返回 etree 对应的字符串,但但但,但是,还没完,返回了个二进制的 bytes 这哪行,写入文件还是可以的哈,正好帮我们修正完了,

    也没关系,我们使用 .decode('utf-8') 转化一下成为人类可读的语言就行了。
    最后结果:

    <html><body><div>
            <ul>
                <li class="item-0"><a href="link1.html">1 item</a></li>
                <li class="item-1"><a href="link1.html">2 item</a></li>
                <li class="item-inactive"><a href="link1.html">3 item</a></li>
                <li class="item-1"><a href="link1.html">4 item</a></li>
                <li class="item-4"><a href="link1.html">5 item</a>
            </li></ul>
        </div>
        </body></html>
    

    相对于之前确实代码被补全了。

    从文件导入

    其实真的跟上面的方法差不多,比如我们将上面的代码写到同目录下的 text.html 文件中,可以这样导入,

    # 通过读取 html 文件创建 etree 实例
    html = etree.parse('./text.html', etree.HTMLParser())
    result = etree.tostring(html)
    print(result.decode('utf-8'))
    

    注意这里面的 HTMLParser 它是一个 python 自带的网页解析工具,也就是说你使用它也是可以完成解析的,但是还是那句话比较麻烦,所以在他上面又封装了一层 etree 。

    结果稍微有点不同:

    <html><body><div>&#13;
        <ul>&#13;
            <li class="item-0">12345<a href="link1.html">1 item</a></li>&#13;
            <li class="item-1"><a href="link2.html">2 item</a></li>&#13;
            <li class="item-inactive"><a href="link3.html">3 item</a></li>&#13;
            <li class="item-1"><a href="link4.html">4 item</a></li>&#13;
            <li class="item-0"><a href="link5.html">5 item</a>&#13;
        </li></ul>&#13;
    </div></body></html>
    

    这里面的 &#13 是一个 placeholder 占位符,表示回车,问题不大。

    说完如何创建 etree 对象之后就开始我们今天的重头戏,XPath 语法,XPath 是一种称为路径表达式的语法,可以用一个类似于 Windows 或 Linux 文件路径的表达式,定位到 XML 或 HTML 中的任意一个或多个节点元素,获取元素的各项信息。

    XPath语法中有四个关键的概念:节点、轴、路径表达式和运算符。

    节点跟 DOM 中的差不多,我也不多说了,直接参考 W3S 上面的介绍吧
    https://www.w3school.com.cn/xpath/xpath_nodes.asp

    2. 路径表达式

    XPath 使用路径表达式来选取节点,最有用的路径表达式有:


    此外我们还有一些谓语使得匹配更加精准,它用来查找某个特定的节点或者包含某个指定的值的节点,它被放在了 [ ] 中。

    好的赶快上手练练:

        '''
        <div>
          <ul>
            <li class='item-0'><a href="link1.html"><span>1 item</a></li>
            <li class='item-1'><a href="link1.html">2 item</a></li>
            <li class='item-inactive'><a href="link1.html">3 item</a></li>
            <li class='item-1'><a href="link1.html">4 item</a></li>
            <li class='item-0'><a href="link1.html">5 item</a>
          </ul>
        </div>
        '''
        from lxml import etree
        html = etree.parse('./text.html', etree.HTMLParser())
        result = html.xpath("//*")
        print(result)
        # [<Element html at 0x1db8445a808>, <Element body at 0x1db8445a7c8>, <Element div at 0x1db8445a8c8>, <Element ul at 0x1db8445a908>, <Element li at 0x1db8445a948>, <Element a at 0x1db8445a9c8>, <Element li at 0x1db8445aa08>, <Element a at 0x1db8445aa48>, <Element li at 0x1db8445aa88>, <Element a at 0x1db8445a988>, <Element li at 0x1db8445aac8>, <Element a at 0x1db8445ab08>, <Element li at 0x1db8445ab48>, <Element a at 0x1db8445ab88>]
        result = html.xpath("//li/a")
        print(result)
        # [<Element a at 0x166d312f7c8>, <Element a at 0x166d312f848>, <Element a at 0x166d312f788>, <Element a at 0x166d312f908>, <Element a at 0x166d312f988>]
    

    我们一般都会使用 // 开头来选取所有满足条件的节点,* 代表匹配所有节点,那么所有的节点都会被获取到,并返回一个列表,
    匹配也可以直接指定节点的名称,比如上面例子的 li 返回所有 li 节点下的 a 节点,如果想获取所有的子孙节点可以这样写 //li//a

        '''
        <div>
          <ul>
            <li class='item-0'><a href="link1.html"><span>1 item</a></li>
            <li class='item-1'><a href="link1.html">2 item</a></li>
            <li class='item-inactive'><a href="link1.html">3 item</a></li>
            <li class='item-1'><a href="link1.html">4 item</a></li>
            <li class='item-0'><a href="link1.html">5 item</a>
          </ul>
        </div>
        '''
        from lxml import etree
        html = etree.parse('./text.html', etree.HTMLParser())
        result = html.xpath("//li/text()")
        print(result)
        # [ '\r\n\t']
        result = html.xpath("//li/a/text()")
        print(result)
        # ['1 item', '2 item', '3 item', '4 item', '5 item']
        result = html.xpath("//li/a/@href")
        print(result)
        # ['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html']
    

    在这个例子中我们使用了 text() 访问节点中的文字,但是只会访问当前节点中的文字,不会访问子孙节点的文字,如果想要访问子孙节点,可以使用 // 或者继续向下写,比如这里的 /a 如果想要指定是某个 a 标签可以 使用 【】 比如 【1】表示第一个 a 标签,如果想要获取属性值,可以使用 @ 比如这里的 href 属性。

    3. 运算符

    下表给出了一些常用的 Xpath 运算符

    运算符主要都是用在了 [ ] 中,

        '''
        <div>
          <ul>
            <li class='item-0 sr' name='li'><a href="link1.html"><span>1 item</a></li>
            <li class='item-1'><a href="link1.html">2 item</a></li>
            <li class='item-inactive'><a href="link1.html">3 item</a></li>
            <li class='item-1'><a href="link1.html">4 item</a></li>
            <li class='item-0'><a href="link1.html">5 item</a>
          </ul>
        </div>
        '''
        from lxml import etree
        html = etree.parse('./text.html', etree.HTMLParser())
        result = html.xpath("//li[@class='item-0']//text()")
        print(result)
        # ['5 item', '\r\n      ']
        result = html.xpath("//li[contains(@class,'item-0')]/a/text()")
        print(result)
        # ['1 item', '5 item']
        result = html.xpath("//li[contains(@class,'item-0')and @name='li']/a/@href")
        print(result)
        # ['link1.html']
    

    注意我们稍微修改了一下 html ,我们给第一个 li 标签的 class 属性新加了一个值 sr ,然后又加了一个 name 属性。
    所以第一个访问到的是最后一个 li 标签,因为它的 class 属性只有 item-0 ,因为后面使用的是 // 所以返回下面所有子孙节点的文字。
    第二个访问到的是是第一个和第五个 li 标签,因为他们的 class 属性都含有 item-0,返回所有a 标签里的文字,
    最后一个是返回 li 标签中class 属性包含 item-0 并且 name属性是 li 的 节点中的a 标签的 href 属性值。

    4. 节点轴选择

    XPath 提供了很多节点轴选择方法,包括获取子元素、兄弟元素、父元素、祖先元素等


        '''
        <div>
            <ul>
                <li class='item-0' title="li"><a href="link1.html"><span>1 item</a></li>
                <li class='item-1'><a href="link2.html">2 item</a></li>
                <li class='item-inactive'><a href="link3.html"> item</a></li>
                <li class='item-1'><a href="link4.html">4 item</a></li>
                <li class='item-0'><a href="link5.html">5 item</a>
            </ul>
        </div>
        '''
        from lxml import etree
        html = etree.parse('./text.html', etree.HTMLParser())
        result = html.xpath("//li[1]/ancestor::*")
        print(result)
        # [<Element html at 0x18f93eaf748>, <Element body at 0x18f93eaf848>, <Element div at 0x18f93eaf888>, <Element ul at 0x18f93eaf8c8>]
        result = html.xpath("//li[1]/ancestor::div")
        print(result)
        # [<Element div at 0x29d426bf7c8>]
        result = html.xpath("//li[1]/attribute::*")
        print(result)
        # ['item-0']
        result = html.xpath("//li[1]/child::a[@href='link1.html']")
        print(result)
        # [<Element a at 0x147bd72a8c8>]
        result = html.xpath("//li[1]/descendant::span")
        print(result)
        # [<Element span at 0x2238266a988>]
        result = html.xpath("//li[1]/attribute::*[2]")
        print(result)
        # ['my_li']
        result = html.xpath("//li[1]/following-sibling::*")
        print(result)
        # [<Element li at 0x1905588f708>, <Element li at 0x1905588f7c8>, <Element li at 0x1905588f808>, <Element li at 0x1905588f848>]
    

    这种东西就是多写写就会了,

    相关文章

      网友评论

          本文标题:爬虫 - XPath

          本文链接:https://www.haomeiwen.com/subject/jkfzjctx.html