오늘도 공부
Claude Skills를 이용해서 PDF 양식 자동화 스킬 만들기(고급) 본문
고급 튜토리얼: PDF 양식 자동화 스킬 만들기
목표
이 튜토리얼에서는 실행 가능한 Python 스크립트와 검증 로직을 포함한 고급 스킬을 만들어봅니다.
난이도
⭐⭐⭐ 고급 - 실행 가능한 스크립트, 계획-검증-실행 패턴, 시각적 분석
소요 시간
약 60-90분
단계 1: 문제 정의하기
시나리오
여러분은 매주 수백 개의 PDF 양식을 처리해야 합니다. 현재 프로세스:
- PDF 양식 열기
- 스프레드시트에서 데이터 찾기
- 수동으로 필드 채우기
- 저장 및 다음 양식으로
이 과정에서 발생하는 문제:
- 😓 반복적이고 시간 소모적
- 🐛 오타나 잘못된 필드 입력 오류
- 🔍 어떤 필드를 채웠는지 추적 어려움
- 📊 일괄 처리 불가능
해결 방법
Claude가 다음을 수행하도록 스킬을 만듭니다:
- PDF 양식 분석 (어떤 필드가 있는지)
- 스프레드시트 데이터 매칭
- 변경 계획 생성 (changes.json)
- 계획 검증 (실제 적용 전 오류 확인)
- 검증 통과 시 PDF에 적용
- 최종 결과 확인
단계 2: 디렉토리 구조 설계하기
pdf-form-automation/
├── SKILL.md # 메인 가이드
├── scripts/ # Python 스크립트들
│ ├── analyze_pdf.py # PDF 분석
│ ├── validate_changes.py # 변경 계획 검증
│ ├── apply_changes.py # PDF에 변경 적용
│ ├── pdf_to_images.py # 시각적 분석용
│ └── requirements.txt # Python 의존성
├── reference/ # 참조 문서
│ ├── field-mapping.md # 필드 매핑 규칙
│ └── validation-rules.md # 검증 규칙
├── templates/ # 템플릿
│ └── changes-template.json # 변경 계획 템플릿
└── examples/ # 예시
├── sample-form.pdf # 샘플 양식
└── sample-data.xlsx # 샘플 데이터
단계 3: SKILL.md 작성하기
---
name: Automating PDF forms
description: PDF 양식을 분석하고 스프레드시트 데이터로 자동으로 채웁니다
---
# PDF 양식 자동화 가이드
이 스킬은 PDF 양식을 분석하고, 데이터를 검증하며, 안전하게 자동으로 채웁니다.
## 핵심 원칙: 계획-검증-실행
PDF를 직접 수정하기 전에 항상 검증 단계를 거칩니다:
1. **분석**: PDF 필드 추출
2. **계획**: 변경 사항을 JSON으로 작성
3. **검증**: 스크립트로 계획 확인
4. **실행**: 검증 통과 시 적용
5. **확인**: 결과 검토
## 사전 준비
### 패키지 설치
```bash
pip install pypdf2 openpyxl pillow pdf2image --break-system-packages
필요한 파일
- PDF 양식 파일
- 채울 데이터가 있는 Excel 파일
워크플로우
1단계: PDF 분석
먼저 PDF에 어떤 필드가 있는지 확인합니다:
python scripts/analyze_pdf.py <pdf_file_path>
출력 예시:
{
"fields": [
{"name": "applicant_name", "type": "text", "required": true},
{"name": "email", "type": "text", "required": true},
{"name": "phone", "type": "text", "required": false},
{"name": "date_of_birth", "type": "text", "required": true}
],
"total_fields": 4,
"required_fields": 3
}
2단계: 시각적 분석 (선택사항)
PDF를 이미지로 변환하여 레이아웃을 시각적으로 이해합니다:
python scripts/pdf_to_images.py <pdf_file_path>
이미지를 보고 필드의 위치와 레이아웃을 파악하세요.
3단계: 변경 계획 생성
분석 결과와 사용자의 데이터를 바탕으로 changes.json 파일을 생성합니다.
템플릿: templates/changes-template.json을 참조하세요.
예시:
{
"metadata": {
"source_pdf": "application_form.pdf",
"output_pdf": "filled_application_form.pdf",
"data_source": "applicants.xlsx",
"created_at": "2024-01-15T10:30:00Z"
},
"changes": [
{
"field": "applicant_name",
"value": "홍길동",
"source_column": "이름"
},
{
"field": "email",
"value": "hong@example.com",
"source_column": "이메일"
},
{
"field": "phone",
"value": "010-1234-5678",
"source_column": "전화번호"
},
{
"field": "date_of_birth",
"value": "1990-01-15",
"source_column": "생년월일"
}
]
}
중요: 실제 PDF에 적용하기 전에 반드시 검증 단계를 거쳐야 합니다!
4단계: 변경 계획 검증 (필수!)
생성한 changes.json을 검증합니다:
python scripts/validate_changes.py changes.json application_form.pdf
검증 항목:
- ✅ 모든 필드가 PDF에 실제로 존재하는지
- ✅ 필수 필드가 모두 채워졌는지
- ✅ 데이터 타입이 올바른지 (이메일, 날짜 등)
- ✅ 데이터 길이가 필드 크기를 초과하지 않는지
- ✅ 중복된 필드가 없는지
검증 성공 예시:
✓ 모든 검증 통과
✓ 필드 4개 확인됨
✓ 필수 필드 3개 모두 채워짐
✓ 데이터 형식 올바름
변경 사항을 적용할 준비가 되었습니다.
검증 실패 예시:
✗ 검증 실패
오류:
1. 필드 'applicant_age'가 PDF에 존재하지 않습니다
2. 필수 필드 'date_of_birth'가 누락되었습니다
3. 'email' 형식이 올바르지 않습니다: "invalid-email"
changes.json을 수정한 후 다시 검증하세요.
검증이 실패하면 changes.json을 수정하고 4단계를 반복하세요.
5단계: 변경 적용
검증을 통과했다면 이제 안전하게 적용할 수 있습니다:
python scripts/apply_changes.py changes.json application_form.pdf filled_application_form.pdf
출력:
✓ 변경 사항 적용 완료
✓ 4개 필드 업데이트됨
✓ 출력 파일: filled_application_form.pdf
다음 단계: 결과를 시각적으로 확인하세요.
6단계: 결과 확인
적용된 PDF를 이미지로 변환하여 확인합니다:
python scripts/pdf_to_images.py filled_application_form.pdf
생성된 이미지를 보고 모든 필드가 올바르게 채워졌는지 확인하세요.
참조 문서
필드 매핑 규칙
Excel 컬럼과 PDF 필드를 매핑하는 규칙은 reference/field-mapping.md를 참조하세요.
검증 규칙
데이터 검증에 사용되는 규칙은 reference/validation-rules.md를 참조하세요.
일괄 처리
여러 PDF를 한 번에 처리할 때:
- 각 행마다 별도의 changes_*.json 생성
- 각 JSON을 검증
- 모든 검증이 통과한 후에만 일괄 적용
중요: 검증 없이 일괄 적용하지 마세요!
오류 처리
스크립트가 명확한 오류 메시지를 제공합니다:
- PDF 파일을 찾을 수 없는 경우
- 필드가 존재하지 않는 경우
- 데이터 형식이 잘못된 경우
- 권한이 없는 경우
오류 발생 시:
- 오류 메시지를 주의 깊게 읽기
- 해당 문제 수정
- 검증 단계부터 다시 시작
---
## 단계 4: Python 스크립트 작성하기
### scripts/analyze_pdf.py
```python
#!/usr/bin/env python3
"""
PDF 양식의 필드를 분석하고 JSON으로 출력합니다.
"""
import sys
import json
from PyPDF2 import PdfReader
def analyze_pdf(pdf_path):
"""PDF 양식 필드 분석"""
try:
reader = PdfReader(pdf_path)
except FileNotFoundError:
print(f"오류: PDF 파일 '{pdf_path}'를 찾을 수 없습니다.", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"오류: PDF를 읽을 수 없습니다: {e}", file=sys.stderr)
sys.exit(1)
if '/AcroForm' not in reader.trailer['/Root']:
print("오류: 이 PDF에는 양식 필드가 없습니다.", file=sys.stderr)
sys.exit(1)
fields = []
form_fields = reader.trailer['/Root']['/AcroForm']['/Fields']
for field in form_fields:
field_obj = field.get_object()
field_name = field_obj.get('/T')
field_type = field_obj.get('/FT')
field_flags = field_obj.get('/Ff', 0)
# 필수 필드 확인 (Ff의 2번째 비트)
is_required = bool(field_flags & 2)
fields.append({
"name": field_name,
"type": str(field_type),
"required": is_required
})
result = {
"fields": fields,
"total_fields": len(fields),
"required_fields": sum(1 for f in fields if f['required'])
}
print(json.dumps(result, indent=2, ensure_ascii=False))
if __name__ == "__main__":
if len(sys.argv) != 2:
print("사용법: python analyze_pdf.py <pdf_file_path>", file=sys.stderr)
sys.exit(1)
analyze_pdf(sys.argv[1])
scripts/validate_changes.py
#!/usr/bin/env python3
"""
changes.json 파일을 검증합니다.
PDF에 필드가 실제로 존재하는지, 필수 필드가 모두 채워졌는지 등을 확인합니다.
"""
import sys
import json
import re
from PyPDF2 import PdfReader
from datetime import datetime
def validate_email(email):
"""이메일 형식 검증"""
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return re.match(pattern, email) is not None
def validate_phone(phone):
"""전화번호 형식 검증 (한국 형식)"""
pattern = r'^\d{2,3}-\d{3,4}-\d{4}$'
return re.match(pattern, phone) is not None
def validate_date(date_str):
"""날짜 형식 검증 (YYYY-MM-DD)"""
try:
datetime.strptime(date_str, '%Y-%m-%d')
return True
except ValueError:
return False
def validate_changes(changes_path, pdf_path):
"""변경 계획 검증"""
errors = []
warnings = []
# changes.json 로드
try:
with open(changes_path, 'r', encoding='utf-8') as f:
changes_data = json.load(f)
except FileNotFoundError:
print(f"오류: '{changes_path}' 파일을 찾을 수 없습니다.", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError as e:
print(f"오류: JSON 형식이 올바르지 않습니다: {e}", file=sys.stderr)
sys.exit(1)
# PDF 필드 로드
try:
reader = PdfReader(pdf_path)
form_fields = reader.trailer['/Root']['/AcroForm']['/Fields']
pdf_fields = {}
for field in form_fields:
field_obj = field.get_object()
field_name = field_obj.get('/T')
field_flags = field_obj.get('/Ff', 0)
is_required = bool(field_flags & 2)
pdf_fields[field_name] = {
'required': is_required,
'type': str(field_obj.get('/FT'))
}
except Exception as e:
print(f"오류: PDF를 읽을 수 없습니다: {e}", file=sys.stderr)
sys.exit(1)
# 변경 사항 검증
changes = changes_data.get('changes', [])
filled_fields = set()
for idx, change in enumerate(changes, 1):
field_name = change.get('field')
value = change.get('value')
# 필드 존재 여부 확인
if field_name not in pdf_fields:
errors.append(f"#{idx}: 필드 '{field_name}'이(가) PDF에 존재하지 않습니다")
continue
# 중복 필드 확인
if field_name in filled_fields:
errors.append(f"#{idx}: 필드 '{field_name}'이(가) 중복되었습니다")
filled_fields.add(field_name)
# 값이 비어있는지 확인
if not value or (isinstance(value, str) and not value.strip()):
if pdf_fields[field_name]['required']:
errors.append(f"#{idx}: 필수 필드 '{field_name}'의 값이 비어있습니다")
else:
warnings.append(f"#{idx}: 선택 필드 '{field_name}'의 값이 비어있습니다")
continue
# 데이터 타입별 검증
if 'email' in field_name.lower() and not validate_email(value):
errors.append(f"#{idx}: '{field_name}'의 이메일 형식이 올바르지 않습니다: '{value}'")
if 'phone' in field_name.lower() and not validate_phone(value):
warnings.append(f"#{idx}: '{field_name}'의 전화번호 형식이 표준 형식이 아닙니다: '{value}'")
if 'date' in field_name.lower() and not validate_date(value):
errors.append(f"#{idx}: '{field_name}'의 날짜 형식이 올바르지 않습니다 (YYYY-MM-DD 필요): '{value}'")
# 값 길이 확인 (최대 500자)
if isinstance(value, str) and len(value) > 500:
errors.append(f"#{idx}: '{field_name}'의 값이 너무 깁니다 (최대 500자): {len(value)}자")
# 필수 필드가 모두 채워졌는지 확인
for field_name, field_info in pdf_fields.items():
if field_info['required'] and field_name not in filled_fields:
errors.append(f"필수 필드 '{field_name}'이(가) changes.json에 없습니다")
# 결과 출력
print("\n" + "="*60)
print("검증 결과")
print("="*60 + "\n")
if warnings:
print(f"⚠️ 경고 {len(warnings)}개:\n")
for warning in warnings:
print(f" {warning}")
print()
if errors:
print(f"✗ 검증 실패 - 오류 {len(errors)}개:\n")
for error in errors:
print(f" {error}")
print("\nchanges.json을 수정한 후 다시 검증하세요.")
sys.exit(1)
else:
print("✓ 모든 검증 통과")
print(f"✓ 필드 {len(changes)}개 확인됨")
print(f"✓ 필수 필드 {sum(1 for f in pdf_fields.values() if f['required'])}개 모두 채워짐")
print("✓ 데이터 형식 올바름\n")
print("변경 사항을 적용할 준비가 되었습니다.")
if __name__ == "__main__":
if len(sys.argv) != 3:
print("사용법: python validate_changes.py <changes.json> <pdf_file>", file=sys.stderr)
sys.exit(1)
validate_changes(sys.argv[1], sys.argv[2])
scripts/apply_changes.py
#!/usr/bin/env python3
"""
검증된 changes.json을 PDF에 적용합니다.
"""
import sys
import json
from PyPDF2 import PdfReader, PdfWriter
def apply_changes(changes_path, input_pdf, output_pdf):
"""변경 사항을 PDF에 적용"""
try:
with open(changes_path, 'r', encoding='utf-8') as f:
changes_data = json.load(f)
except FileNotFoundError:
print(f"오류: '{changes_path}' 파일을 찾을 수 없습니다.", file=sys.stderr)
sys.exit(1)
try:
reader = PdfReader(input_pdf)
writer = PdfWriter()
# 모든 페이지 복사
for page in reader.pages:
writer.add_page(page)
# 필드 값 설정
changes = changes_data.get('changes', [])
field_values = {change['field']: change['value'] for change in changes}
writer.update_page_form_field_values(
writer.pages[0],
field_values
)
# 출력 파일 저장
with open(output_pdf, 'wb') as f:
writer.write(f)
print(f"\n✓ 변경 사항 적용 완료")
print(f"✓ {len(changes)}개 필드 업데이트됨")
print(f"✓ 출력 파일: {output_pdf}\n")
print("다음 단계: 결과를 시각적으로 확인하세요.")
except Exception as e:
print(f"오류: PDF 처리 중 문제가 발생했습니다: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
if len(sys.argv) != 4:
print("사용법: python apply_changes.py <changes.json> <input.pdf> <output.pdf>", file=sys.stderr)
sys.exit(1)
apply_changes(sys.argv[1], sys.argv[2], sys.argv[3])
scripts/pdf_to_images.py
#!/usr/bin/env python3
"""
PDF를 이미지로 변환하여 시각적으로 확인할 수 있게 합니다.
"""
import sys
import os
from pdf2image import convert_from_path
def pdf_to_images(pdf_path, output_dir='output_images'):
"""PDF를 이미지로 변환"""
if not os.path.exists(pdf_path):
print(f"오류: PDF 파일 '{pdf_path}'를 찾을 수 없습니다.", file=sys.stderr)
sys.exit(1)
# 출력 디렉토리 생성
os.makedirs(output_dir, exist_ok=True)
try:
# PDF를 이미지로 변환
images = convert_from_path(pdf_path, dpi=200)
base_name = os.path.splitext(os.path.basename(pdf_path))[0]
for i, image in enumerate(images, 1):
output_path = os.path.join(output_dir, f"{base_name}_page_{i}.png")
image.save(output_path, 'PNG')
print(f"✓ 페이지 {i} 저장됨: {output_path}")
print(f"\n총 {len(images)}개 페이지를 이미지로 변환했습니다.")
print(f"출력 디렉토리: {output_dir}/")
except Exception as e:
print(f"오류: PDF를 이미지로 변환할 수 없습니다: {e}", file=sys.stderr)
print("\n팁: poppler-utils가 설치되어 있는지 확인하세요:", file=sys.stderr)
print(" Ubuntu: sudo apt-get install poppler-utils", file=sys.stderr)
print(" macOS: brew install poppler", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
if len(sys.argv) < 2:
print("사용법: python pdf_to_images.py <pdf_file> [output_dir]", file=sys.stderr)
sys.exit(1)
pdf_path = sys.argv[1]
output_dir = sys.argv[2] if len(sys.argv) > 2 else 'output_images'
pdf_to_images(pdf_path, output_dir)
scripts/requirements.txt
PyPDF2==3.0.1
openpyxl==3.1.2
Pillow==10.1.0
pdf2image==1.16.3
단계 5: 참조 문서 작성하기
reference/field-mapping.md
# 필드 매핑 규칙
Excel 스프레드시트의 컬럼과 PDF 양식 필드를 매핑하는 규칙입니다.
## 기본 규칙
### 1. 이름 매핑
**Excel 컬럼 → PDF 필드**
- "이름" → `applicant_name`
- "성명" → `applicant_name`
- "신청자명" → `applicant_name`
### 2. 연락처 매핑
**이메일:**
- "이메일" → `email`
- "이메일 주소" → `email`
- "E-mail" → `email`
**전화번호:**
- "전화번호" → `phone`
- "연락처" → `phone`
- "휴대폰" → `phone`
**전화번호 형식 변환:**
- `01012345678` → `010-1234-5678`
- `010 1234 5678` → `010-1234-5678`
### 3. 날짜 매핑
**생년월일:**
- "생년월일" → `date_of_birth`
- "출생일" → `date_of_birth`
**날짜 형식 변환:**
- `1990.01.15` → `1990-01-15`
- `1990/01/15` → `1990-01-15`
- `19900115` → `1990-01-15`
### 4. 주소 매핑
**주소:**
- "주소" → `address`
- "거주지" → `address`
**상세주소:**
- "상세주소" → `address_detail`
## 특수 케이스
### 빈 값 처리
- Excel 셀이 비어있으면 PDF 필드도 비워둠
- 단, 필수 필드인 경우 검증 단계에서 오류 발생
### 긴 텍스트 처리
- 500자를 초과하는 경우 잘라냄
- 경고 메시지 표시
### 숫자 필드
- 숫자는 문자열로 변환하여 입력
- 천 단위 구분자는 제거: `1,000` → `1000`
## 커스텀 매핑
프로젝트마다 다른 매핑이 필요한 경우, changes.json에서 `source_column` 필드를 사용하여 명시적으로 지정하세요:
```json
{
"field": "custom_field_name",
"value": "value from excel",
"source_column": "엑셀의 특정 컬럼명"
}
### reference/validation-rules.md
```markdown
# 검증 규칙
changes.json과 PDF 양식에 적용되는 검증 규칙입니다.
## 필수 검증 항목
### 1. 필드 존재 여부
- PDF에 실제로 존재하는 필드만 사용 가능
- 존재하지 않는 필드 사용 시 오류
**오류 예시:**
필드 'non_existent_field'가 PDF에 존재하지 않습니다
### 2. 필수 필드 확인
- PDF에서 필수로 지정된 필드는 반드시 값이 있어야 함
- 빈 값 또는 null인 경우 오류
**오류 예시:**
필수 필드 'applicant_name'의 값이 비어있습니다
### 3. 중복 필드 확인
- 같은 필드에 여러 번 값을 설정할 수 없음
- 중복 발견 시 오류
**오류 예시:**
필드 'email'이 중복되었습니다
## 데이터 타입 검증
### 이메일
필드 이름에 'email'이 포함된 경우 자동으로 이메일 형식 검증
**유효한 형식:**
- `user@example.com`
- `name.surname@company.co.kr`
**무효한 형식:**
- `invalid-email`
- `@example.com`
- `user@`
### 전화번호
필드 이름에 'phone' 또는 'tel'이 포함된 경우 한국 전화번호 형식 검증
**표준 형식 (권장):**
- `010-1234-5678`
- `02-123-4567`
**허용되지만 경고:**
- `01012345678` (하이픈 없음)
- `010 1234 5678` (공백 구분)
### 날짜
필드 이름에 'date' 또는 '일자'가 포함된 경우 날짜 형식 검증
**표준 형식:**
- `YYYY-MM-DD` (예: `2024-01-15`)
**무효한 형식:**
- `2024/01/15`
- `2024.01.15`
- `15-01-2024`
## 값 제약 사항
### 문자 길이
- **최대 길이**: 500자
- 초과 시 오류 발생
### 특수 문자
- 대부분의 특수 문자 허용
- PDF 양식이 지원하지 않는 문자가 있을 수 있음
## 경고 vs 오류
### 오류 (Errors)
오류가 하나라도 있으면 변경 사항을 적용할 수 없습니다:
- 필드가 존재하지 않음
- 필수 필드 누락
- 중복 필드
- 데이터 형식 오류 (이메일, 날짜)
- 값이 너무 긴 경우
### 경고 (Warnings)
경고가 있어도 변경 사항을 적용할 수 있지만, 확인이 권장됩니다:
- 선택 필드가 비어있음
- 전화번호가 표준 형식이 아님
- 기타 형식 권장사항
## 커스텀 검증 규칙 추가
프로젝트에 특화된 검증 규칙이 필요한 경우:
1. `validate_changes.py` 스크립트 수정
2. 새로운 검증 함수 추가
3. 메인 검증 로직에 통합
**예시:**
```python
def validate_student_id(student_id):
"""학번 형식 검증 (8자리 숫자)"""
return bool(re.match(r'^\d{8}$', student_id))
---
## 단계 6: 템플릿 작성하기
### templates/changes-template.json
```json
{
"metadata": {
"source_pdf": "input_form.pdf",
"output_pdf": "filled_form.pdf",
"data_source": "data.xlsx",
"created_at": "2024-01-15T10:30:00Z",
"notes": "Optional notes about this form"
},
"changes": [
{
"field": "field_name_in_pdf",
"value": "value to fill",
"source_column": "column_name_in_excel"
}
]
}
단계 7: 스킬 테스트하기
테스트 케이스 1: 정상 플로우
시나리오: 모든 필드가 올바르게 채워진 경우
- 분석:
python scripts/analyze_pdf.py examples/sample-form.pdf
- changes.json 생성:
{
"metadata": {
"source_pdf": "sample-form.pdf",
"output_pdf": "filled-form.pdf"
},
"changes": [
{"field": "name", "value": "홍길동"},
{"field": "email", "value": "hong@example.com"},
{"field": "phone", "value": "010-1234-5678"}
]
}
- 검증:
python scripts/validate_changes.py changes.json examples/sample-form.pdf
예상 결과: ✅ 모든 검증 통과
- 적용:
python scripts/apply_changes.py changes.json examples/sample-form.pdf output/filled-form.pdf
예상 결과: ✅ PDF 생성 성공
테스트 케이스 2: 검증 실패 - 존재하지 않는 필드
changes.json:
{
"changes": [
{"field": "non_existent_field", "value": "test"}
]
}
검증 결과:
✗ 검증 실패 - 오류 1개:
#1: 필드 'non_existent_field'가 PDF에 존재하지 않습니다
기대 동작: ❌ 적용 전에 오류 감지
테스트 케이스 3: 검증 실패 - 이메일 형식 오류
changes.json:
{
"changes": [
{"field": "email", "value": "invalid-email"}
]
}
검증 결과:
✗ 검증 실패 - 오류 1개:
#1: 'email'의 이메일 형식이 올바르지 않습니다: 'invalid-email'
기대 동작: ❌ 잘못된 데이터 형식 감지
테스트 케이스 4: 경고가 있는 경우
changes.json:
{
"changes": [
{"field": "name", "value": "홍길동"},
{"field": "email", "value": "hong@example.com"},
{"field": "phone", "value": "01012345678"}
]
}
검증 결과:
⚠️ 경고 1개:
#3: 'phone'의 전화번호 형식이 표준 형식이 아닙니다: '01012345678'
✓ 모든 검증 통과
기대 동작: ✅ 경고는 있지만 적용 가능
단계 8: 고급 기능 추가하기
기능 1: Excel 파일 자동 읽기
scripts/read_excel.py 추가:
#!/usr/bin/env python3
"""
Excel 파일을 읽어서 changes.json을 자동으로 생성합니다.
"""
import sys
import json
from openpyxl import load_workbook
from datetime import datetime
def read_excel(excel_path, pdf_path, row_number, output_json='changes.json'):
"""Excel의 특정 행을 읽어서 changes.json 생성"""
try:
wb = load_workbook(excel_path)
ws = wb.active
# 헤더 읽기 (첫 번째 행)
headers = [cell.value for cell in ws[1]]
# 데이터 읽기
if row_number > ws.max_row:
print(f"오류: 행 번호 {row_number}가 범위를 초과합니다 (최대: {ws.max_row})", file=sys.stderr)
sys.exit(1)
row_data = [cell.value for cell in ws[row_number]]
# 필드 매핑 (간단한 예시)
field_mapping = {
'이름': 'name',
'이메일': 'email',
'전화번호': 'phone',
'생년월일': 'date_of_birth'
}
changes = []
for header, value in zip(headers, row_data):
if header in field_mapping and value:
# 전화번호 형식 변환
if header == '전화번호' and isinstance(value, str):
value = value.replace(' ', '-')
if '-' not in value and len(value) == 11:
value = f"{value[:3]}-{value[3:7]}-{value[7:]}"
# 날짜 형식 변환
if header == '생년월일':
if isinstance(value, datetime):
value = value.strftime('%Y-%m-%d')
changes.append({
'field': field_mapping[header],
'value': str(value),
'source_column': header
})
# changes.json 생성
result = {
'metadata': {
'source_pdf': pdf_path,
'output_pdf': pdf_path.replace('.pdf', '_filled.pdf'),
'data_source': excel_path,
'row_number': row_number,
'created_at': datetime.now().isoformat()
},
'changes': changes
}
with open(output_json, 'w', encoding='utf-8') as f:
json.dump(result, f, indent=2, ensure_ascii=False)
print(f"✓ {output_json} 생성 완료")
print(f"✓ {len(changes)}개 필드 추출됨")
print(f"\n다음 단계: python scripts/validate_changes.py {output_json} {pdf_path}")
except FileNotFoundError:
print(f"오류: Excel 파일 '{excel_path}'를 찾을 수 없습니다.", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"오류: Excel 처리 중 문제가 발생했습니다: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
if len(sys.argv) < 4:
print("사용법: python read_excel.py <excel_file> <pdf_file> <row_number> [output.json]", file=sys.stderr)
sys.exit(1)
excel_path = sys.argv[1]
pdf_path = sys.argv[2]
row_number = int(sys.argv[3])
output_json = sys.argv[4] if len(sys.argv) > 4 else 'changes.json'
read_excel(excel_path, pdf_path, row_number, output_json)
SKILL.md에 추가:
## Excel 자동 읽기 (선택사항)
Excel 파일에서 직접 changes.json을 생성할 수 있습니다:
```bash
python scripts/read_excel.py data.xlsx form.pdf 2 changes.json
이 명령은:
- data.xlsx의 2번째 행을 읽음
- 자동으로 필드 매핑 적용
- changes.json 생성
생성 후에는 반드시 검증 단계를 거쳐야 합니다!
### 기능 2: 일괄 처리
**scripts/batch_process.py 추가:**
```python
#!/usr/bin/env python3
"""
여러 PDF를 한 번에 처리합니다.
"""
import sys
import os
import json
import subprocess
from openpyxl import load_workbook
def batch_process(excel_path, pdf_template, output_dir='output'):
"""Excel의 모든 행을 처리하여 여러 PDF 생성"""
os.makedirs(output_dir, exist_ok=True)
try:
wb = load_workbook(excel_path)
ws = wb.active
total_rows = ws.max_row - 1 # 헤더 제외
print(f"총 {total_rows}개 행을 처리합니다...\n")
for row_num in range(2, ws.max_row + 1):
print(f"[{row_num-1}/{total_rows}] 처리 중...")
# changes.json 생성
changes_file = f"changes_{row_num}.json"
result = subprocess.run([
'python', 'scripts/read_excel.py',
excel_path, pdf_template, str(row_num), changes_file
], capture_output=True)
if result.returncode != 0:
print(f" ✗ 행 {row_num} 실패: Excel 읽기 오류")
continue
# 검증
result = subprocess.run([
'python', 'scripts/validate_changes.py',
changes_file, pdf_template
], capture_output=True)
if result.returncode != 0:
print(f" ✗ 행 {row_num} 실패: 검증 오류")
print(result.stdout.decode())
continue
# 적용
output_pdf = os.path.join(output_dir, f"filled_form_{row_num-1}.pdf")
result = subprocess.run([
'python', 'scripts/apply_changes.py',
changes_file, pdf_template, output_pdf
], capture_output=True)
if result.returncode != 0:
print(f" ✗ 행 {row_num} 실패: PDF 생성 오류")
continue
print(f" ✓ 완료: {output_pdf}")
# 임시 파일 정리
os.remove(changes_file)
print(f"\n✓ 일괄 처리 완료")
print(f"✓ 출력 디렉토리: {output_dir}/")
except Exception as e:
print(f"오류: 일괄 처리 중 문제가 발생했습니다: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
if len(sys.argv) < 3:
print("사용법: python batch_process.py <excel_file> <pdf_template> [output_dir]", file=sys.stderr)
sys.exit(1)
excel_path = sys.argv[1]
pdf_template = sys.argv[2]
output_dir = sys.argv[3] if len(sys.argv) > 3 else 'output'
batch_process(excel_path, pdf_template, output_dir)
단계 9: 완성 및 최적화
성능 최적화
- 큰 PDF 처리: 페이지 단위로 분할 처리
- 병렬 처리: 일괄 처리 시 멀티프로세싱 사용
- 캐싱: 같은 PDF 분석 결과 재사용
오류 복구
- 체크포인트: 일괄 처리 중 어디까지 완료했는지 저장
- 재시도 로직: 일시적 오류 시 자동 재시도
- 로그 파일: 모든 처리 과정을 로그에 기록
체크리스트
고급 스킬을 완성했다면:
스크립트
- [ ] 모든 스크립트가 명확한 오류 메시지를 제공하는가?
- [ ] 스크립트가 예상치 못한 입력을 처리하는가?
- [ ] requirements.txt에 모든 의존성이 나열되어 있는가?
- [ ] 스크립트에 docstring이 있는가?
검증
- [ ] 계획-검증-실행 패턴을 따르는가?
- [ ] 검증이 모든 중요한 케이스를 다루는가?
- [ ] 검증 실패 시 적용되지 않는가?
- [ ] 경고와 오류를 구분하는가?
문서화
- [ ] SKILL.md가 모든 워크플로우를 설명하는가?
- [ ] 각 스크립트의 사용법이 명확한가?
- [ ] 예시가 실제로 작동하는가?
- [ ] 참조 문서가 필요한 정보를 모두 포함하는가?
테스트
- [ ] 정상 케이스를 테스트했는가?
- [ ] 오류 케이스를 테스트했는가?
- [ ] 경고 케이스를 테스트했는가?
- [ ] 일괄 처리를 테스트했는가?
배운 내용
핵심 개념
- ✅ 계획-검증-실행 패턴
- ✅ 실행 가능한 스크립트 통합
- ✅ 명확한 오류 처리
- ✅ 시각적 분석 활용
- ✅ 검증 가능한 중간 출력
고급 기술
- ✅ Python 스크립트 작성
- ✅ PDF 처리 (PyPDF2)
- ✅ Excel 처리 (openpyxl)
- ✅ 이미지 변환 (pdf2image)
- ✅ 데이터 검증 로직
- ✅ 일괄 처리 자동화
실전 팁
1. 항상 검증하라
검증 단계를 건너뛰지 마세요. 시간을 절약하는 것 같지만, 오류를 수정하는 데 더 많은 시간이 듭니다.
2. 작은 배치로 시작하라
처음에는 5-10개 파일로 테스트하고, 문제없으면 전체를 처리하세요.
3. 백업을 유지하라
원본 PDF는 항상 별도로 보관하세요.
4. 로그를 확인하라
스크립트 출력을 주의 깊게 읽고, 경고 메시지를 무시하지 마세요.
5. 점진적으로 개선하라
처음부터 완벽할 필요는 없습니다. 사용하면서 개선하세요.
다음 단계
추가 기능 아이디어
- 📊 처리 통계 대시보드
- 🔄 실패한 항목 자동 재처리
- 📧 완료 시 이메일 알림
- 🗄️ 데이터베이스 통합
- 🌐 웹 인터페이스 추가
다른 유즈케이스 적용
이 패턴을 다음에 적용해보세요:
- 워드 문서 자동화
- 이미지 일괄 처리
- 데이터 ETL 파이프라인
- 보고서 자동 생성
자주 묻는 질문
Q: 스크립트가 실패하면 어떻게 하나요?
A: 오류 메시지를 주의 깊게 읽고, 해당 문제를 수정한 후 검증 단계부터 다시 시작하세요.
Q: PDF가 너무 복잡하면?
A: pdf_to_images.py로 시각적으로 확인하고, 필드 이름을 정확히 파악하세요.
Q: Excel 형식이 다르면?
A: read_excel.py의 field_mapping 부분을 프로젝트에 맞게 수정하세요.
Q: 성능을 더 개선하려면?
A: 병렬 처리를 구현하거나, 더 빠른 PDF 라이브러리를 사용해보세요.
축하합니다! 고급 스킬을 완성했습니다! 🎉🚀
이제 실행 가능한 스크립트와 강력한 검증 로직을 포함한 프로덕션 레벨의 스킬을 만들 수 있습니다. 이 패턴은 다양한 자동화 작업에 적용할 수 있습니다.
'AI > Claude code' 카테고리의 다른 글
| Claude Agent Skills Deep dive (0) | 2025.10.31 |
|---|---|
| Claude Code 컴포넌트 완벽 가이드: 실전 워크플로우 (1) | 2025.10.28 |
| Claude Skills 이용해서 API 문서화 스킬 만들기(중급) (0) | 2025.10.26 |
| Claude Skills를 이용해 간단한 블로그 글 작성 스킬 만들기 (초급) (0) | 2025.10.26 |
| Claude Agent Skill 제작 가이드 (심화과정) (0) | 2025.10.26 |
