Поток Parse Огромный файл JSON в маленькие файлы - PullRequest
2 голосов
/ 16 октября 2019

У меня около 96 gzip JSON, что составляет более 350 ГБ файла JSON после распаковки со следующей структурой

{
  "structe": {},
  "beta": {},
  "flow": {
    "1023": {
      "0101": {
        "-LEjllNyHqdHYGntO6vu": {
          "status": "1",
          "t": 1528736191996
        },
        "-LEjllcXKaVOQu3BDpHF": {
          "status": "1",
          "t": 1528736192996
        }
      },
      "0102": {
        "-LEjllNyHqdHYGntO6vu": {
          "status": "1",
          "t": 1528736191996
        },
        "-LEjllcXKaVOQu3BDpHF": {
          "status": "1",
          "t": 1528736192996
        }
      }
    },
    "1024": {
      "0103": {
        "-LEjllNyHqdHYGntO6vu": {
          "lat": 51.128676733981,
          "lng": -113.9318991267252,
          "status": "1",
          "t": 1528736191996
        },
        "-LEjllcXKaVOQu3BDpHF": {
          "lat": 51.128676733981,
          "lng": -113.9318991267252,
          "status": "1",
          "t": 1528736192996
        }
      }
    }
  }
}

Я не могу загрузить это в ОЗУ, Теперь я хочу передать этот файли перетащите путь flow->1023(let id1)->0101(let id2) в новый файл id1_id2.json. Любая мысль, как можно сделать это со скоростью. Вывод, который я ищу, похож на Имя файла = 1023_0101.json

{
        "-LEjllNyHqdHYGntO6vu": {
          "status": "1",
          "t": 1528736191996
        },
        "-LEjllcXKaVOQu3BDpHF": {
          "status": "1",
          "t": 1528736192996
        }
      }

Ответы [ 3 ]

2 голосов
/ 16 октября 2019

Вот решение, которое использует потоковый парсер jq для создания потока, состоящего из $ id1, $ id2 и соответствующего значения интереса;затем этот поток может быть передан в другой инструмент (например, awk, если это удобно) для создания желаемых файлов.

Далее мы используем atomize из поваренной книги jq:

  def atomize(s):
    fromstream(foreach s as $in ( {previous:null, emit: null};
      if ($in | length == 2) and ($in|.[0][0]) != .previous and .previous != null
      then {emit: [[.previous]], previous: $in|.[0][0]}
      else { previous: ($in|.[0][0]), emit: null}
      end;
      (.emit // empty), $in) ) ;

Основная jq-программа (запускаемая с --stream -n -c) будет тогда просто:

atomize(inputs)
| select(type == "object" and .flow)
| .flow
| keys_unsorted[] as $id1
| (.[$id1] | keys_unsorted[]) as $id2
| $id1, $id2, .[$id1][$id2]

Так что для каждого файла gzip, $ gz, конвейер будет выглядеть так:

gunzip -c $ gz |jq -nc --stream -f program.jq |awk ....

Пример использования awk для получения желаемого результата см. в jq, разделении огромного json массива и сохранении в файл с именем со значением

Caveat and Addendum

Потоковый парсер jq избегает использования оперативной памяти за счет скорости, поэтому обычно использование параметра --stream выполняется только в крайнем случае. Из описания проблемы видно, что вы можете обработать некоторые заархивированные файлы с помощью обычного анализатора jq, поэтому вы можете захотеть обработать эти файлы быстро, оставив подход «атомизировать» для тех файлов, которые слишком велики.

Осторожно

В описании проблемы не ясно, что следует делать в случае столкновения id1_id2.json. Если нет возможности такого столкновения, то, конечно, нет проблем. В противном случае это будет зависеть от программы, которая создает эти файлы для управления этим непредвиденным обстоятельством.

1 голос
/ 16 октября 2019

Вы можете использовать jq с опцией --stream, jq - I / O (Streaming) , которая читает текст в потоковом режиме, позволяя программам немедленно начать обработку больших текстов JSON. чем после завершения анализа (сохранение всего файла в оперативной памяти).

Предполагается, что строки входного идентификатора хранятся в контексте переменной оболочки

id1=1023; id2=0101

Передать вывод вашего огромного gzip вследующий фильтр

jq --arg v1 "$id1" --arg v2 "$id2" --stream 'fromstream(inputs)| objects | .flow[$v1][$v2]' > "$id1"_"$id2".json

(или), если имена идентификаторов не могут быть предварительно извлечены, и вам нужно выбрать их на ходу, напрямую используйте их имена как

jq --stream 'fromstream(inputs)| objects | .flow."1023"."0101"'
0 голосов
/ 16 октября 2019

Первое, что приходит мне в голову, - это обрабатывать файл как поток и читать его построчно. Уже есть некоторые библиотеки, которые обрабатывают файлы json как потоки. Например, вы можете получить фрагмент из библиотеки ijson :

Для JSON, например:

{
  "earth": {
    "europe": [
      {"name": "Paris", "type": "city", "info": { ... }},
      {"name": "Thames", "type": "river", "info": { ... }},
      // ...
    ],
    "america": [
      {"name": "Texas", "type": "state", "info": { ... }},
      // ...
    ]
  }
}

Обработка будет выглядеть следующим образом:

import ijson

parser = ijson.parse(urlopen('http://.../'))
stream.write('<geo>')
for prefix, event, value in parser:
    if (prefix, event) == ('earth', 'map_key'):
        stream.write('<%s>' % value)
        continent = value
    elif prefix.endswith('.name'):
        stream.write('<object name="%s"/>' % value)
    elif (prefix, event) == ('earth.%s' % continent, 'end_map'):
        stream.write('</%s>' % continent)
stream.write('</geo>')
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...