# Etsy Scraper: Listings, Prices, Ads, Reviews (`automation_craft/etsy-scraper`) Actor

Scrape Etsy phrases, categories, shops, listing links or ids in 29 Etsy markets, no login. Get price, original price, discount, free shipping, shop rating, ad slot and organic rank, plus optional listing pages, reviews and shops. JSON, CSV or Excel via API or schedule; pay per listing.

- **URL**: https://apify.com/automation_craft/etsy-scraper.md
- **Developed by:** [Automation Craft](https://apify.com/automation_craft) (community)
- **Categories:** E-commerce, Marketing, Automation
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.85 / 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 Scraper: Listings, Prices, Ads, Reviews

**Etsy scraper** for search phrases, categories, shops, listing links and listing ids: every Etsy listing comes as a clean row with
**price, original price, discount, free shipping, the shop's rating and review count, shop id, its organic rank and its ad slot** on the page, in **29
Etsy markets** with **30 display currencies** and **13 Etsy languages**. On request each listing's own page adds the description,
gallery and video, variations with option prices, stock, materials, who made it, favourites, listed date, the in carts, bought and viewed
in 24 hours line, shipping cost and delivery estimate, and shop sales; buyer reviews and shop profiles come as rows of their own. No
login and no API key. You pay per listing delivered; repeats, filtered listings and misses are free.

Use it for Etsy product and keyword research, to compare competitors' prices, to see which listings buy ad slots on a
phrase and where the organic results rank, or to export a category, a shop or a set of listings to Excel.

### Quick start

1. Open the Actor's **Input** tab.
2. Type **Search phrases** as a buyer types them (leather wallet, personalized necklace), paste **Etsy links** (a `/market/` phrase page,
   a `/c/` category page, a `/listing/` or a `/shop/` link), give **Listing links or ids** (1879178124), name **Shops**
   (CaitlynMinimalist) or give **Shop ids** (35055979).
3. Pick the **Country (Etsy market)** (US by default), and optionally a **Display currency** and a **Title language**.
4. Optionally set filters: price range, on sale, free shipping, minimum shop rating or shop reviews, digital or physical, words in the title, ads on
   or off.
5. Set **Maximum listings** and turn on the add-ons you want: **Listing details**, **Reviews**, **Every listing of a shop**.
6. Click **Start**. Download the results as JSON, CSV or Excel, or read them through the Apify API.

### What you get

#### Listing rows (the Listing event)

One row per Etsy listing (`type: listing`), once per run, whichever phrase, category, shop or id found it first (or once per target
with **Each listing once per run** off). The fill column is
measured on 8,408 listing rows of the platform runs of 2026-10-29 (the matrix of this Actor and the matrix and cost runs of its test
copy on the same code).

| Field | What it is | Fill |
|---|---|---|
| `listingId` | Etsy's listing id | 100% |
| `title` | title as Etsy shows it | 100% |
| `url` | listing link | 100% |
| `price` | price a buyer pays now | 100% |
| `currency` | currency of the prices | 100% |
| `originalPrice` | the struck price beside a sale price | 56% |
| `discountPercent` | discount, percent | 58% |
| `onSale` | on sale | 100% |
| `freeShipping` | free shipping | 100% |
| `shopRating` | the shop's star rating (Etsy's card shows the shop's) | 50% |
| `shopReviewCount` | the shop's review count (rounded above 1,000 on the card) | 50% |
| `shopId` | the shop's id | 100% |
| `shopName` | the shop's name (cards that show it, every detailed row) | 4% |
| `shopUrl` | shop link | 4% |
| `isAd` | shown as an ad on the page | 100% |
| `adSlot` | ad slot on the page | 25% |
| `organicRank` | rank among the organic results | 58% |
| `pagePosition` | position on the page, ads included | 88% |
| `badges` | Etsy's card badges | 20% |
| `bestseller` | Bestseller badge | 100% |
| `stockLeft` | "Only N left" on the card | 2% |
| `image` | main image | 100% |
| `titleMatchesSearch` | title holds the phrase's words | 71% |

The row also says where it was found: `source` (search, category, shop, rss or listing), `searchKey` (the target: `m:<phrase>`,
`c:<category path>`, `s:<shop>` or `ids`), `searchPhrase` (also for a phrase Etsy sends to a category page), `categoryPath` or
`searchShop`, `page`, `organicRank` (the organic place: on a phrase page Etsy's own order of 40 organic results a page; on a category,
the organic results of the pages read before it, null when a category walk starts past its page 1), `shopRank` (the place in the
shop's own order), `adSlot` (the paid slot on that page),
`pagePosition` (the place on the page as a shopper sees it, ads included), `isAd`, and `titleMatchesSearch` (whether the title holds
every word of the phrase). A listing shown both as an ad and as an organic result is one row with both positions.

#### Listing details (the Listing details event, optional)

With **Listing details** on, each delivered listing's own page is read and merged into its row (`detailStatus: ok`; otherwise it says
why not: `not_found`, `blocked`, `exits_spent`, `not_read`, `allowance`, `charge_limit` or `stopped`); the fill column
is measured on 412 detailed rows of the same runs:

| Field | What it is | Fill |
|---|---|---|
| `description` | full description | 100% |
| `images` | image gallery | 100% |
| `imageCount` | number of images | 100% |
| `videoUrl` | listing video | 58% |
| `variations` | variations with option prices: each option's text as Etsy shows it (`value`), its price when the text ends in one (`priceText`) and `soldOut` | 64% |
| `quantity` | quantity Etsy offers | 100% |
| `availability` | in stock or not | 100% |
| `categoryPath` | category path | 100% |
| `materials` | materials | 79% |
| `highlights` | item details lines | 95% |
| `madeBy` | "Made by" line | 78% |
| `handmade` | handmade: true when the item details say "Made by" the shop | 100% |
| `vintage` | vintage: true when the item details say "Handpicked by" the shop | 100% |
| `digitalDownload` | digital download | 100% |
| `personalizable` | personalizable | 100% |
| `favorites` | favourites | 79% |
| `listedOn` | listed on date | 85% |
| `listingRating` | the listing's own rating | 97% |
| `listingReviewCount` | the listing's own reviews | 82% |
| `reviewTags` | review topic tags with counts | 62% |
| `sampleReviews` | reviews shown on the page | 82% |
| `inCarts` | in carts now | 47% |
| `boughtLast24h` | bought in 24 hours (Etsy shows one urgency line at a time: 13 of 634 listing page reads carried this one) | 2% |
| `viewsLast24h` | views in 24 hours | 19% |
| `scarcityText` | Etsy's urgency line | 88% |
| `estimatedDelivery` | delivery estimate | 60% |
| `shippingCost` | shipping cost | 27% |
| `priceWithShipping` | price with shipping | 27% |
| `shipsFromCountry` | ships from | 100% |
| `returnsPolicy` | returns and exchanges | 90% |
| `giftWrapping` | gift wrapping | 100% |
| `productionPartners` | production partners | 13% |
| `itemAttributes` | attributes (colour, occasion, style) | 30% |
| `relatedSearches` | Etsy's related searches | 93% |
| `faqs` | the shop's FAQ | 29% |
| `shopSales` | shop's sales | 100% |
| `shopReviewCount` | shop's reviews | 97% |
| `shopLocation` | shop's location | 98% |
| `shopTenure` | years on Etsy | 98% |
| `starSeller` | Star Seller | 100% |
| `etsysPick` | Etsy's Pick | 100% |
| `sellerBadges` | the shop's badges | 84% |

Filters that only the listing page answers need Listing details: handmade, vintage, Star Seller, bestseller, Etsy's Pick and
personalizable only, ships from countries, minimum favourites, listed after or before, category contains, and made by kinds (made,
designed, handpicked or sourced by). Handmade only keeps the listings Etsy marks "Made by" the shop; a listing marked "Designed by"
the shop (made with a production partner) is left out, and made by kinds `made by` plus `designed by` keeps both. Vintage only
keeps the listings Etsy marks "Handpicked by" the shop. A listing a filter drops is free; a page that cannot be read is free and
the row comes without details.

#### Review rows (the Review event, optional)

With **Reviews** on, each delivered listing's buyer reviews come as `type: review` rows (8 per Etsy call, 8 per listing by default, up to
2,400): `reviewId`, `listingId`, `shopId`, `scope`, `rating`, `date`, `dateText`, `recommended`, `text`, `textTranslated`, `photo`,
`buyerName`, `listingTitle`, `sellerResponse`. Pick Etsy's own order (relevance, newest, highest, lowest), one or several star ratings
(read one rating after the other under one cap) and a topic
(Quality, Shipping & Packaging ...); **Reviews with a photo only** is filtered here and the rest is free. With **Whole shop reviews** and
a shop you named, the shop's reviews across all its listings come as well (100 by default).

