5.数据提取方式(json,正则)

5.数据提取方式(json,正则)

明廷盛 嘻嘻😁

第一章 数据提取的概念和数据分类

在爬虫爬取的数据中有很多不同类型的数据,我们需要了解数据的不同类型来又规律的提取和解析数据。

  • 结构化数据:jsonxml
    • 处理方式:直接转化为python数据类型
  • 非结构化数据:HTML
    • 处理方式:正则表达式re包xpathbs4

第二章 转化json数据json包

第一节 字典 与 json格式字符串 的转换

|450

  • 语法: ①字符串=dumps(字典)字典=loads(字符串)

  • 助记: 1. s表示字符串 2.dump是垃圾的意思, dict比json要高级, 所以转成json垃圾要用dump

    1
    2
    3
    4
    5
    6
    7
    8
    # # 字符串与json
    user_input = {"name": "双双", "age": "18"} # 字典
    res = json.dumps(user_input, ensure_ascii=False, indent=4) # 字典=>json字符串
    print(res)

    # json字符串=>字典
    dict = json.loads(res)
    print(dict, type(dict))

第二节 文件 与 json格式字符串

  • 语法: ①写:json.dump(字符串, 文件f) ②读:字典=json.load(文件f)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # dict写入json格式的文件
    user_input = {"name": "双双", "age": "18"} # 字典
    with open("./json/test.text", "w") as f:
    json.dump(user_input, f)

    # 从json格式的文件读取dict
    with open("./json/test.text", "r") as f:
    my_dict = json.load(f)
    print(my_dict, type(my_dict))

第三节 练习:蜻蜓FM排行榜信息

蜻蜓FM的首页urlhttps://m.qingting.fm/rank

代码示例:

1
2
3
4
5
6
7
8
9
10
11
import requests

url = "https://webapi.qingting.fm/api/mobile/rank/hotSaleWeekly"

headers = {
"User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1"
}

r = requests.get(url=url, headers=headers)
print(r.status_code)
print(r.json())

分析过程:

第三章 正则表达式re包

工具链接地址:https://regexr-cn.com 在这个工具中我们可以快速验证自己编写的正则表达式是否存在语法错误。

第一节 前置知识

3.1.1 什么是正则表达式

  • 定义:用事先定义好的一些特定字符,及这些特定字符的组合,组成一个规则字符串, 这个规则字符串用来表达对字符串的一种过滤逻辑。

3.1.2 ASCII码与正则表达式

  • 为什么要提ASCII码: 正则表达式默认是在128个ASCII码的范围内的*(当然后面可以指定Unicode去做中文匹配)*, 而其匹配规则是将这128个ASCII进行了分类的, 不同的规则符号控制不同的类匹配, 所以弄清楚正则表达式是如何将128个ASCII码分类的是十分重要的, 关乎到匹配是否完全, 是否不重不漏!!!

image-20241201081322444

3.1.3 关于字符串的基本知识

  1. 四. 字符串字面量前缀
  2. 五. 转义字符

第二节 单个字符匹配

单字符匹配规则表达式完整匹配的字符串
.匹配任意除换行符\n外的字符a.cabc
\d数字:[0-9]a\dca1c
\D非数字:[^\d]a\Dcabc
\s空白字符:[<空格>\t\r\t\f\v]a\sca c
\S非空白字符:[^\s]a\Scabc
\w单词字符:[A-Za-z0-9_]a\wcabc
\W非单词字符:[^\w]a\Wca c
*匹配前一个字符0次或者无数次abc*ababccccc
+匹配前一个字符1次或者无数次abc+abcabccccc
?匹配前一个字符0次或者1次abc?ababc
{m}匹配前一个字符mab{2}cabbc
[...]字符集(字符类)。对应的位置可以使字符集中任意字符。字符集中的字符可以逐个列出,也可以给定范围:[abc][a-c]。第一个字符如果是^则表示取反:[^abc]表示不是abc的其他字符a[bcd]eabeaceade
\转义字符,使后面一个字符改变原来的意思。如果字符串有字符*需要匹配,则可以使用\*a\.ca.c

