欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

爬虫 结构化 数据 非结构化数据 XPath lxml 类库

程序员文章站 2022-07-14 11:33:08
...

背景

使用正则表达式比较费劲 可以使用xpath
先将 HTML文件 转换成 XML文档,然后用 XPath 查找 HTML 节点或元素。

什么是XPath?

XPath (XML Path Language) 是一门在 XML 文档中查找信息的语言,可用来在 XML 文档中对元素和属性进行遍历。

W3School官方文档:http://www.w3school.com.cn/xpath/index.asp

XPath 开发工具

开源的XPath表达式编辑工具:XMLQuire(XML格式文件可用)
Chrome插件 XPath Helper
Firefox插件 XPath Checker

安装lxml

python3.6 中安装 会安装比较新的lxml lxml-4.2.1 版本的, 里面没有etree版本
使用3.6 指定版本
直接使用 pip install lxml 就ok

现在使用好像是有的啦, 好哦哦奇怪

开始学习xpath

选取节点

XPath 使用路径表达式来选取 XML 文档中的节点或者节点集。这些路径表达式和我们在常规的电脑文件系统中看到的表达式非常相似。

下面列出了最常用的路径表达式:
爬虫 结构化 数据 非结构化数据 XPath lxml 类库

在下面的表格中,我们已列出了一些路径表达式以及表达式的结果:
爬虫 结构化 数据 非结构化数据 XPath lxml 类库

外加说明:
bookstore/book 是找bookstore 的所有为book 的子节点 必须是儿子
bookstore//book 查找的是 bookstore下所有为book的节点

谓语(Predicates)

谓语用来查找某个特定的节点或者包含某个指定的值的节点,被嵌在方括号中。

在下面的表格中,我们列出了带有谓语的一些路径表达式,以及表达式的结果:
爬虫 结构化 数据 非结构化数据 XPath lxml 类库

谓语 都在方括号中

选取未知节点

XPath 通配符可用来选取未知的 XML 元素。
爬虫 结构化 数据 非结构化数据 XPath lxml 类库

在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:

爬虫 结构化 数据 非结构化数据 XPath lxml 类库

选取若干路径

通过在路径表达式中使用“|”运算符,您可以选取若干个路径。

实例

在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:
爬虫 结构化 数据 非结构化数据 XPath lxml 类库

XPath的运算符
下面列出了可用在 XPath 表达式中的运算符:
爬虫 结构化 数据 非结构化数据 XPath lxml 类库

这些就是XPath的语法内容,在运用到Python抓取时要先转换为xml。

lxml库

lxml 是 一个HTML/XML的解析器,主要的功能是如何解析和提取 HTML/XML 数据。

lxml和正则一样,也是用 C 实现的,是一款高性能的 Python HTML/XML 解析器,我们可以利用之前学习的XPath语法,来快速的定位特定元素以及节点信息。

lxml python 官方文档:http://lxml.de/index.html

需要安装C语言库,可使用 pip 安装:pip install lxml (或通过wheel方式安装)

初步使用

我们利用它来解析 HTML 代码,简单示例:


# lxml_test.py

# 使用 lxml 的 etree 库
from lxml import etree 

text = '''
<div>
    <ul>
         <li class="item-0"><a href="link1.html">first item</a></li>
         <li class="item-1"><a href="link2.html">second item</a></li>
         <li class="item-inactive"><a href="link3.html">third item</a></li>
         <li class="item-1"><a href="link4.html">fourth item</a></li>
         <li class="item-0"><a href="link5.html">fifth item</a> # 注意,此处缺少一个 </li> 闭合标签
     </ul>
 </div>
'''

#利用etree.HTML,将字符串解析为HTML文档
html = etree.HTML(text) 

# 按字符串序列化HTML文档
result = etree.tostring(html) 

print(result)

输出结果

<html><body>
<div>
    <ul>
         <li class="item-0"><a href="link1.html">first item</a></li>
         <li class="item-1"><a href="link2.html">second item</a></li>
         <li class="item-inactive"><a href="link3.html">third item</a></li>
         <li class="item-1"><a href="link4.html">fourth item</a></li>
         <li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
 </div>
</body></html>

