Scrape DuckDuckGo Shopping Prices in Python

By Serpent API Team · · 7 min read

DuckDuckGo Shopping product data — title, merchant, price, currency, URL, thumbnail, rating, and a sponsored flag — comes back as clean JSON through a shopping API. There is no official DuckDuckGo Shopping search API, so the practical path for a Python script is a shopping API that returns the product listings as structured data.

This guide shows you exactly how, with a tested Python example, the real response shape, and the honest limits you'll hit (DuckDuckGo does not return everything a Google Shopping scraper would).

TL;DR: DuckDuckGo exposes no public API for its Shopping search results. The fastest reliable way to scrape DuckDuckGo Shopping in Python is one HTTPS call to the DuckDuckGo Shopping API with an X-API-Key header, then parse results.shopping. Below is the exact script — query, normalize prices, handle null, store to CSV or SQLite, and alert on drops.

Table of Contents

Why scrape DuckDuckGo Shopping at all

Google Shopping gets nearly all the attention. Every SERP provider, scraper library, and tutorial covers Google Shopping prices. DuckDuckGo product data is the quieter sibling — and that is exactly why it is worth a look.

Fewer people track it means less competition for the data you collect. If you run a price-monitoring or price-intelligence workflow, adding a second product source gives you a cross-market check on what a shopper actually sees on another engine. DuckDuckGo's product coverage is broad across everyday consumer queries, which makes it a useful complement rather than a low-quality mirror.

What DuckDuckGo actually exposes

First, the honest picture. DuckDuckGo does not have a public API for reading Shopping search results. Its product search returns a compact set of product-listing cards, and those placements are paid slots. That means two practical consequences for a scraper:

Those aren't bugs in an API — they are properties of the source. Any tool that scrapes DuckDuckGo Shopping runs into the same ceiling, because the page itself only shows that much.

The fields the API returns

The DuckDuckGo Shopping API returns each product with the fields below. Here is an example item from an iphone 15 case query:

{
  "position": 1,
  "title": "Spigen Mag Armor MagFit for iPhone 15",
  "price": "$49.99",
  "currency": "USD",
  "merchant": "spigen.com",
  "store": "spigen.com",
  "url": "https://spigen.com/products/...",
  "thumbnail": "https://.../product.jpg",
  "rating": 4.5,
  "reviews": 342,
  "sponsored": true,
  "snippet": "Shockproof military-grade case with built-in MagSafe..."
}
FieldWhat it is
position1-based slot in the DuckDuckGo product listings.
titleProduct name.
priceDisplayed price as text, e.g. $49.99. May be null — see limits below.
currencyISO currency code, e.g. USD. A separate field.
merchant / storeSeller name, e.g. spigen.com.
urlDirect product URL on the merchant's site.
thumbnailProduct image.
rating / reviewsReview score and count when the source shows them (often null).
sponsoredtrue when the listing is a paid placement.
snippetA short product description.

Working Python: one call to a product table

Here is a complete, runnable script. It queries DuckDuckGo Shopping, prints a table, and handles the two things that trip people up: missing prices and the short result list.

Replace sk_live_your_key with your key from the dashboard. Authentication is the X-API-Key header.

import re
import requests

API_KEY = "sk_live_your_key"
BASE_URL = "https://apiserpent.com/api/shopping"

def ddg_shopping(query, num=10):
    resp = requests.get(
        BASE_URL,
        params={"q": query, "engine": "ddg", "num": num},
        headers={"X-API-Key": API_KEY},
        timeout=60,
    )
    resp.raise_for_status()
    return resp.json()["results"]["shopping"]

def to_number(price):
    """'$49.99' -> 49.99 ; None -> None"""
    if not price:
        return None
    m = re.search(r"\d+[\.,]?\d*", price)
    return float(m.group().replace(",", "")) if m else None

products = ddg_shopping("iphone 15 case")

print(f"{'#':>2} {'title':38} {'price':10} {'merchant':22}")
print("-" * 76)
for p in products:
    print(f"{p['position']:>2} {p['title'][:38]:38} {p.get('price') or 'n/a':10} {p.get('merchant','')[:22]:22}")

priced = sum(1 for p in products if to_number(p.get("price")) is not None)
print(f"\n{len(products)} products, {priced} with a numeric price, "
      f"{len(products) - priced} without.")