3.2.1 任意字符匹配(\n) .

  • 语法: .

  • 作用: 可以代表**任意** 一个字符(除了\n)

  • 匹配:

    空白字符(9)数字(10)字母(26*2=52)\n(1)其他类(56)
    ==×==
  • 举例: ①规则:"pers.n" ②方法findall()

    需要匹配的字符串输出结果解释
    "".join([chr(i) for i in range(128)])len(res)=127除了\n都匹配成功
    “personpers\\nn”[‘person’, ‘pers\n’]“pers\\n”中的两个转义\\=>\
    r’personpers\nn’[‘person’, ‘pers\\n’]“pers\n”可以匹配, 但输出是以字符串输出, 不能r(原始字符串), 所以要转义一个斜杠, 控制台输出时才会看起来多一个斜杠
    “pers$n”[‘pers$n’]只要不是\n都可以匹配
    “pers\nn”[]没+r, \n是匹配不上的转义字符, 无法匹配
    “pers\an”[‘pers\x07n’]没+r, 只要不是\n都可以匹配, 这里\x07是(两位16进制)转义为7, 对应ASCII表中的第七个(十进制)=>\a 响铃
  • 注意⚠️: 如果举例第②③⑥个看不懂了, 一定要去把前置知识第三个点回顾下

3.2.2 数字匹配 \d

  • 语法: \d

  • 作用: 匹配**一个** 数字([0-9])

  • 匹配:

    空白字符(9)数字(10)字母(26*2=52)\n(1)其他类(56)
    ==×====×====×====×==
  • 举例: ①规则:"\dabc" ②方法findall()

    需要匹配的字符串输出结果解释
    "".join([chr(i) for i in range(128)])除了10个数字, 都不能匹配
    ‘a0abc’[‘0abc’]

3.2.3 非数字匹配 \D

  • 语法: \D

  • 作用: 匹配**一个** 非数字([0-9])

  • 匹配:

    空白字符(9)数字(10)字母(26*2=52)\n(1)其他类(56)
    ==×==
  • 举例: ①规则:"\Dabe" ②方法findall()

    需要匹配的字符串输出结果解释
    "".join([chr(i) for i in range(128)])除了10个数字, 都匹配
    “7&abe”[‘&abe’]

3.2.4 空白匹配 \小s

  • 语法: \s

  • 作用: 匹配**一个** **空白字符(\t \v \f \r 分隔符*4 空格)**或 \n

  • 匹配:

    空白字符(9)数字(10)字母(26*2=52)\n(1)其他类(56)
    ×××
  • 举例: ①规则:"hello\sworld" ②方法findall()

    需要匹配的字符串输出结果解释
    "".join([chr(i) for i in range(128)])correct:10 error:118
    ‘hello\tworld’[‘hello\tworld’]\t
    ‘hello\nworld’[‘hello\nworld’]\n
    ‘hello world’[‘hello world’]空格

3.2.5 非空白匹配 \大S

  • 语法: \大S

  • 作用: 匹配**一个** 非空白字符, 匹配不了\n

  • 匹配:

    空白字符(9)数字(10)字母(26*2=52)\n(1)其他类(56)
    ==×====×==
  • 举例: ①规则:"\Sabc" ②方法findall()

    需要匹配的字符串输出结果解释
    "".join([chr(i) for i in range(128)])correct:118 error:10
    ‘\nabc’[]匹配不了\n
    ‘#$abc’[‘$abc’]其他类 中的都可以

3.2.6 单词匹配 \小w

  • 语法: \小w

  • 作用: 匹配 一个 字母 数字其他类中的”_”中文字符(涉及到Unicode)

  • 匹配:

    空白字符(9)数字(10)字母(26*2=52)\n(1)其他类(56)中文字符
    ==×====×====×== "_"√
  • 举例: ①规则:'双双\w三斤' ②方法findall()

    需要匹配的字符串输出结果解释
    "".join([chr(i) for i in range(128)])correct:63 error:65
    ‘双双胖三斤’[‘双双胖三斤’]中文字符也可以
    ‘双双p三斤’[‘双双p三斤’]
    ‘双双pp三斤’[]一个
    ‘双双_三斤’[‘双双_三斤’]“_”其他类中只有下划线可以

3.2.7 非单词匹配 \大W

  • 语法: \大W

  • 作用: 匹配 一个 空白字符\n其他类中除”_”的其他字符

  • 匹配:

    空白字符(9)数字(10)字母(26*2=52)\n(1)其他类(56)中文字符
    ==×====×=="_"×
  • 举例: ①规则:"双双\W三斤" ②方法findall()

    需要匹配的字符串输出结果解释
    "".join([chr(i) for i in range(128)])correct:65 error:63
    ‘双双胖三斤’[]中文字符 NO!
    ‘双双p三斤’[]字母 NO!
    ‘双双pp三斤’[]字母 NO! 不是第一个 NO!
    ‘双双_三斤’[]“_” NO!
    ‘双双#三斤’[‘双双#三斤’]其他字符 YES!
    ‘双双\n三斤’[‘双双\n三斤’]\n YES!

