Skip to content

Interested in AI, automation, blockchain, web and apps

Seoul, KR--:-- GMT
Let’s Talk

Work/Automation/EN

Automated Product Listing Tool for Open Markets

자사몰 상품을 오픈마켓에 자동 등록하는 도구

자사몰 상품을 쿠팡 셀러 어드민에 그대로 올리는 도구

손으로 채우던 열두 줄의 옵션

자사몰과 오픈마켓 양쪽에 같은 상품을 올려야 하는 상황이었습니다. 자사몰에는 상품이 이미 올라가 있고, 같은 상품을 오픈마켓 셀러 어드민에 다시 등록하는 작업이었어요. 색상 3개에 사이즈 4개면 옵션만 12줄이고, 상품마다 대표·상세 이미지, 의류 고시정보 9개 항목, 반품지·출고지 코드, 배송비 정책을 채워야 합니다. 주문은 셀러 어드민에 로그인해야 확인할 수 있었습니다. 요구사항은 두 가지였습니다. 자사몰 상품 URL을 넣으면 오픈마켓에 등록되게 할 것, 새 주문이 들어오면 모바일로 알릴 것.

시스템 경계와 외부 의존
시스템 경계와 외부 의존

브라우저 클릭 대신 서명된 요청

어드민 화면을 Selenium으로 몰던 방식을 버리고 오픈 API + HMAC 서명으로 갈아탔습니다

첫 버전은 main.py 에서 절대 XPath로 어드민 폼을 클릭했습니다. 동작은 했지만 화면이 한 번 바뀌면 /html/body/div[1]/div[2]/... 가 통째로 깨졌고, 카테고리는 li 인덱스를 사람이 외워야 했어요. 페이로드 하나를 POST 하는 쪽이 훨씬 덜 깨집니다. 오픈 API에는 어드민에 있던 '전체 행 일괄 적용'이 없어서, 옵션마다 모든 필드를 채운 items 배열을 직접 만들어야 했습니다.

스크래핑은 React 앱이 아니라 Express + Puppeteer 서버(nodeapp)로 떼어냈습니다

브라우저에서는 헤드리스 크롬을 띄울 수 없고, 자사몰은 CORS도 열어주지 않습니다. 결국 로컬에 작은 서버가 하나 있어야 했어요. package.json 의 browser 필드에 fs, child_process 를 false 로 막아 둔 자국이 남아 있는데, 그게 번들에 Puppeteer를 넣어 보려던 시행착오의 흔적입니다.

쿠팡 페이로드는 base / itemBase 상수와 immer 의 produce 로 조립했습니다

필드 60개 중 대부분이 판매자 계정 단위로 고정된 값입니다. 템플릿을 얼려 두고 바뀌는 곳만 draft로 덮는 편이 안전했어요. returnCenterCode, outboundShippingPlaceCode 처럼 계정에 묶인 값은 상수로 박아 둘 수밖에 없었습니다.

주문 알림은 웹훅 대신 10초 폴링과 pickle 스냅샷 비교로 만들었습니다

오픈 API가 주문 웹훅을 주지 않았습니다. 그래서 최근 28일 ACCEPT 주문을 통째로 받아 직전 목록과 비교하고, 새로 생긴 것만 골라 보냅니다. 서버가 없어 로컬 노트북에서 상시 실행해야 했고, nextToken 페이지네이션을 재귀로 다 돌아야 목록이 완성됩니다.

배포와 인프라 구성
배포와 인프라 구성

노트북 한 대에 올린 네 조각

돌아가는 조각은 넷입니다. React 앱(coupang-helper)이 화면을 그리고, nodeapp 이 두 가지 일을 합니다. POST /scrape 는 Puppeteer로 자사몰 상품 페이지를 열어 상품명·가격·이미지·고시정보 표·검색 키워드를 긁어 JSON으로 돌려주고, POST / 는 앱이 만든 페이로드를 api-gateway.coupang.com 으로 그대로 넘깁니다.

앱은 스크래핑 결과가 state에 들어오는 순간 useEffect 안에서 색상 × 사이즈 조합을 만들어 items 를 채우고, 고른 카테고리 코드와 상품코드를 붙여 본문을 완성합니다. 등록이 끝나면 window.location.reload() 로 화면을 비워 다음 상품을 받아요. 여기까지가 등록 라인입니다. 나머지 둘은 등록 이후를 맡습니다. coupang-edit 은 상품 id 목록을 받아 상품 문서를 GET 으로 통째로 받고 필드 하나만 바꿔 PUT 하는 일괄 수정 스크립트고, coupang_bot 은 10초마다 주문 조회 API를 돌며 orders.txt 스냅샷과 다른 주문만 텔레그램으로 밀어 줍니다. 다섯 번째 조각인 coupang-product 는 같은 폼을 Material-UI로 다시 그려 본 프로토타입인데, 제출 핸들러가 없는 채로 멈춰 있습니다.

핵심 데이터 모델
핵심 데이터 모델

브라우저에서 서명하고, 프록시는 헤더만 그대로 흘려보낸다

오픈마켓 API를 붙일 때 먼저 떠오르는 그림은 이렇습니다. 시크릿은 서버에만 두고, 서버가 본문을 받아 서명을 만들어 대신 호출한다. 그런데 그 길은 화면이 보내는 스키마와 서버가 다시 만드는 서명 대상 경로가 항상 같다는 걸 양쪽에서 보장해야 합니다. 하루에도 몇 번씩 페이로드를 고치던 때였어요. 그렇다고 브라우저에서 바로 게이트웨이를 부르면 CORS에 막힙니다.

