Scrape Wine-Searcher's 1,054 grape variety pages, from Pinot Noir to obscure indigenous grapes. Get popularity, critic scores and prices with auto-detected currency, plus product names and grape blends. Export, schedule, or pipe into Zapier, Make, and n8n.
All notable changes to Wine-Searcher Grape Scraper are documented here.
v2.2.1 (2026-06-01)
Fixed (SEV-4 contract drift)
tabFilter input now consistently uses the Wine-Searcher canonical values: mostpopular | best | bestvalue | mostexpensive | cheapest. Previously the input_schema declared this enum, but the TypeScript type accepted any string (| string escape) and the runtime default was 'all' (not in the enum). Result: user-supplied valid schema values worked by luck; the default sent ?tab_F=all regardless of schema. Now all three sources of truth (schema, type, runtime default) agree on mostpopular as the default.
Defensive runtime guard: unknown tabFilter values log a warning and fall back to mostpopular instead of being passed through to Wine-Searcher.
v2.2.0 (2026-06-01)
Changed (breaking - field rename)
The grape field on each wine record is renamed to region, and grape_url is renamed to region_url. Wine-Searcher's listing pages now render Region links (/regions-XXX) in the column that previously held Grape links (/grape-XXX). The v2.0/v2.1 parser silently produced empty grape strings on 100% of rows because the underlying selector pattern stopped matching. The rename aligns the field name with the actual data shape and the page header label.
source_grape and source_grape_id (the input-URL-level grape identifiers) are unchanged.
Output schema (dataset_schema.json) updated to reflect the new field names: region displayed as "Region", region_url displayed as "Region Link".
Parser selector changed from a[href*="/grape-"] to a[href^="/regions-"].
Added two test invariants in tests/parser.test.ts that would have caught the original bug: (1) at least 50% of rows must carry a non-empty region, and (2) every region_url (when present) must match /regions- pattern.
Migration notes
Consumers that read the grape field were already getting empty strings on v2.0/v2.1. The v2.2 rename surfaces this as a clean schema break: update field references from record.grape to record.region (and similarly for url).
If you need the per-grape source identity, use source_grape (e.g., "Pinot Noir") - that field reflects the input URL's grape and is populated correctly.
v2.1.0 (2026-05-31)
Changed
Migrated currency module (fetchExchangeRates, extractPriceAmount, convertAmount) to wine-core@0.3.0 (consolidation, no behavior change). The local src/currency.ts and its tests/currency.test.ts were removed; coverage is preserved in packages/wine-core/tests/currency.test.ts.
Bundle size: 146 KB -> 145 KB (slight decrease).
v2.0.0 (2026-05-29)
Changed (major)
Full rewrite from Python to TypeScript. Source converted from Python 3.12 + Crawlee + Playwright + Camoufox (2719 LOC) to TypeScript + fetch + cheerio (~800 LOC). Behavior preserved end-to-end: same output schema, same field extraction, same multi-currency parsing (24+ symbols / ISO codes / 2-letter country markers), same pagination, same PPE event (wine-result @ $0.003), same input options.
PerimeterX-direct fallback path: in v1.x the Actor could bypass ZenRows and hit Wine-Searcher directly with the Camoufox stack as a last resort. v2.0 drops this entirely (would require re-implementing equivalent stealth in TS, which doesn't exist as of 2026-05).
anti_detection.py (591 LOC) module deleted.
Operational regression (consciously accepted)
ZENROWS_API_KEY is now mandatory at runtime. If the env var is missing (Actor settings: secret zenrowsApiKey), the run aborts cleanly with an explicit status message and exits without producing data. There is no browser fallback. This trade-off was approved to retire ~1000 LOC of browser anti-detection plumbing that had become a maintenance burden.
Migration notes
The Python source (requirements.txt, src/*.py, tests/test_*.py) was removed from the repo. Git history preserves the legacy implementation.
Output schema (dataset_schema.json, input_schema.json, output_schema.json) is unchanged: existing consumers see no row-shape difference.
PPE event (wine-result at $0.003) is unchanged.
scripts/refresh_grape_list.py is kept as a one-shot Python utility (excluded from the Docker image) for refreshing the ALL_GRAPES data table from Wine-Searcher's grape index.
v1.0.1 (2026-05-18)
Added
Sister Actor cross-promotion to wine-searcher-scraper-from-list in both the "Which wine scraper should I use?" comparison table and the Related Actors list
Canonical PPE event table (Event | Price | When triggered) above the Plan capacity table, exposing the wine-result @ $0.003 event
Changed
Dropped legacy "Rankings & Prices" wording from all 4 titles: actor.json title, seoTitle, input_schema.json title, output_schema.json title and description
All titles now use hyphenated "Wine-Searcher" (was inconsistent mix of "Wine Searcher" and "Wine-Searcher")
README H1, intro paragraph and "What does X do?" H2 reordered to popularity-first: "popularity, scores & prices"
seoDescription: extended from 140 to 153 chars (within 145-160 target), reordered popularity-first, added "Pay-per-event" framing
actor.json description rewritten to drop "popularity rankings" wording in favor of "popularity"
Cross-promo table cell wording aligned with the new popularity-first positioning
Removed all em-dashes (zero em-dash policy 2026-05-18)
v1.0.0 (2026-05)
Major pivot: this Actor now scrapes grape variety pages instead of region pages. Wine-Searcher modified its region page layout, breaking the previous scraper. Grape pages retained the legacy DOM structure, so the Actor was redirected at v1.0.
Breaking changes
Input field region renamed to grape (dropdown of 1054 grape varieties)
Input field customUrl pattern: /grape-{id}-{slug} instead of /regions-...
Input field maxPagesPerRegion renamed to maxPagesPerGrape
Removed: scrapeSubRegions, maxDepth (no hierarchy among grapes)
Added: scrapeAllGrapes checkbox to crawl every grape variety in one run
Output field region replaced with source_grape (page name, e.g. "Pinot Noir") + source_grape_id (Wine-Searcher numeric ID)
What stayed the same
All other output fields (product_name, product_url, grape, grape_url, popularity, critics_score, avg_price, currency, avg_price_converted, target_currency, source_url)
The tabFilter sort (Most Popular / Best Rated / Best Value / Most Expensive / Cheapest)
The currency conversion feature (now backed by a 300+ currency live feed, see v1.0.x entries)
Pay-per-event pricing at $0.003/wine
The Camoufox + ZenRows anti-detection stack
If you had scheduled runs on the v0.x region scraper, they will now log a deprecation warning and require reconfiguration with the new grape or customUrl fields.
Earlier history (v0.1 to v0.3, 2025-12 to 2026-04): region scraper era
Before the v1.0.0 pivot, this Actor scraped Wine-Searcher region pages instead of grape pages, under the slug wine-searcher-region-scraper. Across the v0.x line it accumulated: pay-per-event pricing, the Camoufox + ZenRows anti-detection stack, the 30-currency conversion via ECB rates (predecessor of today's 300+ live feed), a 146-region dropdown, recursive sub-region crawling, parser hardening for ~30 currency symbols (Rs / Rp / RM / R / kn / лв / Kč), price-column SVG-marker extraction, popularity-rank leak fix, proxy-country URL cleanup, and graceful exits.
When Wine-Searcher changed its region-page layout in 2026-05, the region scraper broke. The grape pages kept the legacy DOM, so the Actor was redirected to grape pages at v1.0.0. All of the v0.x engineering (anti-detection, currency parsing, pay-per-event billing) carried over and remains live in v1.0.x.