lxml 可以自动修正 html 代码,例子里不仅补全了 li 标签,还添加了 body,html 标签。
文件读取:
除了直接读取字符串,lxml还支持从文件里读取内容。我们新建一个hello.html文件:

<!-- hello.html -->

<div>
    <ul>
         <li class="item-0"><a href="link1.html">first item</a></li>
         <li class="item-1"><a href="link2.html">second item</a></li>
         <li class="item-inactive"><a href="link3.html"><span class="bold">third item</span></a></li>
         <li class="item-1"><a href="link4.html">fourth item</a></li>
         <li class="item-0"><a href="link5.html">fifth item</a></li>
     </ul>
 </div>

再利用 etree.parse() 方法来读取文件。

# lxml_parse.py

from lxml import etree

# 读取外部文件 hello.html
html = etree.parse('./hello.html')
result = etree.tostring(html, pretty_print=True)

print(result)

输出结果与之前相同:

<html><body>
<div>
    <ul>
         <li class="item-0"><a href="link1.html">first item</a></li>
         <li class="item-1"><a href="link2.html">second item</a></li>
         <li class="item-inactive"><a href="link3.html">third item</a></li>
         <li class="item-1"><a href="link4.html">fourth item</a></li>
         <li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
 </div>
</body></html>

XPath实例测试

  1. 获取所有的
  2. 标签
# xpath_li.py

from lxml import etree

html = etree.parse('hello.html')
print type(html)  # 显示etree.parse() 返回类型

result = html.xpath('//li')

print result  # 打印<li>标签的元素集合
print len(result)
print type(result)
print type(result[0])

输出结果:

<type 'lxml.etree._ElementTree'>
[<Element li at 0x1014e0e18>, <Element li at 0x1014e0ef0>, <Element li at 0x1014e0f38>, <Element li at 0x1014e0f80>, <Element li at 0x1014e0fc8>]
5
<type 'list'>
<type 'lxml.etree._Element'>
  1. 继续获取
  2. 标签的所有 class属性
  3. # xpath_li.py
from lxml import etree

html = etree.parse('hello.html')
result = html.xpath('//li/@class')

print result

运行结果

['item-0', 'item-1', 'item-inactive', 'item-1', 'item-0']

