爬虫 — JsonPath 和 CSV 文件读写

一、JsonPath

1、概念

JsonPath 是一种简单的方法来提取给定 JSON 文档的部分内容,JsonPath 有许多编程语言,如 Javascript,Python,PHP 和 JAVA。

JsonPath 提供的 JSON 解析非常强大,它提供了类似正则表达式的语法,基本上可以满足所有你想要获得的 JSON 内容。但 JsonPath 解析的数据只针对 JSON 格式的数据

2、安装

JsonPath 是第三方库,需要安装

在终端运行:pip install jsonpath

3、用法

JsonPath 操作符

符号 说明
$ 查询的根节点对象,用于表示一个 JSON 数据,可以是数组或对象
@ 过滤器断言(filter predicate)处理的当前节点对象,类似 JAVA 中的 this 字段
* 通配符,可以表示一个名字或数字
. . 可以理解为递归搜索
. 表示一个子节点
[‘name’(,‘’)] 表示一个或多个子节点
[(,)] 表示一个或多个数组下标
[start:end] 数组片段,区间为[start, end), 不包含end
[?()] 过滤器表达式,表达式结果必须是boolean
# 数据
dic = {
    "resultCode": "1",
    "resultMsg": "success",
    "reqId": "52f9f3e1-1d76-47b4-b2ae-226633b61476",
    "systemTime": "1681991278593",
    "videoInfo": {  # 父亲
        "playSta": "1",
        "video_image": "https://image.pearvideo.com/cont/20170714/cont-1110173-10436784.png",
        "srcUrl": "https://video.pearvideo.com/mp4/short/20170714/1681991278593-10632788-aaa.mp4",
        "videos": {  # 儿子
            "hdUrl": "",
            "hdflvUrl": "",
            "sdUrl": "",
            "sdflvUrl": "",  # 孙子
            "srcUrl": "https://video.pearvideo.com/mp4/short/20170714/1681991278593-10632788-hd.mp4"
        }
    }
}

3.1、. .

递归搜索,不需要考虑节点位置获取数据。

jsonpath.jsonpath(数据, ‘$. .key’)

print(jsonpath.jsonpath(dic, '$..srcUrl'))
# 返回值
# ['https://video.pearvideo.com/mp4/short/20170714/1681991278593-10632788-aaa.mp4', 'https://video.pearvideo.com/mp4/short/20170714/1681991278593-10632788-hd.mp4']

print(jsonpath.jsonpath(dic, '$..video_image'))
# 返回值
# ['https://image.pearvideo.com/cont/20170714/cont-1110173-10436784.png']

print(jsonpath.jsonpath(dic, '$..[playSta,video_image]'))
# 返回值
# ['1', 'https://image.pearvideo.com/cont/20170714/cont-1110173-10436784.png']

3.2、.

匹配下一级的标签(子集)。

print(jsonpath.jsonpath(dic, '$..srcUrl')[0])
# 返回值
# https://video.pearvideo.com/mp4/short/20170714/1681991278593-10632788-aaa.mp4

print(jsonpath.jsonpath(dic, '$..videoInfo.srcUrl'))
# 返回值
# ['https://video.pearvideo.com/mp4/short/20170714/1681991278593-10632788-aaa.mp4']

print(jsonpath.jsonpath(dic, '$..videos.srcUrl'))
# 返回值
# ['https://video.pearvideo.com/mp4/short/20170714/1681991278593-10632788-hd.mp4']
# 数据
dic = {
    "resultCode": "1",
    "resultMsg": "success",
    "reqId": "52f9f3e1-1d76-47b4-b2ae-226633b61476",
    "systemTime": "1681991278593",
    "videoInfo": {
        "playSta": "1",
        "video_image": "https://image.pearvideo.com/cont/20170714/cont-1110173-10436784.png",
        "videos": [
            {
                "hdUrl": "1",
                "hdflvUrl": "",
                "sdUrl": "",
                "sdflvUrl": "bbbbbb",
            },
            {
                "hdUrl": "",
                "hdflvUrl": "",
                "sdUrl": "",
                "sdflvUrl": "cccccc",
                "srcUrl": "https://video.pearvideo.com/mp4/short/20170714/1681991278593-10632788-hd.mp4"
            },
            {
                "hdUrl": "",
                "hdflvUrl": "",
                "sdUrl": "",
                "sdflvUrl": "dddddd",
                "srcUrl": "https://video.pearvideo.com/mp4/short/20170714/1681991278593-10632788-hd.mp4"
            },
        ]
    }
}

3.3、@

正在处理的当前节点,?:过滤器。

