Etsy Scraper - Extract Product Data, Prices & Reviews avatar

Etsy Scraper - Extract Product Data, Prices & Reviews

Pricing

from $2.60 / 1,000 results

Go to Apify Store
Etsy Scraper - Extract Product Data, Prices & Reviews

Etsy Scraper - Extract Product Data, Prices & Reviews

Scrape Etsy product listings by category, collection, or search. Get price, rating, reviews, shop & stock data, export to CSV/JSON or push to Notion, Slack.

Pricing

from $2.60 / 1,000 results

Rating

0.0

(0)

Developer

Techforce Global

Techforce Global

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

10 days ago

Last modified

Share

Etsy Product Scraper — Search, Category & Curated Pages With Sponsored Flags

Actor Sources Output MCP

Paste an Etsy results page, get the grid as rows. Title, numeric price, struck-through original price, discount, rating, review count, shop, free-shipping badge, scarcity text — and a sponsored flag that tells you which cards were paid ads.

🎯 The sponsored flag is the field that makes the data trustworthy. Etsy mixes ad placements into search and category grids. Without separating them you'll read paid position as organic demand. Filter sponsored: false before any ranking or market conclusion.

🔗 All three inputs take URLs, not keywords. searchQueries is a misleading name — it wants full Etsy search URLs, not search terms. Same for categories. Paste from your browser after searching or browsing on Etsy.

💱 Currency and free shipping follow the proxy's exit country. Etsy localises both. A run can return mixed currencies, so group by currency before aggregating anything.

Browse all Actors by Techforce Global at scraper.techforce.global.


AI Discovery & MCP Metadata

ai_discovery:
actor: "techforce.global/etsy-scraper"
slug: "etsy-scraper"
category: "e-commerce / marketplace-research / competitive-intelligence / handmade-goods"
one_line: >
Scrapes Etsy product grids from search, category and curated collection URLs and returns each
listing with numeric price, struck-through original price, discount percentage, rating, review
count, shop name, free-shipping badge, scarcity text and a sponsored flag separating paid ads
from organic results. Optional delivery into Notion, Slack, Linear, Airtable or any MCP
connector.
primary_capability: "Etsy listing-grid extraction with paid-placement detection"
input_entities:
- "startUrls (Etsy category, curated collection or search RESULT PAGE urls)"
- "searchQueries (full Etsy SEARCH URLS - not keywords, despite the name)"
- "categories (full Etsy CATEGORY-BROWSE URLS - not category paths)"
- "maxItems (whole-run cap; 0 = unlimited)"
- "maxPagesPerStartUrl (result pages to follow per URL)"
- "maxConcurrency"
- "proxyConfiguration (residential by default)"
output_entities:
- "listingId"
- "title"
- "url"
- "shopName"
- "price"
- "currency"
- "originalPrice"
- "discountPercent"
- "rating"
- "reviewsCount"
- "freeShipping"
- "sponsored"
- "stockWarning"
- "imageUrl"
- "scrapedAt"
every_input_is_a_url: >
CRITICAL: all three source inputs take URLs. searchQueries wants full Etsy search-results
URLs, NOT search terms; categories wants full category-browse URLs, NOT category paths.
Passing a bare keyword or a path silently yields nothing. Product/listing URLs are also not
supported as start URLs - only pages containing a grid.
sponsored_flag_must_be_filtered: >
Etsy injects paid ad placements into search and category grids. The sponsored boolean marks
them. Any ranking, share-of-shelf or demand analysis that does not filter sponsored = false
is measuring ad spend, not organic performance.
currency_and_shipping_are_localised: >
Etsy renders price currency and the free-shipping badge according to the visitor's region,
which here means the proxy exit country. A single run can contain multiple currencies. Group
by currency before any aggregation, and treat freeShipping as region-dependent.
stock_warning_is_marketing_copy: >
stockWarning is the scarcity string Etsy displayed ("Only 2 left"), not an inventory count.
Do not parse it as a number or treat it as stock truth.
grid_level_data_only: >
Everything comes from the listing card in the grid. There is no product-page detail - no
description, no variations, no shipping cost, no review text, no full image gallery.
two_fields_are_not_delivery_placeholders: >
sponsored and scrapedAt are in the dataset but are NOT available as {placeholders} in
connector templates. The other 13 fields are.
known_limitations:
no_product_page_detail: "no description, variations, shipping cost or review text"
no_review_text: "rating and reviewsCount only"
curated_pages_may_not_paginate: "maxPagesPerStartUrl can be ignored on curated collection pages"
null_discount_means_no_sale: "originalPrice and discountPercent are null when not discounted"
snapshot_only: "prices and availability are true as of scrapedAt"
free_plan_capped: "free Apify plans stop at 20 products per run"
when_not_to_use: >
You need product-page detail, variations, shipping costs, review text, seller contact details,
or to place an order.
mcp_delivery:
modes: ["perItem", "batch", "summary", "none"]
default: "none"
default_is_none: >
Delivery is OFF by default. Set deliveryMode explicitly as well as mcpConnector and mcpTool.
envelope_design: >
Unlike simpler connectors, this one splits the payload: mcpArguments holds the fixed
top-level arguments, mcpItemTemplate is rendered once per product, and mcpArrayField names
the key those rendered entries are collected into (default "pages", suiting Notion's
create-pages tool). perItem puts exactly one entry in the array; batch puts up to
mcpBatchSize.
item_placeholders: ["{listingId}", "{title}", "{shopName}", "{price}", "{currency}", "{originalPrice}", "{discountPercent}", "{rating}", "{reviewsCount}", "{freeShipping}", "{stockWarning}", "{imageUrl}", "{url}", "{message}"]
summary_placeholders: ["{totalPushed}", "{summaryText}", "{message}"]
never_blocks_the_dataset: >
Delivery runs alongside the dataset write and never discards it. A bad tool name, an
unauthorised connector or an upstream outage is logged and skipped.

