4 min read

Python์œผ๋กœ ์นด๋“œ๋‚ด์—ญ ์ž‘์„ฑ ์ž๋™ํ™”ํ•˜๊ธฐ

Table of Contents

์นด๋“œ ์ด๋ฏธ์ง€

ํ˜„์žฌ ํšŒ์‚ฌ์—์„œ๋Š” ๋ฒ•์ธ์นด๋“œ๋ฅผ ์ผ์ • ๊ธˆ์•ก ๋‚ด์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ๋‹ฌ๋งˆ๋‹ค ์‚ฌ์šฉ๋‚ด์—ญ์„ ์ œ์ถœํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
๋ฌผ๋ก  ํšŒ์‚ฌ์— ๋”ฐ๋ผ ์นด๋“œ์‚ฌ์—์„œ ์ง์ ‘ ์—‘์…€์„ ์ œ๊ณตํ•ด ์ฃผ๊ธฐ๋„ ํ•˜์ง€๋งŒ, ์ง€๊ธˆ์€ ๊ทธ๋ ‡์ง€ ์•Š์•„ ์ˆ˜๊ธฐ๋กœ ์ž‘์„ฑํ•ด์•ผ ํ•˜๋Š”๋ฐ ๋‚ด์—ญ์„ ํ•ธ๋“œํฐ์œผ๋กœ ํ™•์ธํ•ด ์ผ์ผ์ด ์—‘์…€์— ์˜ฎ๊ฒจ ์ ๋Š” ๊ฒŒ ๋„ˆ๋ฌด ๊ท€์ฐฎ์•˜์Šต๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ ์–ด๋–ป๊ฒŒ๋“  ์—‘์…€์„ ์“ฐ์ง€ ๋ง์ž๋ผ๋Š” ์ƒ๊ฐ์œผ๋กœ ์‚ฝ์งˆ์ž๋™ํ™”๋ฅผ ์—ด์‹ฌํžˆ ์ง„ํ–‰ํ–ˆ๋˜ ๋‚ด์šฉ์„ ์ ์–ด ๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

ํ•ต์‹ฌ ์•„์ด๋””์–ด: OCR

์•ฑ์—์„œ ์นด๋“œ ์‚ฌ์šฉ๋‚ด์—ญ ์ •๋ณด๋ฅผ ์–ป๋Š” ๋ฐฉ๋ฒ•์€ ํฌ๊ฒŒ 2๊ฐ€์ง€ ์ •๋„๊ฐ€ ์ƒ๊ฐ์ด ๋‚ฌ์Šต๋‹ˆ๋‹ค.
์ฒซ ๋ฒˆ์งธ๋Š” ์นด๋“œ์Šน์ธ ์‹œ ์˜ค๋Š” ์•Œ๋ฆผ ๋“ฑ์—์„œ ํŒจํ‚ท์„ ๊ธ์–ด ์ •๋ณด๋ฅผ ๋นผ๋‚ด๋Š” ๊ฑด๋ฐโ€ฆ ๊ทธ๋‹ค์ง€ ์ž์‹ ์ด ์—†๊ณ  ์„ค์‚ฌ ๊ฐ€๋Šฅํ•˜๋”๋ผ๋„ ๋ณด์•ˆ์„ ๋šซ๋Š” ๋А๋‚Œ์ด๋ผ ๋ฐ”๋žŒ์งํ•œ ๋ฐฉ๋ฒ•์€ ์•„๋‹ˆ๋ผ๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์„ ์ƒ๊ฐํ–ˆ๊ณ  ๊ทธ ๋ฐฉ๋ฒ•์€ ๋ฐ”๋กœ ์Šคํฌ๋ฆฐ์ƒท์„ ์ฐ์–ด OCR ์ธ์‹์œผ๋กœ ๋‚ด์—ญ์„ ์ถ”์ถœํ•˜๋Š” ๊ฒƒ์ด์—ˆ์Šต๋‹ˆ๋‹ค.

