Divisão de CSVs com células multilinha

3

Estou trabalhando com alguns arquivos CSV gerados pelo YouTube (por isso não posso alterar a estrutura de origem). No arquivo CSV, alguns registros abrangem várias linhas. Um exemplo hipotético com muitas outras colunas omitidas por brevidade é o seguinte:

video_id, upload_time, title, policy
oHg5SJYRHA0, 2007/05/15, "RickRoll'D", "Monetize in all countries except: CU, IR, KP, SD, SY
Track in countries: CU, IR, KP
Block in countries: SD, SY"
dQw4w9WgXcQ, 2009/10/24, "Rick Astley - Never Gonna Give You Up", "Monetize in all countries except: CU, IR, KP, SD, SY
Track in countries: CU, IR, KP, SD, SY"

Um arquivo típico contém centenas de milhares de registros, se não milhões, de registros (um arquivo tem 29,57 GB), o que é muito grande para processar de uma vez, então gostaria de dividi-los em partes menores para processamento em máquinas separadas. Eu usei anteriormente split com -l em outros arquivos de relatório e isso funciona muito bem quando não há nenhuma nova linha nas células. Nesse caso, se a divisão ocorrer em uma linha incorreta (por exemplo, linha 4 do exemplo), eu quebrei os registros em dois arquivos. Falta de analisar o arquivo CSV e, em seguida, recriá-lo em vários arquivos, existe uma maneira eficaz de dividir CSVs como este?

    
por Andy Huang 04.03.2017 / 21:12

3 respostas

1

Você vai querer analisar o arquivo CSV para reemitir em pedaços menores do jeito que você quiser. Durante esta operação, talvez você queira mesmo reemitê-la em um formato diferente, mais rigoroso e bem definido (como, oh, não sei, json).

Seu arquivo de entrada está em um formato bastante incomum. O módulo csv do Python , por exemplo, não pode analisá-lo, porque ele tem um delimitador de vários caracteres: , (espaço de vírgula) em vez do mais comum , . Caso contrário, você seria capaz de analisar e re-emitir o arquivo com 5 linhas de Python.

Você terá que encontrar outro analisador que funcione ou escrever um pequeno. Primeiro, tente descobrir quais são as especificidades do formato que você tem em mãos, como as regras de cotação (por exemplo, o que acontece quando um campo citado com " contém " .)

    
por 04.03.2017 / 23:15
1

Você provavelmente terá que analisá-lo. Aqui está um exemplo de grep comando canalizado em três sed comandos que irão combinar as strings citadas multilinha em uma linha (você pode adicionar um pipe para split -l no final):

  grep -Eoz "((([^\",[:space:]]+|\"[!#-~[:space:]]+\"),? ?){4}[[:space:]]){1}" csvtest |  
  sed -e ':a' -e 'N' -e '$!ba' -e 's/\n\n/XXX new record XXX/g' |
  sed -e ':a' -e 'N' -e '$!ba' -e 's/\n/ /g' |
  sed -e "s/XXX new record XXX/\n/g"  

Quebrando:

  • A opção -E grep permite expressões regulares estendidas.
  • A opção -o grep exibe apenas itens correspondentes
  • A opção -z grep trata caracteres de nova linha como [^\",[:space:]]+
  • \"[!#-~[:space:]]+\" no padrão corresponde a itens não citados
  • quoted items no padrão corresponde a itens citados
  • talvez seja necessário atualizar o padrão " para todos os casos especiais em que as strings citadas contenham aspas ~ ou intervalos de caracteres não padrão. Basta adicionar outros intervalos de caracteres após o sed
  • A primeira instrução XXX new record XXX substitui duas novas linhas com grep . A saída do sed gera duas novas linhas entre as correspondências.
  • A segunda instrução sed substitui cada nova linha individual restante por um espaço.
  • O XXX new record XXX final substitui o split -l adicionado anteriormente por uma única nova linha

Você pode adicionar um %code% pipe ao final de tudo.

    
por 05.03.2017 / 02:33
1

Para a análise de CSV, é aconselhável usar um analisador de CSV real. Com as versões recentes do módulo Text :: CSV do Perl, você pode especificar um separador de campo multi-char

#!/usr/bin/env perl
use strict;
use warnings;
use Text::CSV;
use Data::Dump; # just for this demonstration

# the "binary" option allows newlines in field values
my $csv = Text::CSV->new({binary=>1, sep=>", "})
  or die Text::CSV->error_diag;

open my $fh, "<", "test.csv";

while (my $row = $csv->getline($fh)) {
    print "next row:\n";
    dd $row; # or do something more interesting
}

close $fh;
    
por 05.03.2017 / 19:01