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)가 포함되어 있는데, 이는 한 번에 처리하기에는 너무 크기 때문에 이를 더 작은 덩어리로 나누어서 다른 프로세스에서 처리할 수 있도록 하고 싶습니다. 기계. 이전에 다른 보고서 파일에서 split
with를 사용해 본 적이 -l
있는데 셀에 줄 바꿈이 없을 때 매우 잘 작동했습니다. 이 경우 잘못된 행(예: 예제의 4번째 행)에서 분할이 발생하면 두 파일 모두에 손상된 레코드가 있는 것입니다. CSV 파일을 구문 분석한 다음 여러 파일로 재구성하는 것 외에 이와 같이 CSV를 분할하는 효율적인 방법이 있습니까?
답변1
원하는 방식으로 더 작은 단위로 다시 내보내려면 CSV 파일을 구문 분석해야 합니다. 그동안에는 좀 더 엄격하고 잘 정의된 다른 형식(예: 아, 모르겠어요, json)으로 다시 내보내고 싶을 수도 있습니다.
입력 파일의 형식이 매우 특이합니다.Python의 csv 모듈, 우선, ,
더 일반적인 구분 기호(쉼표 공백) 대신 다중 문자 구분 기호(쉼표 공백)가 있기 때문에 구문 분석할 수 없습니다 ,
. 그렇지 않으면 5줄의 Python 코드로 파일을 쉽게 구문 분석하고 다시 내보낼 수 있습니다.
작동하는 다른 파서를 찾거나 작은 파서를 작성해야 합니다. 먼저, 인용 규칙이 무엇인지(예: "
필드가 포함으로 인용되면 어떤 일이 발생하는지 "
) 현재 가지고 있는 형식의 세부 사항을 찾아보십시오.
답변2
구문 분석해야 할 수도 있습니다. 다음은 grep
3개의 명령으로 파이프되는 명령의 예입니다 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개의 행(따옴표가 없는 개행 문자)에 불과합니다.