数据提取概述

知识点

  • 了解 响应内容的分类
  • 了解 xml和html的区别

1. 响应内容的分类

在发送请求获取响应之后,可能存在多种不同类型的响应内容;而且很多时候,我们只需要响应内容中的一部分数据

  • 结构化的响应内容

    • json字符串

      • 可以使用re、json等模块来提取特定数据
      • json字符串的例子如下图

      结构化响应内容json
      结构化响应内容json

    • xml字符串

      • 可以使用re、lxml等模块来提取特定数据
      • xml字符串的例子如下

        <bookstore>
        <book category="COOKING">
          <title lang="en">Everyday Italian</title> 
          <author>Giada De Laurentiis</author> 
          <year>2005</year> 
          <price>30.00</price> 
        </book>
        <book category="CHILDREN">
          <title lang="en">Harry Potter</title> 
          <author>J K. Rowling</author> 
          <year>2005</year> 
          <price>29.99</price> 
        </book>
        <book category="WEB">
          <title lang="en">Learning XML</title> 
          <author>Erik T. Ray</author> 
          <year>2003</year> 
          <price>39.95</price> 
        </book>
        </bookstore>
  • 非结构化的响应内容

    • html字符串

      • 可以使用re、lxml等模块来提取特定数据
      • html字符串的例子如下图

      非结构化响应内容html
      非结构化响应内容html

2. 认识xml以及和html的区别

要搞清楚html和xml的区别,首先需要我们来认识xml

2.1 认识xml

xml是一种可扩展标记语言,样子和html很像,功能更专注于对传输和存储数据

<bookstore>
<book category="COOKING">
  <title lang="en">Everyday Italian</title> 
  <author>Giada De Laurentiis</author> 
  <year>2005</year> 
  <price>30.00</price> 
</book>
<book category="CHILDREN">
  <title lang="en">Harry Potter</title> 
  <author>J K. Rowling</author> 
  <year>2005</year> 
  <price>29.99</price> 
</book>
<book category="WEB">
  <title lang="en">Learning XML</title> 
  <author>Erik T. Ray</author> 
  <year>2003</year> 
  <price>39.95</price> 
</book>
</bookstore>

上面的xml内容可以表示为下面的树结构:

xml树结构
xml树结构

2.2 xml和html的区别

二者区别如下图

xml和html的区别
xml和html的区别

  • html:

    • 超文本标记语言
    • 为了更好的显示数据,侧重点是为了显示
  • xml:

    • 可扩展标记语言
    • 为了传输和存储数据,侧重点是在于数据内容本身

2.3 常用数据解析方法

常用数据解析方法
常用数据解析方法

数据提取-jsonpath模块

知识点

  • 了解 jsonpath模块的使用场景
  • 掌握 jsonpath模块的使用

1. jsonpath模块的使用场景

如果有一个多层嵌套的复杂字典,想要根据key和下标来批量提取value,这是比较困难的。jsonpath模块就能解决这个痛点,接下来我们就来学习jsonpath模块。jsonpath可以按照key对python字典进行批量数据提取

# 导入模块
from jsonpath import jsonpath

# 示例提取字典
demo_dict = {'key1': {'key2': {'key3': {'key4': {'key5': {'key6': {'key7':'Python'}}}}}}}

# 普通模式提取
print(demo_dict['key1']['key2']['key3']['key4']['key5']['key6']['key7']) # Python

# 使用JsonPath提取示例(注意:jsonpath提取的数据为数组形式)
print(jsonpath(demo_dict, '$.key1.key2.key3.key4.key5.key6.key7')[0]) # Python
print(jsonpath(demo_dict, '$..key7')[0]) # Python

2. jsonpath模块的使用方法

2.1 jsonpath模块的安装

jsonpath是第三方模块,需要额外安装:pip install jsonpath

2.2 jsonpath模块提取数据的方法

from jsonpath import jsonpath
ret = jsonpath(a, 'jsonpath语法规则字符串')

2.3 jsonpath语法规则

