
AI 에이전트가 장보기를 대신하고 영양 성분별로 음식을 순위 매길 수 있도록 Tesco의 API를 역공학했습니다
요약
Tesco의 비공개 GraphQL 게이트웨이를 역공학하여 영양 데이터를 추출할 수 있는 TypeScript SDK인 'basketeer'를 개발했습니다. 이를 통해 AI 에이전트가 영양 성분을 기준으로 식료품을 검색하고 정렬할 수 있는 기반을 마련했습니다.
핵심 포인트
- HTML 스크래핑 대신 안정적인 GraphQL 게이트웨이 활용
- 비정형 영양 데이터를 타입이 지정된 모델로 정규화
- AI 에이전트의 장보기 자동화를 위한 데이터 인터페이스 구축
- TypeScript 기반의 SDK 제공으로 개발 편의성 증대
영국 최대의 슈퍼마켓인 Tesco는 공개 API를 제공하지 않습니다. 저는 저의 주간 장보기를 자동화하고, 궁극적으로는 이를 AI 에이전트에게 맡기고 싶었습니다. 그리고 모든 식료품 사이트가 보유하고 있지만 아무도 쿼리(Query)할 수 있게 허용하지 않는 단 한 가지, 바로 **영양 데이터 (nutrition data)**를 얻고 싶었습니다. 그래서 저는 개인 Tesco 계정을 위한 타입이 지정된 TypeScript SDK인 basketeer를 구축했습니다.
가장 놀라웠던 부분은 다음과 같습니다:
# "고단백 요거트, 단백질 >=10g, 당류 <=7g, 단백질 순으로 정렬" — 로그인 없이 실시간 실행
basketeer search "high protein yogurt" --min-protein 10 --max-sugar 7 --sort protein
스크래핑이 아닌 — GraphQL 게이트웨이
대부분의 "Tesco API" 프로젝트는 HTML을 스크래핑(Scraping)하며, 이는 사이트의 디자인이 변경될 때마다 무너집니다. 하지만 Tesco의 자체 웹사이트는 xapi.tesco.com에 있는 GraphQL 게이트웨이와 통신합니다. 해당 프로토콜을 직접 사용하면, 디자인 변경의 영향을 받지 않는 안정적이고 구조화된 데이터 평면(Data plane)을 얻을 수 있습니다. 읽기 작업을 위한 유일한 필수 헤더는 공개된 x-apikey입니다.
따라서 검색, 제품 조회, 브라우징을 포함한 전체 카탈로그 측면은 단순한 fetch로 이루어집니다. 브라우저가 필요 없고, 상태가 없으며(Stateless), 초당 1회 요청(1 req/s)으로 예의를 지킵니다.
영양 성분의 노다지
제품 엔드포인트(Endpoint)는 제품 포장에 기재된 영양 성분표를 반환합니다. 가공되지 않은 상태로는 엉망입니다. 에너지가 두 줄에 걸쳐 나뉘어 있고("257 kJ/" 다음에 "61 kcal"), 쉼표 소수점, 각주 표시, 레이블 별칭("of which sugars", "salt equivalent") 등이 포함되어 있습니다. basketeer는 이 모든 것을 타입이 지정된 모델로 정규화(Normalize)합니다:
const { results } = await client.searchByNutrition("high protein yogurt", {
where: { protein: { min: 10 }, sugars: { max: 7 } },
sort: { by: "protein", dir: "desc" },
...
거대 영양소(Macros)와 구조화된 미량 영양소(Micronutrients) — 비타민 및 미네랄별로 양, 단위, 영양성분 기준치(Nutrient Reference Value) 대비 비율 포함 — 이 모든 것이 익명 읽기(Anonymous reads)로 제공됩니다. 이는 눈앞에 숨겨져 있던 식단 계획용 데이터셋입니다.
어려운 부분: 인증 (auth)
읽기(Reads)는 무료입니다. 계정과 연결된 모든 작업에는 세션(session)이 필요하며, 바로 이 지점에서 흥미로운 부분이 발생합니다. Tesco의 로그인 시스템은 Akamai(TLS 핑거프린팅 + JS 챌린지) 뒤에 위치하며, 이는 오직 실제 브라우저만이 통과할 수 있습니다. 따라서 basketeer는 Playwright를 통해 실제 Chrome으로 세션을 한 번 생성한 뒤, 베어러 토큰(bearer token)과 쿠키(cookies)를 수집합니다. 그 이후의 모든 호출은 순수 HTTP로 이루어집니다. 세션은 약 30일 동안 유지되며, 약 1시간마다 만료되는 액세스 토큰(access token)은 동일한 브라우저 경로를 통해 갱신됩니다.
에이전트가 쇼핑하도록 하기
깔끔하게 타입이 지정된(typed) 핵심 코어가 하나 있기 때문에, 그 위에 CLI와 **MCP 서버 (MCP server)**를 구축하는 것은 쉬웠습니다. MCP 서버를 사용하면 Claude Desktop(또는 모든 MCP 클라이언트)이 쇼핑을 수행할 수 있습니다. 카탈로그(catalogue) 및 영양 성분(nutrition) 도구는 인증 없이 작동하므로, 에이전트가 즉시 영양 성분에 따라 검색하고 순위를 매길 수 있습니다.
여기서는 안전 모델(safety model)이 중요합니다. 읽기 전용 도구에는 readOnlyHint가, 변경을 가하는 도구에는 destructiveHint가 주석으로 달려 있습니다. 또한 되돌릴 수 없는 작업(주문 취소, 결제 등)은 **2단계(two-step)**로 이루어집니다. 첫 번째 호출은 미리보기와 확인 토큰(confirm token)을 반환하며, 해당 토큰을 사용하여 다시 호출해야만 다음 단계로 진행됩니다. 그리고 checkout()은 의도적으로 결제 URL 단계에서 멈춥니다.
만약 여러분이 소매업체의 비공개 API (private API)를 역공학 (reverse-engineered)했거나, 에이전트 (agent)에 식료품 데이터를 연결하고 있다면, 인증 (auth) 문제와 필연적인 스키마 드리프트 (schema drift)에 어떻게 접근했는지 꼭 듣고 싶습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기