Etsy Scraper - Extract Product Data, Prices & Reviews
Pricing
from $2.60 / 1,000 results
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
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
10 days ago
Last modified
Categories
Share
Etsy Product Scraper — Search, Category & Curated Pages With Sponsored Flags
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
sponsoredflag that tells you which cards were paid ads.
🎯 The
sponsoredflag 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. Filtersponsored: falsebefore any ranking or market conclusion.
🔗 All three inputs take URLs, not keywords.
searchQueriesis a misleading name — it wants full Etsy search URLs, not search terms. Same forcategories. 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
currencybefore 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 eachlisting with numeric price, struck-through original price, discount percentage, rating, reviewcount, shop name, free-shipping badge, scarcity text and a sponsored flag separating paid adsfrom organic results. Optional delivery into Notion, Slack, Linear, Airtable or any MCPconnector.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-resultsURLs, 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 notsupported 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 marksthem. Any ranking, share-of-shelf or demand analysis that does not filter sponsored = falseis 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. Groupby 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 - nodescription, 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} inconnector 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 fixedtop-level arguments, mcpItemTemplate is rendered once per product, and mcpArrayField namesthe key those rendered entries are collected into (default "pages", suiting Notion'screate-pages tool). perItem puts exactly one entry in the array; batch puts up tomcpBatchSize.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, anunauthorised 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.
sponsoredis 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.
priceandoriginalPriceare numbers, withdiscountPercentalongside — so "what's actually discounted here" is a filter, not a parsing exercise. -
Captures the persuasion layer too.
freeShippingandstockWarningare 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.
maxPagesPerStartUrlwalks thepageparameter, with a whole-runmaxItemsceiling 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 counts | ❌ stockWarning is marketing copy |
| Seller contact details | ❌ Not returned |
| Placing an order | ❌ Read-only |
Execution Rules — Avoid Common Agent Failures
-
All three source inputs take URLs.
searchQuerieswants a full Etsy search-results URL, not a keyword — the name is misleading.categorieswants a full category-browse URL, not a path likejewelry/necklaces. Paste from your browser after searching or browsing. A bare keyword yields nothing, with no error. -
Product URLs are not valid start URLs. Only pages containing a grid — search, category or curated. A single listing URL won't work.
-
Supply at least one of the three. None is marked required, so an empty input passes validation and scrapes nothing.
-
Filter
sponsored: falsebefore 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. -
currencycan vary within one run. Etsy localises pricing by visitor region, which here follows the proxy exit country. Group bycurrencybefore aggregating — and if you need one currency, pinapifyProxyCountryinproxyConfiguration. -
freeShippingis region-dependent too. It's what the card showed for that request, not a universal property of the listing. -
stockWarningis 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. -
Null
originalPrice/discountPercentmeans not on sale. That's a signal, not missing data. Filter on non-null to get actual discounts. -
maxItemsis a whole-run ceiling;maxPagesPerStartUrlis per URL. Three URLs at 3 pages each is 9 page loads, butmaxItemscan stop it early. Set both deliberately. -
Curated collection pages may ignore pagination. Getting one page from a curated URL despite
maxPagesPerStartUrl: 3is expected, not a bug. -
Everything is grid-level. No descriptions, variations, shipping costs, review text or image galleries. Don't build a pipeline expecting more.
-
deliveryModedefaults tonone. Delivery is off until you set it explicitly and providemcpConnectorandmcpTool. -
sponsoredandscrapedAtare not delivery placeholders. They're in the dataset; the other 13 fields are available in templates. -
Free Apify plans stop at 20 products per run. The run succeeds and says so.
Input Contract
Sources — all three take URLs
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
startUrls | array | see rule 3 | — | Etsy category, curated or search result page URLs. Product URLs are not supported. |
searchQueries | array<string> | see rule 3 | — | Full Etsy search URLs — e.g. https://www.etsy.com/search?q=statement+necklace. Not keywords. |
categories | array<string> | see rule 3 | — | Full Etsy category URLs — e.g. https://www.etsy.com/c/jewelry/necklaces. Not paths. |
Limits and runtime
| Parameter | Type | Default | Description |
|---|---|---|---|
maxItems | integer | 100 | Whole-run product cap. 0 = unlimited. |
maxPagesPerStartUrl | integer | 3 | Result pages to follow per URL. 1 = first page only. Curated pages may ignore it. |
maxConcurrency | integer | 5 | Parallel requests. Lower if you're being blocked. |
proxyConfiguration | object | Apify Proxy, residential | Leave enabled. Pin apifyProxyCountry if you need one currency. |
Delivery (optional)
| Parameter | Type | Default | Description |
|---|---|---|---|
mcpConnector | string | — | MCP connector to deliver into. The Console offers a picker of connectors you've authorised. |
mcpTool | string | "" | Tool to call — e.g. notion-create-pages, send_message. Required when a connector is set. |
deliveryMode | string | "none" | perItem, batch, summary, none. Off by default. |
mcpArguments | object | {} | Fixed top-level arguments — everything except the product list. |
mcpItemTemplate | object | {} | Shape of one product entry, rendered once per product. |
mcpArrayField | string | "pages" | Key in mcpArguments that receives the rendered entries. "pages" suits Notion's create-pages tool. |
mcpBatchSize | integer | 10 | batch mode only — products per connector call. |
mcpMessageTemplate | string | "" | Text rendered into {message}. |
The envelope split is unusual and worth understanding:
mcpArgumentsis the wrapper,mcpItemTemplateis the repeated element, andmcpArrayFieldnames where the elements go. InperItemthe array holds exactly one entry per call; inbatchit holds up tomcpBatchSize.
Delivery placeholders
| Scope | Placeholders |
|---|---|
Per product (mcpItemTemplate) | {listingId} {title} {shopName} {price} {currency} {originalPrice} {discountPercent} {rating} {reviewsCount} {freeShipping} {stockWarning} {imageUrl} {url} {message} |
| Summary mode | {totalPushed} {summaryText} {message} |
sponsoredandscrapedAtare not available as placeholders.
Output Contract
One dataset item per listing card.
| Field | Type | Nullable | Meaning |
|---|---|---|---|
listingId | string | yes | Etsy's listing id. The stable key for joining runs. |
title | string | yes | Listing title from the card. |
url | string | yes | Canonical listing URL, tracking parameters stripped. |
shopName | string | yes | Selling shop. Often a sole trader — see Compliance. |
price | number | yes | Current price, numeric. |
currency | string | yes | Currency as rendered. Can vary within a run — Etsy localises. |
originalPrice | number | yes | Struck-through pre-sale price. Null when not discounted. |
discountPercent | number | yes | Discount as displayed. Null when not discounted. |
rating | number | yes | Average stars out of 5. Null when unreviewed. |
reviewsCount | integer | yes | Review count. Null or 0 for a new listing. |
freeShipping | boolean | yes | Free-shipping badge. Region-dependent. |
sponsored | boolean | yes | true = a paid ad placement. Filter before ranking analysis. |
stockWarning | string | yes | Scarcity text, e.g. "Only 2 left". Marketing copy, not inventory. |
imageUrl | string | yes | Primary listing image. Third-party copyrighted media. |
scrapedAt | string | yes | ISO-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
| View | Shows |
|---|---|
| Products | Image, title, price, discount, rating, shop, badges |
| Organic vs Sponsored | The sponsored column first — use this before any ranking analysis |
| Deals & Discounts | Discount-first; null means not on sale |
| Shops & Capture | Shop, 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+necklacefirst.
Python
from apify_client import ApifyClientclient = 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 defaultdictby_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.
| Condition | What you see | Cause | Deterministic action |
|---|---|---|---|
NO_SOURCES | SUCCEEDED, empty dataset | All three source inputs empty | Modify Input: supply at least one URL. |
KEYWORD_PASSED_AS_URL | SUCCEEDED, empty dataset | A bare keyword given to searchQueries, or a path to categories | Modify Input: use a full Etsy URL. This is the most common cause of an empty run. |
PRODUCT_URL_AS_START_URL | Nothing from that URL | Listing URLs aren't supported | Modify Input: use a search, category or curated grid page. |
ZERO_RESULTS | SUCCEEDED, empty dataset | Any of the above, blocking, or a genuinely empty grid | Check your inputs are URLs first, then enable/upgrade the proxy and retry once. |
MIXED_CURRENCIES | currency differs across rows | Etsy localises by region | Expected. Group by currency, or pin apifyProxyCountry. |
SPONSORED_SKEWING_ANALYSIS | Rankings look implausible | Paid placements counted as organic | Fix Consumer: filter sponsored: false. |
NULL_DISCOUNT_FIELDS | originalPrice / discountPercent null | The listing isn't on sale | Expected — a signal. Filter non-null for deals. |
NULL_PRICE | price is null | The card showed no price | Expected. Null-check before arithmetic. |
FEWER_PAGES_THAN_REQUESTED | Fewer pages walked than maxPagesPerStartUrl | Grid ran out, or a curated page ignored pagination | Expected. Not a failure. |
MAX_ITEMS_TRUNCATED | Fewer rows than pages × cards | maxItems bound first | Modify Input: raise maxItems. |
STOCK_WARNING_NOT_A_NUMBER | stockWarning won't parse | It's marketing copy by design | Fix Expectation: treat it as a string. |
LIKELY_BLOCKED | Empty or thin dataset with the proxy on | Concurrency too high, or transient block | Modify Input: maxConcurrency: 2 and retry once. |
FREE_PLAN_CAPPED | 20 products; log says so | Free Apify plan | Upgrade the plan. |
CONNECTOR_NOT_DELIVERING | Connector set, nothing delivered, no error | deliveryMode still none (the default) | Modify Input: set it explicitly. |
CONNECTOR_ARRAY_FIELD_WRONG | Connector rejects the payload | mcpArrayField doesn't match the tool's expected key | Modify Input: set it to the key the tool wants (pages for Notion create-pages). |
CONNECTOR_TOOL_NOT_FOUND | Log: tool not available, followed by the available tools | mcpTool name is wrong | Modify Input: copy an exact name from that log line. Dataset unaffected. |
CONNECTOR_UNREACHABLE | Log: could not reach the connector | Connector not authorised, or running outside the Apify platform | Authorise 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… | Intent | Input |
|---|---|---|
| "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?" | Ranking | Search URL + maxItems: 300, then filter sponsored: false |
| "How much do ceramic mugs sell for?" | Price benchmark | Search URL + pin apifyProxyCountry, then group by currency |
| "Best discounts right now" | Deals | Search URL + maxItems: 300, then sort discountPercent |
| "Which shops dominate this niche?" | Shop share | Search URL, filter organic, group by shopName |
| "Are competitors running ads here?" | Ad presence | Search URL, then count sponsored: true |
| "Track this category's prices weekly" | Scheduled monitor | Category URL on a schedule; join on listingId |
| "Just a quick sample" | Cheap probe | {"searchQueries":["<url>"],"maxItems":10,"maxPagesPerStartUrl":1} |
| "It returned nothing" | Wrong input type | Check you passed a URL, not a keyword |
| "Put these listings in Notion" | Scrape + deliver | Above, 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.
| Choice | Effect on cost |
|---|---|
maxItems | The direct ceiling on how much gets collected. |
maxPagesPerStartUrl × number of URLs | The number of page loads — the real driver of run time. |
maxConcurrency | Higher finishes sooner if you're not blocked. Being blocked makes it slower. |
| Passing keywords instead of URLs | Pure waste — the run costs time and returns nothing. Check this first. |
| Being blocked | Retries cost run time and return nothing. Keep the residential proxy on. |
Keeping runs predictable:
- First run: one URL,
maxItems: 10,maxPagesPerStartUrl: 1. Confirm your input really is a URL and the grid parses. - Verify the input type before scaling. The single most common wasted run here is a keyword passed where a URL belongs — it fails silently.
- Pin
apifyProxyCountryif you'll aggregate prices. Otherwise you may pay for a dataset you can't safely average. - Always set a spend cap on unattended runs, mandatory with
maxItems: 0. - Free Apify plans get 20 products per run — a connectivity check.
Companion machine-readable files
| File | What it defines |
|---|---|
| .actor/input_schema.json | Every input, its type, default and bounds |
| .actor/dataset_schema.json | All 15 output fields with types, and the four Console views |
| .actor/output_schema.json | Where the run's output is exposed |
| .actor/actor.json | Actor 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.
shopNameis 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,shopNameis 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
scrapedAtand 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
discountPercentandstockWarningsceptically before repeating them.discountPercentis calculated against a seller-set "was" price, andstockWarningis 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.
imageUrlpoints 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.
urlis 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,maxPagesPerStartUrlandmaxConcurrencyto what your question needs.
🆘 Support & Custom Pipeline Engineering
Need automated multi-city scheduled feeds, webhook enrichment, or enterprise data warehouse syncs?
- Email: bhavin.shah@techforceglobal.com
- Custom Enterprise Integrations: Book a 15-Minute Technical Consultation
- Maintained by: Techforce Global — Specialists in High-Performance Web Scrapers and Agentic Workflows.
Related Actors by Techforce Global
| Actor | Best for |
|---|---|
| This Actor | Etsy grids with paid-placement detection and sale pricing |
| Amazon Product Scraper | Amazon across 7 marketplaces, with search rank and product-page detail |
| Flipkart Search Scraper | Flipkart search results with discount and rating data |
| Website Contact Scraper | Business contact details from a seller's own site |
| Website Content Crawler | Whole-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"}