Product Feed Scraper: Google Merchant & XML Catalogs
Pricing
from $2.00 / 1,000 product scrapeds
Product Feed Scraper: Google Merchant & XML Catalogs
Parse public Google Merchant and RSS 2.0 product feeds: GTIN, MPN, brand, price split into amount and currency, sale price, availability, shipping and images. The catalogue retailers publish deliberately.
Pricing
from $2.00 / 1,000 product scrapeds
Rating
0.0
(0)
Developer
Arman Hossain
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
0
Monthly active users
a day ago
Last modified
Categories
Share

Product Feed Scraper reads the product feeds retailers publish for Google Shopping and other ad platforms, and returns one clean record per product, ID, title, description, link, images, price split into an amount and a currency, sale price, availability, brand, GTIN, MPN, condition, category and shipping.
These feeds exist so machines can read them: a merchant generates one deliberately and hands the URL to Google Merchant Center. This Actor reads the same URL. No proxy setup, no browser, no credentials to manage. A 250-product catalogue comes back in under a second.
Agent skill: SKILL.md
https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/product-feed-scraper.md
What you get
| Output field | Meaning |
|---|---|
feedUrl, feedTitle | Feed that was read after redirects, and the store name from the channel |
productId, itemGroupId | g:id, and the g:item_group_id that ties variants of one product together |
title, description | g:title and g:description, description stripped to plain text |
link, imageLink, additionalImageLinks | Product page and images |
price, salePrice, currency | Numbers, not strings, "69.99 USD" becomes 69.99 plus "USD" |
availability, inStock | Google's enum (in_stock, out_of_stock, preorder, backorder) and a boolean |
brand, gtin, mpn, condition | Identity attributes, with g:ean / g:upc / g:isbn accepted as GTIN aliases |
productType, googleProductCategory | The merchant's own taxonomy and Google's |
shippingCost, shippingCurrency, shippingCountry | Parsed out of the nested g:shipping group |
scrapedAt | Run timestamp |
A RUN_SUMMARY record in the key-value store holds per-run counts, a per-feed breakdown of listed / saved / filtered, the filters used, and any feed that failed.
Common use cases
- Competitive price monitoring. Run daily and diff on
productIdplusprice. - Supplier catalogue ingest. Pull a distributor's whole feed straight into your own catalogue.
- Comparison shopping. Normalise many merchants' feeds into one schema keyed by GTIN.
- Stock and assortment tracking. Watch
availabilityflip across a competitor's range. - Market research. Price distributions by brand, category or condition across a sector.
Quick start
A whole catalogue:
{"feedUrls": ["https://houseofjerky.com/wp-content/uploads/woo-feed/google/xml/googlehojfeed.xml"]}
Several suppliers, capped while you explore:
{"feedUrls": ["https://example-shop.com/feeds/google-shopping.xml","https://another-shop.com/googlebase.xml"],"maxProductsPerFeed": 500}
Only what is buyable, from one brand, in a price band:
{"feedUrls": ["https://example-shop.com/feeds/google-shopping.xml"],"inStockOnly": true,"brandFilter": ["Sony"],"minPrice": 100,"maxPrice": 900}
Input
| Field | Type | Default | Notes |
|---|---|---|---|
feedUrls | array | - | Required. Public product feed URLs. .xml and gzipped .xml.gz both work. |
maxProductsPerFeed | integer | 0 | Cap saved products per feed, applied after filtering. 0 = no limit. |
inStockOnly | boolean | false | Keep only availability: in_stock. Products with no availability are dropped. |
brandFilter | array | [] | Exact, case-insensitive match on g:brand. Empty = all brands. |
minPrice | integer | - | Lower price bound in the feed's own currency. No conversion is done. |
maxPrice | integer | - | Upper price bound in the feed's own currency. |
Filters combine with AND. Setting either price bound drops products whose price could not be parsed, since there is no way to know whether they qualify.
Output example
{"feedUrl": "https://houseofjerky.com/wp-content/uploads/woo-feed/google/xml/googlehojfeed.xml","feedTitle": "House of Jerky","productId": "97864","itemGroupId": "97864","title": "Teriyaki Lovers","description": null,"link": "https://houseofjerky.com/shop-for-jerky/teriyaki-lovers/","imageLink": "https://houseofjerky.com/wp-content/uploads/2025/10/Teriyaki-Lovers-scaled.jpg","additionalImageLinks": [],"price": 69.99,"salePrice": 69.99,"currency": "USD","availability": "in_stock","inStock": true,"brand": "House of Jerky","gtin": null,"mpn": "teriyakilovers","condition": "new","productType": "Beef > Exotic > Game > Turkey > Subscriptions","googleProductCategory": "423","shippingCost": null,"shippingCurrency": null,"shippingCountry": null,"scrapedAt": "2026-08-06T11:56:59.217Z"}
Finding a merchant's feed URL
There is no universal path, but the cart platform narrows it down:
| Platform | Typical feed path |
|---|---|
| WooCommerce (CTX Feed / Product Feed PRO) | /wp-content/uploads/woo-feed/google/xml/<name>.xml |
| Magento and custom carts | /googlebase.xml, /google_shopping.xml, /feeds/google-shopping.xml |
| Feed services (DataFeedWatch, Channable, Feedonomics) | A hosted URL on the vendor's domain |
Merchants who want you to have the feed, suppliers, dropship partners, affiliate programmes, will simply send you the URL. Do not try to guess your way into one that is not published: a feed behind auth returns 403 and is recorded as a failure.
API example
curl -X POST "https://api.apify.com/v2/acts/arman-bd~product-feed-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \-H "Content-Type: application/json" \-d '{"feedUrls": ["https://example-shop.com/feeds/google-shopping.xml"],"inStockOnly": true,"maxProductsPerFeed": 1000}'
JavaScript example
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: 'YOUR_TOKEN' });const run = await client.actor('arman-bd/product-feed-scraper').call({feedUrls: ['https://example-shop.com/feeds/google-shopping.xml'],inStockOnly: true,});const { items } = await client.dataset(run.defaultDatasetId).listItems();for (const p of items) console.log(`${p.productId}\t${p.price} ${p.currency}\t${p.title}`);
Limits and behaviour
- RSS 2.0 only. Google Merchant feeds are RSS 2.0 with the
g:namespace, and that is what this Actor parses, along with plain RSS<title>,<link>and<description>as a fallback. Atom-based product feeds are out of scope and rejected with a clear message rather than parsed into empty records; Shopify's/collections/all.atomis the common example. CSV and TSV feeds are also out of scope. This is an XML parser. - The
g:prefix is matched literally. Every generator in the wild emits the conventionalg:prefix declared asxmlns:g="http://base.google.com/ns/1.0". A feed that rebinds the namespace to a different prefix will parse as plain RSS and lose the merchant fields; the run log warns when a feed yields nog:fields at all. - Compression is handled both ways.
Content-Encoding: gzipis unwrapped by the HTTP layer, and a.xml.gzbody is detected by its magic number and decompressed in-process. No extra dependency. - Non-UTF-8 feeds are decoded. The XML declaration's
encodingattribute is honoured, so windows-1252 and ISO-8859-1 catalogues come through with their accents intact. - Prices become numbers.
"69.99 USD","USD 69.99","1.299,00 EUR"and"£1,050"all parse correctly. A bare$is left ascurrency: nullbecause a dozen currencies share the symbol, the Actor does not guess. - Availability is normalised. Feeds write
in stockandin_stockinterchangeably; both come out asin_stock. - Big catalogues need memory. The feed is parsed in memory, so the default is 2 GB and 30 minutes. A feed over roughly 200 MB may need more.
- One failure never kills the run. A 404, a private feed or an Atom feed is logged into
RUN_SUMMARY.failuresand the next feed is read. The Actor only errors out if every feed fails. - Public data only. No authentication, no access-control bypass, no personal data.
FAQ
Do I need a Google Merchant Center account? No. You supply no credentials.
Will it work on any store? Only on stores that publish a public feed. Most do, because that is how they get into Google Shopping, but the URL is not always advertised.
Does it convert currencies? No. price is the number the feed states and currency is its ISO code. A feed is single-currency; converting is your job.
Why is gtin null on so many products? Because small merchants leave it blank. g:ean, g:upc and g:isbn are read as aliases, but if none are present the field is null rather than fabricated.
Why did a product get filtered out when I set a price range? Either its price is outside the range, or the feed gave no readable price. RUN_SUMMARY.perFeed reports listed vs saved vs filtered per feed, which distinguishes the two.
Can I get only changed prices? Run on a schedule and diff on productId plus price. Combining maxProductsPerFeed: 0 with a scheduled run gives you a full daily snapshot.
Can I integrate it with something else? Yes, Apify API, client libraries, webhooks, scheduled runs, dataset exports (JSON/CSV/Excel) or MCP. Output is structured JSON.