自学Python第十五天-爬虫解析工具 RE 、BS4 、 xpath 和 jsonpath
使用 python 编写爬虫一个重要的步骤就是在爬到的网页内容中,抓取所需要的数据。这里就需要使用各种解析工具了。
解析工具
解析工具用来分析爬取到的内容,常用的工具有:
- 正则表达式
其实对于解析工具多数都会首推正则表达式,解析速度最快,因为它最好用,但是也最难用。由于正则表达式的使用范围广,用法复杂,所以单独进行研究。这里只研究 python 中如何使用正则表达式。 - BeautifulSoup
BeautifulSoup 是个第三方库,它用起来最简单,但是解析速度是最慢的,这里使用版本4。 - lxml(xpath)
lxml 也是个第三方库,解析速度较快,使用难度还算简单。如果想使用的好,还需要 xpath 的相关知识。
re 库
re 库是 python 用来操作正则表达式的一个内置模块,直接引用就可以使用了。
注,很多时候返回的是一个 Match 对象,可以使用 group 方法获取对象中的匹配结果
常用的方法
re 模块常用的方法有:
- findall :查找所有,返回字符串列表,如进行了分组则返回元组列表,
re.findall(pattern, string[,flags])->tuple
lst = re.findall('m', 'mai le fo len, mai ni mei!')
print(lst) # ['m', 'm', 'm']
lst = re.findall(r"\d+", '5点之前,你要给我5000万')
print(lst) # ['5', '5000']
- search :查找匹配,返回第一个查找结果的Match对象,如果没有匹配则返回 None,
re.search(pattern,string[,flags])->Match
ret = re.search(r"\d", '5点之前,你要给我5000万').group()
print(ret) # 5
- match :从字符串的开始进行匹配,返回Match对象
- finditer :类似findall,但是返回是Match对象的迭代器对象。
- compile :预加载正则表达式,返回正则对象
re.compile(string[,flag])->Re
obj = re.complie(r'\d')
ret = obj.search('5点之前,你要给我5000万')
- sub :字符替换,
re.sub(pattern, repl, string, count=0, flags=0),repl 是替换的字符串(也可以是一个函数),count 是替换的最大次数(0表示不限次)
import re
phone = '2004-959-559 # 这是一个电话号码'
# 删除注释
num = re.sub('#.*$','',phone)
print(num) # 2004-959-559
# 移除非数字的内容
num = re.sub(r'\D','',phone)
print(num) # 2004959559
- split :字符串分割,
re.split(pattern, string, maxsplit=0, flags=0)
import re
names = '关羽; 张飞, 赵云, 马超, 黄忠 李逵'
name_list = re.split(r'[;,\s]\s*', names)
print(name_list)
flag 参数
可以发现,很多查找方法都有个 flag 参数,它表示了匹配模式,取值可以使用 “|” 表示同时生效,例如 re.I | re.M 。可选值有:
| 可选参数 | 全拼 | 说明 |
|---|---|---|
| re.I | IGNORECASE | 忽略大小写 |
| re.M | MULTILINE | 多行模式,改变 ‘^’ 和 ‘$’ 的行为 |
| re.S | DOTALL | 点的任意匹配模式,改变 ‘.’ 的行为(例如能够匹配换行等) |
| re.L | LOCALE | 使预定字符类 \w \W \b \B \s \S 取决于当前区域设定 |
| re.U | UNICODE | 使预定字符类 \w \W \b \B \s \S \d \D 取决于 unicode 定义的字符属性 |
| re.X | VERBOSE | 详细模式。这个模式下正则表达式可以是多行、忽略空白字符,并可以加入注释 |
分组
当只需要匹配结果中的一部分时,可以将此部分用括号 () 括起来,进行分组,RE模块会只返回分组内的内容
import re
s = """
苹果,苹果是绿色的
橙子,橙子是橙色的
香蕉,香蕉是黄色的
"""
for temp in re.findall(r'^(.*),', s, re.M): # 不会返回匹配到的 , 字符
print(temp)
需要匹配多个分组时,可以这样写:
import re
content = '''张三,手机号码15945678901
李四,手机号码13945677701
王二,手机号码13845666901'''
for temp in re.findall(r'^(.+),.+?(\d+)', content, re.M):
print(temp)
"""
执行结果:
('张三', '15945678901')
('李四', '13945677701')
('王二', '13845666901')
"""
也可以对分组起别名,通过别名调用
import re
s = """
<div class='class1'><span id='1'>孙悟空</span></div>
<div class='class2'><span id='2'>猪八戒</span></div>
<div class='class3'><span id='3'>唐僧</span></div>
<div class='class4'><span id='4'>沙和尚</span></div>
<div class='class5'><span id='5'>白龙马</span></div>
"""
obj = re.compile(r"<div class='.*?'><span id='\d+'>(?P<gp1>.*?)</span></div>", re.S)
res = obj.finditer(s) # 需要注意的是,不能使用 findall,因为 findall 返回的是元组。
for it in res:
print(it.group('gp1'))
此例中,(?P ) 内为分组,分组名称为 <> 内的 gp1 ,分组内容就是括号中的 .*? 所匹配的内容。使用 group 方法调用分组信息,如果此方法没有参数则是返回全部匹配信息,有参数则返回参数指定的分组信息。
Match 类
re模块的查询返回的大都是 Match 类或 Match 列表,这个类记录了匹配的结果、匹配索引(位置)等信息。
获取匹配结果
默认获取到的匹配数据存在 Match 类的组里,可以使用 Match.group() 方法来获取匹配对象的组里的结果,也可以将组名称字符串作为参数传入,返回特定组的结果。或使用 Match.groups() 方法获取所有组的结果。
import re
pattern = re.compile('abc')
result = re.match(pattern, 'abcdefg')
print(result.group())
需要注意的是,如果正则表达式中使用了多个分组,则第一个分组(索引号为0)表示表达式本身匹配的结果,而不是分组结果。而使用 groups() 方法的返回值则剔除了 0 号组。
import re
pattern = re.compile('(cde).*g(.*)l')
result = re.search(pattern, 'abcdefghijklmn')
print(result.group(), result.group(1), result.group(2)) # Match.group() 相当于 Match.group(0)
# 运行结果
# cdefghijkl cde hijk
获取匹配索引
可以通过 Match.span() 方法获取匹配结果的索引,方法使用及参数同 group() 方法,例如:
import re
pattern = re.compile('abc')
result = re.match(pattern, 'abcdefg')
print(result.span()) # (0, 3)
返回值是起始字符在字符串中的索引号(第一个匹配的字符索引号)和结束字符在字符串中的索引 -1(其实返回的是第一个不匹配的字符的索引,所以最后一个匹配字符索引就是返回值-1)
BS4 解析
BS4 是一个第三方库,全称 beautiful soup 版本4。它可以根据 HTML 的标签来进行分析并获取需要的数据
BS4 的安装和引用
使用 pip install bs4 进行安装,使用 from bs4 import BeautifulSoup 引用 BS4
使用BS4需要注意的是必须安装解析器,BS4其实是方便调用解析器的一个工具。推荐使用的解析器如下表(注:markup为需要解析的文本):
| 解析器 | 使用方法 | 优势 | 劣势 |
|---|---|---|---|
| Python标准库 | BeautifulSoup(markup, "html.parser") | 1.Python的内置标准库 2.执行速度适中 3.文档容错能力强 | Python 2.7.3 或 3.2.2 前的版本中文档容错能力差 |
| lxml HTML解析器 | BeautifulSoup(markup, "lxml") | 1.速度快 2.文档容错能力强 | 需要安装C语言库 |
| lxml XML解析器 | BeautifulSoup(markup, "xml") | 1.速度快 2.唯一一个支持XML的解析器 | 需要安装C语言库 |
| html5lib | BeautifulSoup(markup, "html5lib") | 1.最好的容错性 2.以浏览器的方式解析文档 3.生成HTML5格式的文档 | 1速度慢 2.不依赖外部扩展 |
推荐使用 lxml作为解析器,因为效率更高。其次推荐使用标准库,因为不用另安装第三方库和C语言库。
另:如果一段HTML文档格式不正确的化,在不同解析器中返回的结果可能是不一样的。
使用bs4进行解析的步骤
使用 bs4 解析的步骤如下:
- 获取需要解析的内容
- 将解析内容交给 bs 处理,生成 bs 对象
- 从 bs 对象查找需要的数据
获取解析内容就是正常的获取页面源代码
import requests
url ='https://movie.douban.com/top250'
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36"
}
resp = requests.get(url, headers=headers)
生成 bs 对象
使用 BeautifulSoup(markup, parser)->BeautifulSoup 来生成 bs 对象
from bs4 import BeautifulSoup
page = BeautifulSoup(resp.text, "html.parser")
参数 markup 是输入的文档,参数 parser 为使用的解析器
bs 对象的常用方法
按标签查找
使用 find(name=None, attrs={}, recursive=True, string=None, **kwargs) 和 find_all(name=None, attrs={}, recursive=True, string=None, limit=None, **kwargs) 这两个方法来获取查找的数据对象。name 是标签名称,attrs 是标签属性,recursive 是否遍历符合条件的所有子节点,string 搜索文本中的字符串内容,limit 是最多查找结果的数量。
需注意的是,string 内容是完全匹配字符串,如果是不完全匹配,例如包含特定字符串,需要使用正则表达式 string=re.compile("string") 。name 参数也可以使用正则表达式进行匹配。
find 方法返回的是一个标签结果,类型是 bs4.element.Tag;find_all 方法返回的是一个结果集,类型是 bs4.element.ResultSet 。但是在使用中可以当成 BeautifulSoup 对象使用。另外 find 只查找符合条件的第一个标签。
# 查找并获取第一个 ol 标签
ol = page.find("ol", class_='grid_view') # class 是关键字,所以使用 class_
如果要避免使用关键字或常用字产生冲突,可以这样使用:
ol = page.find("ol", attrs={"class": "grid_view"})
# 参数中也可以不传入标签名,只按照属性筛选
ol = page.find(attrs={"class": "grid_view"})
这两者是等同的。
获取所有数据:
lis = ol.find_all("li") # 获取所有 li 标签,生成一个列表
根据获取的标签,取得标签的文本内容:
for li in lis:
title = li.find("span", attrs={"class": "title"})
print(title.text) # .text 为被标签的文本内容
find_all() 方法支持传入一个列表,匹配列表中任意元素的标签均在返回值列表中,即取或。如果提取标签不存在,则返回空列表。
CSS 选择器
BS支持大部分的CSS选择器,在 Tag 或 BeautifulSoup 对象的 .select() 方法中传入字符串参数,即可使用CSS选择器的语法找到标签 tag。
| 选择器名称 | 用法 |
|---|---|
| 标签选择器 | .select("title") |
| 层级选择器 | .select("body a") |
| 父子选择器 | .select("head > title") |
| 类选择器 | .select(".sister") |
| id选择器 | .select("#link1") |
| 属性选择器(含有特定属性的标签) | .select("[href='...']") |
需注意的是, .select() 方法返回值是一个 Tag 对象的列表
获取标签属性字典
可以使用 Tag.attrs 返回标签的属性字典。
格式化输出
对于一个 BeautifulSoup 对象,可以直接使用 print() 输出查看,但是如果格式不标准,例如是单行文本形式的,查看起来非常麻烦。这时候可以使用方法 BeautifulSoup.prettify() 进行格式化
import requests
from bs4 import BeautifulSoup
url = 'https://movie.douban.com/top250'
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36"
}
resp = requests.get(url, headers=headers)
page = BeautifulSoup(resp.text, "html.parser")
print(page.prettify())
此时输出结果就是带换行和缩进的HTML文档了,方便阅读。
获取标签的属性
使用另一个实例演示如何获取标签属性
import requests, time
from bs4 import BeautifulSoup
url = 'https://www.umei.cc/bizhitupian/weimeibizhi/'
resp = requests.get(url)
resp.encoding = 'utf-8'
# 获取主页面代码
main_page = BeautifulSoup(resp.text, "html.parser")
# 查找子页面超链接
a_list = main_page.find("div", attrs={"class": "pic-box"}).find("ul", attrs={"class", "pic-list after"}).find_all("a")
for a in a_list:
herf = 'https://www.umei.cc/' + a.get('href').strip('/') # 通过 get 方法获取标签属性的值
# 获取子页面源代码
resp_child = requests.get(herf)
resp_child.encoding = 'utf-8'
# 获取子页面需要的数据
child_page = BeautifulSoup(resp_child.text, 'html.parser')
img = child_page.find("section", attrs={"class": "img-content"}).find('img') # 获取 img 标签
src = img.get('src') # 获取 img 标签的 src 属性值
img_name = src.split('/')[-1] # 以 / 进行切割,取最后一部分,作为图片名称
img_resp = requests.get(src) # 请求图片
with open("./download/" + img_name, mode='wb') as f:
f.write(img_resp.content) # 图片内容写入文件
print('Over!!', img_name)
time.sleep(1) # 设置间隔时间,防止请求速度太快
即,使用 Tag.get(attrs_name)->String 来获取标签内的属性值
获取标签文本
可以使用标签对象的 get_text() 方法或 text 属性来获取标签文本
title = soup.select('title')
print(title.text) # 或 title.get_text()
xpath 解析
xpath 解析是一种新的解析方式,比 re 简单,比 bs 高效,所以会经常用到。xpath 是在 xml 文档中搜索内容的一种语言,类似正则表达式。
语法规则
xpath 使用路径表达式来选取文档中的节点或者节点集,遵循一定的规则:
| 表达式 | 描述 | 示例 | 结果 | 备注 |
|---|---|---|---|---|
| nodename | 选中该元素 | bookstore | 选择 bookstroe 标签元素 | 如不确定元素标签名称,可以使用通配符* |
/ | 从根节点选取 | /bookstore | 选择根元素 bookstore | 如果路径起始于/,则此路径代表到某元素的绝对路径 |
/ | 上下级元素过渡 | bookstore/book | 在 bookstore 元素中选择所有 book 子元素 | |
// | 从匹配选择的节点选择文档中的节点,而不考虑其位置 | //book | 选择所有的 book 元素,而不考虑其位置 | |
. | 选取当前节点 | .//title | 选择当前节点下,所有title节点 | 常用在跨节点继续查询中 |
.. | 选取当前节点的父节点 | |||
@ | 选择节点属性 | //book/title/@lang | 选择所有book下的title节点中的lang属性 | |
@ | 查询属性为特定值的节点 | //title[@lang='eng'] | 查询所有属性lang值为eng的title元素节点 | |
text() | 选择节点的文本内容 | //book/title/text() | 选择所有book节点下的title节点的文本 | |
text() | 根据文本内容查询节点 | //title[text()='数学'] | 查询所有文本为“数学”的title节点 | |
contains() | 选择包含特定文本的节点 | //title[contains(text(),'数学')] | 获取文本中包含“数学”的节点 | |
contains() | 选择属性包含特定字符的节点 | //a[contains(@href,'baidu')] | 选择href属性包含’baidu’字符的a节点 | |
[n] | 选择符合条件的第n个元素 | //bookstore/book[2] | 选择bookstore节点下的第2个book节点 | |
[last()] | 选择符合条件的最后元素 | /bookstore/book[last()] | 选择bookstore节点下的最后一个book节点 | 可以进行运算,例如倒数第二个节点为:/bookstore/book[last()-1] |
[position()] | 按照定位条件选择节点 | /bookstore/book[position()>1] | 选择bookstore下的元素,从第2个开始选择 | 如果从第2选到第4个则为:/bookstore/book[position()>1 and position()<5] |
[][] | 多条件查询 | //title[@lange=eng'][text()='数学'] | 查找满足多个条件的title节点 |
注意,xpath 中,第一个元素位置是1,最后一个是 last(),倒数第2个是 last()-1
lxml 的安装和引用
python 中,xpath 解析使用第三方库 lxml,需要注意的是 xpath 检索出来的数据都是列表。
使用 pip install lxml 安装 lxml库,使用 from lxml import etree 来引用
生成 xpath 对象
xpath 可以根据输入的数据类型不同指定处理格式,例如:
from lxml import etree
xml = """...""" # 定义一段 xml 文本
tree_xml = etree.XML(xml) # 生成基于 xml 的对象,可以使用 html 方法来解析 html 文本生成对象
tree_file = etree.parse('b.html') # 基于文件生成对象
查找数据
这里先定义一段 xml 文本
xml = """
<book>
<id>1</id>
<name>野花遍地香</name>
<price>1.23</price>
<nick>臭豆腐</nick>
<author>
<nick id="10086">周大强</nick>
<nick id="10010">周芷若</nick>
<nick class="joy">周杰伦</nick>
<nick class="jolin">蔡依林</nick>
<div>
<nick>热热热热</nick>
</div>
<div>
<nick>特别热</nick>
<div>
<nick>好热好热</nick>
</div>
</div>
<span>
<nick>span的热</nick>
</span>
</author>
<partner>
<nick id="ppc">胖胖陈</nick>
<nick id="ppbc">胖胖不陈</nick>
</partner>
</book>
"""
tree = etree.XML(xml) # 生成基于 xml 的对象
# result = tree.xpath('/book') # / 表示层级关系,第一个 / 是根节点
# 获取 name 的文本内容,使用 text()
# result = tree.xpath('/book/name/text()') # ['野花遍地香']
# 获取的节点不是唯一,输出的是同胞节点列表
# result = tree.xpath('/book/author/nick/text()') # ['周大强', '周芷若', '周杰伦', '蔡依林']
# 跨层级查找同名节点,使用 // 即跨层级查找所有后代节点,查找节点 nick
# result = tree.xpath('/book/author//nick/text()') # ['周大强', '周芷若', '周杰伦', '蔡依林', '热热热热', '特别热', '好热好热', 'span的热']
# 跨节点(任意节点)查找节点,使用通配符 *
result = tree.xpath('/book/author/*/nick/text()') # ['热热热热', '特别热', 'span的热']
print(result)
xpath 的筛选定位
除了按照层级和节点来获取需要的数据,还可以根据情况,进行具体的筛选定位。假设筛选对象为 b.html 文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>Title</title>
</head>
<body>
<ul>
<li><a href="http://www.baidu.com">百度</a></li>
<li><a href="http://www.google.com">谷歌</a></li>
<li><a href="http://www.sogou.com">搜狗</a></li>
</ul>
<ol>
<li><a href="feiji">飞机</a></li>
<li><a href="dapao">大炮</a></li>
<li><a href="huoche">火车</a></li>
</ol>
<div class="job">李嘉诚</div>
<div class="common">胡辣汤</div>
</body>
</html>
from lxml import etree
tree = etree.parse("b.html") # 以文件创建 xpath 对象
# 获取节点文本列表
# result = tree.xpath('/html/body/ul/li/a/text()') # ['百度', '谷歌', '搜狗']
# 获取第一个 li 节点里的 a 标签的文本内容,使用 [1] ,注意从1开始而不是0
# result = tree.xpath('/html/body/ul/li[1]/a/text()') # ['百度']
# 使用属性筛选定位标签,使用 [@属性名=属性值]
# result = tree.xpath('/html/body/ol/li/a[@href="dapao"]/text()') # ['大炮']
# print(result)
# 分次查找
ol_li_list = tree.xpath('/html/body/ol/li')
for li in ol_li_list:
# 从每一个 li 中提取文本信息
result = li.xpath('./a/text()') # 在li中继续寻找, . 表示当前节点 (相对路径)
result2 = li.xpath('./a/@href') # 获取 a 标签的 href 属性的值
print(result)
print(result2)
# 快速获取属性值列表
print(tree.xpath('/html/body/ul/li/a/@href'))
xpath 的一个应用实例
通常爬网页上的数据时,可以将相同类的数据做为一个块(div 的概念),然后使用循环获取每个块的需要的数据。
import requests
from lxml import etree
# 获取页面代码
url = 'https://beijing.zbj.com/search/f/?kw=saas&r=1'
resp = requests.get(url)
# 解析
html = etree.HTML(resp.text)
# 获取每一个块内容
divs = html.xpath('//div[@id="__nuxt"]//div[@class="search-content"]//div[@class="search-result-list-service"]/div')
# 从块内中获取需要的数据
for div in divs:
com_name = div.xpath('./a[@class="name-address"]//div[@class="shop-detail"]/div/text()')[0]
price = div.xpath('.//div[@class="price"]/span/text()')[0].strip('¥')
title = div.xpath('.//div[@class="bot-content"]/a/text()')[0]
location = div.xpath('.//div[@class="price"]/div/text()')[0].strip()
print(com_name, price, title, location)
xpath 节点转字符串
可以使用 etree.tostring() 方法将节点对象转为字符字节
html_str = etree.tostring(etree.HTML(resp.text))
使用注意
需要注意的是,xpath 获取的都是列表。
另可以使用 标签名称[contains(text(),"文本")] 这样来查找包含文本内容包含表达式中文本 的标签,也可以使用 标签名称[text()="文本"] 进行文本内容的绝对匹配查找标签。
jsonpath 模块
和 bs4、xpath不同,这两个工具常用来解析静态页面,jsonpath 通常用来解析动态获取的 json 数据。jsonpath 对于 json 来说,相当于 xpath 相当于 xml。
python 使用 jsonpath ,需要安装模块
pip install jsonpath
jsonpath 语法
类似于 xpath,jsonpath 也有自己的语法
| XPath | JSONPath | 描述 |
|---|---|---|
/ | $ | 根节点 |
. | @ | 当前节点 |
/ | . or [] | 子节点(下级节点) |
.. | 父节点 jsonpath 不支持 | |
// | .. | 不管位置,选择所有符合条件的节点 |
* | * | 所有节点 |
@ | 属性,json是键值对递归结构,不需要属性访问 | |
[] | [] | 迭代器标识(下标、索引、内容选值等) |
| | [,] | 支持迭代器中做多选 |
[] | ?() | 支持过滤操作,括号内部支持逻辑运算符 and、or、not、&&、||、!等 |
() | 支持表达式计算 | |
() | 分组,json不支持 |
需要注意的是jsonpath下标从0开始,而xpath是从1开始。
xpath 和 jsonpath 语法使用示例
| XPath | JSONPath | 结果 |
|---|---|---|
/store/book/author | $.store.book[*].author | store下所有book下的author |
//author | $..author | 所有的author |
/store/* | $.store.* | store下的所有节点 |
/store//price | $.store..price | store下所有的price |
//book[3] | $..book[2] | 所有book节点中的第3个 |
//book[last()] | $..book[(@.length-1)] | 最后一个book(使用索引) |
//book[last()] | $..book[-1:] | 最后一个book(使用切片) |
//book[@price] | $..book[?(@.price)] | 所有包含有price的book节点 |
//book[position()<3] | $..book[0,1] | 前两个book(使用索引) |
//book[position()<3] | $..book[:2] | 前两个book(使用切片) |
//book[@price<10] | $..book[?(@.price<10)] | 过滤book下的price小于10的book(xpath是book的属性,jsonpath是下级节点) |
//* | $..* | 所有元素和节点 |
jsonpath 的使用
使用 jsonpath 解析 json 数据,使用的是 jsonpath.jsonpath(obj,expr) 方法。此方法接受两个参数,obj 是字典,即需要解析的 json 数据;expr 是使用 jsonpath 语法的解析表达式。需要注意的是,此方法返回值是value的列表,如果没有匹配值则返回 False。另外,解析表达式中如果使用了字符串,必须使用单引号'括起来,整个解析表达式使用双引号"括起来,否则无法匹配。
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,
'info': {
"card": 9876543,
"bank_name": '农业银行'
}
}
]
}
ret_1 = jsonpath.jsonpath(info, '$.stu_info[0].name')
print(ret_1)
ret_2 = jsonpath.jsonpath(info, '$..name')
print(ret_2)
# 对于值为列表的节点必须确定索引或切片,也可以使用 * 通配符或 [::] 全部切片
ret_3 = jsonpath.jsonpath(info, "$['stu_info'][*]['info']['bank_name']") # 解析表达式中外层必须双引号,内层必须单引号
print(ret_3)
"""
执行结果:
['小白']
['小白', '小黑']
['中国银行', '农业银行']
"""
1582

被折叠的 条评论
为什么被折叠?



