| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 |
- 메일자동화
- 인증
- Vite
- php
- 마이그레이션
- next.js
- playwright
- 배포
- 보안
- sqlite
- JWT
- github actions
- 상태관리
- React
- FastAPI
- contextBridge
- 도메인설계
- 소셜로그인
- 프론트엔드
- electron
- 백엔드
- 아키텍처
- Android
- 크롤링
- REST API
- Kotlin
- javascript
- typescript
- 온디바이스AI
- python
- Today
- Total
HM소프트
외국어 PDF 표 구조 파싱하기 — 공개 데이터 크롤러 (2) 본문

pdfplumber extract_tables로 외국어 표 PDF에서 구조화 데이터를 뽑아낸 기록. 텍스트 흐름 대신 격자로 읽고, 라벨 사전으로 표기 변형 흡수, 헤더 행 오매핑 차단, 여러 페이지 표의 열 인덱스 이월, 부분합 중 최대값 선택, 외국어 연호 날짜 변환, 표→정규식→원본 페이지 3단 폴백, 합본 PDF 전용 파서, 스캔 PDF OCR 폴백까지.
2편: PDF 파싱
1편에서 BaseCrawler 파이프라인을 다뤘다. 이번 편은 그중 가장 손이 많이 간 부분, 외국어로 된 PDF 문서 파싱이다. 일부 국가는 데이터를 웹페이지가 아니라 PDF로만 공개했다.
첫 시도와 실패: 텍스트를 그냥 긁기
처음엔 PDF에서 텍스트를 순서대로 추출했다. 결과는 엉망이었다.
品名 改善対策 届出 2024年 ...
ABC社 エンジン 不具合
12345 2024/01/15
표로 된 문서라서, 텍스트를 흐름대로 읽으면 칸과 값의 대응이 무너진다. 어느 값이 어느 항목인지 알 수 없게 줄바꿈·정렬이 섞였다. 항목 19개를 정확히 채워야 하는데, 이 방식으론 불가능했다.
전환: 표 구조 자체를 읽기 (pdfplumber tables)
해법은 "텍스트의 흐름"이 아니라 "표의 구조"를 읽는 것이었다. pdfplumber의 표 추출 기능을 쓰면, PDF의 셀 경계를 인식해 2차원 배열로 돌려준다.
import pdfplumber
def parse_pdf_tables(path: str) -> list[dict]:
rows = []
with pdfplumber.open(path) as pdf:
for page in pdf.pages:
for table in page.extract_tables():
header, *body = table
for row in body:
rows.append(map_row(header, row)) # 헤더-값 매핑
return rows
텍스트 흐름이 아니라 셀 단위로 읽으니 칸-값 매핑이 안정됐다. 빈 칸은 빈 칸으로, 병합된 셀은 병합된 대로 구조가 유지됐다.
형식 라벨 사전 — 같은 항목, 다른 표기
표 구조를 읽어도 또 다른 벽이 있었다. 문서마다 같은 항목을 가리키는 표기가 달랐다. 예를 들어 "개선 대책 신고"를 뜻하는 라벨이 문서/연도마다 미묘하게 다르게 적혀 있었다(예: 改善対策届出 같은 형식 라벨의 변형들).
그래서 라벨 사전을 두고, 표기 변형들을 하나의 표준 필드로 매핑했다.
LABEL_MAP = {
"改善対策届出": "improvement_report",
"改善対策 届出": "improvement_report", # 공백 변형
"リコール届出": "recall_report",
# ... 실제 문서에서 발견될 때마다 보강
}
def normalize_label(raw_label: str) -> str | None:
key = raw_label.strip().replace(" ", "")
return LABEL_MAP.get(key) or LABEL_MAP.get(raw_label.strip())
이 사전을 실제 문서에서 새 변형이 나올 때마다 보강하면서, 파싱 품질(빈 칸·오인식)이 눈에 띄게 좋아졌다.
필드 매핑 버그 — 5번과 10번이 섞이다
표를 읽어 항목에 넣는 과정에서, 항목 번호 매핑이 틀어진 버그도 있었다. 5번 항목(주요 내용)과 10번 항목(결함 원인)이 서로 섞여 들어간 것이다. 표의 칸 순서와 우리 모델의 필드 순서가 한 칸 어긋났을 때 생기는 전형적인 실수였다. 매핑 테이블을 명시적으로 다시 정렬해 바로잡았다.
이런 류의 버그는 "그럴듯하게 채워지지만 값이 틀린" 형태라, 눈으로 샘플을 비교하지 않으면 놓치기 쉽다. 그래서 파싱 결과를 항목명과 함께 출력해 사람이 검수할 수 있게 했다.
속도 개선
PDF 파싱과 번역이 합쳐지면서 수집이 느려졌다. 파싱 품질을 올리는 작업과 병행해, 불필요한 재처리를 줄이고 페이지/표 단위 처리를 다듬어 크롤링·번역 속도를 크게 끌어올렸다.
정리하며
복잡한 포맷일수록 "흐름"이 아니라 "구조"를 읽어야 한다.
- 텍스트 추출 → 표 추출(pdfplumber tables) 전환이 칸-값 매핑을 안정시켰다.
- 형식 라벨 사전으로 같은 항목의 표기 변형을 흡수했다.
- 필드 매핑은 명시적으로 — 한 칸 어긋남이 "값은 차 있는데 틀린" 버그를 만든다.
다음 3편은 더 골치 아픈 주제다. "내 PC에서는 되는데 다른 PC에서는 깨지는" cross-PC 강건성 문제를 어떻게 하나씩 잡았는지.
'기술 개발일지' 카테고리의 다른 글
| 크롤러 데이터 검증·검색·스케줄러·데스크톱앱화 — 공개 데이터 크롤러 (4) (0) | 2026.06.11 |
|---|---|
| cross-PC 강건성 잡기 — '내 PC에서는 되는데' 크롤러 (3) (0) | 2026.06.11 |
| 공개 데이터 크롤러 파이프라인 설계 — BaseCrawler로 5개국 출처를 하나로 통합하기 (1) (0) | 2026.06.11 |
| 카카오 로그인 연동 실전 가이드 - OAuth 구현 A to Z (0) | 2026.03.07 |
| 토스페이먼츠 결제 연동 실전 가이드 - 실제 프로젝트 적용 경험담 (0) | 2026.03.07 |