Search Etsy Products and Newest Listings

Site etsy.comTask search-productsVersion v15Updated Oct 23, 2026Category marketplace

Search Etsy listings for structured listing metadata, prices, optional filters, newest-first monitoring, pagination, and DataDome handling. Read-only. This skill was captured from a live agent session on etsy.com and is published here as a reusable recipe for agents.

NoteSelectors and URL schemes drift as sites change. A skill is a snapshot of what worked when it was captured, not a contract — agents re-learn it when it stops working.

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

  1. Run the complete flow in one browserless_agent session with a US residential proxy so DataDome clearance persists:
{"proxy":{"proxy":"residential","proxyCountry":"us"},"commands":[...]}
  1. 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};
})()
  1. If monitoring across runs, compare id or normalized url against the previous run. Treat match as a convenience relevance flag only; do not infer that a listing is newly published from its position alone.
  2. Enforce requested numeric price bounds and minRating in memory on price_raw/rating, because max can behave differently across search states; treat "under $X" as strictly less than X. Missing fields remain null; never infer material, color, condition, or shipping eligibility from absent text.
  3. When destination shipping must be confirmed, open each remaining canonical listing URL, use the visible Deliver to estimator, select {country} in select#estimated-shipping-country, submit with #estimated-shipping-submit-button, wait, and extract the displayed shipping text. ship_to is discovery-only.
  4. When condition is required, inspect the visible expanded Item details section 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 solve with type: "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.com or the DOM contains a captcha-delivery.com iframe, 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_recent is 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-id in nested markup; always deduplicate by ID. data-index is unreliable around sponsored slots.
  • Listing-card markup varies by experiment and localization. Price, title, shop, rating, and badges may be absent; return null or 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.

Call it

GET https://production-sfo.browserless.io/skills?token=TOKEN-HERE&domain=etsy.com&task=search-products