第三节 多个字符匹配

3.3.1 字符数量匹配 * + ? {m}

  • 语法:

    • ①只有和[]连用时, 才将[]看做一个字符, 比如finditer("[a-g]*", "abcdefgg")就是a-g可以出现0~♾️次
    • ②其他时候, 都是针对, 前一个字符, 比如finditer("abcdefg*", "abcdefgg")是只a-f固定, 只有末尾的g可以出现0~♾️次
    单字符匹配规则表达式完整匹配的字符串
    *匹配前一个字符0次或者无数次abc*ababccccc
    +匹配前一个字符1次或者无数次abc+abcabccccc
    ?匹配前一个字符0次或者1次abc?ababc
    {m}匹配前一个字符mab{2}cabbc
  • 注意⚠️: ==是前一个字符, 不是前一个单词==

  • 举例: ①规则:每个不同注意看 ②方法findall()

    规则需要匹配的字符串输出结果规则含义
    “abc*”“abcab”[‘abc’, ‘ab’]ab开头就可以
    ==②==“abc*”“abacab”[‘ab’, ‘ab’]ab开头就可以, 但必须以ab开头
    ==③==“[a-g]*”“abcdefgg123”[‘abcdefgg’, ‘’, ‘’, ‘’, ‘’]a-g可以出现0~♾️次, 所以对于1,2,3,分别出现0次, 匹配0次出现''
    “abcdefg*”“abcdefgg123”[‘abcdefgg’]a-f固定, 只有末尾的g可以出现0~♾️次, 只有一个
    ==⑤==“[a-z]*”“abcdef123”[‘abcdef’, ‘’, ‘’, ‘’, ‘’]'abcdef'是因为[a-z]中的[a-f]出现了一次符合*, 后面三个的理解同③
    ==⑥==[a-z123]*“abcdef3211”[‘abcdef3211’, ‘’][a-z123]* 表示匹配任意数量(包括0个)的小写字母或数字1、2、3的组合。这意味着它可以匹配空字符串,也可以匹配任何由小写字母和/或数字1、2、3组成的字符串
    ==⑦==[a-z123]{2}“abcdef3211”[‘ab’, ‘cd’, ‘ef’, ‘32’, ‘11’]综合来看,[a-z123]{2} 表示匹配任意两个连续的小写字母或数字1、2、3的组合。这意味着它可以匹配任何由两个小写字母和/或数字1、2、3组成的字符串, 顺着走, 不会头匹配是正则的特点, 所以够两个就算匹配到了一个结果

3.3.2 字符集 [...]

  • 语法: 对应的位置可以使字符集中任意一个字符。字符集中的字符可以逐个列出,也可以给定范围:[abc][a-c]。第一个字符如果是^则表示取反:[^abc]表示不是abc的其他字符

  • 举例: ①规则:"a[bcd]e" ②方法findall()

  • 注意⚠️: 不要用空格隔开!!!

    需要匹配的字符串输出结果解释
    “abeaceade”[‘abe’, ‘ace’, ‘ade’]

3.3.3 以什么开头/结尾 ^ $

3.3.4 转义特殊正则字符 \

  • 语法: \

  • 作用: 用与匹配 一个 .(原任意字符) *(原0和无数次) +(原1和无数次) ?(原0次和1次)

  • 举例: ①规则: ②方法findall()

    匹配规则目标字符串输出结果解释
    "\\\\""\\" (就是一个\, 因为两个反斜杠是用于转义=>一个反斜杠)['\\']匹配规则中其实是两个反斜杠, 第一个反斜杠用于转义第二个, 所以是匹配有一个反斜杠的目标字符
    "\.""."['.']
    "\.""\&"[]这个.可不是任意, 转义后, 只能匹配.

第四节 分组匹配

