爬虫 josn模块和josnpath模块

1. json模块

1.1. 什么是json

JSON(JavaScript Object Notation, JS对象简谱)是一种轻量级的数据交换格式。它基于 ECMAScript(European Computer Manufacturers Association, 欧洲计算机协会制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。(百度百科)

{
    "name": "中国",
    "province": [{
        "name": "黑龙江",
        "cities": {
            "city": ["哈尔滨", "大庆"]
        }
    }, {
        "name": "广东",
        "cities": {
            "city": ["广州", "深圳", "珠海"]
        }
    }, {
        "name": "台湾",
        "cities": {
            "city": ["台北", "高雄"]
        }
    }, {
        "name": "新疆",
        "cities": {
            "city": ["乌鲁木齐"]
        }
    }]
}

1.2. json模块的使用

想要使用json模块的话,需要在代码中导入json库import json,该模块是python的内置库,因此不需要单独去pip安装。

常用的json模块方法有四个:json.dumps()json.dump()json.loads()json.load()。下面分别对这几个方法进行介绍。

1.2.1. json.dumps()

方法功能:将 Python 对象编码成 json 字符串。

大家可能不清楚讲python对象编码成 json 字符串有什么作用有什么意义。学过前后端的同学应该是知道,后端传给前端的数据就需要是一个json格式的数据;以及当你想把数据保存为 json 文件时,也是需要将数据转为 json格式。

这里给大家演示一下,当你想要将一个字典的数据保存到json文件中时,会发生什么情况。

dic = {
    "name": "广东",
    "cities": {
        "city": ["广州", "深圳", "珠海"]
    }
}
# print(type(dic))  # <class 'dict'>
with open('广东.json', 'w', encoding='utf8') as f:
    f.write(dic)

这里报错是说文件对象的write()方法只能操作字符串,不能写入字典类型,这里你就算换成列表也是会报错的,那你可能会想,既然他只能写入字符串,那咱把字典强转成字符串不就好了吗?来试试吧。

dic = {
    "name": "广东",
    "cities": {
        "city": ["广州", "深圳", "珠海"]
    }
}
# print(type(dic))  # <class 'dict'>
with open('广东.json', 'w', encoding='utf8') as f:
    f.write(str(dic))  # 强转

这个时候不报错了,文件也写入了,但是你会发现,这里的数值都飘红色波浪线了,虽然只是作为数据存储不会有太大问题,但是这也并非json的格式。所以需要使用json模块的dumps()方法,怎么使用呢,第一步导入json模块,然后将强转字符串的方法str()换成json.dumps()即可。

import json

dic = {
        "name": "广东",
        "cities": {
            "city": ["广州", "深圳", "珠海"]
        }
    }
# print(type(dic))  # <class 'dict'>
with open('广东.json', 'w', encoding='utf8') as f:
    f.write(json.dumps(dic))

现在红色下划线没有了,但是又出现了一个新的问题,原本中文的地方,都被进行了Unicode编码,变成了我们看不懂的样子,这是什么原因呢?其实很简单,这个是库底层实现的,会默认将中文进行编码,那有没有取消编码的方法呢,答案肯定是有的,非常简单,只需要添加一个参数即可。

json.dumps(dic, ensure_ascii=False)

添加参数后再运行就解决了编码问题,但是还有一个问题,现在是数据量比较小的情况,当数据量很多的时候,如果所有数据都在一行的话,就会非常难以查看结构,所以这里再介绍一个参数indent,一般将其设置为4就够了,也就是缩进4字符。

json.dumps(dic, ensure_ascii=False, indent=4)

现在数据的结构就要清晰很多了,层级分明,是不是就好看多了。这个就是json.dumps(),这个方法学会之后,json.dump()就没问题了。

1.2.2. json.dump()

json.dump()json.dumps()功能是一模一样的,区别在于json.dump()是操作文件对象的,怎么个操作法呢,给大家看一下代码就能理解了。

import json

dic = {
        "name": "广东",
        "cities": {
            "city": ["广州", "深圳", "珠海"]
        }
    }
# print(type(dic))  # <class 'dict'>
with open('广东.json', 'w', encoding='utf8') as f:
    # f.write(json.dumps(dic, ensure_ascii=False, indent=4))  # json.dumps()写法
    json.dump(dic, f, ensure_ascii=False, indent=4)  # json.dump()写法

json.dump()第一个参数是要保存的数据对象(不用强转),第二个参数是文件对象,也就是上面with...as f,这个f就是文件对象,后面的参数上文说过,可以不加,但是前面两个参数是必要参数。

1.2.3. json.loads()

json.loads()json.dumps()方法是相反的功能,json.loads()用于解码 json 数据。该函数返回 Python 字段的数据类型。什么意思呢,就好比我们从json文件中把json数据读取出来,这个时候它是字符串类型的,但是想要使用一些该字段的Python数据类型的方法,就可以使用json.loads()

现在来使用json.loads()方法。

import json

with open('广东.json', 'r', encoding='utf8') as f:
    line = f.read()

json_obj = json.loads(line)
print(type(json_obj))
print(json_obj)

这个时候读取出来的内容经过json.loads()转换后就变成了字典类型。如果读取的数据是其他类型,比如是列表,那么就会是列表类型。

[
    {
        "name": "广东",
    "cities": {
        "city": [
            "广州",
            "深圳",
            "珠海"
        ]
    }
    }
]

1.2.4. json.load()

有过json.dump()的经验后,猜都能猜到json.load()json.loads()的区别了吧。没错,json.load()是操作文件对象的,使用方法如下。

import json

with open('广东.json', 'r', encoding='utf8') as f:
    json_obj = json.load(f)
print(type(json_obj))
print(json_obj)

该方法必要参数就只有一个,文件对象参数。

2. jsonpath模块

2.1. jsonpath的介绍与安装

jsonpath模块是一种用于解析json结构的数据解析模块,可以从json中抽取指定信息。由于是第三方库,所以需要pip install jsonpath安装,如果安装速度过慢,可以选择使用国内镜像源

豆瓣源安装该模块:pip install jsonpath -i https://pypi.douban.com/simple/

2.2. jsonpath语法

以下内容来源于JSON Path 介绍 | Apifox 帮助文档,写得蛮不错的,所以借用了一下。

  • $ 表示文档的根元素;根对象使用$来表示,而无需区分是对象还是数组
  • . 匹配下级元素
  • @ 表示文档的当前元素
  • .node_name['node_name'] 匹配下级节点
  • [index] 检索数组中的元素;下标运算符,根据索引获取元素,JsonPath索引从0开始
  • [start:end:step] 支持数组切片语法
  • * 作为通配符,匹配所有成员
  • .. 子递归通配符,匹配成员的所有子元素
  • (<expr>) 使用表达式
  • ?(<boolean expr>)进行数据筛选

大家可能直接看语法会比较蒙,这里给大家上个自定义案例就会明白很多了。

下面是相应的JsonPath的示例,代码来源于JSONPath - XPath for JSON,JSON文档如下:

{
  "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
    }
  }
}