jsonpath的方法
jsonpath的方法

2.4 jsonpath使用示例

# 导入模块
from jsonpath import jsonpath
import json

# 示例提取字典
book_dict = '''{ 
  "store": {
    "book": [ 
      { "category": "reference",
        "author": "Nigel Rees",
        "title": "Sayings of the Century",
        "price": 8.95
      },
      { "category": "fiction",
        "author": "Evelyn Waugh",
        "title": "Sword of Honour",
        "price": 12.99
      },
      { "category": "fiction",
        "author": "Herman Melville",
        "title": "Moby Dick",
        "isbn": "0-553-21311-3",
        "price": 8.99
      },
      { "category": "fiction",
        "author": "J. R. R. Tolkien",
        "title": "The Lord of the Rings",
        "isbn": "0-395-19395-8",
        "price": 22.99
      }
    ],
    "bicycle": {
      "color": "red",
      "price": 19.95
    }
  }
}'''


# 将字符串转化为字典
book_dict = json.loads(book_dict)

# 使用jsonpath提取数据
# 提取自行车颜色
print(jsonpath(book_dict, '$..color')[0])

# 提取所有商品价格并计算
all_price = jsonpath(book_dict, '$..price')

price_sum = 0
for price in all_price:
    price_sum += price

print(price_sum)

jsonpath使用示例
jsonpath使用示例

3. jsonpath练习

我们以拉勾网城市JSON文件 http://www.lagou.com/lbs/getAllCitySearchLabels.json 为例,获取所有城市的名字的列表,并写入文件。

参考代码:

import requests  # 导入请求模块
import json  # 导入josn处理模块
from jsonpath import jsonpath  # 导入json提取模块

# 爬取数据url
url = 'https://www.lagou.com/lbs/getAllCitySearchLabels.json'

# 构建请求头
headers = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36'
}

# 发送请求
data = requests.get(url, headers=headers)

# 将字符串转为字典
data_dict = json.loads(data.content)

# 提取数据
city_name = jsonpath(data_dict, '$..name')

# 定义26个字母
letter_arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
          'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']

# 定义接受结果的字典(城市名字按首字母分类)
result = {}

for letter in letter_arr:
    search_query = '$..'+letter+'..name'
    city_name = jsonpath(data_dict, search_query)
    result[letter] = city_name

数据提取-lxml模块

知识点

  • 了解 lxml模块和xpath语法的关系
  • 了解 lxml模块的使用场景
  • 了解 lxml模块的安装
  • 了解 谷歌浏览器xpath helper插件的安装和使用
  • 掌握 xpath语法-基础节点选择语法
  • 掌握 xpath语法-节点修饰语法
  • 掌握 xpath语法-其他常用语法
  • 掌握 lxml模块中使用xpath语法定位元素提取属性值或文本内容
  • 掌握 lxml模块中etree.tostring函数的使用

1. 了解 lxml模块和xpath语法

对html或xml形式的文本提取特定的内容,就需要我们掌握lxml模块的使用和xpath语法。

  • lxml模块可以利用XPath规则语法,来快速的定位HTMLXML 文档中特定元素以及获取节点信息(文本内容、属性值)
  • XPath (XML Path Language) 是一门在 HTMLXML 文档中查找信息的语言,可用来在 HTMLXML 文档中对元素和属性进行遍历

  • 提取xml、html中的数据需要lxml模块和xpath语法配合使用

2. 谷歌浏览器xpath helper插件的安装和使用

要想利用lxml模块提取数据,需要我们掌握xpath语法规则。接下来我们就来了解一下xpath helper插件,它可以帮助我们练习xpath语法

2.1 谷歌浏览器xpath helper插件的作用

在谷歌浏览器中对当前页面测试xpath语法规则

2.2 谷歌浏览器xpath helper插件的安装和使用

我们以windos为例进行xpath helper的安装

