Scrape Google Lens with Python
Tags: webscraping, tutorial, python, programming
Published: 2023-01-29 12:06:18

- <a href="#what_will_be_scraped">What will be scraped</a>
- <a href="#using_google_lens_api">Using Google Lens API from SerpApi</a>
    - <a href="#why_use_api">Why use API?</a>
    - <a href="#preparation_serpapi">Preparation</a>
    - <a href="#code_explanation_serpapi">Code Explanation</a>
    - <a href="#output_serpapi">Output</a>
- <a href="#diy_solution">DIY solution</a>
    - <a href="#preparation_diy">Preparation</a>
    - <a href="#code_explanation_diy">Code Explanation</a>
    - <a href="#output_diy">Output</a>
- <a href="#links">Links</a>


<h2 id="what_will_be_scraped">What will be scraped</h2>

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


<h2 id="using_google_lens_api">Using Google Lens API from SerpApi</h2>

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))
```


<h3 id="why_use_api">Why use API?</h3>

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_lens) for a live and interactive demo.


<h3 id="preparation_serpapi">Preparation</h3>

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.


<h3 id="code_explanation_serpapi">Code Explanation</h3>

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 the all data is retrieved, it is output in JSON format:

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


<h3 id="output_serpapi">Output</h3>

```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
  ]
}
```


<h2 id="diy_solution">DIY solution</h2>

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.


<h3 id="preparation_diy">Preparation</h3>

Install library:

```lang-none
pip install playwright selectolax
```

Install the required browser:

```lang-none
playwright install chromium
```


<h3 id="code_explanation_diy">Code Explanation</h3>

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_first). 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)
```


<h3 id="output_diy">Output</h3>

```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
  ]
}
```


<h2 id="links">Links</h2>

- [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_lens)
- [Playwright](https://playwright.dev/python/)
- [Selectolax Lexbor module](https://selectolax.readthedocs.io/en/latest/lexbor.html)


<p style="text-align: center;">Join us on <a href="https://twitter.com/serp_api">Twitter</a> | <a href="https://www.youtube.com/channel/UCUgIHlYBOD3yA3yDIRhg_mg">YouTube</a></p>

<p style="text-align: center;">Add a  <a href="https://github.com/serpapi/public-roadmap/issues">Feature Request</a>💫 or a <a href="https://github.com/serpapi/public-roadmap/issues">Bug</a>🐞</p>