Разбор XML через xq (jq) с помощью GNU Parallel

У меня есть входной xml-файл следующим образом:

      <?xml version="1.0" encoding="UTF-8"?>
<yml_catalog date="2022-12-19T18:02:16+03:00">
 <shop>
  <offers>
   <offer id="177484701" available="true">
    <url>http:/site.com/offer1</url>
    ...
   </offer>
   <offer id="177485701" available="true">
    <url>http:/site.com/offer2</url>
    ...
   </offer>
   <offer id="177484401" available="true">
    <url>http:/site.com/offer3</url>
    ...
   </offer>
   ...
  </offers>
 </shop>
</yml_catalog>

Этот файл содержит от 2 до 8 миллионов предложений и после очистки занимает 10 гигабайт.

Я использую встроенный функционал xq без циклов (с циклами время обработки сильно увеличивается). Xq предоставляется пакетом Python yq и использует jq.

      cat file.xml | xq \
    --arg id "${id}" \
    --arg available "${available}" \
    --arg url "${url}" \
    -r '.yml_catalog.shop.offers[][] | "~~~\(.["@id"])~~~\(.["@available"])~~~\(.url)"' > /tmp/data.txt

Но обработка использует только 1 ядро. Этот файл периодически обновляется и должен быть обработан как можно скорее. Как я могу распараллелить его через параллель GNU? Можно с xq?

Я пробовал разные варианты, но все они дают ошибки.

      $ parallel -j8 --spreadstdin "xq --arg id '${id}' --arg available '${available}' --arg url '${url}' -r '.yml_catalog.shop.offers[][] | \"~~~\(.[\"@id\"])~~~\(.[\"@available\"])~~~\(.url)\"'" < file.xml > /tmp/data.txt

...
xq: Error running jq: ExpatError: junk after document element: line 2, column 4.
xq: Error running jq: ExpatError: no element found: line 11643, column 0.
...
      $ cat file.xml | parallel --results output/{/} -q xq \
        --arg id "${id}" \
        --arg available "${available}" \
        --arg url "${url}" \
        -r '.yml_catalog.shop.offers[][] | "~~~\(.["@id"])~~~\(.["@available"])~~~\(.url)"' > /tmp/data.txt

...
usage: xq [-h] [--xml-output] [--xml-dtd] [--xml-root XML_ROOT]
          [--xml-force-list XML_FORCE_LIST] [--in-place] [--version]
          [jq_filter] [files ...]
xq: error: argument files: can't open '<?xml version="1.0" encoding="UTF-8"?>': [Errno 2] No such file or directory: '<?xml version="1.0" encoding="UTF-8"?>'
...
      $ parallel -j8 --pipepart --block 100M -a file.xml -k "xq --arg id '${id}' --arg available '${available}' --arg url '${url}' -r '.yml_catalog.shop.offers[][] | \"~~~\(.[\"@id\"])~~~\(.[\"@available\"])~~~\(.url)\"'" > /tmp/data.txt 

...
xq: Error running jq: ExpatError: no element found: line 801268, column 0.
xq: Error running jq: ExpatError: junk after document element: line 2, column 4.
xq: Error running jq: ExpatError: not well-formed (invalid token): line 2, column 4.
...

Последний вариант мне кажется нелогичным, так как скорее всего данные разрываются между ядрами. Остальные проверенные варианты еще более нелогичны.

1 ответ

Я знаю, что вы просили о решении, и этот ответ не касается этого предпочтения. Но, возможно, следующее решение безGNU parallelВам поможет.

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

Я добавил образец обработки в качестве заполнителя для произвольной обработки в приведенный ниже код (отфильтруйте доступные предложения и извлеките идентификатор). Я также добавил код для объединения результатов обработки в конце.

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

      #!/bin/bash

INPUT='<?xml version="1.0" encoding="UTF-8"?>
<yml_catalog date="2022-12-19T18:02:16+03:00">
 <shop>
  <offers>
   <offer id="177484701" available="true">
    <url>http:/site.com/offer1</url>
   </offer>
   <offer id="177485702" available="false">
    <url>http:/site.com/offer2</url>
   </offer>
   <offer id="177484403" available="true">
    <url>http:/site.com/offer3</url>
   </offer>
   <offer id="177484404" available="true">
    <url>http:/site.com/offer4</url>
   </offer>
  </offers>
 </shop>
</yml_catalog>'

PARTITIONS=2   # number of partitions wanted
FILENAME_PARTITIONS="result_partition_"

CODE_SPLIT_OFFERS='
  .yml_catalog.shop.offers.offer |
  _nwise((length - (length % $partitions)) / $partitions)
'

CODE_PROCESSING='
  [.[] | select(."@available" == "true") |
  ."@id" |
  tonumber]
'

PARTITION=1
while read LINE
do
  echo "start processing partition $PARTITION"
  export FILENAME="${FILENAME_PARTITIONS}$PARTITION"
  jq -r -c "$CODE_PROCESSING" <<< "$LINE" > $FILENAME &
  ((PARTITION++))
done <<< $(xq -c --argjson partitions $PARTITIONS "$CODE_SPLIT_OFFERS" <<< "$INPUT")

echo -e "\nwait until all partitions are processed"
wait

echo -e "\nall partitions are processed. Processing results:"
find . -name "${FILENAME_PARTITIONS}*" -print -exec cat {} \;

echo -e "\nmerged results:"
jq -s -c 'flatten' <<< $(find . -name "${FILENAME_PARTITIONS}*" -exec cat {} \;)

Выход

      start processing partition 1
start processing partition 2

wait until all partitions are processed

all partitions are processed. Processing results:
./result_partition_2
[177484403,177484404]
./result_partition_1
[177484701]

merged results:
[177484403,177484404,177484701]

Замените две части приведенного выше кода на альтернативную обработку:

      CODE_PROCESSING='
  .[] | "~~~\(.["@id"])~~~\(.["@available"])~~~\(.url)"
'
      echo -e "\nmerged results:"
find . -name "${FILENAME_PARTITIONS}*" -exec cat {} \;

Выход

      start processing partition 1
start processing partition 2

wait until all partitions are processed

all partitions are processed. Processing results:
./result_partition_2
~~~177484403~~~true~~~http:/site.com/offer3
~~~177484404~~~true~~~http:/site.com/offer4
./result_partition_1
~~~177484701~~~true~~~http:/site.com/offer1
~~~177485702~~~false~~~http:/site.com/offer2

merged results:
~~~177484403~~~true~~~http:/site.com/offer3
~~~177484404~~~true~~~http:/site.com/offer4
~~~177484701~~~true~~~http:/site.com/offer1
~~~177485702~~~false~~~http:/site.com/offer2
Другие вопросы по тегам