# Google Maps Photos Scraper - All Place Photos & Videos (`webdata_labs/google-maps-photos-scraper`) Actor

\[💵 $1.2 / 1K] Every photo and video for any Google Maps place, from a search query or a place URL - no second Actor needed. One flat row per image with direct URL, size, caption, upload date, media type, geotag and camera heading.

- **URL**: https://apify.com/webdata\_labs/google-maps-photos-scraper.md
- **Developed by:** [WebData Labs](https://apify.com/webdata_labs) (community)
- **Categories:** Travel, For creators, Marketing
- **Stats:** 4 total users, 3 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.70 / 1,000 photos

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

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

## Google Maps Photos Scraper - Every Photo of a Place, Not One Tab

**Pull the full photo and video library of any Google Maps place - direct image URLs, sizes, captions, upload dates, coordinates and camera heading - starting from a plain search query.**

Google splits a place's photos across tabs it composes per place, and every other photo scraper makes you supply place URLs it cannot find on its own: you run a Google Maps scraper first, export a column, paste it in, and hope the tab you got is the one you wanted. This Actor takes `restaurants in Austin TX`, resolves the places itself, sweeps **every** photo tab, deduplicates by Google's own photo id, and writes **one flat row per image** with the fields you would otherwise have to re-derive by hand.

### ✅ What you get / ❌ what this isn't

| ✅ This Actor gives you | ❌ This Actor is not |
|---|---|
| A search query is enough to get photos | Not a second Actor you must chain behind a Maps scraper |
| Every photo tab swept and deduplicated | Not one tab's worth of photos passed off as the library |
| One flat row per image, ready for CSV or ML | Not a single nested blob you must flatten yourself |
| `latitude`, `longitude` and camera `heading` per photo | Not a bare URL list with the location stripped out |
| `mediaType` separating videos from photos | Not videos silently padding your photo count |
| A README cost table that matches the configured price | Not a listing that advertises one price and bills another |

### 🔎 Why use this Actor

- **Search is built in.** `searchQueries` resolves places through Google Maps itself, so `coffee in Austin TX` is a complete input. No place ids, no CSV round trip.
- **The whole library, not one tab.** Google composes photo tabs per place - a restaurant and a hotel do not even use the same set - so the Actor sweeps all of them and dedupes on `photoId` rather than trusting a fixed tab number.
- **Every identifier a buyer actually has.** Full Maps URLs, `maps.app.goo.gl` share links, `place_id:ChIJ…` URLs, and raw hex CIDs all resolve. Coordinate-only URLs are rejected with an explanation instead of silently returning nothing.
- **Geotag and camera heading on every photo.** `latitude`, `longitude` and `heading` come straight from Google's own camera pose block - no competing photo Actor returns them.
- **Two output shapes.** `outputMode: "photo"` gives one row per image for spreadsheets and training sets; `outputMode: "place"` nests photos under their place for joining back to a places export.
- **Videos are labelled, not hidden.** `mediaType` is `photo` or `video`, and `photoCategories: ["videos"]` keeps only the clips.
- **Cost-safe default, no hard ceiling.** 100 photos per place by default so a first run stays cheap; `maxPhotosPerPlace: 0` lifts the limit entirely.
- **Parallel by place, automatically.** Big lists are spread across several workers, each on its own proxy session, with the worker count chosen from the size of the job: measured 111 s for 20 places against 225 s scraping them one at a time. Nothing to tune.

### 👥 Who it's for

Delivery and directory teams, hospitality and real-estate researchers, brand monitors, and ML engineers who need location imagery at volume. Common jobs:

- Pull menu photos for every restaurant in a city to build or enrich a delivery catalogue.
- Collect interior and exterior shots for every hotel or venue in an area for a listings site.
- Build a labelled, geotagged image set for a computer-vision model.
- Track what customers photograph at each branch of a chain, week over week.
- Source authentic location imagery for travel guides and city pages.

### Example tasks

- [Scrape Google Maps photos for any search query](https://apify.com/webdata_labs/google-maps-photos-scraper/examples/google-maps-place-photos-by-search)
- [Get restaurant menu photos from Google Maps](https://apify.com/webdata_labs/google-maps-photos-scraper/examples/google-maps-restaurant-menu-photos)
- [Scrape hotel photos from Google Maps](https://apify.com/webdata_labs/google-maps-photos-scraper/examples/google-maps-hotel-photos)
- [Download Google Maps photos from place URLs](https://apify.com/webdata_labs/google-maps-photos-scraper/examples/google-maps-photos-by-place-url)
- [Get geotagged Google Maps photos with camera heading](https://apify.com/webdata_labs/google-maps-photos-scraper/examples/google-maps-geotagged-photos)
- [Scrape videos from Google Maps places](https://apify.com/webdata_labs/google-maps-photos-scraper/examples/google-maps-place-videos)
- [Build an image training set from Google Maps places](https://apify.com/webdata_labs/google-maps-photos-scraper/examples/google-maps-photos-for-ml-training-data)
- [Google Maps Fotos scrapen (Deutschland)](https://apify.com/webdata_labs/google-maps-photos-scraper/examples/google-maps-fotos-deutschland)

### ⚙️ How to scrape a place's photos

1. Open the Actor on Apify.
2. Enter `searchQueries` like `pizza in New York` or `hotels in Lisbon` - one query per line.
3. Set `maxPlacesPerQuery` for how many places each query should contribute.
4. Optionally paste your own `placeUrls` or `placeIds` instead - search mode is then skipped for those.
5. Choose `imageSize` (`thumbnail`, `medium`, `large`, `original`) and, if you only want part of the library, set `photoCategories`.
6. Leave `maxPhotosPerPlace` at 100 for a cheap first run, or set `0` for everything Google has.
7. Click **Start**.
8. Open the **Photo gallery** view for previews, the **Geotagged photos** view for mapping, or export CSV/JSON/Excel or pull from the API.

#### Scraping a big list of places

Paste the `url` column from any Google Maps scrape into `placeUrls`. The Actor scales its own parallelism to the size of the list, so a few hundred places finish in a fraction of the sequential time, and one unreachable place reports its own status instead of failing the run.

### 📥 Input

```json
{
    "searchQueries": ["pizza in New York"],
    "maxPlacesPerQuery": 5,
    "maxPhotosPerPlace": 100,
    "photoCategories": ["all"],
    "imageSize": "large",
    "outputMode": "photo"
}
```

- `searchQueries` - Google Maps searches to resolve into places. A query is a complete input on its own.
- `placeUrls` - place URLs: full Maps URLs with a `!1s0x…:0x…` segment, `place_id:ChIJ…` URLs, or `maps.app.goo.gl` / `goo.gl/maps` share links. Coordinate-only URLs (`@lat,lng,zoom`) are not valid - click the place pin first so the URL picks up its place id.
- `placeIds` - hex CID identifiers (`0x…:0x…`) when you already have them.
- `photoCategories` - what to keep: `all` (default), `menu`, `exterior`, `photos`, `videos`. Every tab is swept either way; this narrows the dataset and the bill.
- `maxPhotosPerPlace` - default `100`, `0` means unlimited. No hard ceiling.
- `maxPlacesPerQuery` - default `5`. Ignored when you pass place URLs or ids.
- `imageSize` - `thumbnail` (~150px), `medium` (~800px), `large` (~1600px) or `original` (default).
- `outputMode` - `photo` (default, one row per image) or `place` (one row per place, photos nested).
- `language`, `country` - locale for captions and which Google frontend to query, default `English` / `United States`. 18 languages and 21 countries in the dropdowns.

Proxying is handled internally and is not an input: these endpoints block by exit IP, and the Actor's session warming, pacing and rotation are tuned for that. There is nothing for you to configure.

### 📤 Output

| placeName | imageUrl | mediaType | width x height | uploadDate | latitude, longitude |
|---|---|---|---|---|---|
| Joe's Pizza Broadway | `lh3.googleusercontent.com/p/AF1Q…=w1600-h1600-k-no` | photo | 3000 x 4000 | 2023-06-08 | 40.75468, -73.98703 |
| Joe's Pizza Broadway | `lh3.googleusercontent.com/p/AF1Q…=w1600-h1600-k-no` | video | 1920 x 1080 | 2023-07-03 | 40.75468, -73.98703 |
| Secret Patio Lisbon | `lh3.googleusercontent.com/p/AA0Q…=w1600-h1600-k-no` | photo | 1024 x 683 | 2026-03-16 | 38.71227, -9.13939 |

#### Every field on a photo row

| Field | Type | What it is |
|---|---|---|
| `photoId` | string | Google's own id for the image. Stable across runs - the right key to deduplicate, diff and join on. |
| `imageUrl` | string | Direct image URL at the requested resolution. |
| `mediaType` | string | `photo` or `video`. |
| `caption` | string | Google's caption or alt text, where one exists. |
| `width` | number | Pixel width of the image. |
| `height` | number | Pixel height of the image. |
| `latitude` | number | Latitude the photo was taken at. |
| `longitude` | number | Longitude the photo was taken at. |
| `heading` | number | Camera bearing in degrees, 0 = north. |
| `uploadDate` | string | Upload date as `YYYY-MM-DD`. |
| `category` | string | Readable category where Google's tag is verified: `menu` or `exterior`. `null` otherwise. |
| `categoryTag` | number | Google's raw category id, so you can group by it yourself. |
| `tabId` | number | Which of the place's photo tabs the image came from. |
| `placeId` | string | Hex CID of the place, e.g. `0x89c259ab3c1ef289:0x3b67a41175949f55`. |
| `placeName` | string | Place name, when the place came from a search. |
| `address` | string | Street address, when the place came from a search. |
| `searchQuery` | string | The query that found this place, `null` in URL or ID mode. |
| `scrapedAt` | string | ISO 8601 timestamp of the scrape. |

#### Fields on a place row (`outputMode: "place"`)

| Field | Type | What it is |
|---|---|---|
| `placeId`, `placeName`, `address`, `searchQuery` | string | Same as above, at place level. |
| `sourceUrl` | string | The URL or ID you supplied for this place. |
| `photosCount` | number | Photos returned for this place. |
| `photosCountByCategory` | object | Breakdown by category and media type. |
| `totalPhotosAvailable` | number | The largest count Google reported across the place's tabs. |
| `status` | string | `ok`, `no_photos_found`, or the error that place hit. |
| `photos` | array | The photo objects, with every per-photo field above. |
| `scrapedAt` | string | ISO 8601 timestamp of the scrape. |

**About categories:** Google tags every photo with a category id, returned raw as `categoryTag`. Two ids are verified by content and get a readable `category` - **menu** and **exterior**. The rest come through as numbers with `category: null` instead of a guessed label, because a confidently wrong category is worse than an honest number.

### 💵 How much does it cost?

**$1.20 per 1,000 photos** - you pay per photo actually written to the dataset, not per place scanned or per request made. A place with 5 photos costs 5 photos' worth. In search mode there is a second charge of **$2.00 per 1,000 places resolved**, which covers the Maps lookup that turns your query into places; paste your own URLs or ids and that event is never charged. There is **no Actor start fee**.

| Job | Cost |
|---|---|
| 20 places x 100 photos, place URLs supplied | $2.40 |
| 20 places x 100 photos, from search queries | $2.44 |
| 10,000 photos | $12.00 |
| 100,000 photos | $120.00 |

A typical restaurant publishes 40-200 photos and a busy hotel several hundred, so budget from the `totalPhotosAvailable` field a first capped run reports back. Apify platform usage (compute, proxy, storage) is billed separately by your plan and measured at roughly $0.06 per 1,000 photos.

### 🌍 Languages and countries

`language` sets the language of captions and labels Google returns. Supported values:

- English
- German (Deutsch)
- French (Francais)
- Spanish (Espanol)
- Portuguese (Portugues)
- Italian (Italiano)
- Dutch (Nederlands)
- Polish (Polski)
- Turkish (Turkce)
- Russian
- Ukrainian
- Arabic
- Hindi
- Indonesian
- Japanese
- Korean
- Chinese (Simplified)
- Chinese (Traditional)

`country` sets which Google frontend the Actor queries, which changes what a search returns:

- United States
- United Kingdom
- Canada
- Australia
- Germany
- France
- Spain
- Italy
- Netherlands
- Portugal
- Brazil
- Poland
- Turkey
- Mexico
- Argentina
- India
- Indonesia
- Japan
- South Korea
- United Arab Emirates
- South Africa

Place URLs and place IDs work worldwide regardless of these two settings - they address a specific place, not a regional index.

### 🔁 Run it on the Apify platform

Schedule it daily or weekly, call it from the API or any Apify client, and export to JSON, CSV, Excel or XML. Webhooks fire on finish, and the dataset connects to Make, Zapier, Google Sheets, Slack and your own endpoints.

```bash
curl -X POST "https://api.apify.com/v2/acts/webdata_labs~google-maps-photos-scraper/runs?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"searchQueries":["pizza in New York"],"maxPlacesPerQuery":5,"maxPhotosPerPlace":100}'
```

### 🤖 Use with AI agents via MCP

The Actor is exposed over the Model Context Protocol, so an agent can call it as a tool and get structured photo rows back without any glue code. A search query is a complete input, which is what makes it agent-friendly: the agent never has to construct a Google Maps URL.

#### Setup for Claude Code

```bash
claude mcp add apify --transport sse "https://mcp.apify.com/sse?actors=webdata_labs/google-maps-photos-scraper" --header "Authorization: Bearer YOUR_APIFY_TOKEN"
```

#### Setup for Claude Desktop or Cursor

Add this to the MCP configuration file (`claude_desktop_config.json` for Claude Desktop, `.cursor/mcp.json` for Cursor):

```json
{
    "mcpServers": {
        "apify": {
            "url": "https://mcp.apify.com/sse?actors=webdata_labs/google-maps-photos-scraper",
            "headers": { "Authorization": "Bearer YOUR_APIFY_TOKEN" }
        }
    }
}
```

#### Prompts that work

- "Get the menu photos for the top 10 pizza places in New York and list the ones uploaded this year."
- "Pull every photo for this Google Maps place URL and tell me how many are videos."
- "Collect photos for five hotels in Lisbon and give me the coordinates and camera heading for each shot."
- "Build me a CSV of restaurant exterior photos in Austin, original resolution only."

### 🔗 Use this Actor in n8n

n8n ships an Apify node, so this fits into a workflow without custom code.

1. Add the **Apify** node and connect it with your Apify API token.
2. Set the resource to **Actor** and the operation to **Run Actor**.
3. Pick `webdata_labs/google-maps-photos-scraper`.
4. Paste the input JSON, for example `{"searchQueries":["hotels in Lisbon"],"maxPlacesPerQuery":10}`.
5. Enable **Wait for finish**, then add a **Get dataset items** step to pull the rows.
6. Send the rows onward - Google Sheets, Airtable, a database node, or an HTTP request that downloads each `imageUrl`.

For scheduled collection, put an n8n **Schedule Trigger** in front of it, or use an Apify schedule and have n8n subscribe to the run-finished webhook instead.

### ⚠️ Limits and caveats

- **Google decides how many photos a place exposes.** Each tab is served complete in one response and there is no working pagination past it, so if Google publishes 70 photos for a place, 70 is what exists to collect however high you set `maxPhotosPerPlace`.
- **The Actor returns image URLs, not image files.** Downloading and storing the images is your side of the job.
- **Captions are sparse** - roughly a third of photos have one. Dimensions, media type, upload date and coordinates are present on essentially every item.
- **Only two category labels are backed by evidence.** Everything else is an honest `categoryTag` number.
- **`totalPhotosAvailable` is Google's own count** for the busiest tab, so a full sweep can legitimately return more rows than that number.
- **A place with no photos returns a `no_photos_found` row**, not silence, and one unreachable place never fails the whole run.
- **Google rate-limits these endpoints by exit IP.** The Actor paces requests, warms and rotates its own proxy sessions, and retries. That is handled internally, not exposed as an input.

### 🧩 Related Actors

- **[Google Maps Business Scraper API](https://apify.com/webdata_labs/google-maps-business-scraper-api)** - pull the places first, then feed their URLs in here for imagery.
- **[Google Maps Email Leads Scraper](https://apify.com/webdata_labs/google-maps-email-leads-scraper)** - contact details for the same places whose photos you are collecting.
- **[Google Maps Reputation Leads Scraper](https://apify.com/webdata_labs/google-maps-reputation-leads-scraper)** - find places whose ratings need work, then audit how they present themselves visually.
- **[Google Images Scraper](https://apify.com/webdata_labs/google-images-scraper)** - the same job for open-web image search rather than a place page.
- **[Google Search Results Scraper](https://apify.com/webdata_labs/google-search-results-scraper)** - SERP data to pair with the visual side of a location audit.

### ⚖️ Is it legal to scrape Google Maps photos?

This is the question buyers ask most, so here is the honest answer rather than a disclaimer.

**What the Actor reads.** Only data Google serves publicly to any browser, with no login and no account. Nothing behind authentication, nothing private, no personal data beyond what a business itself publishes on its own listing.

**What it returns.** Image **URLs**, not image files. The photos stay on Google's servers; the Actor never downloads or rehosts them. If you fetch and store the images afterwards, that step is yours and so is the responsibility for it.

**Who owns the photos.** Copyright stays with whoever uploaded each one - the business owner or a customer. Collecting a URL for analysis, cataloguing, monitoring or model training is a different act from republishing the image, and only you know which you are doing. Clear republication rights yourself.

**Where the lines are.** Public data scraping has repeatedly been held lawful in the US (`hiQ v. LinkedIn`), and in the EU the GDPR question turns on whether you process personal data and on what basis. Business listing imagery generally is not personal data, but a photo can contain identifiable people, so if you republish or run face recognition on it, that is your processing to justify.

**What is not acceptable.** Do not use the output for harassment, impersonation, passing someone's photos off as your own, or anything unlawful in your jurisdiction. If a rights holder asks you to stop using their image, stop.

None of the above is legal advice. If your use case is commercial republication at scale, ask a lawyer about your specific case rather than trusting a README.

### ❓ FAQ

#### Do I pay for places that have no photos?

Not for photos, because none were written. In search mode the place-resolution event still applies, since the lookup happened; in URL or ID mode nothing is charged for that place at all.

#### Can I get photos from just a place name?

Yes - that is what `searchQueries` is for. You can also pass a Maps share link or a `place_id:ChIJ…` URL if that is what you have.

#### Which photo tab do I get?

All of them. The Actor sweeps every tab Google composed for that place and deduplicates by photo id, so you never have to know how Google organised a particular listing.

#### Are videos included?

Yes, labelled with `mediaType: "video"`. Use `photoCategories: ["videos"]` for clips only or `["photos"]` to exclude them.

#### Can I get the original full-resolution images?

Yes - `imageSize: "original"` is the default and returns the highest resolution Google stores. Street View panorama URLs are returned untouched, since their format does not accept a resize.

#### Do I need a Google API key?

No. There is no API key, no Google Cloud project, no billing account and no quota to manage. The Actor talks to the same public endpoints the Maps website uses.

#### How do I download the actual image files?

Take the `imageUrl` column and fetch each URL - any HTTP client, n8n node or scripting language will do. Set `imageSize` first so you download the resolution you actually want rather than resizing later.

#### Why did a place return fewer photos than Google shows?

Google decides how many photos it exposes per tab, and there is no pagination past that. `totalPhotosAvailable` tells you what Google reported, so you can see the ceiling rather than guess at it.

#### Can I run this on a schedule?

Yes - use an Apify schedule, or trigger it from n8n, Make or your own cron through the API. Photo libraries change slowly, so weekly is usually enough to catch new uploads.

### 📝 Changelog

#### 0.2 - 2026-07-30

- **Fixed: `imageSize: "original"` returned thumbnail URLs.** Google's response already carries a `w203-h152` render spec, and the default setting was passing it straight through - so "original" meant a 203x152, 18 KB image. Original now asks Google for the full file: the same three photos came back at 1.9 MB, 1.7 MB and 3.8 MB.
- **Fixed:** Runs that reached their maximum charge could continue scraping and writing unpaid photos. Photo rows are now written atomically with their charge, nested output is limited to paid photos, and the Actor stops when the spending limit is exhausted.
- Removed the parallelism setting from the input. The Actor now scales workers to the size of the job by itself.
- `language` and `country` are dropdowns with readable names instead of code fields, covering 18 languages and 21 countries.
- Input form grouped into Sources, Filters, Output and Localization.
- Added MCP and n8n integration guides, a full output field table, and this changelog.

#### 0.1 - 2026-07-29

- First release. Search queries, place URLs, share links, `place_id:` URLs and hex CIDs as input.
- Whole-library sweep across every photo tab with deduplication by photo id.
- Flat and nested output modes, geotag and camera heading per photo, media type labelling, upload dates.
- Per-place error isolation: one unreachable place never fails the run.

### 🛠️ Support

Something wrong? Open an issue on the Actor's Issues tab with the run URL, the input you used, and what you expected instead. That is the fastest path to a fix.

### ⭐ Rate this Actor

If this saved you a chained scrape and a CSV cleanup, please leave a review on the **Reviews** tab. Review count is the main trust signal buyers use on the Store, and reviews are what tell me which fields and filters to build next. If something is broken, open an issue first - I would much rather fix it than have you rate around it.

# Actor input Schema

## `searchQueries` (type: `array`):

Search Google Maps and pull photos for the places it finds - no need to run a separate Maps scraper first. Example: "pizza in New York". Combine with maxPlacesPerQuery to control how many places each query contributes.

## `placeUrls` (type: `array`):

Google Maps place URLs. Full URLs containing a !1s0x…:0x… segment, place\_id:ChIJ… URLs, and maps.app.goo.gl / goo.gl/maps share links all work. URLs with only coordinates (@lat,lng,zoom) are not valid - click the place pin first so the URL picks up its place id.

## `placeIds` (type: `array`):

Google Maps place identifiers in hex CID form (0x…:0x…). Use this when you already have ids and want to skip URL parsing entirely.

## `photoCategories` (type: `array`):

The Actor always sweeps every photo tab Google composed for the place and deduplicates by photo id; this narrows what ends up in the dataset. "all" keeps everything. "menu" and "exterior" filter on the category tag Google puts on each photo; "photos" / "videos" split on media type. Every row also carries the raw categoryTag so you can filter further yourself.

## `maxPhotosPerPlace` (type: `integer`):

Cap photos collected per place. A popular restaurant can hold thousands, so the default keeps a first run cheap and fast. Set 0 for unlimited - there is no hard ceiling.

## `maxPlacesPerQuery` (type: `integer`):

How many places each search query contributes. Ignored when you pass place URLs or ids directly.

## `imageSize` (type: `string`):

Resolution of the returned image URLs. "original" returns the full-size file Google stores, which for a phone photo is typically 1-4 MB.

## `outputMode` (type: `string`):

"photo" gives one flat row per image with the place fields on it - drops straight into a CSV or an ML pipeline. "place" gives one row per place with the photos nested, which is easier to join back to a places export.

## `language` (type: `string`):

Language for photo captions and labels Google returns.

## `country` (type: `string`):

Country whose Google frontend the Actor queries. Affects which places a search returns and how results are localized.

## Actor input object example

```json
{
  "searchQueries": [
    "coffee in Austin TX",
    "hotels in Lisbon"
  ],
  "placeUrls": [
    "https://www.google.com/maps/place/Joe's+Pizza/@40.7546,-73.9868,17z/data=!4m6!3m5!1s0x89c259ab3c1ef289:0x3b67a41175949f55!8m2!3d40.7546!4d-73.9868"
  ],
  "placeIds": [
    "0x89c259ab3c1ef289:0x3b67a41175949f55"
  ],
  "photoCategories": [
    "all"
  ],
  "maxPhotosPerPlace": 20,
  "maxPlacesPerQuery": 2,
  "imageSize": "large",
  "outputMode": "photo",
  "language": "en",
  "country": "us"
}
```

# Actor output Schema

## `gallery` (type: `string`):

No description

## `geotagged` (type: `string`):

No description

## `places` (type: `string`):

No description

## `runSummary` (type: `string`):

No description

# 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 = {
    "searchQueries": [
        "pizza in New York"
    ],
    "photoCategories": [
        "all"
    ],
    "maxPhotosPerPlace": 20,
    "maxPlacesPerQuery": 2,
    "imageSize": "large",
    "outputMode": "photo",
    "language": "en",
    "country": "us"
};

// Run the Actor and wait for it to finish
const run = await client.actor("webdata_labs/google-maps-photos-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 = {
    "searchQueries": ["pizza in New York"],
    "photoCategories": ["all"],
    "maxPhotosPerPlace": 20,
    "maxPlacesPerQuery": 2,
    "imageSize": "large",
    "outputMode": "photo",
    "language": "en",
    "country": "us",
}

# Run the Actor and wait for it to finish
run = client.actor("webdata_labs/google-maps-photos-scraper").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "searchQueries": [
    "pizza in New York"
  ],
  "photoCategories": [
    "all"
  ],
  "maxPhotosPerPlace": 20,
  "maxPlacesPerQuery": 2,
  "imageSize": "large",
  "outputMode": "photo",
  "language": "en",
  "country": "us"
}' |
apify call webdata_labs/google-maps-photos-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=webdata_labs/google-maps-photos-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

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