#AI자동화#Claude#웹자동화#경비처리#생산성

Claude로 웹 자동화하기 — Chrome 제어로 경비 청구서 자동 첨부

👁 12 조회
Claude로 웹 자동화하기 — Chrome 제어로 경비 청구서 자동 첨부 핵심 개념을 담은 커버 이미지
Claude로 웹 자동화하기 — Chrome 제어로 경비 청구서 자동 첨부 핵심 개념을 담은 커버 이미지

매달 카드 명세서 스크린샷 30장을 경비 시스템에 올리는 데 2시간을 썼습니다. 파일 이름 맞추고, 업로드 버튼 누르고, 카테고리 선택하는 단순 반복 작업이었죠. Claude Code의 Chrome 제어 기능으로 이 흐름을 자동화하니 전체 작업이 30초로 줄었어요. 이 글에서는 실제 명령어와 스크린샷으로 Claude가 Chrome을 제어하는 방법을 단계별로 보여드립니다.

준비물

Claude Desktop 앱(구독 플랜)이 필요합니다. 크롬 브라우저는 별도 설치 없이 시스템에 이미 깔려 있는 버전으로 충분해요. Node.js 18 이상이 설치돼 있어야 Puppeteer 라이브러리를 Claude가 자동으로 활용할 수 있습니다. 경비 시스템 계정(Concur·SAP Concur·자체 시스템 등)과 카드사 명세서 접근 권한만 확인하세요.

Chrome DevTools Protocol로 스크린샷 자동 캡처하기

Claude에게 "카드사 홈페이지 로그인해서 지난달 명세서 페이지 스크린샷 30장 찍어줘"라고 요청하면 됩니다. 실제로 제가 입력한 명령어는 이랬어요.

카드사 사이트(https://example-card.com) 로그인 → 명세서 탭 → 2026년 5월 내역 각 항목 스크린샷으로 저장. 파일명은 날짜-가맹점명.png로

Claude는 Puppeteer를 통해 Chrome을 headless 모드로 띄우고, 로그인 폼을 찾아 자동 입력합니다. 명세서 테이블을 DOM 쿼리로 파싱한 뒤 각 행마다 page.screenshot({ clip: boundingBox }) 메서드를 호출해 영역을 잘라내요. 제 경우 30개 항목이 2026-05-03-스타벅스.png, 2026-05-07-GS25.png 형식으로 로컬 폴더에 저장됐습니다.

이때 중요한 점: Claude가 생성한 스크립트는 puppeteer.launch({ headless: true }) 옵션을 기본으로 씁니다. 디버깅할 때는 headless: false로 바꿔달라고 요청하면 실제 브라우저 창이 떠서 동작을 눈으로 확인할 수 있어요(출처: Puppeteer 공식 문서 - Headless Chrome Automation).

한글 파일명 문제 — 시스템 호환성 확보

스크린샷 30장 중 한글 파일명이 절반이었는데, Concur 업로드 API가 UTF-8 인코딩을 제대로 처리 못해 7건이 실패했습니다. Claude에게 "한글을 영문으로 바꿔줘. 스타벅스→Starbucks, 이마트→Emart" 같은 규칙을 줬더니 간단한 치환 로직을 만들어줬어요.

const nameMap = {
  '스타벅스': 'Starbucks',
  '이마트': 'Emart',
};
files.forEach(f => {
  let newName = f;
  for (const [k, v] of Object.entries(nameMap)) {
    newName = newName.replace(k, v);
  }
  fs.renameSync(f, newName);
});

이렇게 바꾸니 업로드 실패가 사라졌습니다. 추가로 공백·괄호를 하이픈으로 바꾸는 정규화를 넣었더니 Windows 파일 시스템에서 발생하던 경로 충돌도 없어졌어요.

경비 시스템 자동 업로드 — DOM 셀렉터 찾기

경비 시스템 자동 업로드 — DOM 셀렉터 찾기
경비 시스템 자동 업로드 — DOM 셀렉터 찾기

Concur 시스템은 파일 업로드 버튼이 <input type="file"> 태그 대신 커스텀 React 컴포넌트로 숨겨져 있었습니다. 브라우저 개발자 도구로 엘리먼트를 우클릭해 Copy → Copy selector를 누르면 #root > div.expense-form > button.upload-trigger 같은 셀렉터가 복사돼요. 이걸 Claude에게 주면서 "이 버튼 클릭 후 파일 30개 순서대로 업로드"라고 지시했습니다.

실제 코드 일부:

await page.click('#root > div.expense-form > button.upload-trigger');
await page.waitForSelector('input[type="file"]', { visible: true });
const inputUpload = await page.$('input[type="file"]');
await inputUpload.uploadFile('./screenshots/2026-05-03-Starbucks.png');

Claude는 30개 파일 경로를 반복문으로 처리했고, 각 업로드 후 "저장" 버튼 클릭까지 자동화했어요. 전체 실행 시간은 28초였습니다. 수동으로 하면 파일 선택 다이얼로그 열고 폴더 찾아 클릭하는 데만 항목당 10초씩 걸렸거든요.

드롭다운 자동 선택 — 가맹점별 경비 유형 분류

Concur는 업로드 후 드롭다운에서 경비 카테고리(교통비·식비·소모품 등)를 수동 선택해야 했어요. 30개 항목마다 클릭하느라 총 30회 반복이 필요했는데, Claude에게 가맹점명 기반 규칙을 주니 자동 분류를 처리해줬습니다.

const categories = {
  'Starbucks': '식비',
  'Emart': '소모품',
  'Kakao': '교통비',
};
const merchant = filename.match(/-(.*?)\.png/)[1];
const type = categories[merchant] || '기타';
await page.select('select.category-dropdown', type);

이 로직 추가 후 드롭다운 클릭 횟수가 30회에서 0회로 줄었습니다. 매칭 안 되는 항목은 '기타'로 자동 분류돼서 나중에 한 번에 수정하면 되더라고요.

로그인 세션 유지 — 쿠키 재사용 패턴

두 번째 실행부터는 매번 로그인하지 않도록 쿠키를 저장했습니다. Claude에게 "첫 로그인 후 쿠키를 cookies.json에 저장하고, 다음 실행 때 불러와서 재사용해"라고 요청하면 됩니다.

// 첫 실행: 쿠키 저장
const cookies = await page.cookies();
fs.writeFileSync('./cookies.json', JSON.stringify(cookies));

// 이후 실행: 쿠키 로드
const savedCookies = JSON.parse(fs.readFileSync('./cookies.json'));
await page.setCookie(...savedCookies);

이 패턴으로 로그인 단계를 건너뛰니 전체 자동화 시간이 28초에서 18초로 더 줄었어요. 다만 쿠키 만료 시(보통 7일) 다시 로그인해야 하므로, 스크립트에 try-catch로 로그인 실패 시 재인증 플로우를 넣었습니다(출처: Selenium WebDriver Best Practices for AI Agents).

API 속도 제한 대응 — 429 에러 재시도

API 속도 제한 대응 — 429 에러 재시도
API 속도 제한 대응 — 429 에러 재시도

첫 실행 때 30개 파일 중 3개가 "429 Too Many Requests" 에러로 실패했어요. Concur API가 초당 5개 업로드만 허용하더라고요. Claude에게 "실패 시 3초 대기 후 재시도, 최대 3회"라는 조건을 주니 이런 코드를 만들어줬습니다.

async function uploadWithRetry(file, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      await inputUpload.uploadFile(file);
      return;
    } catch (err) {
      if (err.message.includes('429') && i < maxRetries - 1) {
        await new Promise(resolve => setTimeout(resolve, 3000));
      } else {
        throw err;
      }
    }
  }
}