#### Shop rows and shop listings (the Shop page event, optional)

A shop you name (or give by its id: the run reads its name from the shop's reviews first, and a shop id without reviews gets a free
`shop_not_resolved` row) gives its latest 10 listings from its RSS feed, charged as Listings. With **Every listing of a shop** on, the
shop's own pages are the shop's rows instead, read through Apify's own unblocker: every listing in the shop's order (about 40 a page,
`shopRank`), and on its first page a
free `type: shop` row with `shopId`, `shopName`, `url`, `headline`, `location`, `starSeller`, `rating`, `reviewCount`, `sales`,
`admirers`, `onEtsySince`, `latestActivity`, `activeListings`, `sections` (each with its `id`), `announcement`, `announcementUpdatedOn`
and `faqs`. When no shop page can be read, the shop's latest 10 listings from its RSS feed stand in (its coverage row says so). Each shop
page read is one Shop page event, also when its listings are filtered out or already delivered. **Search inside the shop**
(`shopSearchQuery`) sends the shop's own search box, **Shop section** (`shopSectionId`) reads one section of the shop (newest first
unless an order is set), and **Shop listing order** (`shopSortOrder`: featured, newest, price low to high or high to low) the shop's own
order; with any of them the RSS feed's latest listings are never delivered. The unblocker is slow (30 to 60 seconds a
page) and at busy hours often has no free browser: an attempt it could not answer is free, and after six failures in a row the run stops
asking it; a shop whose page 1 could not be read gets its RSS feed's latest 10 listings instead.

#### One row per listing, or per search

A listing that several of your phrases, categories or shops find is delivered and charged once per run (with the rank, page and
position of the target that found it first). Turn off **Each listing once per run** (`dedupeAcrossSearches`) for rank tracking: each
target then delivers every listing it finds, a listing found by two phrases is two rows (each a Listing), and `searchKey` on every row
names the target (`m:<phrase>`, `c:<category path>`, `s:<shop>`, or `ids`). Each run starts fresh: nothing is kept from one run to the
next.