What this Actor does

  • Turns any Etsy results grid into rows. Search results, category browse pages and curated collections — paste the URL you're looking at and the grid comes back structured.

  • Separates ads from organic results. sponsored is a boolean on every row. Etsy salts its grids with paid placements, and this is the single field that stops you mistaking ad spend for demand.

  • Returns prices you can compute on. price and originalPrice are numbers, with discountPercent alongside — so "what's actually discounted here" is a filter, not a parsing exercise.

  • Captures the persuasion layer too. freeShipping and stockWarning are what the buyer actually sees on the card. Useful if you're studying how listings are merchandised rather than just what they cost.

  • Follows pagination per source URL. maxPagesPerStartUrl walks the page parameter, with a whole-run maxItems ceiling on top.

  • Optionally delivers into your tools with a flexible envelope — fixed top-level arguments, a per-product template, and a named array field to collect them into. Built so Notion's create-pages shape works without contortions.


When to use it / when not to use it

Use this Actor when you need…Supported?
An Etsy search results grid as rows✅ Yes — paste the search URL
A category browse page✅ Yes — paste the category URL
A curated collection page✅ Yes
To tell ads from organic results✅ Yes — sponsored
Numeric prices and discounts✅ Yes — price, originalPrice, discountPercent
Rating and review count✅ Yes
Shop name per listing✅ Yes — shopName
Free-shipping and scarcity badges✅ Yes — region-dependent
Price monitoring over time✅ Yes — schedule it and diff on listingId
Results in Notion / Slack / Airtable✅ Yes — MCP delivery
Keywords instead of URLs❌ All three inputs take URLs
Individual product URLs as input❌ Grid pages only
Product description or variations❌ Grid-level data only
Shipping cost (as a number)❌ Only the free-shipping badge
Review text or reviewer names❌ Counts and averages only
Actual inventory countsstockWarning is marketing copy
Seller contact details❌ Not returned
Placing an order❌ Read-only