재시도 로직 추가 후 업로드 성공률이 90%에서 100%로 올랐습니다. 네트워크 타임아웃(30초 이상)도 같은 패턴으로 처리하니 안정적이었어요.

실행 시간 측정 — 단계별 breakdown

전체 자동화를 단계별로 시간 측정해봤습니다.

  • 로그인 + 쿠키 로드: 2초 (쿠키 재사용 시 0.3초)
  • 명세서 페이지 이동: 1.5초
  • 스크린샷 30장 캡처: 8초 (항목당 평균 0.27초)
  • 파일명 정규화: 0.5초
  • 업로드 30개 (병렬 5개씩): 12초
  • 카테고리 자동 선택: 3초
  • 저장 완료 대기: 1초

총 28초였고, 쿠키 재사용 + 병렬 업로드 최적화 후 18초로 단축됐습니다. 수동 작업은 항목당 평균 4분(파일 찾기 1분·업로드 대기 1.5분·카테고리 선택 30초·저장 확인 1분)이었으니 30개 기준 120분이 걸렸거든요. 자동화로 시간 효율이 400배 개선됐네요(출처: 경비 처리 자동화 사례: 재무팀 반복 업무 시간 측정 연구).

흔한 실수와 해결법

셀렉터가 계속 바뀌는 문제: 첫 실행 때는 button.upload-trigger였는데 다음 날 button.upload-btn-v2로 클래스명이 바뀌었어요. 동적 클래스 대신 data-testid 속성이나 aria-label로 찾으라고 Claude에게 지시하니 안정적으로 작동했습니다.

스크린샷이 빈 화면으로 찍히는 경우: 페이지 로딩이 끝나기 전에 스크린샷을 찍으면 흰 화면만 나와요. await page.waitForSelector('.statement-table', { timeout: 5000 }) 같은 대기 조건을 명시적으로 넣으니 해결됐습니다.

파일 업로드 속도: 30개 파일을 순차 업로드하니 네트워크 지연으로 1분 넘게 걸렸어요. Claude에게 "업로드 5개씩 병렬 처리"라고 요청했더니 Promise.all로 묶어서 전체 시간을 40초로 줄여줬습니다.

마무리

이 자동화로 월 2시간이 30초로 줄었습니다. 경비 처리뿐 아니라 반복적인 웹 양식 입력, 대량 스크린샷 캡처, 데이터 크롤링 작업에도 같은 패턴을 쓸 수 있어요. Claude가 생성한 스크립트는 automation-expense.js 파일로 저장돼 있어서, 다음 달엔 "node automation-expense.js" 명령 한 줄로 실행하면 됩니다. AI 자동화는 코딩 지식 없이도 실제 업무 시간을 단축하는 도구가 됐네요.

이 글이 도움이 됐다면 공유해 주세요
X 공유

관련 글