Run it and you get a table like this (trimmed):

 # title                                       price      merchant
 1 Spigen Mag Armor MagFit for iPhone 15      $49.99     spigen.com
 2 iPhone 15 Clear Case                        n/a        casetify.com
 3 OtterBox Defender for iPhone 15            $44.95     otterbox.com

10 products, 8 with a numeric price, 2 without.

Notice two things. First, the result list is short — that is DuckDuckGo's own product surface, not a missing feature. Second, one product came back without a price. The script treats that honestly instead of pretending a number exists.

Store to CSV or SQLite

Once you have parsed JSON, persisting it is a few lines. To CSV:

import csv

with open("ddg_shopping.csv", "w", newline="", encoding="utf-8") as f:
    w = csv.writer(f)
    w.writerow(["position", "title", "price", "currency", "merchant", "url", "rating", "reviews"])
    for p in products:
        w.writerow([
            p["position"], p["title"], p.get("price"), p.get("currency"),
            p.get("merchant"), p.get("url"), p.get("rating"), p.get("reviews"),
        ])

Or to SQLite, which scales better for a long-running history:

import sqlite3

conn = sqlite3.connect("prices.db")
conn.execute("""CREATE TABLE IF NOT EXISTS shopping (
    position INTEGER, title TEXT, price TEXT, currency TEXT,
    merchant TEXT, url TEXT, rating REAL, reviews INTEGER,
    fetched_at TEXT DEFAULT CURRENT_TIMESTAMP)""")

for p in products:
    conn.execute(
        "INSERT INTO shopping (position, title, price, currency, merchant, url, rating, reviews) VALUES (?,?,?,?,?,?,?,?)",
        (p["position"], p["title"], p.get("price"), p.get("currency"),
         p.get("merchant"), p.get("url"), p.get("rating"), p.get("reviews")),
    )
conn.commit()

Alerting on price drops

With a history table, price-drop detection is a comparison against yesterday's lowest offer per product. Every run, convert the price string with to_number(), skip None values, and compare:

# daily job - assume prev holds the previous lowest per product title
for p in products:
    num = to_number(p.get("price"))
    if num is None:
        continue                      # no price shown - nothing to compare
    if num < prev.get(p["title"], float("inf")):
        print(f"PRICE DROP on {p['title']}: now {p.get('price')}")

For the full schedule-and-diff pattern, see our shopping price-monitoring playbook; to turn a drop into an email or webhook notification, the price-drop alerts guide has the details.

Honest limits: what DuckDuckGo won't give you

Scraping DuckDuckGo Shopping has hard ceilings, and a good scraper plans around them:

If you need deep catalogs, delivery data, or discount history, a dedicated product feed or a marketplace's own API is the right tool. For a fast, clean read of what DuckDuckGo Shopping shows for a query — which is the realistic use case — a shopping API is the sweet spot. See the Shopping API documentation for the full parameter reference.

What it costs

DuckDuckGo Shopping searches with Serpent are billed per 1,000 calls. New accounts get 10 shared free calls on every endpoint, then pay-as-you-go. Current tiers:

TierPer 1,000 callsPer call
Default$0.60$0.0006
Growth$0.06$0.00006
Scale$0.03$0.00003

There is no per-page multiplier on shopping and no subscription — credits never expire. See the pricing page for the full table. If you want the Google-shaped view of the same product data instead, the Google Shopping API engine returns link/source/product_rating fields, and the Shopping API hub compares all four engines.

Next step: pull your key from the dashboard, hit the live playground to see the raw response for your own query, then wire the script above into your daily price monitoring job. Start at $0.03 per 1,000 at the Scale tier.

FAQ

Is there an official DuckDuckGo Shopping API?

No. DuckDuckGo does not offer a public API that reads its product search listings. A shopping API that returns the product listings as JSON is the standard workaround.

Are DuckDuckGo Shopping results organic or paid?

They are paid placements. Every product card in the results is a paid slot, so the sponsored field is true across the board rather than an organic/ads split.

Why is my result list so short?

DuckDuckGo shows a compact set of product listings per query. The endpoint accepts num up to 20 but returns as many as the source shows; it cannot exceed what DuckDuckGo surfaces.

Why is the price sometimes null?

Some placements render without a visible price at read time. The API returns price: null rather than inventing a number.

Can I scrape DuckDuckGo Shopping without a browser?

Yes — that is the whole point of the API. One HTTPS request with an X-API-Key header returns parsed JSON. You never run a headless browser or consent flow yourself.