JavaScript로 TCG 카드 가격 트래커 만들기
트레이딩 카드 가격에 관심을 갖게 된 개발자라면 누구나 결국 같은 질문을 하게 됩니다: 내 컬렉션(또는 포트폴리오, 관심 목록)의 가치를 프로그래밍 방식으로 어떻게 추적할까? 단순한 답변 — TCGPlayer나 eBay를 직접 스크래핑하기 — 은 어느 한 사이트가 레이아웃을 수정하는 순간, 프록시 서버, HTML 파싱, 속도 제한, 유지보수 부담이 따르는 수 주짜리 프로젝트가 되어버립니다.
이 포스트에서는 빠른 해답을 소개합니다: npm에 공개된 공식 @tcgpricelookup/sdk를 사용하여 TypeScript 50줄 미만으로 동작하는 TCG 가격 트래커를 만드는 방법입니다. 이 포스트를 마치면 다음을 갖추게 됩니다:
- 8가지 트레이딩 카드 게임에 걸쳐 카드 목록의 실시간 가격을 가져오는 스크립트
- 가격이 임계값을 초과하면 이메일/로그로 알림을 보내는 diff 기반 알림 시스템
- Node.js 앱, Discord 봇, 예약된 cron 작업 어디에나 넣을 수 있는 깔끔하고 재사용 가능한 패턴
SDK는 MIT 라이선스로 GitHub에 오픈소스로 공개되어 있으며, 자체 외에 런타임 의존성 없이 네이티브 fetch를 사용합니다. Node 18+, 브라우저, Cloudflare Workers, Bun, Deno에서 동작합니다.
CLI보다 완성도 높은 웹 앱을 원하신다면?
nextjs-tcg-starter를 Vercel에 원클릭 배포하면 60초 안에 TCGPlayer + eBay 가격이 포함된 카드 검색 UI를 갖출 수 있습니다. 이 포스트의 나머지 내용은 Node CLI 관점에서 동일한 SDK를 다룹니다.
무엇을 만드는가
최종 스크립트는 세 가지 작업을 수행합니다:
- 관심 목록 로드 — 카드 ID, 별명, 가격 임계값이 담긴 작은 JSON 파일
- 배치 조회를 사용하여 단일 API 호출로 현재 가격 가져오기
- 마지막으로 알려진 가격과 비교하고 카드가 설정된 임계값 이상 움직이면 알림 출력
이것은 거의 모든 TCG 가격 도구의 기반입니다 — 포트폴리오 트래커, 차익거래 스캐너, Discord 가격 봇, 투자자 대시보드 등. 지루한 부분들(HTTP, 인증, 오류 처리, 속도 제한, 배치 청킹)은 모두 SDK가 처리하므로 흥미로운 로직에 집중할 수 있습니다.
사전 요구사항
- Node.js 18 이상 (SDK가 네이티브
fetch를 사용합니다) - 무료 TCG Price Lookup API 키 — tcgpricelookup.com/tcg-api에서 가입하세요. 신용카드 불필요. Free 티어의 일 200 요청이면 개인 관심 목록으로 충분합니다.
- 기본적인 TypeScript 지식 (JavaScript 개발자라면 15분 정도의 튜토리얼 읽기)
- 터미널
1단계: 프로젝트 설정
새 프로젝트를 만들고 SDK를 설치합니다:
mkdir tcg-price-tracker
cd tcg-price-tracker
npm init -y
npm install @tcgpricelookup/sdk
npm install -D typescript tsx @types/node
최소한의 tsconfig.json을 만듭니다:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"noEmit": true
}
}
package.json에 스크립트를 추가합니다:
{
"scripts": {
"tracker": "tsx tracker.ts"
}
}
2단계: API 키 저장
키를 절대 하드코딩하지 마세요. 셸에서 내보내거나 .env 파일을 사용하세요:
export TCG_API_KEY="tcg_your_key_here"
SDK는 대부분의 예제에서 관례적으로 process.env.TCG_API_KEY에서 키를 읽지만, 클라이언트 생성자에 명시적으로 전달합니다.
3단계: 관심 목록 파일
추적하고 싶은 카드로 watchlist.json을 만듭니다. TCG Price Lookup 카탈로그에서 카드를 검색하면 카드 ID를 얻을 수 있습니다 — 모든 카드에는 URL과 API 응답에서 볼 수 있는 UUID가 있습니다.
[
{
"id": "019535a1-d5d0-7c12-a3e8-b7f4c6d8e9a2",
"nickname": "Charizard ex (Obsidian Flames)",
"alertThreshold": 0.10
},
{
"id": "019c9751-afc3-7af6-920f-392ad69d859c",
"nickname": "Charizard (Base Set) 1st Ed",
"alertThreshold": 0.05
}
]
alertThreshold는 소수로 표현됩니다 — 0.10은 가격이 어느 방향으로든 10% 이상 움직이면 알림을 보낸다는 의미입니다.
4단계: 트래커 스크립트
완성된 tracker.ts입니다 — 실제 코드 46줄(임포트와 주석 포함):
import { TcgLookupClient } from "@tcgpricelookup/sdk";
import { readFile, writeFile } from "node:fs/promises";
interface WatchItem {
id: string;
nickname: string;
alertThreshold: number;
}
interface Snapshot {
[cardId: string]: number | null;
}
const SNAPSHOT_FILE = "last-prices.json";
async function main() {
// 1. 클라이언트 설정. 429 / 5xx에서 자동 재시도.
const tcg = new TcgLookupClient({
apiKey: process.env.TCG_API_KEY!,
retry: { attempts: 3, baseDelayMs: 600 },
});
// 2. 관심 목록 + 마지막으로 알려진 가격 로드
const watchlist: WatchItem[] = JSON.parse(
await readFile("watchlist.json", "utf-8")
);
const lastPrices: Snapshot = await readFile(SNAPSHOT_FILE, "utf-8")
.then(JSON.parse)
.catch(() => ({})); // 첫 실행: 빈 스냅샷
// 3. 모든 카드에 대한 단일 배치 요청. SDK가 > 20이면 자동 청킹.
const ids = watchlist.map((w) => w.id);
const { data: cards } = await tcg.cards.search({ ids });
// 4. 차이 비교 및 알림
const newSnapshot: Snapshot = {};
for (const item of watchlist) {
const card = cards.find((c) => c.id === item.id);
const current =
card?.prices.raw.near_mint?.tcgplayer?.market ?? null;
newSnapshot[item.id] = current;
const previous = lastPrices[item.id];
if (current == null || previous == null) {
console.log(`[SEED] ${item.nickname}: $${current ?? "n/a"}`);
continue;
}
const delta = (current - previous) / previous;
const pct = (delta * 100).toFixed(1);
const symbol = delta >= 0 ? "▲" : "▼";
if (Math.abs(delta) >= item.alertThreshold) {
console.log(
`[ALERT] ${item.nickname}: $${previous} → $${current} (${symbol}${pct}%)`
);
} else {
console.log(
`[ok] ${item.nickname}: $${current} (${symbol}${pct}%)`
);
}
}
// 5. 다음 실행을 위해 스냅샷 저장
await writeFile(SNAPSHOT_FILE, JSON.stringify(newSnapshot, null, 2));
console.log(`\n${tcg.rateLimit.remaining}/${tcg.rateLimit.limit} calls left today`);
}
main().catch((err) => {
console.error(err);
process.exit(1);
});
이것이 전체 스크립트입니다. 실제 로직 50줄 미만, 네이티브 fetch, SDK 외 의존성 없음, 재시도와 속도 제한을 자동으로 처리합니다.
5단계: 실행
npm run tracker
첫 실행 출력:
[SEED] Charizard ex (Obsidian Flames): $48.97
[SEED] Charizard (Base Set) 1st Ed: $488.20
99998/100000 calls left today
하루를 기다린 후 다시 실행합니다:
[ok] Charizard ex (Obsidian Flames): $49.12 (▲0.3%)
[ALERT] Charizard (Base Set) 1st Ed: $488.20 → $520.00 (▲6.5%)
99997/100000 calls left today
축하합니다 — 동작하는 TCG 가격 트래커가 완성되었습니다. SDK의 retry 옵션을 통해 자동으로 재시도를 처리하고, 20개 이상의 ID를 전달하면 더 큰 관심 목록을 투명하게 청킹하며, 간단한 JSON 파일을 통해 실행 간 상태를 유지합니다.
배치 조회가 호출 횟수를 절약하는 방법
tcg.cards.search({ ids }) 호출은 자세히 이해할 가치가 있습니다. 카드 ID 배열을 전달하면:
- SDK가 배열(어떤 길이든)을 받습니다
- 백엔드의 요청당 최대치인 20개씩 그룹으로 청킹합니다
- 모든 청크 요청을 병렬로 실행합니다
- 응답을 단일
{ data, total, limit, offset }형태로 병합합니다 - 마치 하나의 호출인 것처럼 합쳐진 결과를 반환합니다
일일 쿼터의 경우, 각 HTTP 요청 하나가 호출 하나로 계산되지만, 청킹은 투명하게 처리되므로 코드 관점에서는 함수 호출 하나를 만드는 것입니다. 카드 100개 관심 목록 = HTTP 요청 5개 = 일일 실행당 쿼터 5회 소모. Free 티어(일 200 요청)에서 카드 100개 관심 목록으로 하루 40번 실행해도 여유가 있습니다.
배치 조회 패턴에 대한 자세한 내용은 API 레퍼런스를 참조하세요. TCGPlayer vs eBay 포스트에서는 응답에 TCGPlayer와 eBay 데이터가 기본으로 포함되는 이유를 설명합니다.
확장: 이메일 알림
트래커는 표준 출력으로 출력합니다. 작은 확장: 트랜잭션 이메일 서비스를 통해 이메일로 알림을 파이프합니다. 범용 이메일 훅을 사용하는 관련 변경사항입니다:
async function sendEmail(to: string, subject: string, body: string) {
// Resend, SendGrid, Postmark, 또는 원하는 SMTP
// await fetch("https://api.resend.com/emails", { ... });
}
// console.log ALERT 줄을 다음으로 교체:
if (Math.abs(delta) >= item.alertThreshold) {
const line = `${item.nickname}: $${previous} → $${current} (${symbol}${pct}%)`;
console.log(`[ALERT] ${line}`);
await sendEmail(
"[email protected]",
`TCG price alert: ${item.nickname}`,
line
);
}
이 변경으로 카드가 임계값을 초과할 때마다 이메일을 받을 수 있습니다. cron이나 GitHub Action을 통해 스크립트를 예약하면 개인 컬렉션을 위한 무료 가격 알림 서비스를 구축한 것입니다.
확장: 등급 카드 가격
관심 목록에 빈티지 또는 등급 카드가 포함된 경우, 원시 TCGPlayer 데이터만으로는 부족합니다 — eBay 등급 판매 완료 목록 평균이 필요합니다. 이는 Trader 플랜 이상이 필요합니다(Free 티어는 원시 TCGPlayer 가격만 반환). 등급 데이터가 eBay에서 오는 이유는 PSA vs BGS vs CGC 비교를 참조하세요.
SDK의 TypeScript 타입에는 이미 등급 가격이 포함되어 있으므로 접근하는 것은 다음과 같습니다:
// PSA 10 가격, 안전한 옵셔널 체이닝
const psa10 = card.prices.graded?.psa?.["10"]?.ebay?.avg_7d;
// BGS 9.5 가격
const bgs95 = card.prices.graded?.bgs?.["9.5"]?.ebay?.avg_7d;
// CGC 10 가격
const cgc10 = card.prices.graded?.cgc?.["10"]?.ebay?.avg_7d;
관심 목록 항목에 graderTarget 필드(예: "psa-10" 또는 "bgs-9.5")를 추가하고, 해당 필드가 있을 때 트래커의 current 가격 추출을 등급 조회로 교체하세요. 10줄 추가로 관심 목록이 원시+등급 혼합 트래커가 됩니다.
확장: Discord 봇
동일한 로직을 실행하는 Discord 슬래시 커맨드는 매우 작습니다. 전체 아키텍처:
- 봇 프레임워크로
discord.js사용 - 카드 이름 인수를 받는
/price커맨드 등록 - 커맨드 핸들러 내에서
tcg.cards.search({ q: cardName, limit: 3 })호출 - 상위 결과를 카드 이름, TCGPlayer 마켓, eBay 7일 평균,
/catalog페이지 링크가 담긴 Discord 임베드로 포맷
전체 동작하는 Discord 봇은 이 포스트의 범위를 벗어나지만, 그것이 전체 아키텍처입니다 — 약 80줄의 코드. SDK가 모든 API 배관을 처리하므로 봇은 Discord 전용 포맷팅에 집중할 수 있습니다.
오류 적절히 처리하기
위의 기본 트래커에는 오류를 로깅하고 종료하는 catch가 있습니다. 프로덕션 환경에서는 SDK의 타입된 오류를 사용하여 더 세밀한 오류 처리가 필요할 것입니다:
import {
TcgLookupClient,
AuthenticationError,
PlanAccessError,
RateLimitError,
NotFoundError,
} from "@tcgpricelookup/sdk";
try {
const { data } = await tcg.cards.search({ ids });
// ... 정상 흐름
} catch (err) {
if (err instanceof AuthenticationError) {
console.error("Invalid API key. Check TCG_API_KEY env var.");
process.exit(1);
}
if (err instanceof PlanAccessError) {
console.error(
"Your plan doesn't include this feature. Upgrade at /pricing."
);
process.exit(1);
}
if (err instanceof RateLimitError) {
console.error("Rate limited. The SDK already auto-retried 3x.");
process.exit(1);
}
if (err instanceof NotFoundError) {
console.error("Card ID in your watchlist doesn't exist anymore.");
// 종료하지 않음 — 해당 항목 건너뜀
}
throw err; // 알 수 없는 오류, 다시 던지기
}
각 오류 클래스에는 .status, .url, .body, 그리고 사람이 읽을 수 있는 .message가 있습니다. 전체 오류 계층 구조는 SDK README를 확인하세요.
Cron 작업으로 실행하기
트래커가 로컬에서 동작하면 예약합니다. 가장 간단한 옵션 — cron 항목:
0 9 * * * cd /path/to/tcg-price-tracker && /usr/local/bin/npm run tracker >> tracker.log 2>&1
이것은 매일 오전 9시에 실행됩니다. 출력을 로그 파일로 파이프하고 주기적으로 읽거나, 위의 이메일 확장을 추가하여 받은 편지함에서 알림을 받을 수 있습니다.
클라우드 옵션으로는 GitHub Actions가 이에 잘 맞습니다. .github/workflows/tracker.yml을 만듭니다:
name: TCG price tracker
on:
schedule:
- cron: "0 9 * * *"
workflow_dispatch:
jobs:
track:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
- run: npm ci
- run: npm run tracker
env:
TCG_API_KEY: ${{ secrets.TCG_API_KEY }}
- uses: actions/upload-artifact@v4
with:
name: last-prices
path: last-prices.json
TCG_API_KEY를 GitHub 시크릿으로 추가하면 매일 가격 트래커를 실행하는 무료 서버리스 cron 작업이 완성됩니다.
TCGPlayer를 직접 스크래핑하지 않는 이유
이것은 처음 이를 고려하는 모든 개발자가 묻는 질문입니다. 솔직한 답변: 할 수 있습니다. 하지만 SDK보다 나빠지기 전까지 2~4주의 지속적인 노력이 필요하고, 그 이후에는 영원히 유지보수 비용이 발생합니다.
스크래핑에 필요하지만 SDK가 대신 처리해주는 것들:
- IP 차단을 피하기 위한 프록시 로테이션 (월 $200~500)
- TCGPlayer의 봇 탐지를 위한 캡차 해결 (월 $50~200)
- TCGPlayer가 마크업을 업데이트할 때마다 몇 주마다 깨지는 HTML 파싱
- 소스별로 튜닝된 속도 제한 관리
- 조건 정규화 — "NM", "Near Mint", "Near-Mint"이 모두 하나의 표준 문자열에 매핑되어야 함
- 멀티게임 스크래핑 — TCGPlayer, eBay, 게임별 엣지 케이스에 대한 별도의 스크래퍼 필요
- 무언가 깨졌을 때 유지보수 — 항상 발생합니다
이 수학을 TCG API 랜딩 페이지의 "Build vs Buy" 섹션에서 자세히 살펴봤습니다. 1년 차에 직접 스크래퍼를 구축하는 대략적인 실제 비용은 인프라 + 개발자 시간으로 $3,000~$8,000이며, 그 이후에도 지속적인 유지보수 비용이 발생합니다. SDK는 시작 비용 $0(Free 티어)이고 사용량에 따라 선형적으로 확장됩니다.
가격 추적 프로젝트에 개발 시간을 쓴다면, 데이터 레이어가 아닌 제품 기능에 투자하세요.
더 나아가기
50줄 트래커는 최소 실행 가능 제품입니다. 다음으로 갈 수 있는 곳:
- 포트폴리오 평가 — 트래커를 확장하여 관심 목록의 총 가치를 하나의 숫자로 합산하고, 역사적 총합을 추적하고, 시간에 따라 차트로 표시
- 멀티게임 확장 — 코드 변경 없이 지원되는 8가지 게임 전체에서 카드를 혼합할 수 있는 관심 목록
- 가격 이력 차트 —
tcg.cards.history(id, { period: "30d" })(Trader 플랜)를 사용하여 일별 가격 데이터를 가져오고 Chart.js, Recharts 등으로 차트 구축 - 웹훅 통합 — 이메일 대신 Slack, Telegram, 또는 사용자 정의 알림 서비스로 알림 파이프
- 자동화된 차익거래 — TCGPlayer 마켓과 eBay 판매 완료 평균을 비교하여 두 가격이 크게 다른 카드 찾기(클래식 플리퍼 전략)
- 공개 가격 대시보드 — 트래커 출력을 작은 Next.js 앱으로 감싸서 포트폴리오의 공개 랜딩 페이지로 만들기
이 모든 것은 동일한 cards.search({ ids }) 호출의 작은 확장입니다. SDK는 여러분을 방해하지 않습니다.
자주 묻는 질문
Free 티어에서 몇 개의 카드를 추적할 수 있나요?
Free 티어는 하루 200개의 요청을 제공합니다. 최대 20개의 카드 ID를 포함하는 각 cards.search({ ids }) 호출은 1개의 요청으로 계산됩니다. 하루에 한 번 실행되는 카드 100개 관심 목록은 5개의 요청 — 일일 쿼터의 약 2.5%입니다. 카드 100개 관심 목록으로 하루 수백 번 실행해도 한도에 도달하지 않습니다.
SDK가 브라우저에서도 동작하나요, Node에서만 되나요?
둘 다 됩니다. SDK는 네이티브 fetch를 사용하며, 이는 현대 브라우저, Node 18+, Cloudflare Workers, Bun, Deno에서 사용 가능합니다. 그러나 클라이언트 사이드 JavaScript에서는 절대 API 키를 노출하지 마세요 — SDK 호출을 제어하는 백엔드를 통해 프록시하세요. 인증 문서에서 보안 관련 사항을 다룹니다.
tcglookup CLI는 어떻게 되나요?
코드를 작성할 필요 없는 터미널 전용 워크플로우의 경우, tcglookup을 확인하세요 — 공식 커맨드라인 도구입니다. 동일한 SDK를 예쁜 색상 테이블과 jq에 파이프할 수 있는 --json 플래그로 감쌉니다. npm install -g tcglookup으로 설치하고 어떤 터미널에서든 tcglookup search "charizard" --game pokemon을 실행하세요.
다른 프레임워크(Next.js, Remix, SvelteKit)에서도 이 SDK를 사용할 수 있나요?
네. SDK는 프레임워크에 구애받지 않습니다. Next.js 서버 컴포넌트에서는 async 함수에서 직접 호출하세요. API 라우트에서는 서버 사이드에서 호출하고 데이터를 클라이언트에 JSON으로 반환하세요. Remix 로더나 SvelteKit load 함수에서도 동일한 패턴입니다. 유일한 규칙은: 클라이언트 사이드 컴포넌트에서는 절대 호출하지 마세요 — 브라우저에서 API 키가 노출됩니다.
전체 SDK 문서는 어디서 볼 수 있나요?
GitHub README에는 빠른 시작, 모든 엔드포인트에 대한 코드 예제, 오류 처리, 재시도 구성, TypeScript 타입 레퍼런스가 있습니다. tcgpricelookup.com의 API 레퍼런스에는 SDK가 내부적으로 무엇을 하는지 이해하고 싶다면 원시 HTTP 엔드포인트 문서가 있습니다.
무언가 작동하지 않을 때 도움을 어디서 받을 수 있나요?
SDK 관련 문제는 github.com/TCG-Price-Lookup/tcglookup-js에 이슈를 여세요. API 문제(예상치 못한 응답, 누락된 카드, 데이터 품질)는 [email protected]으로 이메일을 보내세요. 모든 이슈를 읽고 평일 24시간 내에 응답합니다.
GitHub의 전체 소스
전체 트래커 스크립트는 TCG API 랜딩 페이지에서 연결된 참조 gist로 제공됩니다. @tcgpricelookup/sdk 소스와 문서는 다음 위치에 있습니다:
- npm 패키지: @tcgpricelookup/sdk
- GitHub 리포: TCG-Price-Lookup/tcglookup-js
- Next.js 스타터 (웹 앱, 원클릭 Vercel 배포): TCG-Price-Lookup/nextjs-tcg-starter
- CLI 버전 (코드 불필요): tcglookup on npm
MIT 라이선스, 오픈소스, 런타임 의존성 없음, Node 18/20/22, 브라우저, Cloudflare Workers, Bun, Deno에서 지속적으로 테스트됩니다.
이 튜토리얼에 대하여
이 포스트는 TCG Price Lookup API로 트레이딩 카드 가격 도구를 구축하는 개발자 튜토리얼 시리즈의 일부입니다. 더 많은 콘텐츠:
- 카드 수집가에게 eBay 가격이 중요한 이유 — 이 SDK가 노출하는 이중 소스(TCGPlayer + eBay) 모델의 기초 설명
- TCGPlayer vs eBay: 어느 쪽이 더 정확한 카드 가격을 제공하나요? — 이 트래커를 빈티지 카드 대 모던 카드에 사용할 때 직면하는 트레이드오프
- PSA vs BGS vs CGC: 완전한 트레이딩 카드 등급 비교 — 트래커를 등급 카드로 확장하고 싶다면 관련 내용
- TCG 가격 API 소개 — API 공지 포스트
tcgpricelookup.com/tcg-api에서 무료 API 키를 신청하세요 — 신용카드 불필요, 일 200 요청, 전체 카탈로그로 구축을 시작하세요.
관련 포스트: Complete MTG Card Price Guide 2026 · Complete Yu-Gi-Oh! Card Price Guide 2026 · One Piece TCG Price Guide 2026