# Etsy Listings Scraper & Price Tracker (`myth84/etsy-search-scraper`) Actor

Etsy listings scraper and price tracker: scrape Etsy search results for any keyword in USD, EUR or GBP — price, sale price and discount, rating and review count, free-shipping, Star Seller and sponsored flags, image. Track price changes between runs. No personal data.

- **URL**: https://apify.com/myth84/etsy-search-scraper.md
- **Developed by:** [Flatrow Data Studio](https://apify.com/myth84) (community)
- **Categories:** E-commerce, Automation, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.29 / 1,000 listings

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

### Etsy Listings Scraper & Price Tracker – prices and ratings, no personal data

Etsy listings scraper and Etsy price tracker: get Etsy product prices, sale prices, ratings and review counts for any common search term, in USD, GBP or EUR, as a clean table or through an API — no login. Use it as an Etsy scraper for niche and product research, pricing your own listings, price monitoring and trend watching.

**No login, no cookies, no personal data:** nothing to sign in to, no cookies to paste, no shop profiles and nothing about buyers in the output.

- **What you get:** one flat row per Etsy listing for any common search term or pasted Etsy URL: price and currency, sale price and discount, rating and review count, free-shipping, Star Seller and sponsored flags, image, position and link, in USD, GBP or EUR; a price filter and a price-monitoring mode that returns only what changed. No personal data.
- **For whom:** Etsy sellers researching a niche, price-tracking tools, content and curation sites, market analysts, AI agents through MCP.
- **How much:** from $2.00 per 1,000 listings on the Free plan, lower on paid plans; Apify's free monthly credit of $5 covers about 2,499 listings. A fixed price per listing saved and nothing else (compute and proxy traffic included, see the Pricing tab); `maxResults` caps every run.

**Sample output**: one real row from a "ceramic mug" search on etsy.com, key columns only (the full row is under *Output example*):

```json
{
  "platformProductId": "4296141840",
  "title": "Coffee Mug - KJ Pottery",
  "currency": "USD",
  "price": 57,
  "originalPrice": null,
  "discountPercent": null,
  "rating": 4.88,
  "reviewCount": 3500,
  "shippingFree": true,
  "isSponsored": true,
  "searchRank": 1
}
```

Scrape Etsy search results for any keyword and get **one flat row per listing**: price and currency, sale price and discount, shop rating and number of reviews, free-shipping, Star Seller and sponsored flags, main image and position in the results, in **USD (etsy.com), GBP (/uk/) or EUR (/fr/)**. This Etsy scraper reads the public *market page* Etsy publishes for the keywords (`etsy.com/market/ceramic_mug`), the same results the search box shows, without logging in: it keeps one browsing session per keyword, renders the listings the page defers exactly as a browser would, removes duplicates and stops at the number of listings you ask for. About 60 listings per page, 44 of them organic. Keep only a **price range**, paste your own **Etsy market or search URLs**, and **monitor prices**: in price-monitoring mode a scheduled run returns only the new listings and the price changes since your previous run.

**No personal data, by design.** An Etsy shop is very often a person: shop names, shop ids, seller ratings and anything about buyers are never collected, and the parts of the page that name shops are never read. The rows contain listing facts only (the stars shown on a card are kept as `rating` and `reviewCount`, without the shop's name). Every row goes through a privacy filter before it is saved. The dataset is safe to store, share and feed to AI agents.

### What can you do with Etsy data?

- **Track prices and sales** for a product niche: price, sale price and discount per listing, run daily and compared by listing id. With `monitorPrices` and `onlyChanges` on, a daily schedule gives you only the price drops, rises and new listings, and you pay only for those rows.
- **Research a niche before selling**: what Etsy ranks first for a keyword, how many listings are sponsored, the price distribution, the share of free shipping, the ratings and review counts of the competition.
- **Source gift ideas and trends** for content, curation or marketplaces, with titles, images and links ready to use.
- **Feed AI agents, spreadsheets and BI tools** with a stable, documented schema shared by the other scrapers of this e-commerce studio (AliExpress Scraper and eBay Scraper use the same columns), so listings from several marketplaces have the same columns.

### What data does Etsy Scraper extract?

Every row has every column: a value Etsy does not show on the card is `null` (or `[]` / `{}` for lists and objects), so CSV and Excel exports keep the same columns from run to run.

| Group | Columns |
|---|---|
| Identity | `schemaVersion`, `platform`, `platformProductId`, `variantId`, `url`, `title`, `brand`, `category`, `categoryPath` |
| Prices | `currency`, `price`, `originalPrice`, `discountPercent`, `inStock`, `stockQuantity` |
| Popularity | `rating`, `ratingMax`, `reviewCount`, `soldCount`, `isBestSeller`, `isChoice`, `listedAt` |
| Seller | `storeType`, `storeName`, `storeId`, `storeUrl`, `storeRating` |
| Shipping | `shippingFree`, `shippingCost`, `deliveryEstimate`, `shipsFrom` |
| Media and attributes | `imageUrl`, `imageCount`, `attributes` |
| Search context | `isSponsored`, `searchQuery`, `searchRank`, `searchPage`, `viewCountryCode`, `scrapedAt` |
| Price monitoring | `changeType`, `previousPrice`, `priceDelta`, `priceDeltaPercent` (`null` unless `monitorPrices` is on, see *Price monitoring* below) |

Market cards show a subset of what a listing page holds. For this Actor, `brand`, `category`, `categoryPath`, `variantId`, `stockQuantity`, `soldCount`, `shippingCost`, `deliveryEstimate`, `shipsFrom`, `isBestSeller`, `isChoice` and `listedAt` are always empty, and the seller columns stay empty by design (`storeType` is always `unknown`; `storeName`, `storeId`, `storeUrl`, `storeRating` are always `null`). `inStock` comes from the page's structured data and is filled for the first cards of each page only (about 7 of 60). `rating` and `reviewCount` are the stars Etsy prints on the card, which are the shop's rating and review count.

`attributes` holds the facts of the card as flat strings, only the keys the card shows: `starSeller` (`"true"` when the card carries the Star Seller badge).

### How to scrape Etsy listings

1. **Enter search keywords**, one per line ("ceramic mug", "leather wallet"), as you would type them in the Etsy search box. Etsy has a market page for common search terms; a term without one is skipped with a message in the log (try a shorter or more common wording).
2. **Choose the locale**: etsy.com (USD, English), `/uk/` (GBP, English) or `/fr/` (EUR, French), validated at build time; `/de/`, `/es/`, `/it/`, `/nl/`, `/ie/`, `/ca/` and `/au/` follow Etsy's URL convention and are to confirm at the first runs.
3. **Set the maximum number of listings per keyword** and, if needed, a price range (`priceMin`, `priceMax`, applied to the listings fetched), keep only free-shipping listings or skip the sponsored ones. You can also paste Etsy market or search URLs copied from your browser into `startUrls` (see *Price range and search URLs* below).
4. **Run** and open the **Output** tab: the *Overview*, *Prices*, *Ranking* and *Changes* views show the main columns; export everything as JSON, CSV, Excel or through the API.
5. **To follow prices**, schedule the run and turn on `monitorPrices` with `onlyChanges`: every run after the first returns only what changed (see *Price monitoring* below).

### Input

Give at least one keyword in `searchQueries` or one Etsy URL in `startUrls` (with nothing at all, "ceramic mug" is searched). Field names and defaults are those of the input form.

The input of our validation run: the first 60 listings of Etsy's market page for "ceramic mug" on etsy.com, prices in USD (every other field keeps its default):

```json
{
  "searchQueries": [
    "ceramic mug"
  ],
  "locale": "",
  "freeShippingOnly": false,
  "maxResults": 60
}
```

Every field, with its type and default:

| Field | Type | Default | Description |
|---|---|---|---|
| `searchQueries` | string\[] | `["ceramic mug"]` | Search keywords, one search per entry, as typed in the Etsy search box. Each one reads Etsy's market page for those words. Ignored when only `startUrls` are given. |
| `startUrls` | string\[] | `[]` | Etsy market pages (`https://www.etsy.com/market/ceramic_mug`) or search URLs (`https://www.etsy.com/search?q=ceramic+mug`) copied from your browser, with their locale prefix and free-shipping parameter; `order=` is removed and `min=` / `max=` become the price range applied to the listings fetched (the log says so). The options below are applied on top only when set. Any other Etsy URL stops the run with an explicit message. |
| `locale` | string | `""` | Etsy site section of the keyword searches: `""` (etsy.com, USD), `uk` (GBP), `fr` (EUR), validated; `de`, `es`, `it`, `nl`, `ie`, `ca`, `au` (to confirm at the first runs). Decides the currency and the language of the results. |
| `maxResults` | integer | `60` | Stop each keyword after this many unique listings (1 to 1,000); in price-monitoring mode, the number of listings compared per keyword. You are never charged for more. |
| `priceMin` | integer | — | Keep only listings priced at least this whole amount, in the currency of the locale. Applied by the Actor to the listings fetched (Etsy's search is not filtered); a listing without a price is left out. |
| `priceMax` | integer | — | Keep only listings priced at most this whole amount, in the currency of the locale. Applied by the Actor to the listings fetched (Etsy's search is not filtered); a listing without a price is left out. |
| `freeShippingOnly` | boolean | `false` | Etsy's free-shipping filter (validated). `shippingFree` tells what each card shows anyway. |
| `includeSponsored` | boolean | `true` | Keep the sponsored listings mixed into the results (flagged in `isSponsored`) or skip them. |
| `monitorPrices` | boolean | `false` | Compare every listing with the previous run of the same search (by listing id) and fill `changeType`, `previousPrice`, `priceDelta`, `priceDeltaPercent`. `maxResults` then counts the listings compared per search. |
| `onlyChanges` | boolean | `false` | With `monitorPrices`: return and charge only the new listings and the price changes; a run without any change succeeds with an empty dataset. Off: every compared listing with its `changeType`. |
| `monitorStoreName` | string | `"etsy-price-monitor"` | Key-value store of your account that keeps the previous prices (one record per search). One name per schedule when two schedules run the same search with different settings; letters, digits and dashes. |
| `proxyConfiguration` | object | `{"useApifyProxy":true,"apifyProxyGroups":["RESIDENTIAL"],"apifyProxyCountry":"US"}` | The default proxy configuration (Apify proxies, `RESIDENTIAL` group, United States) is the only one validated for Etsy and its traffic is included in the price: keep it. Your own proxy URLs of the same kind are accepted. |
| `maxRequests` | integer | `300` | Safety cap on requests in one run, all keywords included (two per result page); with a narrow price range, it bounds the pages read to find listings in the range (a search also stops after 3 consecutive pages without any listing in the range). |
| `delayMs` | integer | `1500` | Pause between two result pages of the same keyword, in milliseconds. |
| `debugStoreHtml` | boolean | `false` | Debugging only: store every fetched page and API answer in the key-value store. |

### Output example

One real row, produced by the Actor from a "ceramic mug" search on etsy.com (the first, sponsored card of the market page). All columns are present, in this order, for every listing:

```json
{
  "schemaVersion": "ecommerce-product/1.1",
  "platform": "etsy",
  "platformProductId": "4296141840",
  "variantId": null,
  "url": "https://www.etsy.com/listing/4296141840/coffee-mug-kj-pottery",
  "title": "Coffee Mug - KJ Pottery",
  "brand": null,
  "category": null,
  "categoryPath": [],
  "currency": "USD",
  "price": 57,
  "originalPrice": null,
  "discountPercent": null,
  "inStock": true,
  "stockQuantity": null,
  "rating": 4.88,
  "ratingMax": 5,
  "reviewCount": 3500,
  "soldCount": null,
  "storeType": "unknown",
  "storeName": null,
  "storeId": null,
  "storeUrl": null,
  "storeRating": null,
  "shippingFree": true,
  "shippingCost": null,
  "deliveryEstimate": null,
  "shipsFrom": null,
  "imageUrl": "https://i.etsystatic.com/7825028/r/il/553467/6863380541/il_600x600.6863380541_5poq.jpg",
  "imageCount": 1,
  "attributes": {},
  "isSponsored": true,
  "isBestSeller": null,
  "isChoice": null,
  "listedAt": null,
  "searchQuery": "ceramic mug",
  "searchRank": 1,
  "searchPage": 1,
  "viewCountryCode": "US",
  "scrapedAt": "2026-10-20T00:00:00.000Z",
  "changeType": null,
  "previousPrice": null,
  "priceDelta": null,
  "priceDeltaPercent": null
}
```

`price` is the price displayed on the card (the sale price when the listing is on sale), shipping not included; `originalPrice` is the strike-through price when the listing is on sale and `discountPercent` the percentage off Etsy prints next to it ("(12% off)"), or the one computed from both prices. `reviewCount` is rounded when Etsy rounds ("(3.5k)" gives 3500). The last four columns are filled in price-monitoring runs only.

The **Output** tab offers four views of the same rows: **Overview** (title, price, currency, original price, discount, rating, reviews, free shipping, sponsored, link, image), **Prices** (listing id, price, original price, discount, currency, in stock, free shipping, attributes, link), **Ranking** (query, page, rank, sponsored flag, rating, reviews, price, currency) and **Changes** (price-monitoring runs: change type, price, previous price, difference, query, link).

### Price range and search URLs

`priceMin` and `priceMax` keep only the listings priced within the range, bounds included, in whole amounts of the locale's currency (10 to 50 means 10 to 50 USD on etsy.com, EUR on `/fr/`). The filter **applies to the listings fetched, not to Etsy's search**: the Actor reads the market pages in Etsy's default order, as without a range, and keeps only the listings in the range. A listing without a price, or shown in another currency than the locale's (see *Limits* below), is left out as soon as a bound is set. The listings left out are neither saved nor charged and do not count against `maxResults`, so a narrow range makes the Actor read more pages for the same number of listings: it stops at `maxResults` listings in the range, at the last page, after **3 consecutive pages without any listing in the range** (a range almost nothing falls in is not scanned page after page) or at `maxRequests`, and the end of the run log says when a search stopped before finding enough listings in the range (`RUN_STATS.extra.priceFiltered` counts the listings left out, `priceFilterStopped` and `priceFilterCapped` the searches stopped by those two rules). Results always come in Etsy's default order: no sort option is offered, sort the dataset by `price` instead.

`startUrls` takes Etsy URLs copied from your browser, one per line: market pages (`https://www.etsy.com/market/ceramic_mug`, `https://www.etsy.com/fr/market/tasse`) and search URLs (`https://www.etsy.com/search?q=ceramic+mug&free_shipping=1`). Each URL is read as the market page for its words, in the locale of its prefix (the `locale` field applies to keywords only), with its free-shipping parameter. A sort order (`order=`) in the URL is removed and Etsy's default order is kept; a price range (`min=`, `max=`) is removed from the URL and becomes the price range the Actor applies to the listings fetched, unless you set `priceMin` / `priceMax`; the run log warns about every parameter removed. `priceMin`, `priceMax` and `freeShippingOnly` are applied on top when you set them. The search words of the URL fill `searchQuery`. A URL that is not a market page nor a search URL (a listing, a shop, a category page, another site, an unknown locale prefix) stops the run before any request, with a message that names the URL. A search URL is never requested as such: the Actor always reads the market page for its words, so a search with no market page is reported like a keyword without one.

### Price monitoring

Turn on `monitorPrices` and `onlyChanges`, then **schedule the run** (daily is typical). The scraper keeps the last price of every listing it compared in a key-value store of your account (`monitorStoreName`, `etsy-price-monitor` by default), with one record per search (same keywords or URL, locale, price range and free-shipping filter). The first run of a search is the reference: every listing comes back with `changeType` = `"new"` (and `RUN_STATS.monitorBaseline` is true). From the second run on, with `onlyChanges` the dataset holds **only the new listings and the listings whose price changed**, with the previous price and the difference, and **only those rows are charged**; a run where nothing changed succeeds with an empty dataset. Without `onlyChanges`, every compared listing is returned with its `changeType` (`new`, `price_changed` or `unchanged`). In both cases `maxResults` is the number of listings compared per search, and `RUN_STATS.monitor` sums up the run (listings compared, new, price changes, records written and kept).

A scheduled input that watches the first 120 ceramic mugs of etsy.com:

```json
{
  "searchQueries": ["ceramic mug"],
  "locale": "",
  "maxResults": 120,
  "monitorPrices": true,
  "onlyChanges": true,
  "monitorStoreName": "mugs-daily"
}
```

A `price_changed` row, as the Actor produces it from the row of the output example when the previous run had recorded 57 USD and the listing now costs 49 USD (the other columns are the same as above):

```json
{
  "platformProductId": "4296141840",
  "title": "Coffee Mug - KJ Pottery",
  "currency": "USD",
  "price": 49,
  "changeType": "price_changed",
  "previousPrice": 57,
  "priceDelta": -8,
  "priceDeltaPercent": -14
}
```

What to know before relying on it:

- **The record is written only after a search that ended cleanly.** A blocked, failed or partial search (the deferred listings could not be rendered), a run stopped by its budget or an interrupted run never overwrites the reference, so a bad day cannot erase your prices; the log says when a record was kept unchanged.
- **Same currency only.** A price is compared only with a previous price in the same currency. On a locale-prefixed page Etsy may show the first cards in USD and the others in the locale's currency (see *Limits* below): such a listing is reported `unchanged` until both runs show it in the same currency, and the latest currency becomes the reference.
- **Sale prices are compared as displayed.** `price` is the sale price when the listing is on sale: a listing going on sale, or a sale ending, shows up as a price change (`originalPrice` and `discountPercent` tell why).
- **Rankings move.** Etsy reshuffles results between sessions, so the set of compared listings (the first `maxResults` of each search) drifts a little from run to run; a listing that drops out of the results and comes back later is not reported as new (its last price is kept for 90 days).
- **One store per schedule when searches collide.** Runs sharing a `monitorStoreName` share the records: two schedules with the same keywords, locale and filters would overwrite each other's reference, so give them different store names. Changing the keywords, locale or filters of a search starts a new reference for it. Delete the key-value store in Apify Console to start over.

### How much does it cost to scrape Etsy?

This Actor uses **pay-per-event pricing: you pay a fixed price per listing** saved to your dataset, and nothing else. On the Free plan, listings cost $2.00 per 1,000, so Apify's free monthly credit of $5 covers about 2,499 listings; paid plans pay less per listing, and the price of every plan tier is on the **Pricing tab** of this page.

| What you pay for | How it is counted | Price |
|---|---|---|
| `result` event: one listing saved to your dataset | once per unique listing of the run, capped by `maxResults` | Pricing tab, per plan tier |
| Run start | a symbolic amount, once per run | Pricing tab |
| Compute, proxies, storage and data transfer | included: no platform usage is passed on to you | — |

The cost of a run is known before you start: 1,000 listings cost 1,000 times the `result` price of your tier, whatever the number of pages fetched, and in price-monitoring runs with `onlyChanges` only the new listings and the price changes are charged. A page takes about 4 seconds (two requests and a pause); a run of a few hundred listings takes under a minute.

### Limits and behaviour worth knowing

- **Market pages only.** The Actor reads the market page Etsy publishes for a search term, not the `/search` results (which Etsy's robots.txt reserves to browsers) nor listing pages: item details, variations, stock, shipping cost, ships-from country, shop pages and reviews are not available (see the column notes above). Etsy has a market page for common search terms only (8 of the 12 terms tried at build time): a term without one is reported as *no Etsy market page for this term* and skipped, and a run where every term is missing ends as failed with the list of terms. A term Etsy redirects to a category page (`/c/…`, e.g. "personalized gifts") yields that single page of about 30 listings.
- **Etsy's default order; the price range filters what is fetched.** The free-shipping filter is Etsy's and was validated at build time. Etsy's robots.txt reserves its sort and price parameters to browsers, so the Actor never sends them: results keep Etsy's default order, and the price range is applied by the Actor to the listings it fetches (a narrow range reads more pages per listing kept, up to `maxRequests`, and a search stops after 3 consecutive pages without any listing in the range; only the listings kept are charged).
- **About 60 listings per page, 16 of them sponsored.** Each market page renders 8 listings and defers 36 organic and 16 sponsored ones, which the Actor renders through the page's own API call in the same session (`RUN_STATS.extra.apiCalls`). Sponsored listings ("Ad by Etsy seller") are flagged in `isSponsored` and counted in `RUN_STATS.extra.sponsoredSeen`; `includeSponsored: false` skips them while keeping their positions in `searchRank`.
- **Rankings move and sponsored cards repeat.** Etsy reshuffles results between sessions and repeats sponsored cards from one page to the next. The Actor keeps one session per keyword (same proxy session, same cookies), removes duplicates by listing id across the whole run (`RUN_STATS.extra.duplicates`) and stops at the last page, when two consecutive pages bring nothing new, or at `maxResults` / `maxRequests`. `searchRank` counts 60 positions per page, sponsored cards included. The usable depth of a market page is not known yet (links up to page 5 were seen; `maxResults` is capped at 1,000).
- **Currency follows the displayed symbol.** On the `/fr/` capture, the 4 cards Etsy renders on the page were still shown in dollars while the deferred listings were in euros: `currency` is read from each card's symbol, so check that column when you mix locales; with a price range, such cards are left out (their price cannot be compared with bounds in the locale's currency). On `/ca/` and `/au/` a bare `$` is read as CAD / AUD. The `/de/`, `/es/`, `/it/`, `/nl/`, `/ie/`, `/ca/` and `/au/` prefixes were not exercised at build time (marked *to confirm* in the input form).
- **Blocking.** If a page is unusable (challenge page, 403, page without results) or the call that renders the deferred listings fails (`RUN_STATS.extra.apiFailures`), the Actor opens a new session and retries the page once (`RUN_STATS.extra.sessionRotations`); then it gives up on that keyword (page) or keeps the 8 rendered listings (API) and reports it in the log. Terms without market page are counted in `RUN_STATS.extra.termsWithoutMarketPage`, redirected ones in `RUN_STATS.extra.categoryHubs`. If a whole run is blocked, keep the default proxy configuration, raise `delayMs` and try again later; persistent blocking is fixed as a priority, open an issue with your run id.
- **No empty successes.** A run that finds no listing ends as failed with an explicit message (no market page for the terms, blocked, or a proxy configuration error), so that your schedules and integrations notice. The one exception is a price-monitoring run that compared listings and found no change: it succeeds with an empty dataset.

### Integrations and API

Start the Actor from Apify Console, schedule it, or call it from the [Apify API](https://docs.apify.com/api/v2) with any language (the **API** tab shows ready-made code for Node.js, Python and curl). Webhooks notify your systems when a run finishes; the dataset can be pulled as JSON, CSV, Excel, XML or RSS, or pushed to Google Sheets, Make, Zapier, n8n, Slack, Airtable, Google Drive and more through the Apify integrations.

### Use it from your AI assistant

Add this Actor as a tool to Claude, ChatGPT, Cursor, VS Code or any MCP client with the Apify MCP server URL below (sign in with your Apify account when asked):

`https://mcp.apify.com?tools=myth84/etsy-search-scraper`

Then ask in plain words. Copy this prompt and change the details:

```text
Use myth84/etsy-search-scraper to list 20 leather wallets on Etsy UK with prices in pounds, and tell me which ones are on sale. Input: {"searchQueries": ["leather wallet"], "locale": "uk", "maxResults": 20}. Show the title, price, sale price, discount, rating, number of reviews and link of each listing.
```

### For AI agents (MCP)

Through the [Apify MCP server](https://docs.apify.com/mcp) this Actor is a tool that answers questions such as *"what do handmade ceramic mugs cost on Etsy, and which ones are on sale?"* or *"find leather wallets on Etsy France with prices in euros and free shipping"*.

**Minimal input** (every other field keeps its default; raise `maxResults` only when the question needs more rows):

```json
{
  "searchQueries": [
    "leather wallet"
  ],
  "locale": "uk",
  "maxResults": 20
}
```

- **Key output fields**: `platformProductId`, `title`, `url`, `price`, `originalPrice`, `discountPercent`, `currency`, `rating`, `reviewCount`, `shippingFree`, `isSponsored`, `attributes.starSeller`, `imageUrl`, `searchRank`; in monitoring mode `changeType`, `previousPrice`, `priceDelta`. Every column is always present, in a fixed order; `null` means Etsy did not show the value. The full list is in *What data does Etsy Scraper extract?* above.
- **Use it when** you need current Etsy search results for a keyword: prices, sale prices and discounts, ratings and review counts, free-shipping and sponsored flags, images, links and positions. Pass `searchQueries` (common search terms, e.g. "ceramic mug") or `startUrls` (Etsy market or search URLs), optionally `locale` (`""`, `uk`, `fr`), `priceMin` / `priceMax` (applied to the listings fetched) and `freeShippingOnly`, and a small `maxResults` (20–60 is enough for an answer and fits in one page). To report price changes between two calls, pass `monitorPrices: true`, `onlyChanges: true` and a stable `monitorStoreName`.
- **Do not use it for** listing pages, variations, stock, shipping costs, shop pages, reviews, or anything behind a login: it only reads public market pages and collects no personal data. A term without market page gives nothing (the log says so): prefer common, short search terms.
- **Cost per 1,000 results**: 1,000 `result` events, $2.00 per 1,000 on the Free plan and less on paid plans (Pricing tab); nothing else is charged (in price-monitoring runs with `onlyChanges`, unchanged prices are free).
- **Typical time**: about 4 seconds per page of 60 listings: 20 listings in a few seconds, a few hundred in under a minute.

### Alternatives and switching

- **Official API.** Etsy's official Open API needs a registered app and an API key. This Actor reads what any visitor sees in Etsy search results (public prices, sale prices, ratings, review counts) with no account, no keys and no approval.
- **Your own scraper.** Nothing to build or maintain: sessions, retries, pagination, deduplication and Etsy page changes are handled for you. The columns stay the same from run to run, a run that finds nothing fails with an explicit message instead of succeeding empty, and a scheduled test run catches Etsy changes early.
- **Another scraper from the Store.** The input names differ from one Actor to the next; the usual ones map to this Actor as follows:

| Usual input | Here | Note |
|---|---|---|
| `search`, `query`, `keyword` | `searchQueries` | Common search terms, several per run |
| `startUrls` | `startUrls` | Etsy market or search URLs |
| `maxItems`, `limit` | `maxResults` | Unique listings per search, never charged beyond |
| `country`, `currency` | `locale` | `""` (USD), `uk` (GBP), `fr` (EUR)… |
| `minPrice`, `maxPrice` | `priceMin`, `priceMax` | In the locale currency |
| `freeShipping` | `freeShippingOnly` | Keep only free-shipping listings |
| `proxy` | `proxyConfiguration` | Keep the default: proxies are included in the price |

### FAQ

#### Does Etsy Scraper collect personal data?

No. Shop names, shop ids, seller ratings, buyers and reviewers are never collected, and the parts of the page that name shops are never read. The rows contain listing facts only: prices, discounts, the stars printed on the card, free-shipping, Star Seller and sponsored flags, images, links and positions. Every row goes through a privacy filter before it is saved.

#### Is it legal to scrape Etsy?

The Actor only reads market pages that Etsy serves to any visitor without an account and stores no personal data; it never reads the search page, which Etsy's robots.txt reserves to browsers (a pasted search URL is converted to the market page for its words). Nor does it send the sort and price parameters the same file reserves to browsers: results keep Etsy's default order and the price range is applied by the Actor to the listings it fetches. Scraping public data is generally lawful, but Etsy's terms of use restrict automated access, and you are responsible for how you use the results and for complying with the laws that apply to you (including copyright on listing texts and images). Use the data for analysis, not to republish listings.

#### Do I need proxies, and do I pay for them?

No extra setup: the default proxy configuration (United States) is the only one validated for Etsy, and its traffic is included in the price per listing: you pay per listing, nothing more. The locale decides the currency and the language, not the proxy country. Your own proxy URLs of the same kind are accepted too.

#### Which keywords work?

Etsy publishes a market page for common search terms ("ceramic mug", "leather wallet", "christmas ornaments", "custom stickers", "handmade necklace", "iphone case"…), the pages Google indexes. Long or unusual phrases often have none: the Actor then reports *no Etsy market page for this term* and skips it. Prefer short, generic terms, singular or plural as Etsy writes them, and check the log of your first run.

#### Which currencies and languages are supported?

Three validated locales: etsy.com (USD, English), `/uk/` (GBP, English) and `/fr/` (EUR, French). `/de/`, `/es/`, `/it/`, `/nl/` (EUR), `/ie/` (EUR), `/ca/` (CAD) and `/au/` (AUD) follow the same URL convention and are confirmed at the first runs. The currency of each row is read from the card itself (`currency`): the first cards of a locale-prefixed page may still be shown in USD by Etsy.

#### Can I set a price range, sort the results or use my own Etsy URL?

`priceMin` and `priceMax` keep the listings priced within the range, in whole amounts of the locale's currency. The price filter applies to the results fetched, not to Etsy's search: the Actor reads the pages in Etsy's order and keeps the listings in the range, reading more pages when few of them fall in it (up to `maxRequests`, and never more than 3 pages in a row without any listing in the range); you pay only for the listings kept. Sorting is not offered: results come in Etsy's default order, sort the dataset by `price` instead. Paste market pages or search URLs copied from your browser into `startUrls`: their locale and free-shipping parameter are read, `order=` is removed, and `min=` / `max=` become the price range unless you set `priceMin` / `priceMax` (the run log says so); `searchQueries` can stay empty. Listing, shop and category URLs are refused with an explicit message.

#### What do `price`, `originalPrice` and `discountPercent` mean?

`price` is the amount displayed on the card, shipping excluded: the sale price when the listing is on sale. `originalPrice` is the strike-through price shown next to it, and `discountPercent` the percentage off Etsy prints ("(12% off)", "(58 % de réduction)"), or the one computed from both prices when the text is missing.

#### What are sponsored listings, and why do I get fewer listings than expected?

Etsy mixes 16 sponsored ("Ad by Etsy seller") listings into each page of 60 and repeats some of them on the next pages; they are flagged in `isSponsored` and can be skipped with `includeSponsored`, their positions being kept in `searchRank` either way. Sponsored cards repeat from page to page and Etsy's ranking changes between sessions: the Actor removes duplicates within a run by listing id, which is why a page may yield fewer new listings than it holds, and stops when two pages bring nothing new or when the page has no next link. Across separate runs, use `platformProductId` to deduplicate.

#### How often should I run it, and how do I get only price changes?

Every run fetches live market pages. Prices, sales and rankings change by the day, so schedule the Actor as often as your use case needs (daily is typical for price tracking) and compare runs by `platformProductId`, `locale` and `currency`, or let the Actor compare them for you: turn on `monitorPrices` and `onlyChanges`, keep the default `monitorStoreName` (or one per schedule, for example `mugs-daily`) and schedule the run. The first run records every listing of the search and returns it as `new`; each later run returns only the listings that appeared since and the listings whose price changed, with `previousPrice`, `priceDelta` and `priceDeltaPercent`, and charges only those rows. A run without any change succeeds with an empty dataset. Keep the same keywords, locale and filters from run to run. Turn `onlyChanges` off to get every listing with its `changeType`.

#### How do I export or integrate the results, and where do I get support?

From the run's Output tab (JSON, CSV, Excel, XML, RSS) or automatically: Apify API, webhooks, scheduled runs and integrations with Google Sheets, Make, Zapier, n8n, Slack and others; AI agents can call the Actor through the Apify MCP server. For bugs or questions, open an issue in the **Issues** tab of this Actor with your run id and input: page-layout changes and blocking are fixed as a priority, feature requests (listing pages, other locales, Star Seller and instant-download filters) are welcome.

### More scrapers by Flatrow Data Studio

The e-commerce scrapers of the studio share the same columns, so products from several marketplaces land in one comparable table:

- [AliExpress Products Scraper & Price Tracker](https://apify.com/myth84/aliexpress-search-scraper): AliExpress prices, discounts, units sold and shipping for any keywords and delivery country, with price-drop alerts.
- [eBay Listings Scraper & Price Tracker](https://apify.com/myth84/ebay-search-scraper): eBay prices, condition, shipping and units sold on 9 marketplaces, with price monitoring.

Did this scraper give you the data you needed? A rating on its Store page helps other people find it. For a bug or a missing field, open an issue in the **Issues** tab.

### Legal note

This Actor reads publicly available Etsy market pages without logging in and collects no personal data. You are responsible for your use of the data and for complying with Etsy's terms and the laws that apply to you.

Not affiliated with, endorsed or sponsored by Etsy. Etsy is a trademark of Etsy, Inc.

# Changelog

This Actor's version history is a separate document: https://apify.com/myth84/etsy-search-scraper/changelog.md

# Actor input Schema

## `searchQueries` (type: `array`):

Enter one search per line, as you would type it in the Etsy search box, e.g. <code>ceramic mug</code> or <code>leather wallet</code>. Each search reads Etsy's market page for those words (<code>etsy.com/market/ceramic_mug</code>), up to the maximum number of listings below; a term without a market page is skipped with a message in the log. Default <code>ceramic mug</code>: it only serves runs started without any keyword or URL; with URLs alone, only the URLs are read.

## `startUrls` (type: `array`):

Etsy market pages or search URLs from your browser, one per line, e.g. <code>https://www.etsy.com/market/ceramic_mug</code> or <code>https://www.etsy.com/search?q=ceramic+mug</code>. Each is read as the market page for its words, in the URL's locale, with its free-shipping parameter; <code>order=</code> is removed and <code>min=</code> / <code>max=</code> become the price range (see the README). Any other URL stops the run with a message. Default empty: keywords are enough for most searches.

## `locale` (type: `string`):

Etsy site section the listings are read from: it decides the currency and the language of the results. etsy.com (USD), /uk/ (GBP) and /fr/ (EUR) were validated at build time; the other prefixes follow Etsy's URL convention and will be confirmed at the first runs. Default etsy.com (empty value, no prefix): prices in US dollars, in English.

## `maxResults` (type: `integer`):

Stop each search after this many unique listings, from 1 to 1,000; you are never charged for more. A market page yields about 60 listings (44 organic and 16 sponsored). Default 60: about one market page per search; the form is prefilled with 20 for a quick first run.

## `priceMin` (type: `integer`):

Keep only listings priced at least this amount, in the currency of the locale (whole amount), e.g. <code>10</code> for 10 USD on etsy.com. The Actor applies it to the listings it fetches (Etsy's search itself is not filtered): a listing without a price is left out, maxResults counts the listings kept and Max requests bounds the pages read. Leave empty for no minimum.

## `priceMax` (type: `integer`):

Keep only listings priced at most this amount, in the currency of the locale (whole amount), e.g. <code>50</code> for 50 USD on etsy.com; it must be at least Min price. The Actor applies it to the listings it fetches (Etsy's search itself is not filtered): a listing without a price is left out, maxResults counts the listings kept and Max requests bounds the pages read. Leave empty for no maximum.

## `freeShippingOnly` (type: `boolean`):

Keep only listings with free shipping (Etsy's own filter, validated at build time). The free-shipping flag of each card is always in the <code>shippingFree</code> column. Default off: every listing, whatever its shipping cost.

## `includeSponsored` (type: `boolean`):

Etsy mixes 16 sponsored listings ("Ad by Etsy seller") into each page of 60. Keep them (they are flagged in the isSponsored column) or skip them. Default on: sponsored listings are real offers shown in Etsy's results.

## `monitorPrices` (type: `boolean`):

Off (default): every listing is returned and the four monitoring columns stay null. On: every listing is compared with the previous run of the same search, by listing id, and maxResults becomes the number of listings compared per search.

## `onlyChanges` (type: `boolean`):

With price monitoring on: return and charge only the new listings and the listings whose price changed since the previous run; a run without any change succeeds with an empty dataset. Default off: every compared listing is returned with its changeType (new, price_changed, unchanged). Turn it on for scheduled runs.

## `monitorStoreName` (type: `string`):

Name of the key-value store, in your account, that keeps the previous prices (one record per search), e.g. <code>mugs-daily</code>. Runs sharing a name share the records; use one name per schedule when two schedules run the same search with different settings. Letters, digits and dashes only, at most 63 characters; upper case is lowered. Default etsy-price-monitor: one store for all your monitoring runs.

## `proxyConfiguration` (type: `object`):

Apify residential proxies with a US exit (default, required for Etsy). The locale above decides the currency and language, not the proxy country. You can also provide your own residential proxy URLs.

## `maxRequests` (type: `integer`):

Safety cap on the number of requests in one run, all searches included, from 1 to 5,000: each result page costs two (the page and the call that renders its deferred listings). With a narrow price range it also bounds the pages read to find the listings in the range. Default 300: about 150 result pages, far more than a search needs (1,000 listings take about 17 pages).

## `delayMs` (type: `integer`):

Pause between two result pages of the same search, in milliseconds, from 0 to 30,000. Increase it if you get blocked; 0 disables the pause. Default 1500 (1.5 s): the pace of the validation runs.

## `debugStoreHtml` (type: `boolean`):

Debugging only: save every fetched market page and API answer in the run's key-value store. Slows the run down and uses storage. Default off: only useful to investigate a parsing problem.

## Actor input object example

```json
{
  "searchQueries": [
    "ceramic mug"
  ],
  "startUrls": [],
  "locale": "",
  "maxResults": 20,
  "freeShippingOnly": false,
  "includeSponsored": true,
  "monitorPrices": false,
  "onlyChanges": false,
  "monitorStoreName": "etsy-price-monitor",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  },
  "maxRequests": 300,
  "delayMs": 1500,
  "debugStoreHtml": false
}
```

# Actor output Schema

## `products` (type: `string`):

All extracted listings as dataset items, one flat row per listing. Append ?format=csv or ?format=xlsx for spreadsheets. The Overview view is selected by default; Prices and Ranking views are also available.

## `runStats` (type: `string`):

Requests made (pages and API calls), bytes transferred, blocked pages, listings seen, listings pushed, duplicates, sponsored cards, terms without market page, category pages, session rotations and the estimated platform cost of the run (residential proxy traffic included).

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searchQueries": [
        "ceramic mug"
    ],
    "maxResults": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("myth84/etsy-search-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "searchQueries": ["ceramic mug"],
    "maxResults": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("myth84/etsy-search-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searchQueries": [
    "ceramic mug"
  ],
  "maxResults": 20
}' |
apify call myth84/etsy-search-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,myth84/etsy-search-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/LBL4xVDYcQDWSQDfO/builds/a0mxc65p9XaRaHYMO/openapi.json