3.4.1 分组对象 <class '_sre.SRE_Match'>

  • 类型: 这个是re包中的一个类型

  • 常用方法:

    方法名称描述
    group()返回正则表达式匹配的整个字符串。
    group(n)返回正则表达式中第 n 个子组匹配的字符串。默认0=group()
    start()返回匹配结果的起始位置。
    end()返回匹配结果的结束位置。
    span()返回一个元组,包含匹配结果的起始和结束位置。
  • 为什么要分组, 分组有什么用?

    1
    2
    3
    4
    5
    # 邮箱匹配:比如这里就可以知道什么什么邮箱
    # 分组的の作用: 就可以单独获取每个组匹配的内容;
    result = re.finditer('\w{4,20}@(163|126|gmail|qq)\.com', '[email protected]')
    for i in result:
    print(i.group(1)) # 输出结果: gmail

3.4.2 管道符|

3.4.3 引用分组\num

  • 语法: \num ①注意转义(一般是两个\\num) ②可以不再组内使用

image-20241201215443943

1
2
3
4
5
6
7
8
# 需求一: ①匹配html中的一组标签 ②同时打印是什么标签
html_data = "<div>this is a div</div>"
result = re.finditer("<([a-zA-Z1-6]+)>.*</\\1>", html_data)
# []+:中的内容至少一次
# \\1和前一个相同

for i in result:
print(i.group(), i.group(1)) # <div>this is a div</div> div
1
2
3
4
5
6
7
8
# 需求二: ①匹配html中的两组标签 ②同时打印是什么标签
html_data = "<div><p>this is a p in the div</p></div>"
result = re.finditer("<([a-zA-Z1-6]+)><([a-zA-Z1-6]+)>.*</\\2></\\1>", html_data)
# \\2 要和靠近的这个同(是引用第二个)
# \\1 是引用第一个

for i in result:
print(i.group(), i.group(1), i.group(2)) # <div><p>this is a p in the div</p></div> div p

3.4.4 分组别名(?P)

  • 语法: (?P) 注意:①必须在组内命名(是新的组) ②必须在组内引用(但引用不是新的组, 无法获取)

image-20241201215414640

1
2
3
4
5
6
# 需求二的另一种实现方式: ①匹配html中的两组标签 ②同时打印是什么标签
result = re.finditer("<(?P<name_1>[a-zA-Z1-6]+)><(?P<name_2>[a-zA-Z1-6]+)>.*</(?P=name_2)></(?P=name_1)>", html_data)
for i in result:
print(i.group()) # <div><p>this is a p in the div</p></div>
print(i.group(1), i.group(2)) # div p
print(i.group(3), i.group(4)) # 报越界错误

3.4.5 强制不使用(捕获)组(?:)

  • 语法: 非捕获组的语法是在圆括号内部的模式前加上?:,这表示该组仅用于匹配,但**不捕获匹配的内容也不会分配组号**。
1
2
3
4
5
6
7
8
# 注意⚠️: 当规则中有组的时候, 只返回组的内容, 并不会返回整个的内容
result = re.findall("(\w{4,20})@(qq|gmail|163|126|sina)\.com", users_email)
print(result) # [('12345', 'qq'), ('wocwoc', 'gmail'), ('tbltbl', '163')]

# 需求: 我不想要组的内容,只想匹配全部 (你必须用到分组,因为只哟分组中才能用到管道符表示|,[]中是不允许的)
users_email = "[email protected]#[email protected]#[email protected]"
result = re.findall("(?:\w{4,20})@(?:qq|gmail|163|126|sina)\.com", users_email)
print(result)

第五节 其他匹配

3.5.1 中文匹配r'[\u4e00-\u9fa5]+'

3.5.2 贪婪模式和非贪婪模式

  • 语法: ①只有多个字符匹配的时候才用的到
  • 助记: ?限制他不让他贪婪,

第六节 re模块常用方法

re包中的匹配就是单纯的一个个对照, 不是KMP, 不找子串, 不找重复, 匹配不上就下一个

  • ①所以当只匹配一个时, 匹配不上, 就是结束匹配,
  • ②当匹配多个的, 匹配不上, 就从那个位置继续往下匹配, 不会回头再找重叠的

