# Discogs Marketplace Scraper (`devilscrapes/discogs-sold-price`) Actor

Scrape Discogs marketplace listings — asking price, seller, condition, ships-from — plus public marketplace stats for any release ID or search query, export to JSON or CSV. A Discogs API + HTML scraper that handles the rate limits and retries for you. No login.

- **URL**: https://apify.com/devilscrapes/discogs-sold-price.md
- **Developed by:** [DevilScrapes](https://apify.com/devilscrapes) (community)
- **Categories:** E-commerce
- **Stats:** 6 total users, 0 monthly users, 93.8% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

<img src="https://apify.com/.actor/icon.svg" width="160" alt="Discogs Price Scraper" />

## Discogs Price Scraper

*We do the dirty work so your dataset stays clean.* 😈

**$5.20 / 1,000 rows** — Pay only for results that land. No credit card required to try.

Pull live marketplace listings from Discogs — asking prices, condition grades, seller
ratings, ships-from country — for any release ID or free-text search query. Joins the
public Discogs release API, the marketplace-stats API, and the Cloudflare-protected
marketplace HTML into a single flat dataset: one row per listing, one optional aggregate
row per release. The per-listing detail that the official Discogs API does not expose.

### 🎯 What this scrapes

Two row types share one flat schema. The `row_type` discriminator selects which fields
are populated — listing rows carry per-seller pricing, condition grades, and seller
ratings; stats rows carry the per-release marketplace aggregate.

| Field | Type | Populated for |
|---|---|---|
| `row_type` | `"listing"` | `"stats"` | both |
| `release_id` | integer | both |
| `release_title`, `artist`, `year`, `country`, `format_name`, `format_descriptions`, `genres`, `master_id`, `release_url` | from `/releases/{id}` | both |
| `listing_id`, `listing_url` | integer / string | listing rows |
| `asking_price`, `asking_currency` | float / ISO 4217 | listing rows |
| `shipping_text` | string (seller free-form) | listing rows |
| `condition_media`, `condition_sleeve` | Discogs grade vocab | listing rows |
| `seller_username`, `seller_rating_pct`, `seller_rating_count`, `seller_country` | strings + numerics | listing rows |
| `stats_lowest_price`, `stats_lowest_currency`, `stats_num_for_sale`, `stats_blocked_from_sale` | from `/marketplace/stats/{id}` | stats rows |
| `scraped_at` | ISO 8601 UTC | both |

### 🔥 Features

- Per-listing asking prices, condition grades, seller ratings, and ships-from country
  for every release you supply — the detail the official Discogs API withholds.
- Per-release marketplace stats row (lowest asking price, total active listings,
  blocked-from-sale flag), opt-in and on by default.
- Input by release ID (deterministic, fast) or by free-text search query — the search
  endpoint resolves the top N matches automatically.
- Single flat schema with a `row_type` discriminator — easy to join or aggregate
  downstream by `release_id` or `master_id`.
- Pydantic v2 input validation — XOR between `releaseIds` and `searchQuery` is
  enforced before any network call.
- Conservative rate-limit pacing at one request every 1.5 seconds (~40 req/min) —
  well under Discogs' API ceiling and the HTML surface's unwritten limit.
- Per-release fail isolation — one bad release ID logs a warning and the run continues.

### 🛡️ What we handle for you

Discogs marketplace pages sit behind a multi-layer blocking stack. We absorb all of it:

- **We rotate browser fingerprints** (curl-cffi chrome/firefox impersonation) so the
  target sees real-browser TLS and HTTP/2, not Python. Verified clean pass as of
  2026-05-16.
- **We warm up the session** with a one-shot homepage hit before any listing pages —
  the same cold-start sequence a real browser runs.
- **We rotate residential proxies** via Apify Proxy on every block — fresh session ID,
  fresh exit IP, no stale fingerprint reuse.
- **We retry with exponential backoff** on 408 / 429 / 503 and honour `Retry-After`.
  Up to 5 attempts per page before we log and move on.
- **We back off when the target rate-limits.** Partial successes surface with a clear
  status message — we never silently return an empty dataset.
- **We keep the dataset clean** — Pydantic-validated rows, ISO-8601 timestamps, stable
  field names across every run.
- **You pay only for results that land.** If a release fetch fails entirely, no
  result-row charges fire for it.

### 💡 Use cases

- **Vinyl / CD reseller benchmarking** — pull every active listing for releases in your
  inventory and benchmark your asking price against the live competing supply (median,
  min, max, condition mix).
- **Music-collectibles arbitrage** — monitor cross-country shipping spreads
  (`seller_country` + `asking_price` + `asking_currency`) for the same release; spot
  regional under-pricing.
- **Catalog / label market intel** — for a label's release IDs, track `num_for_sale`
  and `lowest_price` over time to see which titles are appreciating.
- **Journalism / pricing studies** — "the cheapest first pressing of Nevermind on
  Discogs right now" generators, automated with one scheduled Actor run per article.
- **Marketplace health monitoring** — count `stats_blocked_from_sale=true` rows across
  a watchlist to flag releases Discogs has quietly removed from sale.
- **Seller-quality screening** — filter listings by `seller_rating_pct >= 99.0` and
  `seller_rating_count >= 100` for a curated high-trust subset.

### ⚙️ How to use it

1. Open the Actor input form on the Apify Console.
2. Provide **either** `releaseIds` (recommended — direct, no ambiguity) **or**
   `searchQuery` — not both. Find a release ID in any Discogs URL:
   `discogs.com/release/249504-Rick-Astley-...` → `249504`.
3. If using `searchQuery`, set `maxSearchResults` to cap how many top hits to fetch
   (default 5, max 50).
4. Set `maxPagesPerRelease` (default 4 = 100 listings) and `maxListingsPerRelease`
   (default 100, max 500) to control per-release volume. The lower of the two wins.
5. Leave `includeStatsRow` on (default) to also receive one aggregate
   `row_type="stats"` row per release. Turn off if you only want per-listing rows.
6. Leave `useProxy` on (default) — the proxy is mandatory for the Discogs marketplace
   HTML surface on Apify datacenter IPs.
7. Click **Start**. Results stream into the default dataset in JSON, CSV, Excel, or XML.

#### Single release, first page

```json
{
  "releaseIds": [249504],
  "maxPagesPerRelease": 1,
  "maxListingsPerRelease": 25,
  "includeStatsRow": true,
  "useProxy": true
}
```

#### Search-driven, top 5 results, two pages each

```json
{
  "searchQuery": "nirvana nevermind",
  "maxSearchResults": 5,
  "maxPagesPerRelease": 2,
  "maxListingsPerRelease": 50,
  "includeStatsRow": true
}
```

### 📥 Input

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `releaseIds` | integer\[] | XOR | — | List of Discogs release IDs (1-100). XOR with `searchQuery`. |
| `searchQuery` | string | XOR | — | Free-text Discogs search; top N results become release IDs. XOR with `releaseIds`. |
| `maxSearchResults` | integer | no | `5` | Cap on results from `searchQuery` (1-50). |
| `maxPagesPerRelease` | integer | no | `4` | Cap on listing pages per release (1-20; 25 listings per page). |
| `maxListingsPerRelease` | integer | no | `100` | Hard cap on listing rows per release (1-500). |
| `includeStatsRow` | boolean | no | `true` | Emit one extra `row_type="stats"` row per release. |
| `useProxy` | boolean | no | `true` | Route through Apify Proxy (`BUYPROXIES94952`). Required on Apify datacenter IPs. |

Exactly one of `releaseIds` or `searchQuery` must be provided. Passing both or neither
raises a validation error before any network call fires.

### 📤 Output

One row per marketplace listing (and optionally one extra stats row per release),
pushed to the default dataset and available as JSON, CSV, Excel, or XML.

```json
{
  "row_type": "listing",
  "release_id": 249504,
  "release_title": "Never Gonna Give You Up",
  "artist": "Rick Astley",
  "year": 1987,
  "country": "UK",
  "format_name": "Vinyl",
  "format_descriptions": ["7\"", "45 RPM", "Single", "Stereo"],
  "genres": ["Electronic", "Pop"],
  "master_id": 96559,
  "release_url": "https://www.discogs.com/release/249504",
  "listing_id": 3761251765,
  "listing_url": "https://www.discogs.com/sell/item/3761251765",
  "asking_price": 0.5,
  "asking_currency": "GBP",
  "shipping_text": "+£15.00",
  "condition_media": "Very Good Plus (VG+)",
  "condition_sleeve": "Generic",
  "seller_username": "Ronan266",
  "seller_rating_pct": 100.0,
  "seller_rating_count": 35,
  "seller_country": "United Kingdom",
  "stats_lowest_price": null,
  "stats_lowest_currency": null,
  "stats_num_for_sale": null,
  "stats_blocked_from_sale": null,
  "scraped_at": "2026-05-16T12:00:00.000Z"
}
```

#### Export formats

- **JSON** — full fidelity, all 27 fields, newline-delimited
- **CSV** — flat, one row per listing or stats record
- **Excel** — `.xlsx` via the Apify dataset converter
- **XML** — structured per-item

All formats are available via the Apify API:
`GET /datasets/{id}/items?format=csv&clean=true`

### 💰 Pricing

Pay-Per-Event (PPE) — you pay only for what you use:

| Event | Price (USD) | When |
|---|---|---|
| `actor-start` | $0.20 | Once per run, at boot |
| `result-row` | $0.005 | Per listing OR per stats row written |

#### Example costs

| Run | Rows | Cost |
|---|---|---|
| 1 release × 25 listings + 1 stats row | 26 | $0.18 |
| 5 releases × 100 listings + 5 stats rows | 505 | $2.58 |
| 10 releases × 100 listings + 10 stats rows | 1,010 | $5.10 |
| 50 releases × 100 listings + 50 stats rows | 5,050 | $25.30 |

At scale the per-row charge dominates: ~$5.20 per 1,000 rows. Pricing reflects the
commercial value of hand-parsed per-listing data (asking price, condition grade, seller
country, seller rating) that the official Discogs API does not surface.

### 🚧 Limitations

- **Asking prices only — no closed-sale history.** Discogs hosts sold-price data at
  `/sell/history/{release_id}` but the page is gated behind account login. Without
  user OAuth credentials it is inaccessible — out of scope for this Actor. What you
  get instead: per-listing asking prices (the live offer side) plus the public
  `lowest_price` aggregate (the floor of the ask side).
- **Public Discogs surfaces only.** No authenticated Discogs API calls, no personal
  token usage, no OAuth flows.
- **One snapshot per run.** Schedule recurring runs via Apify Schedules for
  time-series tracking; nothing persists across runs.
- **25 listings per page** — a Discogs-imposed pagination size. Combined with
  `maxPagesPerRelease: 20`, the hard ceiling is 500 listings per release per run.
- **Currency is not normalised.** Discogs serves prices in the seller's local currency
  for listings and in the request IP's currency for the stats API. Join by the
  `asking_currency` field; a single canonical USD is not available.
- **Throughput ~40 req/min.** A 10-release run with 4 pages each takes roughly
  (10 × (1 + 1 + 4)) × 1.5 s ≈ 90 seconds plus session warm-up.
- **7-day default storage retention** on the Apify FREE tier. Export your dataset
  immediately after the run, or upgrade for longer retention.

### ❓ FAQ

**What is the difference between "discogs price scraper" asking-price data and sold-price history?**

Discogs publishes two distinct price signals: (1) **active asking prices** on the
marketplace listings page — public, and what this Actor delivers — and (2)
**completed-sale prices** at `/sell/history/{release_id}` — login-walled, not
accessible without user OAuth. For most reseller and arbitrage workflows the live
asking-price distribution is the more actionable signal: it tells you what the market
is asking right now, not what it cleared months ago.

**Does this Actor work as a Discogs marketplace API alternative?**

Yes. The official Discogs API only exposes `lowest_price` and `num_for_sale` aggregates
for any given release. This Actor adds the full per-listing detail — who is selling,
at what price, in what condition, shipping from where — by joining the REST endpoints
with the marketplace HTML. It is the data layer the official API does not provide.

**How does it handle blocks and Cloudflare protection?**

We take care of that. The Actor uses browser-fingerprint impersonation (curl-cffi),
a session warm-up sequence, residential proxy rotation, and exponential backoff with
`Retry-After` support. You run the Actor; we handle the connection layer.

**Why does this Actor need a custom `User-Agent` for Discogs?**

Discogs enforces a written API policy ([Discogs Developer Terms](https://www.discogs.com/developers/))
requiring every API request to carry an `Application-Name/Version` style
`User-Agent`. The Actor sends `DevilScrapes/0.1 (+https://apify.com/DevilScrapes)` on
every API call automatically — nothing to configure.

**What does `stats_blocked_from_sale=true` mean?**

It means Discogs has flagged the release as un-sellable — a legal, ToS, or licensing
restriction. `stats_num_for_sale=0` alongside it confirms zero active listings. To
identify scarce releases, use `WHERE stats_num_for_sale < 5`.

**Can I scrape my own wantlist or private collection?**

No. This Actor scrapes only public marketplace data. Private user data (wantlist,
collection, messages) requires Discogs OAuth, which is intentionally out of scope.

**Can I fetch more than 100 release IDs in one run?**

No. The input model caps `releaseIds` at 100 (and `maxListingsPerRelease` at 500,
`maxPagesPerRelease` at 20). A single run emits at most 100 × 500 + 100 = 50,100 rows.
Split larger workloads across multiple runs and concatenate the datasets.

**Is this the same as a Discogs scraper for bulk catalog export?**

This Actor is optimised for marketplace pricing data — asking prices, condition, seller
metadata — not full catalog metadata export. If your goal is release metadata (tracklist,
labels, formats) at bulk scale, the official Discogs data dump is a better starting
point. If your goal is live pricing intelligence, this is the right tool.

### 💬 Your feedback

Found a bug, hit a rate limit, or need a new field on the output row (median asking
price? seller-country histogram? condition-grade distribution)? Open an issue on the
Actor's Apify Store page or reach the Devil Scrapes team at
[apify.com/DevilScrapes](https://apify.com/DevilScrapes). We ship updates within days
of validated reports.

# Actor input Schema

## `releaseIds` (type: `array`):

List of Discogs release IDs (integers) to scrape. Either <code>releaseIds</code> OR <code>searchQuery</code> must be provided — not both. Find a release ID in the URL: <code>discogs.com/release/<b>249504</b>-Rick-Astley-...</code>.

## `searchQuery` (type: `string`):

Free-text Discogs search. The Actor resolves the top <code>maxSearchResults</code> hits into release IDs. Either <code>releaseIds</code> OR <code>searchQuery</code> must be provided — not both.

## `maxSearchResults` (type: `integer`):

Cap on releases resolved from <code>searchQuery</code>. Ignored when <code>releaseIds</code> is set.

## `maxPagesPerRelease` (type: `integer`):

Hard cap on listings pages fetched per release (each page = 25 listings). Combined with <code>maxListingsPerRelease</code>.

## `maxListingsPerRelease` (type: `integer`):

Hard cap on listing rows emitted per release. Lower wins when both this and <code>maxPagesPerRelease</code> are set.

## `includeStatsRow` (type: `boolean`):

When true, emit one extra <code>row\_type="stats"</code> row per release with the public marketplace aggregate (lowest\_price, num\_for\_sale, blocked\_from\_sale).

## `useProxy` (type: `boolean`):

Route requests through Apify Proxy (<code>BUYPROXIES94952</code>). Default <strong>ON</strong> — Cloudflare on the Discogs marketplace HTML surface 403s un-proxied Apify datacenter IPs. Turn off only when running locally from a residential ISP.

## Actor input object example

```json
{
  "releaseIds": [
    "249504",
    "10843"
  ],
  "searchQuery": "nirvana nevermind",
  "maxSearchResults": 5,
  "maxPagesPerRelease": 4,
  "maxListingsPerRelease": 100,
  "includeStatsRow": true,
  "useProxy": true
}
```

# Actor output Schema

## `datasetItems` (type: `string`):

All dataset items as JSON.

## `datasetItemsCsv` (type: `string`):

Same data exported to CSV.

## `datasetView` (type: `string`):

Open the run dataset in the Console.

# 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 = {
    "releaseIds": [
        "249504"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("devilscrapes/discogs-sold-price").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 = { "releaseIds": ["249504"] }

# Run the Actor and wait for it to finish
run = client.actor("devilscrapes/discogs-sold-price").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 '{
  "releaseIds": [
    "249504"
  ]
}' |
apify call devilscrapes/discogs-sold-price --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,devilscrapes/discogs-sold-price"
        }
    }
}

```

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/w22F5odGnHnd0lJ0e/builds/JqSHSQsbrgqGXvHsz/openapi.json
