# Product Feed Scraper: Google Merchant & XML Catalogs (`arman-bd/product-feed-scraper`) Actor

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.

- **URL**: https://apify.com/arman-bd/product-feed-scraper.md
- **Developed by:** [Arman Hossain](https://apify.com/arman-bd) (community)
- **Categories:** E-commerce, Lead generation, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 product scrapeds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Product Feed Scraper: Google Merchant & XML Catalogs

![Product Feed Scraper: Every product in a merchant's Google Shopping feed, price split into amount and currency, availability, brand, GTIN and shipping](https://api.apify.com/v2/key-value-stores/ZQOcNAOHrIgTacAmy/records/product-feed-scraper.jpg)

**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)**

```
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 `productId` plus `price`.
- **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 `availability` flip across a competitor's range.
- **Market research.** Price distributions by brand, category or condition across a sector.

### Quick start

A whole catalogue:

```json
{
 "feedUrls": ["https://houseofjerky.com/wp-content/uploads/woo-feed/google/xml/googlehojfeed.xml"]
}
```

Several suppliers, capped while you explore:

```json
{
 "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:

```json
{
 "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

```json
{
 "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

```bash
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

```js
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.atom` is 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 conventional `g:` prefix declared as `xmlns: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 no `g:` fields at all.
- **Compression is handled both ways.** `Content-Encoding: gzip` is unwrapped by the HTTP layer, and a `.xml.gz` body is detected by its magic number and decompressed in-process. No extra dependency.
- **Non-UTF-8 feeds are decoded.** The XML declaration's `encoding` attribute 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 as `currency: null` because a dozen currencies share the symbol, the Actor does not guess.
- **Availability is normalised.** Feeds write `in stock` and `in_stock` interchangeably; both come out as `in_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.failures` and 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.

# Actor input Schema

## `feedUrls` (type: `array`):

Public product feed URLs. Retailers usually link theirs from the Google Merchant Center setup docs of whichever cart they use. common paths are '/google-shopping.xml', '/googlebase.xml' and, on WooCommerce, '/wp-content/uploads/woo-feed/google/xml/<name>.xml'. Gzipped feeds (.xml.gz or Content-Encoding: gzip) are decompressed automatically. A feed that 404s or is not RSS is recorded in RUN\_SUMMARY.failures and the run continues.

## `maxProductsPerFeed` (type: `integer`):

Cap on saved products per feed, applied after filtering. Catalogues run to tens of thousands of items, so set this while you are still exploring a new feed. 0 = no limit.

## `inStockOnly` (type: `boolean`):

Keep only products whose availability is 'in\_stock'. Feeds write this as either 'in stock' or 'in\_stock' and both are recognised. Products that omit availability entirely are dropped by this filter, because there is no evidence they are buyable.

## `brandFilter` (type: `array`):

Keep only products whose 'g:brand' exactly matches one of these, case-insensitively. Empty keeps every brand. Note that many small merchants leave 'g:brand' blank or set it to their own store name.

## `minPrice` (type: `integer`):

Keep only products priced at or above this amount, in the feed's own currency. feeds are single-currency, but no conversion is done, so check 'currency' before relying on it. Products with an unreadable price are dropped when this is set. Leave empty for no lower bound.

## `maxPrice` (type: `integer`):

Keep only products priced at or below this amount, in the feed's own currency. Products with an unreadable price are dropped when this is set. Leave empty for no upper bound.

## Actor input object example

```json
{
  "feedUrls": [
    "https://example.com/feeds/google-shopping.xml"
  ],
  "maxProductsPerFeed": 0,
  "inStockOnly": false,
  "brandFilter": [
    "Sony",
    "Bosch"
  ],
  "minPrice": 25,
  "maxPrice": 500
}
```

# Actor output Schema

## `items` (type: `string`):

Every record the run produced.

## `runsummary` (type: `string`):

The RUN\_SUMMARY record from the run's key-value store.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "feedUrls": [
        "https://houseofjerky.com/wp-content/uploads/woo-feed/google/xml/googlehojfeed.xml"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("arman-bd/product-feed-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "feedUrls": ["https://houseofjerky.com/wp-content/uploads/woo-feed/google/xml/googlehojfeed.xml"] }

# Run the Actor and wait for it to finish
run = client.actor("arman-bd/product-feed-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "feedUrls": [
    "https://houseofjerky.com/wp-content/uploads/woo-feed/google/xml/googlehojfeed.xml"
  ]
}' |
apify call arman-bd/product-feed-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,arman-bd/product-feed-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/EqtC96RdDlIvyIGNE/builds/DKbb7vcgMdlNQaMJu/openapi.json
