Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Yandex Reverse Image API

yandex-reverse-image-api

checks pypi python npm node license

Yandex reverse image search is four different datasets behind one URL, switched by a single query parameter. This is a yandex reverse image search api built on ScrapingBee's web scraping API, documented tab by tab, with the parameter that selects each one.

Yandex calls the feature CBIR, content based image retrieval, and that acronym is all over the response payload, so it is worth knowing before you read the JSON.

Verified live on 2026-09-10 against two real images. Where a configuration returned nothing, the reason is recorded.

Start here: the proxy tier is not optional

The first thing to know is that the cheap request does not work.

# 1 credit. Returns a CAPTCHA, not results.
curl -G "https://app.scrapingbee.com/api/v1/" \
  -H "Authorization: Bearer $SCRAPINGBEE_API_KEY" \
  --data-urlencode "url=https://yandex.com/images/search?rpt=imageview&url=$IMG" \
  -d mode=auto

That returned HTTP 200, 14,885 bytes, and a page titled Are you not a robot? carrying Yandex SmartCaptcha. The spb-initial-status-code header read 302. A 200 from the API with a challenge page inside it is the failure mode to watch for here, because nothing about the status code tells you the scrape failed.

Stealth is what clears it:

# 75 credits. Returns results.
curl -G "https://app.scrapingbee.com/api/v1/" \
  -H "Authorization: Bearer $SCRAPINGBEE_API_KEY" \
  --data-urlencode "url=https://yandex.com/images/search?rpt=imageview&cbir_page=sites&url=$IMG" \
  -d stealth_proxy=true -d wait=8000

221 KB, title Yandex Images: search for websites with images, and a populated result set. Reverse image search on Yandex costs 75 credits per call. Budget for that rather than discovering it in production.

The four tabs

Every tab is the same base URL with a different cbir_page value. These forms were read out of the live page's own cbirNavigation.menuItems, not guessed:

Tab URL parameter Answers
Search by image rpt=imageview only Everything Yandex knows, in one page
Similar cbir_page=similar Visually similar images
Sites cbir_page=sites Every page where the image appears
Products cbir_page=products Shopping matches for the object

The image goes in the url parameter, percent encoded, nested inside the ScrapingBee url parameter. Two layers of encoding, which is the most common place this breaks.

Where the data actually lives

Not in the DOM. Yandex renders its result grid client side and ships the data in data-state attributes as HTML escaped JSON. There are no result links in the markup to select, so CSS selectors and extract_rules both come back empty on this target.

The parse is: find every data-state attribute, unescape the HTML entities, load the JSON, then read the slice you want off initialState.

import html, json, os, re

import requests

img = "https://upload.wikimedia.org/wikipedia/commons/3/3c/Shaki_waterfall.jpg"
target = (
    "https://yandex.com/images/search?rpt=imageview&cbir_page=sites"
    f"&url={requests.utils.quote(img, safe='')}"
)

page = requests.get(
    "https://app.scrapingbee.com/api/v1/",
    headers={"Authorization": f"Bearer {os.environ['SCRAPINGBEE_API_KEY']}"},
    params={"url": target, "stealth_proxy": "true", "wait": 8000},
    timeout=180,
).text

for attr in re.findall(r'data-state="([^"]{200,})"', page):
    state = json.loads(html.unescape(attr)).get("initialState")
    if isinstance(state, dict) and state.get("cbirSites", {}).get("sites"):
        for site in state["cbirSites"]["sites"]:
            print(site["domain"], site["title"])
            print("  ", site["url"])
            print("  ", site["originalImage"]["url"])

That returned 37 matches for the test image, with pageSize: 10.

Response shape per slice

Every field below came back populated on the live call.

initialState.cbirSites.sites is the main event. One entry per page carrying the image:

{
  "title": "Shaki Waterfall - Wikipedia",
  "description": "Shaki Waterfall. ",
  "url": "https://en.wikipedia.org/wiki/Shaki_Waterfall?utm_medium=organic&utm_source=yandexsmartcamera",
  "domain": "en.wikipedia.org",
  "thumb":         { "url": "//avatars.mds.yandex.net/i?id=eb74556e...", "height": 90,  "width": 148 },
  "originalImage": { "url": "https://upload.wikimedia.org/.../960px-Shaki_waterfall.jpg", "height": 719, "width": 960 }
}

Two things to handle in your own code. Yandex appends ?utm_medium=organic&utm_source=yandexsmartcamera to every result URL, so strip the query string before you deduplicate. And thumb.url is protocol relative, starting with //, so prefix https: before you fetch it.

The other slices, all confirmed present on the live page:

Slice Holds
cbirSites.sites 37 source pages on the test image, pageSize 10
cbirSitesList.sites 4 entries, same object shape, the abbreviated render
cbirSimilar.thumbs Visually similar images, plus similarPageUrl
cbirMarketProducts.products Shopping matches, plus relatedProducts and moreButtonUrl
cbirOcr plainText, hasText, blocks, entities. Text recognised inside the image
cbirBarcode.barcodeDataList Barcodes and QR codes decoded from the image
cbirTags.tags Yandex's own category labels for the image
cbirOtherSizes.items small_dups, medium_dups, large_dups. The same image at other resolutions
cbirPreview cropRect, imageWidth, imageHeight, imageExpired

