2GIS Scraper — Business Search in 206 Cities
Pricing
from $0.17 / 1,000 business listeds
2GIS Scraper — Business Search in 206 Cities
Search 206 cities across Russia, the CIS, the Gulf, Czechia, Italy and Chile for businesses by name or category. Rows carry the address, coordinates, categories, opening hours and ratings — and a flag on every one saying whether 2GIS matched your query or quietly returned something else.
Pricing
from $0.17 / 1,000 business listeds
Rating
0.0
(0)
Developer
The Netaji
Maintained by CommunityActor stats
0
Bookmarked
3
Total users
2
Monthly active users
3 days ago
Last modified
Categories
Share
2GIS Search Scraper
Business search across the 206 cities 2GIS covers — Russia and the CIS, plus the UAE, Saudi Arabia, Qatar, Kuwait, Bahrain, Oman, Iraq, Czechia, Italy, Cyprus and Chile. Each row carries the branch id, the name, the street address with its city, map coordinates, every category the business is filed under, opening hours, its own rating and rating count with the parent chain's beside them, whether the row is a paid placement, and 2GIS's own relevance score.
And one flag that matters more than any of them.
A query 2GIS cannot match does not come back empty. It answers with a healthy response
and a list of unrelated real businesses, in identical row shapes, at a relevance around
1e-09 instead of the 10 to 8,800 a real match scores. Every row here carries
is_partial_match saying which it is. Measured over 15 queries in 5 cities the flag
separated them perfectly: false on 10 of 10 real queries, true on 5 of 5 nonsense ones.
Accepted input
queries is required and takes one search term per line, in the city's own language — кафе,
pharmacy, kavárna. A term is never split on a comma, because a business name contains
one: Му-Му, кафе is a single search term, not a chain and a category.
cities is required and takes one 2GIS city slug per line — the segment in 2GIS's own
address, such as moscow, spb, dubai, almaty or praha. A pasted 2GIS link is reduced
to the city in it. The slug is the whole key: 2GIS is organised by city rather than by
country, and its internal id for a city cannot be derived from a name — Moscow is 32,
Novosibirsk is 1, Dubai is 99. 2gis-scraper (coveredCities mode) publishes all 206.
Every term is searched in every city. Three terms across five cities is fifteen searches, and the run log says so before it makes any of them.
startPage defaults to 1 and is where each search's walk begins, not the page it fetches.
Use it to resume a harvest.
locale is optional and takes one of the city's own locales, written as ru_RU or en_AE.
Leave it empty for the city's default. This is not a translation layer: a locale a city
does not offer is refused by 2GIS outright rather than falling back — Dubai serves en_AE
and refuses en_US — and each city's accepted set is on its row in the Covered Cities export.
maxItems defaults to 100 and caps each search separately, so three terms across five
cities with a cap of 100 returns up to 1,500 rows rather than 100 shared between them. 0
means no limit.
There is no page-size input. It is always the maximum 2GIS allows, because a row here is priced as a share of one request and a smaller page divides that share among fewer rows for nothing in return.
{"queries": ["кафе", "аптека"],"cities": ["moscow", "spb"],"startPage": 1,"maxItems": 100}
Response fields
{"query": "кафе","requested_city": "moscow","page": 1,"is_partial_match": false,"match_type": "discovery","relevance": 1137.25,"max_relevance": 2151.38,"total_results": 8616,"id": "70000001007179642","search_context_id": "70000001007179642_ku4dspBmdBdB9A82AJ7H…","name": "Му-Му, кафе","type": "branch","url": "https://2gis.ru/moscow/firm/70000001007179642","street_address": "Манежная площадь, 1 ст2","address_comment": "-2 этаж; Средний уровень","building_name": "Охотный ряд","postcode": "125009","country": "Россия","region": "Москва","address_city": "Москва","district": "Тверской","latitude": 55.755301,"longitude": 37.614586,"region_id": "32","city_alias": "moscow","primary_rubric": "Кафе","rubrics": [{ "id": "161", "name": "Кафе", "alias": "kafe", "kind": "primary" }],"schedule": { "Mon": { "working_hours": [{ "from": "09:00", "to": "22:00" }] } },"phones": [],"websites": [],"emails": [],"chain_name": "Му-Му, кафе","chain_branch_count": 18,"rating": 4.8,"review_count": 1090,"org_rating": 4.3,"is_advertisement": false}
Fields absent from a record are returned as null rather than omitted, so every row has the same shape.
is_partial_match is the column to filter on before anything else. relevance and
max_relevance are the numeric corroboration: real matches measured 10.98 to 8,827.83 and
unmatched ones 4.8e-16 to 2.9e-06, seven orders of magnitude apart with no overlap. No
threshold is published here, because fifteen samples is not a threshold.
match_type is not that signal, and treating it as one is the mistake to avoid. It names
which search mode answered: discovery means 2GIS resolved the term to one of its own
categories and browsed it, recovery means it ran a literal text search instead. recovery
is not a warning — four of the ten good measured queries took that path and every one
answered well.
phones, websites and emails are empty on every row here, and that is a property of
the search endpoint rather than of the businesses. 2GIS's own search request omits contact
fields entirely, so no amount of paging produces a phone number. Pass a row's id to 2GIS
Business Scraper for those.
id is the branch id and it is exactly what the Business and Reviews Actors take. 2GIS's own
search response does not publish it: its id is this value plus a 130-character per-request
context blob that works nowhere else on the site. The tail is stripped here and kept beside
it as search_context_id.
street_address is the street line only — the city is not in it. That is 2GIS's own
shape, and the city is lifted out of its administrative division list into address_city,
because flattening to the street line would lose it.
rating and org_rating are both published because a branch of a chain frequently disagrees
with its parent, and quoting either alone misstates the other.
is_advertisement is true when the row is a paid placement. 2GIS mixes advertised
organisations into the ranked list, so anyone measuring ranking needs to be able to exclude
them.
Behaviour on partial results
A term 2GIS cannot match produces rows, flagged. They are returned rather than hidden — a near miss is occasionally what a caller wants — but never unlabelled, and the run log warns about it at the top of the search.
A city slug that is not a slug stops the run before any request is made, with a message naming the shape. A city 2GIS does not serve is skipped with a line in the run log, and the rest of the cross product still runs.
The walk goes to the end of the stated total, which is unusual: pages run to exactly
ceil(total / 50) and the last one is short, rather than the walk dying early. A query
stating 8,616 results served 173 pages and page 174 came back empty.
Rows repeat a little across pages — 2GIS honours its own page exclusion loosely, and over six five-page walks 0 to 12 rows of 250 repeated an organisation already returned. This Actor drops the repeats before they reach the dataset and says how many it dropped, so a run that fetched five pages can legitimately save fewer than 250 rows.
Frequently asked questions
Why is there no phone number on these rows?
Because 2GIS's own search request does not ask for one. The same organisation returns no
contact fields at all from search and four phone numbers, a website and two email addresses
from the detail endpoint. It is a property of which lookup you made, not of the business, so
paging further will never produce one. 2GIS Business
Scraper takes a row's id directly.
My query returned businesses that have nothing to do with it. Is that a bug?
No, and the row already told you. Check is_partial_match — true means 2GIS could not match
the term and returned unrelated real businesses anyway, under a perfectly healthy response.
The fix is usually the language: search in the city's own, and use 2GIS's own category names.
Where do I get city slugs?
From 2gis-scraper (coveredCities mode), which returns
all 206 with their region ids, countries and accepted locales in a single request. They are
not guessable from a country, because 2GIS is organised by city.
Why did I get fewer rows than pages × 50? Because repeats are dropped. See Behaviour on partial results. The run log states the number it skipped per search.
Can I sort the results?
No. 2GIS returns them in its own relevance order and offers no ordering parameter on this
surface, so an input for one would be a control that does nothing. relevance is on every
row if you want to re-sort them yourself.
Is a 2GIS account or API key required?
No. No account, session cookie or key of any kind is supplied to the Actor or needed by it.
Open the url on any row and you see the same record.
Related Actors
2GIS Business Scraper takes the id from
any row here and returns the phone numbers, websites and email addresses this surface cannot
carry, plus the full structured attributes. It costs one request per business, so it is the
second pass over a shortlist rather than a way to read a market.
2GIS Reviews Scraper takes the same id
and returns the written reviews, each naming which source it came from.
2gis-scraper (coveredCities mode) is the city vocabulary
this Actor's cities input needs.
2GIS Scraper puts this search and the reviews export behind one mode picker, if you would rather run one Actor than two.