JsonPath

Result

$.store.book[*].author

所有book的author节点

$..author

所有author节点

$.store.*

store下的所有节点,book数组和bicycle节点

$.store..price

store下的所有price节点

$..book[2]

匹配第3个book节点

$..book[(@.length-1)],或 $..book[-1:]

匹配倒数第1个book节点

$..book[0,1],或 $..book[:2]

匹配前两个book节点

$..book[?(@.isbn)]

过滤含isbn字段的节点

$..book[?(@.price<10)]

过滤price<10的节点

$..*

递归匹配所有子节点

import jsonpath

dic = {
    "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
        }
    }
}
# 第一个参数是要解析的对象,注意只能解析json格式的数据,字符串是不行的;第二个参数是解析规则
# 以列表的形式返回,如果有结果就返回结果,没有结果就返回Fasle
result = jsonpath.jsonpath(dic, '$.store.book[*].author')  
print(result)

其他的大家自己去测试一下吧。

2.3. jsonpath案例练习

第一章的时候给大家演示的post请求案例不知道还有没有印象,就是一个返回响应数据是一些蔬菜的数据,先进入主页面,右键打开页面源代码,看看页面中的数据是否在页面源代码里面,进入页面源代码后按下ctrl + F搜索一下关键词,这里我就搜索"小白菜"了。

