ENFRKOantony langlois
프로젝트

Vinbot

바로가기자동화PythonDiscordWeb Scraping

데모 영상이 곧 공개됩니다...

Vinted 특가 탐지 봇 · 무료 프록시를 바꿔가며 문서화되지 않은 비공개 API를 5초마다 확인 · 새 매물이 올라오면 몇 초 안에 Discord로 알림

아키텍처

free tier
외부 · 프록시 소스
ProxyScrape
api.proxyscrape.com
무료 프랑스 HTTP 프록시 목록 · JSON · 키 없음, 비용 없음, SLA 없음
api/v2
외부 · 대상 플랫폼
Vinted 비공개 API
vinted.fr/api/v2
웹 앱 뒤에 있는 문서화되지 않은 JSON API · /catalog/items · /items/{id}
url_data
구성 · 저장된 검색
Watchlist
script.py · url_data
저장된 검색 15개: 카탈로그, 브랜드, 가격 상한, 대상 채널
thread
백그라운드 · 프록시 풀
Proxy refresher
script.py · update_proxies_thread
데몬 스레드가 공용 프록시 목록을 120초마다 갱신
Python
HTTP 클라이언트 · 수정한 포크
Patched vinted_scraper
vintedWrapperModified.py
프록시 지원 추가 · 세션 쿠키 자동 조회 · 401에서 재시도 · 실패 시 None 반환
Python
코어 · 폴링 루프
Deal poller
script.py · main()
5초마다 무작위 프록시로 검색 15개 실행 · 기존 매물과 비교 · 새 매물의 상세 정보를 가져와 전송
5초 주기사이클당 무작위 프록시ID + 채널 중복 제거첫 패스 기준선상세 정보 조회실패 시 건너뛰기
VPS
전달 · 별도 서비스
Discord bot API
separate project · hosted on a VPS
단순한 POST /article 데이터 수신 · 임베드, 채널, 대체 이미지 처리
SaaS
전달 · 목적지
Discord 채널
11 price-bucketed channels
가격대에 맞는 특가가 등록되는 즉시 멤버에게 알림

5초마다 Deal pollerProxy refresherProxyScrape에서 계속 갱신하는 목록에서 프록시 하나를 무작위로 고릅니다. 이어 수정한 vinted_scraper로 15개의 Watchlist 검색을 Vinted의 /api/v2/catalog/items에 요청합니다. 각 결과는 항목 ID와 채널을 키로 삼아 이미 확인한 매물과 비교합니다. 새 매물만 판매자 평점과 사진을 가져오기 위해 /items/{id}를 한 번 더 호출합니다. 이후 VPS에서 실행 중인 Discord bot APIPOST /article로 보내 임베드를 만들고 가격대별 채널에 알립니다.

배포: Poller → 장시간 실행되는 Python 프로세스 · 로컬 머신 · Discord bot API → VPS · 항상 켜져 있는 Discord 연결

성과

  • 5초 · 15개의 저장된 검색에 걸친 폴링 주기
  • 120초 · 프록시 풀 자동 갱신, 수동 재시작 없음
  • 0 € · 프록시 비용: 회전하는 무료 프록시, 유료 풀 없음
  • 15 → 11 · 가격대별 채널로 분류한 검색

보여준 역량: 비공개 API 리버스 엔지니어링 · 의존성 수정(프록시, 인증 재시도) · 유료 인프라 없는 프록시 순환 · 오류 격리와 점진적 성능 저하 · 식별자 기반 중복 제거 · 생산자와 소비자 서비스 분리

문제와 해결책

핵심 제약은 Vinted에 공개 API가 없고 잦은 요청을 적극적으로 제한하지만 저렴한 매물은 몇 분 안에 팔린다는 점입니다.

▤ HTML 대신 비공개 API 사용

문제: 저평가된 항목은 몇 분 안에 팔리므로 알림은 몇 초 안에 도착해야 합니다. 렌더링된 HTML을 스크래핑하는 것은 느리고, 마크업이 바뀔 때마다 취약하며, 사이트가 지문을 식별하기 쉽습니다.

Vinted 웹 앱이 호출하는 JSON API를 리버스 엔지니어링했습니다. 프론트엔드와 같은 방식으로 실제 세션 쿠키와 브라우저 User-Agent를 /api/v2/catalog/items 요청에 포함합니다.

  • 브라우저 자동화(Selenium)는 훨씬 무겁고 느리며 사이트 디자인이 바뀔 때마다 깨질 수 있어 제외했습니다
  • 문서화되지 않은 API가 예고 없이 바뀔 수 있다는 위험은 감수했습니다
  • 전체 페이지 대신 작은 JSON만 받아 15개 검색을 5초마다 확인할 수 있습니다

