# Bodega Aurrera Scraper - Products, Prices & Stores (Mexico) (`jmlp/bodega-aurrera-scraper`) Actor

Scrape Bodega Aurrera Mexico products and prices by keyword, category or product link, priced for a postal code or store.

- **URL**: https://apify.com/jmlp/bodega-aurrera-scraper.md
- **Developed by:** [Mary Lou](https://apify.com/jmlp) (community)
- **Features:** Limited permissions
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $15.00 / 1,000 products

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

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

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Bodega Aurrera Scraper

Scrape products and prices from **Bodega Aurrera Mexico**
(bodegaaurrera.com.mx), **priced for the store you choose**:

- search by keyword
- scrape whole categories
- track a fixed list of products by their links

Pick stores by **postal code** (the nearest store, or the nearest 10) or by
**store number**. Every record says **whose price it is**: the chosen
store's own shelf price (store-verified), or the website's general online
price for products shipped from a warehouse or sold by a marketplace seller.

For every product you get the name, brand, size and pack count, the
barcode (EAN/UPC), the price, the was price and saving, the unit price,
stock, the seller, the category, the image and the product's link.

No login, no account.

***

### What you can do with it

- **Competitive price intelligence**: Bodega Aurrera's prices at the stores
  that compete with yours, e.g. every Bodega in Mérida
- **Price and promotion tracking**: run it daily and compare; rollbacks
  ("Rebaja") come with the was price and the saving
- **Assortment analysis**: which products each store carries, in stock or
  not
- **Basket monitoring**: the same product links priced at many stores
- **Retail data feeds**: clean JSON, CSV or Excel for your BI system

***

### How to scrape Bodega Aurrera

**1.** Enter **Search terms** (`leche`, `aceite`), paste **Bodega Aurrera
links** (categories, searches, products), or both.

**2.** Enter **Postal codes** (`97000` for Mérida) or **Store IDs**
(`2029`). Leave both empty for the website's default prices.

**3.** Set **Max products**, then press Start.

```json
{
  "searchTerms": ["leche", "aceite", "arroz"],
  "startUrls": [
    "https://www.bodegaaurrera.com.mx/content/lacteos/120006",
    "https://www.bodegaaurrera.com.mx/ip/Leche-Santa-Clara-deslactosada-caja-con-6-pzas-1-l-c-u/00750105536751"
  ],
  "postalCodes": ["97000"],
  "storesPerPostalCode": 3,
  "maxItems": 5000
}
```

***

### What each input gives

| You give | You get |
| --- | --- |
| A search term | Every product the site's search finds for it at each store, in the site's order (`position`), plus the store's own products the site's pages skip |
| A category link (`/content/...`, `/browse/...`) or id | Every product in the category at each store |
| A product link (`/ip/...`) or id | That product at each store, when the store carries it |
| A postal code | The nearest store (or several: **Stores per postal code**), looked up with Bodega's own store finder. The **STORES** record lists every store near the code with its number, address, distance and hours |
| A store number | That store |
| No store | The website's default prices (store 3874, Coyoacán, CDMX) |

Every search, category and product list runs once per store. A product
found by several searches at one store appears once for that store.

***

### Output

One row per product per store. Export as **JSON, CSV, Excel, XML or RSS**.
The dataset has views for an overview, prices by store, and the catalog.

```json
{
  "product_id": "00750105536751",
  "barcode": "7501055367511",
  "barcode_type": "EAN-13",
  "name": "Leche Santa Clara deslactosada caja con 6 pzas 1 l c/u",
  "brand": "Santa Clara",
  "size": "1 l",
  "size_value": 1.0,
  "size_unit": "l",
  "pack_count": 6,
  "price": 153.0,
  "currency": "MXN",
  "was_price": 171.0,
  "savings": 18.0,
  "price_scope": "store",
  "store_verified": true,
  "store_id": "2029",
  "store_name": "Ba Merida Itzaes",
  "store_postal_code": "97000",
  "store_city": "Merida",
  "store_state": "YUC",
  "fulfillment": [{"type": "PICKUP", "store_id": "2029", "date": "2026-11-06T00:00:00.000Z"}],
  "availability": "IN_STOCK",
  "in_stock": true,
  "seller_name": "Walmart.mx",
  "sold_by_bodega": true,
  "url": "https://www.bodegaaurrera.com.mx/ip/Leche-Santa-Clara-deslactosada-caja-con-6-pzas-1-l-c-u/00750105536751",
  "image_url": "https://i5-mx.walmartimages.com/asr/81b1043e-4975-4553-8e7a-bdf861d96321.8e50c8f2eb38fe4be867f5750a6f0883.jpeg",
  "category_path": ["Lácteos", "Leche", "Leche Deslactosada"],
  "flags": ["Rebaja"],
  "source_type": "search",
  "source": "leche",
  "position": 1,
  "scraped_at": "2026-11-05T19:58:52Z"
}
```

#### Store-verified or website price

| `price_scope` | Meaning |
| --- | --- |
| `store` | The chosen store sells the product at this price, as the site itself confirms. `store_verified` is `true` |
| `online` | The product ships from a warehouse or a marketplace seller: the website's general price, the same whatever the store |
| `website_default` | No store was chosen: the price the website shows a visitor without a location |

Turn off **Include online-only offers** to keep only the store's own
products.

| Field | Notes |
| --- | --- |
| `barcode`, `barcode_type` | The barcode as printed on the pack: EAN-13, UPC-A or EAN-8. Empty for produce and products weighed in store, which carry a scale label instead |
| `size`, `size_value`, `size_unit`, `pack_count` | Read from the name: `6 pzas ... 1 l` is 6 x 1 l. A count without a measure (`30 piezas`) is the size, in `pza` |
| `price`, `was_price`, `savings`, `unit_price` | Mexican pesos. Products sold by weight are priced per kg |
| `store_*` | The store the record was priced for |
| `fulfillment` | Pickup or delivery, from which store or warehouse, and the date the site promises |
| `availability`, `in_stock` | Stock at the store. Out-of-stock products keep their price |
| `seller_name`, `sold_by_bodega` | Walmart.mx for Bodega's own products, otherwise the marketplace seller |
| `category_path`, `category_path_ids` | The product's category; each id works as a category link |
| `source_type`, `source`, `position` | What found the product and its rank there. `position` is empty for products found while completing a listing; `price_band` then says which price range found it |

***

### Complete listings, with proof

Bodega Aurrera's own pages skip products: they show 40 per page but step
through 44, and their order shifts between requests. In tests the site's
pages listed 79% of a large category (Lácteos, 658 of 834).

The scraper pages each listing the way the site does (keeping each product's
rank), then completes it: it splits the listing by price, and by brand where
one price holds too many, until every piece fits on a single page, which
always holds everything it counts. Then it reads the whole listing four more
times in different orders (cheapest first, dearest first, newest, best
sellers), because the site's answers shift from one request to the next.

Every run's **SUMMARY** shows, for each search and category at each store,
the site's own count, the products found, and how many the site's own full
listings showed during the run: **every one of those is in your dataset.**
In tests on a whole store's grocery catalogue (11,000 products), nothing the
site listed was missed. The site also counts some products it never shows
anyone (Bebés: counts 1,300, lists 1,213); SUMMARY reports those as
`countedButNotListed`.

For a search, completeness covers the store's own products. Online offers in
a search are as the site lists them: with a price filter the site's search
reaches into its whole marketplace, so there is no fixed list to complete.
Categories are completed whole, online offers included.

Turn off **Complete listings** for the site's pages alone, with fewer
requests.

***

### Reliable on big runs

- **Inputs are read forgivingly.** Links in any form, bare ids, postal codes
  written `C.P. 97000`, store numbers in a sentence. Anything that cannot be
  read is skipped with a note in the log.
- **Refusals are retried.** A refused request moves to a new IP, then to
  Apify's Unblocker and a Mexican residential proxy when your plan has them.
  Jobs still refused at the end get a second pass.
- **Survives site updates.** When Bodega's site ships a new version, the
  scraper reads the new query from the site itself and carries on.
- **No failed runs for blocks.** If the site refuses everything, the run
  ends normally and its status message says why. Progress is saved every 30
  seconds.

***

### Cost

**$25 per 1,000 products, plus Apify platform usage.** The same price on
every Apify plan, and no scraper charge for runs that find nothing.

You pay per row in the dataset: one product priced at one store, so the
same product at nine stores is nine rows. Requests are not charged:
completing a listing costs only the products it adds.

Platform usage is billed by Apify at your plan's own rate, separately from
the scraper charge. It stays small because the scraper reads the site's own
data with plain requests through datacenter proxies, with no browser.

| What you scrape | Scraper charge | + Platform usage |
| --- | --- | --- |
| 100 products | $2.50 | under $0.01 |
| One department at one store: Bebés, 1,213 store products | $30.33 | ~$0.01 |
| One store's whole grocery catalogue: ~11,000 store products | ~$274 | ~$0.12 |

Set **Max products**, or a maximum cost per run, to cap any run.

***

### FAQ

**Do I need a Bodega Aurrera account?**
No. The scraper reads what any visitor sees, without signing in.

**How do I find a store's number?**
Run it once with your postal code. The **STORES** record in the run's
storage lists every Bodega Aurrera store near the code, whatever its format
(Ba Bodega Aurrera, Mb Mi Bodega, ...), with its number, address and
distance. For Mérida's 97000 that is nine stores.

**Are prices the same in every store?**
Mostly, not always. Some products differ in price or availability by store,
and each store has its own assortment. That is why every record carries its
store.

**How long does it take?**
Five searches at all nine stores in Mérida (8,351 records) took about 4
minutes; one search at one store takes seconds.

**Which proxy should I use?**
The default, Apify's datacenter proxy.

**Can I run it on a schedule?**
Yes. Schedule the same input daily or weekly, and compare the datasets.

**Can I get the data in my own system?**
Yes: through Apify's API, webhooks, or integrations (Make, Zapier, n8n,
Google Sheets, S3).

