~ / guides / How to Scrape Google Images (Python Guide)

How to Scrape Google Images (Python Guide)

MR
Marcus Reed
Founder & lead tester · about the author
the short version
  • I fetched google.com/search?tbm=isch live 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=isch parameter now redirects to udm=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.

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.

FieldAvailable?Where it livesNotes
Thumbnail imageYesencrypted-tbn0.gstatic.com URLLow-res preview Google hosts
Full-resolution URLYesOriginal host domainBehind a click; not Google-hosted
Source page URLYesResult metadataThe page the image appears on
Title / alt textYesResult metadataShort descriptive string
Image dimensionsYesResult metadataWidth x height of the original
Host domainYesSource URLUseful for filtering by site
Related searchesYesPage chipsRendered, 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:

FactorHeadless browser (DIY)Scraper API
Runs the page JSYou manage ChromiumHandled for you
OutputRaw DOM you parseParsed JSON
Proxies / IP rotationYou build itIncluded
CAPTCHA / /sorry/ pageYou solve itHandled
Full-res imagesClick each resultSource URLs returned
CostFree (your compute)Per-request, paid
Scales to thousandsSlow, blocks appearVendor’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

Is there an official Google Images API?

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.

Why does tbm=isch return no images now?

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.

Can I just download the thumbnail URLs from the HTML?

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.

MR
Marcus Reed
I've built and run web scrapers for the better part of a decade. On this site I put scraper APIs and scraping tools through real jobs against real targets, then write up what actually holds up.