봇이 '70% 남는 딜'을 찾았다며 자랑했는데, 알고 보니 목걸이를 신발로 착각한 거였습니다 — 딜 찾기 봇에게 '의심'을 가르친 이야기

소개

저는 일본과 한국을 오가며 옷·스니커즈를 양방향으로 판매합니다. 상품마다 어느 방향(한국↔일본)으로 넘겨야 남는지가 달라서, 늘 양쪽 시세를 함께 봐야 해요. 이번에 자동화한 건 그중 일본에서 사서 KREAM(한국)으로 넘기는 방향입니다. 이 장사는 결국 "어느 쪽으로, 얼마에 넘기느냐"가 전부라, 매일 밤 일본 공급처 사이트를 하나씩 열어 KREAM 시세와 비교하고 있었어요. "이거 지금 넘기면 수수료 떼고 얼마 남지?"를 손으로 계산하는 거죠.

이 짓을 매일 반복하다가, Claude Code로 이 과정을 자동화하는 딜 찾기 시스템을 붙여보기로 했습니다. 목표는 단순했어요. 여러 공급처를 자동으로 훑어서 "지금 사면 KREAM에서 남는 딜"만 추려주는 것. 그런데 만들고 보니, 딜을 '찾는' 것보다 그럴듯하게 틀린 딜을 '거르는' 게 훨씬 어려웠습니다. 오늘은 그 삽질기를 공유합니다.

스택: Claude Code + Python(FastAPI) 백엔드 + React 프론트 + SQLite. 크롤링·매칭·마진계산 로직을 대화로 하나씩 붙여나갔습니다.

진행 방법

⚠️ 아래 프롬프트는 실제 세션 로그 원문이 아니라, 제가 봇에게 어떤 식으로 지시했는지 흐름을 재구성한 대표 예시입니다. 실제로는 훨씬 잘게 쪼개서 주고받았어요.

1) 공급처 크롤러 — "매장 추가를 공짜로"

먼저 편집샵을 긁어오는 크롤러부터 시켰습니다.

일본 편집샵 상품을 긁어오는 크롤러를 만들어줘.
- 몽벨/진즈팩토리는 HTML 직접 파싱
- 스니커즈 편집샵(kickslab, wormtokyo, mita)은 Shopify 기반이니
  /products.json 엔드포인트로 상품을 통째로 받아와
- 공급처를 추가할 때 base_url만 넘기면 되도록 전략(Strategy) 패턴으로 구성

여기서 Shopify /products.json 트릭 덕에 매장 추가가 거의 공짜가 됐습니다. 반면 Nike·adidas 공식몰은 일부러 뺐어요. nike.com/jp는 JS로 그리는 SPA라 정적 HTML에 상품이 없고, adidas는 403으로 봇을 막고, 무엇보다 공식 정가라 KREAM 마진이 안 나옵니다. 편집샵에서 두 브랜드가 다 커버되기도 했고요.

2) 매칭 & 마진 — KREAM 시세에 붙이기

긁어온 상품을 KREAM 시세와 맞대고 마진을 계산하게 했습니다. 마진 공식의 핵심은 이렇게 잡았어요(수수료를 숨은 상수로 두지 않고 명시 필드로).

# 판매가(KREAM 실거래가) 기준 마진율
estimated_margin_krw  = kream_price_krw - purchase_cost_krw \
                        - shipping_krw - customs_krw - commission_krw
estimated_margin_rate = round(estimated_margin_krw / kream_price_krw * 100, 2)
실제 Deal Recommendations 화면 — 공급처가·KREAM 시세·마진이 카드로 표시된다

▲ 실제 딜 추천 화면. 활성 딜 13건, 평균 마진 18.7%. 각 카드에 공급처가(¥)·KREAM가(₩)·마진이 한눈에.

결과와 배운 점

만드는 내내 벽을 세 번 넘었습니다. 그리고 셋 다 "코드가 안 돌아서"가 아니라 "봇이 자신 있게 틀려서" 생긴 벽이었어요.

벽 ① — "0건이 원래 정상인 줄 알았다"

전체를 돌렸더니 추천이 0건. 저는 순간 "일본 정가로 사서 KREAM에 팔면 수수료 떼고 원래 안 남는 거구나" 하고 납득했습니다. 그럴듯하죠. 근데 찜찜해서 로그를 팠더니 — DB에 컬럼 하나(kream_product_name)가 없어서, 조건을 통과한 딜조차 저장에 전부 실패하고 있었어요. 컬럼을 추가하니 바로 8건이 떴습니다.

배운 점: 수상한 결과를 '그럴듯한 경제 논리'로 덮으면, 버그가 정상인 척 숨는다. 0건을 납득해버린 나 자신이 제일 위험했다.

벽 ② — 목걸이를 신발로 착각한 봇

8건 중 몇 개는 마진이 70%를 넘었습니다. "대박!" 하고 열어봤더니… 피어스·뱅글·네크리스 액세서리 3개가 전부 똑같은 버켄스탁 신발(₩239,000)에 매칭돼 있었어요. 목걸이를 신발이라 우기면서 신뢰도 0.85로 아주 당당하게요.

원인은 매칭이 헐거웠던 것. 모델 번호 앞부분 몇 글자만 겹치면 같은 상품 취급했거든요. 그래서 봇에게 '의심'을 가르쳤습니다.

