Etsy Scraper — listings, prices, shops, ratings avatar

Etsy Scraper — listings, prices, shops, ratings

Pricing

Pay per event

Go to Apify Store
Etsy Scraper — listings, prices, shops, ratings

Etsy Scraper — listings, prices, shops, ratings

Scrape Etsy listings past DataDome: price, shop, rating, review count, stock, materials and images. Search by keyword or scrape listing IDs directly. No browser. Powered by xhr.dev.

Pricing

Pay per event

Rating

0.0

(0)

Developer

xhrdev

xhrdev

Maintained by Community

Actor stats

0

Bookmarked

3

Total users

2

Monthly active users

3 days ago

Last modified

Categories

Share

Scrape Etsy listings past DataDome — price, shop, rating, review count, stock on hand, shipping origin, materials, category and the full image set. Search by keyword, pass listing IDs you already hold, or hand it listing and category URLs.

No browser is launched. The DataDome challenge is solved as plain HTTP by xhr.dev, so a page costs a few hundred milliseconds of compute instead of the several seconds and gigabyte of RAM a stealth browser needs.

Quickstart

Paste this into the input and press Run.

{
"queries": [
"leather wallet"
],
"scrapeDetails": true,
"maxItems": 20
}

Search is thin — this is Etsy, not the Actor

Etsy server-renders only about a dozen results per page and loads the rest in the browser as you scroll. There is no fix for that from an HTTP-only scraper, and any Actor claiming hundreds of results per search fetch is either driving a browser or counting differently.

So treat search as a way to discover listings, and raise maxPagesPerQuery to go wider rather than expecting depth from one page. scrapeDetails is on by default precisely because the listing page is where Etsy's good data lives.

Input

FieldTypeDefaultDescription
queriesstring[]["leather wallet"]Keywords to search. See the note on search depth below.
itemIdsstring[][]Etsy listing IDs to fetch directly — the number in the URL, e.g. 1214703903.
startUrlsarray[]/listing/ URLs are read as products; /search, /c/ and /market/ URLs are read as listings.
scrapeDetailsbooleantrueOn, every listing found is opened for its full record. Off, you get only what search results show — cheaper, much thinner. See below.
maxPagesPerQueryinteger1How deep to page each search, 1–250.
maxItemsinteger50Stop once this many listings have been scraped.
proxyConfigurationobjectApify RESIDENTIALWhich proxy to route through. Leave it on residential — see Proxies.
maxRetriesinteger3Attempts per page before giving up on it, 1–5. Each attempt takes a fresh proxy session.
timeoutSecsinteger120How long one page — challenge, solve and all — may take before the attempt fails. 10–300.
maxConcurrencyinteger4Pages worked on at once, 1–10.

Nothing is required — the defaults run a real search out of the box.

Output — with scrapeDetails: true (the default)

One dataset item per listing, from the listing page itself.

FieldTypeDescription
idstringEtsy listing ID.
titlestringListing title.
urlstringCanonical listing URL, tracking parameters stripped.
shopstringShop name.
pricenumberCurrent price.
currencystringISO currency code, e.g. USD.
availabilitystringInStock, OutOfStock, etc.
quantityAvailablenumberUnits the shop has on hand.
shipsFromstringCountry code the item ships from.
ratingnumberAverage rating out of 5.
reviewCountnumberNumber of reviews.
categorystringFull category path, e.g. Bags & Purses < Wallets & Money Clips < Wallets.
materialstringPrimary material where the shop set one.
descriptionstringFull listing description.
imagesstring[]Full-size image URLs.

Output — with scrapeDetails: false

One item per search result card. No listing page is fetched, so this is billed at the cheaper listing rate.

FieldTypeDescription
idstringEtsy listing ID.
titlestringListing title.
urlstringCanonical listing URL.
pricenumberPrice as shown on the card.
currencystringCurrency symbol as rendered, e.g. $ — the card does not carry an ISO code.
imagestringThumbnail URL (255px), not the full-size image.
shopIdstringNumeric shop ID. The shop name is only on the listing page.
ratingnumberAverage rating, when the card shows one.
reviewCountnumberReview count, when the card shows one.

Example record

Taken verbatim from a live run. Long text and image lists are trimmed here for readability; the real record carries them in full.

{
"id": "4461240474",
"title": "Personalized Leather Cash Wallet for Men, Full Grain Slim Front Pocket Card Holder, Handmade Minimalist Wallet, Gift for him",
"url": "https://www.etsy.com/listing/4461240474/leather-cash-wallet-slim-front-pocket",
"shop": "AmericanLeatherGift",
"price": 37.79,
"currency": "USD",
"availability": "InStock",
"quantityAvailable": 359,
"shipsFrom": "US",
"rating": 4.8,
"reviewCount": 56,
"category": "Bags & Purses < Wallets & Money Clips < Wallets",
"material": "Leather",
"description": "Personalized Leather Cash Wallet for Men Upgrade everyday carry with this personalized wallet crafted from premium full grain leather. Designed for modern simplicity, this leather cash wallet offers a refined alternative to bulky traditional wallets. Compact … (truncated here for readability)",
"images": [
"https://i.etsystatic.com/41443453/r/il/b11a3f/7994642756/il_fullxfull.7994642756_ld98.jpg",
"https://i.etsystatic.com/41443453/r/il/deb604/7782209464/il_fullxfull.7782209464_1lsp.jpg",
"https://i.etsystatic.com/41443453/r/il/3ccf3a/7830135935/il_fullxfull.7830135935_4qh6.jpg",
"… 17 more"
]
}

A row from the same search with scrapeDetails: false