▒ 백그라운드에서 갱신하는 무료 프록시 목록

문제: 하나의 가정용 IP에서 5초마다 폴링하면 빠르게 속도 제한되거나 차단되고, 무료 프록시는 끊임없이 죽습니다: 오래된 목록은 실행 도중 봇을 멈추게 합니다.

매 주기마다 공용 목록에서 프록시 하나를 무작위로 선택합니다. 데몬 스레드는 120초마다 ProxyScrape에서 목록을 다시 받아오고 Vinted 지역에 맞는 프랑스 프록시만 남깁니다.

  • 취미 프로젝트에서 지속적인 비용을 감당하기 어려워 유료 가정용 프록시는 제외했습니다
  • 무료 프록시는 느리고 불안정하므로 개별 요청이 실패해도 전체 프로세스가 멈추지 않게 했습니다
  • 인프라 비용 없이 죽은 프록시를 자동으로 교체합니다

▓ 클라이언트를 다시 쓰지 말고 라이브러리를 패치하기

문제: 기성 vinted_scraper는 프록시 지원이 없었고 200이 아닌 응답이면 RuntimeError를 던져, 불안정한 프록시 하나에 전체 루프가 죽었습니다.

라이브러리 파일 두 개를 수정했습니다. proxies 값을 실제 요청까지 전달하고, 401 응답이 오면 세션 쿠키를 다시 받아 재시도합니다. 그래도 실패하면 None을 반환해 전체 프로세스를 종료하지 않고 해당 주기만 건너뜁니다.

  • requests 클라이언트를 처음부터 만들면 라이브러리가 이미 처리하는 쿠키와 User-Agent 로직까지 다시 구현해야 하므로 제외했습니다
  • 모든 호출부에 try/except를 넣으면 오류 처리 정책이 코드 전체에 흩어지므로 제외했습니다
  • 프로세스를 재시작하지 않아도 프록시 장애와 쿠키 만료를 처리할 수 있습니다

▞ 항목 ID와 채널로 메모리에서 중복 제거

문제: 최신순 결과 페이지는 확인할 때마다 일부 매물이 겹칩니다. 같은 매물을 반복해서 알리면 채널이 도배되고 사용자도 알림을 무시하게 됩니다.

첫 실행에서는 현재 매물을 기준값으로 저장하고 알리지 않습니다. 이후 결과는 대상 채널과 묶어 항목 ID + 채널 조합으로 비교하고, 처음 보는 조합만 알립니다.

  • 매물 하나가 50 €와 150 € 가격대 채널에 모두 포함될 수 있어 채널을 키에 포함했습니다
  • 장시간 실행되는 프로세스 하나에는 단순한 메모리 목록이면 충분해 데이터베이스는 쓰지 않았습니다
  • 재시작하면 현재 매물을 다시 기준값으로 잡으며 실행 사이에는 상태가 유지되지 않습니다

░ 2단계 요청: 가벼운 검색 후 상세 정보 조회

문제: 검색 결과에는 유용한 임베드에 필요한 판매자 평판과 사진이 없지만, 모든 결과의 전체 세부 정보를 가져오면 API 호출과 차단 위험이 배가됩니다.

페이지당 매물 5개를 반환하는 가벼운 검색 요청을 보내고, 중복 제거 후 새 매물로 확인된 항목만 /items/{id}로 상세 정보를 가져옵니다.

  • 모든 매물의 상세 정보를 가져오지 않아 평소에는 결과 수와 관계없이 주기당 요청 15개만 보냅니다
  • 새 매물이 한꺼번에 많이 올라오는 일은 드물어 추가 요청은 예외적으로만 발생합니다
  • 기본 요청 수를 늘리지 않고 별점, 리뷰 수, 국가가 포함된 알림을 만들 수 있습니다

▚ 폴러를 Discord 전달에서 분리하기

문제: 하나의 프로세스에서 데이터 수집과 Discord 메시지 생성을 함께 처리하면 불안정하고 차단되기 쉬운 수집기가 봇의 가동 시간과 채널 로직에 영향을 줍니다.

폴러는 데이터 수집만 담당합니다. POST /article로 단순한 JSON을 VPS의 봇 서비스에 보내면 봇이 임베드 생성, 채널 분류, 사진이 없는 매물의 대체 이미지 처리를 맡습니다.

  • VPS는 Discord 연결을 항상 유지하므로 스크래퍼가 중단되거나 재시작돼도 봇은 계속 동작합니다
  • 프로세스마다 한 가지 책임만 맡도록 폴러에 discord.py를 포함하지 않았습니다
  • 배포 대상이 두 개로 늘고 둘 사이에 네트워크 요청이 추가되는 비용은 감수했습니다