How to Scrape Brave Shopping Prices in Python

By Serpent API Team · · 8 min read

Brave Shopping product data — title, merchant, price, currency, URL, thumbnail, and a sponsored flag — comes back as clean JSON through a shopping API. There is no official Brave 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 working Python example: query engine=brave (Brave's coverage is strongest for US product queries), normalize the price strings, handle the placements that have no price, store the results to CSV or SQLite, and alert when a price drops.

TL;DR: Brave exposes no public API for its product search results. The fastest reliable way to scrape Brave Shopping in Python is one HTTPS call to the Brave Shopping API with an X-API-Key header, then parse results.shopping. Below is the exact script, including price normalization, CSV/SQLite storage, and a price-drop alert.

Table of Contents

Why scrape Brave Shopping at all

Google Shopping gets nearly all the attention. Every SERP provider, scraper library, and tutorial covers Google Shopping prices. Brave is the newest major engine, and its product listings are rarely exposed as data — which 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 workflow, adding a Brave view gives you a cross-market check on what a shopper actually sees on another engine. Brave's coverage is strongest for US product queries, so it is a high-value addition to a US-focused monitoring stack. Because all four engines share one endpoint, adding Brave to an existing monitor is a one-parameter change. The pattern is the same one we cover in our shopping price-monitoring playbook.

What Brave actually exposes

First, the honest picture. Brave does not have a public API for reading its product search results. Its product search returns a compact set of listings, strongest for US queries, and those listings are placements — some carry a sponsored flag. Two practical consequences for a scraper:

Those aren't bugs in an API — they are properties of the index. Any tool that reads Brave Shopping runs into the same shape, because the placement itself only shows that much.

The fields the API returns

The Brave Shopping API returns each product with the fields below. Here is one example item from a live sony headphones query (August 6, 2026):

{
  "position": 2,
  "title": "Sony WH-1000XM6",
  "price": "$299.99",
  "currency": "USD",
  "merchant": "Best Buy",
  "store": "Best Buy",
  "url": "https://www.bestbuy.com/product/sony-wh1000xm6-...",
  "thumbnail": "https://img.example.com/products/wh1000xm6.jpg",
  "rating": null,
  "reviews": null,
  "sponsored": true,
  "snippet": "These headphones deliver class-leading noise cancellation..."
}
FieldWhat it is
position1-based slot in the Brave product placements.
titleProduct name.
priceDisplayed price as text, e.g. $299.99. May be null — see below.
currencyISO currency code, e.g. USD.
merchant / storeSeller name, e.g. Best Buy.
urlDirect product URL on the merchant's site.
thumbnailProduct image.
rating / reviewsReview score and count when the index shows them (can be null).
sponsoredtrue when the listing is a paid placement.
snippetA short product description.

Working Python: query, normalize, store

Here is a complete, runnable script. It queries Brave Shopping, normalizes prices to floats, and stores the results to both CSV and SQLite. Replace sk_live_your_key with your key from the dashboard. Authentication is the X-API-Key header.

import csv
import sqlite3
import requests

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

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

def normalize_price(item):
    raw = item.get("price")            # e.g. "$49.99"
    if not raw:
        return None                    # some placements show no price
    return float(raw.replace("$", "").replace(",", ""))

def save_csv(products, path="brave_prices.csv"):
    with open(path, "w", newline="") as fh:
        w = csv.writer(fh)
        w.writerow(["position", "title", "price", "currency",
                    "merchant", "url", "rating", "reviews"])
        for p in products:
            w.writerow([p.get("position"), p.get("title"), normalize_price(p),
                        p.get("currency"), p.get("merchant"), p.get("url"),
                        p.get("rating"), p.get("reviews")])

def save_sqlite(products, db="brave_prices.db"):
    con = sqlite3.connect(db)
    con.execute("""
        CREATE TABLE IF NOT EXISTS products (
            position INTEGER, title TEXT, price REAL, currency TEXT,
            merchant TEXT, url TEXT, rating REAL, reviews INTEGER,
            fetched_at TEXT DEFAULT (datetime('now'))
        )
    """)
    rows = [(p.get("position"), p.get("title"), normalize_price(p),
             p.get("currency"), p.get("merchant"), p.get("url"),
             p.get("rating"), p.get("reviews")) for p in products]
    con.executemany(
        "INSERT INTO products (position,title,price,currency,merchant,url,rating,reviews) "
        "VALUES (?,?,?,?,?,?,?,?)", rows)
    con.commit()
    con.close()

products = brave_shopping("sony wh-1000xm5 headphones")
save_csv(products)
save_sqlite(products)

with_price = sum(1 for p in products if normalize_price(p) is not None)
print(f"{len(products)} products saved, {with_price} with a price.")

Run it and the script prints something like:

20 products saved, 17 with a price.

Notice two things. First, the result list is compact — that is Brave's placement, not a missing feature. Second, some products came back without a price. normalize_price returns None for those, so the CSV and SQLite rows get an empty price cell instead of a made-up number.

Price-drop alerting

To alert on drops, store a baseline lowest price, then compare each run. Using the normalized helper above, the comparison is a few lines:

def lowest(products):
    priced = [normalize_price(p) for p in products
              if normalize_price(p) is not None]
    return min(priced) if priced else None

baseline = 348.00                        # lowest price from a previous run
current = lowest(brave_shopping("sony wh-1000xm5 headphones"))

if current is not None and current < baseline:
    print(f"PRICE DROP: {baseline:.2f} -> {current:.2f}")

Wire that into a scheduler, and you have a Brave price monitor. For a fuller alerting setup — thresholds, dropping duplicate offers, and notification delivery — see our price-drop alerts with a Shopping API guide.

What it costs

Brave 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:

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 and the Shopping API docs for the endpoint reference, and the Shopping API hub to compare all four engines (Google, Yahoo, DuckDuckGo, and Brave) on the same endpoint.

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 monitor. Start at $0.03 per 1,000 at the Scale tier.

FAQ

Is there an official Brave Shopping API?

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

How many Brave Shopping results can I get per query?

The Shopping API accepts num up to 20 products per call (default 10). Brave returns a compact set of product listings per query, so treat the response length as what the placement showed rather than a fixed page size.

Are Brave Shopping results organic or paid?

Brave product placements can carry a sponsored flag, so expect some listings marked sponsored rather than a clean organic/ads split. The sponsored field tells you which ones are placements.

Why is the price sometimes null?

Some Brave product placements render without a displayed price at the time the page is read. The API returns price: null rather than inventing a number. Always handle null prices in your normalization step.

Can I scrape Brave 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, consent flow, or other machinery yourself.