다중 행 셀을 사용하여 CSV 분할

다중 행 셀을 사용하여 CSV 분할

YouTube에서 생성된 일부 CSV 파일을 처리 중입니다(그래서 소스 구조를 변경할 수 없습니다). CSV 파일에서 일부 레코드는 여러 줄에 걸쳐 있습니다. 간결성을 위해 다른 많은 열을 생략한 가상의 예는 다음과 같습니다.

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"

일반적인 파일에는 수십만 또는 심지어 수백만 개의 레코드(한 파일 크기는 29.57GB)가 포함되어 있는데, 이는 한 번에 처리하기에는 너무 크기 때문에 이를 더 작은 덩어리로 나누어서 다른 프로세스에서 처리할 수 있도록 하고 싶습니다. 기계. 이전에 다른 보고서 파일에서 splitwith를 사용해 본 적이 -l있는데 셀에 줄 바꿈이 없을 때 매우 잘 작동했습니다. 이 경우 잘못된 행(예: 예제의 4번째 행)에서 분할이 발생하면 두 파일 모두에 손상된 레코드가 있는 것입니다. CSV 파일을 구문 분석한 다음 여러 파일로 재구성하는 것 외에 이와 같이 CSV를 분할하는 효율적인 방법이 있습니까?

답변1

원하는 방식으로 더 작은 단위로 다시 내보내려면 CSV 파일을 구문 분석해야 합니다. 그동안에는 좀 더 엄격하고 잘 정의된 다른 형식(예: 아, 모르겠어요, json)으로 다시 내보내고 싶을 수도 있습니다.

입력 파일의 형식이 매우 특이합니다.Python의 csv 모듈, 우선, ,더 일반적인 구분 기호(쉼표 공백) 대신 다중 문자 구분 기호(쉼표 공백)가 있기 때문에 구문 분석할 수 없습니다 ,. 그렇지 않으면 5줄의 Python 코드로 파일을 쉽게 구문 분석하고 다시 내보낼 수 있습니다.

작동하는 다른 파서를 찾거나 작은 파서를 작성해야 합니다. 먼저, 인용 규칙이 무엇인지(예: "필드가 포함으로 인용되면 어떤 일이 발생하는지 ") 현재 가지고 있는 형식의 세부 사항을 찾아보십시오.

답변2

구문 분석해야 할 수도 있습니다. 다음은 grep3개의 명령으로 파이프되는 명령의 예입니다 sed. 이 명령은 여러 줄의 인용 문자열을 한 줄로 결합합니다( split -l마지막에 파이프를 추가할 수 있음).

  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"  

그것을 파괴:

  • grep -E옵션은 확장 정규식을 허용합니다.
  • grep 옵션은 -o일치하는 항목만 출력합니다.
  • grep -z옵션은 개행 문자를 다음과 같이 처리합니다.\0
  • [^\",[:space:]]+패턴에서 인용되지 않은 항목 일치
  • \"[!#-~[:space:]]+\"패턴의 참조 항목과 일치합니다.
  • quoted items따옴표로 묶인 문자열에 따옴표나 비표준 문자 범위가 포함 "된 특별한 경우에 대해 스키마를 업데이트해야 할 수도 있습니다 . 뒤에 다른 문자 범위를 추가하면 됩니다.~
  • 첫 번째 sed문은 두 개의 개행 문자를 XXX new record XXX. 의 출력은 grep일치 항목 사이에 두 개의 줄 바꿈을 생성합니다.
  • 두 번째 sed문은 나머지 단일 개행 문자를 공백으로 바꿉니다.
  • 마지막 내용은 sed이전 XXX new record XXX내용을 다시 추가된 단일 줄 바꿈으로 대체합니다.

split -l끝에 파이프를 추가 할 수 있습니다 .

답변3

CSV 구문 분석의 경우 실제 CSV 구문 분석기를 사용하는 것이 가장 좋습니다. 최신 버전의 Perl을 사용하세요텍스트::CSV모듈에서는 다중 문자 필드 구분 기호를 지정할 수 있습니다.

#!/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;

답변4

나는 다음을 사용하여 이 문제를 해결했습니다.csvkitstream 명령은 CSV 행을 JSON 객체(줄 바꿈 이스케이프 처리)로 변환하고 split변환된 JSON 스트림을 -ing한 다음 분할된 JSON 파일을 다시 CSV로 변환합니다.

실행 가능한 스크립트의 요점은 다음과 같습니다.https://gist.github.com/vergenzt/d717bbad096dcf4be2151c66af47bf3a

전체 구조:

FILE="..."
BASE="$(basename "$FILE" | cut -d. -f1)"

cat "$FILE" \
  | csvjson --stream --no-inference --snifflimit 0 \
  | gsplit -d --additional-suffix=.json -l $ROWS_PER_FILE -u - "${BASE}_"

for chunk_json in ${BASE}_*.json; do
  chunk_csv="$(basename "$chunk_json" .json).csv"
  in2csv -f ndjson --no-inference "$chunk_json" > "$chunk_csv"
  rm "$chunk_json"
done

~에 따르면csvkit 문서:

  • csvjson--stream --no-inference --snifflimit 0"설정 및 --skip-lines설정되지 않음"인 경우 스트리밍이 사용됩니다.
  • in2csv--format ndjson --no-inference"설정..."인 경우 스트리밍을 사용합니다.

규모 측면에서 볼 때 내 컴퓨터(8GB RAM, 1.8GHz 프로세서)에서는 약 450만 개의 행이 포함된 약 300MB의 CSV 파일을 분할하는 데 약 2분 30초가 걸리지만 약 180,000개의 행(따옴표가 없는 개행 문자)에 불과합니다.

관련 정보