Google Maps Photos Scraper - All Place Photos & Videos avatar

Google Maps Photos Scraper - All Place Photos & Videos

Pricing

from $0.70 / 1,000 photos

Go to Apify Store
Google Maps Photos Scraper - All Place Photos & Videos

Google Maps Photos Scraper - All Place Photos & Videos

[💵 $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.

Pricing

from $0.70 / 1,000 photos

Rating

0.0

(0)

Developer

WebData Labs

WebData Labs

Maintained by Community

Actor stats

1

Bookmarked

4

Total users

3

Monthly active users

20 hours ago

Last modified

Share

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 photosNot a second Actor you must chain behind a Maps scraper
Every photo tab swept and deduplicatedNot one tab's worth of photos passed off as the library
One flat row per image, ready for CSV or MLNot a single nested blob you must flatten yourself
latitude, longitude and camera heading per photoNot a bare URL list with the location stripped out
mediaType separating videos from photosNot videos silently padding your photo count
A README cost table that matches the configured priceNot 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

⚙️ 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

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

placeNameimageUrlmediaTypewidth x heightuploadDatelatitude, longitude
Joe's Pizza Broadwaylh3.googleusercontent.com/p/AF1Q…=w1600-h1600-k-nophoto3000 x 40002023-06-0840.75468, -73.98703
Joe's Pizza Broadwaylh3.googleusercontent.com/p/AF1Q…=w1600-h1600-k-novideo1920 x 10802023-07-0340.75468, -73.98703
Secret Patio Lisbonlh3.googleusercontent.com/p/AA0Q…=w1600-h1600-k-nophoto1024 x 6832026-03-1638.71227, -9.13939

Every field on a photo row

FieldTypeWhat it is
photoIdstringGoogle's own id for the image. Stable across runs - the right key to deduplicate, diff and join on.
imageUrlstringDirect image URL at the requested resolution.
mediaTypestringphoto or video.
captionstringGoogle's caption or alt text, where one exists.
widthnumberPixel width of the image.
heightnumberPixel height of the image.
latitudenumberLatitude the photo was taken at.
longitudenumberLongitude the photo was taken at.
headingnumberCamera bearing in degrees, 0 = north.
uploadDatestringUpload date as YYYY-MM-DD.
categorystringReadable category where Google's tag is verified: menu or exterior. null otherwise.
categoryTagnumberGoogle's raw category id, so you can group by it yourself.
tabIdnumberWhich of the place's photo tabs the image came from.
placeIdstringHex CID of the place, e.g. 0x89c259ab3c1ef289:0x3b67a41175949f55.
placeNamestringPlace name, when the place came from a search.
addressstringStreet address, when the place came from a search.
searchQuerystringThe query that found this place, null in URL or ID mode.
scrapedAtstringISO 8601 timestamp of the scrape.

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

FieldTypeWhat it is
placeId, placeName, address, searchQuerystringSame as above, at place level.
sourceUrlstringThe URL or ID you supplied for this place.
photosCountnumberPhotos returned for this place.
photosCountByCategoryobjectBreakdown by category and media type.
totalPhotosAvailablenumberThe largest count Google reported across the place's tabs.
statusstringok, no_photos_found, or the error that place hit.
photosarrayThe photo objects, with every per-photo field above.
scrapedAtstringISO 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.

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

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

$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):

{
"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.

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.