๋ฌธ์ œ๋Š” ์˜คํ”ˆ์†Œ์Šค(๋ฌด๋ฃŒ) OCR ๋„๊ตฌ์˜ ํ•œ๊ธ€ ์ธ์‹๋ฅ ์ด ์˜ ์ข‹์ง€ ์•Š๋‹ค๋Š” ์ ์ด์—ˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ ์‹œํ–‰์ฐฉ์˜ค๋ฅผ ๊ฑฐ์ณ ๋ณด์•˜์ง€๋งŒ ๋ฒˆ๊ฑฐ๋กญ๊ธฐ๋งŒ ํ•˜๊ณ  ๊ฒฐ๊ณผ๋„ ๊ทธ๋ฆฌ ๋งŒ์กฑ์Šค๋Ÿฝ์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ๊ฒฐ๊ตญ ์ƒ๊ฐ๋‚˜๋Š” ๊ฒƒ์€ ๋„ค์ด๋ฒ„โ—ฆ์นด์นด์˜ค ๋“ฑ ๊ตญ๋‚ด์—์„œ ์ œ๊ณตํ•˜๋Š” OCR์ธ๋ฐ, ์œ ๋ฃŒ๋ผ๋Š” ์ ์ด ์—ญ์‹œ ๊ฑธ๋ ธ์Šต๋‹ˆ๋‹ค.
๊ทธ๋Ÿฌ๋˜ ์ค‘ CLOVA OCR ์ฒดํ—˜ ํŽ˜์ด์ง€๋ฅผ ์ฐพ๊ฒŒ ๋˜์—ˆ๋Š”๋ฐ, ์ฒดํ—˜ ๋ช…๋ชฉ์œผ๋กœ ์ ์€ ์–‘์ด์ง€๋งŒ ๋ฌด๋ฃŒ๋กœ ์‚ฌ์ง„ ๋ช‡ ๊ฐœ๋ฅผ ์—…๋กœ๋“œํ•ด OCR ๊ฒฐ๊ณผ๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ํ•ด๋‹น ํŽ˜์ด์ง€๋ฅผ Selenium์œผ๋กœ ์กฐ์ž‘ํ•ด OCR ์ธ์‹ ๊ฒฐ๊ณผ๋ฅผ ์–ป์œผ๋ฉด ๋˜๊ฒ ๋‹ค๋Š” ์ƒ๊ฐ์„ ํ–ˆ๊ณ , ์‹ค์ œ๋กœ ๋งŒ์กฑ์Šค๋Ÿฌ์šด ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

์ด์ „ ๋ฐฉ์‹: FastAPI

OCR ๋ฌธ์ œ๋Š” ํ•ด๊ฒฐ๋˜์—ˆ๊ณ , ์ด์ œ ์Šคํฌ๋ฆฐ์ƒท์„ ์ฒ˜๋ฆฌํ•  ๋ฐฉ๋ฒ•๋งŒ ์ƒ๊ฐํ•˜๋ฉด ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
์Šคํฌ๋ฆฐ์ƒท์€ ๋ชจ๋ฐ”์ผ์—์„œ ์ฐ์„ ํ…๋ฐ, ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด ์ปดํ“จํ„ฐ์— ์˜ฎ๊ธฐ๋Š” ๊ฒƒ๋„ ํ•˜๋‚˜์˜ ์ผ์ด๋ผ๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค.
๊ทธ๋ž˜์„œ ๊ฐ€์žฅ ๋จผ์ € ์ƒ๊ฐ๋‚œ ๊ฒƒ์ด REST API์˜€์Šต๋‹ˆ๋‹ค.

