> ## Content Index
> Fetch the complete content index at: https://serpapi.com/blog/llms.txt
> Use this file to discover other available public pages before exploring further.

# Scrape Google Lens with Python
- URL: https://serpapi.com/blog/scrape-google-lens/
- Published: 2023-01-25T11:00:00.000Z
- Updated: 2025-04-04T00:28:33.000Z
- Description: This blog post is a step-by-step tutorial to scraping Google Lens using Python.
- Author: Artur Chukhrai
- Tags: Google Lens, Web Scraping, Python

## What will be scraped

![wwbs-google-lens](https://user-images.githubusercontent.com/81998012/210693341-3474574d-cb2e-4a12-a2e7-b82d1abcfd23.png)

> Google has introduced a new layout for Google Lens. Here is an updated tutorial on [how to scrape Google Lens results using Python](https://serpapi.com/blog/scraping-google-lens-new-layout-visual-matches/)

## Using Google Lens API from SerpApi

If you don't need an explanation, have a look at [the full code example in the online IDE](https://replit.com/@chukhraiartur/blog-google-lens-api#main.py).

```python
from serpapi import GoogleSearch
import json

params = {
    'api_key': '...',
    'engine': 'google_lens',
    'url': 'https://user-images.githubusercontent.com/81998012/210290011-c175603d-f319-4620-b886-1eaad5c94d84.jpg',
    'hl': 'en',
}

search = GoogleSearch(params)                   # data extraction on the SerpApi backend
google_lens_results = search.get_dict()         # JSON -> Python dict

del google_lens_results['search_metadata']
del google_lens_results['search_parameters']

print(json.dumps(google_lens_results, indent=2, ensure_ascii=False))

```

### Why using API?

There're a couple of reasons that may use API, ours in particular:

- No need to create a parser from scratch and maintain it.
- Bypass blocks from Google: solve CAPTCHA or solve IP blocks.
- Pay for proxies, and CAPTCHA solvers.
- Don't need to use browser automation.

SerpApi handles everything on the backend with fast response times under \~4.3 seconds per request and without browser automation, which becomes much faster. Response times and status rates are shown under [SerpApi Status page](https://serpapi.com/status):

![serpapi-status-product](https://user-images.githubusercontent.com/81998012/210670143-c6216e06-7bce-4fe2-a997-dbe27032605f.png)

Head to the [Google Lens playground](https://serpapi.com/playground?engine=google%5Flens) for a live and interactive demo.

### Preparation

Install library:

```lang-none
pip install google-search-results

```

[google-search-results](https://github.com/serpapi/google-search-results-python) is a SerpApi API package.

### Code Explanation

Import libraries:

```python
from serpapi import GoogleSearch
import json

```

| Library                                             | Purpose                                                                                        |
| --------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| GoogleSearch                                        | to scrape and parse Google results using [SerpApi](https://serpapi.com/) web scraping library. |
| [json](https://docs.python.org/3/library/json.html) | to convert extracted data to a JSON object.                                                    |

The parameters are defined for generating the URL. If you want to pass other parameters to the URL, you can do so using the `params` dictionary:

```python
params = {
    'api_key': '...',
    'engine': 'google_lens',
    'url': 'https://user-images.githubusercontent.com/81998012/210290011-c175603d-f319-4620-b886-1eaad5c94d84.jpg',
    'hl': 'en',
}

```

| Parameters                                                                                | Explanation                                                                                                                                                                                                                 |
| ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [api\_key](https://serpapi.com/google-lens-api#api-parameters-serpapi-parameters-api-key) | Parameter defines the SerpApi private key to use. You can find it under [your account -> API key](https://serpapi.com/manage-api-key).                                                                                      |
| [engine](https://serpapi.com/google-lens-api#api-parameters-serpapi-parameters-engine)    | Set parameter to google\_lens to use the Google Lens API engine.                                                                                                                                                            |
| [url](https://serpapi.com/google-lens-api#api-parameters-search-query-url)                | Parameter defines the URL of an image to perform the Google Lens search.                                                                                                                                                    |
| [hl](https://serpapi.com/google-lens-api#api-parameters-localization-hl)                  | Parameter defines the language to use for the Google Lens search. It's a two-letter language code. Head to the [Google languages page](https://serpapi.com/google-languages) for a full list of supported Google languages. |

📌Note: You can also add other [API Parameters](https://serpapi.com/google-lens-api#api-parameters).

Then, we create a `search` object where the data is retrieved from the SerpApi backend. In the `google_lens_results` dictionary we get data from JSON:

```python
search = GoogleSearch(params)               # data extraction on the SerpApi backend
google_lens_results = search.get_dict()     # JSON -> Python dict

```

The `google_lens_results` dictionary, in addition to the necessary data, contains information about the request. The request information is not needed, so we remove the corresponding keys using the [del statement](https://docs.python.org/3/tutorial/datastructures.html#the-del-statement):

```python
del google_lens_results['search_metadata']
del google_lens_results['search_parameters']

```

After all the data is retrieved, it is output in JSON format:

```python
print(json.dumps(google_lens_results, indent=2, ensure_ascii=False))

```

### Output

```json
{
  "reverse_image_search": {
    "link": "https://www.google.com/search?tbs=sbi:AMhZZiurdULpuTy4_1HSkPv2ZrEBN9afXDH2j7s2drhaSQmdFuOJlf9HaxhrjxEfBrWzj1xi-ZONFSwWi3UlhnMtRXlu68S24Kv5fLuNstTqFQfpUQXGbPBuplF8jDJuvLTDAJow06N44R7keGB1GOU5fRzsc4rirzA"
  },
  "knowledge_graph": [
    {
      "title": "Black cat",
      "link": "https://www.google.com/search?q=Black+cat&kgmid=/m/03dj64&hl=en&gl=US",
      "more_images": {
        "link": "https://www.google.com/search?q=Black+cat&kgmid=/m/03dj64&ved=0EOTpBwgAKAAwAA&source=.lens.button&tbm=isch&hl=en&gl=US",
        "serpapi_link": "https://serpapi.com/search.json?device=desktop&engine=google&gl=US&google_domain=google.com&hl=en&q=Black+cat&tbm=isch"
      },
      "thumbnail": "https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcQEdppH7x_edJGSKSky2KSKK773r4HOp55AnejH0-sYBpO3-M5w",
      "images": [
        {
          "title": "Image #1 for Black cat",
          "source": "https://vbspca.com/tag/stigma/",
          "link": "https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcQEdppH7x_edJGSKSky2KSKK773r4HOp55AnejH0-sYBpO3-M5w",
          "size": {
            "width": 293,
            "height": 172
          }
        },
        ... other images
      ]
    },
    ... other knowledge graph results
  ],
  "visual_matches": [
    {
      "position": 1,
      "title": "Pet Talk: Smoke can create problems quickly for your cat | VailDaily.com",
      "link": "https://www.vaildaily.com/opinion/pet-talk-smoke-can-create-problems-quickly-for-your-cat/",
      "source": "vaildaily.com",
      "source_icon": "https://encrypted-tbn0.gstatic.com/favicon-tbn?q=tbn:ANd9GcSXpzpJuQgYt20Jd-moiGdOr6HoDpS-WQ_vjcfrNvtLJy_gjDrYJIs3abOVeBb7g24x5kLNBg2T-KGdiQ_NkFkcBjt2s7exhkQg46swp-DMTF3S1_lemg",
      "thumbnail": "https://encrypted-tbn1.gstatic.com/images?q=tbn:ANd9GcQCjR3dx5H8xz9fSevbe6JqPtBlakSxJwrECbaMS64UcP05CwC4"
    },
    ... other visual matches results
  ]
}

```

## DIY solution

This section is to show the comparison between our solution and the DIY solution.

The fact is that when you click on a regular link, it changes to another link. The GIF below shows this:

![google-lens](https://user-images.githubusercontent.com/81998012/210688661-855c42c5-1e3b-4329-82ff-394ba0c7d1b7.gif)

The data is correspondingly different and there is no way to extract it without reverse engineering. For simplicity, the DIY solution uses [playwright](https://pypi.org/project/playwright/). It helps to extract data from the modified link.

The data extraction itself is done with [selectolax](https://pypi.org/project/selectolax/) because it has Lexbor parser which is incredibly fast. In terms of syntax, it is very similar to both `bs4` and `parsel`, making it easy to use. Please note that [selectolax does not currently support XPath](https://news.ycombinator.com/item?id=28144159).

Example code to integrate:

```python
from playwright.sync_api import sync_playwright
from selectolax.lexbor import LexborHTMLParser
import json

def run(playwright):
    image_url = 'https://user-images.githubusercontent.com/81998012/210290011-c175603d-f319-4620-b886-1eaad5c94d84.jpg'

    page = playwright.chromium.launch(headless=True).new_page()
    page.goto(f'https://lens.google.com/uploadbyurl?url={image_url}&hl=en')
    
    parser = LexborHTMLParser(page.content())
    page.close()

    reverse_image_search = {
        'link': parser.root.css_first('.kuwdsf .VfPpkd-RLmnJb').attributes['href']
    }

    knowledge_graph = {
        'title': parser.root.css_first('.DeMn2d').text(),
        'subtitle': parser.root.css_first('.XNTym').text() if parser.root.css_first('.XNTym') else None,
        'link': parser.root.css_first('.OCDsub .VfPpkd-RLmnJb').attributes['href'],
        'more_images': parser.root.css_first('[aria-label="More Images"]').attributes['href'],
        'thumbnail': parser.root.css_first('.oLfv5c .FH8DCc').attributes['src'],
        'images': [
            {
                'title': image.attributes['aria-label'],
                'source': image.attributes['href'],
                'link': image.css_first('.wETe9b').attributes['src']
            }
            for image in parser.root.css('.Y02Gld a')
        ]
    }

    visual_matches = [
        {
            'title': result.css_first('.UAiK1e').text(),
            'link': result.css_first('.GZrdsf').attributes['href'],
            'source': result.css_first('.fjbPGe').text(),
            'source_icon': result.css_first('.KRdrw').attributes['src'],
            'thumbnail': result.css_first('.jFVN1').attributes['src']
        }
        for result in parser.root.css('.xuQ19b')
    ]

    google_lens_results = {
        'reverse_image_search': reverse_image_search,
        'knowledge_graph': knowledge_graph,
        'visual_matches': visual_matches
    }

    print(json.dumps(google_lens_results, indent=2, ensure_ascii=False))

with sync_playwright() as playwright:
    run(playwright)

```

📌Note: In the [online IDE](https://replit.com/@chukhraiartur/blog-google-lens-api#main.py) this code does not work because the Replit does not support the playwright. You can do all the manipulations described below to check how the DIY solution works.

### Preparation

Install library:

```lang-none
pip install playwright selectolax

```

Install the required browser:

```lang-none
playwright install chromium

```

### Code Explanation

Import libraries:

```python
from playwright.sync_api import sync_playwright
from selectolax.lexbor import LexborHTMLParser
import json

```

| Library                                                                                      | Purpose                                                                                                                                        |
| -------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| [sync\_playwright](https://playwright.dev/python/docs/api/class-playwright)                  | for synchronous API. [playwright have asynchronous API](https://playwright.dev/python/docs/api/class-playwright) as well using asyncio module. |
| [LexborHTMLParser](https://selectolax.readthedocs.io/en/latest/lexbor.html#lexborhtmlparser) | a fast HTML5 parser with CSS selectors using [Lexbor](https://github.com/rushter/selectolax#available-backends) engine.                        |
| [json](https://docs.python.org/3/library/json.html)                                          | to convert extracted data to a JSON object.                                                                                                    |

Declare a function:

```python
def run(playwright):
    # further code ...

```

The `image_url` variable is defined, which contains the URL of the image:

```python
image_url = 'https://user-images.githubusercontent.com/81998012/210290011-c175603d-f319-4620-b886-1eaad5c94d84.jpg'

```

Initialize `playwright`, connect to `chromium`, `launch()` a browser `new_page()` and `goto()` a given URL:

```python
page = playwright.chromium.launch(headless=True).new_page()
page.goto(f'https://lens.google.com/uploadbyurl?url={image_url}&hl=en')

```

| Parameters                                                                                         | Explanation                                                                                   |
| -------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| [playwright.chromium](https://playwright.dev/python/docs/api/class-playwright#playwright-chromium) | is a connection to the Chromium browser instance.                                             |
| [launch()](https://playwright.dev/python/docs/api/class-browsertype#browser-type-launch)           | will launch the browser, and headless argument will run it in headless mode. Default is True. |
| [new\_page()](https://playwright.dev/python/docs/api/class-browser#browser-new-page)               | creates a new page in a new browser context.                                                  |
| [page.goto()](https://playwright.dev/python/docs/api/class-page#page-goto)                         | will make a request to provided website.                                                      |

After the page has loaded, pass HTML content to `Lexbor` and `close` the browser:

```python
parser = LexborHTMLParser(page.content())
page.close()

```

The first thing to extract is the reverse image search link. To do this, you need to pass the `.kuwdsf .VfPpkd-RLmnJb` selector that is responsible for this element to the [css\_first() method](https://selectolax.readthedocs.io/en/latest/lexbor.html#selectolax.lexbor.LexborHTMLParser.css%5Ffirst). Then extract the value of the `href` attribute from [attributes](https://selectolax.readthedocs.io/en/latest/lexbor.html#selectolax.lexbor.LexborNode.attributes):

```python
reverse_image_search = {
    'link': parser.root.css_first('.kuwdsf .VfPpkd-RLmnJb').attributes['href']
}

```

The algorithm for extracting data from the knowledge graph works similarly. There is a difference in extracting `title` and `subtitle`. For them, the text content is retrieved, so the corresponding [text() method](https://selectolax.readthedocs.io/en/latest/lexbor.html#selectolax.lexbor.LexborHTMLParser.text) is used. Sometimes there may not be a `subtitle`, so a [ternary expression](https://docs.python.org/3/reference/expressions.html#conditional-expressions) is used for such cases:

```python
knowledge_graph = {
    'title': parser.root.css_first('.DeMn2d').text(),
    'subtitle': parser.root.css_first('.XNTym').text() if parser.root.css_first('.XNTym') else None,
    'link': parser.root.css_first('.OCDsub .VfPpkd-RLmnJb').attributes['href'],
    'more_images': parser.root.css_first('[aria-label="More Images"]').attributes['href'],
    'thumbnail': parser.root.css_first('.oLfv5c .FH8DCc').attributes['src'],
    'images': [
        {
            'title': image.attributes['aria-label'],
            'source': image.attributes['href'],
            'link': image.css_first('.wETe9b').attributes['src']
        }
        for image in parser.root.css('.Y02Gld a')
    ]
}

```

For both knowledge graph images and visual matches, [list comprehensions](https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions) are used to provide a concise way to create lists. To find multiple elements and iterate them, the [css() method](https://selectolax.readthedocs.io/en/latest/lexbor.html#selectolax.lexbor.LexborHTMLParser.css) was used:

```python
visual_matches = [
    {
        'title': result.css_first('.UAiK1e').text(),
        'link': result.css_first('.GZrdsf').attributes['href'],
        'source': result.css_first('.fjbPGe').text(),
        'source_icon': result.css_first('.KRdrw').attributes['src'],
        'thumbnail': result.css_first('.jFVN1').attributes['src']
    }
    for result in parser.root.css('.xuQ19b')
]

```

The `google_lens_results` dictionary is created and previously extracted data is added to the corresponding keys:

```python
google_lens_results = {
    'reverse_image_search': reverse_image_search,
    'knowledge_graph': knowledge_graph,
    'visual_matches': visual_matches
}

```

After the all data is retrieved, it is output in JSON format:

```python
print(json.dumps(google_lens_results, indent=2, ensure_ascii=False))

```

Run your code using [context manager](https://realpython.com/python-with-statement/):

```python
with sync_playwright() as playwright:
    run(playwright)

```

### Output

```json
{
  "reverse_image_search": {
    "link": "https://www.google.com/search?tbs=sbi:AMhZZivbhNZ5ZFwCBpcEUAlEHVFDQnaZIC-4PcD5za7g6xuScvksUbf8osCVDaAg70m3b2eMkaodmPSm_1PiNZgCOEV5wma9PX1piaCV3GtLReFcsjRlP7On4aF3HUJAyPinMnEYGIATNPvQ7PLMoMZlmUXj4uQ1xHw"
  },
  "knowledge_graph": {
    "title": "Black cat",
    "subtitle": null,
    "link": "https://www.google.com/search?q=Black+cat&kgmid=/m/03dj64&hl=en&gl=US",
    "more_images": "https://www.google.com/search?q=Black+cat&kgmid=/m/03dj64&ved=0EOTpBwgAKAAwAA&source=.lens.button&tbm=isch&hl=en&gl=US",
    "thumbnail": "https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcQEdppH7x_edJGSKSky2KSKK773r4HOp55AnejH0-sYBpO3-M5w",
    "images": [
      {
        "title": "Image #1 for Black cat",
        "source": "https://vbspca.com/tag/stigma/",
        "link": "https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcQEdppH7x_edJGSKSky2KSKK773r4HOp55AnejH0-sYBpO3-M5w"
      },
      ... other images
    ]
  },
  "visual_matches": [
    {
      "title": "Pet Talk: Smoke can create problems quickly for your cat | VailDaily.com",
      "link": "https://www.vaildaily.com/opinion/pet-talk-smoke-can-create-problems-quickly-for-your-cat/",
      "source": "vaildaily.com",
      "source_icon": "https://encrypted-tbn0.gstatic.com/favicon-tbn?q=tbn:ANd9GcSXpzpJuQgYt20Jd-moiGdOr6HoDpS-WQ_vjcfrNvtLJy_gjDrYJIs3abOVeBb7g24x5kLNBg2T-KGdiQ_NkFkcBjt2s7exhkQg46swp-DMTF3S1_lemg",
      "thumbnail": "https://encrypted-tbn1.gstatic.com/images?q=tbn:ANd9GcQCjR3dx5H8xz9fSevbe6JqPtBlakSxJwrECbaMS64UcP05CwC4"
    },
    ... other visual matches results
  ]
}

```

## Links

- [Code in the online IDE](https://replit.com/@chukhraiartur/blog-google-lens-api#main.py)
- [Google Lens API](https://serpapi.com/google-lens-api)
- [Google Lens playground](https://serpapi.com/playground?engine=google%5Flens)
- [Playwright](https://playwright.dev/python/)
- [Selectolax Lexbor module](https://selectolax.readthedocs.io/en/latest/lexbor.html)

---

Join us on [Twitter](https://twitter.com/serp%5Fapi) | [YouTube](https://www.youtube.com/channel/UCUgIHlYBOD3yA3yDIRhg%5Fmg)

Add a [Feature Request](https://github.com/serpapi/public-roadmap/issues)💫 or a [Bug](https://github.com/serpapi/public-roadmap/issues)🐞