PDF 보고서에서 표와 텍스트만 뽑아 엑셀로 저장했더니, 업무 시간 반토막

PDF 보고서에서 필요한 표와 텍스트를 자동으로 뽑아 엑셀로 저장하는 파이썬 시스템, 직접 만들어 써본 결과 월 15시간 이상 수작업이 사라졌습니다.

회사에서 매달 쏟아지는 PDF 보고서. 거기서 숫자 몇 개, 표 하나 뽑자고 Ctrl+C, Ctrl+V를 반복하고 있진 않으세요? 저도 그랬거든요. 거래처에서 오는 월간 실적 보고서가 PDF로만 오는데, 매번 엑셀에 옮기는 게 일이었어요. 한두 장이면 괜찮은데, 20~30페이지짜리 보고서를 매달 수십 개 처리해야 하니까 눈이 빠질 지경이었습니다.

그래서 파이썬으로 자동화 시스템을 만들었어요. PDF 파일을 폴더에 넣기만 하면 텍스트와 표를 알아서 뽑아서 엑셀 파일로 저장해주는 구조인데, 만들고 나서 진짜 세상이 달라졌습니다. 이 글에서는 제가 삽질하면서 알아낸 라이브러리 선택 기준부터 완전 자동화 시스템 구축까지 전부 공유할게요.

매달 PDF 100개, 수작업 복붙의 한계

처음엔 솔직히 자동화까지 필요하다고 생각 안 했어요. PDF 열어서 표 드래그하고, 엑셀에 붙여넣기 하면 되니까. 근데 이게 깨지거든요. PDF에서 복사한 표가 엑셀에 붙으면 셀이 다 합쳐져 있거나, 숫자 사이에 이상한 공백이 들어가 있거나. 한 파일 정리하는 데 15분은 기본이었습니다.

월간 보고서가 매달 80~100개 정도 들어오는데, 보고서당 표가 평균 3~4개씩이에요. 계산해보면 한 달에 순수하게 PDF 복붙에만 20시간 넘게 쓰고 있었던 거예요. 팀 후배한테 시키기도 미안한 단순 반복 작업이었죠.

Adobe Acrobat Pro 같은 유료 도구도 써봤는데, 일괄 변환 기능이 생각보다 유연하지 않더라고요. 보고서마다 표 위치가 다르고 형식도 제각각이라서, 결국 수작업 후처리가 필요했습니다. 그때 파이썬 자동화를 진지하게 고민하기 시작했어요.

pdfplumber vs tabula-py vs camelot 실전 비교

파이썬에서 PDF 표를 추출하는 라이브러리는 크게 세 가지예요. pdfplumber, tabula-py, camelot. 세 개 다 설치해서 실제 업무용 PDF 보고서 50개로 테스트해봤습니다. 결론부터 말하면, pdfplumber가 압도적이었어요.

항목 pdfplumber tabula-py
표 추출 정확도 90% 이상 70~80%
설치 난이도 pip만으로 완료 Java 8+ 필수
디버깅 도구 시각적 디버깅 지원 제한적
셀 병합 처리 파라미터 조정으로 대응 자동 처리 어려움
텍스트 동시 추출 가능 표만 추출

camelot도 테스트했는데, 솔직히 기대 이하였어요. Ghostscript라는 별도 프로그램을 시스템에 설치해야 하고, 테두리 없는 표에서는 추출률이 확 떨어졌거든요. 실험 결과에서도 pdfplumber 다음으로 tabula-py가 나았고, camelot은 가장 추출률이 낮았습니다.

tabula-py의 가장 큰 걸림돌은 Java 의존성이에요. 파이썬만 깔면 되는 게 아니라 JRE(Java Runtime Environment)가 반드시 있어야 합니다. 도커 환경에서 배포할 때 이미지 크기가 확 커지는 것도 신경 쓰이고요. 그래서 저는 pdfplumber를 메인으로 쓰고 있어요.

📊 실제 데이터

pdfplumber GitHub 저장소 기준 Python 3.10~3.14까지 테스트 통과, 최신 버전 0.11.9에서 pdfminer.six 엔진이 업그레이드되었습니다. 한 실험 블로그에서 세 라이브러리를 비교한 결과, pdfplumber가 다른 라이브러리로는 추출 불가능한 표도 잡아내는 높은 성능을 보였다고 합니다.

pdfplumber로 PDF 텍스트와 표 추출하는 법

설치부터 해볼게요. 터미널에서 한 줄이면 됩니다.

pip install pdfplumber openpyxl pandas

pdfplumber는 PDF의 문자 위치와 선분 좌표를 분석해서 표를 찾아내는 방식이에요. 단순히 텍스트만 긁어오는 게 아니라, 셀 경계선의 교차점을 계산해서 행과 열을 구분하거든요. 그래서 정확도가 높은 거예요.

