当前位置: 首页 > 科技观察

如何从深度嵌套的JSON中快速找到特定的Key

时间:2023-03-16 23:17:15 科技观察

在爬虫开发过程中,我们经常会遇到一些加载Ajax的接口返回JSON数据。如下图,是推特的用户时间线接口,返回一个3000多行的深度嵌套的JSON:cursor字段是请求下一页的必填字段,必须读取它的值,拼接成请求下一页内容的请求URL。现在的问题是,这个JSON中的游标字段在哪里?从最外层开始,如何读取最内层游标中value域的值呢?我知道现在已经有一些第三方库可以直接根据字段名读取JSON内部任意深度的值,但是用别人的东西还不如自己写个轮子过瘾。所以今天我们自己写一个模块,我叫它JsonPathFinder,传入一个JSON字符串和要读取的字段名,返回从最外层到这个字段的路径。效果演示我们使用Python之父桂大叔的推特时间线作为演示。运行后效果如下图所示:可以看到,从最外层一路读取到游标字段需要很多字段名,对应于InJSON,如下图所示:由于有entries字段列表中一共有20个元素,这里的18和19其实对应的是倒数第二个倒数第二个数据。其中,倒数第二个光标对应本页第一条推文,倒数第二个光标对应本页最后一条推文。所以当我们要翻页时,应该使用倒数第二页的光标。让我们尝试读取结果:数据非常容易获得。无需再用肉眼寻找JSON中的字段。原理分析JsonPathFinder的原理并不复杂。所有代码加上空行只有32行,如下图所示:由于JSON中一个字段可能出现多次,所以find_one方法从外层返回第一行到目标字段路径。而find_all方法返回从外层到目标字段的所有路径。核心算法是iter_node方法。该方法将JSON字符串转换为Python字典或列表后,采用深度优先遍历整个数据,记录其经过的每一个字段,如果遇到列表,则将列表的索引作为Key。结束这条路径,直到遍历完目标字段,或者某个字段的值不是列表或字典,继续遍历下一个节点。代码第10-15行分别处理列表和字典。对于字典,我们将键和值分开,写作:forkey,valueinxxx.items():...对于列表,我们将索引和元素分开,写作:forindex,elementinenumerate(xxx):...所以如第11行和第13行,使用generatorcomprehensions分别处理字典和列表,使得得到的key_value_iter生成器对象可以在第16行通过相同的for循环进行迭代。我们知道除了字典和列表之外,还有很多其他的对象可以迭代Python,但我这里只处理字典和列表。也可以尝试修改第10-15行的条件判断,增加对其他可迭代对象的处理逻辑。代码的第16-22行迭代处理过的键值。首先在current_path列表中记录到当前字段的迭代路径。然后判断当前字段是否为目标字段。如果是,则产生当前路径。如果当前路径的值是一个列表或者字典,那么递归的把这个值传给iter_node方法,进一步检查里面是否有target字段。需要注意的是,无论当前字段是否为目标字段,只要其值为列表或字典,就需要继续迭代。因为即使当前字段的名字是目标字段,它里面也可能有一个后代字段的字段名也是目标字段名。对于普通函数,要递归调用,直接返回当前函数(参数)即可。但是对于生成器来说,要递归调用,需要从当前函数名(参数)使用yield。由于iter_node方法返回的是generator对象,在find_one和find_all方法中,for循环的每次迭代都可以得到一条从第20行抛出到目标字段的路径。在find_one方法中,当我们得到第一条路径后,就不再继续迭代,这样可以节省很多时间,减少迭代次数。正确使用这个工具后,我们可以直接用它来分析数据,也可以用它来辅助分析数据。比如推特时间线的文本是full_text,我可以直接使用JsonPathFinder获取所有的文本:但是有时候,除了获取文本之外,我们还需要每条推文的其他信息,如下图所示:可以看到,在这种情况下,我们可以先获取外层到full_text的路径列表,然后手动处理该列表以辅助开发:从打印出来的路径列表可以看出,我们只需要获取globalObjects->tweets就是这样。它的值为20个字典,每个字典的Key是推文的ID,Value是推文的详情。这时候,如果我们手动修改代码,也可以很方便的提取出一条推文的所有字段。