๋ฐ”๋กœ FastAPI๋กœ ํŒŒ์ผ์„ ๋ฐ›์•„์„œ, OCR๋กœ ์ฒ˜๋ฆฌํ•˜๊ณ  ์ž๋™์œผ๋กœ ์—‘์…€๊นŒ์ง€ ์ž‘์„ฑํ•ด ๋‹ค์šด๋กœ๋“œํ•  ์ˆ˜ ์žˆ๋„๋ก ์ž‘์—…ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ž‘์—…์— ์‹œ๊ฐ„์ด ๊ฑธ๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์—, ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋„๋ก ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์‹ค์‚ฌ์šฉ๊นŒ์ง€ ์„ฑ๊ณต์€ ํ–ˆ๋Š”๋ฐ, ๋ช‡ ๊ฐ€์ง€ ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

  • ์‚ฌ์šฉ๋Ÿ‰์ด ์ ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด VM์„ ์˜ฌ๋ฆฌ๋Š” ๊ฒƒ์€ ๋‹ค์†Œ ๋‚ญ๋น„์˜€์Šต๋‹ˆ๋‹ค.
  • Selenium์€ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ๋Œ๋ ค์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฌด๋ฃŒ VM์œผ๋กœ๋Š” ๋ฉ”๋ชจ๋ฆฌ ์••๋ฐ•์ด ์‹ฌํ–ˆ์Šต๋‹ˆ๋‹ค.
  • VM์—์„œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ๋กœ ๋Œ๋ฆฌ๋‹ค ๋ณด๋‹ˆ ์—๋Ÿฌ ๋กœ๊น…์ด ์‰ฝ์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ํŒŒ์ผ๋กœ ๋กœ๊ทธ๋ฅผ ๋นผ๋Š” ๋“ฑ ๋ฐฉ๋ฒ•์ด ์—†๋Š” ๊ฒƒ์€ ์•„๋‹ˆ์—ˆ์ง€๋งŒ ๋ฐฐ๋ณด๋‹ค ๋ฐฐ๊ผฝ์ด ๋” ํฌ๊ธฐ๋„ ํ•˜๊ณ โ€ฆ

๊ทธ๋Ÿฌ๋‹ค๊ฐ€ ๊ธฐ์กด์— VM์„ ๋„์›Œ ๋†“์•˜๋˜ Oracle Cloud ๊ณ„์ •์ด ๊ฐœ์ธ์ ์ธ ๋ฌธ์ œ๋กœ ํ„ฐ์ง€๋ฉด์„œ(โ€ฆ) ์ด ๋ฐฉ๋ฒ•์€ ํ๊ธฐํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

ํ˜„์žฌ ๋ฐฉ์‹: Google Drive API

๊ณฐ๊ณฐ์ด ์ƒ๊ฐํ•ด ๋ณด๋‹ˆ, ์—‘์…€ ํŒŒ์ผ์€ ์–ด์ฐจํ”ผ ๋ฉ”์ผ๋กœ ๋ณด๋‚ด์•ผ ํ•˜๊ณ  ์ด๋ž˜์ €๋ž˜ ๊ฐœ์ธ ์ปดํ“จํ„ฐ์—์„œ ์ฝ”๋“œ๋ฅผ ๋Œ๋ ค๋„ ์ƒ๊ด€์—†๊ฒ ๋‹ค ์‹ถ์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋ชจ๋ฐ”์ผ๋กœ ํŒŒ์ผ์„ ์˜ฌ๋ฆฌ๊ณ  ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์œผ๋กœ ํด๋ผ์šฐ๋“œ ์Šคํ† ๋ฆฌ์ง€๊ฐ€ ๋จธ๋ฆฌ๋ฅผ ์Šค์ณ๊ฐ”์Šต๋‹ˆ๋‹ค. ๊ฐ€์žฅ ์นœ์ˆ™ํ•œ Google Drive๋กœ ์‹คํ–‰์— ์˜ฎ๊ฒจ ๋ณด๊ธฐ๋กœ ํ–ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์‹์œผ๋กœ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