Execution Rules — Avoid Common Agent Failures

  1. All three source inputs take URLs. searchQueries wants a full Etsy search-results URL, not a keyword — the name is misleading. categories wants a full category-browse URL, not a path like jewelry/necklaces. Paste from your browser after searching or browsing. A bare keyword yields nothing, with no error.

  2. Product URLs are not valid start URLs. Only pages containing a grid — search, category or curated. A single listing URL won't work.

  3. Supply at least one of the three. None is marked required, so an empty input passes validation and scrapes nothing.

  4. Filter sponsored: false before any ranking claim. Etsy mixes paid placements into grids. A "top 20 by position" that includes sponsored rows is a measure of ad spend. This is the most consequential mistake you can make with this dataset.

  5. currency can vary within one run. Etsy localises pricing by visitor region, which here follows the proxy exit country. Group by currency before aggregating — and if you need one currency, pin apifyProxyCountry in proxyConfiguration.

  6. freeShipping is region-dependent too. It's what the card showed for that request, not a universal property of the listing.

  7. stockWarning is marketing copy, not inventory. "Only 2 left" is a string Etsy chose to display. Don't parse a number out of it or treat it as stock truth.

  8. Null originalPrice / discountPercent means not on sale. That's a signal, not missing data. Filter on non-null to get actual discounts.

  9. maxItems is a whole-run ceiling; maxPagesPerStartUrl is per URL. Three URLs at 3 pages each is 9 page loads, but maxItems can stop it early. Set both deliberately.

  10. Curated collection pages may ignore pagination. Getting one page from a curated URL despite maxPagesPerStartUrl: 3 is expected, not a bug.

  11. Everything is grid-level. No descriptions, variations, shipping costs, review text or image galleries. Don't build a pipeline expecting more.

  12. deliveryMode defaults to none. Delivery is off until you set it explicitly and provide mcpConnector and mcpTool.

  13. sponsored and scrapedAt are not delivery placeholders. They're in the dataset; the other 13 fields are available in templates.

  14. Free Apify plans stop at 20 products per run. The run succeeds and says so.


Input Contract

Sources — all three take URLs

ParameterTypeRequiredDefaultDescription
startUrlsarraysee rule 3Etsy category, curated or search result page URLs. Product URLs are not supported.
searchQueriesarray<string>see rule 3Full Etsy search URLs — e.g. https://www.etsy.com/search?q=statement+necklace. Not keywords.
categoriesarray<string>see rule 3Full Etsy category URLs — e.g. https://www.etsy.com/c/jewelry/necklaces. Not paths.

Limits and runtime

ParameterTypeDefaultDescription
maxItemsinteger100Whole-run product cap. 0 = unlimited.
maxPagesPerStartUrlinteger3Result pages to follow per URL. 1 = first page only. Curated pages may ignore it.
maxConcurrencyinteger5Parallel requests. Lower if you're being blocked.
proxyConfigurationobjectApify Proxy, residentialLeave enabled. Pin apifyProxyCountry if you need one currency.

Delivery (optional)

ParameterTypeDefaultDescription
mcpConnectorstringMCP connector to deliver into. The Console offers a picker of connectors you've authorised.
mcpToolstring""Tool to call — e.g. notion-create-pages, send_message. Required when a connector is set.
deliveryModestring"none"perItem, batch, summary, none. Off by default.
mcpArgumentsobject{}Fixed top-level arguments — everything except the product list.
mcpItemTemplateobject{}Shape of one product entry, rendered once per product.
mcpArrayFieldstring"pages"Key in mcpArguments that receives the rendered entries. "pages" suits Notion's create-pages tool.
mcpBatchSizeinteger10batch mode only — products per connector call.
mcpMessageTemplatestring""Text rendered into {message}.

The envelope split is unusual and worth understanding: mcpArguments is the wrapper, mcpItemTemplate is the repeated element, and mcpArrayField names where the elements go. In perItem the array holds exactly one entry per call; in batch it holds up to mcpBatchSize.

Delivery placeholders

ScopePlaceholders
Per product (mcpItemTemplate){listingId} {title} {shopName} {price} {currency} {originalPrice} {discountPercent} {rating} {reviewsCount} {freeShipping} {stockWarning} {imageUrl} {url} {message}
Summary mode{totalPushed} {summaryText} {message}

sponsored and scrapedAt are not available as placeholders.


Output Contract

One dataset item per listing card.