3.3.1 从头匹配match()

  • 语法: match("正则表达式的规则", "需要匹配的字符串")

  • 返回值: <class '_sre.SRE_Match'> 简称为匹配对象

  • 说明: ①强制从头开始匹配 ②只匹配一个

  • 举例: ①规则为”mts” ②方法为match()

    需要匹配的字符串输出结果解释
    “mtss”mts
    “mmts”None因为第一个是m符合, 下一个是m不是t, 结束匹配返回None
    “.com.mts”None从头开始,第一个不对就结束了
    “mts.com.mts”mts找到就结束, 找到一个就结束了, 尽管后面还有
    1
    2
    3
    4
    5
    result = re.match("mts", "mts.com.mts")
    print(result.group()) # 结果为"mts";

    result = re.match("mts", ".com.mts")
    print(result.group()) # 无匹配结果,返回None,调用报错
  • 语法:match() search("正则表达式的规则", "需要匹配的字符串")

  • 返回值: <class '_sre.SRE_Match'>

  • 说明: ①整段匹配(不一定从头, 有就匹配) ②只匹配一个

    1
    2
    result = re.search("667", "776,667,667")
    print(result.group()) # 输出结果:667 尽管后面还有

3.3.3 查找全部 findall()

  • 语法: findall("正则表达式的规则", "需要匹配的字符串")

  • 返回值: <class 'list'> 列表, 其中每个元素为匹配的结果

  • 说明: ①整段匹配 ②匹配多个

  • 注意:⚠️: findall() 在规则中存在(捕获)组的时候, 只是返回组内容

    1
    2
    3
    4
    5
    6
    7
    8
    # 需求: 匹配所有含"667"的字符
    result = re.findall("667", "776,667,667")
    print(result) # ['667', '667'] 将所有能匹配的都匹配了

    # 注意⚠️: 当规则中有组的时候, 只返回组的内容, 并不会返回整个的内容
    users_email = "[email protected]#[email protected]#[email protected]"
    result = re.findall("(\w{4,20})@(qq|gmail|163|126|sina)\.com", users_email)
    print(result) # [('12345', 'qq'), ('wocwoc', 'gmail'), ('tbltbl', '163')]

3.3.4 查找全部返回迭代器 finditer()

  • 语法: finditer("正则表达式的规则", "需要匹配的字符串")

  • 返回值: <class 'callable_iterator'> 可以理解为匹配对象的列表(不是真列表,是一个可迭代的类型)

  • 作用: 对比findall(), 不仅可以找到, 还可以找到每个那段匹配, 组值等 // 我一般用findall(), finditer()用于检查

  • 说明: ①整段匹配 ②匹配多个 ③返回多个<class '_sre.SRE_Match'>类型的迭代器 ④规则存在分组时不会和findall()一样

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 需求: ①匹配users_email(字符串)的邮箱 ②获取是字符串的那些段匹配 ③获取都有哪些邮箱后缀
    users_email = "[email protected]#[email protected]#[email protected]"

    result = re.finditer("\w{4,20}@(qq|gmail|163|126|sina)\.com", users_email)
    for i in result:
    print(i.span(), ":", i.group(), "后缀是:", i.group(1))
    """ 输出结果
    (0, 12) : [email protected] 后缀是: qq
    (13, 29) : [email protected] 后缀是: gmail
    (30, 44) : [email protected] 后缀是: 163
    """

3.3.5 规则定义模型complie()

  • 语法: complie("正则表达式的规则")

  • 返回值: <class '_sre.SRE_Pattern'> 规则类型, 也是上面4个方法的第一个参数需要传递的类型

  • 说明: 定义一个规则, ①可以直接用于匹配 ②也可以作为规则传入

    1
    2
    3
    4
    5
    users_email = "[email protected]#[email protected]#[email protected]"

    patten = re.compile("\w{4,20}@(?:qq|gmail|163|126|sina)\.com") # 用于定义一个匹配规则
    print(patten.findall(users_email)) # 可以直接用它调用方法
    print(re.findall(patten, users_email)) # 也可以当成规则传入

3.3.6 替换 sub()

  • 语法: sub("正则表达式的规则", "替换为什么", "目标替换文本")

  • 返回值: <class 'str'> 普通的字符串类型, 为替换后的文本

  • 说明: ①和str.replace()原生的替换比起来, 就是可以正则替换

    1
    2
    3
    result = re.sub("maoliang", "<forbidden>", "wo zui xi huan maoliang la , maoliang")
    print(type(result))
    print(result) # wo zui xi huan <forbidden> la , <forbidden>