2.2.1 xpath helper插件的安装
  1. 下载Chrome插件 XPath Helperx

  2. 把文件的后缀名crx改为rar,然后解压到同名文件夹中
  3. 把解压后的文件夹拖入到已经开启开发者模式的chrome浏览器扩展程序界面
  4. 重启浏览器后,访问url之后在页面中点击xpath图标,就可以使用了
  5. 如果是linux或macOS操作系统,无需操作上述的步骤2,直接将crx文件拖入已经开启开发者模式的chrome浏览器扩展程序界面

3. xpath的节点关系

学习xpath语法需要先了解xpath中的节点关系

3.1 xpath中的节点是什么

每个html、xml的标签我们都称之为节点,其中最顶层的节点称为根节点。我们以xml为例,html也是一样的

节点
节点

3.2 xpath中节点的关系

xpath中节点的关系
xpath中节点的关系

*authortitle的第一个兄弟节点

4. xpath语法-基础节点选择语法

  1. XPath 使用路径表达式来选取 XML 文档中的节点或者节点集。
  2. 这些路径表达式和我们在常规的电脑文件系统中看到的表达式非常相似。
  3. 使用chrome插件选择标签时候,选中时,选中的标签会添加属性class="xh-highlight"

4.1 xpath定位节点以及提取属性或文本内容的语法

表达式描述
nodename选中该元素。
/从根节点选取、或者是元素和元素间的过渡。
//从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。
.选取当前节点。
..选取当前节点的父节点。
@选取属性。
text()选取文本。

4.2 语法练习

接下来我们通过itcast的页面来练习上述语法:http://www.itcast.cn/

  • 选择所有的h2下的文本

    • //h2/text()
  • 获取所有的a标签的href

    • //a/@href
  • 获取html下的head下的title的文本

    • /html/head/title/text()
  • 获取html下的head下的link标签的href

    • /html/head/link/@href

5. xpath语法-节点修饰语法

可以根据标签的属性值、下标等来获取特定的节点

5.1 节点修饰语法

路径表达式结果
//title[@lang="eng"]选择lang属性值为eng的所有title元素
/bookstore/book[1]选取属于 bookstore 子元素的第一个 book 元素。
/bookstore/book[last()]选取属于 bookstore 子元素的最后一个 book 元素。
/bookstore/book[last()-1]选取属于 bookstore 子元素的倒数第二个 book 元素。
/bookstore/book[position()>1]选择bookstore下面的book元素,从第二个开始选择
//book/title[text()='Harry Potter']选择所有book下的title元素,仅仅选择文本为Harry Potter的title元素
/bookstore/book[price>35.00]/title选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。
//div[contains(@id, 'qiushi_tag_')]选取页面中id属性包含‘qiushi_tag_’的div
//a[contains(text(), '热图')]/@href获取页面内文本被包含‘热图’的a元素的href属性值
//a[contains(text(), '热图')]/@href \//div[contains(@class, 'article block')]获取页面内文本被包含‘热图’的a元素的href属性值和类名属性包含‘article block’的div

5.2 关于xpath的下标

  • 在xpath中,第一个元素的位置是1
  • 最后一个元素的位置是last()
  • 倒数第二个是last()-1

5.3 语法练习

从itcast的页面中,选择所有学科的名称、第一个学科的链接、最后一个学科的链接:http://www.itcast.cn/

  • 所有的学科的名称

    • //div[@class="nav_txt"]//a[@class="a_gd"]
  • 第一个学科的链接

    • //div[@class="nav_txt"]/ul/li[1]/a/@href
  • 最后一个学科的链接

    • //div[@class="nav_txt"]/ul/li[last()]/a/@href

6. xpath语法-其他常用节点选择语法

可以通过通配符来选取未知的html、xml的元素

6.1 选取未知节点的语法

通配符描述
*匹配任何元素节点。
node()匹配任何类型的节点。

6.2 语法练习

从itcast的页面中 http://www.itcast.cn/ ,选中全部的标签、全部的属性

  • 全部的标签

    • //*
  • 全部的属性

    • //node()

7. lxml模块的安装与使用示例