{
"id": "4544756667",
"title": "Green Alligator Wallet - Handmade Luxury Bifold Wallet with Red Python Leather Interior - Exotic Leather Wallet for Men - Personalized Gift",
"url": "https://www.etsy.com/listing/4544756667/green-alligator-wallet-handmade-luxury",
"price": 570,
"currency": "$",
"image": "https://i.etsystatic.com/13350861/r/il/97feff/8302896792/il_255x319.8302896792_4bxf.jpg",
"shopId": "13350861",
"rating": 4.8,
"reviewCount": 470
}

Pricing

Two rates, and which one you pay depends on scrapeDetails:

EventPriceWhen
product-detail$15.00 / 1,000 ($0.015 each)A listing page was fetched and parsed — scrapeDetails: true.
listing-item$3.00 / 1,000 ($0.003 each)A search-result row only — scrapeDetails: false.

Charged on delivered rows only; failed pages cost nothing. The five-fold difference is real work: detail mode fetches one page per listing, listing mode gets ~12 rows from a single fetch.

Use listing mode to survey, detail mode to collect. A price-tracking job that already holds the IDs should pass itemIds and skip search entirely.

Proxies: read this before changing anything

Set this to Apify RESIDENTIAL and leave it there. It is not a style preference, and it is the single most common reason a run comes back empty.

DataDome decides which challenge to serve based on the exit IP. We measured this from Apify against twelve DataDome-protected sites, same code, minutes apart:

Exit IPChallenge servedResult
Datacenter (Apify default, or no proxy)The hard captcha3 of 12 sites passed
ResidentialInterstitial, or no challenge at all12 of 12 sites passed, 2–5s each

A datacenter address does not make Etsy slower. It changes the problem into a different, much harder one. Every attempt already pins a fresh residential session automatically, so a burnt exit node gets a genuinely new IP rather than a retry down the same dead pipe.

If a run fails wholesale, check the proxy group before anything else.

Run it from the API or CLI

Replace <TOKEN> with your Apify API token.

Run and get the results back in one call:

curl -X POST "https://api.apify.com/v2/acts/xhrdev~etsy-scraper/run-sync-get-dataset-items?token=<TOKEN>" \
-H "Content-Type: application/json" \
-d '{"queries":["leather wallet"],"scrapeDetails":true,"maxItems":20}'

Start a run without waiting:

curl -X POST "https://api.apify.com/v2/acts/xhrdev~etsy-scraper/runs?token=<TOKEN>" \
-H "Content-Type: application/json" \
-d '{"queries":["leather wallet"],"scrapeDetails":true,"maxItems":20}'

With the Apify CLI:

$apify call xhrdev/etsy-scraper --input '{"queries":["leather wallet"],"scrapeDetails":true,"maxItems":20}'

Fetching results

Every run writes to a dataset. Change format for JSON, CSV, or Excel:

# JSON
curl "https://api.apify.com/v2/datasets/<DATASET_ID>/items?token=<TOKEN>&clean=true&format=json"
# CSV
curl "https://api.apify.com/v2/datasets/<DATASET_ID>/items?token=<TOKEN>&clean=true&format=csv"
# Page through a large dataset
curl "https://api.apify.com/v2/datasets/<DATASET_ID>/items?token=<TOKEN>&offset=1000&limit=1000"

<DATASET_ID> comes back as defaultDatasetId on the run object.

Limits and failure behaviour

100 pages per run, hard. Not configurable. It is a guard against a typo in maxPagesPerQuery turning into a bill and a hammering of Etsy. For more than that, split the work across runs.

Failures never enter the dataset. A page that could not be fetched is not written as a half-empty row — that would corrupt the clean table you are paying for. Instead every run writes a RUN_SUMMARY record to the key-value store:

{
"site": "etsy",
"itemsScraped": 12,
"pagesFetched": 13,
"pagesFailed": 0,
"failures": [],
"solverHost": "https://trial.xhr.dev"
}

Read it at https://api.apify.com/v2/key-value-stores/<STORE_ID>/records/RUN_SUMMARY. failures holds up to 50 entries, each with the URL and why it failed.

A run that scrapes nothing and failed at least one page exits as failed, rather than reporting success over an empty dataset. If everything failed, the message points at the proxy group first, because that is nearly always the cause.

Pagination is never speculative. Page 2 is queued only after page 1 comes back and the site confirms how many pages exist, so you are not billed for fetching past the end of a short result set.

Spend cap. Set maxTotalChargeUsd on the run. The Actor stops fetching once it is reached, mid-run, rather than overshooting.

Questions

A run came back empty. Why? Check the proxy group first — a datacenter exit is the cause the overwhelming majority of the time. Then read RUN_SUMMARY in the key-value store for the per-page reasons.

Is this affiliated with Etsy? No. This is an independent tool with no affiliation with, endorsement by, or connection to Etsy or any bot-protection vendor. Names and trademarks belong to their owners.

How does it get past the challenge without a browser? It is solved as HTTP, by xhr.dev. No Chrome is launched, which is why a page costs a few hundred milliseconds of compute instead of the several seconds and ~1 GB of RAM a stealth browser needs.

Can I use the solver directly, on a site that isn't Etsy? Yes — that is DataDome Unblocker, which takes any URL and hands back the HTML and the clearance cookies. There is also Akamai Unblocker for Akamai Bot Manager.

Can I run this on my own infrastructure? Yes. The solver these Actors call is a self-hosted Docker container, sold on a flat fee with no per-request pricing. See xhr.dev.

Why is currency a $ symbol in listing mode but USD in detail mode? Because that is what each page provides. The search card renders a symbol; the listing page carries a proper ISO code in its structured data. Nothing is normalised or guessed.


Built by xhr.dev. Independent tool, not affiliated with Etsy or any bot-protection vendor. Scrape only what you are permitted to access, and check the site's terms before you run anything at scale.