# 只取 videos 列表里,包含 srcUrl 的对象
print(jsonpath.jsonpath(dic, '$..videos[?(@.srcUrl)]'))
# 返回值
# [{'hdUrl': '', 'hdflvUrl': '', 'sdUrl': '', 'sdflvUrl': 'cccccc', 'srcUrl': 'https://video.pearvideo.com/mp4/short/20170714/1681991278593-10632788-hd.mp4'}, {'hdUrl': '', 'hdflvUrl': '', 'sdUrl': '', 'sdflvUrl': 'dddddd', 'srcUrl': 'https://video.pearvideo.com/mp4/short/20170714/1681991278593-10632788-hd.mp4'}]

# 获取所有 key 为 srcUrl 的值
print(jsonpath.jsonpath(dic, '$..srcUrl'))
# 返回值
# ['https://video.pearvideo.com/mp4/short/20170714/1681991278593-10632788-hd.mp4', 'https://video.pearvideo.com/mp4/short/20170714/1681991278593-10632788-hd.mp4']

# 根据值筛选数据
print(jsonpath.jsonpath(dic, '$..videos[?(@.hdUrl=="1")]'))
# 返回值
# [{'hdUrl': '1', 'hdflvUrl': '', 'sdUrl': '', 'sdflvUrl': 'bbbbbb'}]

3.4、*

通配符,可以表示一个名字或数字。

# 多个子节点,通过下标,获取第一个对象 sdflvUrl 值
print(jsonpath.jsonpath(dic, '$..videos[0].sdflvUrl'))
# 返回值
# ['bbbbbb']

# 多个子节点,通过下标,获取第一和第二两个对象 sdflvUrl 值
print(jsonpath.jsonpath(dic, '$..videos[0,1].sdflvUrl'))
# 返回值
# ['bbbbbb', 'cccccc']

# 取所有节点
print(jsonpath.jsonpath(dic, '$..videos[*].sdflvUrl'))
# 返回值
# ['bbbbbb', 'cccccc', 'dddddd']

3.5、[start:end]

数组片段,区间为[start, end),不包含end。

# 列表下标,取两个数据 —— 切片,取1,2
print(jsonpath.jsonpath(dic, '$..videos[1:3].sdflvUrl'))
# 返回值
# ['cccccc', 'dddddd']

二、CSV 文件读写

1、概念

CSV(Comma Separated Values),是一种常用的文本格式,用以存储表格数据,包括数字或者字符。很多程序在处理数据时都会碰到 CSV 这种格式的文件。

Python 自带了 CSV 模块,专门用于处理 CSV 文件的读取。

2、写入 CSV 文件

2.1 方式一

# 导入模块
import csv

# 爬取下来的数据
data = [('肖申克的救赎', '9.7', '希望让人自由'), ('霸王别姬', '9.6', '风华绝代'), ('阿甘正传', '9.5', '一部美国近现代史')]

# 设置表头
head = ('电影名', '评分', '简介')

# 保存
# 'w':写入,encoding='utf-8-sig':编码格式,newline='':清除空行
with open('douban.csv','w',encoding='utf-8-sig',newline='') as f:
    # 创建 csv 写入对象
    writer = csv.writer(f)
    # 写入表头
    writer.writerow(head)
    # 写入数据
    writer.writerows(data)

2.2 方式二

# 导入模块
import csv

# 列表嵌套字典格式数据
data = [
    {'标题': '肖申克的救赎', '评分': '9.7', '引言': '希望让人自由'},
    {'标题': '霸王别姬', '评分': '9.6', '引言': '风华绝代'},
    {'标题': '阿甘正传', '评分': '9.5', '引言': '一部美国近现代史'}
]

# 设置表头,key 必须要跟表头保持一致
head = ('标题', '评分', '引言')

# 创建文件对象
with open('douban1.csv', 'w', encoding='utf-8-sig', newline='') as f:
    # 创建 csv 字典写入对象,fieldnames 固定参数,不可修改
    writer = csv.DictWriter(f, fieldnames=head)  # 设置表头
    # 写入表头
    writer.writeheader()
    # 写入数据
    writer.writerows(data)
'''
如果出现报错信息
ValueError: dict contains fields not in fieldnames: '标题', '引言'
检查表头是否与字典当中的 key 保持一致
'''

3、读取 CSV 文件

3.1 方式一

# 导入模块
import csv

# 读取数据
with open('douban.csv','r', encoding='utf-8-sig') as f:
    # 创建 csv 读取对象
    read = csv.reader(f)
    # 不读取表头
    next(read)
    # list列表,直接下标取值
    for i in read:
        print(i)

3.2 方式二

# 导入模块
import csv

# 读取数据
with open('douban.csv','r', encoding='utf-8-sig') as f:
    # 创建 csv 读取对象,返回数据为字典格式
    read = csv.DictReader(f)
    # dict 字典,通过 key 取值
    for i in read:
        print(i['电影名'])

