# Etsy Scraper - Bestsellers, In-Cart Counts & Shop Sales (`scrapewise/etsy-scraper`) Actor

Scrape Etsy search results and shops: price, discount, Bestseller and Popular now badges, how many people have each item in their cart, shop total sales and sales per day since your last run. Digital downloads filter. Error rows are free.

- **URL**: https://apify.com/scrapewise/etsy-scraper.md
- **Developed by:** [Scrapewise Data](https://apify.com/scrapewise) (community)
- **Categories:** E-commerce, Lead generation
- **Stats:** 3 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.10 / 1,000 listing delivereds

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: Bestsellers, in-cart counts and shop sales per day

Scrape **Etsy search results and shops** into one table: title, price, original price and discount, the **Bestseller**,
**Popular now** and **Star Seller** badges, whether it is an ad or a digital download, and the numbers buyers see but
other scrapers skip: **how many people have each item in their cart** and **how many sales the shop has made**. Turn on
the daily schedule and every run adds **sales since your last run** and **sales per day** for each shop.

Built for **Etsy product research**: find what sells before you make it, watch competitor shops, and track which
listings are heating up. No Etsy account, no API key, no cookies to paste.

### At a glance

- **Price per 1,000 listings, Free plan:** US$ 6.00
- **Fee per run:** US$ 0.02, charged only after the run gets past Etsy's bot protection
- **Three modes:** search results by keyword, shops (summary + every item), full listing pages
- **Demand signals:** Bestseller, Popular now, Star Seller, in-cart count, "bought in the past 24 hours", shop sales, seller tenure
- **Sales velocity:** `salesSinceLastRun` and `salesPerDay` per shop, `inCartsChange` and `becameBestseller` per item
- **Filters:** digital downloads only, min and max price, sort order, skip ads
- **Proxy:** US residential included in the price
- **Error rows (blocked page, invalid input, listing not found):** free, with an `errorCode`

### One real row

From a test on 2026-10-09, search `budget spreadsheet google sheets`, page 1:

```json
{
  "type": "listing",
  "source": "search",
  "searchQuery": "budget spreadsheet google sheets",
  "page": 1,
  "position": 2,
  "listingId": "4316136064",
  "title": "Simple Budget Spreadsheet: Google Sheets Budgeting Planner (Digital Download)",
  "url": "https://www.etsy.com/listing/4316136064/simple-budget-spreadsheet-google-sheets",
  "price": 5.4,
  "originalPrice": 9.0,
  "currency": "USD",
  "discountPercent": 40,
  "isAd": true,
  "isBestseller": true,
  "isPopularNow": false,
  "isStarSeller": true,
  "isDigitalDownload": true,
  "inCartsCount": null,
  "shopName": "AbbyOrganizes",
  "shopId": "58970278",
  "shopSalesApprox": 9900,
  "shopTenure": "1 year on Etsy",
  "shopYearsOnEtsy": 1,
  "shipsFrom": "Pittsburgh, Pennsylvania",
  "scrapedAt": "2026-10-09T23:41:12Z"
}
```

And a shop summary row (Shop mode), with the comparison fields that appear from the second scheduled run on:

```json
{
  "type": "shop",
  "shopName": "DesignStudioTeti",
  "url": "https://www.etsy.com/shop/DesignStudioTeti",
  "slogan": "Printable Planners, Calendars, Resume Templates & more",
  "location": "Houten, The Netherlands",
  "rating": 4.9,
  "reviewCount": 7572,
  "totalSales": 84425,
  "admirers": 8063,
  "yearsOnEtsy": 13,
  "previousTotalSales": 84301,
  "salesSinceLastRun": 124,
  "hoursSinceLastRun": 24.0,
  "salesPerDay": 124.0
}
```

The comparison values above show the format; they depend on your own previous run.

### What you can do with it

- **Validate a product idea:** search the keyword, count Bestseller and Popular now badges on page 1, look at prices and
  discounts, and see how many sales the shops behind those listings have.
- **Spy on competitor shops:** schedule Shop mode daily and get each shop's sales per day, new items and which items
  people are adding to their carts.
- **Find rising listings:** `inCartsChange` and `becameBestseller` tell you what started selling since the last run.
- **Price research:** original price, sale price and discount for every card, filtered by price range.
- **Lead lists of Etsy sellers:** shop name, tenure, total sales and where they ship from.

### Input

| Field | Type | What it does |
|---|---|---|
| `mode` | `search`, `shop` or `listing` | What to scrape. Default `search`. |
| `searchQueries` | list of text | Keywords, one per line, as typed on Etsy (Search mode). |
| `shopUrls` | list of text | Shop URLs or shop names (Shop mode). |
| `listingUrls` | list of text | Listing URLs or ids (Listing mode). |
| `maxItems` | number | Listings per keyword or per shop. Default 48. In Shop mode, 0 returns only the summary row. |
| `maxPages` | number | Safety cap on pages per keyword or shop. Default 5. |
| `sortBy` | `relevance`, `newest`, `price_low`, `price_high`, `top_reviews` | Search order. |
| `digitalOnly` | true/false | Only instant digital downloads. |
| `minPrice`, `maxPrice` | number | Price range in USD. |
| `includeAds` | true/false | Keep sponsored cards (marked `isAd`). Default true. |
| `compareWithPreviousRun` | true/false | Add the change since your last run. Default true. |
| `proxyConfiguration` | proxy | US residential by default. |

Example, three keywords, digital only:

```json
{
  "mode": "search",
  "searchQueries": ["digital planner", "wedding invitation template", "svg cut file"],
  "digitalOnly": true,
  "maxItems": 60
}
```

Example, daily shop tracking:

```json
{
  "mode": "shop",
  "shopUrls": ["https://www.etsy.com/shop/DesignStudioTeti", "AbbyOrganizes"],
  "maxItems": 100,
  "compareWithPreviousRun": true
}
```

### How sales tracking works

Etsy shows each shop's total number of sales. With `compareWithPreviousRun` on, the Actor saves that number (and each
listing's in-cart count, price and Bestseller badge) in a key-value store named `etsy-scraper-state` in your own Apify
account. On the next run it subtracts:

- `salesSinceLastRun` = total sales now minus total sales at your previous run
- `salesPerDay` = the same, scaled to 24 hours (shown when the runs are at least one hour apart)
- `inCartsChange`, `previousPrice` and `becameBestseller` for each listing seen before

Schedule the Actor once a day (Apify Schedules) for clean daily numbers. The state belongs to your account only; delete
the key-value store to start over.

### Output

Every row has a `type`:

- `listing`: a card from a search page or a shop page.
- `shop`: a shop summary (Shop mode), one per shop.
- `listingDetail`: a full listing page (Listing mode) with `rating`, `reviewCount`, `category`, `categoryPath`,
  `quantityAvailable`, `availability`, `description`, `shopRating`, `shopReviewCount`, `shopSalesApprox`,
  `shopFavorites`, `isBestseller`, and `boughtLast24h` / `inCartsCount` when Etsy shows them (in a test on
  2026-10-10 a budget template page showed 59 purchases in the past 24 hours).
- `error`: something that could not be read. Free.

Notes on the fields:

- `inCartsCount` comes from Etsy's "N people have this in their cart" note. Etsy shows it on shop pages and some
  listing pages, not on search cards; "Over 20" is returned as 20 with the original text in `inCartsText`. It shows up
  mostly on physical items: in a test on 2026-10-10, 1 of 190 digital-download cards in six shops carried it.
- Etsy shows Bestseller and Popular now badges on search cards and listing pages, not on shop pages. To see which of a
  shop's items carry a badge, search its keywords or use Listing mode.
- `yearsOnEtsy` is 0 for shops younger than one year.
- `shopSalesApprox` is the rounded figure shown on search cards ("9.9k sales"). The exact count is `totalSales` on the
  shop row.
- Search results include ads by default because they are part of what buyers see. Use `isAd` to filter them.

Download as JSON, CSV, Excel or through the API from the run's Output tab.

### Error codes (never charged)

| Code | Meaning |
|---|---|
| `INVALID_INPUT` | A shop name, listing URL or option could not be understood. The message says which. |
| `BLOCKED` | Etsy refused the page after several fresh sessions. Run again later; each run uses new IPs. |
| `NOT_FOUND` | The shop or listing does not exist anymore (Etsy answered 404). |
| `NO_DATA` | The listing page opened but had no product data. |
| `NOT_REACHED` | The run timeout came before this input. Opening an Etsy session takes 30 to 90 s, so give runs at least 300 s. |

If Etsy refuses every session in a run, the run ends without delivering or charging anything.

### Pricing

Pay per event, no monthly fee:

| Event | Price |
|---|---|
| Listing delivered (search or shop card) | US$ 0.006 (US$ 6.00 per 1,000) |
| Shop summary delivered | US$ 0.006 |
| Full listing page delivered | US$ 0.015 |
| Run started (only after the bot protection is passed) | US$ 0.02 |

Examples: one keyword, 60 listings: about US$ 0.38. Ten competitor shops tracked daily with their 40 newest items
each: about US$ 2.48 per day. Apify platform usage and the residential proxy are included.

### Limitations

- Etsy uses strong bot protection. Each run opens a real browser session first, which takes 30 to 90 seconds and
  sometimes needs a few IPs (about 1 attempt in 10 is refused and the next IP is tried). After that, pages load in
  about a second each. Keep the run timeout at 300 s or more; with less, inputs come back as free `NOT_REACHED` rows.
- Search pages show up to about 60 listings and Etsy limits how deep search goes; use more specific keywords for
  more coverage instead of many pages.
- Etsy does not publish per-listing sales. Shop sales, badges and in-cart counts are the public demand signals; this
  Actor returns them exactly as Etsy shows them.
- Prices are in USD as seen from the US.
- Only public pages are read. No login, no buyer data, no private messages.

### FAQ

**Do I need an Etsy API key?** No.

**Why is the first page slower?** The run first opens a browser to pass Etsy's protection, then reuses that session
for fast page loads.

**Can I get sales for a single listing?** Etsy does not publish a sales total per listing. Listing mode returns
`boughtLast24h` when Etsy shows "N people bought this in the past 24 hours"; combine it with the shop's daily sales,
the in-cart count and the Bestseller badge.

**Does it work for non-English Etsy?** Pages are read in English from the US. Listings from every country appear.

**Can I schedule it?** Yes. Daily schedules are what make `salesPerDay` useful.

### Changelog

- **18 Oct 2026:** a run with a timeout too short to open the first session now says so (`NOT_REACHED`) instead of
  reporting a block. An empty input runs the example search.
- **0.1 (2026-10):** search, shop and listing modes; demand badges; in-cart counts; shop sales; daily comparison.

# Actor input Schema

## `mode` (type: `string`):

Search: the cards of Etsy search results for each keyword, with badges, prices and the seller's sales. Shop: one summary row per shop (exact total sales, admirers, rating, years on Etsy) plus its items with in-cart counts. Listing: the full page of each listing (rating, review count, category, stock, shop favorites).

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

Used in Search mode. One keyword per line, exactly as you would type it on Etsy, for example: digital planner, wedding invitation template, svg cut file, crochet pattern.

## `shopUrls` (type: `array`):

Used in Shop mode. Shop URLs (https://www.etsy.com/shop/ShopName) or just shop names, one per line.

## `listingUrls` (type: `array`):

Used in Listing mode. Listing URLs (https://www.etsy.com/listing/1234567890/...) or listing ids, one per line.

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

How many listings to return for each keyword (Search) or each shop (Shop). A search page has about 60 listings, a shop page about 40. In Shop mode, 0 returns only the shop summary row.

## `maxPages` (type: `integer`):

Safety cap on result pages read for each keyword or shop.

## `sortBy` (type: `string`):

Search mode only.

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

Search mode only: keep only instant digital downloads (templates, printables, SVG, patterns).

## `minPrice` (type: `string`):

Search mode only. Leave empty for no minimum.

## `maxPrice` (type: `string`):

Search mode only. Leave empty for no maximum.

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

Etsy mixes ads into search results. They are returned with isAd = true. Turn off to skip them (skipped ads are not charged).

## `compareWithPreviousRun` (type: `boolean`):

Keeps a small state in your account (key-value store 'etsy-scraper-state') so the next run adds salesSinceLastRun and salesPerDay to each shop, and inCartsChange and becameBestseller to each listing. Schedule the Actor daily to track sales velocity.

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

Etsy blocks datacenter traffic. US residential proxy is the default and its cost is included in the price. Change it only to use your own proxies.

## Actor input object example

```json
{
  "mode": "search",
  "searchQueries": [
    "budget spreadsheet google sheets"
  ],
  "maxItems": 20,
  "maxPages": 5,
  "sortBy": "relevance",
  "digitalOnly": false,
  "includeAds": true,
  "compareWithPreviousRun": true,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `resultsCsv` (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 = {
    "mode": "search",
    "searchQueries": [
        "budget spreadsheet google sheets"
    ],
    "maxItems": 20,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapewise/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 = {
    "mode": "search",
    "searchQueries": ["budget spreadsheet google sheets"],
    "maxItems": 20,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("scrapewise/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 '{
  "mode": "search",
  "searchQueries": [
    "budget spreadsheet google sheets"
  ],
  "maxItems": 20,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}' |
apify call scrapewise/etsy-scraper --silent --output-dataset

```

## MCP server setup

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