Unofficial Python client for Tweakers.net Pricewatch — the biggest price comparison site in the Netherlands.
Lets you search products, browse categories, pull current shop prices, and grab full price history (years of daily min/avg data). All from reverse-engineered endpoints, no API key needed.
Just requests as a dependency. Copy tweakers.py + categories.json into your project, or:
pip install requests
from tweakers import TweakersClient
client = TweakersClient()results = client.search("DDR5 6000")
for r in results:
print(f"[{r.product_id}] {r.name} — {r.url}")Or grab just the first match:
r = client.search_one("F4-3600C16D-32GTZN")
if r:
print(f"{r.name} [{r.product_id}]")List all products in a category with pagination and sorting — 40 items per page:
page = client.browse_category("smartphones", sort="prijs", sort_dir="asc")
print(f"{page.total_count} products, page {page.page}/{page.total_pages}")
for item in page.items[:5]:
print(f" {item.name} — EUR {item.price} ({item.shop_count} shops)")Auto-paginate through all results with browse_all():
for item in client.browse_all("interne-ssds"):
print(f"{item.name}: EUR {item.price}")Limit to the first N pages:
for item in client.browse_all("videokaarten", max_pages=3):
print(f"{item.name}: EUR {item.price}")Sort by "prijs" (price), "popularity", or "score" (rating). See CATEGORIES.md for all 243 available category slugs.
from tweakers import get_categories, category_id
cats = get_categories() # {slug: {id, name}, ...}
cid = category_id("videokaarten") # -> 49Daily min and average prices going back years:
history = client.get_price_history(2064068)
print(f"{len(history.prices)} days of data")
print(f"Lowest ever: EUR {history.lowest_ever} ({history.lowest_ever_date})")
print(f"Last known: EUR {history.last_price} ({history.last_price_date})")
for p in history.prices[-5:]:
print(f" {p.date} min=EUR {p.min_price} avg=EUR {p.avg_price}")Belgium prices too:
history_be = client.get_price_history(2064068, country="be")Structured data from JSON-LD:
info = client.get_product_info(2064068)
print(f"{info.brand} {info.name}")
print(f"MPN: {info.mpn}") # ['KF560C30BBEK2-32']
print(f"EAN: {info.gtin}") # ['0740617342994']
print(f"EUR {info.low_price} - {info.high_price} ({info.offer_count} shops)")All offers:
offers = client.get_current_prices(2064068)
for o in offers:
print(f" {o.shop_name}: EUR {o.price}")Or just the cheapest:
cheapest = client.get_cheapest_offer(2064068)
if cheapest:
print(f"EUR {cheapest.price} at {cheapest.shop_name}")info, offers = client.get_product_details(2064068)pid = TweakersClient.product_id_from_url(
"https://tweakers.net/pricewatch/2064068/kingston-fury-beast-kf560c30bbek2-32.html"
)
# -> 2064068No official API exists. This uses reverse-engineered endpoints:
| Endpoint | Auth | Returns |
|---|---|---|
/ajax/zoeken/pricewatch/?keyword=... |
None | Search results (JSON) |
/{category}/vergelijken/?page=... |
Session cookies | Category listing with pagination (HTML) |
/ajax/price_chart/{id}/{country}/ |
None | Full price history (JSON) |
/pricewatch/{id}/ |
Session cookies | Product page with JSON-LD + shop listings (HTML) |
The search and price history endpoints need zero authentication — they just work. Product and category pages need a session cookie which the client obtains automatically on init by following Tweakers' DPG Media consent redirect.
Category IDs are stored in categories.json (243 categories). To refresh:
python scripts/update_categories.py
- Search returns ~8 results max (it's the autocomplete/suggest endpoint, not full search — the real search page has reCAPTCHA). Use
browse_category()to list products by category instead. - Category browsing and shop offer parsing use regex on HTML, so they may break if Tweakers redesigns the page
- Price history only covers NL and BE
- Be nice with request frequency — there's no rate limiting implemented, so add your own delays if you're hitting it in a loop
MIT