3. 继续获取

  • 标签下hre 为 link1.html 的 标签
  • # xpath_li.py
    
    from lxml import etree
    
    html = etree.parse('hello.html')
    result = html.xpath('//li/a[@href="link1.html"]')
    
    print result

    result

    [<Element a at 0x10ffaae18>]

    获取

  • 标签下的所有 标签
  • # xpath_li.py
    
    from lxml import etree
    
    html = etree.parse('hello.html')
    
    #result = html.xpath('//li/span')
    #注意这么写是不对的:
    #因为 / 是用来获取子元素的,而 <span> 并不是 <li> 的子元素,所以,要用双斜杠
    
    result = html.xpath('//li//span')
    
    print result

    result

    [<Element span at 0x10d698e18>]

    获取

  • 标签下的标签里的所有 class
  • # xpath_li.py
    
    from lxml import etree
    
    html = etree.parse('hello.html')
    result = html.xpath('//li/a//@class')
    
    print result

    运行结果

    ['blod']

    获取最后一个

  • 的 href
  • # xpath_li.py
    
    from lxml import etree
    
    html = etree.parse('hello.html')
    
    result = html.xpath('//li[last()]/a/@href')
    # 谓语 [last()] 可以找到最后一个元素
    
    print result

    运行结果

    ['link5.html']
    

    获取倒数第二个元素的内容

    # xpath_li.py
    
    from lxml import etree
    
    html = etree.parse('hello.html')
    result = html.xpath('//li[last()-1]/a')
    
    # text 方法可以获取元素内容
    print result[0].text

    运行结果

    fourth item
    

    获取 class 值为 bold 的标签名

    # xpath_li.py
    
    from lxml import etree
    
    html = etree.parse('hello.html')
    
    result = html.xpath('//*[@class="bold"]')
    
    # tag方法可以获取标签名
    print result[0].tag

    运行结果

    span

    使用xpath解析 内涵段子

    import urllib.request
    
    from lxml import etree
    
    
    class NH(object):
        def __init__(self):
            self.page = 1
    
        def get_data(self, li_list):
            for q in li_list:
                title = q.find_all("a")[0].string
                print(title)
                content_list = q.find_all("div", class_="f18 mb20")[0].strings
                content_string = "".join(content_list).strip().replace(" ", "")
                print(content_string)
                print("==============")
    
                data = dict()
                data["title"] = title
                data["content"] = content_string
                yield data
    
        def loadPage(self):
            url = "https://www.neihan8.com/article/list_5_" + str(self.page) + ".html"
            # print(url)
            # url = "https://www.neihan8.com/article/list_5_1.html"
            header = {
                "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11"}
    
            request = urllib.request.Request(url, headers=header)
    
            response = urllib.request.urlopen(request)
            text = response.read().decode("gb2312")
            html = etree.HTML(text)
            result = html.xpath("//ul[@class='piclist longList']/li")
            for q in result:
                text2 = etree.tostring(q)
                html2 = etree.HTML(text2)
                # 获取h4 下面的第一个a 里面的内容 string(.) 函数获取a 标签 内的所有文本
                # 即便a 标签里面还有别的标签
                title = html2.xpath("//li/h4/a")[0].xpath('string(.)')
                # print(ele[0].text)
                print(title)
                content = html2.xpath("//li/div[@class='f18 mb20']")[0].xpath('string(.)').strip()
                print(content)
                print("=======================================")
    
    
    if __name__ == '__main__':
        instance = NH()
        instance.loadPage()
    

    上面在for 循环时候有问题 实现的复杂了点
    就是xpath 解析时候

    import urllib.request
    
    from lxml import etree
    
    
    class NH(object):
        def __init__(self):
            self.page = 1
    
        def get_data(self, li_list):
            for q in li_list:
                title = q.find_all("a")[0].string
                print(title)
                content_list = q.find_all("div", class_="f18 mb20")[0].strings
                content_string = "".join(content_list).strip().replace(" ", "")
                print(content_string)
                print("==============")
    
                data = dict()
                data["title"] = title
                data["content"] = content_string
                yield data
    
        def loadPage(self):
            url = "https://www.neihan8.com/article/list_5_" + str(self.page) + ".html"
            # print(url)
            # url = "https://www.neihan8.com/article/list_5_1.html"
            header = {
                "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11"}
    
            request = urllib.request.Request(url, headers=header)
    
            response = urllib.request.urlopen(request)
            text = response.read().decode("gb2312")
            html = etree.HTML(text)
            result = html.xpath("//ul[@class='piclist longList']/li")
            for q in result:
                # q.xpath("//h4/a")[0].xpath('string(.)') 直接这样是不对的
                title = q.xpath(".//h4/a")[0].xpath('string(.)')
                print(title) ## 主要是title和content 这个地方要使用相对 . 当前 的才行不能直接写
                content = q.xpath(".//div[@class='f18 mb20']")[0].xpath('string(.)').strip()
                print(content)
    
    
    if __name__ == '__main__':
        instance = NH()
        instance.loadPage()
    

    对比使用bs4中beautifulsoup 简单一些

    使用xpath 和 bs4 中beautiful比较

    BeautifulSoup是一个库,而XPath是一种技术,python中最常用的XPath库是lxml,因此,这里就拿lxml来和BeautifulSoup做比较吧

    1 性能 lxml >> BeautifulSoupBeautifulSoup和lxml的原理不一样,BeautifulSoup是基于DOM的,会载入整个文档,解析整个DOM树,因此时间和内存开销都会大很多。而lxml只会局部遍历,另外lxml是用c写的,而BeautifulSoup是用python写的,因此性能方面自然会差很多。

    2 易用性 BeautifulSoup >> lxml
    BeautifulSoup用起来比较简单,API非常人性化,支持css选择器。lxml的XPath写起来麻烦,开发效率不如BeautifulSoup。

    title = soup.select('.content div.title h3')

    同样的代码用Xpath写起来会很麻烦

    title = tree.xpath("//*[@class='content']/div[@class='content']/h3")
    

    3 总结
    需求比较确定,要求性能的场合用lxml,快速开发用BeautifulSoup
    ps: BeautifulSoup4可以使用lxml作为parser了

    最后说一点, 那个顺手在不考虑别的情况下就使用哪个xpath beautifulsoup

    相关标签: xpath