3.3.7 自动转义特殊正则字符 escape()

  • 语法: result=escape("存在特殊字符") 将所有的特殊字符进行转义

  • 返回值: <class 'str'>

  • 作用: 省去了一个手写转义的麻烦

  • 说明: 很有用, PAT那题就因为这个, 没有考虑到违禁词中存在特殊正则字符

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 帮你自动完成对特殊正则字符转义
    print(re.escape(".*{m}+^&")) # "\.\*\{m\}\+\^\&"

    # 不转义无法匹配
    result = re.findall(".*{m}+^&", ".*{m}+^&")
    print(result) # 输出结果: []

    # 转义后可以匹配
    result = re.findall(re.escape(".*{m}+^&"), ".*{m}+^&")
    print(result) # 输出结果: ['.*{m}+^&']

3.3.8 其他开关参数

  • 语法: 可以在上述所有的方法中, 作为最后一个参数进行传递, 可以修改规则

    修饰符描述
    re.I使匹配对大小写不敏感
    re.L做本地化识别(locale-aware)匹配
    re.M多行匹配,影响 ^$
    re.S使 . 匹配包括换行在内的所有字符
    re.U根据Unicode字符集解析字符。这个标志影响 \w, \W, \b, \B.
    re.X该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。
    1
    2
    3
    4
    5
    6
    # 以re.S举例
    result = re.search("Hello.World", "Hello\nWorld")
    print(result) # 结果将是 None,因为.不匹配换行符

    result = re.search("Hello.World", "Hello\nWorld", re.S)
    print(result.group()) # <_sre.SRE_Match object; span=(0, 11), match='Hello\nWorld'>

第七节 作业

爬音乐 https://houzi8.com/peiyue/qingyinyue/2.html

需求: 爬取小说排行前10页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# =================================
# @Time : 2024年12月03日
# @Author : 明廷盛
# @File : 11.正则表达式爬取小说排行.py
# @Software: PyCharm
# @ProjectBackground: 需求: 爬取[小说](https://www.77xsw.com/top/all_0_1.html)排行前10页
# =================================
import requests
import re


def grab_novel_rank(page):
aim_url = f"https://www.77xsw.com/top/all_0_{page}.html"
headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
}
result = requests.get(aim_url, headers=headers).content.decode()
# print(result)
# <a href="/35/35259/">[科幻]卡牌密室(重生)</a>
"""
你这个'<div class="col-12 col-md-6">.*?<a href="(.*?)">(.*?)</a>.*?</div>'为什么不对, 因为html的标签之间会有\n,
这就导致了<div>...\n<dd>\n...<a>中间的...<dd>...不能直接.*?去匹配(因为.不能匹配\n)
"""
pattern = re.compile('<a href="(.*?)">(.*?)</a></h3></dd>') # 确实不建议html用正则, 太难受
match_results = pattern.findall(result)
for i in match_results:
print(i[1], "https://www.77xsw.com/" + i[0])


if __name__ == '__main__':
for i in range(10):
print(f"page{i + 1}")
grab_novel_rank(i)

第四章 处理json数据 jsonpath包

JsonPath是一种可以快速解析json数据的方式,JsonPath对于JSON来说,相当于XPath对于XMLJsonPath用来解析多层嵌套的json数据。

官网:https://goessner.net/articles/JsonPath/

想要在Python编程语言中使用JsonPathjson数据快速提取,需要安装jsonpath模块

1
pip install jsonpath -i https://pypi.tuna.tsinghua.edu.cn/simple

第一节 jsonpath常用语法

代码示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import jsonpath

info = {
"error_code": 0,
"stu_info": [
{
"id": 2059,
"name": "小白",
"sex": "男",
"age": 28,
"addr": "河南省济源市北海大道xx号",
"grade": "天蝎座",
"phone": "1837830xxxx",
"gold": 10896,
"info": {
"card": 12345678,
"bank_name": '中国银行'
}
},
{
"id": 2067,
"name": "小黑",
"sex": "男",
"age": 28,
"addr": "河南省济源市北海大道xx号",
"grade": "天蝎座",
"phone": "87654321",
"gold": 100
}
]
}

"""
未使用jsonpath时,提取dict时的方式
"""

res = info["stu_info"][0]['name'] # 取某个学生姓名的原始方法:通过查找字典中的key以及list方法中的下标索引
print(res) # 输出结果是:小白
res = info["stu_info"][1]['name']
print(res) # 输出结果是:小黑

print("----------我是分割线----------")

"""
使用jsonpath时,提取dict时的方式
"""