lxml模块是一个第三方模块,安装之后使用

7.1 lxml模块的安装

对发送请求获取的xml或html形式的响应内容进行提取

pip/pip3 install lxml

知识点:了解 lxml模块的安装

7.2 爬虫对html提取的内容

  • 提取标签中的文本内容
  • 提取标签中的属性的值

    • 比如,提取a标签中href属性的值,获取url,进而继续发起请求

7.3 lxml模块的使用

  1. 导入lxml 的 etree 库

    from lxml import etree

  2. 利用etree.HTML,将html字符串(bytes类型或str类型)转化为Element对象,Element对象具有xpath的方法,返回结果的列表

    html = etree.HTML(text) 
    ret_list = html.xpath("xpath语法规则字符串")
  3. xpath方法返回列表的三种情况

    • 返回空列表:根据xpath语法规则字符串,没有定位到任何元素
    • 返回由字符串构成的列表:xpath字符串规则匹配的一定是文本内容或某属性的值
    • 返回由Element对象构成的列表:xpath规则字符串匹配的是标签,列表中的Element对象可以继续进行xpath

7.4 lxml模块使用示例

运行下面的代码,查看打印的结果

from lxml import etree

text = ''' 
<div> 
  <ul> 
    <li class="item-1">
      <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>
  </ul> 
</div>
'''

# 将字符串对象转化为hmtl对象
html = etree.HTML(text) # <Element html at 0x1481d444188>

# 获取href的列表和title的列表
href_list = html.xpath("//a/@href")
title_list = html.xpath("//a/text()")

# 组装成字典
for title, href in zip(title_list ,href_list):
    item = {}
    item["href"],item["title"] = href, title
    print(item)

第二种方法:

# 将字符串对象转化为hmtl对象
html = etree.HTML(text) # <Element html at 0x1481d444188>

# 获取a标签
all_a = html.xpath('//a') # 此时的各个a还是element对象

# 循环从各个a标签中获取href与title
for a in all_a:
    title, href = a.xpath('text()')[0], a.xpath('@href')[0]
    print(title, href)

8 练习

将下面的html文档字符串中,将每个class为item-1的li标签作为1条新闻数据。提取a标签的文本内容以及链接,组装成一个字典。

text = ''' <div> <ul> 
        <li class="item-1"><a>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> 
        </ul> </div> '''
  • 注意:

    • 先分组,再提取数据,可以避免数据的错乱
    • 对于空值要进行判断
  • 参考代码

    from lxml import etree
    text = ''' <div> 
      <ul> 
        <li class="item-1">
          <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>
      </ul> 
    </div>'''

#根据li标签进行分组
html = etree.HTML(text)
li_list = html.xpath("//li[@class='item-1']")

#在每一组中继续进行数据的提取
for li in li_list:

  item = {}
  item["href"] = li.xpath("./a/@href")[0] if len(li.xpath("./a/@href"))>0 else None
  item["title"] = li.xpath("./a/text()")[0] if len(li.xpath("./a/text()"))>0 else None
  print(item)
------

##### 知识点:掌握 lxml模块中使用xpath语法定位元素提取属性值或文本内容

------



## 10. lxml模块中etree.tostring函数的使用

> 运行下边的代码,观察对比html的原字符串和打印输出的结果

​```python
from lxml import etree
html_str = ''' <div> <ul> 
      <li class="item-1"><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> 
      </ul> </div> '''

html = etree.HTML(html_str)

handeled_html_str = etree.tostring(html).decode()
print(handeled_html_str)

10.1 现象和结论

打印结果和原来相比:

  1. 自动补全原本缺失的li标签
  2. 自动补全html等标签
<html><body><div> <ul> 
<li class="item-1"><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.etree.HTML(html_str)可以自动补全标签
  • lxml.etree.tostring函数可以将转换为Element对象再转换回html字符串
  • 爬虫如果使用lxml来提取数据,应该以lxml.etree.tostring的返回结果作为提取数据的依据