#### Example listing row

A real detailed row of a "leather wallet" run (arrays and the description shortened here, buyers' display names left out):

```json
{
  "type": "listing",
  "listingId": "4501095565",
  "url": "https://www.etsy.com/listing/4501095565/rfid-full-grain-leather-wallet",
  "title": "Personalised Full Grain Leather RFID Wallet, Engraved Mens Bifold Wallet",
  "shopId": "64259013",
  "shopName": "ShipyardBelfast",
  "shopUrl": "https://www.etsy.com/shop/ShipyardBelfast",
  "price": 44.56,
  "priceText": "$44.56",
  "priceFrom": false,
  "cardPrice": 44.56,
  "currency": "USD",
  "originalPrice": null,
  "discountPercent": null,
  "onSale": false,
  "shopRating": 4.98,
  "shopReviewCount": 80,
  "shopReviewCountRounded": false,
  "freeShipping": false,
  "freeShippingText": null,
  "isAd": false,
  "adSlot": null,
  "organicRank": 15,
  "shopRank": null,
  "page": 1,
  "pagePosition": 26,
  "digitalDownload": false,
  "starSeller": true,
  "bestseller": false,
  "etsysPick": false,
  "popularNow": false,
  "stockLeft": null,
  "badges": [],
  "image": "https://i.etsystatic.com/64259013/r/il/ba9e2b/8018515648/il_800x800.8018515648_fe37.jpg",
  "imageAlt": "May include: A brown leather wallet with the initials \"S.Y.\" embossed on the front, displayed on a black box. The wallet is open, revealing an interior pocket. The image also shows a sign that reads \"SHIPYARD - Local leathersmith and engraving-\".",
  "source": "search",
  "searchPhrase": "leather wallet",
  "categoryPath": [
    "Bags & Purses",
    "Wallets & Money Clips",
    "Wallets"
  ],
  "searchShop": null,
  "titleMatchesSearch": true,
  "country": "US",
  "detailStatus": "ok",
  "scrapedAt": "2026-10-16T19:41:39.687Z",
  "description": "Crafted from premium full grain leather, this bifold wallet is designed to age beautifully and last for years. Each wallet develops its own unique patina over t ...",
  "priceMin": 44.56,
  "priceMax": 44.56,
  "availability": "InStock",
  "quantity": 58,
  "categoryUrl": "https://www.etsy.com/c/bags-and-purses/wallets-and-money-clips/wallets",
  "listingRating": 5,
  "listingReviewCount": 29,
  "favorites": 204,
  "listedOn": "2026-10-15",
  "scarcityText": "In 19 carts",
  "inCarts": 19,
  "boughtLast24h": null,
  "viewsLast24h": null,
  "shipsFromCountry": "GB",
  "shipsFromRegion": null,
  "shipsFromText": "United Kingdom",
  "shippingCost": 11.14,
  "priceWithShipping": 55.7,
  "freeShippingThreshold": null,
  "estimatedDelivery": "Oct 20-28",
  "returnsPolicy": "Returns & exchanges not accepted",
  "returnsWindow": null,
  "giftWrapping": false,
  "materials": [
    "Brass",
    "Leather"
  ],
  "itemAttributes": [
    {
      "name": "Primary fabric type",
      "value": "Leather"
    }
  ],
  "madeBy": "ShipyardBelfast",
  "madeByKind": "made by",
  "handmade": true,
  "vintage": false,
  "vintageText": null,
  "personalizable": true,
  "highlights": [
    "Made by ShipyardBelfast",
    "Materials: Brass, Leather; Primary fabric type: Leather"
  ],
  "productionPartners": [],
  "variations": [
    {
      "label": "Primary color",
      "options": [
        {
          "id": "6588354668",
          "value": "Black pebble",
          "priceText": null,
          "soldOut": false
        },
        {
          "id": "6588354664",
          "value": "Dark Coffee",
          "priceText": null,
          "soldOut": false
        }
      ]
    },
    {
      "label": "Engraving options",
      "options": [
        {
          "id": "6613527541",
          "value": "Engraved ($48.04)",
          "priceText": "$48.04",
          "soldOut": false
        },
        {
          "id": "6612692934",
          "value": "Not Engraved ($44.56)",
          "priceText": "$44.56",
          "soldOut": false
        }
      ]
    }
  ],
  "images": [
    "https://i.etsystatic.com/64259013/r/il/ba9e2b/8018515648/il_fullxfull.8018515648_fe37.jpg",
    "https://i.etsystatic.com/64259013/r/il/18883f/7994578680/il_fullxfull.7994578680_dl3y.jpg"
  ],
  "imageCount": 18,
  "videoUrl": null,
  "videoDuration": null,
  "reviewTags": [
    "Great quality",
    "Looks great"
  ],
  "sampleReviews": [
    {
      "rating": 5,
      "date": "2026-10-15",
      "text": "Very soft good quality leather",
      "author": "(left out here)"
    },
    {
      "rating": 5,
      "date": "2026-10-14",
      "text": "Absolutley LOVE this wallet, it is beautiful and well made and the engraving looks great, thanks so much i cant wait to give this as a gift",
      "author": "(left out here)"
    }
  ],
  "relatedSearches": [
    "wallet",
    "rfid wallet"
  ],
  "shopSales": 416,
  "shopSalesText": "416 sales",
  "shopTenure": "5 months",
  "shopLocation": null,
  "shopOwnerName": null,
  "gtin": null,
  "sellerBadges": [
    "Smooth shipping",
    "Speedy replies",
    "Rave reviews"
  ],
  "faqs": []
}
```

#### Free rows

- one `search` row per phrase, category or shop: `pagesRead`, `listingsFound`, `listingsDelivered`, `adsFound`, `totalReported`
  (Etsy's own total), `relatedSearches` and `autocomplete` (Etsy's related searches and its own suggestions for a phrase),
  `redirectedTo` (a phrase Etsy sends to a category page is read as that category), `rssFallback` (a shop whose pages could not be read),
  `complete` and `stopReason` (a category read to 250 pages or a shop read to 2,500 pages while Etsy still links onward stops with
  `actor_page_bound` and is not complete; a second name of a shop another target of the run finished, such as a renamed shop named by
  its old and its new name, stops with `same_shop_as_another_target` and adds no charge);
- `status` rows: an input that cannot be used (the setting and position named, nothing fetched), a phrase Etsy has no page for
  (`no_phrase_page`, HTTP 404) or a category link without a page (`no_category_page`), a listing Etsy no longer shows
  (`listing_not_found`), a shop Etsy does not know (`shop_not_found`), a shop id whose name Etsy did not show (`shop_not_resolved`), a
  page no exit could read (`lookup_failed`), listings a detail filter could not be checked on because their page could not be read
  (`details_not_read`), targets not started because the run stopped (`skipped`);
- the shop profile row (above) and one `summary` row at the end: counters, requests, stop reason and what was charged.

Every free row carries `attempt` (1, or 2 and on when the platform restarted the run). After a restart the rows already delivered are
never delivered or charged again, the caps count them, and the rest of the run is delivered free, as far as the unpaid work allowance
the run had earned before the restart covers (a restarted run with Listing details can therefore stop early, `work_allowance` in its
status, or deliver later rows without their listing page, `detailStatus: allowance`; the rows it delivered stay, run the rest again). When Etsy did not answer some calls
after their retries, the run's final status message says so: how many calls and why, what could not be read in full, and that it
was not charged.

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

You pay per event, on the Store's discount tiers:

| Event | Free plan | Bronze | Silver | Gold |
|---|---|---|---|---|
| Listing (per 1,000) | $1.19 | $1.09 | $0.99 | $0.85 |
| Listing details (per 1,000, only with Listing details) | $6.80 | $6.40 | $5.80 | $5.00 |
| Review (per 1,000, only with Reviews) | $0.53 | $0.49 | $0.44 | $0.38 |
| Shop page (per 1,000, only with Every listing of a shop) | $22.50 | $20.50 | $18.50 | $16.00 |
| Actor start (once per run at the default 256 MB) | $0.02 | $0.02 | $0.02 | $0.02 |

Platinum and Diamond plans pay the Gold price. The Actor start event has no discount. The Store pricing card shows these same prices
per 1,000 events: "$6.80 / 1,000" on the Listing details row means one listing's details cost 0.68 cents, and "$22.50 / 1,000" on the
Shop page row means one shop page costs 2.25 cents (about 40 listings, each a Listing).

Examples at Gold: 1,000 listings of a phrase cost $0.85 plus the $0.02 start; the same 1,000 with their listing pages $0.85 + $5.00 +
$0.02 = $5.87; a shop's latest 10 listings $0.0085 plus the start; every listing of a 200 listing shop through its pages 200 Listings
($0.17) plus 5 Shop pages ($0.08) plus the start. A listing is charged once per run, and only after its row is in your dataset.
Repeats, filtered listings, dead ids, unknown shops, phrases without a page, unanswered shop page attempts and refused input are free
(the run's start fee is charged on every run, by the platform). The run stops at your maximum cost per run.

### Input

| Setting | What it does |
|---|---|
| `searchTerms` | phrases, each read on Etsy's own phrase page (`etsy.com/market/<phrase>`): 40 organic results a page plus about 20 ad slots, up to Etsy's cap of 25 pages (1,000 organic results); a phrase with a slash is refused (Etsy's phrase pages have no form for one: write 1 12 for 1/12) |
| `startUrls` | Etsy links: `/market/<phrase>`, `/c/<category path>` (about 64 listings a page), `/listing/<id>`, `/shop/<name>`; a regional link (`/uk/`, `/de/` ...) must match the country setting; links carrying Etsy's own filters or sort are refused free |
| `listingIds` | listing ids or links, looked up 100 at a time; a dead id gets a free row |
| `shopNames` | shop names or shop links |
| `shopIds` | shop ids (numbers); each shop's name is read from its reviews first, then the shop is read like a shop name |
| `country`, `currency`, `language` | the Etsy market (29), the display currency (30, converted by Etsy itself), the title language (13; the English title stays in `titleEnglish`) |
| `maxItems`, `maxItemsPerSearch`, `maxPagesPerSearch`, `startPage`, `maxItemsPerShop` | limits in all, per phrase or category, pages read, the first page, per shop |
| `includeAds`, `minPrice`, `maxPrice`, `onSaleOnly`, `freeShippingOnly`, `minRating`, `minReviews`, `digitalOnly`, `physicalOnly`, `titleIncludes`, `titleExcludes`, `onlyTitleMatches` | filters on the listing card, applied here and checked again on the listing page's values when Listing details are read; with a title language the title words are looked for in both the translated and the English title (a word excluded in either drops the listing); a dropped listing is free |
| `includeDetails`, `includeDescription`, `includeImages` and the detail filters | the listing page add-on (see above) |
| `includeReviews`, `maxReviewsPerListing`, `reviewsSort`, `reviewsRatings`, `reviewsTopic`, `reviewsWithPhotosOnly`, `shopReviews`, `maxReviewsPerShop` | the reviews add-on |
| `includeShopPages`, `includeShopProfile`, `shopSearchQuery`, `shopSectionId`, `shopSortOrder`, `includeShopNames` | every listing of a shop and its profile, the shop's own search, a section and the shop's order; shop names on search rows (one free call per new shop; a shop Etsy's call does not name keeps it empty) |
| `includeRelatedSearches` | Etsy's related searches and suggestions on each phrase's `search` row (on by default) |
| `sortResultsBy` | Etsy's own order, or each phrase's rows sorted by price (both ways), shop rating, shop reviews, discount; with Listing details also by favourites, newest listed, in carts, views in 24 hours or shop sales. A sorted run holds its rows until each target ends, so `maxItems` can be at most 2,000 with Listing details and 20,000 without |
| `dedupeAcrossSearches` | each listing once per run (default), or once per target that finds it |
| `customMapFunction`, `includeRawData`, `includePersonalData` | your own function per row, run in a sandbox with a quarter second a row (its value in `custom`), raw page values, names of private persons |
| `maxConcurrency`, `maxRuntimeSeconds`, `proxyConfiguration` | phrases walked at once (1 to 4), a time limit, your own proxy addresses |

Example: 100 listings on sale for "ceramic mug" in the UK market, prices in euros, with their listing pages, delivered cheapest first:

```json
{
    "searchTerms": ["ceramic mug"],
    "country": "GB",
    "currency": "EUR",
    "onSaleOnly": true,
    "sortResultsBy": "priceAsc",
    "includeDetails": true,
    "maxItems": 100
}
```

#### Phrases, relevance and ads

Etsy fills every phrase page, so a phrase with few matches returns loosely related listings. Every row carries `titleMatchesSearch`;
turn on **Only titles holding every search word** to keep only listings whose title holds each word of the phrase (the rest free). Prices
and sorting are applied here, to the delivered rows, because Etsy's robots.txt asks crawlers not to use its own price and sort
parameters. Measured on the platform at the default 256 MB: 1,000 listings of one phrase in about 2 minutes.

#### Markets

Each page is read for the market you pick and checked against the market Etsy names on the page; a page answered for another market is
asked again and never delivered. Prices come in the market's currency as Etsy shows them there, or in the display currency you set.
With a display currency and Listing details, `price` stays in the display currency and the listing page's own figures (price range,
option prices, shipping cost) come in the market's currency, named in `detailsCurrency`.

### Integrations

Run it from the Apify API, schedule it, or connect it to Make, Zapier, n8n, Google Sheets or a webhook. With the API:

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

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_TOKEN>' });
const run = await client.actor('automation_craft/etsy-scraper').call({ listingIds: ['1879178124'], includeDetails: true });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

```python
from apify_client import ApifyClient
client = ApifyClient("<YOUR_TOKEN>")
run = client.actor("automation_craft/etsy-scraper").call(run_input={"shopNames": ["CaitlynMinimalist"], "includeReviews": True, "shopReviews": True})
items = client.dataset(run["defaultDatasetId"]).list_items().items
```

#### Use with AI agents (MCP)

AI agents can run it through the Apify MCP server (`https://mcp.apify.com`) as `automation_craft/etsy-scraper`. The smallest useful
input is `{"searchTerms": ["<phrase>"], "maxItems": 20}`; rows of `type: listing` hold the data, the `search` and `status` rows say what
was read and what was missing.

### What this Actor does NOT do

- **Log in.** It reads the public pages any visitor sees; no Etsy account, no cookies of yours and no API key.
- **Etsy's site search links** (`/search?q=`): robots.txt disallows them; put the words in Search phrases (read on Etsy's phrase page).
- **Etsy's own sort and price parameters**: robots.txt disallows them, so sorting and price filters run on the delivered rows, and one
  phrase gives at most Etsy's 1,000 organic results.
- **The seller's 13 tags, lifetime views, the first creation date, a listing's shop section**: Etsy serves them only under `/api/`, a
  path its robots.txt asks crawlers not to read, so this Actor does not read them (a listing page shows views in the last 24 hours, the
  listed or renewed date, review topic tags and the searches that led buyers to it, all delivered).
- **An exact in carts count, the last sale date, top rated, a shop wide sale, processing days as numbers, made to order**: the pages this
  Actor reads do not carry them (Etsy's own "In 20+ carts" line is delivered as Etsy writes it, in `scarcityText` and `inCarts`).
- **Review sub scores, review videos or a count of review photos**: Etsy's review answers carry no sub score, no review video was found
  on 137 listings, and review photos are listed only up to 20 (each review's own photo is delivered).
- **A memory between runs** ("new listings only", price change tracking): each run starts fresh; schedule it and compare the runs in your
  own sheet.
- **Seller e-mail or phone**, sold item lists or the lists of who favourited a listing (robots.txt disallows the last two).

### FAQ

#### Does Etsy have an API?

Etsy offers the Open API v3 for apps registered on its developer site, with an API key. This Actor reads the public pages any visitor
sees, with no key, and returns listings, reviews and shops as rows through the Apify API or a schedule.

#### How do I scrape Etsy listings with prices?

Type search phrases or paste Etsy links, pick the market, and start: every listing comes with price, original price, discount, currency,
free shipping and the shop's rating and review count. Turn on Listing details for variations with option prices, shipping cost and the delivery
estimate.

#### How do I track competitor prices on Etsy?

Name the competitors' shops (every listing with shop pages, or their latest 10 from the RSS feed) or the phrases you sell on, and run: each listing comes with its price, original price, discount and free shipping, and with Listing details the option prices and the shipping cost. Run it on a schedule and compare the runs in your own sheet.

#### How can I tell which Etsy search results are ads?

Every row from a phrase page carries isAd and adSlot (the paid slot on the page) next to isOrganic, organicRank and pagePosition, so ads and organic results are told apart, also when one listing is both on one page. Turn off Include ad slots to keep organic results only.

#### How do I export Etsy reviews to CSV?

Turn on Reviews (and Whole shop reviews for a shop you named), run, then open the run's Storage tab and download the dataset as CSV or
Excel; the Reviews view keeps the review columns.

#### Why does this Actor run with limited permissions?

Least privilege: it reads and writes only its own run's storages (the dataset, and a small record in its key-value store that keeps a
restarted run from delivering a row twice). It cannot see your other actors, datasets or settings.

### Changelog

See the Changelog tab: 0.1, the first version.

### More data tools by Automation Craft

- [Amazon Product Scraper - Search, Best Sellers](https://apify.com/automation_craft/amazon-data-scraper)
- [Amazon Sponsored Products Scraper - Ads by Keyword](https://apify.com/automation_craft/amazon-sponsored-products-scraper)
- [Mercari US Scraper: Listings, Sold Prices](https://apify.com/automation_craft/mercari-us-scraper)
- [Facebook Marketplace Scraper with Sold Tracker](https://apify.com/automation_craft/facebook-marketplace-scraper)
- [OfferUp Scraper: Local Listings, Prices, Sellers](https://apify.com/automation_craft/offerup-scraper)
- [Kijiji Scraper: Listings, Cars, Rentals & Prices](https://apify.com/automation_craft/kijiji-scraper)

# Changelog

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

# Actor input Schema

## `searchTerms` (type: `array`):

Words to search, one phrase per line, as a buyer types them ("leather wallet", "personalized necklace"). Each phrase is read on Etsy's own page for it (etsy.com/market/<phrase>): 40 results a page, up to 25 pages (1,000 results), plus the ad slots of each page. Etsy fills every phrase page, so a phrase nobody sells returns loosely related listings; turn on onlyTitleMatches to keep only listings whose title holds every word. A phrase with a slash is refused (write 1 12 for 1/12).

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

Etsy links: a phrase page (https://www.etsy.com/market/leather_wallet), a category page (https://www.etsy.com/c/jewelry/necklaces), a listing (https://www.etsy.com/listing/1879178124/...) or a shop (https://www.etsy.com/shop/ShopName). A link of a regional Etsy site (/uk/, /de/ ...) must match the country setting. Etsy's site search links (/search?q=) are not read; put their words in Search phrases. Links that carry filters (price, sort) are refused: set those filters below.

## `listingIds` (type: `array`):

Etsy listing ids (1879178124) or listing links. They are read 100 at a time through Etsy's own card call; a listing Etsy no longer shows gets a free listing_not_found row. Turn on includeDetails to read each listing's page as well.

## `shopNames` (type: `array`):

Shop names (CaitlynMinimalist) or shop links. Each shop gives its latest 10 listings from its RSS feed (Listing events). Turn on includeShopPages for every listing of the shop and its profile.

## `shopIds` (type: `array`):

Etsy shop ids (numbers such as 35055979). The run reads each shop's name from Etsy first (from the shop's reviews; a shop without reviews cannot be named this way and gets a free row), then reads the shop like a shop name.

## `country` (type: `string`):

Two letter country code of the Etsy market to read, for example US, GB, DE, CA or AU. Etsy shows prices in the market's currency, with VAT where it applies, and its own selection of listings for that country. Default: US.

## `currency` (type: `string`):

One of Etsy's 30 display currencies (USD, EUR, GBP, CAD, AUD, JPY ...). Prices are converted by Etsy itself, as a shopper sees them after picking a currency. Leave it empty for the market's own currency.

## `language` (type: `string`):

One of Etsy's interface languages (de, fr, es, it, ja, nl, pl, pt, ru, sv, en-GB ...). Titles come in Etsy's own translation for that language, and the English title stays in titleEnglish; the title filters look in both titles. Leave it empty for English.

## `maxItems` (type: `integer`):

The most listing rows this run delivers, over every target together. Reviews and the shop profile rows do not count toward it. Default: 100.

## `maxItemsPerSearch` (type: `integer`):

The most listing rows one phrase or category delivers. Leave empty for no per phrase limit.

## `maxPagesPerSearch` (type: `integer`):

How many result pages to read per phrase, category or shop. A phrase page holds 40 results plus ad slots and Etsy stops at page 25; a category page holds about 64. Leave empty for every page.

## `startPage` (type: `integer`):

The result page each phrase or category starts on (1 = the first page). A link's own page number wins for that link.

## `maxItemsPerShop` (type: `integer`):

The most listing rows one shop delivers. Leave empty for every listing of the shop (up to Maximum listings).

## `includeAds` (type: `boolean`):

Deliver the listings Etsy shows in paid ad slots (isAd: true, with their adSlot). Off: only organic results. A listing shown both as an ad and organically is one row with both positions.

## `minPrice` (type: `number`):

Only listings at or above this price, in the run's currency.

## `maxPrice` (type: `number`):

Only listings at or below this price, in the run's currency.

## `onSaleOnly` (type: `boolean`):

Only listings with a sale price below their original price.

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

Only listings whose card shows free shipping or free delivery.

## `minRating` (type: `number`):

Only listings whose shop is rated at least this (0 to 5, two decimals as Etsy keeps them): the stars an Etsy search card shows are the shop's. Listings without a rating are left out when this is set.

## `minReviews` (type: `integer`):

Only listings whose shop has at least this many reviews (the count an Etsy card shows is the shop's; Etsy rounds it above 1,000, 9.1k, and the row says so in shopReviewCountRounded).

## `digitalOnly` (type: `boolean`):

Only listings marked Digital Download on their card (with includeDetails, also on the listing page).

## `physicalOnly` (type: `boolean`):

Leave out listings marked Digital Download.

## `titleIncludes` (type: `array`):

Words that must all appear in the listing title (case does not matter).

## `titleExcludes` (type: `array`):

Words that leave a listing out when its title holds any of them.

## `onlyTitleMatches` (type: `boolean`):

Keep only listings whose title holds every word of the phrase (by word start: mug matches mugs). Every row carries titleMatchesSearch either way.

## `includeDetails` (type: `boolean`):

Read each delivered listing's own page: description, all images and the video, variations with their prices and sold out options, stock, materials, item attributes, who made it, ships from, delivery estimate, cost to ship, returns, favourites, listed date, the in carts, bought or viewed in the last 24 hours line Etsy shows, review tags, sample reviews, shop name, sales, years on Etsy and location. Charged as Listing details per listing page read; a page that cannot be read is free and the row comes without details.

## `includeDescription` (type: `boolean`):

With listing details: keep the full description text in each row. Off: leave it out (shorter rows).

## `includeImages` (type: `boolean`):

With listing details: keep the list of every image link in each row. Off: only the main image.

## `handmadeOnly` (type: `boolean`):

Only listings that Etsy marks as made by the shop itself (the item details say Made by and the shop's name). Needs includeDetails (the listing page says it).

## `vintageOnly` (type: `boolean`):

Only vintage listings: Etsy marks them Handpicked by and the shop's name in the item details. Needs includeDetails (the listing page says it).

## `starSellerOnly` (type: `boolean`):

Only listings of shops that hold the Star Seller badge. Needs includeDetails (the listing page says it).

## `bestsellerOnly` (type: `boolean`):

Only listings Etsy marks Bestseller. Needs includeDetails (the listing page says it).

## `etsysPickOnly` (type: `boolean`):

Only listings Etsy marks Etsy's Pick. Needs includeDetails (the listing page says it).

## `personalizableOnly` (type: `boolean`):

Only listings that take a personalization. Needs includeDetails (the listing page says it).

## `shipsFromCountries` (type: `array`):

Only listings that ship from one of these two letter country codes (US, GB, DE ...). Needs includeDetails.

## `minFavorites` (type: `integer`):

Only listings with at least this many favourites. Needs includeDetails.

## `listedAfter` (type: `string`):

Only listings whose Listed on date (Etsy's listed or renewed date) is on or after this day, YYYY-MM-DD. Needs includeDetails.

## `listedBefore` (type: `string`):

Only listings whose Listed on date is on or before this day, YYYY-MM-DD. Needs includeDetails.

## `categoryContains` (type: `string`):

Only listings whose Etsy category path holds this text (for example Wallets). Needs includeDetails.

## `madeByKinds` (type: `array`):

Only listings whose page names the shop with one of these words (Etsy's own wording in the item details). Needs includeDetails.

## `includeReviews` (type: `boolean`):

Deliver buyer reviews of every delivered listing (8 a call from Etsy's own review call): rating, date, text, translation, photo, the buyer's display name, the listing and the seller's reply. Charged per review; a listing without reviews costs nothing.

## `maxReviewsPerListing` (type: `integer`):

The most reviews delivered per listing. Default: 8 (one call).

## `reviewsSort` (type: `string`):

The order Etsy returns reviews in: Etsy's own four orders.

## `reviewsRatings` (type: `array`):

With reviews: only reviews of these star ratings (Etsy's own filter, one rating at a time, read in the order given under one cap). Leave empty for every rating.

## `reviewsTopic` (type: `string`):

Only reviews Etsy files under this topic (Etsy's own topics: Quality, Appearance, Shipping & Packaging, Customer service, Price ...; a listing's topics come in its first review call).

## `reviewsWithPhotosOnly` (type: `boolean`):

Only reviews that carry the buyer's photo (left out here, free: Etsy's review call has no photo filter).

## `shopReviews` (type: `boolean`):

With includeReviews and a shop you named: deliver the shop's own reviews across all its listings as well.

## `maxReviewsPerShop` (type: `integer`):

The most shop reviews delivered per shop. Default: 100.

## `includeShopPages` (type: `boolean`):

Read each shop's own pages through Apify's own unblocker: every listing of the shop in the shop's order (about 40 a page) and its profile row. Each page read is a Shop page event; an attempt the unblocker could not answer is free. The unblocker is slow (30 to 60 seconds a page) and at busy hours often has no free browser: after six failures in a row the run stops asking it, and each shop's latest 10 listings from its RSS feed stand.

## `includeShopProfile` (type: `boolean`):

With includeShopPages: for each shop you named, one free row with its id, name, headline, location, Star Seller, rating, review count, total sales, admirers, year it opened, latest activity, active listing count, sections, announcement and FAQ.

## `shopSearchQuery` (type: `string`):

With shop pages: the words for the shop's own search box (search_query on its shop pages); only the shop's listings that match are read, and the shop's latest listings from its RSS feed are not delivered. Each shop page read is a Shop page event.

## `shopSortOrder` (type: `string`):

With shop pages: the shop's own order of its listings. featured is the order the shop page opens with; newest, priceAsc and priceDesc are the shop's own sort orders. Any order other than featured leaves out the RSS feed's latest listings.

## `shopSectionId` (type: `string`):

With shop pages: one section of the shop, by its id (the shop row lists each section with its id; a section link carries it as section_id). Only that section's listings are read, newest first unless a shop listing order is set; the RSS feed's latest listings are not delivered.

## `includeShopNames` (type: `boolean`):

Search and category cards carry the shop id but not its name. On: one extra Etsy call per new shop names it (free, about one second per shop). Listing details always carry the shop name.

## `includeRelatedSearches` (type: `boolean`):

Add Etsy's related searches and its own autosuggest phrases for each phrase to that phrase's free search row (relatedSearches, autocomplete).

## `sortResultsBy` (type: `string`):

Etsy's own order delivers each page as it is read. Any other order collects each phrase's or category's rows and delivers them sorted when its walk ends, so it allows at most 2,000 listings in a run with listing details and 20,000 without.

## `dedupeAcrossSearches` (type: `boolean`):

On (the default): a listing that several of your searches, categories or shops find is delivered and charged once per run. Off: it is delivered once for every target that finds it (each with that target's rank, page and position), and each such row is a Listing, for rank tracking across searches.

## `includeRawData` (type: `boolean`):

With listing details: add a raw object with the listing page's own structured values.

## `customMapFunction` (type: `string`):

A JavaScript function (row) => value, run on each listing row in a sandbox with a time limit of a quarter second a row (a row that meets it is tried once more; a function that runs past it on 3 rows is not run again); what it returns is added to the row as custom. It never replaces the row's own fields.

## `includePersonalData` (type: `boolean`):

Adds the names of private persons that Etsy shows publicly: the shop owner's name on a listing (shopOwnerName), the shop members of a shop profile (members), a review buyer's profile link (buyerProfileUrl) and the name of the person who replied to a review (sellerResponderName). Off: those fields are null. You are responsible for a lawful use of personal data, GDPR included.

## `maxConcurrency` (type: `integer`):

How many phrases or categories are walked at the same time. Default: 2.

## `maxRuntimeSeconds` (type: `integer`):

Stop starting new work after this many seconds; rows in hand are delivered and every target not reached gets a free skipped row.

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

Optional. Your own proxy addresses (proxyUrls) replace the residential exits this Actor uses for listing, category and phrase pages. Apify proxy groups chosen here are not used: the Actor picks its own egress per page kind.

## Actor input object example

```json
{
  "searchTerms": [
    "leather wallet"
  ],
  "maxItems": 20,
  "includeAds": true,
  "onSaleOnly": false,
  "freeShippingOnly": false,
  "digitalOnly": false,
  "physicalOnly": false,
  "onlyTitleMatches": false,
  "includeDetails": false,
  "includeDescription": true,
  "includeImages": true,
  "handmadeOnly": false,
  "vintageOnly": false,
  "starSellerOnly": false,
  "bestsellerOnly": false,
  "etsysPickOnly": false,
  "personalizableOnly": false,
  "includeReviews": false,
  "reviewsSort": "relevance",
  "reviewsWithPhotosOnly": false,
  "shopReviews": false,
  "includeShopPages": false,
  "includeShopProfile": true,
  "shopSortOrder": "featured",
  "includeShopNames": false,
  "includeRelatedSearches": true,
  "sortResultsBy": "etsy",
  "dedupeAcrossSearches": true,
  "includeRawData": false,
  "includePersonalData": false
}
```

# Actor output Schema

## `items` (type: `string`):

No description

# 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 = {
    "searchTerms": [
        "leather wallet"
    ],
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation_craft/etsy-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 = {
    "searchTerms": ["leather wallet"],
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("automation_craft/etsy-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 '{
  "searchTerms": [
    "leather wallet"
  ],
  "maxItems": 20
}' |
apify call automation_craft/etsy-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation_craft/etsy-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/7zGHgQdXRH9dhxUDC/builds/GZhDMfcIpoMkjIg8d/openapi.json