import os
import shutil
import config
from func.gdrive import gdrive_down, gdrive_up_excel
from func.img_process import crop_and_save
from func.ocr import make_excel_ocr

IMG_FOLDER = "download"

# ํด๋” ์„ธํŒ…
if os.path.exists(IMG_FOLDER):
    shutil.rmtree(IMG_FOLDER)
os.makedirs(IMG_FOLDER, exist_ok=True)

# ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ
gdrive_down(folderName=config.GDRIVE_FOLDER_NAME)

# ํŒŒ์ผ ์ „์ฒ˜๋ฆฌ
for file in os.listdir(IMG_FOLDER):
    crop_and_save(fileName=file, folder=IMG_FOLDER)

# ํŒŒ์ผ CLOVA๋กœ OCR ์ฒ˜๋ฆฌํ•ด์„œ ์—‘์…€ ์ž‘์„ฑ
fileName = make_excel_ocr(
    folderName=IMG_FOLDER,
    cardName=config.CARD_NAME,
    name=config.NAME,
    year=config.YEAR,
    month=config.MONTH
)

# ์—‘์…€ ํŒŒ์ผ ์—…๋กœ๋“œ
gdrive_up_excel(fileName=fileName)

ํŒŒ์ผ ์ „์ฒ˜๋ฆฌ๋Š” ๊ฐ„๋‹จํžˆ Pillow ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ•ด ์ด๋ฏธ์ง€๋ฅผ ์ž๋ฅด๋Š” ๊ณผ์ •์ด๊ณ , OCR์„ ํ†ตํ•œ ์ฒ˜๋ฆฌ๋Š” ๊ธฐ์กด๋Œ€๋กœ Selenium์„ ํ™œ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

Google Drive API๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์ดˆ๊ธฐ ์„ค์ •์ด ํ•„์š”ํ•œ๋ฐ, ์ด ๋ถ€๋ถ„์€ ๊ณต์‹ ๋ฌธ์„œ์— ์ž˜ ์„ค๋ช…๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์˜คํžˆ๋ ค ์ดํ›„์— Python ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ณผ์ •์ด ๋ฒˆ๊ฑฐ๋กœ์› ์Šต๋‹ˆ๋‹ค. ๊ตฌ๊ธ€์—์„œ ์ œ๊ณตํ•˜๋Š” ์˜ˆ์ œ๊ฐ€ 2% ๋ถ€์กฑํ•˜๊ณ , ๊ณผ๊ฑฐ API ๋ฒ„์ „ ๊ธฐ์ค€์˜ ์ •๋ณด๋„ ๋‹ค์ˆ˜ ํ˜ผ์žฌํ•ด ์žˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ž˜๋„ ๊ฐ„๋‹จํ•œ ๋‹ค์šด๋กœ๋“œโ—ฆ์—…๋กœ๋“œ๋งŒ ๊ตฌํ˜„ํ•˜๋ฉด ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ช‡ ๊ฐ€์ง€ ์ฐธ๊ณ  ์ž๋ฃŒ๋ฅผ ์ฐพ์•„๊ฐ€๋ฉฐ ์ง„ํ–‰ํ–ˆ์Šต๋‹ˆ๋‹ค.

ํ•ต์‹ฌ ๋กœ์ง์€ ๋ณ€๊ฒฝ์ด ์—†์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ถ”๊ฐ€์ ์ธ ๊ณต์ˆ˜๊ฐ€ ๋งŽ์ด ๋“ค์–ด๊ฐ€์ง€ ์•Š์•˜๊ณ , ํ˜„์žฌ ๋ฌธ์ œ์—†์ด ์‚ฌ์šฉ ์ค‘์ž…๋‹ˆ๋‹ค.
์ด ์ •๋„๋ฉด ์†Œ๊ธฐ์˜ ๋ชฉ์ ์„ ๋‹ฌ์„ฑํ•ด์„œ ๋งŒ์กฑ์Šค๋Ÿฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™๋„ค์š”.