发现是没有收到结果的,没有搜到结果只能说明该数据是动态加载的,而非静态数据,这个时候我们打开抓包工具刷新页面进行抓包,再进行搜索。

数据包找到了之后,看一下该数据包的头部信息。

请求URL确定了,请求方式为post,该数据包需要参数,那就看看参数是些什么。

这个数据包的参数值全是空,有点奇怪,那就看看其他页面的,通过X-Requested-With: XMLHttpRequest可以确定这个数据包是xhr类型的,那么就可以进行过滤抓取第二页的数据包。

第二页数据包的参数如下:

第三页的参数如下:

limit参数控制每页数据的数量(可以通过查看每页数据量猜测得知),current参数控制页数。参数确定后就可以开始写爬虫代码了。

import requests

url = 'http://www.xinfadi.com.cn/getPriceData.html'
data = {
  'limit': '20',
  'current': '3'
}
headers={
  'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36'
}
response = requests.post(url, headers=headers).content.decode()
print(response)
print(type(response))

响应数据是字符串,因为进制解码后就是字符串,这个时候可以用json.loads()将其转为json格式数据。当然也有另一种方法,当你确定响应数据是json格式的时候,可以直接把.content.decode()改为.json()

这样也就不用再使用json模块方法转换了,是不是就方便很多了。现在就可以直接用jsonpath模块去解析数据了。这里我们就拿每个菜品的这些数据吧:

通过观察数据结构可以发现,所有的数据都是在list里面的,列表里面的每一个字典就是每一个菜品数据,那代码就可以这么写:

import jsonpath
import requests

url = 'http://www.xinfadi.com.cn/getPriceData.html'
data = {
  'limit': '20',
  'current': '3'
}
headers={
  'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36'
}
response = requests.get(url, headers=headers).json()
# print(response)
prodName = jsonpath.jsonpath(response, '$..list..prodName')
lowPrice = jsonpath.jsonpath(response, '$..list..lowPrice')
highPrice = jsonpath.jsonpath(response, '$..list..highPrice')
avgPrice = jsonpath.jsonpath(response, '$..list..avgPrice')

print(prodName, lowPrice, highPrice, avgPrice)

非常简单,其实jsonpath只需要会..跨节点获取数据就好了,其他的操作的话不用jsonpath也是能够实现的(毕竟返回值是列表,可以使用列表的方法实现想要的功能)。那现在如何把这些数据对应保存到json文件当中呢?

...  # 接上
json_list = list(zip(prodName, lowPrice, highPrice, avgPrice))
for i in json_list:
  print(i)

现在只需要每次循环都生成一个字典,再在最外面全局定义一个字典嵌套列表,每次循环将生成的字典添加到全局字典列表里面去即可。

import jsonpath
import requests

url = 'http://www.xinfadi.com.cn/getPriceData.html'
data = {
    'limit': '20',
    'current': '3'
}
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36'
}
response = requests.get(url, headers=headers).json()
# print(response)
prodName = jsonpath.jsonpath(response, '$..list..prodName')
lowPrice = jsonpath.jsonpath(response, '$..list..lowPrice')
highPrice = jsonpath.jsonpath(response, '$..list..highPrice')
avgPrice = jsonpath.jsonpath(response, '$..list..avgPrice')
json_dic = {
    "list": []
}
json_list = list(zip(prodName, lowPrice, highPrice, avgPrice))
for i in json_list:
    dic = {
        'prodName': i[0],
        'lowPrice': i[1],
        'highPrice': i[2],
        'avgPrice': i[3]
    }
    json_dic['list'].append(dic)
print(json_dic)

数据构造好之后就需要将数据存储为json文件了,那么现在使用json.dumps()json.dump()都是可以的,建议大家都试试,我这里就用json.dump()了。

import json

...  # 省略

with open('菜品.json', 'w', encoding='utf8') as f:
    json.dump(json_dic, f, ensure_ascii=False, indent=4)

数据存储成功,案例结束,恭喜各位又学会了一个新的知识点。

  • 34
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱学习的小su

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值