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 值都是数字,则此条件成立。