# Actor input Schema

## `searchTerms` (type: `array`):

What you would type into Bodega Aurrera's search box, one per line: 'leche', 'aceite', 'detergente'. Each search pages through all its results.

## `startUrls` (type: `array`):

Paste links straight from your browser: a category (www.bodegaaurrera.com.mx/content/leche/120096 or /browse/...), a search (/search?q=...) or a product (/ip/...). Bare category ids (120096) and product ids (00750649502153) work too. Product links are priced at each store like everything else.

## `postalCodes` (type: `array`):

Price everything for the Bodega Aurrera store nearest each postal code, e.g. 97000 (Merida, Yucatan). The run looks the stores up with the site's own store finder; the STORES record lists every store near each code with its id, address and distance.

## `storeIds` (type: `array`):

Price everything for these stores, by Bodega Aurrera's store number, e.g. 2029 (Ba Merida Itzaes). Get the numbers from a run with postal codes. With no store and no postal code, prices are the website's default ones (store 3874, Coyoacan, CDMX).

## `storesPerPostalCode` (type: `integer`):

How many of the nearest stores to price for each postal code, 1 to 25. 1 is the nearest only; 10 covers most of a city like Merida. Every search runs once per store.

## `includeOnlineOffers` (type: `boolean`):

Keep products the chosen store does not sell itself but the website ships from a fulfilment centre or a marketplace seller. They carry the website's general price and are marked price_scope 'online', never as the store's price.

