Google Shopping Scraper — Prices, Merchants & Offers
Pricing
from $3.00 / 1,000 delivered products
Google Shopping Scraper — Prices, Merchants & Offers
Google Shopping scraper by keyword and country: title, price, sale price, merchant, rating, shipping and image per product; offers mode returns up to 5 merchant offers per product with price, delivery, total and link. Watch mode delivers only new or changed rows. Pay per result, no login.
Pricing
from $3.00 / 1,000 delivered products
Rating
0.0
(0)
Developer
architek.earth
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
14 hours ago
Last modified
Categories
Share
Recorded run: 9 products with 24 merchant offers from 10 product IDs in 301 seconds (2026-10-23, build 0.1.7, run mwQKGPC56O2U3wiyK; the 10th product had no offer and was free). Merchant offers cost $12 per 1,000 products here; the one other Google Shopping offers actor in the Store charges $18 per 1,000 on its Bronze tier ($15 to $20 by plan), and the field leader has no offers mode. Keyword search costs $3.00 per 1,000 products, against $3.50 and $5.00 at the two most-used keyword scrapers. Every validation run on 2026-10-23 charged exactly the rows it delivered.
architek.earth — an independent data studio. Pay-per-result scrapers with recorded runs, published canaries and stable schemas, built for people and AI agents.
Google Shopping in two modes. Search: give it up to 50 search terms and a country, and get each product card Google shows, with title, price and currency, sale price, merchant, rating, review count, shipping text, Google product ID and image. Offers: give it up to 100 Google product IDs (the offersId of a search row) and get one row per product with its top merchant offers: merchant, price, old price, delivery text, delivery cost, total, stock and the offer link.
| Source | Market | From | Standout fields |
|---|---|---|---|
| Google Shopping search and product pages (google. | 24 countries; US and GB validated | $3.00 / 1,000 products; $12 / 1,000 offers rows | priceValue + currency, merchants[].total, bestTotalPrice, totalOffersKnown |
✅ Why choose this scraper
- Merchant offers per product, at $12 per 1,000. Offers mode returns the merchants Google shows on a product's page, each with
price,oldPrice,delivery,shippingCost,total,inStock, merchantratingand itsurl, plus the product'sbestPrice,bestTotalPriceandtotalOffersKnown. RunmwQKGPC56O2U3wiyK: 9 of 10 products delivered with 24 offers; 23 of 24 offers had a delivery text and a computed total. - Search feeds offers directly. Every search row carries
offersId, which offers mode takes as-is: search for "laundry detergent", then pass the IDs to get the merchants for each product. - Charges equal delivered rows, measured. 10 validation runs on build 0.1.7: 120 product rows and 21 offers rows delivered, 120
productand 21offerscharges. Status rows, products with no offer, GTIN misses and unchanged watch rows were free. - Localized to the country you pick.
countrysets the Google domain,gland the proxy country. Pages priced in another market's currency, and product pages that show no offer, are fetched again and never delivered. - Price watch that charges only for changes. With
incrementalModeand astateKey, search delivers only new or re-priced products, and offers delivers only products whose offer set or best total price changed: the rerunQMqAcZreUkfkXgYWvcharged 1 of 9 products and skipped 8 unchanged ones for free. - Filters that never cost you. In search mode,
minPrice,maxPrice,condition,freeShippingandonSaledrop cards before delivery; dropped cards and nonsense queries are never charged.
⚠️ Worth knowing up front
- Offers mode returns up to 5 merchant offers per product, the ones Google's product page shows before "More stores" (validation: 3 offers on 7 of 9 products, 1 and 2 where Google knew only 1 and 2).
totalOffersKnownis Google's own count of all offers (up to 378 in validation), not what is delivered. The remaining stores load through a session-bound request this route cannot make. - GTIN lookup is best-effort. Google has no exact barcode lookup: each
gtinsitem is searched and counts only when a result's title carries at least 60% of the words in the title you give. Recorded hit rate: 3 of 10 grocery GTINs on the wall test, 2 of 10 on validation (runsfH2QJgzvav1Pxmh4T,pOwrC9XWNu6uZ4GjL); 2 bogus GTINs were free misses. Every miss is a freeNO_RESULTSrow. PreferproductIdsfrom search rows when you can. - Condition is not a stable field yet. No live row has shown an item condition, so it is left out of the schema table until one does; the search
conditionfilter works on card labels such as used, pre-owned or refurbished. - Search returns the cards on Google's first results page, in its order: up to 40 per query. Page 2 came back empty in the US and GB, so
maxResultsPerQuerystops at 40. offersIdisnullon search cards that Google shows as a single merchant listing without a catalog product (about half of the cards on a recorded US page); those cards have no comparison page.- No product page link: Google's search cards carry IDs only. Offers carry Google's
/gotoredirect link to the merchant's offer.merchantDomainis alwaysnullin search rows. shippingCostis0for free delivery and the amount for a flat delivery price; it isnullfor conditional texts such as "2 hour delivery on orders $10+", and thentotalisnulltoo.bestTotalPriceis the lowest knowntotal.- Pacing measured on 2026-10-23, two requests at a time: 10 US search queries in 106 seconds; 10 offers products in 301 to 344 seconds (product pages are about 1 MB and the SERP proxy takes up to about a minute on some). Each page is fetched with a 90-second timeout and retried once. A product page from another market can show no offer; it is fetched up to 3 times before a free
NO_RESULTS. - All search filters are applied by the actor to the card values: Google ignored the price filter sent to it when tested on 2026-10-22.
freeShippingreads English labels;languageother thanenis accepted but was not validated. - A run stops cleanly 3 minutes before its deadline (
PARTIAL,stopReasonACTOR_DEADLINE), at your spending limit (BUDGET_LIMIT), and after two blocked items in a row (PARTIAL). The deadline is the run timeout, capped at 6 hours. Rerun with the samestateKeyto continue. - Invalid input (
INVALID_INPUT), astateKeystill held by another run (STATE_BUSY), no matching products or offers (NO_RESULTS), a blocked, failed or other-market page (BLOCKED,SOURCE_ERROR,SOURCE_CHANGED), the deadline (ACTOR_DEADLINE) and the spending limit (BUDGET_LIMIT) each write a free status row ("recordType": "status"). An unconfirmed delivery (RECONCILIATION_REQUIRED) or a storage fault fails the run and replays nothing.
⚡ Try the example
This actor has no free preview of its own; the example below costs $0.036 (3 products with their offers).
Open the actor's input page, switch to JSON, paste this and start the run:
{"mode": "offers","country": "us","productIds": ["catalogid:3426141390508526362", "catalogid:14697744329736728216", "catalogid:9908745006175137581"]}
For keyword search use {"queries": ["sony wh-1000xm5"], "country": "us", "maxResultsPerQuery": 10} ($0.03), then pass the offersId values of the rows you want into productIds.
⚙️ Input
| Parameter | Type | Default | Description |
|---|---|---|---|
mode | select | search | search: product cards by keyword ($0.003 per product). offers: merchant offers per product ($0.012 per product). |
queries | array | — (prefill ["sony wh-1000xm5"]) | Search mode. 1–50 product searches, as typed into Google Shopping. Duplicates are removed. Ignored in offers mode. |
productIds | array | — | Offers mode. Up to 100 IDs: a search row's offersId (catalogid:<id> or gpcid:<id>), or a search row's productId digits (read as a catalog ID). |
gtins | array | — | Offers mode. Up to 100 objects {"gtin": "...", "title": "..."}; the title is required for the match check. Best-effort, misses are free. |
country | select | us | us, gb, ca, au, nz, ie, de, fr, es, it, nl, be, at, ch, se, no, dk, pl, in, jp, br, mx, sg, za. Sets the Google domain, gl, the proxy country and the expected currency. |
language | string | en | Google hl code, for example en, en-GB, de. Validated with en. |
maxResultsPerQuery | integer | 40 (prefill 10) | Search mode. 1–40 products per query. |
minPrice | number | — | Search mode. Keep cards priced at least this much, in the country's currency. |
maxPrice | number | — | Search mode. Keep cards priced at most this much, in the country's currency. |
condition | select | any | Search mode. any; new drops cards labelled used, pre-owned or refurbished; used keeps only those. |
freeShipping | boolean | false | Search mode. Keep cards whose shipping text says free. |
onSale | boolean | false | Search mode. Keep cards with a was-price or a percent-off badge. |
incrementalMode | boolean | false | Needs stateKey. Search: only products new or re-priced since the last run (identity: query, country, productId). Offers: only products whose offer set or best total price changed (identity: country, offersId, gtin). |
stateKey | string | — | Any name up to 100 characters. Keeps the watch baseline and lets an interrupted run resume. One key per set of inputs. |
feedback | string | — | Optional note for the maintainers. It is logged and read by a human, and it never changes output. |
The run's spending limit (maxTotalChargeUsd, set in the run options) caps how many rows are delivered.
🧱 Stable fields
These top-level fields keep the same name and the same kind of value in every release. Fields are only ever added, never renamed or removed, so an n8n, Make, Zapier or spreadsheet pipeline can bind to them.
Search rows (recordType product):
| Field | Type | Meaning |
|---|---|---|
query | string | The search term as run (whitespace collapsed). |
country | string | The requested country code, for example us. |
position | number | Rank of the product on Google's page for this query (1 = first), before your filters. |
title | string | Product title as Google shows it. |
price | string | Displayed price, for example $254.99 or £290+. |
currency | string | ISO currency of price, for example USD. |
priceValue | number | price as a number; the lowest price for + ("from") prices. |
originalPrice | number, nullable | The was-price Google shows next to a sale price. |
onSale | boolean | true when the card shows a was-price or a percent-off badge. |
merchant | string, nullable | Merchant name on the card; null on multi-seller "from" cards. |
merchantDomain | string, nullable | Always null in this version (cards carry no domain). |
rating | number, nullable | Product rating out of 5. |
reviewCount | number, nullable | Displayed review count (15K = 15000). |
shippingText | string, nullable | Shipping text, for example Free delivery. |
productId | string | Google's catalog ID for the product (data-cid); the group or offer ID when the card has none. |
offersId | string, nullable | The value offers mode takes in productIds (catalogid:<id> or gpcid:<id>); null on single-merchant cards. |
imageUrl | string, nullable | Google's product thumbnail (encrypted-tbn*.gstatic.com). |
scrapedAt | string | Collection time, UTC ISO 8601. |
Offers rows (recordType offers):
| Field | Type | Meaning |
|---|---|---|
gtin | string, nullable | The GTIN you gave, on rows that came from gtins. |
productId | string | Google's catalog or group ID (the digits of offersId). |
offersId | string | catalogid:<id> or gpcid:<id>, as requested or as matched for a GTIN. |
title | string, nullable | Product title on Google's product page. |
country | string | The requested country code. |
offerCount | number | Offers delivered in merchants (1–5). |
totalOffersKnown | number, nullable | Google's count of all offers for the product. |
bestPrice | number, nullable | Lowest offer price. |
bestTotalPrice | number, nullable | Lowest known offer total (price plus delivery). |
currency | string | The country's currency, for example USD. |
merchants | array | One object per offer: merchant, merchantId, offerId, offerTitle, price, priceText, currency, oldPrice, delivery, shippingCost, total, inStock, rating (the merchant's, out of 5), returns, url (Google's /goto link). Any of them can be null. |
scrapedAt | string | Collection time, UTC ISO 8601. |
recordType is product, offers or status. Data rows also carry deliveryId (unique per delivered row) and, in watch mode, changeType (new, priceChanged or offersChanged). Status rows carry recordType, mode, inputRef, status and message. Other fields are extras, not part of the contract. To have one pinned, say so in feedback.
📦 Output example
Abridged real offers row from run mwQKGPC56O2U3wiyK on 2026-10-23 (2 of its 3 merchants shown, links shortened); values are a historical snapshot.
{"recordType": "offers","gtin": null,"productId": "3745473023353098900","offersId": "catalogid:3745473023353098900","title": "Persil Liquid Laundry Detergent Original","country": "us","offerCount": 3,"totalOffersKnown": 377,"bestPrice": 12.97,"bestTotalPrice": 18.98,"currency": "USD","merchants": [{"merchant": "Walmart", "merchantId": "8175035", "offerId": "8466545222292388478", "offerTitle": "Persil Deep Clean Liquid Laundry Detergent, Original, 84 fl oz, 64 loads", "price": 12.97, "priceText": "$12.97", "currency": "USD", "oldPrice": null, "delivery": "Delivery between Monday - Tuesday $6.99", "shippingCost": 6.99, "total": 19.96, "inStock": true, "rating": 4.3, "returns": "Free 90-day returns", "url": "https://www.google.com/goto?url=CAEStwEB6zsw..."},{"merchant": "Target", "merchantId": "10046", "offerId": "14342441942682820700", "offerTitle": "Persil Original HE Deep Cleaning Concentrated Liquid Laundry Detergent 64 Loads - 84 fl oz", "price": 12.99, "priceText": "$12.99", "currency": "USD", "oldPrice": null, "delivery": "Delivery between Monday - Thursday $5.99", "shippingCost": 5.99, "total": 18.98, "inStock": true, "rating": 4.5, "returns": "90-day returns", "url": "https://www.google.com/goto?url=CAESvQIB6zsw..."}],"scrapedAt": "2026-10-23T14:23:32.140676+00:00","changeType": "new","deliveryId": "6d9bc6ec8004b1732d3f9beca972ab8a-0"}
A search row (run mlaXxQhmpNvpT1go5, 2026-10-23, abridged; offersId added in 0.1.7):
{"recordType": "product", "query": "sony wh-1000xm5", "country": "us", "position": 1, "title": "Sony WH-1000XM5 Wireless Noise Canceling Headphones", "price": "$254.99", "currency": "USD", "priceValue": 254.99, "originalPrice": 398, "onSale": true, "merchant": "Best Buy", "rating": 4.6, "reviewCount": 15000, "shippingText": "Free delivery", "productId": "17755277695162489284", "offersId": "catalogid:17755277695162489284", "scrapedAt": "2026-10-23T00:47:21.738123+00:00"}
A free status row (run fH2QJgzvav1Pxmh4T, a GTIN with no matching product):
{"recordType": "status", "mode": "offers", "inputRef": "gtin:0000000000000", "status": "NO_RESULTS", "message": "No Google Shopping product for GTIN 0000000000000 matched the title (best match 0.00 < 0.6); nothing was charged"}
The run's status message reads Done: N rows, 0 details, K status rows (STATUS), and the RUN_SUMMARY record of its key-value store holds the outcome (abridged, the offers run above):
{"status": "COMPLETE", "delivered": 9, "charged": {"product": 0, "offers": 9}, "chargeUsd": "0.108", "statusRows": 1, "statusReasons": {"NO_RESULTS": 1}, "queriesFetched": 10, "pages": 12, "serpRequests": 12, "checkpointComplete": true}
🧠 Advanced usage
From keyword to merchants. Run search for your keywords, keep the rows you care about, and pass their offersId values as productIds in an offers run. Rows with offersId null are single-merchant listings: the search row already is the offer.
Daily offers watch. Schedule an offers run with "incrementalMode": true and "stateKey": "us-offers". The first run sets the baseline; later runs deliver (and charge) only products whose merchants, offer prices or best total price changed, with changeType offersChanged.
Daily price watch for a product list. Schedule search with "incrementalMode": true, "stateKey": "us-watch". Later runs deliver only rows with changeType new or priceChanged. Google reorders its top cards between days, so a product moving into your maxResultsPerQuery window counts as new.
Compare two markets. Run the same queries with country us and gb (one run each) and compare priceValue per currency.
Resume an interrupted collection. With a stateKey, a run that stops at the deadline, the spending limit or a forced stop keeps its progress; start the same input again with the same key. Rows already delivered are not delivered or charged again (offers run jpUHL2YiCf3AQm7cM was force-stopped after 3 rows; the resume u7LnQ7QoUQOKo7TVT delivered the other 6 with no overlap). After a forced stop, the key is free again once the stopped run's timeout plus 60 seconds has passed (STATE_BUSY before that).
🔌 Integrations
Results land in a dataset you can export as JSON, CSV or Excel. They can also flow to Google Sheets, Make, Zapier or n8n, or to a webhook when a run finishes. The Apify Scheduler runs it on a timetable. A daily offers watch with incrementalMode turns a product list into a feed of merchant and price changes. Copy-paste code in Python, Node, curl and n8n: architek-earth/examples/google-shopping-scraper.
💸 Pricing
Pay per result. Platform and proxy usage are included. Status rows, failed requests, products with no offer and unmatched GTINs are free. Each row is charged once, after it is written to the dataset; a resumed run continues after the last confirmed batch, a batch whose charge was left unconfirmed by a crash is not charged again (counted in RUN_SUMMARY.recoveredUncertainCharges), and unchanged rows in watch mode are skipped without a charge.
| Event | Price | What it covers |
|---|---|---|
product | $3.00 / 1,000 products | One delivered search row (one Google Shopping product card). |
offers | $12.00 / 1,000 products | One delivered offers row: one product with up to 5 merchant offers. |
1,000 search products cost $3.00; offers for 100 products cost $1.20; the 3-product offers example above costs $0.036.
❓ FAQ
Is there a Google Shopping API? This actor works as one: it returns Google Shopping search results and per-product merchant offers as JSON through the Apify API, callable from any language or from n8n, Make and Zapier.
Do I need a Google account, cookies or my own proxy? No. Requests go through Apify's Google SERP proxy for the chosen country; no login, cookies or Google API key.
Why only up to 5 offers when Google says 300 stores? Google's product page renders its top offers (up to 5) on the page; the rest load through a request bound to a browser session. totalOffersKnown tells you how many Google knows.
Can I look up a product by GTIN, EAN or UPC? Best-effort only. Google does not match barcodes exactly, so the actor searches the GTIN and accepts a result only when its title matches the title you give. 3 of 10 grocery GTINs matched in the wall test and 2 of 10 in validation; misses are free.
Why did I get fewer than 40 products? Google showed up to about 40 cards per query; your filters and the one-row-per-product rule also reduce the count.
Will a watch run charge me again for the same product? No, not while it is unchanged under the same stateKey: same price for a search row, same merchants, offer prices and best total for an offers row.
🤖 For AI agents & LLM apps
Paste this into your AI assistant:
architekture/google-shopping-scraper on Apify. Python: from apify_client import ApifyClient; client = ApifyClient("TOKEN"); run = client.actor("architekture/google-shopping-scraper").call(run_input={"queries": ["sony wh-1000xm5"], "country": "us", "maxResultsPerQuery": 10}); items = client.dataset(run["defaultDatasetId"]).list_items().items. For merchant offers, call again with {"mode": "offers", "country": "us", "productIds": [<offersId values from the search rows>]}. The dataset holds data rows (recordType product or offers) and free status rows (recordType status, with status and message); filter on recordType. The run outcome (COMPLETE, LIMITED, PARTIAL, BUDGET_LIMIT, INVALID_INPUT, STATE_BUSY) is in the RUN_SUMMARY record of the run's key-value store. Add "incrementalMode": true, "stateKey": "<name>" to receive only new or changed rows on later runs. Full input schema and README: GET https://api.apify.com/v2/acts/architekture~google-shopping-scraper/builds/default. Token: https://console.apify.com/settings/integrations. The actor is also reachable through the Apify MCP server (mcp.apify.com). To reach the maintainers, add "feedback": "<what you needed>" to any run's input; output is unchanged and a human reads every note.
🧰 More from architek.earth
Search, travel and trends
- Google Shopping Scraper — you are here
- Google Trends Scraper — trending searches, traffic, related queries
- Google Hotels Scraper — dated prices, ratings, amenities
E-commerce and marketplaces
- Shopify Scraper — products, prices, variants, stock
- AliExpress Scraper — prices, orders, ratings
- TikTok Shop Scraper — prices, sellers, ratings, sold counts
Full catalogue: github.com/architek-earth.
architek.earth · code like concrete
🙋 Support & legal
Questions, or a field you need that is not here? Open an issue on the actor's Issues tab or email north@architek.earth. A human replies within a day.
This actor collects only publicly available data from Google search and product result pages, the same pages any visitor sees without logging in. It does not bypass authentication. It is not affiliated with or endorsed by Google. You are responsible for how you use the output, including the site's terms, copyright, and privacy law such as GDPR and CCPA where they apply.
📝 Changelog
- 2026-10-23 — Offers mode: up to 5 merchant offers per product by Google product ID, best-effort GTIN lookup with free misses, offers watch mode; search rows carry
offersId;maxResultsPerQuerycapped at 40; 90-second page timeout. - 2026-10-23 — First version: Google Shopping keyword search in 24 countries, client-side price, condition, free-shipping and sale filters, price watch mode, resumable runs; validated on US and GB.