개발 삽질 일지

[직링] 캡챠 이미지 인식 본문

자유로운 개발일지/실험일지

[직링] 캡챠 이미지 인식

그낙이 2025. 6. 29. 20:41
반응형

이전 글들에서 퍼피티어와 일렉트론을 이용한 프로그램을 개발하면서, 타겟 서버의 서버 시간을 받아와서 그 시간에 맞춰 API를 호출 하는 기능까지 개발 했습니다. 이번에는 캡챠 이미지를 인식해서 자동으로 입력되어 티켓 예매 시간을 단축해보는 기능을 개발해보려고 합니다. 완성된다면 프로그램 실행으로 로그인만 진행하면 자동으로 해당 콘서트 이동, 시간이 되면 넷퍼넬 대기열 진입 및 완성 후 보안 문자까지 입력이 되겠네요. 

 

캡챠(CAPTCHA)란?

캡챠는 Completely Automated Public Turing test to tell Computers and Humans Apart의 약자로, 사람과 컴퓨터를 구별하기 위한 자동화된 테스트입니다. 대부분의 예매 사이트는 문자 인식형 캡챠이기 때문에 OCR을 이용하는 방법이 가장 쉬워보입니다. 대표적인 OCR로는 네이버 OCR, 구글 OCR이 있습니다. 대부분의 문자는 영어로 나오기 때문에 구글 OCR로 API 요청을 보내서 진행해보도록 하겠습니다. 

 

우선 해당하는 문자열의 base64값을 알아와야 합니다. 

<img id="sampleImg" src="data:image/png;base64,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" style="width:230px;height:70px;">

 

이런 식으로 특정 이미지 src에 (,) 뒤에 나오는 값이 base 64값입니다. base64를 바로 OCR로 보내기보다, 이미지 전처리를 거친 이후에 구글OCR로 보내기 위해서 이미지를 우선 저장해봅시다. 

const fs = require("fs");
const path = require("path");

async function start() {
  const base64 =
    "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"; 
  
  const outputPath = path.resolve(__dirname, "captcha.png");
  fs.writeFileSync(outputPath, Buffer.from(base64, "base64"));

}

start();

captcha.png로 저장된 이미지입니다. 지금은 배경화면이 하얀색이여서 상관없지만, png 형태보다는 바탕화면을 하얀색으로 입힌 jpg를 더 잘 인식할 것 같으니까 png에서 jpg로 변환해봅시다. sharp를 이용해서 바탕화면을 입혀줍시다. sharp는 Node.js에서 이미지 처리를 도와주는 이미지 변환 라이브러리입니다. npm install sharp로 설치 후 아래 코드를 입력하면 변환이 됩니다.

const sharp = require("sharp");

await sharp(Buffer.from(base64, "base64"))
  .flatten({ background: "#ffffff" })
  .jpeg({ quality: 100 })
  .toFile(outputPath)
  .then(() => {
    console.log("배경 흰색 적용 + JPG 저장 완료");
  })
  .catch((err) => {
    console.error(err);
    return;
  });

 

그 다음은 OCR 처리입니다. 구글 OCR을 실행하기 위해서는 다소 복잡한 단계를 거쳐야 합니다. 천천히 가보시죠.

 

0. 환경설정 

구글 콘솔에 접속 후 로그인, 프로젝트 생성을 진행합니다. API 및 서비스, IAM 및 관리자, 서비스 계정을 설정해야합니다. 

 

Google 클라우드 플랫폼

로그인 Google 클라우드 플랫폼으로 이동

accounts.google.com

1. API 및 서비스

Google Vision API 활성화

2. 서비스 계정 

 

서비스 계정 생성 후, 키(JSON) 생성 및 다운로드

 

이제 구글 OCR 성능을 테스트 해봅시다. JSON도 파일 경로에 추가해주셔야됩니다. 

const visionClient = new vision.ImageAnnotatorClient({
  keyFilename: path.resolve(__dirname, "ocr_api.json"),
});

// OCR 처리
const [result] = await visionClient.textDetection(outputPath);
const detections = result.textAnnotations;

if (!detections || detections.length === 0) {
  console.error("OCR 실패: 결과 없음");
  return;
}

const text = detections[0].description.trim().replace(/\s/g, "");
console.log("OCR 결과:", text);

 

처음에 받은 입력값을 잘 읽어서 온 것을 확인할 수 있습니다. 그렇다면 이제 예매 완성하기 위한 엑조디아 중 버튼 클릭, 서버 시간 그리고 캡챠 이미지 자동 인식을 모았습니다. 그렇다면 다음편에서는 이것들을 합치고 순수 API 요청으로 예매페이지에 접속하는 걸 구현해보도록 하겠습니다.

 

언제나처럼 — 시작은 삽질이지만, 끝은 지식입니다.

반응형