Compare products from any e-commerce site by extracting structured data via the
Zyte API, building a normalized comparison table, and recommending the best option.
A natural language product query.
zyte-ecommerce-products-compare-skill/
├── SKILL.md ← Workflow and instructions (you are here)
├── scripts/
│ ├── fetch_products.py ← Parallel fetcher (2–20+ URLs, rate-limit aware)
│ └── parse_product.py ← Response parser (handles edge cases in Zyte output)
└── references/
└── zyte-api-notes.md ← API reference notes and known gotchas
When to read what:
scripts/fetch_products.py — always. This is the primary data fetching tool.scripts/parse_product.py — always. Run it on each fetched response file.references/zyte-api-notes.md — when you hit unexpected errors or need tounderstand a parsing edge case.
python3 (3.8+, stdlib only — no pip installs required)ZYTE_API_KEY set in the environmentGather from the user:
| Field | Required | Description |
|---|---|---|
| ----------- | ---------- | --------------------------------------------------------- |
urls | Yes | List of product page URLs (at least 1, ideally 2+) |
intent | No | What the user cares about (e.g. "best value", "most durable") |
api_key | Yes | Zyte API key (prefer $ZYTE_API_KEY from env) |
present its data but note that comparison requires 2+.
http:// or https://.ZYTE_API_KEY is set:```bash
echo "$ZYTE_API_KEY" | head -c 4; echo "..."
```
If empty, ask the user to export it.
warn the user and ask for confirmation before proceeding.
Use the bundled fetch script to call the Zyte API for all URLs in parallel:
python3 scripts/fetch_products.py "$ZYTE_API_KEY" \
"https://example.com/products/item-a" \
"https://example.com/products/item-b" \
"https://example.com/products/item-c"
The script handles everything:
/tmp/product_1_raw.json, /tmp/product_2_raw.json, etc.Performance: Parallel fetching cuts wall-clock time significantly. For 3 URLs,
expect ~35s instead of ~90s sequential (roughly 60% faster). For 10+ URLs the
savings are even greater since most calls run concurrently.
Read the summary output to check which URLs succeeded:
{
"total": 3,
"success": 3,
"failed": 0,
"total_elapsed": 35.0,
"results": [
{"index": 1, "url": "...", "status": "ok", "file_path": "/tmp/product_1_raw.json", "elapsed": 18.2},
{"index": 2, "url": "...", "status": "ok", "file_path": "/tmp/product_2_raw.json", "elapsed": 34.9},
{"index": 3, "url": "...", "status": "ok", "file_path": "/tmp/product_3_raw.json", "elapsed": 21.4}
]
}
Exit codes: 0 = all succeeded, 1 = partial success (some failed), 2 = all failed.
For each successful result from Step 2, run the parse script:
python3 scripts/parse_product.py /tmp/product_1_raw.json
python3 scripts/parse_product.py /tmp/product_2_raw.json
python3 scripts/parse_product.py /tmp/product_3_raw.json
Skip any index where the fetch status was not "ok".
The script outputs normalized JSON to stdout with: name, price, currency,
currencyRaw, brand, sku, availability, rating, reviewCount,
bestRating, description, features, additionalProperties, breadcrumbs,
mainImage, url, regularPrice.
Exit codes: 0 = success, 1 = no product data in response, 2 = file/JSON error.
Make the extracted data comparable:
"2999.0") to floats. Note eachproduct's currency. If currencies differ, flag it — don't auto-convert.
bestRating differs across products. Formula: normalized = (ratingValue / bestRating) * 5. If a product has no
rating, show — and don't penalize it in ranking.
InStock → "In Stock", OutOfStock → "Out of Stock", PreOrder → "Pre-Order".
features and additionalProperties into one key-value map.Filter out junk entries (seller addresses, numeric-only keys, metadata like
"net quantity" or "item count"). See references/zyte-api-notes.md for
known junk patterns.
columns. Product-specific fields go in a "Unique Features" section.
Generate a markdown table adapted to the product category:
| Attribute | Product A | Product B |
|----------------|--------------------|--------------------|
| Name | ... | ... |
| Price | $29.99 | $34.99 |
| Regular Price | $39.99 | — |
| Brand | Brand X | Brand Y |
| Rating | 4.5/5 (120 reviews)| — |
| Availability | In Stock | In Stock |
| Key Features | feature1, feature2 | feature3, feature4 |
Rules:
— for missing values, never leave cells blank.$29.99 (was $39.99).List 3–5 bullet points focused on what would influence a purchase decision:
- Product A is 70% cheaper
- Only Product B has customer ratings
- Product C is the only one with detailed material specs
- Product A has the steepest discount (40% off)
With user intent — map intent keywords to relevant attributes:
| Keywords | Prioritize |
|---|---|
| ------------------------------- | ----------------------------------------------------- |
| budget / cheap / value | lowest price, price-to-rating ratio |
| best / premium / top | highest rating, most reviews, brand reputation |
| comfort / walking / running | cushioning, weight, sole tech, material |
| sport / court / outdoor | support, traction, durability, construction |
| durability / lasting | material quality, warranty, build |
Produce up to 3 recommendations:
🏆 **Best Overall:** [Name] — [1-sentence reason]
💰 **Best Value:** [Name] — [1-sentence reason]
⭐ **Best Premium:** [Name] — [1-sentence reason]
Only include categories that make sense for the product set.
Be honest about product-intent mismatch. If none of the products actually match
the user's stated need (e.g. user wants running shoes but all products are casual
sneakers), say so clearly and suggest what to look for instead.
Without intent — rank by value score:
value_score = (rating / 5) * 0.6 + (1 - normalized_price) * 0.4
Where normalized_price = (price - min) / (max - min) across the set. If a product
has no rating, use the average of the other products as a stand-in.
Structure the response as:
## Product Comparison
[Table from Step 5]
### Key Differences
[Bullets from Step 6]
### Recommendation
[From Step 7]
### Data Notes
- Source: Zyte API automatic product extraction
- [List any failed URLs with reasons]
- [Note if currencies differ across products]
- [Note if any data was incomplete]
- [Total fetch time and number of parallel workers used]
Most errors are handled automatically by scripts/fetch_products.py. Check
the JSON summary output to see per-URL status.
| Error | Handled by |
|---|---|
| ---------------------------- | ------------------------------------------------------- |
| Missing ZYTE_API_KEY | You (Step 1) — stop and ask user to export it. |
| Invalid URL format | fetch_products.py — skipped, reported in summary. |
| HTTP 401 | fetch_products.py — reported as auth_error. |
| HTTP 422 | fetch_products.py — reported as payload_error. |
| HTTP 429 (rate limit) | fetch_products.py — auto-retries 3× with backoff. |
| HTTP 520/521 | fetch_products.py — reported as http_error. |
No .product in response | fetch_products.py — reported as no_product_data. |
| JSON control characters | parse_product.py — handled via strict=False. |
| Missing individual fields | You (Step 5) — show — in table, never crash. |
| All URLs failed | Report errors from summary, suggest manual URL check. |
| Mixed currencies | You (Step 4) — show both, don't convert, flag it. |
If network calls fail with DNS resolution errors in sandboxed environments,
force a public DNS resolver before running:
echo "nameserver 8.8.8.8" > /etc/resolv.conf
共 1 个版本