A web app that crawls Amazon search results for a given keyword and flags physical products sold by third-party sellers (potential counterfeits). Built as a prototype for integration into a larger counterfeit detection system.
Live demo: https://argos.ravenduran.com (password: argos)
Given a keyword (e.g. a brand name), Argos:
- Crawls Amazon search results page by page (up to 20 pages)
- Pre-filters each product on the search page (no extra API call):
- Skips products where the keyword is not an exact word in the title (case-insensitive)
- Skips digital products (Kindle, Prime Video, MP3, Audible, etc.)
- Skips media/entertainment (Blu-ray, DVD, film collections, animated series, etc.)
- Skips books, coloring books, art books, puzzles, pinball machines
- For remaining candidates, fetches the product detail page to get the seller name
- Flags as a HIT if:
- Seller is NOT Amazon (Amazon.com, Amazon Export Sales LLC, etc.)
- Product is NOT digital
- Product is NOT licensed media
- Returns the full hit list with title, ASIN, seller, price, category, and Amazon URL
- Allows export of results to CSV
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router) |
| Styling | Tailwind CSS + shadcn/ui |
| Scraping API | Oxylabs Amazon Scraper API |
| Streaming | Server-Sent Events (SSE) for live progress |
| Auth | Edge Middleware + httpOnly cookie |
| Hosting | Vercel |
src/
├── app/
│ ├── page.tsx # Main UI — search form + live log + results + CSV export
│ ├── login/
│ │ └── page.tsx # Password-protected login page
│ └── api/
│ ├── auth/
│ │ └── route.ts # POST /api/auth — validates password, sets httpOnly cookie
│ └── scan/
│ └── route.ts # POST /api/scan — SSE stream, core crawl + filter logic
├── middleware.ts # Edge auth guard — protects all routes + API endpoints
└── providers/
└── query-provider.tsx # TanStack Query provider
The scan endpoint streams Server-Sent Events (SSE) to the client. All crawling and filtering is deterministic TypeScript — no LLM involved in the crawl loop.
POST /api/scan { keyword, pages }
└── for page 1..N:
└── oxylabsSearch(keyword, page) → search results page
└── pre-filter items (title, digital, media)
└── Promise.all(oxylabsProduct(asin)) → parallel detail fetches
└── apply seller + category filters
└── emit SSE "candidate" event if HIT
└── emit SSE "done" event with full report
| Event | Payload | Description |
|---|---|---|
status |
{ message } |
Scan started |
progress |
{ message } |
Page-level or ASIN-level progress |
candidate |
{ message } |
A hit was found (real-time) |
done |
{ report } |
Final report JSON |
error |
{ message } |
Something failed |
{
keyword: string;
pages_crawled: number;
total_products_checked: number;
hits: Array<{
title: string;
asin: string;
seller: string;
price: string;
url: string;
category: string;
reason: string;
}>;
}| Filter | Examples |
|---|---|
| Amazon as seller | "Amazon.com", "Amazon Export Sales LLC" |
| Digital products | Kindle editions, Prime Video, MP3, Audible, digital downloads |
| Entertainment media | Blu-ray, DVD, 4K UHD, film collections, animated series, criterion collection |
| Books & other | Coloring books, art books, encyclopedias, puzzles, pinball machines |
| Keyword mismatch | Title doesn't contain the keyword as an exact word |
Physical products (toys, clothing, accessories, figures, merchandise, etc.) sold by a third-party seller that are NOT Amazon, NOT digital, NOT licensed media, and contain the keyword in the title.
| Variable | Description |
|---|---|
OXYLABS_USER |
Oxylabs API username |
OXYLABS_PASS |
Oxylabs API password |
OPENAI_API_KEY |
OpenAI API key (used in standalone Python script, not web app) |
ARGOS_PASSWORD |
The password users type to log in (e.g. argos) |
ARGOS_SECRET |
Internal cookie token — must be a long random string, never exposed |
- Edge Middleware (
src/middleware.ts) intercepts ALL requests (pages + API routes) before they hit any handler - Unauthenticated requests → redirect to
/login POST /api/authvalidatesARGOS_PASSWORD, sets anhttpOnly + secure + sameSite=strictcookie containingARGOS_SECRET- Cookie is valid for 7 days
- Direct API abuse (hitting
/api/scanwithout cookie) is blocked at the middleware level — no way to bypass from the frontend
Uses two endpoints:
POST https://realtime.oxylabs.io/v1/queries
{
"source": "amazon_search",
"domain": "com",
"query": "<keyword>",
"start_page": <N>,
"pages": 1,
"parse": true
}
Response structure used:
results[0].content.results.organic[]— main product listresults[0].content.results.amazons_choices[]— Amazon's Choice productsresults[0].content.last_visible_page— to detect when results run out
POST https://realtime.oxylabs.io/v1/queries
{
"source": "amazon_product",
"domain": "com",
"query": "<ASIN>",
"parse": true
}
Fields used from response:
results[0].content.featured_merchant.name→ seller name (primary)results[0].content.buybox[0].seller_name→ seller name (fallback)results[0].content.category[0].ladder[]→ category breadcrumbresults[0].content.title→ full product titleresults[0].content.variation[]→ variations (for digital detection)
A standalone CLI script also exists at ../project-argos/argos.py (sibling directory). It uses the same Oxylabs + OpenAI agent approach and can be run directly:
python3 argos.py --keyword "Godzilla" --pages 20 --output results.jsonThe web app's scan route is the production-ready version (no LLM, deterministic, parallel).
When integrating into a larger app:
-
Copy the filter logic from
src/app/api/scan/route.ts— theisDigital(),isMediaProduct(),isAmazonSeller(), andkeywordInTitle()functions are self-contained and reusable. -
The scan loop (lines inside
start(controller)) can be extracted into a standalonescanAmazon(keyword, maxPages, onEvent)function. The SSE streaming is just a delivery mechanism. -
Parallelization pattern — product detail calls are batched with
Promise.all()per page. This keeps the scan fast without overwhelming Oxylabs. -
Auth is self-contained —
middleware.ts+/api/auth/route.tscan be dropped in or replaced with whatever auth system the larger app uses. -
Vercel timeout — max 300s on Vercel Pro. For scans > 20 pages or slower keywords, consider moving the scan to a long-running backend (VPS + FastAPI/Express) and having Vercel proxy to it.
-
Oxylabs rate limits — the free trial gives 2,000 results. Each search page = 1 result, each product detail = 1 result. A 20-page scan with ~5 candidates/page = ~120 Oxylabs calls.
npm install
# Create .env.local
cp .env.example .env.local
# Fill in your values
npm run dev
# → http://localhost:3000.env.local:
OXYLABS_USER=your_oxylabs_username
OXYLABS_PASS=your_oxylabs_password
OPENAI_API_KEY=sk-...
ARGOS_PASSWORD=argos
ARGOS_SECRET=any-long-random-string
vercel --prodSet env vars via Vercel dashboard or CLI:
printf "value" | vercel env add VAR_NAME production
⚠️ Useprintfnotecho—echoadds a trailing newline that corrupts the value.
Built by Nap Solutions · May 2026