매칭 false positive가 너무 많아. 두 개를 넣어줘:
1) 모델번호 접두사가 너무 짧으면(5자 미만) 매칭 거부
2) 공급처 브랜드와 KREAM 브랜드가 실제로 같은지 검증하는 게이트
그리고 크로스브랜드 거부 / 동일브랜드 유지 단위테스트도 같이.
_MIN_BASE_MODEL_LEN = 5   # 짧은 접두사 충돌 차단

def _kream_brand_matches(supplier_brand, kream_result) -> bool:
    # KREAM 브랜드 필드 또는 상품명에 공급처 브랜드가 포함되는지
    ...

재스캔하니 목걸이-신발 같은 쓰레기는 싹 사라지고, 브랜드가 맞는 딜만 남았습니다. 저는 "가끔 놓치더라도 절대 거짓말은 안 하는" 쪽(정밀도 우선)을 택했어요.

여기서 한 발 더 나갔습니다. 매칭이 왜 맞는지를 눈으로 검증할 수 있게, 실물 품번과 KREAM 품번목록을 나란히 놓고 대조하는 화면을 만들었어요. 제1원칙은 "AI는 후보를 찾아오는 데만 쓰고, 확정 근거로는 안 쓴다" — 최종 매치는 실물 품번이 KREAM 리스팅 품번목록에 포함되는지의 기계 판정으로만 확정합니다.

품번 확정 매칭 화면 — 실물 품번과 KREAM 품번목록을 나란히 대조, 오매칭 0건

▲ 스캔 9건 중 품번 확정 4건·수동 검수 5건·오매칭 0건. 초록으로 하이라이트된 품번(예: FB7818-100)이 양쪽에 똑같이 들어있을 때만 확정. 아래 빨간 띠("단순차액 −18,340원 · 역마진")처럼, 매칭이 맞아도 마진이 안 나오면 자동 딜에서 제외됩니다.

배운 점: 딜 찾기의 진짜 적은 '0건'이 아니라 '자신 있게 틀린 것'이다. 없는 건 없다고 알면 그만이지만, 가짜를 진짜라 우기면 진짜 돈을 잃는다.

벽 ③ — '정확한 매칭'이 '팔리는 딜'은 아니었다

여기서 "이제 됐다" 했는데, 그게 방심이었습니다. 남은 목록을 보니 몽벨 아웃도어 자켓이 마진 28%로 딜에 올라와 있었어요. 매칭은 정확합니다. 그런데 몽벨 자켓은 KREAM에서 거의 거래가 안 됩니다. 사겠다는 사람이 없으면 28%는 그냥 종이 위 숫자죠.

Asics Gel-Kayano 딜과 몽벨 자켓 딜이 거의 같은 마진으로 나란히 표시된 화면

▲ 왼쪽 Asics Gel-Kayano 14(마진 29.2%, KREAM에서 잘 거래됨)와 오른쪽 몽벨 클라이마플러스 자켓(마진 28.5%, KREAM 거래 거의 없음). 봇 눈엔 둘 다 신뢰도 98%의 똑같이 좋은 딜로 보인다 — 이게 벽③이다.

코드를 다시 까보고 이유를 알았습니다. KREAM 응답엔 '총 거래량(total_sales)'이 같이 오는데, 저는 그 값을 스냅샷에 저장만 하고 딜 판정엔 한 번도 안 쓰고 있었어요. 판정 기준이 오직 '마진'과 '매칭 신뢰도'뿐이라, 반년에 한 번 팔린 물건이든 매일 팔리는 물건이든 봇 눈엔 똑같아 보였던 겁니다.

배운 점: 봇에게 "같은 물건이냐"(정확도)는 가르쳤지만, "실제로, 곧 팔리냐"(유동성)는 아직 못 가르쳤다. 데이터를 손에 쥐고도.

앞으로의 계획 & 도움이 필요한 부분

다음 할 일은 정해졌습니다. 이미 받아둔 거래량(total_sales)과 '마지막 거래가 얼마나 최근이냐'를 딜 판정에 실제로 반영하는 것. 거래가 뜸한 물건은 마진이 높아도 뒤로 밀고, 자주 팔리는 물건에 가중치를 주려고요. 마진이 진짜가 되려면 그 가격에 팔 수 있다는 증거가 먼저 붙어야 하니까요.

혹시 국경 간 중고/병행 시세에서 '유동성(회전율)'을 점수화하는 좋은 방법을 써보신 분 있으면 팁 구합니다. 지금은 단순 거래량 + 최근성 가중치를 생각 중인데, 더 나은 접근이 있을 것 같아요.

돌아보면 이 봇은 벽을 하나씩 넘어온 과정이었어요. 저장이 안 되던 벽, 아무거나 갖다 붙이던 벽, 그리고 "정확하지만 안 팔리는" 벽. 딜을 '찾는' 코드는 하루면 짜지만, 믿을 수 있게 만드는 데는 벽이 계속 나옵니다. 좋은 딜을 많이 찾아주는 봇보다 틀렸을 때 틀렸다고 말하는 봇이 훨씬 든든하다는 것, 그거 하나는 확실히 배웠습니다.

1
1개의 답글

뉴스레터 무료 구독