Appearance
Scraping Etsy with Firecrawl
Updated Feb 2026Firecrawl enables extraction of Etsy marketplace data including product listings, variations, shop information, ratings, trending items, and pricing. This guide covers all extraction methods with code examples.
Installation
bash
npm install @mendable/firecrawl-js zodbash
# Or with Python
pip install firecrawl-pyJSON Mode: Structured Listing Extraction
Define a Zod schema to extract validated product data from individual Etsy listing pages.
typescript
import FirecrawlApp from "@mendable/firecrawl-js";
import { z } from "zod";
const app = new FirecrawlApp({ apiKey: process.env.FIRECRAWL_API_KEY });
const listingSchema = z.object({
title: z.string().describe("Listing title"),
price: z.string().describe("Current price with currency"),
shopName: z.string().describe("Name of the Etsy shop"),
shopRating: z.string().optional().describe("Shop star rating"),
totalReviews: z.string().optional().describe("Total number of shop reviews"),
totalSales: z.string().optional().describe("Total number of shop sales"),
description: z.string().describe("Product description text"),
variations: z.array(z.string()).optional().describe("Available variations (size, color, etc.)"),
shipping: z.string().optional().describe("Shipping information"),
materials: z.array(z.string()).optional().describe("Listed materials"),
});
const result = await app.scrapeUrl(
"https://www.etsy.com/listing/1234567890/example-handmade-item",
{
formats: ["json"],
jsonOptions: {
schema: listingSchema,
},
}
);
console.log(result.json);
// {
// title: "Handmade Ceramic Mug - Custom Name",
// price: "$24.99",
// shopName: "CeramicStudioCo",
// shopRating: "4.9",
// totalReviews: "3,456",
// totalSales: "12,345",
// description: "Beautiful handcrafted ceramic mug...",
// variations: ["Blue Glaze", "Green Glaze", "Natural"],
// shipping: "Free shipping",
// materials: ["Ceramic", "Food-safe glaze"]
// }Search: Find Listings by Query
Search the Etsy marketplace programmatically using site-scoped queries.
typescript
const results = await app.search("handmade jewelry site:etsy.com", {
limit: 10,
scrapeOptions: {
formats: ["markdown"],
},
});
for (const result of results.data) {
console.log(`${result.title}`);
console.log(` URL: ${result.url}`);
console.log(` ${result.description}\n`);
}Scrape: Single Listing Page
Extract a listing page as markdown for content analysis or LLM processing.
typescript
const result = await app.scrapeUrl(
"https://www.etsy.com/listing/1234567890/example-item",
{
formats: ["markdown", "html", "links"],
}
);
console.log(result.markdown); // Clean listing content
console.log(result.links); // Related listings and shop linksMap: Discover Shop URLs
Discover all available URLs in an Etsy shop or category page. Map returns URLs only, without content.
typescript
const shopUrls = await app.mapUrl(
"https://www.etsy.com/shop/ExampleShopName"
);
console.log(`Found ${shopUrls.links.length} URLs in shop`);
// Filter to listing pages only
const listingUrls = shopUrls.links.filter((url) =>
url.includes("/listing/")
);
console.log(`Found ${listingUrls.length} product listings`);Crawl: Multi-Page Shop Extraction
Crawl an entire shop or category to collect multiple listings.
typescript
const crawlResult = await app.crawlUrl(
"https://www.etsy.com/shop/ExampleShopName",
{
limit: 50,
scrapeOptions: {
formats: ["markdown"],
},
}
);
console.log(`Crawled ${crawlResult.data.length} pages`);
for (const page of crawlResult.data) {
console.log(`Page: ${page.metadata.title}`);
console.log(`URL: ${page.metadata.sourceURL}\n`);
}Batch Scrape: Multiple Listings in Parallel
Process a list of known listing URLs simultaneously.
typescript
const listingUrls = [
"https://www.etsy.com/listing/1111111111/item-one",
"https://www.etsy.com/listing/2222222222/item-two",
"https://www.etsy.com/listing/3333333333/item-three",
];
const batchResult = await app.batchScrapeUrls(listingUrls, {
formats: ["json"],
jsonOptions: {
schema: listingSchema,
},
});
console.log(`Completed: ${batchResult.completed}/${batchResult.total}`);
for (const listing of batchResult.data) {
const { title, price, shopName } = listing.json;
console.log(`${title} by ${shopName}: ${price}`);
}Extracting Shop Information
Create a dedicated schema for shop-level data rather than individual listings.
typescript
const shopSchema = z.object({
shopName: z.string().describe("Shop display name"),
shopOwner: z.string().optional().describe("Shop owner name"),
location: z.string().optional().describe("Shop location"),
totalSales: z.string().optional().describe("Total shop sales count"),
starRating: z.string().optional().describe("Average star rating"),
reviewCount: z.string().optional().describe("Total review count"),
about: z.string().optional().describe("Shop about/description text"),
policies: z.string().optional().describe("Return/exchange policy summary"),
});
const shopResult = await app.scrapeUrl(
"https://www.etsy.com/shop/ExampleShopName",
{
formats: ["json"],
jsonOptions: {
schema: shopSchema,
},
}
);
console.log(shopResult.json);Use Cases
| Use Case | Method | Notes |
|---|---|---|
| Price monitoring | Batch Scrape + JSON mode | Track prices across competitor listings |
| Market research | Search + Scrape | Find trending products in a category |
| Shop analysis | Map + Batch Scrape | Get all listings from a specific shop |
| Category trends | Crawl + JSON mode | Extract data from category pages |
| Review analysis | Scrape with review schema | Pull review text and ratings for sentiment analysis |
Python Example
python
from firecrawl import FirecrawlApp
from pydantic import BaseModel
from typing import Optional
app = FirecrawlApp(api_key="fc-YOUR_API_KEY")
class EtsyListing(BaseModel):
title: str
price: str
shop_name: str
rating: Optional[str] = None
description: str
variations: list[str] = []
result = app.scrape_url(
"https://www.etsy.com/listing/1234567890/example-item",
params={
"formats": ["json"],
"jsonOptions": {
"schema": EtsyListing.model_json_schema(),
},
},
)
listing = result["json"]
print(f"{listing['title']} - {listing['price']}")TIP
Use Map first to discover all listing URLs in a shop, then filter to the ones you need before running Batch Scrape. This avoids wasting credits on non-listing pages (about, policies, etc.).