cbirOcr deserves a mention on its own. It means one 75 credit call gives you reverse image matches and the text inside the image, with no separate OCR step.

The image has to be one Yandex can fetch

This is the failure that looks like a scraping problem and is not one. Three images, same configuration, same stealth tier:

Image cbirPreview Result
Wikimedia Commons full size JPEG imageExpired: false, 1024x767 37 site matches, 40 similar, 5 tags
A Wikimedia thumb/ derivative URL imageExpired: true 0 matches
A nasa.gov PNG imageExpired: true, width 0, height 0 0 matches, pageSize: 0

Yandex fetches the image from the URL you hand it before it searches. If its crawler cannot reach the file, or the URL is a short lived derivative, you get a valid page with an empty result set and no error message. So before you conclude an image has no matches:

  1. Check cbirPreview.imageExpired. If it is true, the URL was not usable, and both failing images above reported exactly that.
  2. Check cbirPreview.imageWidth. A zero there means Yandex never loaded the file. The working image reported 1024 by 767 instead.
  3. Retry with a stable, directly addressable, full size URL.

An empty sites array plus imageWidth: 0 is an input problem. An empty sites array with real dimensions is a genuine no match.

Ready made packages

pip install yandex-reverse-image-api
npm install yandex-reverse-image-api
from yandex_reverse_image_api import YandexReverseImage

bee = YandexReverseImage("YOUR_API_KEY")
img = "https://upload.wikimedia.org/wikipedia/commons/3/3c/Shaki_waterfall.jpg"

bee.check_image(img)   # {'expired': False, 'width': 1024, 'height': 767, 'usable': True}
bee.sites(img)         # 37 matches, utm stripped, thumbnails made absolute
bee.domains(img)       # match count per domain, deduplicated
bee.similar(img)       # 40 visually similar images
bee.tags(img)          # 5 Yandex category labels
bee.other_sizes(img)   # {'small_dups': 6, 'medium_dups': 6, 'large_dups': 6}
bee.ocr(img)           # {'hasText': False, 'plainText': '', ...} on a photo with no text
bee.products(img)      # empty on a landscape photo, populated on an object

Those are the real return values from the live sweep, not illustrative ones. products and ocr came back empty because a waterfall photograph has neither a shopping match nor text in it, which is the correct answer rather than a failure.

Credit cost

Measured on live calls:

Configuration Credits Outcome
mode=auto 1 SmartCaptcha page
stealth_proxy=true 75 Real results
Validation error 0 Nothing billed

There is no middle rung that works here, so the practical cost of Yandex reverse image search is 75 credits per lookup. At the entry paid tier of 250,000 credits that is roughly 3,300 lookups a month. Cache aggressively, because ScrapingBee does not cache for you and reverse image results for a fixed image change slowly. Plan tiers are on the pricing page.

Also worth knowing: stealth_proxy forces JavaScript rendering, and mode=auto is incompatible with it. Sending both returns HTTP 400 and bills nothing.

Scope

Public Yandex Images results only. There is no signed in surface involved here, and scraping under login credentials is prohibited by ScrapingBee's terms of service in any case. Reverse image results routinely point at pages carrying photographs of identifiable people, so if you are running this for brand protection or counterfeit detection, handle the matches as personal data where that applies rather than as a plain URL list.

Related surfaces

Yandex text search and Yandex Images keyword search are different endpoints with different economics. ScrapingBee covers those through the Yandex images API page, and the walkthrough for the text side is in how to scrape Yandex search results. For interacting with the upload widget rather than passing a URL, the JavaScript scenario parameter drives clicks and waits inside the rendered page.

Yandex's own Terms of Use of Yandex Search govern the service, and Yandex publishes a paid Search API for the text corpus, which is the right tool when the text index is what you need.

FAQ

Why do I get a CAPTCHA on Yandex reverse image search? Because the request came from a datacenter IP. Yandex serves SmartCaptcha to anything below the stealth tier on this surface. Verified across both cheap configurations tested.

How do I find every website using my image? The sites tab, cbir_page=sites, then read initialState.cbirSites.sites. Each entry gives the page URL, its domain, and the resolution of the copy hosted there.

Can I use extract_rules for this? No. There are no result nodes in the delivered markup, so selectors match nothing. The data is JSON inside data-state attributes and has to be parsed after the fetch.

Does Yandex reverse image search read text in images? Yes, and it comes back in the same response, under cbirOcr.plainText. No separate call and no extra credits.

Is it better than Google reverse image search? For some categories, notably faces and Eastern European sources, Yandex returns matches Google does not. Run both if coverage matters.

License

MIT. See LICENSE.