How to Scrape Brave Shopping Prices in Python
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
- What Brave actually exposes
- The fields the API returns
- Working Python: query, normalize, store
- Price-drop alerting
- What it costs
- FAQ
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:
- You get a compact list per query — request up to 20 products per call with
num(default 10). - Expect some listings marked sponsored rather than a clean organic/ads split.
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..."
}
| Field | What it is |
|---|---|
position | 1-based slot in the Brave product placements. |
title | Product name. |
price | Displayed price as text, e.g. $299.99. May be null — see below. |
currency | ISO currency code, e.g. USD. |
merchant / store | Seller name, e.g. Best Buy. |
url | Direct product URL on the merchant's site. |
thumbnail | Product image. |
rating / reviews | Review score and count when the index shows them (can be null). |
sponsored | true when the listing is a paid placement. |
snippet | A 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:
| Tier | Per 1,000 calls | Per 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.


