Search Etsy consumer listings for any caller-provided query, optionally sorted newest-first, and return structured listing data suitable for product research or repeated new-listing monitoring. Results may include listing ID, title, shop, canonical URL, current and original price, rating, review count, badges, shipping indicators, image, and sponsored-ad status. The workflow is read-only: never add items to carts, favorite listings, sign in, or initiate purchases.
Use Cases
Use for Etsy product discovery, competitive research, price or availability checks, keyword searches, newest-listing monitoring, optional category or facet filtering, sorting, shipping-country filtering, multi-page result collection. For recurring monitoring, use the same normalized query and filters on each run, sort by order=most_recent, and compare returned listing IDs or canonical URLs with the caller's previous observation. Use listing-detail verification only when the caller explicitly requires destination-specific shipping or a condition shown on the detail page.
Automation Flow
- Run the complete flow in one
browserless_agentsession with a US residential proxy so DataDome clearance persists:
{"proxy":{"proxy":"residential","proxyCountry":"us"},"commands":[...]}- Warm up and clear DataDome:
{"method":"goto","params":{"url":"https://www.etsy.com/","waitUntil":"load","timeout":45000}},
{"method":"solve","params":{"type":"dataDome"}},
{"method":"waitForTimeout","params":{"time":7000}}Dismiss a consent dialog only when present, commonly button[aria-label="Ok"].
3. Navigate directly to the requested search URL: https://www.etsy.com/search?q={url-encoded-query}. Append only requested parameters, including order=most_relevant|most_recent|price_asc|price_desc|highest_reviews, min={dollars}, max={dollars}, is_handmade=true, is_vintage=true, is_supply=true, instant_download=true|false, free_shipping=true, ship_to={ISO-country-code}, is_discounted=true, customizable=true, and is_personalizable=true. For newest-listing monitoring, use order=most_recent; this is an optional sort and does not imply that Etsy exposes a precise publication timestamp.
4. For pagination use &page={page-number} and deduplicate by listing ID across pages. Collect as many pages as the caller requests, or just the first page for a lightweight recurring check.
5. For dynamic category, color, material, occasion, recipient, style, or other facets, inspect loaded facet links and reuse their complete href parameters rather than guessing opaque IDs. Preserve caller-supplied parameters such as ref and as_prefix.
6. After the page stabilizes, run the evaluator below once per loaded page. It deduplicates repeated card markup, extracts visible listing metadata, and optionally marks cards matching caller-provided monitoring terms. Replace {match_terms_json} with a JSON array of lowercase terms, or use [] when no secondary match flag is needed:
(() => {
const terms={match_terms_json};
const clean=x=>String(x??'').replace(/\s+/g,' ').trim()||null;
const seen=new Set(),items=[];
for(const root of document.querySelectorAll('[data-listing-id],li,article,[role="listitem"]')){
const id=root.getAttribute('data-listing-id')||root.querySelector('[data-listing-id]')?.getAttribute('data-listing-id');
const link=root.querySelector('a[href*="/listing/"]');
if(!link)continue;
const url=link.href.split('?')[0];
const inferred=(url.match(/\/listing\/(\d+)/)||[])[1]||null;
const key=id||inferred||url;
if(seen.has(key))continue;
seen.add(key);
const title=clean(link.getAttribute('aria-label'))||clean(root.querySelector('h3,h2,[data-listing-title]')?.textContent)||clean(link.textContent);
const shop=clean(root.querySelector('[data-shop-name],.v2-listing-card__shop,[class*="shop-name"]')?.textContent);
const priceNode=root.querySelector('[data-price],.currency-value,p[class*="price" i],[aria-label*="price" i]');
const money=t=>{const s=String(t||'');const C='(?:CA\\$|A\\$|R\\$|US\\$|AU\\$|NZ\\$|HK\\$|S\\$|[$€£¥₹]|USD|EUR|GBP|JPY|INR|CAD|AUD|CHF|BRL)';const m=s.match(new RegExp('('+C+')\\s*([\\d.,]+)|([\\d.,]+)\\s*('+C+')','i'));if(!m)return{raw:null,cur:null};const norm={'R$':'BRL','CA$':'CAD','A$':'AUD','AU$':'AUD','US$':'USD','NZ$':'NZD','HK$':'HKD','S$':'SGD'};let cur=m[1]||m[4]||null;if(cur){const k=cur.replace(/\s/g,'').toUpperCase();cur=norm[k]||k;}if(/[-–—]|\bto\b|\baté\b/i.test(s)&&(s.match(new RegExp(C+'\\s*[\\d.,]+|[\\d.,]+\\s*'+C,'gi'))||[]).length>1)return{raw:null,cur};let n=(m[2]||m[3]||'');n=n.includes(',')&&n.includes('.')?(n.lastIndexOf(',')>n.lastIndexOf('.')?n.replace(/\./g,'').replace(',','.'):n.replace(/,/g,'')):n.includes(',')?(/,\d{2}$/.test(n)?n.replace(/\./g,'').replace(',','.'):n.replace(/,/g,'')):(/\.\d{3}$/.test(n)?n.replace(/\./g,''):n);const v=parseFloat(n);return{raw:Number.isFinite(v)?v:null,cur};};const price=clean(priceNode?.getAttribute('aria-label')||priceNode?.textContent)||clean(root.innerText)?.match(/[$€£¥₹]\s*[\d.,]+/)?.[0]||null;const pm=money(price);
const original=clean(root.querySelector('[class*="original" i],[data-original-price]')?.textContent);
const ratingText=clean(root.querySelector('[aria-label*="star rating" i]')?.getAttribute('aria-label'));
const ratingMatch=ratingText?.match(/([\d.]+)\s*star rating with\s*([\d.,kK]+)\s*reviews/i);
const blob=clean(root.innerText)||'';
const hay=[title,shop,blob,url].filter(Boolean).join(' ').toLowerCase();
const match=terms.length?terms.some(t=>hay.includes(String(t).toLowerCase())):null;
items.push({id:id||inferred,title,shop_name:shop,url,price,price_raw:pm.raw,currency:pm.cur,original_price:original,rating:ratingMatch?Number(ratingMatch[1]):null,review_count:ratingMatch?ratingMatch[2]:null,badges:[...new Set((blob.match(/Bestseller|Star Seller|Etsy's Pick/gi)||[]).map(x=>x))],is_ad:/\bAd from shop\b/i.test(blob),free_shipping:/free shipping/i.test(blob),match});
}
return {success:!(/captcha-delivery\.com/i.test(document.documentElement.innerHTML)||document.title==='etsy.com'),title:document.title,search_url:location.href,count:items.length,items};
})()- If monitoring across runs, compare
idor normalizedurlagainst the previous run. Treatmatchas a convenience relevance flag only; do not infer that a listing is newly published from its position alone. - Enforce requested numeric price bounds and
minRatingin memory onprice_raw/rating, becausemaxcan behave differently across search states; treat "under $X" as strictly less than X. Missing fields remainnull; never infer material, color, condition, or shipping eligibility from absent text. - When destination shipping must be confirmed, open each remaining canonical listing URL, use the visible
Deliver toestimator, select{country}inselect#estimated-shipping-country, submit with#estimated-shipping-submit-button, wait, and extract the displayed shipping text.ship_tois discovery-only. - When condition is required, inspect the visible expanded
Item detailssection and report condition only when Etsy explicitly displays it.
Possible Friction Points
- Etsy may interpose a DataDome challenge. Use stealth, a US residential proxy, and
solvewithtype: "dataDome"; never extract from the challenge shell. - A cold direct search can trigger a hard CAPTCHA. Warm up on
https://www.etsy.com/, solve DataDome, then navigate to the requested search URL in the same session. - If the title remains
etsy.comor the DOM contains acaptcha-delivery.comiframe, start a genuinely fresh browser session with a new proxy IP rather than repeatedly reloading the poisoned session. - Search results are directly addressable at
/search?q={url-encoded-query};order=most_recentis the relevant optional modifier for newest-first monitoring. - Etsy does not reliably expose a precise listing-publication timestamp in search cards. Use stable listing IDs and canonical URLs for cross-run change detection, and describe results as newest-first rather than timestamp-verified.
- "Under $X" means strictly less than X: filter on
price_raw < X, not<=. - Search and shop pages can repeat
data-listing-idin nested markup; always deduplicate by ID.data-indexis unreliable around sponsored slots. - Listing-card markup varies by experiment and localization. Price, title, shop, rating, and badges may be absent; return
nullor an empty array rather than guessing. - Dynamic facet IDs are opaque and category-dependent; read them from live facet links.
- Search-card condition is unreliable. Destination shipping and condition require detail-page verification when explicitly requested.
- Never click Add to Cart, Buy it Now, Favorite, or Sign In.