스캔한 PDF를 엑셀로 변환하기
스캔한 PDF를 엑셀로 변환하는 방법. 각 페이지 이미지를 구조화된 필드로 읽어들이고, 원본과 대조해 검수한 뒤, 엑셀이 깔끔하게 여는 UTF-8 BOM CSV로 내보냅니다.
스캔한 PDF는 파일 안에 스프레드시트가 숨어 있는 게 아닙니다. 그건 문서를 찍은 사진입니다. 각 페이지는 행과 열, 합계가 담긴 이미지라서 사람 눈에는 표처럼 보이지만 컴퓨터에는 그냥 픽셀 덩어리일 뿐이죠. 스캔본에 "엑셀로 내보내기" 버튼이 좀처럼 없는 이유도 이것입니다. 내보낼 셀이 없고 이미지만 있으니까요. 진짜 행을 얻으려면 페이지를 구조화된 필드로 다시 읽어들인 다음, 그 필드를 엑셀이 열 수 있는 파일로 써내야 합니다.
이 글에서 다루는 흐름이 바로 그것입니다. 문서 이미지(스캔한 페이지, 휴대폰으로 찍은 사진, 팩스로 받은 영수증)를 가져와서 값을 이름이 붙은 필드로 추출하고, 엑셀에서 바로 열리는 CSV로 내보냅니다. 바이트 순서 표시(BOM)가 붙은 UTF-8이라서 한국어, 일본어, 중국어 텍스트가 깨지지 않고 제 열에 정확히 들어갑니다. "스캔한 PDF를 엑셀로 변환"의 결과물이 바로 이 CSV입니다.
스캔본이 곧장 엑셀로 가지 못하는 이유
종이 청구서를 스캔하면 결과물은 래스터 이미지입니다. JPEG 사진과 똑같은 종류의 파일이죠. space-ocr은 이런 래스터 포맷을 바로 받습니다. JPEG, PNG, GIF, BMP, TIFF, WebP 입니다. 원본이 여러 페이지짜리 PDF라면 두 가지 길이 있습니다. PDF를 space-ocr 앱에 그대로 끌어다 놓으면 각 페이지를 알아서 이미지로 렌더링해 줍니다. 또는 REST API를 직접 호출하는 경우라면 각 페이지를 먼저 이미지(PNG 또는 TIFF)로 내보낸 뒤 전송하면 됩니다. 어느 쪽이든 OCR은 페이지 이미지를 대상으로 동작합니다.
엔진은 각 이미지를 읽어 값을 찾아내고, 그 값을 페이지의 어디에서 읽었는지 출처 좌표를 특정한 만큼 함께 돌려줍니다. 0–1000 으로 정규화된 축 정렬 box 와, 지면의 기울기를 따라가는 4점 quad 입니다. 영역을 특정하지 못한 값이나 인쇄된 글자와 대조가 어긋난 값은 조용히 통과하지 않고 검토 대상으로 표시됩니다. 페이지가 필드로 구조화되고 나면, 엑셀로 만드는 건 CSV 다운로드일 뿐입니다. 어렵고 또 제대로 해야 할 부분은 내보내기가 아니라 읽어들이는 과정입니다.
문서 이미지에서 구조화된 필드로
문서 이미지를 업로드하거나, 그냥 사진이나 PDF를 끌어다 놓기만 하면 값이 글자 더미가 아니라 이름이 붙은 필드로 나옵니다. 가장 빠른 길은 앱이 필드를 대신 제안하게 하는 것입니다. 페이지를 끌어다 놓으면 별도 설정 없이 스키마를 자동으로 제안해 줍니다. 필요한 열이 이미 정해져 있다면 직접 컬럼(스키마)을 정의하면 됩니다. 이름과 타입을 한 번 선언해 두면 그 시트에 올리는 페이지마다 같은 정의로 읽힙니다. 스캔본이 라벨이 붙은 열로 바뀌는 과정을 보세요:
반복되는 행이 있는 문서(청구서 품목 라인, 영수증 상품 목록)라면, 자식 열을 가진 array 필드를 선언하세요. 페이지의 각 줄이 저마다 하나의 행이 되는데, 스프레드시트에서 합계를 내야 할 때 바로 이게 필요한 형태입니다. 이런 반복 행을 특별히 다루고 있다면, 필드 스펙의 세부 사항은 청구서에서 품목 라인 추출하기 글을 참고하세요.
{
"image": "https://example.com/scanned-page-01.png",
"imageType": "url",
"fields": [
{ "name": "vendor", "type": "string" },
{ "name": "invoice_date", "type": "string" },
{ "name": "total", "type": "string" },
{
"name": "line_items", "type": "array",
"children": [
{ "name": "description", "type": "string" },
{ "name": "unit_price", "type": "string" },
{ "name": "qty", "type": "string" }
]
}
]
}값은 저희 쪽에서 정규화하지 않습니다. 인쇄된 7,855 는 7,855 로 돌아옵니다. 쉼표, 소수점, 전각 문자를 표준 숫자 표기로 고쳐 쓰지 않고 읽은 표기 그대로 두므로, 지면과 대조해 합계를 맞출 수 있습니다. 앱에서 보이는 통화 기호는 UI 장식일 뿐 값의 일부가 아닙니다. 필드 설명에 무엇을 적어도 이건 바뀌지 않습니다 — 좌표와 대조가 의미를 가지려면 값이 인쇄된 모습에 붙어 있어야 하니까요. 다만 values 는 모델이 지면을 읽어낸 텍스트라, 인쇄된 글자와 문자 단위로 정확히 맞춰 봐야 한다면 그 좌표의 원시 OCR 글자인 evidence.printed_text 를 쓰세요. 계산에 쓸 숫자가 필요하면 API 가 인쇄된 값 옆에 그것도 같이 돌려줍니다. 필드의 type 을 number·integer·date 로 선언하면 values 와 나란히 해석된 normalized 가 함께 옵니다.
검수한 다음 엑셀로 내보내기
엑셀로 가져오기 전에 읽어들인 결과를 점검하세요. 값에 마우스를 올리면 원본 이미지에서 해당 영역이 강조되므로, 스캔본 전체를 다시 훑을 필요 없이 눈이 바로 그 지점으로 향합니다.
값을 하나하나 눈으로 확인할 필요도 없습니다. 읽기 결과에는 확인할 목록이 함께 옵니다. data.review.flagged 에는 검토가 필요한 값마다 그 값의 path 와 플래그 reasons 가 담깁니다. 인쇄된 글자와 대조가 어긋나면 text_mismatch, 출처 영역을 특정하지 못하면 nobox, 필수로 선언한 필드가 비어서 돌아오면 missing 입니다. 셀의 verified 는 그 판정이라, 사유가 하나라도 서면 false 가 됩니다. 이 목록만 처리하고 나머지는 그대로 두면 됩니다. evidence.match_ratio 는 셀에 붙는 보조 근거이지, 임계값으로 걸러 내라고 있는 숫자가 아닙니다.
엑셀에서 열리는 CSV로 내보내기
필드가 제대로 보이면 시트를 내보냅니다. 열 이름이 담긴 헤더 행과 함께 <sheetName>.csv 가 만들어지고, array 필드는 column.child 열로 펼쳐지며 반복되는 품목 라인은 하위 행으로 전개됩니다. 파일은 BOM이 붙은 UTF-8 인데, 더블클릭만으로 엑셀이 CJK 텍스트를 깔끔하게 여는 건 바로 이 디테일 덕분입니다. 직접 수정한 부분은 내보내기에서 원래 OCR 값을 덮어씁니다.
내보내기 자체는 Free 와 Pay-as-you-go 플랜에서 1크레딧을 씁니다. Starter 와 Pro 에서는 다운로드가 무료입니다. 같은 데이터를 다시 받는 것은 어느 플랜에서도 비용이 들지 않습니다.
엑셀에서 여는 법은 간단합니다. .csv 를 더블클릭하면 됩니다. BOM 덕분에 엑셀이 자동으로 UTF-8로 읽어들이므로, 텍스트 가져오기 마법사도 글자 깨짐도 없습니다. 거기서 네이티브 워크북이 필요하면 다른 이름으로 저장 → .xlsx 하면 됩니다. 최종 목표가 엑셀이라기보다 일반 CSV 파이프라인이라면, 스캔 문서를 CSV로 변환하는 자매 가이드가 동일한 내보내기 과정을 처음부터 끝까지 다룹니다.
API로 대량 처리하기
스캔본이 한 폴더 가득이라면, 열 스키마를 가진 시트를 한 번 만들어 두고 그 시트에 페이지 이미지를 업로드하세요. 각 이미지는 그 스키마에 맞춰 읽혀 행으로 추가되고, 나중에 하나의 CSV로 내보낼 수 있습니다.
POST /upload 의 한도는 요청당 20개 파일, 파일당 20MB, 요청 전체 28MB 입니다(넘으면 413 이 돌아옵니다). 비용은 페이지당 1크레딧(₩100, 부가세 포함)입니다. 호출은 기본이 비동기라 응답에 jobs[] 가 담기고, 결과는 ocr.completed 웹훅이나 GET /jobs/{jobId} 폴링으로 받습니다. 아래 요청처럼 wait=true 를 주면 동기로 기다리는데, 대기는 이미지당 최대 30초이고 그 안에 끝나지 않은 항목은 status: "pending" 으로 돌아와 나중에 회수하게 됩니다. 재시도할 때 Idempotency-Key 를 함께 보내면 같은 요청이 두 번 스캔되지 않고 캐시된 응답이 그대로 돌아옵니다. 전체 요청/응답 형태는 API 문서에 있습니다.
curl -X POST https://api.space-ocr.com/upload \
-H "Authorization: Bearer $SPACE_OCR_API_KEY" \
-F "path=/Invoices 2026" \
-F "files=@scan-page-01.png" \
-F "files=@scan-page-02.png" \
-F "wait=true"스캔한 PDF를 엑셀로 변환하는 방법
- PDF나 페이지 이미지 추가하기space-ocr 앱에서는 PDF를 그냥 끌어다 놓기만 하면 됩니다. 각 페이지가 알아서 이미지로 래스터화되므로 변환할 게 없습니다. REST API를 직접 호출하는 경우라면, 엔진이 PDF 바이트가 아니라 래스터 이미지(JPEG, PNG, GIF, BMP, TIFF, WebP)를 읽으므로 각 페이지를 먼저 래스터 이미지로 내보내세요.
- 페이지를 필드로 읽어들이기값을 이름이 붙은 필드로 추출하세요. 가장 빠른 방법은 앱이 페이지에서 필드를 자동으로 제안하게 하는 것입니다. 필요한 열이 정해져 있다면 직접 컬럼(스키마)을 정의해도 됩니다. 반복되는 품목 라인에는 array 필드를 선언합니다.
- 값 검수하기필드에 마우스를 올려 원본 스캔본에서 값이 읽힌 위치를 강조해 보세요. 확인은 검토 대상으로 표시된 값만 하면 됩니다. API 로는 data.review.flagged 에 사유와 함께 돌아오므로, 그 목록만 처리하고 나머지는 그대로 둡니다.
- CSV 내보내기시트를 CSV로 내보냅니다. BOM이 붙은 UTF-8이며 array 품목 라인을 하위 행으로 펼치고, 직접 수정한 부분은 원래 OCR 값을 덮어씁니다.
- 엑셀에서 열기CSV를 더블클릭하면 엑셀이 BOM을 읽어 행과 열이 정렬되고 CJK 텍스트가 온전한 상태로 엽니다. 네이티브 워크북이 필요하면 다른 이름으로 저장 .xlsx 하세요.