【问题标题】:Select lots of known IDs from a big JSON document efficiently从大型 JSON 文档中高效地选择大量已知 ID
【发布时间】:2020-12-11 21:25:12
【问题描述】:

我正在尝试通过 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 list 超过 50 左右,它仍然会变慢

【问题讨论】:

  • 与您的性能问题无关,但echo $pagejson | 可能会损坏您的数据。总是总是引用你的扩展; echo "$pagejson" | 不会扩展 glob、合并空格等。
  • 啊,编辑后好多了;这确实解释了它为什么起作用,以及为什么它很慢。
  • (顺便说一句,for id in $(echo $pid) 只是写for id in $pid 的一种较慢的方式;您可以在 IFS 上进行拆分,而无需任何命令替换)。
  • 顺便说一句,请注意,要获得经过测试的答案,您需要提供足以进行测试的示例输入;它需要是真正的 JSON,而不是伪代码,需要包含与示例中的数字一致的数字等。
  • ...您最近所做的编辑——在我添加了我的答案之后——似乎反映了我的 cmets,而不是我的答案。只有预期的答案才能真正解决您的问题; cmets只是旁白。 (如果我认为他们会解决你的问题,我会把它们放在答案中!)

标签: json bash select jq processing-efficiency


【解决方案1】:

每个 id 调用一次 jq 总是很慢。不要那样做——只调用一次 jq,然后让它与整个集合匹配。

您可以通过将整个以逗号分隔的 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"

【讨论】:

    【解决方案2】:

    以下解决方案使用与 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 值都是数字,则此条件成立。

    【讨论】:

      【解决方案3】:

      以下是对原问题的回应,提出:

      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

      【讨论】:

      • OP 在revision 2 中添加了循环,该循环是在发布此(或任何)答案之前进行的。
      • @CharlesDuffy - 无论编辑交错的确切时间是什么,我的原始帖子都是对原始问题的回应。
      • @CharlesDuffy - 顺便说一句,在修订版 2 中,即使有 for 循环,也没有基于逗号的拆分,因此仍然只有一次迭代。
      • 我假设 OP 设置了IFS=,。当然,这确实需要做出假设。
      【解决方案4】:

      以下解决方案,基于 Charles 发布的解决方案 Duffy (*),如果 $pid 中每个指定的 id 值都可以使用 在 .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 值都是数字,则此条件成立。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-07-08
        相关资源
        最近更新 更多