기본적인 텍스트 추출 코드부터 볼게요.

import pdfplumber

with pdfplumber.open(“보고서.pdf”) as pdf:
for page in pdf.pages:
text = page.extract_text()
if text:
print(f”— {page.page_number}페이지 —“)
print(text)

이게 끝이에요. 진짜로요. 처음 돌려보고 허탈했던 게, 그동안 손으로 하던 걸 코드 5줄로 해결할 수 있었다는 사실이었습니다.

표 추출은 이렇게 해요.

import pdfplumber
import pandas as pd

with pdfplumber.open(“보고서.pdf”) as pdf:
all_tables = []
for page in pdf.pages:
tables = page.extract_tables()
for table in tables:
df = pd.DataFrame(table[1:], columns=table[0])
all_tables.append(df)
print(f”{page.page_number}p 표 추출 완료”)

extract_tables()가 핵심 메서드인데, 페이지 안에 있는 모든 표를 리스트 형태로 돌려줘요. 첫 번째 행이 헤더라고 가정하고 pandas DataFrame으로 변환하면 바로 데이터 가공이 가능합니다. 근데 여기서 한 가지 함정이 있어요. 모든 PDF의 표가 첫 행을 헤더로 쓰는 건 아니거든요. 보고서 양식에 따라 table[0]이 헤더가 아닐 수도 있으니까, 실제 데이터를 눈으로 한번 확인하는 과정이 필요합니다.

특정 영역만 잘라서 추출하는 것도 됩니다. 페이지 전체가 아니라 원하는 좌표 범위만 지정할 수 있어요.

page = pdf.pages[0]
cropped = page.within_bbox((30, 200, 580, 500))
table = cropped.extract_table()

within_bbox에 넣는 4개 숫자는 (왼쪽x, 위쪽y, 오른쪽x, 아래쪽y) 좌표예요. 보고서 상단의 로고나 주소 같은 불필요한 영역을 잘라내고 표만 깔끔하게 뽑을 때 유용합니다.

추출 데이터를 엑셀에 깔끔하게 저장하기

표를 뽑았으면 엑셀로 저장해야겠죠. pandas의 to_excel 메서드를 쓰면 한 줄이에요. 근데 여기서 제가 처음에 실수한 게 있어요. 그냥 저장하면 서식이 하나도 없는 밋밋한 엑셀이 나오거든요.

import pandas as pd
from openpyxl.styles import Font, Border, Side, Alignment

# 기본 저장 (서식 없음)
df.to_excel(“결과.xlsx”, index=False)

# 서식 적용 저장
with pd.ExcelWriter(“결과_서식.xlsx”, engine=”openpyxl”) as writer:
for i, df in enumerate(all_tables):
sheet_name = f”표_{i+1}”
df.to_excel(writer, sheet_name=sheet_name, index=False)

ws = writer.sheets[sheet_name]
# 헤더 서식
for cell in ws[1]:
cell.font = Font(bold=True, size=11)
cell.alignment = Alignment(horizontal=”center”)
# 열 너비 자동 조정
for col in ws.columns:
max_len = max(len(str(c.value or “”)) for c in col)
ws.column_dimensions[col[0].column_letter].width = max_len + 3

openpyxl을 같이 쓰면 헤더를 굵게 만들거나, 셀 정렬을 맞추거나, 열 너비를 내용에 맞게 자동 조절할 수 있어요. 이거 하나 넣었을 뿐인데 팀원들 반응이 완전 달라졌습니다. “이거 직접 만든 거야?” 소리를 듣게 되더라고요.

💡 꿀팁

PDF 하나에 표가 여러 개라면, 각 표를 엑셀의 별도 시트로 나눠 저장하는 게 훨씬 관리하기 좋아요. ExcelWriter의 sheet_name 파라미터로 시트 이름을 지정하면 됩니다. 한 시트에 다 몰아넣으면 나중에 어떤 표가 어느 페이지에서 온 건지 구분이 안 돼요.

여러 PDF를 한번에 처리해서 하나의 엑셀로 합치는 것도 가능합니다.

import glob

