Google Maps Place Photos Scraper
Scrape the photos and videos on a Google Maps place: full-size image URL, caption, pixel size, upload date and coordinates. No API key, no account.
How it works
- 1Open it on Apify
Hit Run on Apify — it opens the tool in the cloud, no install.
- 2Set the inputs
Adjust
placeUrls,maxPhotosPerPlace,maxPhotos(sensible defaults are pre-filled). - 3Click Run
The tool runs on Apify’s cloud and collects the data for you.
- 4Export the results
Download as JSON, CSV or Excel, or pipe straight into your app, Google Sheets, or an AI agent.
Pricing
$0.00023 per photo = $0.23 per 1,000
| You are charged for | When | Price |
|---|---|---|
| Photo scraped | One photo from a place gallery, with its size, upload date and caption where there is one. Blocked runs are never charged. | $0.00023 |
Pay-per-event pricing: you are billed per result, not per subscription. Billing is handled by Apify on your own account. These are the live Apify store prices, in effect since 2026-09-20, and they are what you are actually charged.
Inputs
| Field | What it does | Type |
|---|---|---|
placeUrls | The places whose pictures you want, up to 50 per run. A full Google Maps link works, so does the short link from the Share button, a ?cid= link, or the raw 0x...:0x... place id if you already have one. | array |
maxPhotosPerPlace | Ceiling on how many rows a single place may produce. | integer |
maxPhotos | Total rows for the whole run, shared evenly between your places. Keep it low while you are testing - you pay per row. | integer |
mediaType | Keep everything, drop the videos and Street View panoramas, or keep only video clips. | string |
imageWidth | Width for the photoUrl link on each row. The height follows the picture's own aspect ratio. Each row also reports the native size, so you can tell when you asked for more pixels than exist. | integer |
language | Two-letter interface language. It changes captions Google has translated, not the pictures themselves. | string |
country | Two-letter country code used for the request. | string |
proxyUrls | Leave this empty. By default the run rotates a large pool of addresses that cost you nothing per gigabyte. Fill it in only if you specifically want the traffic to leave through proxy servers you already pay for, in the form http://user:pass@host:port. | array |
What you get
A structured dataset — each result includes fields like:
placeNamemediaTypephotoUrlcaptionwidthheightuploadedAtphotoIdplaceUrlExport every run as JSON, CSV or Excel, or send it to your app, a database, Google Sheets, or an AI agent.
Related tools in Developer & Research Tools
Other ready-to-run tools in the same category — all pay-per-use on the Apify cloud.
GitHub Scraper
Search GitHub repos and users: stars, forks, language, topics, licence, plus user bio, company and followers. No token needed. $0.90 per 1,000 rows.
Stack Overflow / Stack Exchange Scraper
Search Stack Overflow and Stack Exchange by keyword or tag. Score, answer count, views, reputation and body text. $2 per 1,000 questions.
Package Registry Scraper (npm + PyPI)
Get npm and PyPI package metadata as JSON. Version, license, author, repo, keywords and npm monthly downloads. $2 per 1,000 packages.
arXiv Scraper
Search arXiv papers by title, author, abstract or category. Get full abstracts, authors, categories, DOI, dates and PDF links. $2 per 1,000 papers.
OpenAlex Scholarly Works Scraper
Search 250M+ OpenAlex papers with no API key. Get titles, authors, venue, year, citations, DOI, OA links and full abstracts. $2.00 per 1,000 papers.
Crossref Scholarly Works Scraper
Search 150M+ papers on Crossref: DOI, title, authors, journal, publisher, date, citations and abstract. No API key. $1.00 per 1,000 works.
Where this tool sits
- Categories
- Developer & Research Tools
Google Maps Place Photos Scraper
Point it at a Google Maps place and get back the pictures on it — one row per photo or video, with a full-size image link, the caption the person typed, the pixel dimensions, the date it went up and where the camera was standing. No API key, no Google account, no browser.
- A full-size image link, not a thumbnail. Ask for the width you want and the URL comes back at that width.
- Photos, videos and Street View entries are labelled, so you can keep only the kind you need.
- Every row carries the upload date, so a venue's pictures sort by year without any extra work.
- Runs with empty input return a labelled sample row, free, so you can see the shape before spending anything.
Price
$0.23 per 1,000 photos, plus a $0.0005 start fee per run.
This is a flat rate on every plan, free or paid. There are no volume tiers, no minimum spend, no subscription and no add-on fees. What you read here is what you pay on day one and on day four hundred.
| Photos | Total cost |
|---|---|
| 100 | $0.0235 |
| 1,000 | $0.2305 |
| 10,000 | $2.3005 |
| 100,000 | $23.0005 |
What is actually charged
- One
photo-scrapedevent per photo row written to the dataset. Nothing else is metered per row. - Free: the sample row an empty run returns, and every diagnostic row — a blocked target, a dead URL, a search that matched nothing. Those rows all carry
"charged": false. - A place that shows no pictures at all costs nothing beyond the start fee.
- Videos and Street View entries you filtered out are dropped before they are charged.
- A run that finds nothing costs the start fee and nothing more.
- Rows never leave the dataset without a charge, and are never charged without a row. The billed event is a named one, so there is no price quietly attached to
apify-default-dataset-item— the trick that makes some scrapers bill you for their own error messages.
Input
{
"placeUrls": [
"https://www.google.com/maps/place/Starbucks+Reserve+Roastery/data=!4m2!3m1!1s0x54906acdccf44db1:0x6da0c1f2d7a6736e",
"https://maps.app.goo.gl/example"
],
"maxPhotosPerPlace": 30,
"maxPhotos": 60,
"mediaType": "all",
"imageWidth": 1600
}
| Field | What it does |
|---|---|
placeUrls | One or more places, up to 50 per run. A full Google Maps link works, so does the short link from the Share button, a ?cid= link, or the raw 0x...:0x... id if you already have one. |
maxPhotosPerPlace | Ceiling on rows from a single place. Default 30. |
maxPhotos | Total rows for the whole run, shared evenly between your places. Default 200. Keep it low while you are testing — you pay per row. |
mediaType | all (default), photos to drop videos and Street View, or videos to keep only clips. |
imageWidth | Pixel width for the photoUrl link. Default 1600. The height follows the original aspect ratio. |
language | Two-letter interface language, default en. It changes captions Google has translated, not the pictures. |
country | Two-letter country for the request, default us. |
proxyUrls | Leave empty. Fill it in only if you want the traffic to leave through proxy servers you already pay for, as http://user:pass@host:port. |
Run it with empty input and you get one clearly labelled sample row, free, so you can see the output shape before you spend anything.
Output
One row per photo. A real row from a real run:
{
"ok": true,
"charged": true,
"recordType": "photo",
"target": "https://www.google.com/maps?cid=7899526995152106350",
"placeName": "Starbucks Reserve Roastery",
"placeId": "0x54906acdccf44db1:0x6da0c1f2d7a6736e",
"cid": "7899526995152106350",
"placeUrl": "https://www.google.com/maps?cid=7899526995152106350",
"photoId": "CIHM0ogKEICAgICxpfGj4wE",
"mediaType": "photo",
"photoUrl": "https://lh3.googleusercontent.com/gps-cs-s/AHRPTWlnERVZyB9D3zFYFg7wFEDmh5g=w1600-h900-k-no",
"thumbnailUrl": "https://lh3.googleusercontent.com/gps-cs-s/AHRPTWlnERVZyB9D3zFYFg7wFEDmh5g=w203-h114-k-no",
"caption": "Espresso flight of 2",
"width": 4032,
"height": 2268,
"uploadedAt": "2022-11-10T20:00:00.000Z",
"latitude": 47.6140399947503,
"longitude": -122.328100047145,
"videoDurationMs": null,
"videoUrl": null,
"scrapedAt": "2026-09-19T12:04:11.418Z"
}
Field notes
photoUrl— the image at the width you asked for. Google serves these directly; no token, no expiry seen in testing.thumbnailUrl— the small version exactly as Google returned it, if you want to avoid re-requesting a big file.caption— what the uploader typed under the picture. Null when they typed nothing, and null when the only text was the place name repeated.mediaType—photo,videoorstreet_view.uploadedAt— when the picture went up, to the hour, in UTC. Google does not publish the minute.latitude/longitude— where the camera was, not the centre of the business. On a large place these differ by tens of metres.videoDurationMs— clip length in milliseconds, on video rows only.placeId— Google's internal feature id for the place, stable across runs and the safest join key.cid— the same place as a decimal id, which is whatmaps?cid=links use.
Every real row carries "charged": true. Sample rows carry "_sample": true and diagnostic rows carry "_diagnostic": true with an errorCode you can filter on, and neither is ever billed.
How it works
- The gallery is not in the page source — a logged-out fetch of a place page comes back with no image links in it at all. The pictures come from the JSON feed the Maps front end calls for itself, so that is what this reads.
- That feed only answers properly once it has handed the caller a guest session, so each run asks Google for its own front page first and keeps what it gets back. Nobody logs in and no account is involved.
- Requests are paced, because the feed limits how often it will answer.
- If an address stops serving the gallery, the run moves to another one rather than reporting the place as empty. That distinction matters: "no photos here" and "this run got throttled" look identical unless you check, and only one of them is true.
What people use it for
- Filling a listing, directory or travel page with real photography of a venue instead of stock images.
- Watching what a location looks like over time — the upload dates let you sort a venue's pictures by year and see a refurbishment happen.
- Checking whether a business's own pictures are outnumbered by customer ones, which is usually the first sign a listing has been left to drift.
- Building an image set for a computer-vision job, where the caption and the pixel dimensions matter as much as the file.
- Auditing a chain: run every branch through in one go and compare how many pictures each location has.
Reading the output
Every run writes three kinds of row, and they are easy to tell apart:
- Real rows carry
"charged": trueand"recordType": "photo". One billed event each. - The sample row carries
"_sample": trueand"charged": false. There is exactly one, it only appears when the input had no places in it, and it is there so you can look at the shape of the output before you spend anything. - Diagnostic rows carry
"_diagnostic": true,"charged": falseand anerrorCodeyou can switch on:PLACE_NOT_RESOLVEDwhen the link could not be tied to a place,NO_PHOTOSwhen the place genuinely shows none,BLOCKEDwhen every request the run made was refused the gallery,PHOTOS_PARSEwhen the feed answered with something else,NETWORKwhen it could not be reached, andTIME_BUDGETwhen the run ran out of time before reaching a place.
If you only want the data, filter on charged == true. The count of those rows equals the number of events you were billed for, so the dataset is its own invoice.
Limitations
- This returns the pictures on a place, and nothing else about it. No rating, no address, no opening hours, no reviews.
- There is no uploader name in the feed and none is invented, so rows tell you what the picture is and when it went up, not who took it.
- Captions are rare. On a 150-picture venue measured here, 4 rows carried a caption and the other 146 came back with
caption: null. Treat it as a bonus, not a field you can plan around. - The upload time is only published to the hour, so two pictures added in the same hour cannot be ordered between themselves.
- Street View entries are labelled and returned, but they are panorama stills rather than someone's photograph, so most callers will want
mediaType: "photos". - A place with no pictures returns an uncharged diagnostic row, not an empty dataset.
- Short links and
ChIJids each cost one extra request to turn into a place before any picture is read. - The hard ceiling is 50 places per run. For more than that, split the work across runs.
Questions
Do I need a Google API key?
No. There is no key, no OAuth and no Google account anywhere in the run. It reads the same public feed the Maps website uses when you click the photo grid.
How do I get the link for a place?
Open the place on Google Maps and copy the address bar, or press Share and copy the short link. Both work. If you already store Google place ids, paste those instead.
Can I get the original resolution?
Ask for the width you want with imageWidth. Each row also reports the picture's native width and height, so you can tell whether you asked for more pixels than exist.
Will the run fail if a place is blocked?
No. You get an uncharged diagnostic row saying what happened and the run finishes as succeeded. A failed run would still bill the start fee, which would mean paying to be told something went wrong.
Does it tell me who uploaded each photo?
No. The feed this reads does not carry the uploader, and nothing is guessed or joined in from elsewhere. If you need attribution, the photo page on Google Maps shows it.
Can I run this on a schedule?
Yes. Nothing is held between runs. photoId is stable, so diff on it to see only what is new since last time.