How to Scrape Google Images (Python Guide)
- I fetched
google.com/search?tbm=ischlive on June 12, 2026: HTTP 200, 91,200 bytes, but 0<img>tags and 0 thumbnail URLs. Google serves a JavaScript-only shell, so requests + BeautifulSoup parses nothing. - The old
tbm=ischparameter now redirects toudm=2, Google's current image mode. Both return the same empty shell to a no-JS client. - DIY that works needs a headless browser (Playwright/Selenium) to run the page's JavaScript, plus proxies and CAPTCHA handling once you scale past a few queries.
- For volume or clean JSON without managing browsers and IPs, a scraper API like ChocoData renders the page and returns parsed image results.
Google Images is the largest public index of images on the web, which makes it a magnet for dataset building, reverse-image research, and brand monitoring. This guide builds a real Python scraper and is honest about where Google blocks you. I fetched the live image-search page on June 12, 2026 and pasted the real result below. The short version: a plain request returns HTTP 200 but a JavaScript-only shell with zero image tags, so requests plus BeautifulSoup cannot parse it. The methods that work are a headless browser or a scraper API. For the fundamentals, see my web scraping guide and the Python walkthrough.
Can you scrape Google Images, and is it legal?
You can collect public Google Images data with code, but a plain HTTP scraper fails on the technical side, and the legal risk depends on method and what you store. The technical reality first: the image-search page renders results in JavaScript, so a no-cookie GET returns an empty shell (I prove this below). The legal side has two layers worth separating.
On US computer-access law, reading pages anyone can load without logging in sits on the safer side. In hiQ Labs v. LinkedIn, the Ninth Circuit held that scraping public data does not violate the Computer Fraud and Abuse Act, and the Supreme Court’s Van Buren decision narrowed the CFAA toward traditional hacking. Loading a public Google search page is not, by itself, a federal computer crime.
Two further constraints apply. Contract law is the live risk: Google’s Terms of Service restrict automated access, and hiQ won the CFAA question yet still paid a $500,000 judgment for breaching a user agreement. Copyright is the second and it bites harder here than with text: the images Google indexes are owned by the original hosts, and downloading full-resolution files to redistribute is a copyright problem, not a Google problem. Store the result metadata (source URL, host, thumbnail link) rather than rehosting images, never script a logged-in Google account, and respect each source’s rights. I am a tester, not a lawyer, so get advice before a commercial scrape. My is web scraping legal pillar covers the full picture.
What data can you extract from Google Images?
Each image result carries a thumbnail, the full-resolution source URL, the host page, and a title, but none of it is in the plain HTML. The table shows what exists once the page renders and where it lives.
| Field | Available? | Where it lives | Notes |
|---|---|---|---|
| Thumbnail image | Yes | encrypted-tbn0.gstatic.com URL | Low-res preview Google hosts |
| Full-resolution URL | Yes | Original host domain | Behind a click; not Google-hosted |
| Source page URL | Yes | Result metadata | The page the image appears on |
| Title / alt text | Yes | Result metadata | Short descriptive string |
| Image dimensions | Yes | Result metadata | Width x height of the original |
| Host domain | Yes | Source URL | Useful for filtering by site |
| Related searches | Yes | Page chips | Rendered, JS only |
Every field above requires the page’s JavaScript to run first. A plain request returns none of them, which the next section demonstrates with live output.
How does Google Images block scrapers?
Google Images stops naive scrapers primarily by rendering everything in JavaScript, then layers IP rate limits and CAPTCHAs on top. When I requested the image-search URL without cookies, the response was HTTP 200 but the body was the pre-render shell, not results. Here is the proof from my run on June 12, 2026:
import requests, re
UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36")
r = requests.get(
"https://www.google.com/search?q=puppies&tbm=isch",
headers={"User-Agent": UA, "Accept-Language": "en-US,en;q=0.9"},
timeout=20,
)
body = r.text.lower()
img_urls = re.findall(r"https?://[^\"\s]+\.(?:jpg|jpeg|png|webp)", r.text)
print("HTTP", r.status_code, "| bytes", len(r.text))
print("final URL:", r.url)
print("<img> tags:", body.count("<img"))
print("gstatic thumbnails:", body.count("encrypted-tbn"))
print("real image URLs in HTML:", len(img_urls))
print("JS-required shell:", "enablejs" in body and "noscript" in body)
HTTP 200 | bytes 91200
final URL: https://www.google.com/search?q=puppies&udm=2
<img> tags: 0
gstatic thumbnails: 0
real image URLs in HTML: 0
JS-required shell: True
Three things stand out. The request returned 200 and 91,200 bytes, so it looks like a success, but every byte is the JavaScript shell. Zero <img> tags and zero encrypted-tbn URLs mean there is nothing for BeautifulSoup to parse. And the tbm=isch parameter I requested was rewritten to udm=2, Google’s current image-search mode, which serves the same empty shell to a no-JS client. Past the rendering wall, a single IP firing rapid queries draws rate limits and a /sorry/ CAPTCHA interstitial. The plain-requests approach clears none of this, so there is no honest BeautifulSoup-only scraper to show. The working methods follow.
Method 1: Scrape Google Images with a headless browser (DIY)
The DIY method that works runs the page in a headless browser so its JavaScript executes and the image grid renders. Playwright is the cleanest option in Python; Selenium works too. I did not install Playwright in this environment, so I am not pasting fake output for this section. The structure of a working scraper looks like this:
# pip install playwright && playwright install chromium
from playwright.sync_api import sync_playwright
QUERY = "puppies"
URL = f"https://www.google.com/search?q={QUERY}&udm=2"
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page(locale="en-US")
page.goto(URL, wait_until="networkidle", timeout=60_000)
# Google may show a consent dialog first; accept it if present.
try:
page.get_by_role("button", name="Accept all").click(timeout=4000)
except Exception:
pass
page.mouse.wheel(0, 6000) # lazy-loaded grid: scroll to load more
page.wait_for_timeout(1500)
# After JS runs, thumbnails exist as <img> elements in the result grid.
thumbs = page.locator("div[data-attrid] img, g-img img")
count = thumbs.count()
print("rendered image elements:", count)
for i in range(min(count, 10)):
print(thumbs.nth(i).get_attribute("src"))
browser.close()
Why this works where requests fails: Playwright drives a real Chromium, so the page’s JavaScript runs and the grid hydrates into actual <img> elements you can read. Three honest limits to plan around. First, the src you read is the encrypted-tbn gstatic thumbnail (a low-res preview); the full-resolution file lives on the original host and loads only after a click, so grabbing originals means clicking each result and reading the loaded image. Second, Google lazy-loads the grid, so you scroll to pull more than the first ~20-50 results. Third, this is one browser on one IP: run it at volume and you hit rate limits and the /sorry/ CAPTCHA, at which point you are adding proxy rotation and CAPTCHA solving yourself. For the selector mechanics, see my XPath and CSS selectors guide; for the wider toolkit, Scrapy plus a browser is the heavier route, with all the anti-block work falling on you.
Method 2: Scrape Google Images with a scraper API
A scraper API is the right tool when you want the rendered results as clean JSON without running browsers, rotating IPs, or solving CAPTCHAs yourself. It executes the page’s JavaScript on its side, routes through a residential IP pool, retries on blocks, and returns parsed image data. ChocoData exposes a universal endpoint (one URL for any site) plus 453 dedicated endpoints. You point the universal endpoint at the Google Images URL:
import requests
API_KEY = "cd_live_YOUR_KEY" # free tier: 1,000 requests/month, no card
target = "https://www.google.com/search?q=puppies&udm=2"
resp = requests.get(
"https://api.chocodata.com/api/v1/universal/get",
params={
"api_key": API_KEY,
"url": target,
"render": "true", # run the page JavaScript so the grid hydrates
"country": "us", # exit from a US residential IP
},
timeout=90,
)
print("STATUS:", resp.status_code)
data = resp.json() # rendered result: thumbnails, source URLs, titles
I did not run this call (it needs a paid key), so I am not pasting fake output. The endpoint pattern is GET /api/v1/{site}/{resource}, so a dedicated route is also available where one exists; verify the exact field names against ChocoData’s own docs on the free tier before you build against them. What an API buys you over the headless-browser route, by trade-off:
| Factor | Headless browser (DIY) | Scraper API |
|---|---|---|
| Runs the page JS | You manage Chromium | Handled for you |
| Output | Raw DOM you parse | Parsed JSON |
| Proxies / IP rotation | You build it | Included |
CAPTCHA / /sorry/ page | You solve it | Handled |
| Full-res images | Click each result | Source URLs returned |
| Cost | Free (your compute) | Per-request, paid |
| Scales to thousands | Slow, blocks appear | Vendor’s problem |
Decision rule: for a one-off pull of a few hundred images, a headless browser on your own machine is enough and free, so I would ship that. For steady volume, many queries, or when you do not want to babysit a browser farm and proxy pool, the API earns its cost. For the wider anti-block toolkit (proxies, headers, retries), see scraping without getting blocked.
What are the limits of scraping Google Images?
The honest limits: no official API, a JavaScript-only page that defeats plain HTTP, thumbnails instead of originals, and aggressive rate limiting at scale. The full-resolution files are owned by their original hosts, so the cleanest legal posture is to collect source URLs and Google-hosted thumbnails as metadata, then fetch originals from the rights holder only when your use allows it. A headless browser gets you the rendered grid for free but caps out fast on one IP; a scraper API removes the rendering, IP, and CAPTCHA work for a per-request fee. Build on Playwright for small jobs, layer an API when you need volume or clean JSON, and store links and thumbnails rather than rehosted originals to stay on the right side of copyright. For the broader picture, start with my web scraping pillar.
FAQ
No. Google has no public Images API. The closest sanctioned product is Programmable Search Engine (Custom Search JSON API), which can return image results for a search engine you configure, capped at 100 free queries per day and 10 results per call. It is not the full google.com/search image grid.
Google migrated image search to the udm=2 parameter, and the page renders results client-side in JavaScript. A plain HTTP request gets the pre-render shell, so tbm=isch and udm=2 both hand a no-JS client a page with zero image elements. You need a browser engine to execute the script.
Not from a plain request, because they are not in the served HTML. After a headless browser runs the page, thumbnails appear as encrypted-tbn gstatic URLs (low-res previews) and the full-resolution source sits behind a click that loads the original host's image.