Skip to content

Scraping Etsy with Firecrawl

Updated Feb 2026

Firecrawl 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 zod
bash
# Or with Python
pip install firecrawl-py

JSON 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 links

Map: 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 CaseMethodNotes
Price monitoringBatch Scrape + JSON modeTrack prices across competitor listings
Market researchSearch + ScrapeFind trending products in a category
Shop analysisMap + Batch ScrapeGet all listings from a specific shop
Category trendsCrawl + JSON modeExtract data from category pages
Review analysisScrape with review schemaPull 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.).

Resources