## `maxItems` (type: `integer`):

Stop after this many records across every search, link and store. Caps both runtime and cost. Empty means everything found.

## `maxItemsPerSearch` (type: `integer`):

Cap each search or category at each store separately, so one big category does not use up 'Max products' alone.

## `sort` (type: `string`):

The order the site lists results in. With a product cap it decides which products you get first: price_low collects the cheapest. Other spellings are read too (precio menor, cheapest).

## `splitByPrice` (type: `boolean`):

The site's own pages skip products. With this on, every search and category is also split by price (and by brand where one price holds too many) into pieces that each fit one page, so every product the site can display is found. The run's SUMMARY proves it per listing: found plus 'counted but never displayed' equals the site's own count. Off: only what the site's pages list (about 80-90%), with fewer requests.

## `maxConcurrency` (type: `integer`):

How many requests to make in parallel, 1 to 20, each from its own exit IP. 4 is gentle on the site and fast enough for thousands of products.

## `proxyConfiguration` (type: `object`):

Apify's datacenter proxy works. Requests the site refuses are retried from fresh exits, then through Apify's Unblocker and a Mexican residential exit when your plan has them.

## `resume` (type: `boolean`):

Save progress every ~30s so a run that gets migrated or restarted by the platform picks up where it stopped, without repeating products.

## `continueFromLastRun` (type: `boolean`):

If your previous run with the same input was interrupted, scrape only what it missed. Products already collected stay in THAT run's dataset.

## Actor input object example

```json
{
  "searchTerms": [
    "leche"
  ],
  "postalCodes": [
    "97000"
  ],
  "storesPerPostalCode": 1,
  "includeOnlineOffers": true,
  "maxItems": 100,
  "sort": "best_match",
  "splitByPrice": true,
  "maxConcurrency": 4,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "resume": true,
  "continueFromLastRun": false
}
```

# Actor output Schema

## `products` (type: `string`):

One record per product per store, with its price and whose price it is.

## `stores` (type: `string`):

Every Bodega Aurrera store the store finder returned for each postal code: id, name, address, distance, hours.

## `summary` (type: `string`):

Products per store, store-verified versus online prices, what each search and category gave, and anything refused or not found.

# 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 = {
    "searchTerms": [
        "leche"
    ],
    "postalCodes": [
        "97000"
    ],
    "maxItems": 100,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("jmlp/bodega-aurrera-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 = {
    "searchTerms": ["leche"],
    "postalCodes": ["97000"],
    "maxItems": 100,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("jmlp/bodega-aurrera-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 '{
  "searchTerms": [
    "leche"
  ],
  "postalCodes": [
    "97000"
  ],
  "maxItems": 100,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call jmlp/bodega-aurrera-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,jmlp/bodega-aurrera-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/d84Tch1IEchvEd5ik/builds/YKsv8PR9amQ6NBk7K/openapi.json
