어느정도 규모가 있는 기업들은 이미 ERP 시스템과 연동된 자동화 솔루션을 쓰고 있겠지만, 많은 중소기업과 스타트업은 여전히 영수증을 수동으로 처리하고 있을 것이다.
우리 회사도 그랬다. 영수증을 하나하나 Excel에 입력하는 모습을 자주 봤다. 날짜, 상호명, 금액, 품목... 영수증 한 장에 1-2분씩, 월말이면 수백 장이 쌓인다.
시중의 경비처리 솔루션들도 있지만 도입 비용이나 학습 곡선 때문에 결국 익숙한 Excel로 돌아온다. "Excel 안에서 바로 자동화할 수 없을까?"라는 생각이 들었다.
간단한 사이드 프로젝트로 시작했다. 목표는 명확했다:
Tesseract, EasyOCR, PaddleOCR 등 여러 오픈소스 OCR을 테스트했다.
공통적인 문제:
실제로 사용하기에는 정확도가 너무 낮았다. 수동으로 수정해야 할 부분이 많아 자동화의 의미가 없었다.
네이버 클로바 OCR와 업스테이지 Document AI를 검토했다. 둘 다 한국어 영수증을 잘 인식한다는 리뷰를 확인할 수 있었다.
하지만 비용이 문제였다:


혹시나 하는 마음에 OpenAI의 GPT-4o 멀티모달을 테스트해봤는데 결과가 예상보다 좋았다.
실제 테스트에서 확인한 것:
Temperature 0에서도 100% 일관성은 없었지만, 기존 OCR 모델보다 정확하며 상용 OCR API 보다 비용이 저렴했다.
처음엔 독립 프로그램을 생각했다. 하지만:
가장 어려웠던 부분은 Excel API와의 통합이었다.
// Excel 테이블 메타데이터 가져오기
const getTableMeta = async (): Promise<TableMeta> => {
return Excel.run(async (context) => {
const selectedRange = context.workbook.getSelectedRange();
selectedRange.load("rowIndex,columnIndex,columnCount,values");
await context.sync();
// 사용자가 선택한 헤더 추출
const headers = selectedRange.values[0].map((cell) => (cell === null ? "" : String(cell)));
return {
headers,
startRow: selectedRange.rowIndex,
startCol: selectedRange.columnIndex,
colCount: selectedRange.columnCount,
};
});
};
문제: Excel API는 비동기인데, 에러 처리가 까다롭다.
// 데이터 삽입 로직 (실제는 더 복잡)
const insertBillData = async (data: any[]) => {
try {
await Excel.run(async (context) => {
const sheet = context.workbook.worksheets.getActiveWorksheet();
const usedRange = sheet.getUsedRange();
usedRange.load("rowCount");
await context.sync();
// 빈 행 찾기, 데이터 삽입
// 각 단계마다 context.sync() 필요
// 에러 발생 시 롤백이 어려움
});
} catch (error) {
// Excel API 에러는 매우 불친절하다
// "GeneralException" 같은 모호한 메시지
console.error("데이터 삽입 실패:", error);
}
};
# 서버 핵심 로직 (Python/FastAPI)
@app.post("/scan")
async def scan_receipts(
files: List[UploadFile],
columns: str = Form(...)
):
results = []
for file in files:
# 이미지를 base64로 인코딩
image_base64 = base64.b64encode(await file.read()).decode()
# GPT-4o에 요청
response = await openai.chat.completions.create(
model="gpt-4o",
messages=[{
"role": "system",
"content": f"영수증에서 다음 정보를 추출: {columns}"
}, {
"role": "user",
"content": [{
"type": "image_url",
"image_url": {"url": f"data:image/jpeg;base64,{image_base64}"}
}]
}],
temperature=0,
response_format={"type": "json_object"}
)
results.append(json.loads(response.choices[0].message.content))
return {"results": results}
# 요청 제한 구현
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
@app.post("/scan")
@limiter.limit("10/minute") # 분당 10개 요청
async def scan_receipts(...):
# GPT-4o API도 제한이 있고
# 비용도 고려해야 한다
pass
실제로 사용해본 결과:
이 프로젝트는 "완벽한 OCR 솔루션"이 아니다. 하지만:
무엇보다, 실제로 사용 가능한 수준이다.
물론 한계는 있다. 하지만 영수증 자동화라는 실용적 목표에는 충분했다.
코드는 GitHub에 공개했다. 개선 아이디어가 있다면 PR은 언제나 환영이다.