
Ecommerce Listing
- 4 installs
- 5.2k repo stars
- Updated July 21, 2026
- browser-act/skills
E-commerce Product Listing is a Claude skill that extracts a paginated list of products (URL, name, price, image, rating) from any e-commerce category or search page via browser automation.
About
This skill extracts a structured list of products from an e-commerce category page, search results page, or keyword search. Each item includes URL, name, price, currency, image, rating, and review count. It runs browser-automation scripts against a live page and works on Amazon, eBay, Walmart, Shopify collections, WooCommerce, and Google Shopping with price, brand, and rating filters plus multi-page pagination.
- Extracts paginated product lists (URL, name, price, currency, image, rating, review count) from category/search pages
- Prebuilt filter-URL patterns for Amazon, eBay, Walmart, Google Shopping plus a generic --site mode
- Supports keyword search with price/brand/rating/in-stock filters and multi-page pagination
Ecommerce Listing by the numbers
- 4 all-time installs (skills.sh)
- Ranked #1,780 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
ecommerce-listing capabilities & compatibility
Free; needs the browser-act tool and an open browser, no API key stated
- Capabilities
- ecommerce product detail · ecommerce reviews · ecommerce seller info
- Use cases
- web scraping · data analysis · research
- Pricing
- Free
What ecommerce-listing says it does
Extract a structured list of products from any e-commerce category, search results, or keyword search page, with support for price/brand/rating filters and multi-page pagination.
Works on Amazon, eBay, Walmart, Shopify collections, WooCommerce shops, Google Shopping, and any public product listing page.
No login required for public listing pages
npx skills add https://github.com/browser-act/skills --skill ecommerce-listingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 4 |
|---|---|
| repo stars | ★ 5.2k |
| Last updated | July 21, 2026 |
| Repository | browser-act/skills ↗ |
What it does
Extract a paginated, structured product list with prices and ratings from any e-commerce category or search page.
Who is it for?
Bulk product-list extraction and price monitoring across marketplaces
Skip if: Single-product deep detail (use ecommerce-product-detail instead)
When should I use this skill?
You need a paginated list of products with prices and ratings from a category or search results page
What you get
A structured, paginated array of products with price, rating, image, and review count.
- Paginated product array with URL, name, price, currency, image, rating, review count
By the numbers
- Returns products with 7 fields per item (url, name, price, currency, image, rating, review_count)
- Default 20 items per page
Files
E-commerce — Product Listing
Category/search URL or keyword + filters → paginated product list (URL, name, price, image, rating per item)
Language
All process output to user (progress updates, process notifications) follows the user's language.
Objective
Extract a structured list of products from any e-commerce category, search results, or keyword search page, with support for price/brand/rating filters and multi-page pagination.
Prerequisites
- Target browser is open and connected
- No login required for public listing pages
Pre-execution Checks
1. Tool Readiness
If browser-act has been confirmed available in the current session → skip this step.
Invoke browser-act via Skill tool to load usage. If installation or configuration issues arise, follow its guidance to resolve then retry.
Capability Components
This Skill's operational boundary = what the user can manually do in their browser. It only reads data already displayed to the user on the page. JS code is encapsulated in Python files under thescripts/directory, invoked viaeval "$(python scripts/xxx.py {params})". Use the bash tool for execution.
DOM: Extract product list from current page
Navigate to the listing/search page first, then extract:
eval "$(python scripts/extract-listing.py --max-results 20)"Parameters:
--max-results: max items to return per page, default 20
Output example:
{
"count": 20,
"items": [
{
"url": "https://www.amazon.com/dp/B09WNK39JN",
"name": "Amazon Echo Pop",
"price": 39.99,
"currency": "USD",
"image": "https://m.media-amazon.com/images/I/...jpg",
"rating": 4.7,
"review_count": 103789,
"asin": "B09WNK39JN"
}
]
}DOM: Get next page URL
After extracting a page, get the URL to navigate to for the next page:
eval "$(python scripts/extract-listing-next-page.py)"Output example:
{"next_url": "https://www.amazon.com/s?k=headphones&page=2", "has_next": true, "method": "amazon"}When has_next is false, pagination is complete.
Composite: Keyword search with filters → product list
Step 1 — Build search URL with filters:
Construct the URL based on target site and desired filters using the patterns below, then navigate:
Amazon (amazon.com):
https://www.amazon.com/s?k={keyword_urlencoded}&s={sort}&rh={filter_params}- Sort (
s):price-asc-rank|price-desc-rank|review-rank|date-desc-rank(omit for relevance) - Price filter: append
p_36:{min_cents}-{max_cents}torh(dollars × 100, e.g. $50–$200 →p_36:5000-20000) - Rating filter: append
avg_customer_review:four-and-above|three-and-above|two-and-abovetorh - In-stock: append
p_n_availability:1248801011torh - Multiple
rhvalues: comma-separate (e.g.rh=p_36:5000-20000,avg_customer_review:four-and-above)
eBay (ebay.com):
https://www.ebay.com/sch/i.html?_nkw={keyword_urlencoded}&_udlo={min_price}&_udhi={max_price}&_sop={sort_num}- Sort:
12=BestMatch |15=PriceLow |16=PriceHigh |24=NewlyListed
Walmart (walmart.com):
https://www.walmart.com/search?q={keyword_urlencoded}&min_price={min}&max_price={max}&sort={sort}- Sort:
best_match|price_low|price_high|rating_high
Google Shopping (cross-site, no --site):
https://www.google.com/search?tbm=shop&q={keyword_urlencoded}&tbs=p_ord:{sort}- Sort:
rv=relevance |pd=price ascending |prd=price descending
Any site with `--site` (generic):
https://{site}/search?q={keyword_urlencoded}Step 2 — Navigate and extract: 1. navigate {constructed_url} → wait stable 2. eval "$(python scripts/extract-listing.py --max-results {n})"
Step 3 — Paginate (repeat until done): 1. eval "$(python scripts/extract-listing-next-page.py)" 2. If has_next is true: navigate {next_url} → wait stable → re-run extract-listing.py 3. If has_next is false: stop
Pagination
URL Pagination: extract-listing-next-page.py detects rel=next link, platform-specific pagination controls, and URL page parameters. Returns next_url for navigation.
DOM Pagination: For sites with load-more buttons (some Shopify themes): 1. state to find "Load more" or "Show more" button 2. click <index> → wait stable → re-run extract-listing.py 3. Termination: button no longer present, or item count stops increasing
Success Criteria
result.count >= 1 AND items[0].url != null
Known Limitations
- Amazon: direct navigation may trigger bot detection on fresh sessions — navigate from
https://www.amazon.comfirst - eBay listing pages may require navigating from
https://www.ebay.comfirst - Google Shopping results have complex SPA structure and may have reduced accuracy; prefer direct site search when
--siteis specified - Filter URL parameters are site-specific; unsupported filter parameters are silently ignored by some sites
- Shopify themes vary widely; if the generic DOM strategies miss items, check if the page has JSON-LD ItemList or Product array in page source
Execution Efficiency
- Batch orchestration: Loop through pages serially within a single session; add 1–2 second intervals between page navigations
- Test before batch execution: Test with 1 page before running multi-page extraction
- Error resumption: Record page number; on failure, resume from the last successful page
Experience Notes
Path: {working-directory}/browser-act-skill-forge-memories/ecommerce-scraper-ecommerce-listing.memory.md
Before execution: If the file exists, read it first — it records unexpected situations encountered during past executions; adjust strategy order accordingly.
After execution: If an unexpected situation is encountered (strategy became ineffective, page redesigned, anti-scraping upgraded, better path discovered), append a line: {YYYY-MM-DD}: {what happened} → {conclusion}
import argparse
import sys
def main():
sys.stdout.reconfigure(encoding='utf-8', newline='\n')
parser = argparse.ArgumentParser()
parser.parse_args()
js = r"""
(function() {
try {
// rel=next link (SEO-standard)
const relNext = document.querySelector('link[rel="next"]')?.href;
if (relNext) return JSON.stringify({ next_url: relNext, has_next: true, method: 'rel-next' });
// Amazon pagination
const amzNext = document.querySelector('.a-pagination .a-last:not(.a-disabled) a')?.href;
if (amzNext) return JSON.stringify({ next_url: amzNext, has_next: true, method: 'amazon' });
// eBay pagination
const ebayNext = document.querySelector('.pagination__next a, a[aria-label="Go to next search page"]')?.href;
if (ebayNext) return JSON.stringify({ next_url: ebayNext, has_next: true, method: 'ebay' });
// WooCommerce pagination
const wooNext = document.querySelector('.woocommerce-pagination .next')?.href;
if (wooNext) return JSON.stringify({ next_url: wooNext, has_next: true, method: 'woocommerce' });
// Generic next link patterns
const genericNext = document.querySelector(
'a[aria-label*="Next"], a[aria-label*="next"], .next-page a, .pagination .next a, [class*="pagination"] a[rel="next"], .pager .next a, a[class*="next-page"], a[class*="page-next"]'
)?.href;
if (genericNext) return JSON.stringify({ next_url: genericNext, has_next: true, method: 'generic' });
// URL-based page parameter increment
const url = new URL(window.location.href);
const pageKey = url.searchParams.has('page') ? 'page' : (url.searchParams.has('p') ? 'p' : (url.searchParams.has('pg') ? 'pg' : null));
if (pageKey) {
const nextPage = parseInt(url.searchParams.get(pageKey)) + 1;
url.searchParams.set(pageKey, nextPage);
const hasItems = document.querySelectorAll('[data-component-type="s-search-result"], .s-item, ul.products li.product, [class*="product-card"]').length > 0;
if (hasItems) return JSON.stringify({ next_url: url.href, has_next: true, method: 'url-param' });
}
return JSON.stringify({ next_url: null, has_next: false });
} catch(e) {
return JSON.stringify({ error: true, message: e.message });
}
})()
"""
print(js)
if __name__ == '__main__':
main()
import argparse
import sys
def main():
sys.stdout.reconfigure(encoding='utf-8', newline='\n')
parser = argparse.ArgumentParser()
parser.add_argument('--max-results', type=int, default=20)
args = parser.parse_args()
js_template = r"""
(function() {
try {
const maxResults = MAX_RESULTS;
let items = [];
// Strategy 1: JSON-LD ItemList
const lds = Array.from(document.querySelectorAll('script[type="application/ld+json"]')).map(s => {
try { return JSON.parse(s.textContent); } catch(e) { return null; }
}).filter(Boolean);
const flat = lds.flatMap(l => Array.isArray(l) ? l : [l]);
const listEl = flat.find(l => l['@type'] === 'ItemList' && l.itemListElement);
if (listEl) {
items = (listEl.itemListElement || []).slice(0, maxResults).map(e => {
const item = e.item || e;
const offer = Array.isArray(item.offers) ? item.offers[0] : item.offers;
return { url: item.url, name: item.name, price: offer?.price != null ? parseFloat(offer.price) : null, currency: offer?.priceCurrency || null, image: Array.isArray(item.image) ? item.image[0] : (item.image || null), rating: item.aggregateRating?.ratingValue != null ? parseFloat(item.aggregateRating.ratingValue) : null, review_count: item.aggregateRating?.reviewCount != null ? parseInt(item.aggregateRating.reviewCount) : null };
}).filter(item => item.url || item.name);
}
// Strategy 2: Amazon search results
if (items.length === 0) {
const cards = Array.from(document.querySelectorAll('[data-component-type="s-search-result"]'));
if (cards.length > 0) {
items = cards.slice(0, maxResults).map(card => {
const link = card.querySelector('h2 a, .a-link-normal.s-no-outline');
const url = link?.href || null;
const name = card.querySelector('h2 .a-text-normal, h2 span')?.textContent.trim() || null;
const priceText = card.querySelector('.a-price .a-offscreen')?.textContent.trim();
const price = priceText ? parseFloat(priceText.replace(/[^0-9.]/g, '')) : null;
const currency = priceText?.match(/[A-Z]{3}/)?.[0] || (priceText?.includes('$') ? 'USD' : null);
const image = card.querySelector('.s-image')?.src || null;
const ratingText = card.querySelector('.a-icon-alt')?.textContent.trim();
const rating = ratingText ? parseFloat(ratingText) : null;
const rcText = card.querySelector('.a-size-small .a-link-normal')?.textContent.trim();
const review_count = rcText ? parseInt(rcText.replace(/[^0-9]/g, '')) : null;
const asin = card.getAttribute('data-asin') || null;
return { url, name, price, currency, image, rating, review_count, asin };
}).filter(item => item.url || item.name);
}
}
// Strategy 3: eBay search results
if (items.length === 0) {
const cards = Array.from(document.querySelectorAll('.s-item:not(.s-item--placeholder)'));
if (cards.length > 0) {
items = cards.slice(0, maxResults).map(card => {
const link = card.querySelector('.s-item__link');
const url = link?.href || null;
const name = card.querySelector('.s-item__title')?.textContent.trim() || null;
const priceText = card.querySelector('.s-item__price')?.textContent.trim();
const price = priceText ? parseFloat(priceText.replace(/[^0-9.]/g, '')) : null;
const image = card.querySelector('.s-item__image img')?.src || null;
const condition = card.querySelector('.s-item__condition, .SECONDARY_INFO')?.textContent.trim() || null;
const shipping = card.querySelector('.s-item__shipping, .s-item__logisticsCost')?.textContent.trim() || null;
return { url, name, price, currency: null, image, condition, shipping };
}).filter(item => item.url || item.name);
}
}
// Strategy 4: WooCommerce product grid
if (items.length === 0) {
const cards = Array.from(document.querySelectorAll('ul.products li.product'));
if (cards.length > 0) {
items = cards.slice(0, maxResults).map(card => {
const link = card.querySelector('a.woocommerce-loop-product__link');
const url = link?.href || null;
const name = card.querySelector('.woocommerce-loop-product__title')?.textContent.trim() || null;
const priceEl = card.querySelector('.price .woocommerce-Price-amount.amount');
const price = priceEl ? parseFloat(priceEl.textContent.replace(/[^0-9.]/g, '')) : null;
const image = card.querySelector('img.attachment-woocommerce_thumbnail, img.wp-post-image')?.src || null;
const ratingEl = card.querySelector('.star-rating');
const rating = ratingEl ? parseFloat(ratingEl.getAttribute('aria-label') || '') || null : null;
return { url, name, price, currency: null, image, rating };
}).filter(item => item.url || item.name);
}
}
// Strategy 5: Shopify collection — JSON-LD Product array (multiple products)
if (items.length === 0) {
const productLds = flat.filter(l => l['@type'] === 'Product');
if (productLds.length > 1) {
items = productLds.slice(0, maxResults).map(p => {
const offer = Array.isArray(p.offers) ? p.offers[0] : p.offers;
return { url: p.url || p['@id'] || null, name: p.name || null, price: offer?.price != null ? parseFloat(offer.price) : null, currency: offer?.priceCurrency || null, image: Array.isArray(p.image) ? p.image[0] : (p.image || null), rating: p.aggregateRating?.ratingValue != null ? parseFloat(p.aggregateRating.ratingValue) : null };
}).filter(item => item.url || item.name);
}
}
// Strategy 6: Generic product cards heuristic
if (items.length === 0) {
const candidates = Array.from(document.querySelectorAll('article[class*="product"], [class*="product-card"], [class*="product-item"], [class*="product-tile"]'));
const results = candidates.slice(0, maxResults).map(card => {
const link = card.querySelector('a[href]');
const name = card.querySelector('h2, h3, h4, [class*="title"], [class*="name"]')?.textContent.trim() || null;
const priceText = card.querySelector('[class*="price"]')?.textContent.trim();
const price = priceText ? parseFloat(priceText.replace(/[^0-9.]/g, '')) : null;
const image = card.querySelector('img')?.src || null;
return { url: link?.href || null, name, price, currency: null, image };
}).filter(item => (item.url || item.name) && item.price != null);
if (results.length > 0) items = results;
}
if (items.length === 0) {
return JSON.stringify({ error: true, message: 'No product listings found on this page. Ensure this is a category, search results, or product listing page.' });
}
return JSON.stringify({ count: items.length, items });
} catch(e) {
return JSON.stringify({ error: true, message: e.message });
}
})()
"""
js = js_template.replace('MAX_RESULTS', str(args.max_results))
print(js)
if __name__ == '__main__':
main()
Related skills
FAQ
Which sites does it support?
Amazon, eBay, Walmart, Shopify collections, WooCommerce shops, Google Shopping, and any public product listing page via a generic --site mode.
Can it filter results?
Yes, it supports price range, brand, category, minimum rating, in-stock only, and sort order via site-specific URL patterns.