FieldTypeNullableMeaning
listingIdstringyesEtsy's listing id. The stable key for joining runs.
titlestringyesListing title from the card.
urlstringyesCanonical listing URL, tracking parameters stripped.
shopNamestringyesSelling shop. Often a sole trader — see Compliance.
pricenumberyesCurrent price, numeric.
currencystringyesCurrency as rendered. Can vary within a run — Etsy localises.
originalPricenumberyesStruck-through pre-sale price. Null when not discounted.
discountPercentnumberyesDiscount as displayed. Null when not discounted.
ratingnumberyesAverage stars out of 5. Null when unreviewed.
reviewsCountintegeryesReview count. Null or 0 for a new listing.
freeShippingbooleanyesFree-shipping badge. Region-dependent.
sponsoredbooleanyestrue = a paid ad placement. Filter before ranking analysis.
stockWarningstringyesScarcity text, e.g. "Only 2 left". Marketing copy, not inventory.
imageUrlstringyesPrimary listing image. Third-party copyrighted media.
scrapedAtstringyesISO-8601 UTC capture time. The row's shelf life.

Missing values are genuine null, not "N/A". Null discount fields mean no sale.

Example output object

{
"listingId": "1234567890",
"title": "Hammered Brass Statement Necklace, Handmade Minimalist Jewellery",
"url": "https://www.etsy.com/listing/1234567890/hammered-brass-statement-necklace",
"shopName": "NorthLightMetalwork",
"price": 42.0,
"currency": "USD",
"originalPrice": 60.0,
"discountPercent": 30,
"rating": 4.9,
"reviewsCount": 1284,
"freeShipping": true,
"sponsored": false,
"stockWarning": "Only 3 left",
"imageUrl": "https://i.etsystatic.com/<image-path>.jpg",
"scrapedAt": "2026-08-25T14:02:11.884213Z"
}

Values illustrate shape and formatting — the listing and shop are invented. Note sponsored: false: a real grid will contain a mix, and that field is what lets you tell.

What a successful run looks like

  • Status: SUCCEEDED
  • Dataset: one item per listing card, as each result page is parsed
  • Log: the resolved sources, a per-page count as pagination proceeds, and a final total
  • Zero results is also a success. A keyword passed where a URL was expected, a product URL used as a start URL, a blocked request, or a genuinely empty grid all return an empty dataset with SUCCEEDED. Check that your inputs are URLs first — that's the most common cause.

Dataset views

ViewShows
ProductsImage, title, price, discount, rating, shop, badges
Organic vs SponsoredThe sponsored column first — use this before any ranking analysis
Deals & DiscountsDiscount-first; null means not on sale
Shops & CaptureShop, listing id and capture time, for joining scheduled runs

Invocation & Integration

Apify Console

Open the Actor page, paste an Etsy search or category URL, leave the proxy at its default, click Start.

MCP — add as an agent tool

$claude mcp add apify --transport http https://mcp.apify.com/?actors=techforce.global/etsy-scraper

When an agent calls this Actor, it must supply URLs, not keywords. If a user asks for "statement necklaces on Etsy", the agent needs to construct https://www.etsy.com/search?q=statement+necklace first.

Python

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("techforce.global/etsy-scraper").call(run_input={
# A full search URL — NOT the keyword "statement necklace".
"searchQueries": ["https://www.etsy.com/search?q=statement+necklace"],
"maxItems": 200,
"maxPagesPerStartUrl": 3,
"proxyConfiguration": {
"useApifyProxy": True,
"apifyProxyGroups": ["RESIDENTIAL"],
"apifyProxyCountry": "US", # pin the country to pin the currency
},
})
rows = list(client.dataset(run["defaultDatasetId"]).iterate_items())
# Ads first — never analyse ranking without separating them.
organic = [r for r in rows if not r.get("sponsored")]
print(f"{len(organic)}/{len(rows)} organic ({len(rows) - len(organic)} sponsored)")
# Currency can vary in one run — group before you aggregate.
from collections import defaultdict
by_cur = defaultdict(list)
for r in organic:
if r.get("price") is not None:
by_cur[r.get("currency") or "?"].append(r["price"])
for cur, prices in by_cur.items():
prices.sort()
print(f"{cur} n={len(prices):<4} median {prices[len(prices) // 2]:.2f}")