pdf_files = glob.glob(“보고서폴더/*.pdf”)
with pd.ExcelWriter(“전체_통합.xlsx”, engine=”openpyxl”) as writer:
for pdf_path in pdf_files:
file_name = pdf_path.split(“/”)[-1].replace(“.pdf”,””)
with pdfplumber.open(pdf_path) as pdf:
for page in pdf.pages:
tables = page.extract_tables()
for j, table in enumerate(tables):
df = pd.DataFrame(table[1:], columns=table[0])
sheet = f”{file_name}_p{page.page_number}_{j+1}”
df.to_excel(writer, sheet_name=sheet[:31], index=False)

한 가지 주의할 점. 엑셀 시트 이름은 최대 31자까지만 허용돼요. 파일명이 길면 잘려야 하니까 [:31]로 슬라이싱하는 게 안전합니다. 이거 모르고 처음에 에러 나서 30분 삽질한 적 있어요.

폴더 감시로 완전 자동화 시스템 구축

여기까지는 스크립트를 직접 실행해야 하잖아요. 진짜 자동화는 폴더에 PDF를 넣기만 하면 알아서 돌아가는 거예요. 파이썬의 watchdog 라이브러리를 쓰면 특정 폴더를 실시간으로 감시해서, 새 파일이 들어오는 순간 자동으로 처리할 수 있습니다.

pip install watchdog

import time
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
import pdfplumber, pandas as pd

class PDFHandler(FileSystemEventHandler):
def on_created(self, event):
if event.src_path.endswith(“.pdf”):
time.sleep(1) # 파일 쓰기 완료 대기
self.process_pdf(event.src_path)

def process_pdf(self, pdf_path):
output = pdf_path.replace(“.pdf”, “.xlsx”)
try:
with pdfplumber.open(pdf_path) as pdf:
with pd.ExcelWriter(output, engine=”openpyxl”) as writer:
for page in pdf.pages:
# 텍스트 저장
text = page.extract_text()
if text:
text_df = pd.DataFrame({“내용”: text.split(“\n”)})
text_df.to_excel(writer, sheet_name=f”텍스트_p{page.page_number}”[:31], index=False)
# 표 저장
tables = page.extract_tables()
for j, table in enumerate(tables):
df = pd.DataFrame(table[1:], columns=table[0])
df.to_excel(writer, sheet_name=f”표_p{page.page_number}_{j+1}”[:31], index=False)
print(f”완료: {output}”)
except Exception as e:
print(f”오류: {pdf_path} – {e}”)

observer = Observer()
observer.schedule(PDFHandler(), path=”./pdf_input”, recursive=False)
observer.start()
print(“폴더 감시 시작… (Ctrl+C로 종료)”)
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
observer.join()

이 스크립트를 실행해두면 pdf_input 폴더를 계속 감시하고 있다가, PDF가 들어오는 순간 자동으로 엑셀 변환을 시작해요. time.sleep(1)을 넣은 건 파일이 아직 복사 중일 때 열어버리는 걸 방지하기 위해서인데, 네트워크 드라이브에서 복사할 때는 2~3초로 늘리는 게 안전합니다.

저는 이걸 회사 공용 NAS에 걸어뒀어요. 팀원들이 PDF를 해당 폴더에 넣기만 하면, 옆에 같은 이름의 엑셀 파일이 자동으로 생기니까. 파이썬을 모르는 팀원도 쓸 수 있게 되더라고요. 처음 이걸 보여줬을 때 팀장님이 “이거 외주 맡긴 거야?” 하신 게 아직도 기억나요.

💬 직접 써본 경험

3개월 정도 운영해보니 한 달에 약 400개 PDF를 자동 처리하게 됐어요. 가끔 표 추출이 깨지는 파일이 있는데, 비율로 따지면 5% 미만이었습니다. 그 5%만 수작업으로 보정하면 되니까, 이전에 비하면 업무량이 비교할 수 없을 만큼 줄었어요.

표 추출이 깨질 때 해결법

pdfplumber가 아무리 좋아도 만능은 아니에요. 제가 겪은 대표적인 문제 세 가지와 해결법을 공유합니다.

첫 번째, 셀 병합이 많은 표. 이게 가장 골치 아파요. pdfplumber는 셀 경계선의 교차점으로 표를 인식하는데, 셀이 병합되면 교차점이 없어지니까 표 구조가 무너집니다. 이럴 때는 table_settings에서 snap_tolerance 값을 올려보세요. 기본값 3에서 5~8 정도로 조정하면 근접한 선분을 하나로 인식해서 병합 셀도 어느 정도 잡아냅니다.

두 번째, 테두리 없는 표. 디자인이 깔끔한 보고서일수록 표에 선이 없는 경우가 많아요. 이때는 추출 전략을 바꿔야 해요.

table_settings = {
“vertical_strategy”: “text”,
“horizontal_strategy”: “text”,
“min_words_vertical”: 2,
“min_words_horizontal”: 1,
“snap_tolerance”: 5,
}
table = page.extract_table(table_settings)

vertical_strategy와 horizontal_strategy를 “lines”에서 “text”로 바꾸면, 선이 아니라 텍스트의 정렬 패턴으로 표를 추론해요. 물론 정확도가 좀 떨어질 수 있지만, 선이 아예 없는 표에서는 이 방법밖에 없습니다.

세 번째, 스캔된 PDF. 이건 pdfplumber로 해결이 안 돼요. pdfplumber는 텍스트 레이어가 있는 PDF에서만 작동하거든요. 스캔해서 이미지로 된 PDF라면 먼저 OCR(광학 문자 인식)을 돌려야 합니다. Tesseract OCR이나 클라우드 Vision API를 써서 텍스트 레이어를 만든 다음에 pdfplumber를 돌리는 2단계 처리가 필요해요.

⚠️ 주의

pdfplumber의 시각적 디버깅 기능을 꼭 활용하세요. page.to_image(resolution=150) 후 im.debug_tablefinder()를 실행하면 pdfplumber가 어떤 선을 감지하고 어디를 표로 인식했는지 이미지로 확인할 수 있습니다. 추출 결과가 이상할 때 원인을 바로 파악할 수 있어요.

여러 페이지에 걸쳐 있는 표도 흔한 문제인데요. 10페이지짜리 표가 3페이지에 나눠져 있으면 pdfplumber는 각 페이지별로 따로 추출해요. 이런 경우에는 추출 후에 pandas의 concat으로 합쳐주면 됩니다. 다만, 페이지마다 헤더 행이 반복되는 경우가 있으니까 중복 헤더를 제거하는 로직을 넣어두는 게 좋아요.

# 여러 페이지 표 합치기
merged = pd.concat(all_tables, ignore_index=True)
# 중복 헤더 행 제거 (첫 번째 표의 헤더와 동일한 행 삭제)
header_values = list(all_tables[0].columns)
merged = merged[~merged.apply(lambda row: list(row) == header_values, axis=1)]

흔한 오해 하나 바로잡을게요. “PDF를 엑셀로 변환하면 원본과 100% 동일하게 나온다”고 생각하시는 분들이 있는데, 그건 아니에요. PDF는 원래 화면 표시용 포맷이라 데이터 구조 정보를 담고 있지 않거든요. 라이브러리들이 시각적 패턴을 분석해서 추론하는 거라, 복잡한 표일수록 후처리가 필요합니다. 완벽한 자동화보다는 90%를 자동화하고 10%만 확인하는 시스템을 목표로 잡는 게 현실적이에요.

❓ 자주 묻는 질문

Q. 한글이 포함된 PDF도 잘 추출되나요?

네, pdfplumber는 유니코드를 지원해서 한글 PDF도 정상 추출됩니다. 다만, 일부 한글 폰트가 임베딩되지 않은 PDF에서는 글자가 깨질 수 있어요. 그런 경우 pdfplumber.open() 시 unicode_norm=”NFKC” 옵션을 추가하면 개선되는 경우가 많습니다.

Q. 암호가 걸린 PDF도 처리할 수 있나요?

pdfplumber.open(“파일.pdf”, password=”비밀번호”)로 비밀번호를 전달하면 열 수 있어요. 단, 소유자 비밀번호로 편집 권한만 제한된 PDF는 대부분 읽기는 가능합니다.

Q. 수백 페이지 PDF를 처리하면 메모리 문제가 생기지 않나요?

pdfplumber는 페이지 단위로 처리하기 때문에 한번에 전체 PDF를 메모리에 올리지 않아요. 다만, 처리한 페이지 객체가 캐시를 보유하고 있어서 대용량 PDF에서는 page.close() 메서드로 캐시를 해제해주는 것이 좋습니다.

Q. Windows 작업 스케줄러로 자동 실행할 수 있나요?

watchdog 방식 대신 특정 시간에 실행하고 싶다면, Windows 작업 스케줄러나 Linux crontab에 파이썬 스크립트를 등록하면 됩니다. 예를 들어 매일 오전 9시에 폴더 내 PDF를 전부 처리하는 방식으로 운영할 수 있어요.

Q. 추출한 데이터를 구글 스프레드시트로 바로 보낼 수 있나요?

gspread 라이브러리와 Google Sheets API를 연동하면 가능합니다. pandas DataFrame을 gspread의 update 메서드로 전달하면 구글 시트에 바로 업로드돼요. 다만 API 인증 설정이 별도로 필요합니다.

본 포스팅은 개인 경험과 공개 자료를 바탕으로 작성되었으며, 전문적인 의료·법률·재무 조언을 대체하지 않습니다. 정확한 정보는 해당 분야 전문가 또는 공식 기관에 확인하시기 바랍니다.

PDF 수작업 복붙에 시간을 뺏기고 있다면, pdfplumber + pandas + watchdog 조합으로 자동화 시스템을 한번 만들어보세요. 설치부터 폴더 감시까지 파이썬 초보도 반나절이면 세팅할 수 있고, 한번 만들어두면 매달 수십 시간을 아낄 수 있습니다.


직접 시도해보시고 궁금한 점이나 다른 방법이 있으시면 댓글로 공유해주세요. 비슷한 자동화 팁이 필요하시면 공유 부탁드려요!

댓글 남기기