서명을 브라우저에서 만들었습니다. 핵심은 서명 대상이 '실제로 요청을 보내는 주소'가 아니라 '최종 목적지의 경로'라는 점입니다. getAuthHeader('POST', 'https://api-gateway.coupang.com/.../seller-products', body) 로 헤더를 만든 뒤, 요청 자체는 http://localhost:4000 으로 보냅니다. Express 쪽 app.post('/') 는 본문을 건드리지 않고 authorization 과 x-requested-by 만 그대로 실어 다시 보냅니다.

CEA HmacSHA256 서명은 timestamp + method + path + queryString 만 해시합니다. 호스트도 본문도 서명에 들어가지 않아요. 그래서 중간에서 호스트만 바꿔 보내도 서명은 유효합니다. 덕분에 프록시는 키를 몰라도 되고 서명 로직은 hmac.js 한 곳에만 있으면 됐습니다. 다만 본문이 서명에 안 들어간다는 건 본문 위변조를 서명으로 못 잡는다는 뜻이기도 합니다. 노트북 안에서만 도는 도구였기에 받아들인 선택이었어요.

이미지를 내려받지 않고 URL의 한 조각만 바꾼다

제가 먼저 만든 Selenium 버전이 그 순진한 방법 그 자체였습니다. 상세 이미지를 urllib.request.urlretrieve 로 노트북에 내려받고, 어드민 드롭존의 input 에 파일 경로를 send_keys 로 밀어 넣고, 업로드된 li 가 나타날 때까지 기다립니다. 이미지가 다섯 장이면 다섯 번 왕복하고, 스피너가 늦게 사라지면 그다음 클릭이 통째로 실패했어요.

API 버전에서는 이미지를 한 바이트도 만지지 않습니다. 갤러리에서 긁은 썸네일 URL을 / 로 쪼개 여덟 번째 조각만 600 으로 바꾼 뒤 그 문자열을 vendorPath 로 넣습니다. 첫 장은 REPRESENTATION, 나머지는 DETAIL 로 imageOrder 를 붙였고요.

자사몰 이미지 서버가 경로 안에 크기를 담는 규칙을 쓰고 있었습니다. 조각 하나만 바꾸면 같은 이미지의 600px 주소가 나오죠. 게다가 마켓플레이스는 vendorPath 로 받은 주소를 자기 쪽에서 내려받아 저장하니, 제 노트북은 문자열만 조립하면 됩니다. 업로드 왕복과 스피너 대기가 통째로 사라졌고, 실패 지점도 함께 사라졌어요.

유저가 할 수 있는 일

셀러 어드민에 로그인해 상품 등록 화면까지 연다
셀러 어드민에 로그인해 상품 등록 화면까지 연다

자사몰 상품 주소를 붙여넣어 상품 정보를 가져온다
자사몰 상품 주소를 붙여넣어 상품 정보를 가져온다

노출 카테고리를 골라 상품 코드를 정한다
노출 카테고리를 골라 상품 코드를 정한다

조립된 페이로드에 서명해 상품을 등록한다
조립된 페이로드에 서명해 상품을 등록한다

예전 버전으로 어드민 폼을 직접 채워 등록한다
예전 버전으로 어드민 폼을 직접 채워 등록한다

등록해 둔 상품들을 한 번에 훑어 고친다
등록해 둔 상품들을 한 번에 훑어 고친다

새 주문이 들어오면 텔레그램으로 받는다
새 주문이 들어오면 텔레그램으로 받는다

슬래시 now 를 보내 현재 결제완료 목록을 파일로 받는다
슬래시 now 를 보내 현재 결제완료 목록을 파일로 받는다

프로토타입 화면에서 카테고리와 주소 입력 폼을 확인한다
프로토타입 화면에서 카테고리와 주소 입력 폼을 확인한다

1 / 1

아직 소스에 남은 API 키

가장 큰 문제는 키입니다. src/helper/hmac.js 와 config.py 에 액세스 키와 시크릿, 텔레그램 토큰이 그대로 박혀 있고 Selenium 버전에는 어드민 비밀번호까지 코드에 있습니다. .gitignore 로 config.py 를 뺀 게 전부였어요. 다시 만든다면 환경변수부터 깔고 서명은 서버에서 만들게 하겠습니다.

두 번째는 실패를 삼키는 코드입니다. 프록시는 게이트웨이가 4xx를 줘도 { r: true } 를 돌려주고 화면은 그냥 새로고침됩니다. 등록이 됐는지 알 방법이 없어 결국 어드민을 열어 확인해야 했어요. 응답 코드와 sellerProductId 를 화면에 띄우기만 했어도 달랐을 겁니다. 모든 걸 아름답게 처리하진 못했지만, 최소한 결과를 사람에게 보여주는 일은 값이 컸을 거예요.

마지막으로 스크래핑은 DOM 셀렉터에 통째로 묶여 있습니다. 자사몰이 개편되면 그날로 멈추는 도구였고 실제로 그렇게 수명을 다했습니다. 셀렉터를 코드 밖 설정으로 빼고 어떤 값이 비었는지를 화면에 보여줬어야 합니다.

Read next

AI 기반 3D 파트 그룹핑, Material 생성 도구

X3X Studio — 2026