res1 = jsonpath.jsonpath(info, '$.stu_info[0].name') # $表示最外层的{}, . 表示子节点的意思
print(res1) # 输出结果是list:['小白']
res2 = jsonpath.jsonpath(info, '$.stu_info[1].name')
print(res2) # 输出结果是list:['小黑']

res3 = jsonpath.jsonpath(info, '$..name') # 嵌套n层也能取到所有学生姓名信息,$表示最外层的{},..表示模糊匹配
print(res3) # 输出结果是list:['小白', '小黑']

res4 = jsonpath.jsonpath(info, '$..bank_name')
print(res4) # 输出结果是list:['中国银行']

第二节 jsonpath练习

jsonpath对比XPath

练习代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import jsonpath

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

# 1. 提取第1本书的title
print("\n1. 提取第1本书的title")
ret = jsonpath.jsonpath(info, "$.store.book[0].title")
print(ret)

ret = jsonpath.jsonpath(info, "$['store']['book'][0]['title']")
print(ret)

# 2. 提取2、3、4本书的标题
print("\n2. 提取2、3、4本书的标题")
ret = jsonpath.jsonpath(info, "$.store.book[1,2,3].title")
print(ret)
ret = jsonpath.jsonpath(info, "$.store.book[1,2,3]['title']")
print(ret)
ret = jsonpath.jsonpath(info, "$.store.book[1:4]['title']")
print(ret)

# 3. 提取1、3本书的标题
print("\n3. 提取1、3本书的标题")
ret = jsonpath.jsonpath(info, "$.store.book[::2].title")
print(ret)

# 4. 提取最后一本书的标题
print("\n4. 提取最后一本书的标题")
ret = jsonpath.jsonpath(info, "$.store.book[(@.length-1)].title")
print(ret)
ret = jsonpath.jsonpath(info, "$.store.book[-1:].title")
print(ret)

# 5. 提取价格小于10的书的标题
print("\n5. 提取价格小于10的书的标题")
ret = jsonpath.jsonpath(info, "$.store.book[?(@.price < 10)].title")
print(ret)

# 6. 提取价格小于或者等于20的所有商品的价格
print("\n6. 提取价格小于或者等于20的所有商品的价格")
ret = jsonpath.jsonpath(info, "$..*[?(@.price <= 20)].price")
print(ret)

# 7. 获取所有书的作者
print("\n7. 获取所有书的作者")
ret = jsonpath.jsonpath(info, "$.store.book[::].author")
print(ret)
ret = jsonpath.jsonpath(info, "$.store.book[*].author")
print(ret)

# 8. 获取所有作者
print("\n8. 获取所有作者")
ret = jsonpath.jsonpath(info, "$..author")
print(ret)

# 9. 获取在store中的所有商品(包括书、自行车)
print("\n9. 获取在store中的所有商品(包括书、自行车)")
ret = jsonpath.jsonpath(info, "$..store")
print(ret)

# 10. 获取所有商品(包括书、自行车)的价格
print("\n10. 获取所有商品(包括书、自行车)的价格")
ret = jsonpath.jsonpath(info, "$.store..price")
print(ret)

# 11. 获取带有isbn的书
print("\n11. 获取带有isbn的书")
ret = jsonpath.jsonpath(info, "$..book[?(@.isbn)]")
print(ret)

# 12. 获取不带有isbn的书
print("\n12. 获取不带有isbn的书")
ret = jsonpath.jsonpath(info, "$..book[?([email protected])]")
print(ret)

# 13. 获取价格在5~10之间的书
print("\n13. 获取价格在5~10之间的书")
ret = jsonpath.jsonpath(info, "$..book[?(@.price>=5 && @.price<=10)]")
print(ret)

# 14. 获取价格不在5~10之间的书
print("\n13. 获取价格在5~10之间的书")
ret = jsonpath.jsonpath(info, "$..book[?(@.price<5 || @.price>10)]")
print(ret)

# 15. 获取所有的元素
print("\n15. 获取所有的元素")
ret = jsonpath.jsonpath(info, "$..")
print(ret)
ret = jsonpath.jsonpath(info, "$..*")
print(ret)
  • Title: 5.数据提取方式(json,正则)
  • Author: 明廷盛
  • Created at : 2025-02-15 14:34:49
  • Updated at : 2025-02-09 15:55:00
  • Link: https://blog.20040424.xyz/2025/02/15/🐍爬虫工程师/第一部分 爬虫基础/5.数据提取方式(json,正则)/
  • License: All Rights Reserved © 明廷盛