Select 大量来自大型 JSON 文档的已知 ID
Select lots of known IDs from a big JSON document efficiently
我正在尝试通过 bash 中的 jq 从 json 获取一些值。小值它工作得很好但是大 json 它工作得太慢,比如每 2-3 秒 1 个值。我的代码示例:
json=$(curl -s -A "some useragent" "url" )
pid=$(cat idlist.json | jq '.page_ids[]')
for id in $pid
do
echo $pagejson|jq -r '.page[]|select(.id=='$id')|.url'>>path.url
done
“pid”是我在 运行 脚本之前键入的 ID 列表。它可能包含 700-1000 个 ID。 json
的示例对象
{
"page":[
{
"url":"some url",
"id":some numbers
},
{
"url":"some url",
"id":some numbers
}
]
}
有什么办法可以加快速度吗?在 javascript 它比它工作得更快。 javascript 示例:
//First sort object with order
var url="";
var sortedjson= ids.map(id => obj.find(page => page.id === id));
//Then collect url
for ( x=0 ; x < sortedjson.length;x++) {
url+=sortedjson[x].url
};
我是否应该像 javascript 那样对 json 进行排序以获得更好的性能?我没有尝试过,因为不知道如何。
编辑:
将“pid”变量替换为 json 以使用更少的代码,并将 for id in $(echo $pid)
替换为 for id in $pid
。
但如果 id 列表超过 50
,它仍然会变慢
每个 ID 调用一次 jq
总是很慢。不要那样做——只调用 jq 一次,让它与整个集合匹配。
您可以通过将整个 comma-separated id 列表传递到您的 jq 副本中,并让 jq 自己完成将该字符串拆分为单个项目(然后将它们放入字典中)的工作来实现这一点用于快速访问)
例如:
pid="24885,73648,38758,8377,747"
jq --arg pidListStr "$pid" '
($pidListStr | [split(",")[] | {(.): true}] | add) as $pidDict |
.page[] | select($pidDict[.id | tostring]) | .url
' <<<"$pagejson"
以下是对原问题的回复,其中提出:
pid="24885,73648,38758,8377,747"
echo $pagejson|jq -r '.page[]|select(.id=='$pid')|.url'
(根据对问题的后续编辑,似乎意图是分别迭代 id 值,每个值调用 jq 一次。这也是一个坏主意,但可以单独处理回复。)
对原问题的回应
基于jq的调用有几个问题
像最初所做的那样插入 $pid
。
主要问题是您的查询在扩展时包含此 select
语句:
select(.id==24885,73648,38758,8377,747)
而您的意图显然是:
select(.id==(24885,73648,38758,8377,747))
不难看出两者存在巨大差异,影响功能和性能。
由于您没有提供有关预期输入的任何提示,因此建议如何优化查询是不可行的。不过,为了说明这一点,假设已知输入中的 .id 值是不同的。然后,一旦找到查询中的所有 id,就可以停止执行。
通常,通过字符串插值传递 shell 变量不是一个好主意。要考虑的一些替代方案是使用 --arg
或 --argjson
.
以下解决方案使用与 Charles Duffy (*) 发布的解决方案相同的方法,但仅适用:
如果 $pid 中每个指定的 id 值在 .page 数组中的 JSON 个对象中最多出现一次;或
如果目标是为 $pid 中的每个 id 从 .page 数组中提取最多一个对应的对象。
我们的想法是,一旦找到某个 id,就从字典中删除它,并在找到所有 id 后停止。
jq --arg pidListStr "$pid" '
($pidListStr | [splits(" *, *") | {(.): true}] | add) as $pidDict
| label $finish
| foreach .page[] as $page ($pidDict + {emit:null};
if length == 1 then break $finish
else ($page.id | tostring) as $id
| if .[$id] then delpaths([[$id]]) | .emit = $page.url
else .emit = null
end
end;
.emit // empty )
'
(*) 警告
在这里使用 $pidDict 假设没有“冲突”;如果 .page 对象中的所有 id 值都是数字,则此条件成立。
以下解决方案,基于 Charles 发布的解决方案
Duffy(*),如果每个指定的id值都在$pid中可以使用
在 .page 数组的 JSON 个对象中作为 id 最多出现一次。
我们的想法是在找到所有 $pid id 时停止。
这可以通过以下辅助函数来完成:
def first_n(stream; $n):
label $done
| foreach stream as $x (-1; .+1; if . >= $n then break $done else $x end);
解法可以写成:
($pidListStr | [splits(" *, *") | {(.): true}] | add) as $pidDict
| ($pidDict|length) as $n
| first_n(.page[] | select($pidDict[.id | tostring]) | .url; $n)
此解决方案类似于使用 foreach
发布在其他地方的解决方案
在此页面上,但更简单并且可能稍微更有效
字典一旦构建,就不会改变。
使用foreach
的解决方案,但是,如果
.page 数组中的对象不是唯一的,如果目标是
为 $pid 中的每个 id 提取最多一个对应的对象
.page 数组。
(*) 警告
这里使用 $pidDict 假设没有“冲突”;如果 .page 对象中的所有 id 值都是数字,则此条件成立。
我正在尝试通过 bash 中的 jq 从 json 获取一些值。小值它工作得很好但是大 json 它工作得太慢,比如每 2-3 秒 1 个值。我的代码示例:
json=$(curl -s -A "some useragent" "url" )
pid=$(cat idlist.json | jq '.page_ids[]')
for id in $pid
do
echo $pagejson|jq -r '.page[]|select(.id=='$id')|.url'>>path.url
done
“pid”是我在 运行 脚本之前键入的 ID 列表。它可能包含 700-1000 个 ID。 json
的示例对象{
"page":[
{
"url":"some url",
"id":some numbers
},
{
"url":"some url",
"id":some numbers
}
]
}
有什么办法可以加快速度吗?在 javascript 它比它工作得更快。 javascript 示例:
//First sort object with order
var url="";
var sortedjson= ids.map(id => obj.find(page => page.id === id));
//Then collect url
for ( x=0 ; x < sortedjson.length;x++) {
url+=sortedjson[x].url
};
我是否应该像 javascript 那样对 json 进行排序以获得更好的性能?我没有尝试过,因为不知道如何。
编辑:
将“pid”变量替换为 json 以使用更少的代码,并将 for id in $(echo $pid)
替换为 for id in $pid
。
但如果 id 列表超过 50
每个 ID 调用一次 jq
总是很慢。不要那样做——只调用 jq 一次,让它与整个集合匹配。
您可以通过将整个 comma-separated id 列表传递到您的 jq 副本中,并让 jq 自己完成将该字符串拆分为单个项目(然后将它们放入字典中)的工作来实现这一点用于快速访问)
例如:
pid="24885,73648,38758,8377,747"
jq --arg pidListStr "$pid" '
($pidListStr | [split(",")[] | {(.): true}] | add) as $pidDict |
.page[] | select($pidDict[.id | tostring]) | .url
' <<<"$pagejson"
以下是对原问题的回复,其中提出:
pid="24885,73648,38758,8377,747"
echo $pagejson|jq -r '.page[]|select(.id=='$pid')|.url'
(根据对问题的后续编辑,似乎意图是分别迭代 id 值,每个值调用 jq 一次。这也是一个坏主意,但可以单独处理回复。)
对原问题的回应
基于jq的调用有几个问题
像最初所做的那样插入 $pid
。
主要问题是您的查询在扩展时包含此 select
语句:
select(.id==24885,73648,38758,8377,747)
而您的意图显然是:
select(.id==(24885,73648,38758,8377,747))
不难看出两者存在巨大差异,影响功能和性能。
由于您没有提供有关预期输入的任何提示,因此建议如何优化查询是不可行的。不过,为了说明这一点,假设已知输入中的 .id 值是不同的。然后,一旦找到查询中的所有 id,就可以停止执行。
通常,通过字符串插值传递 shell 变量不是一个好主意。要考虑的一些替代方案是使用 --arg
或 --argjson
.
以下解决方案使用与 Charles Duffy (*) 发布的解决方案相同的方法,但仅适用:
如果 $pid 中每个指定的 id 值在 .page 数组中的 JSON 个对象中最多出现一次;或
如果目标是为 $pid 中的每个 id 从 .page 数组中提取最多一个对应的对象。
我们的想法是,一旦找到某个 id,就从字典中删除它,并在找到所有 id 后停止。
jq --arg pidListStr "$pid" '
($pidListStr | [splits(" *, *") | {(.): true}] | add) as $pidDict
| label $finish
| foreach .page[] as $page ($pidDict + {emit:null};
if length == 1 then break $finish
else ($page.id | tostring) as $id
| if .[$id] then delpaths([[$id]]) | .emit = $page.url
else .emit = null
end
end;
.emit // empty )
'
(*) 警告
在这里使用 $pidDict 假设没有“冲突”;如果 .page 对象中的所有 id 值都是数字,则此条件成立。
以下解决方案,基于 Charles 发布的解决方案 Duffy(*),如果每个指定的id值都在$pid中可以使用 在 .page 数组的 JSON 个对象中作为 id 最多出现一次。
我们的想法是在找到所有 $pid id 时停止。 这可以通过以下辅助函数来完成:
def first_n(stream; $n):
label $done
| foreach stream as $x (-1; .+1; if . >= $n then break $done else $x end);
解法可以写成:
($pidListStr | [splits(" *, *") | {(.): true}] | add) as $pidDict
| ($pidDict|length) as $n
| first_n(.page[] | select($pidDict[.id | tostring]) | .url; $n)
此解决方案类似于使用 foreach
发布在其他地方的解决方案
在此页面上,但更简单并且可能稍微更有效
字典一旦构建,就不会改变。
使用foreach
的解决方案,但是,如果
.page 数组中的对象不是唯一的,如果目标是
为 $pid 中的每个 id 提取最多一个对应的对象
.page 数组。
(*) 警告
这里使用 $pidDict 假设没有“冲突”;如果 .page 对象中的所有 id 值都是数字,则此条件成立。