JavaScript / TypeScript

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('techforce.global/etsy-scraper').call({
categories: ['https://www.etsy.com/c/jewelry/necklaces'],
maxItems: 300,
maxPagesPerStartUrl: 5,
proxyConfiguration: { useApifyProxy: true, apifyProxyGroups: ['RESIDENTIAL'] },
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
// Genuine discounts only — null discount means "not on sale".
const onSale = items
.filter((i) => !i.sponsored && i.discountPercent !== null)
.sort((a, b) => b.discountPercent - a.discountPercent);
console.table(onSale.slice(0, 10).map(({ title, price, originalPrice, currency, discountPercent, shopName }) =>
({ title: title?.slice(0, 40), was: `${currency} ${originalPrice}`, now: `${currency} ${price}`, off: `${discountPercent}%`, shopName })));

cURL — synchronous (waits, returns the dataset)

curl -X POST "https://api.apify.com/v2/acts/techforce.global~etsy-scraper/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
-H 'Content-Type: application/json' \
-d '{
"searchQueries": ["https://www.etsy.com/search?q=ceramic+mug"],
"maxItems": 50,
"maxPagesPerStartUrl": 2,
"proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}'

cURL — asynchronous with a spend cap

curl -X POST "https://api.apify.com/v2/acts/techforce.global~etsy-scraper/runs?token=<YOUR_APIFY_TOKEN>&maxTotalChargeUsd=3" \
-H 'Content-Type: application/json' \
-d '{ "categories": ["https://www.etsy.com/c/jewelry"], "maxItems": 0, "maxPagesPerStartUrl": 10 }'

maxItems: 0 is unlimited — maxTotalChargeUsd is then your only ceiling.

Competitive analysis — the configuration that answers the question

{
"searchQueries": ["https://www.etsy.com/search?q=personalised+dog+collar"],
"maxItems": 300,
"maxPagesPerStartUrl": 5,
"proxyConfiguration": {
"useApifyProxy": true,
"apifyProxyGroups": ["RESIDENTIAL"],
"apifyProxyCountry": "US"
}
}

Pinning the country pins the currency, so prices are comparable. Then filter sponsored: false and group by shopName to see who actually owns the organic grid.

With MCP delivery — a Notion database of listings

{
"searchQueries": ["https://www.etsy.com/search?q=ceramic+mug"],
"maxItems": 100,
"proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] },
"mcpConnector": "notion",
"mcpTool": "notion-create-pages",
"deliveryMode": "batch",
"mcpBatchSize": 10,
"mcpArrayField": "pages",
"mcpArguments": {
"parent": { "database_id": "<YOUR_NOTION_DATABASE_ID>" }
},
"mcpItemTemplate": {
"properties": {
"Name": "{title}",
"Shop": "{shopName}",
"Price": "{price}",
"Rating": "{rating}",
"Link": "{url}"
}
}
}

mcpArguments is the wrapper, mcpItemTemplate is one row, mcpArrayField collects them. batch with mcpBatchSize: 10 is ten products per call — far fewer calls than perItem.

Scheduling for price monitoring

Put a fixed input on a schedule and join runs on listingId. Because originalPrice is captured too, you can tell a genuine price drop from a changed "was" price — and because sponsored is captured, you can track whether a competitor started buying ads.


Autonomous Agent Error Handling & Resolution Matrix

Each row is a condition observable from the run's status, log or dataset.

ConditionWhat you seeCauseDeterministic action
NO_SOURCESSUCCEEDED, empty datasetAll three source inputs emptyModify Input: supply at least one URL.
KEYWORD_PASSED_AS_URLSUCCEEDED, empty datasetA bare keyword given to searchQueries, or a path to categoriesModify Input: use a full Etsy URL. This is the most common cause of an empty run.
PRODUCT_URL_AS_START_URLNothing from that URLListing URLs aren't supportedModify Input: use a search, category or curated grid page.
ZERO_RESULTSSUCCEEDED, empty datasetAny of the above, blocking, or a genuinely empty gridCheck your inputs are URLs first, then enable/upgrade the proxy and retry once.
MIXED_CURRENCIEScurrency differs across rowsEtsy localises by regionExpected. Group by currency, or pin apifyProxyCountry.
SPONSORED_SKEWING_ANALYSISRankings look implausiblePaid placements counted as organicFix Consumer: filter sponsored: false.
NULL_DISCOUNT_FIELDSoriginalPrice / discountPercent nullThe listing isn't on saleExpected — a signal. Filter non-null for deals.
NULL_PRICEprice is nullThe card showed no priceExpected. Null-check before arithmetic.
FEWER_PAGES_THAN_REQUESTEDFewer pages walked than maxPagesPerStartUrlGrid ran out, or a curated page ignored paginationExpected. Not a failure.
MAX_ITEMS_TRUNCATEDFewer rows than pages × cardsmaxItems bound firstModify Input: raise maxItems.
STOCK_WARNING_NOT_A_NUMBERstockWarning won't parseIt's marketing copy by designFix Expectation: treat it as a string.
LIKELY_BLOCKEDEmpty or thin dataset with the proxy onConcurrency too high, or transient blockModify Input: maxConcurrency: 2 and retry once.
FREE_PLAN_CAPPED20 products; log says soFree Apify planUpgrade the plan.
CONNECTOR_NOT_DELIVERINGConnector set, nothing delivered, no errordeliveryMode still none (the default)Modify Input: set it explicitly.
CONNECTOR_ARRAY_FIELD_WRONGConnector rejects the payloadmcpArrayField doesn't match the tool's expected keyModify Input: set it to the key the tool wants (pages for Notion create-pages).
CONNECTOR_TOOL_NOT_FOUNDLog: tool not available, followed by the available toolsmcpTool name is wrongModify Input: copy an exact name from that log line. Dataset unaffected.
CONNECTOR_UNREACHABLELog: could not reach the connectorConnector not authorised, or running outside the Apify platformAuthorise it in Apify, or set deliveryMode: "none". Data already saved.

Verify any platform-level HTTP status behaviour against the Apify API reference before relying on it in automation — the rows above describe this Actor's observable behaviour, not Apify's transport layer.


Natural Language → Actor Mapping

Every row below assumes the caller builds the Etsy URL first.

A user says…IntentInput
"Statement necklaces on Etsy"Search grid{"searchQueries":["https://www.etsy.com/search?q=statement+necklace"]}
"Everything in the necklaces category"Category{"categories":["https://www.etsy.com/c/jewelry/necklaces"]}
"Scrape this Etsy page"Paste-through{"startUrls":[{"url":"<the-url>"}]}
"Who ranks organically for X?"RankingSearch URL + maxItems: 300, then filter sponsored: false
"How much do ceramic mugs sell for?"Price benchmarkSearch URL + pin apifyProxyCountry, then group by currency
"Best discounts right now"DealsSearch URL + maxItems: 300, then sort discountPercent
"Which shops dominate this niche?"Shop shareSearch URL, filter organic, group by shopName
"Are competitors running ads here?"Ad presenceSearch URL, then count sponsored: true
"Track this category's prices weekly"Scheduled monitorCategory URL on a schedule; join on listingId
"Just a quick sample"Cheap probe{"searchQueries":["<url>"],"maxItems":10,"maxPagesPerStartUrl":1}
"It returned nothing"Wrong input typeCheck you passed a URL, not a keyword
"Put these listings in Notion"Scrape + deliverAbove, plus the delivery envelope

Should NOT route here: bare keywords — build the search URL first · individual product URLs · product descriptions or variations · shipping costs as numbers · review text or reviewer names · real inventory counts · seller contact details · placing an order · Amazon or other marketplaces (see the Amazon and Flipkart Actors).


Agent Execution & Routing Logic

Does the user want ETSY listing data?
├── NO → not this Actor.
│ Amazon → Amazon Product Scraper
│ Flipkart → Flipkart Search Scraper
│ descriptions/variations → not returned (grid data only)
│ placing an order → read-only Actor
└── YES
├── FIRST: do you have a URL, or a keyword?
│ ├── keyword → BUILD THE URL:
│ │ https://www.etsy.com/search?q=<url-encoded terms>
│ │ then pass it in searchQueries
│ ├── category → build https://www.etsy.com/c/<path>, pass in categories
│ ├── a pasted grid URL → startUrls
│ └── a PRODUCT url → NOT SUPPORTED. Find its category or search instead.
├── ALWAYS keep proxyConfiguration at residential
│ └── need one currency? → pin apifyProxyCountry
├── How much?
│ ├── a probe → maxItems 10, maxPagesPerStartUrl 1
│ ├── normal → maxItems 100-300, pages 3-5
│ └── deep → maxItems 0 + ALWAYS a spend cap
├── What is the actual question?
│ ├── organic ranking → FILTER sponsored == false ← non-negotiable
│ ├── price benchmark → pin country, GROUP BY currency
│ ├── deals → filter discountPercent != null, sort desc
│ ├── shop share → filter organic, group by shopName
│ ├── ad presence → count sponsored == true
│ └── change over time → SCHEDULE it; join on listingId
└── Should results land in another app?
├── yes → deliveryMode (DEFAULT none — set it!) + the envelope:
│ mcpArguments = fixed wrapper
│ mcpItemTemplate = one product
│ mcpArrayField = where entries go ("pages" for Notion)
│ batch + mcpBatchSize → far fewer calls than perItem
└── no → leave mcpConnector empty; dataset only

Cost Control & Pricing Transparency

This Actor bills for platform usage — the compute time and resources a run consumes — not per row. Current rates are on the Actor's Apify page, and the Console shows the live total while a run is in progress.

ChoiceEffect on cost
maxItemsThe direct ceiling on how much gets collected.
maxPagesPerStartUrl × number of URLsThe number of page loads — the real driver of run time.
maxConcurrencyHigher finishes sooner if you're not blocked. Being blocked makes it slower.
Passing keywords instead of URLsPure waste — the run costs time and returns nothing. Check this first.
Being blockedRetries cost run time and return nothing. Keep the residential proxy on.

Keeping runs predictable:

  1. First run: one URL, maxItems: 10, maxPagesPerStartUrl: 1. Confirm your input really is a URL and the grid parses.
  2. Verify the input type before scaling. The single most common wasted run here is a keyword passed where a URL belongs — it fails silently.
  3. Pin apifyProxyCountry if you'll aggregate prices. Otherwise you may pay for a dataset you can't safely average.
  4. Always set a spend cap on unattended runs, mandatory with maxItems: 0.
  5. Free Apify plans get 20 products per run — a connectivity check.

Companion machine-readable files

FileWhat it defines
.actor/input_schema.jsonEvery input, its type, default and bounds
.actor/dataset_schema.jsonAll 15 output fields with types, and the four Console views
.actor/output_schema.jsonWhere the run's output is exposed
.actor/actor.jsonActor identity and storage wiring

If this page and the schema files disagree, the schema files win. They are what the platform validates against at run time; this page is documentation and can lag a release.


Compliance & Data Privacy

  • Public listing pages only. Everything returned is published on a public Etsy grid with no login and no paywall.

  • Etsy is different from Amazon here: most sellers are individuals. shopName is a trading name, but on Etsy it very often belongs to a sole trader or a one-person business, and shop names frequently contain the maker's actual name. Where that's the case, shopName is personal data under GDPR / UK GDPR and India's DPDP Act 2023, and aggregating it at scale — building a ranked table of named makers and their prices — is processing you need a basis for. This is the main way this Actor differs from the marketplace scrapers in the same family.

  • No buyer or reviewer data is collected. No review text, no reviewer names, no customer information — only rating averages and counts.

  • Etsy's Terms of Use prohibit automated collection. Scraping and automated access are disallowed without permission. Etsy also operates an official Open API for sanctioned access, which is the route if your use case fits it. This Actor does not remove that risk — it is a risk you accept by running it.

  • Republishing prices carries consumer-protection duties, and Etsy's localisation makes it worse. A price is true only as of scrapedAt and only for the region the run resolved to. Presenting a scraped price as "the" price — when a buyer elsewhere sees a different currency and a different shipping badge — risks misleading-advertising exposure under the UK CPRs, Australian Consumer Law and the US FTC Act. Publish the capture time and the currency.

  • Treat discountPercent and stockWarning sceptically before repeating them. discountPercent is calculated against a seller-set "was" price, and stockWarning is scarcity copy rather than inventory truth. Repeating either as a verified fact is a claim you can't support.

  • Competitive monitoring is normal; price coordination is not. Watching public prices is standard. Using shared data to align pricing with competitors is antitrust exposure under EU Article 101, the UK Competition Act and the US Sherman Act. Keep monitoring internal.

  • Images are the makers' own work. imageUrl points at a photograph a small seller took of something they made. A URL is not a licence — and on a handmade marketplace, reusing listing photography is a live copyright and design-appropriation issue rather than a theoretical one.

  • Attribution over mirroring. url is on every row. Link back rather than mirroring listings, and don't present another maker's products as your own inventory.

  • Respect the load you generate. Keep maxItems, maxPagesPerStartUrl and maxConcurrency to what your question needs.


🆘 Support & Custom Pipeline Engineering

Need automated multi-city scheduled feeds, webhook enrichment, or enterprise data warehouse syncs?


ActorBest for
This ActorEtsy grids with paid-placement detection and sale pricing
Amazon Product ScraperAmazon across 7 marketplaces, with search rank and product-page detail
Flipkart Search ScraperFlipkart search results with discount and rating data
Website Contact ScraperBusiness contact details from a seller's own site
Website Content CrawlerWhole-site text, markdown or HTML for RAG and search

Three marketplace Actors, three different questions. Etsy is the one where paid placements are flagged, so it's the one to reach for when organic-versus-ad separation matters.

Browse the full catalogue at scraper.techforce.global.



{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "Etsy Product Scraper — Search, Category & Curated Pages With Sponsored Flags",
"applicationCategory": "BusinessApplication",
"applicationSubCategory": "Web Scraping / Marketplace & Competitive Intelligence",
"operatingSystem": "Any (cloud-hosted on Apify)",
"url": "https://apify.com/techforce.global/etsy-scraper",
"description": "Scrapes Etsy product grids from search, category and curated collection URLs, returning each listing with a numeric price, struck-through original price, discount percentage, rating, review count, shop name, free-shipping badge, scarcity text, image URL, capture timestamp and a sponsored flag that separates paid ad placements from organic results. Pagination is followed per source URL under a whole-run item ceiling. Optional direct delivery into Notion, Slack, Linear, Airtable or any MCP connector, using a configurable envelope of fixed arguments plus a per-product template collected into a named array field.",
"author": {
"@type": "Organization",
"name": "Techforce Global",
"url": "https://www.techforceglobal.com"
},
"provider": {
"@type": "Organization",
"name": "Apify",
"url": "https://apify.com"
},
"offers": {
"@type": "Offer",
"category": "Pay per platform usage",
"description": "Billed for the compute time and resources a run consumes. Current rates are shown on the Actor's Apify page.",
"url": "https://apify.com/techforce.global/etsy-scraper"
},
"featureList": [
"Search, category and curated collection grids from pasted URLs",
"Sponsored flag separating paid ad placements from organic results",
"Numeric price plus struck-through original price and discount percentage",
"Rating and review count per listing",
"Free-shipping badge and scarcity text as the buyer sees them",
"Shop name per listing for seller-level aggregation",
"Per-row capture timestamp for auditable, diffable snapshots",
"Pagination per source URL under a whole-run item ceiling",
"Flexible MCP delivery envelope with per-product templates and batching",
"Four Console dataset views including organic-versus-sponsored"
],
"isAccessibleForFree": false,
"softwareRequirements": "Apify account and API token; a paid plan for residential proxies",
"keywords": "Etsy scraper, Etsy product data, handmade marketplace, sponsored listings, ad detection, price monitoring, competitive intelligence, discount tracking, shop analysis, MCP connector"
}