Google Shopping Scraper — Prices, Merchants & Offers avatar

Google Shopping Scraper — Prices, Merchants & Offers

Pricing

from $3.00 / 1,000 delivered products

Go to Apify Store
Google Shopping Scraper — Prices, Merchants & Offers

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

architek.earth

Maintained by Community

Actor 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.

SourceMarketFromStandout fields
Google Shopping search and product pages (google.24 countries; US and GB validated$3.00 / 1,000 products; $12 / 1,000 offers rowspriceValue + 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, merchant rating and its url, plus the product's bestPrice, bestTotalPrice and totalOffersKnown. Run mwQKGPC56O2U3wiyK: 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 product and 21 offers charges. Status rows, products with no offer, GTIN misses and unchanged watch rows were free.
  • Localized to the country you pick. country sets the Google domain, gl and 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 incrementalMode and a stateKey, search delivers only new or re-priced products, and offers delivers only products whose offer set or best total price changed: the rerun QMqAcZreUkfkXgYWv charged 1 of 9 products and skipped 8 unchanged ones for free.
  • Filters that never cost you. In search mode, minPrice, maxPrice, condition, freeShipping and onSale drop 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). totalOffersKnown is 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 gtins item 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 (runs fH2QJgzvav1Pxmh4T, pOwrC9XWNu6uZ4GjL); 2 bogus GTINs were free misses. Every miss is a free NO_RESULTS row. Prefer productIds from 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 condition filter 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 maxResultsPerQuery stops at 40.
  • offersId is null on 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 /goto redirect link to the merchant's offer. merchantDomain is always null in search rows.
  • shippingCost is 0 for free delivery and the amount for a flat delivery price; it is null for conditional texts such as "2 hour delivery on orders $10+", and then total is null too. bestTotalPrice is the lowest known total.
  • 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. freeShipping reads English labels; language other than en is accepted but was not validated.
  • A run stops cleanly 3 minutes before its deadline (PARTIAL, stopReason ACTOR_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 same stateKey to continue.
  • Invalid input (INVALID_INPUT), a stateKey still 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

ParameterTypeDefaultDescription
modeselectsearchsearch: product cards by keyword ($0.003 per product). offers: merchant offers per product ($0.012 per product).
queriesarray— (prefill ["sony wh-1000xm5"])Search mode. 1–50 product searches, as typed into Google Shopping. Duplicates are removed. Ignored in offers mode.
productIdsarray—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).
gtinsarray—Offers mode. Up to 100 objects {"gtin": "...", "title": "..."}; the title is required for the match check. Best-effort, misses are free.
countryselectusus, 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.
languagestringenGoogle hl code, for example en, en-GB, de. Validated with en.
maxResultsPerQueryinteger40 (prefill 10)Search mode. 1–40 products per query.
minPricenumber—Search mode. Keep cards priced at least this much, in the country's currency.
maxPricenumber—Search mode. Keep cards priced at most this much, in the country's currency.
conditionselectanySearch mode. any; new drops cards labelled used, pre-owned or refurbished; used keeps only those.
freeShippingbooleanfalseSearch mode. Keep cards whose shipping text says free.
onSalebooleanfalseSearch mode. Keep cards with a was-price or a percent-off badge.
incrementalModebooleanfalseNeeds 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).
stateKeystring—Any name up to 100 characters. Keeps the watch baseline and lets an interrupted run resume. One key per set of inputs.
feedbackstring—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):

FieldTypeMeaning
querystringThe search term as run (whitespace collapsed).
countrystringThe requested country code, for example us.
positionnumberRank of the product on Google's page for this query (1 = first), before your filters.
titlestringProduct title as Google shows it.
pricestringDisplayed price, for example $254.99 or £290+.
currencystringISO currency of price, for example USD.
priceValuenumberprice as a number; the lowest price for + ("from") prices.
originalPricenumber, nullableThe was-price Google shows next to a sale price.
onSalebooleantrue when the card shows a was-price or a percent-off badge.
merchantstring, nullableMerchant name on the card; null on multi-seller "from" cards.
merchantDomainstring, nullableAlways null in this version (cards carry no domain).
ratingnumber, nullableProduct rating out of 5.
reviewCountnumber, nullableDisplayed review count (15K = 15000).
shippingTextstring, nullableShipping text, for example Free delivery.
productIdstringGoogle's catalog ID for the product (data-cid); the group or offer ID when the card has none.
offersIdstring, nullableThe value offers mode takes in productIds (catalogid:<id> or gpcid:<id>); null on single-merchant cards.
imageUrlstring, nullableGoogle's product thumbnail (encrypted-tbn*.gstatic.com).
scrapedAtstringCollection time, UTC ISO 8601.

Offers rows (recordType offers):

FieldTypeMeaning
gtinstring, nullableThe GTIN you gave, on rows that came from gtins.
productIdstringGoogle's catalog or group ID (the digits of offersId).
offersIdstringcatalogid:<id> or gpcid:<id>, as requested or as matched for a GTIN.
titlestring, nullableProduct title on Google's product page.
countrystringThe requested country code.
offerCountnumberOffers delivered in merchants (1–5).
totalOffersKnownnumber, nullableGoogle's count of all offers for the product.
bestPricenumber, nullableLowest offer price.
bestTotalPricenumber, nullableLowest known offer total (price plus delivery).
currencystringThe country's currency, for example USD.
merchantsarrayOne 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.
scrapedAtstringCollection 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.

EventPriceWhat it covers
product$3.00 / 1,000 productsOne delivered search row (one Google Shopping product card).
offers$12.00 / 1,000 productsOne 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

E-commerce and marketplaces

Full catalogue: github.com/architek-earth.

architek.earth · code like concrete

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; maxResultsPerQuery capped 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.