w:写入数据(重新写入,覆盖)
r:读取数据
a:追加,文件原有的内容基础上添加新的内容
wb:保存视频,图片,音频

三、JsonPath + CSV 文件读写案例

目标网站:‘https://www.liepin.com/zhaopin/?inputFrom=www_index&workYearCode=0&key=python&scene=input&ckId=wodpawgkbhefdidqtq1hso4xqml8alw9&dq=’
翻页爬取前两页的数据(职位名称,公司地点,薪资)

# 导入模块
import requests
import csv

# 指定url
url = 'https://api-c.liepin.com/api/com.liepin.searchfront4c.pc-search-job'

# 设置请求头参数
head = {
    'Accept': 'application/json, text/plain, */*',
    'Accept-Encoding': 'gzip, deflate, br',
    'Accept-Language': 'en,zh-CN;q=0.9,zh;q=0.8',
    'Connection': 'keep-alive',
    'Content-Length': '406',
    'Content-Type': 'application/json;charset=UTF-8;',
    'Cookie': 'XSRF-TOKEN=Bh2gi6GcTtqIvM4lrU3m4g; __gc_id=f204bf9d8cc34c659470e09660711986; _ga=GA1.1.1267984231.1683208911; __uuid=1683208911082.70; __tlog=1683208911084.44%7C00000000%7C00000000%7C00000000%7C00000000; Hm_lvt_a2647413544f5a04f00da7eee0d5e200=1683208911; __session_seq=13; __uv_seq=13; Hm_lpvt_a2647413544f5a04f00da7eee0d5e200=1683210035; _ga_54YTJKWN86=GS1.1.1683208911.1.1.1683211483.0.0.0',
    'Host': 'api-c.liepin.com',
    'Origin': 'https://www.liepin.com',
    'Referer': 'https://www.liepin.com/',
    'sec-ch-ua': '"Chromium";v="112", "Google Chrome";v="112", "Not:A-Brand";v="99"',
    'sec-ch-ua-mobile': '?0',
    'sec-ch-ua-platform': '"Windows"',
    'Sec-Fetch-Dest': 'empty',
    'Sec-Fetch-Mode': 'cors',
    'Sec-Fetch-Site': 'same-site',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36',
    'X-Client-Type': 'web',
    'X-Fscp-Bi-Stat': '{"location": "https://www.liepin.com/zhaopin/?inputFrom=www_index&workYearCode=0&key=python&scene=input&ckId=wodpawgkbhefdidqtq1hso4xqml8alw9&dq="}',
    'X-Fscp-Fe-Version': '',
    'X-Fscp-Std-Info': '{"client_id": "40108"}',
    'X-Fscp-Trace-Id': '046a4cc3-72bc-4b95-8f7a-c39df3ee11d8',
    'X-Fscp-Version': '1.1',
    'X-Requested-With': 'XMLHttpRequest',
    'X-XSRF-TOKEN': 'Bh2gi6GcTtqIvM4lrU3m4g',
}

# 表格数据
csvList = []

# 获取前两页数据
for i in range(0, 2):

    # post请求参数
    data = {"data":{"mainSearchPcConditionForm":{"city":"410","dq":"410","pubTime":"","currentPage":i,"pageSize":40,"key":"python","suggestTag":"","workYearCode":"0","compId":"","compName":"","compTag":"","industry":"","salary":"","jobKind":"","compScale":"","compKind":"","compStage":"","eduLevel":""},"passThroughForm":{"scene":"input","skId":"","fkId":"","ckId":"vmglvn6pyc3nv5scebhp02euhaexpkd8"}}}

    # 发送请求
    res = requests.post(url, headers=head, json=data)

    # 获取响应内容
    json_data = res.json() # 获取json格式的数据
    result = json_data['data']['data']['jobCardList']
    # print(result)

    # 循环获取所需数据
    for j in result:
        # 职位名称
        title = j['job']['title']
        # 薪资
        salary = j['job']['salary']
        # 公司地点
        compName = j['comp']['compName']
        # 打印所需数据
        print(title, salary, compName)

        # 追加表格数据
        csvList.append((title, salary, compName))
# print(csvList)

# 规定表头
tableHead = ('职位名称','薪资','公司地点')

# 创建文件对象
with open('liepin.csv', 'w', encoding='utf-8-sig', newline='') as f:
    # 创建csv写入对象
    writer = csv.writer(f)
    # 写入表头
    writer.writerow(tableHead)
    # 写入数据
    writer.writerows(csvList)

记录学习过程,欢迎讨论交流,尊重原创,转载请注明出处~