Stop wasting your budget on slow, resource-heavy browser-based scrapers. This is the fastest, most cost-effective, and data-rich Google Maps scraper on Apify, designed for high-scale lead generation and market research.
All notable changes to this Actor are documented here. Public builds use
Apify build revisions such as 3.0.0 and 2.0.25.
[3.0.38] - 2026-08-08
A run whose residential proxy cannot be opened no longer fetches pasted
Maps URLs or Place IDs from the worker's own IP — direct targets obey the
same refusal as the search.
Direct targets are a phase of their own in the log, with progress every
twenty seconds. A pasted Place ID resolves through more doors: the place
page is asked on one session with the same settle the search uses, then
through the place/?q= form and the ?cid= page the ID itself encodes.
An ID that still fails is explained as what it usually is: stale. Google
retires a Place ID when a listing is edited, moved or merged — re-copy the
ID, or paste the place's URL.
The relevance filter's description is honest and short: not a 100%
guarantee, best when a term names a kind of place, and every row carries
searchTermMatch either way.
The log names things a reader can recognise: a failed target by its Place
ID, an exhausted area by its search term, a long diagnosis by its first
whole sentence; the strict-mode area summary keeps every count on the line.
[3.0.37] - 2026-08-07
A run stopped by its charge limit before it could save a batch now says the
batch was not saved, with the count. The message used to claim those places
were "saved without contacts or reviews" — a leftover from an older flow;
nothing about what is saved or charged changes, only the words.
[3.0.36] - 2026-08-07
A custom search area is now searched at the size it was drawn. A drawn polygon
or a point with radiusKm was framed about four times too wide, so the run
downloaded and then discarded a great deal of ground nobody had asked about.
A run given only Google Maps URLs or only Place IDs no longer fails before its
first request.
A run stopped while it was still resolving Locations now stops there and writes
its summary, instead of geocoding on until the platform kills it with nothing
saved and nothing said.
A run that reports itself as failed is now reported as failed by the platform
too, rather than listed as succeeded with no results.
A run cut short by its charge limit now counts what its last answer dropped,
so the totals add up for the runs that end early as well.
keepUnverifiedLocations sent as the text "false" no longer switches it on.
An unknown reviewsSort, and a minRequestIntervalMs that is not a number,
are now reported instead of being changed in silence.
The run summary names its search terms as you typed them, in the one list that
still showed them with the run's own internal punctuation in the middle.
The summary counts how many Google Maps search URLs were given against how
many were used, which it could not tell apart before.
A run that cannot open the residential proxy is told to look at the allowance,
not to remove a proxyConfiguration it was never given.
The locatedIn column is gone. It was documented and always empty.
The word model costs a run far less CPU. Its per-row questions are answered
from caches that only recompute when the evidence actually changes, and a
new category name's first vector is computed off the event loop — a large
run with a vague term was spending minutes of CPU re-deriving the same
answers row by row. No verdict moves.
One verdict column instead of two. searchTermMatch says which of four
things the run concluded about a row — match, arguable, not-a-match or
not-judged — and the matchesSearchTerm boolean that rode beside it is
gone: it collapsed arguable into the same firm true as match, which
confused more than it told. A saved filter on matchesSearchTerm=true becomes
searchTermMatch is match or arguable; =false becomes not-a-match.
Everything the relevance judge does now happens only when
skipPlacesNotMatchingSearch is on. A run that did not ask for filtering no
longer loads the language model or reports judge statistics — its record
carried hints about verdicts that were never written.
With the filter on, rows removed after the answer ran out of what you searched
for are now counted apart (paddingTailPlacesDropped), so the summary can say
"ask for fewer places" instead of "check the term". No row moves in or out of
the dataset; the same rows are removed under a more precise reason.
The one row a run that saved nothing writes to explain itself is no longer
billed as a result.
Duplicate Place IDs in the input are folded and reported, like duplicate
search terms already were.
With skipPlacesNotMatchingSearch on, a "no" that arrived before all of a
place's search terms had answered used to be saved anyway, flagged — on a
many-term, many-location run that was half the delivery. Such a row now waits
until its place's terms have all spoken and is then removed or kept on the
full evidence; only a run cut short before that still delivers it flagged.
A run's Locations are also searched one place at a time rather than
interleaved, so each place's verdicts settle within one wave of workers.
The judge's notes in the log are said once per search term, not once per term
in every Location — a 50-city run repeated each one fifty times.
The SEARCH block reads as a table: one opening line instead of two, the
judge's notes aligned on one term column, and the progress counters
right-aligned to fixed columns so successive lines line up.
One log message is one line, always — no message continues onto the next
record, which read as broken lines in the Console. What does not fit gives
way at whole units, not mid-word: a list shows whole items and counts the
rest (+2 more), and a failed search names its reason as a plain phrase
("the proxy connection failed") instead of a truncated traceback.
The judge's notes say what happens in the dataset's own words — a term the
run cannot judge now reads "nothing to judge its rows by; saved as
not-judged" instead of judge shorthand — and the note is said only of a term
the run truly cannot judge, not of every term beside rows it was judging.
With website contacts on, a run whose budget covered only part of what it
found could save none of it: the places the budget did cover were queued for
enrichment and then dropped as the run stopped. They are now enriched and saved
before it stops.
[3.0.35] - 2026-08-06
A neighbourhood or district Location written as district, state, country
keeps its own boundary instead of falling back to the centre of the state, so
the places found in it are no longer marked as outside the area you asked for.
A Location that has no boundary at all can no longer end the run, and one that
resolves to a point rather than an area now says so in the run log.
A run that stops on an unexpected error writes the diagnostic row explaining
it, and reports itself as failed instead of finishing quietly.
[3.0.34] - 2026-08-06
Everything below has been waiting behind the 2.0 line and arrives together.
Every run
Review counts are back on every place, and price ranges with them. The Actor
was asking Google for a search's results before Google had filled them in, so
reviewsCount, reviewsUrl and price were missing from most rows.
The reviews link opens the reviews; it used to land on a search results page.
The Reviews view shows the count and a link. The Overview shows the count
beside the rating, and one search-term column instead of two.
Wards and districts find their boundary where the city is also a province —
Ho Chi Minh City and cities filed the same way. They were searched by name,
with no area to check results against.
Areas are searched deeper, and one thin area no longer retires a search term
for the whole city.
A run with skipPlacesNotMatchingSearch off no longer logs verdicts it did
not act on.
Only when skipPlacesNotMatchingSearch is on
Everything in this section is the relevance filter. With the option off — the
default — no place is removed and no verdict column is written, so none of it
changes what a run returns.
About a quarter fewer places that are not what you searched for. A narrow
search no longer returns the whole family, a two-word search no longer treats
every shop in town as the trade, neighbouring trades are no longer delivered
as the one you asked for, and a deep search stops saving the padding at the
end of an answer.
A place is no longer either a match or not: the dataset says how firmly, and
the run summary counts the arguable rows beside the flagged ones.
Search terms are understood in your own language — panetteria,
boulangerie, пекарня and パン屋 now reach the same trade, from 18,606
words in 33 languages read from OpenStreetMap.
The Actor also knows the words Google itself uses for a trade, so a bakery is
recognised as Fornaio in Italy.
A search term with a place in it now names the trade inside it.
A term that names a whole class of business is still left alone.
[3.0.30] - 2026-08-02
A search whose trade has no name of its own is judged properly now. Where
Google answers with a plain "manufacturer" and the shops under it are makers
of parts, paint or electronics, those rows were being flagged as not the kind
of place asked for. Measured over ten production runs of one such term: 1,471
rows recovered, and no other run in the audit set changed at all.
One trade's several names are read as one.lodging and lodge,
building and builder, veterinary and veterinarian — a run for lodging
kept the lodges and flagged the places filed as lodging, in the same answer.
A short answer no longer throws away real results where each place carries
one category. In much of the world Google files a business under exactly one
category; there, the check that tells a neighbouring trade from a corner of
the searched-for one had no evidence to read and treated every kind as
unrelated. It now reads the answer as well as the card.
More of Google's area cards are recognised and dropped — a town, a
district or a regency answering a business search is not a business, whatever
the country calls that division.
The run summary now says what the log said. The places a contact run never
had time to check the websites of are named in OUTPUT, not only in the log;
and when most of a dataset comes from outside the requested area, the summary
gives the share and the towns rather than a bare count.
[3.0.29] - 2026-08-02
An aborted or timed-out run now delivers what it is holding. The Actor
reads the platform's own stop signal instead of guessing at a deadline, so
everything already found and accepted is written before the process ends —
a run that used to finish with nothing now saves the full batch it had in
hand. Website enrichment, which is the slowest part of a contact run, is
released the moment the signal arrives rather than blocking the save, and the
run's dedup state is left correct so it can be resumed without re-charging or
re-fetching a place it already delivered.
Every run ends with a diagnosis, including the ones that end badly: a run
that cannot get a proxy, or fails unexpectedly, now says so in plain words
instead of printing a Python traceback.
A stopped run says how many found places it never saved, and why. The
places waiting in the enrichment queue when a run ends are now named in
OUTPUT as well as in the log — on a large contact run that is the biggest
single number in it, and the summary used to omit it. Two different skips
that were reported as one number are now told apart: the per-search result
limit and the charge limit.
The run status in the Console is short, current, and never one phase
behind; the log no longer runs its sections together.
A term is quoted in messages exactly as it was typed.
What a term means is now learned from thirty of an answer's rows, not
twenty — measured, twenty was a coin toss on trades whose answers open with
several kinds of place, and a term that had learned too little was judging
rows it had no evidence for.
[3.0.28] - 2026-08-02
A trade whose own name is short is now recognised by it. What a search
term means is partly read off the name Google's own categories share, and that
name had to be five characters long — so bar, spa, pub and deli have
never had a trade name at all, and a tiki bar was not a bar. In Japanese, Chinese
and Thai, where a trade is written in two or three characters and categories
carry no spaces to cut on, no answer ever named its own trade. Both are now
decided by the writing system rather than by a count: whole words where there
are words, characters where there are none.
A wholesale search in Japanese recovers the shops filed as a kind of
wholesaler, a search for a hairdresser in Turkish no longer counts pet
groomers, and a bar search keeps the bars filed as tiki bar or bar & grill.
[3.0.26] - 2026-08-02
Build 3.0.27 is the same code; it corrected the wording below.
A search term written in a script that does not space its words is now
recognised inside a category. It never was: the rule was written for the
Latin alphabet and has read every script that way since it shipped. A term
shorter than five characters was looked for as a whole word, and Japanese,
Chinese, Korean and Thai put no boundary around a word for it to find. Places
filed under the search term with a qualifier in front of it were therefore
marked matchesSearchTerm=false — on a sector search that was most of the
answer. No rule in the judge counts characters now: where the match may fall
is decided by the writing system, and by where the head of a compound is.
A run given several languages judges a place the same way whichever language
finds it. A place is found once, and everything a term's profile concludes
was written in one language's words, so the same business could come back as
the trade or as padding depending on which search reached it first. Google's
category ids are the same string in every market and now carry the verdict
across.
Search terms that differ only by capitalisation or spacing are searched
once. Google reads them as one term, so the rest bought the same places
again at the same cost. The run says which ones it folded.
A run that cannot open the Apify residential proxy explains itself —
whether the allowance is spent, the account lacks access, or the token was
revoked — instead of ending on a Python traceback with no diagnosis. Any
unexpected error now ends the same way: the log stays readable and OUTPUT
carries the reason.
The run's own advice no longer shows an internal separator inside a search
term when several Locations were given.
[3.0.25] - 2026-08-02
A search for a profession keeps its specialists even where the city's own
answer never names the profession. A place was recognised as a narrower kind
of what you searched for only when the wider trade appeared in that city's
results — and it often does not: one city answers doctor with medical
clinics and family practices and nothing filed as a doctor, so the
paediatricians, podiatrists and cardiologists it also returned were marked
matchesSearchTerm=false. The word you typed is now read as a trade in its
own right, which needs no results to interpret. Live in that city, flagged
rows fall from 21 in 120 to 7, with none lost; five other trades are unchanged.
[3.0.24] - 2026-08-02
A run over several locations judges each of them by its own answer. What a
search term means was learned from every location's results pooled together,
so a trade that is a real part of one city's answer could fall below the bar
once another city was added to the same run — and those rows went out marked
matchesSearchTerm=false. Wine bars, bistros and gastropubs in an Italian city
were flagged because a British city was in the same run; espresso bars and
cafeterias likewise. Measured on live answers, one city went from 16 flagged
rows in 120 to 4, and another from 7 to 0. The effect was strongest with
two locations and faded as more were added, so it mattered most in the
commonest kind of run. Runs with a single location are unaffected.
[3.0.23] - 2026-08-02
More of the same question: is a saved place the kind of place you searched for.
A short trade word is read on the place's own sign. A search for cafe,
bar, spa or pub ignored the name of the business entirely — only terms
of five letters or more were looked for there. Google files a great many cafés
under Restaurant, Bistro, Bakery or Sushi restaurant, and those now
answer a search for a café: eleven of them in one city, measured live.
A name that only borrows a trade no longer answers for it. A carpet
cleaner, an appliance repairer, a lawn mower shop or a driving school can
carry doctor in its name, and each is filed by Google under what it actually
does — when the two disagree that plainly, the category wins. Measured over
21,791 places, this removes 24 such rows at a cost of 6.
A search that Google answers with only a dozen or so places is judged like
any other. Below twenty results the run decided it had learned nothing about
the term, so every row went out with no verdict on it, and
skipPlacesNotMatchingSearch quietly kept everything. Rare trades, small
towns and unusual professions are exactly the searches that come back that
short.
[3.0.22] - 2026-08-02
Two days of work on one question — is a saved place the kind of place you
searched for. Replaces the 3.0.12 - 3.0.21 entries.
A search for a whole profession keeps its answer. Asked for a doctor, or
its equivalent in any language, Google answers with general practices and
clinics and files the specialists under names that share no word with them.
Those specialists were flagged matchesSearchTerm=false — and a run with
skipPlacesNotMatchingSearch turned on never received them at all. Such a
search now recognises 98% of its real answers where it recognised 58%.
A run keeps digging where the answers are. What an area was worth was
measured by how often its categories led the first answer, so an area full of a
profession's specialists — or of the building trades — read as a third to a
half empty and the run moved on while the ground still held what you asked for.
Areas are priced knowing what the judge knows now. Measured over 195 captured
answers against what each area really held, areas judged less than half as rich
as they were fall from 27 to one.
The same word with a different ending is the same trade: a place filed
under veterinary care in a search for a vet, an electrical utility in a
search for an electrician. It matters most in the languages that decline the
trade, where the words drift apart as well as the ids.
A narrower kind of the trade is the trade: a cardiologist in a search for
doctor, an Italian restaurant in a search for restaurant, a truck repair
shop in a search for auto repair. Read from Google's own language-free
category ids and an open taxonomy of them, so it works the same in every
market — the largest single gain measured was on a non-English search.
The same run twice returns the same rows. What a term means no longer
depends on which requested location the network answered first.
A term whose first answer came back with almost nothing is still judged,
from the smaller areas opened under it, instead of being left unjudged for the
whole run.
A short search word matches the places filed under it — a search for
shop keeps the barber shops and gift shops Google answers it with.
A Location means the same thing on every run. When OpenStreetMap's search
was busy, the area quietly became a box around a point and geoStrictMatch
kept or dropped the next town by chance; the boundary is now recovered from
the place OpenStreetMap did name.
Judging is faster and steadier on large runs, and the verdicts do not depend
on how Google spells a category or how many it gives one place.
[3.0.10] - 2026-08-01
A district or borough of a large city resolves to itself again. OpenStreetMap
files one under the city, and the check that refuses "a district of a town you
did not name" read only the top of that address — so a borough asked for by
name could fall back to a same-named village elsewhere in the country.
A term that names something other than a kind of place — an attribute, a
language, a material — no longer lets that other thing count as the trade. Only
the leading categories of Google's answer stand for what a term means, so the
places the second word pulls in are flagged rather than kept.
When an answer really does hold two kinds of place, the run says which two, and
says it once, while the search can still be changed.
The same run twice now judges its rows the same way: two equally common
categories used to be ranked by whichever search answered first.
The time the search spent waiting for the website pass is stated per worker.
Summed over all of them it read as wall-clock, and a 14-minute run could report
98 minutes of waiting.
[3.0.4] - 2026-08-01
Fixed: a place lost on its way out of the website-enrichment queue is
counted and named in the run summary. It used to be logged where nothing
prints it, so the rows simply went missing while the summary counted them.
Fixed: rows the run deliberately does not judge are saved again. The field
that carries the verdict was declared as a boolean, and writing null into it
made the platform refuse the whole write — which cost not only that row but
every other place found in the same map view.
A place the Apify API refuses to save is now answered rather than dropped: a
rate limit is tried once more, and a row holding a value no JSON parser accepts
is saved without that value. What still cannot be written costs that place
alone — the rest of its map view is delivered, the run says how many were
refused and with which HTTP status, and they are left to be found again.
A search that fails now says what refused it, and the run summary tells an
Apify API refusal apart from a blocked Google request — the first is the
platform having a bad hour, and no change to the input fixes it.
A point pasted into Location as degrees and minutes —
40°46'04.6"N 73°54'42.2"W
— is recognised as the coordinate it is, and answered with the
same customGeolocation line as the decimal form, that point already in it.
[3.0.2] - 2026-08-01
A customGeolocation point with radiusKm is matched against the circle it
asks for, not the square around it — which reached 41% further at the corners.
The map draws that circle too, and its resolution follows the radius.
An area of many drawn shapes costs the same to judge as one: a grid of 248
circles went from 13.7 ms a place to 10 µs, with identical verdicts.
A place whose own name carries the trade — as a word, not as a run of letters
— is recognised as that trade, whatever Google filed it under.
matchesSearchTerm is on every row, null where the term names a class of
business rather than one kind of place.
Every count in the run summary is a count of saved rows. A place Google
published no location for is no longer among the ones called inside the area.
A run over a drawn area is told to switch on geoStrictMatch, not to name a
place it never named; a strict run names the towns its results were in.
[3.0.0] - 2026-07-31
Every saved place says whether it matches the search
New field matchesSearchTerm, on every row. It is decided from the categories
Google itself returns for the search term, so there is no category list to
maintain and it works in any language. On hand-labelled results from ten markets
in six writing systems it keeps 99.8% of the places that match the term and marks
94.6% of those that do not.
A place whose own name carries the trade — as a word, not as a run of letters
inside one — is recognised as one, whatever Google filed it under: a great many
real shops are filed under the generic store, which is what a supermarket
gets too.
A term that names a class of business rather than one kind of place — "food
manufacturing", say — has no single trade to measure a row against, so its rows
carry null and none of them is removed, beyond the ones that name the trade
themselves. The run log names such a term when it meets one.
New setting skipPlacesNotMatchingSearch drops the marked rows instead of
saving them. It is off by default; the field is on every row either way. Rows
dropped this way are not charged and do not count towards the number of places
to extract, so the ask is filled with places that match.
New field categoryId
Google's own id for a category — dental_clinic, jewelry_store — identical in
every market whatever language the category names come back in, so an export can
be filtered without reading the local words.
It also changed what a run saves. Towns, districts and other map features, which
Google returns to a business search and which carry no categories at all, are
recognised by their id and are no longer saved as places. Two spellings of one
trade count as one trade, and a subtype is matched through its parent.
More places from the same searches
A map area opened during a search now searches itself instead of repeating the
whole region's query: +22% places of the searched trade, same search count.
The requested area is split whatever its size, so a large city or region is
covered rather than read from a single map view.
A search stops when new places run out rather than when the number is filled.
The website pass runs alongside the search, so a run takes as long as the
slower of the two rather than both in sequence.
End-of-run report
Every count comes from the rows that were saved. Dropped places are counted
once each, not once per map view they appeared in.
A run without strict area matching whose rows came mostly from around the area
reports that share, names the towns they were in, and points at
geoStrictMatch and insideSearchArea. A strict run names the towns its
dropped results were in, so they can go straight into the Location field.
Places dropped for having no coordinates are reported separately from places
dropped for being outside the area, and a saved place whose location Google
never published is no longer counted among the ones inside it.
A customGeolocation point with radiusKm is now matched against the circle
it asks for. It used to be matched against the square around it, which reaches
41% further at the corners.
Log
One framed block per phase; the result block carries the place count and the
run time in its header.
Network failures are reported in plain words instead of curl's text.
A resolved location reports its width in kilometres and whether it came from a
boundary or a box.
Rows that do not match the search term have their own colour on the map.
Fixes
A resumed run no longer saves, or charges for, a place it already saved.
A run migrated by the platform keeps the profile it learned for each search
term, and goes on judging rows after the migration.
[2.0.25] - 2026-07-27
A city matched as a point is now searched as the city, not as a square
OpenStreetMap holds a city as two separate objects: the city as a labelled point,
and the city as an outline. A location search answers with whichever of the two
its name matched better, and for a great many cities that is the point — a city's
name is more prominent than its boundary's. When that happened, the Actor had no
outline to work with and fell back to the placeholder box OpenStreetMap puts
around a point: a square roughly 35 km on a side, the same square for every city
on Earth. It missed whole districts of a city on one side while reaching into
farmland on the other, and in strict mode it decided what counted as "inside" the
city by that arbitrary square.
The city's real outline is now found and used. It is usually sitting in the very
same response, listed one line below the point — that costs nothing to read and
is where most of these cities are now fixed. When it is not there, the previous
route through the city's place hierarchy still applies. Which object the outline
comes from is now decided the same way in both: it must be an administrative
boundary, hold the city's own point, carry the city's name, and be at least a
municipality. A city keeps its point only when neither route finds one.
This fixes cities in whole countries at once, not one by one. In Australia, no
council boundary is used as part of an address, which is exactly why none of them
could ever be found by the hierarchy route, and every Australian city searched as
that square. Boundaries that are simply not in OpenStreetMap — small towns,
sub-city neighbourhoods — are unaffected and still search from their point, as
before.
A district inside a city can no longer stand in for the city
The hierarchy route took the nearest administrative unit above the city without
reading its name. In countries that divide a city into administrative wards, the
nearest unit above the city centre is one ward inside the city — the right rank,
holding the right point, and a small fraction of the place the user asked for. One
wide city was being searched as a 6 km box because of it. The name is now checked,
so a piece of a city cannot pass for the city.
Where a city has no boundary of its own in OpenStreetMap but the district around
it carries the same name, that district is used and the run log says so, along
with the note that customGeolocation will draw the built-up area exactly. It
covers the city rather than missing most of it, but it does reach past it.
The name test works in every language, not just in English
A city is almost never spelled exactly like its own boundary, and every language
disagrees about how. The qualifier may follow the name or lead it, and the name
itself is often inflected on the way in — Swedish turns a town into "…s kommun",
Greek puts the city in the genitive behind the word for municipality. Matching
the two names word for word answers "different place" to all of that, and the
city loses its boundary. Names outside the Latin alphabet had it worse: they
compared as empty, so nothing matched anything.
A translation can also disagree with itself. Names come back in the language the
run asks for, and English spells one Saudi city's name one way and its
governorate's another — far enough apart to read as two different places, while
the original Arabic on both is the same word. So the two names are now compared
in the run's language and in the original, and agreement in either settles it:
matching in one language is evidence of one place, while differing in one is as
likely to be a transliteration as a difference. A town that sits inside a
differently-named neighbour's municipality disagrees in both, and is still
declined.
The city's name is now looked for anywhere inside the boundary's, and compared
for near-equality rather than sameness, in whatever alphabet it is written in.
Measured over a spread of small towns in about fifty countries, this is the
difference between a dozen towns keeping their boundary and losing it. The
tolerance is deliberately narrow, so places whose names merely resemble each
other stay apart, and a boundary named after a different town — the case where
a small town sits inside a larger neighbour's municipality — is still declined.
A recovered boundary now frames the search too
When a city's outline was recovered, it decided what counted as inside the city
but not where the search itself was pointed — that stayed framed on the
placeholder square. A city wider than the square was searched only in its middle.
The recovered outline now sets the framing as well, so the search covers the city
it just identified.
[2.0.22] - 2026-07-25
Short runs no longer spend a third of themselves waiting on a counter
A run that finished right after saving its results spent 5 to 9.5 seconds doing
nothing before it exited — on runs that took 13 to 19 seconds in total, close to
half of the run.
It was waiting for Apify's count of the saved rows to catch up with the rows
themselves. The two settle at different speeds: every row is readable the moment
it is saved, while the count that reports how many there are trails several
seconds behind. The Actor cross-checks that count against what it saved, and it
was waiting the difference out in a ladder of retries, up to nine seconds of
them.
The cross-check now asks about the rows instead of the count, which answers the
same question immediately, and only pauses when a row really is missing — the
case the check exists for. Runs that keep searching for another ten seconds
after their last save, which is most of them, never paid this and are unchanged.
It also could not report fewer places than it saved any more. If the count was
still behind when the retries ran out, the smaller number was the one that went
into the run summary.
A pin on the map opens the place, not a search for it
Making the map lighter in 2.0.21 dropped the Google Maps link each pin carried
and rebuilt it from the pin's coordinates. That opens a search for a name near a
point, which is not the same thing as the place: a common business name, or one
of the many places Google gives a stand-in coordinate because they have no
storefront, can land on a neighbour or on a list of results.
Pins now carry the place's identifier and the link names it directly, the way
the saved row's own link does. It costs about a quarter of what the full link
cost, so the map still holds roughly 48,000 places of typical lead data.
[2.0.21] - 2026-07-25
The map of the results no longer stops at 20,000 places
A run of nearly 40,000 places drew 20,000 of them on the map. That ceiling was set
against a storage limit the platform has since removed, and it stayed behind
after the reason for it went away.
The map now budgets how heavy the page is instead of counting pins, because
pins are not the same size — a place with a long address, a website and a phone
is several times a bare one. Roughly 54,000 places of typical lead data now fit,
and around 260,000 of sparser rows. Two things made room: each pin used to carry
its own Google Maps link, nearly a third of the whole page, which is now built
from the pin's coordinates when you open it; and empty fields were being written
out as blanks on every place that lacked them.
The map is also cheaper to keep up to date. It is rewritten in full each time it
refreshes, so a large one was being re-uploaded every ten seconds all run. Small
maps still refresh every ten seconds; larger ones refresh less often, so the
cost stays flat as results accumulate instead of growing with them.
The run log says what the map left off
A map that drew 20,000 places out of nearly twice that reported only the ones it drew and the
places it could not locate, which read as though it held nearly everything. It
now names the page limit alongside them.
"Action needed" tells you again to move a location out of a search term
The 2.0.20 note about this advice firing when it did not apply went too far: it
stopped firing for the searches it is for — a term that names a city while
that same city is also the Location. Those runs get the guidance back, and the
false positive stays fixed.
[2.0.20] - 2026-07-25
The map search no longer waits for business websites
With contact enrichment on, a search of one map area was held open until every
place it found had had its website checked. Checking websites is most of what
such a run does, so map coverage advanced at the speed of the slowest websites:
on a statewide run a search that found nothing finished in 3 seconds while a
full one took 800-1250, and the run reached 36 of the 99 map areas it had
identified before its time ran out. The remaining areas were reported as
skipped.
Website checks now run alongside the search instead of inside it. The map search
completes its areas at its own pace, and the website pass works through the
places it found for as long as the run has time. Two things follow:
Coverage no longer depends on website speed. The search finishes the map
areas it identified, so results are drawn from the whole requested area rather
than from the part reached before the clock ran out.
The most prominent businesses everywhere are checked first. The website
pass takes the top result of every map area before the second result of any of
them, so a run that runs out of time has covered the area evenly instead of
exhausting a few places in it.
What is saved is unchanged: a place is only saved once its website has actually
been checked, so a run that asked for contact details is never padded with rows
that have none. Places found after the time for website checks ran out are not
saved and not charged for, and the run summary now names how many there were.
Runs that also request reviews or place details, and lead-list runs that keep
only places with an email, are unaffected — those still need the website result
before the row can be finished, so they keep the previous behaviour.
Contact pages get the whole timeout instead of a retry that rarely worked
Two residential-proxy retries in website enrichment were measured across five
statewide runs and removed.
Retrying a contact page over proxy returned a usable page 3% of the time
(117 of 3,978 attempts). Keeping the retry available also cost every direct
contact-page fetch most of its time: the direct attempt was cut to 3.5 seconds
to leave room for the retry, against a site timeout that is normally 10.
Contact pages are now fetched direct only, with the whole site timeout.
Measured on the same search before and after: the time spent per email
recovered from a contact page fell from 17.4 to 11.9 seconds, and the average
time spent on one website fell from 5.3 to 5.0 seconds. The share of contact
pages that never answer did not move (33.8% before, 33.2% after) — those sites
were not refusing the datacenter route, they simply have no page at the address
the link pointed to.
Retrying a homepage that answered but published no contacts recovered
contacts once in 1,351 attempts. A site that responds and shows no email is not
hiding one behind a datacenter IP. The retry also ran inside the site's own time
budget, taking it from the contact-page pass that does find emails.
The homepage proxy fallback for sites that do not respond at all is unchanged —
that one succeeds 41% of the time and is how blocked sites get read.
Business websites are asked for a compressed page
The Actor told every business website not to compress its reply, so a page it
only reads the first slice of still arrived in full size. That was there to make
the read limit exact, which it never needed to be — the transfer is cut off by
the Actor itself either way. Pages now arrive compressed and are decoded while
they download, which measured 8% faster per page across 77 sites with byte-for
byte identical content, and sends a fraction of the data over the wire. It also
removes a mismatch: the Actor presents itself as Chrome, and no browser asks a
site to skip compression.
Businesses with no storefront no longer carry a made-up location
A business Google has no address for — a mobile plumber, a contractor working
out of a van — gets a placeholder coordinate instead, one that several unrelated
firms share. Those rows were already flagged locationUnverified: true and left
off the run's map, but the placeholder itself was still saved in location, so
anything that plotted the results or filtered them by distance placed the
business wherever the filler happened to point. The commonest of those points is
open ocean; most of the others land on a city centre and look entirely real,
which is what made them hard to notice. Between 9% and 27% of rows in a typical
lead-generation run are affected.
Those rows now arrive with locationUnverified: true and no location at all —
the same rule the address fields already followed: an empty field rather than an
invented one. Everything else about them is unchanged, and places with a real
address keep their coordinates exactly as before.
"Action needed" no longer tells you to fix something you did correctly
A run that put the service in the search terms and the city in Location — the
documented way round — could still be told to "put only the service/category in
searchStringsArray and the city in Location(s)". The advice was triggered by the
words "search term" and "location" appearing anywhere in the run's warnings, so
an unrelated timeout notice ending "Reduce search terms/languages/reviews/depth"
was enough to set it off. It now appears only when a warning actually reports a
location stuck inside a search term.
Clearer name for a search diagnostic
searchPagesFetched / searchPagesSaved counted the page Google embeds its
search request in — fetched once per search term and reused for every map area —
not pages of results. Reported as "1" beside 36 completed searches it read as a
broken counter instead of the saving it describes. They are now
searchXhrTemplatesFetched and searchXhrTemplatesReused.
[2.0.19] - 2026-07-24
A location like "City, IN" is read as the country, not a US state
A two-letter code after a city name — "Kolkata, IN", "Medellín, CO", "Rabat, MA"
— is now understood as the country it names (India, Colombia, Morocco) rather
than the US state that happens to share the abbreviation (Indiana, Colorado,
Massachusetts). Previously the comma made the code look like a US state, so the
location was resolved on the wrong continent: the seed landed in the United
States, the city's real boundary was lost, and the residential proxy could no
longer be matched to the target country. Spelling out the country
("City, IN, USA") still selects the US state, and codes that are not country
codes (FL, TX, NY, …) are unaffected.
[2.0.18] - 2026-07-23
City-boundary recovery now works the same way in every country
The recovery added over the last two builds picked the boundary by a fixed map
zoom, which lands on a municipality in one country and a state in another — so it
worked in some places and had to be held back in others. It now reads the
boundary straight from the city's own place hierarchy in OpenStreetMap: the
city's municipality is identified by its position in that hierarchy, not by a
zoom, so the exact area is recovered consistently regardless of how a country
numbers its administrative levels. A neighbourhood or suburb still keeps its own
point, and a city whose boundary the search already returns is untouched.
[2.0.17] - 2026-07-23
Boundary recovery no longer widens a neighbourhood into a state
The boundary recovery added in 2.0.16 looked up the administrative area sitting
at a city point at a fixed zoom. That zoom maps to different administrative
levels in different countries — a municipality in one, a whole state in another —
so a neighbourhood search in some regions came back framed by an entire state.
Recovery now runs only for a city/town-level query (a neighbourhood or suburb
keeps its own point, which is the right scale for it), and the area it accepts
must be a municipal-level boundary — a state, region, province, or country is
never used in place of the city, whatever its size. Cities whose exact
municipality is available still get it; everything else keeps the point it had.
[2.0.16] - 2026-07-23
The exact city boundary is now found even when the search returns only a point
A city's outline is what a strict search area is measured against, and what the
seed grid is drawn over. But OpenStreetMap's text search does not always return
it: for some cities — especially where the everyday name differs from the
administrative one — the search answers with the city point and a placeholder
box around it, and never returns the boundary relation at all, even though it
exists. The run then treated a ~35 km square of countryside as the city:
insideSearchArea was judged against a box that was not the city, and the seeds
gridded the land around it.
When the search comes back with only a point for a city, the boundary is now
looked up by location instead of by name — the actual administrative area sitting
at that point is fetched and used as the exact search area. It is accepted only
when it is a real administrative boundary, city or county in scale (never a state
standing in for a city), that genuinely encloses the point; otherwise the point
is kept as before. Cities whose boundary the search already returns are
unaffected — the extra lookup only runs when there was no outline to use.
[2.0.15] - 2026-07-23
A run that hits its cost limit now stops cleanly instead of paying out a quiet tail
When a run reached its maximum cost (maxTotalChargeUsd), the search stopped
taking new work — but the requests already in flight did not. A worker mid-fetch
would still run its full proxy-retry ladder, mint a fresh residential session
for the next attempt, and even open a "second look" at a viewport, all after the
run had already decided to stop. Every one of those was proxy bandwidth bought
for rows that could no longer be saved, and the run then idled for several
seconds with nothing in the log before the summary was written.
In-flight work now honours the stop the moment it is called. Retries at every
layer, fresh-session attempts, and the extra second-look and re-query passes all
bail out as soon as the cost limit — or the soft time budget — is reached, so the
run stops spending on the proxy it set out to save. The first, already-open
request of each task is left to finish so a sticky session is never torn
mid-flight; only the paid re-tries and new requests are cut.
The wind-down is honest now, too. The "stopping search early" line used to report
skipping zero queued tasks — the workers had already emptied the queue by the
time it printed — while the run summary reported the real figure. It now reports
the running total and how many tasks are still finishing, and it agrees with the
summary. The short wait for in-flight requests to settle logs a heartbeat instead
of going silent.
[2.0.14] - 2026-07-20
A custom search area is now read in whatever form you drew it
customGeolocation only accepted a bare GeoJSON geometry — Polygon,
MultiPolygon, or a Point with radiusKm. But drawing an area and exporting
it almost never gives you that: geojson.io, QGIS, Mapbox and My Maps all wrap
the shape in a FeatureCollection, and exporting a single shape gives a
Feature. Pasting either one produced no search area at all, and the run
finished in seconds having saved nothing.
Two things made that hard to act on. The area was dropped silently — the log
said "Using customGeolocation" and then that no search area was available — and
the advice printed at the end was to "provide customGeolocation for an exact
area", which is exactly what had been provided.
The area is now read out of any envelope it arrives in: FeatureCollection,
Feature, GeometryCollection, a plain array of any of those, a
{"geometry": …} wrapper, and the whole thing as a JSON string. Alongside
polygons and points, MultiPoint seeds one area per point, and a LineString
that closes back on itself is read as the polygon it draws. Several shapes in
one collection each become a search area, and all of them count for
membership. Coordinates pasted latitude-first are corrected when that is
provable — a value past ±90 in the second slot can only be a longitude —
and left alone when it is genuinely ambiguous, per the spec.
When an area still cannot be read, the run now says so directly and names what
is accepted, instead of blaming the Location field.
[2.0.13] - 2026-07-18
Multi-city runs no longer freeze on area membership
Deciding whether a place falls inside a requested area does two things per place:
a point-in-polygon test, and — when that fails — a distance to the area's
boundary. The point-in-polygon test has used a fast edge index for a while. The
distance test never did: it walked every segment of every requested area's
outline, and a large OSM city boundary has thousands. On a run over many
adjacent towns that ran once per returned place against every requested area, so
one search's membership pass could burn ~11 seconds of pure CPU tracing outlines
for a number it only ever compared to a threshold.
That CPU ran on the event loop, and a frozen loop cannot fire its own request
timeouts — so in-flight searches ran unbounded and surfaced as a wave of
"proxy/network" timeouts and retries that were really the loop locked up. Worst
on smaller memory tiers, where there is less CPU to absorb it.
The distance is now settled without the trace wherever a cheaper bound already
decides it: the bounding box (which encloses the outline) gives a lower bound, so
an area whose box is already past the cut-off is skipped, and the first area
close enough ends the search — usually the one that surfaced the place. The
name-match leash checks the name before measuring the distance. Same verdicts to
the row (every membership decision is identical), same billing; a 50-town
membership pass drops from ~11 s to ~40 ms of CPU.
Geocoding setup for many-location runs is about twice as fast
Before a single search runs, each requested location is geocoded, and each one
used to take two calls to the OpenStreetMap geocoder — one to find the place, a
second to fetch its exact boundary — with a mandatory one-second spacing between
every call. A run over dozens of towns spent that entire setup waiting on the
round-trips, serially, before any results could come back.
The boundary is now asked for in the first call, so each location takes one
request instead of two. City- and county-scale outlines are byte-for-byte the
same as before; country-scale areas are still fetched simplified on their own.
On a 50-location run the geocode setup drops from ~120 s to ~65 s.
Multi-city runs stop paying for searches that no longer find anything
The search frontier already stops opening map areas once they stop turning up
new places. It measured that "stopped paying" point per city, though, so every
city in a run re-learned the same lesson from scratch, spending several probe
searches each before the brake could apply. That decay is a property of the
search term, not of any one city, so it is now measured across the run: the term
learns its curve once and applies it everywhere. Genuinely dense cities still
explore, because a split off a high-yield area still ranks high on its own merit.
On a many-city run this cut total searches and proxy traffic by roughly half for
a ~3% change in places found — most of it map areas that were returning only
duplicates of what a neighbour had already saved.
The end-of-run summary no longer stacks its writes
Finishing a run persists three things to Apify — resume state, the results map,
and the OUTPUT summary — as independent writes to separate keys. They ran one
after another, so their round-trip times stacked up (~12 s on a large run, all
after the last place was already scraped). They now run concurrently, so the tail
costs the slowest single write rather than their sum; each still keeps its own
error handling.
[2.0.12] - 2026-07-17
Fewer results from other towns: a city search now asks Google for the city
Asked for a map at a given zoom, Google frames four times the ground the Actor
believed it did. The two numbers that decide this — the one that picks a zoom for
an area, and the one that says what a zoom covers — have both been wrong the same
way since the project's first commit, so they agreed with each other and neither
agreed with Google.
The effect: a city resolving to zoom 10 was searched through a 95 km map view
rather than the 24 km it was meant to be. On a real run over one city, 53% of
the results landed outside the area the Actor thought it had asked about, and
they were not Google misbehaving — 93% of everything returned sat inside the view
it had genuinely been handed. Towns 40 km away were in the picture because the
picture was 95 km wide.
Zoom is now read off what Google actually frames. Measured, same input, same run
shape — the same searches, the same depths, the same traffic:
places inside the city: unchanged (215 → 208)
places more than 24 km away: 50 → 18
total rows: 459 → 360
Nothing else moves. For everything from a small town to a state the geometry is
identical to the digit; only the zoom written into the request changes, so what
the run counts as inside an area, how it lays its grid and where it splits are
all exactly as before. Country-scale runs get an honest grid out of it — a
continent now fits the cells it is given instead of falling back.
Probed across zooms: narrowing the view does not thin the answer, it re-aims it.
Every zoom comes back with the same ~207 places; the tighter one spends them on
the city — and turns up 36 the wide view never showed.
[2.0.11] - 2026-07-17
Fixed: a strict run kept searching long after it had everything
geoStrictMatch=true drops the places that fall outside the area and the ones
Google publishes without a location. That is the point of it — but the search was
deciding where to look next by counting every place it had been shown, dropped
ones included. A strict search answering with 182 places and keeping 2 was read
as having found 85, so the map areas below it were ranked at forty times their
worth, and the check that stops a run once looking stops paying could not fire at
all. One strict run over a single city spent 100 searches on the deepest levels
to keep 3 places, and was still going when it was stopped by hand.
A place is worth something once it is saved, so saved is now the only thing the
search ranks or judges by. On that same run: 114 searches down to 16, a fifth
of the data, and the same places. Loose runs keep what they are shown and were
never affected, which is why this only ever showed up in strict mode.
[2.0.9] - 2026-07-17
Faster: a smaller map area no longer downloads the places it was just given
Google ranks an area's answer outward from its centre, so when a map area is
split, each smaller one opens on the same well-known places the bigger one had
already returned. The run downloaded them again and threw them away.
Probed over one city, on four splits of one area: the first place the parent had
not already returned sat at rank 41, 44, 47 and 107 of about 209. A split now
asks Google to start past that head, which it answers exactly — probed at ranks
20, 40 and 50, nothing at or beyond the offset went missing. A fifth fewer
places are downloaded for the same results.
That matters because downloaded places are what a run's clock is made of: its
speed is fixed however many searches it runs at once, so the wall time is simply
what it pulled down. A seed is not affected — it has no parent, and its head is
its best places.
More accurate: what a search must find to be worth running is now measured
A search only earns its place if it finds enough that is new. How much is
"enough" depends on how much it costs, and that is not a constant: measured
across three real runs, the same search came back with 186 KB, 246 KB and 460 KB
— a spread of two and a half times, decided entirely by how much Google chose to
answer with.
A run now reads that off its own answers instead of assuming it, so an area whose
answers come back big has to find proportionally more to be worth opening. The
run had been assuming the cheap end, which had it opening areas that were not
worth their traffic on exactly the searches that cost the most.
[2.0.8] - 2026-07-17
Replaces 2.0.3 through 2.0.7, which were this worked out in public.
The search opens the most promising map area next, and stops when a search stops paying
The search used to divide every map area into four, and each of those into four
again, down to a fixed limit — whatever the ground held. Nothing bounded it
except a term's own place budget, so a term whose city holds fewer places than
the budget could never reach it and never stopped. Measured on a real run: three
terms filled their budget in 5 to 80 searches, and a fourth, with only 56 places
to find in that city, found its last on search 44 and then ran 118 more finding
nothing — 41% of the whole run.
Smaller map areas are now offered to a frontier rather than queued, ranked by
what the area they came from actually found, and a worker always opens the best
one known. That ordering is what lets the run stop on an argument instead of a
rule of thumb: if the most promising area left is not expected to find enough to
pay for the search, nothing else can be, because everything else ranked below it.
Both halves of that are measured on the run itself rather than assumed. What an
area is expected to find comes from how much this run's splits have found against
what they were split from — that decay ranges from 1.1x to 47x a level between
runs, so there is nothing to assume. And what a search has to find to be worth
running is read off what this run's searches actually download, which is no more
constant: the same search comes back with 186 KB over one city and 460 KB over
another.
Measured on a real run, against the same input before any of this:
places
time
data
searches
before
710
350 s
106 MB
860
now
593
57 s
12 MB
63
Six times quicker on a ninth of the data, for 16% fewer places. Those places
are not lost to a cap — they are the ones a search would spend more finding than
they add, and the run now declines them rather than not noticing. 136 searches
were never started, as against being queued and then abandoned.
A map area's shape is measured rather than assumed
A viewport frames a square of ground: Google's own map height, which it sends
with every search, divided by cos(latitude) is one constant across Merida,
Bologna, Nottingham and Bergen. The Actor divided by that cosine where it had to
multiply, so the shape came out wrong by 0.5 / cos²(latitude) — exactly 1.0 at
45°, which is where every city the coverage was ever tuned on happens to sit.
Near the equator a map area was modelled half as tall as it is, so the four it
split into packed together and answered with each other's places. North of 45° it
was modelled up to twice too tall, so they were thrown past the city's edge and
dropped for being outside it. One fault, two opposite symptoms, and no test
covered any of it — reverting the fix passed all 694.
The width is deliberately left as it was. Google frames four times what the
Actor models, which is now written down where the number lives, but closing that
gap makes map areas stop overlapping — and overlap is what re-centres Google's
ranking and shakes out different places. Tried, measured, worse on both counts.
The seed goes in first, and the grid follows only if it asks for one
A run whose place budget was under 120 had a grid of smaller map areas laid over
its city before a single search had run. That was there because such a run could
not find its own way to the grid: an area earned its split by coming back "full",
"full" meant "as many places as this run still wants", and so a run asking for 20
called an area truncated at 20 — whether Google put 20 or 6 on the first page was
luck, and the run returned 5 places one time and 19 the next. That bar stopped
moving with the budget in 2.0.2, and the reason went with it.
What it cost, measured on a 20-term run over one city with a budget of 100: the
grid turned 20 seed searches into 80 searches of quarter-areas, so the seeds —
which return 49 new places each against 4 to 5 for a split, and fill a budget
outright where the ground is dense — never ran at all. Reaching the first
thousand places took 176 seconds and 92 searches; it now takes 75 and 34.
The country grid buys what its budget affords
Covering a continent evenly does not fit in sixteen searches once a map area's
height is measured honestly, and the grid returned nothing at all rather than
what the budget does buy. It used to look like it fit only because cells were
modelled twice as tall as they are at those latitudes — two rows "covering" four
rows of ground, the gaps invisible rather than absent. It now spends its budget
on the shape of the ground and samples what it cannot tile. A box wrapped around
the planet still yields no grid, because it bounds nothing to lay one on.
[2.0.2] - 2026-07-16
Fixed: a run could spend its whole budget dividing the map instead of searching it
2.0.0 removed the rule that stopped a map area being split when it showed
nothing new — correctly, because "nothing new" measures what has already been
downloaded, not what the area still holds. But that rule had been hiding a
second one underneath it, and removing the cover exposed it.
Whether Google truncated an area was judged against a bar that moved with the
run's remaining budget. With ten places left to save, the bar became ten — and
an area that returned thirteen read as "Google is holding more back", split into
four, and each child did the same. The tree exploded at exactly the moment there
was no budget left to spend on it. Worse, the answer was also read down to the
remaining budget, so the two numbers met: every area returned exactly the bar and
so every area looked truncated.
Both are now what they always described. The bar is a property of Google's
answer and does not move. The whole answer is read — it is paid for either way,
one request returns all of it — and the per-term budget is enforced where it
always really was, when rows are claimed for saving.
Measured against runs that hit the bug:
A run asking for 20 places in one city returned 15 after 342 requests and
hit its own timeout with 59 areas still queued. It now returns all 20, in 27
seconds, in 22 requests.
A capped multi-city run: same places, half the wall clock, an eighth of the
proxy traffic, 220 requests down to 28.
Runs whose budget is never reached — where the bar could not collapse — are
unaffected.
Strict-area runs gain the most: the answer is full of rows outside the area that
are dropped and never charged, so reading only as many rows as there was budget
left meant reading mostly the rows that were about to be thrown away.
[2.0.0] - 2026-07-16
Runs are two to four times faster and return far more places
Three things were wrong at once, and each was hiding the next. Measured on the
test Actor, several repetitions per configuration, one variable at a time.
Area membership was starving the event loop. Deciding whether a place fell
inside the requested area was a ray cast over every vertex of the area's
boundary — for a state-sized region, sixteen thousand of them, 5.11 ms per
place, against 0.17 ms to parse the whole search response. It ran synchronously,
so while it ran no worker could send or receive a single byte. Boundary edges
are now indexed by latitude and only the handful that can touch the ray are
walked: 5.11 ms → 0.004 ms per place, and the answer is identical — the
skipped edges provably cannot change it. On default settings this alone took a
run from 216 to 772 places per minute and cut proxy errors by 92%: most of them
were the stalled loop failing to service its own sockets, not bad exits.
Subdivision was pruning the very areas it should have split. A map area was
split only if it showed enough new places. Novelty measures what has already
been downloaded, not what the area still holds — and because results are
deduplicated across the whole run and neighbouring areas overlap, whichever area
was searched first left the next looking empty. The tree's shape depended on
which request happened to finish first: the same input returned 683 or 1191
places from one run to the next. Supply is now counted over everything Google
returns, duplicates included, and an area is split when Google truncates it,
whoever saw it first. Places per run +110%, and the run-to-run spread fell
from 1.74x to 1.23x.
Six requests were fetching what one can. Google's own page asks for 20
results at a time and every published scraper copies that number. It is not a
limit — asked for 250, Google answers with everything it holds for the area
(measured 104 to 230; there is no fixed cap, and the same request returns 196
one minute and 208 the next). One request now replaces up to thirteen: +25%
places for the same wall clock, and each place costs less than it did.
Together, on an identical input: +43% places in 26% less time.
What this means for a run
More rows, so a larger bill for the same input.maxCrawledPlacesPerSearch
(default 50) and maxTotalChargeUsd still bound every run exactly as before —
raise the first only if you want the extra places.
Nothing about the output shape, the fields, the strict-area rules or the
billing events changed. Only how much of the map a run manages to look at.
Runs are noticeably steadier: the same input now returns close to the same
number of places each time.
Fixed
A page-size above 199 silently reset itself to 20 and paginated to a nonsense
offset — the offset was spliced into the middle of the page size, because
!7i20 is a prefix of !7i200.
The counter reporting how many saturated areas went unsplit was itself gated
on novelty, so it could not see the areas the novelty rule blocked.
[1.6.120] - 2026-07-16
New add-on: full place details (opt-in)
extractPlaceDetails fetches each place's own Maps detail page to add the
fields the search response never carries. One extra residential request per
place, billed per enriched place (place-with-details), off by default — base
runs are unchanged. It adds:
openingHours — the full seven-day schedule (search gives only today).
popularTimesHistogram — the per-day/per-hour "how busy" graph.
peopleAlsoSearch — related places; editorialSummary; timeSpent.
gasPrices — live per-grade fuel prices for gas stations, and
evConnectors — charger type/power/availability for EV stations. Neither is
populated by competing scrapers today.
[1.6.119] - 2026-07-16
Booking links and place photos, pulled from the same search response
Every place now carries its photo as a usable image, and bookable venues
carry their reservation links — both read straight from the search response,
so there is no extra request and no change to pricing or result counts.
reserveTableUrl and bookingLinks expose the "Reserve a table" providers
Google shows on the panel (Resy, OpenTable, ...), each as {url, provider}.
On a restaurant search ~80% of places came back with at least one.
imageUrls (and the images gallery) are now populated for ordinary places
— restaurants, shops — not just hotels. Previously a non-hotel row had only
the tiny imageUrl thumbnail; now it also gets the full-resolution photo
URL. The search response carries only a photo or two per place; the complete
gallery still needs a per-place detail fetch, which this does not add.
[1.6.118] - 2026-07-16
A city whose boundary reaches distant islands is framed on the city, not the ocean
Searching a place like Tokyo, Japan now covers the city densely instead of
barely sampling it. An administrative boundary can bundle far-flung island
territory — Tokyo Metropolis reaches ~1,000 km south to the Ogasawara and Iwo
islands — which blows its bounding box up to a size that framed the search at
continental zoom. Google then returns only a thin national skim, so the actual
city was badly under-covered (a restaurant search of Tokyo returned ~50
places where a proper city-zoom search returns the full requested amount).
The seed is now framed on the boundary's mainland — the ring carrying the vast
majority of the outline's detail — while the whole boundary (islands included)
still decides what counts as inside the area. Ordinary cities, whose outline is
one solid piece, are unaffected; only a boundary inflated by distant outliers
is pulled back to where its city actually is.
[1.6.117] - 2026-07-16
A language typed by name (or in its own script) no longer breaks the search
Additional languages now accepts a language's name, maps it to the right
code, and never lets a bad value break a search. Entering Arabic — or
العربية — instead of the code ar used to be accepted silently and then, for
a non-Latin value, crash every search task that ran in that language deep in
the HTTP layer: those searches simply failed and their results went missing,
with nothing in the run to explain it. Common language names (in English and
in their own script) are now mapped to the correct code, a value that is
neither a code nor a name we know is dropped with a clear warning instead of
taken into a doomed request, and the language is escaped on the way out so it
can never crash a search again. A run that used to lose ~8% of its coverage to
this now keeps it.
[1.6.116] - 2026-07-16
Fewer network errors and faster searches, by reusing a warm proxy exit
Each worker now keeps one warm residential IP and reuses it across searches
instead of allocating a brand-new exit for every viewport. Asking the proxy
for a fresh exit on every task is expensive twice over: the allocation itself
is slow, and a freshly drawn exit has a real chance of being a bad one that
times out or resets — so the run spent much of its time paying for, and then
retrying around, exits it had no reason to draw. A warm exit that has already
answered cleanly keeps answering cleanly.
Measured against the previous behaviour on the same searches: ~80–100% fewer
proxy/network errors and 20–45% faster, with identical coverage. Each
worker rotates to a fresh exit periodically (and immediately if its current
one misbehaves), so no single IP carries enough of the run's traffic to draw
Google's attention — validated across healthy and difficult proxy regions,
light and heavy runs, with zero blocks.
A subdivided viewport no longer re-fetches a thin first page on fresh
exits. When Google answers a search with a short first page, the run retries
it on a couple of fresh IPs in case that was a glitch. That is worth doing for
a whole city, but not for one small cell of a subdivided area, where a short
answer is simply the truth — so those re-fetches (each a slow cold exit) are
now skipped below the top level, cutting wasted requests on large gridded runs
with no loss of coverage.
[1.6.115] - 2026-07-16
A viewport that runs out of good proxy exits gets another try, not dropped
A search that fails after cycling through several fresh residential exits is
no longer written off. Residential proxy pools have bad moments — a handful
of exits in a country time out or reset at once — and a viewport unlucky
enough to draw only those used to be abandoned, leaving a hole in coverage the
user could only fill by re-running the whole job. Such a viewport is now put
back at the end of the queue and retried later (bounded, so a genuinely
unreachable target can't loop), by which time the pool has usually rotated in
working exits. Runs that finish cleanly are unaffected — there is nothing to
retry — and a run stopping for a charge limit or time budget still stops at
once so its partial results save.
[1.6.114] - 2026-07-16
Runs over many large areas no longer freeze, and big runs finish faster
A run that searches a dozen city-sized areas at once no longer stalls.
Every place Google returns is checked against the outline of each requested
area to label whether it falls inside. Those outlines can be enormous — a
single city boundary runs to ten or fifteen thousand points — and each place
was being traced against every area's full outline, tens of millions of
geometry checks per batch, all on the one thread that also has to fire request
timeouts. On a large multi-area run that thread could be busy for a minute at
a stretch: timeouts stopped firing, requests hung, and the run looked frozen.
A place is now cheaply rejected against an area's bounding box before its
outline is traced at all — an area a place is nowhere near is dismissed in one
comparison instead of fifteen thousand. The inside/outside labelling is
unchanged; the run just stops doing the work it never needed. Large multi-area
runs that used to freeze now run clean, and about twice as fast.
Large runs use the extra workers they were already asking for. When the
queue dwarfs the worker count the run widens its pool so it spends its time
searching instead of waiting in line, but the search body ran under a gate
sized to the original count, quietly capping the wider pool. The gate now
opens as wide as the pool. Small runs are unaffected; each task still holds
its own residential IP with per-IP spacing, so a wider pool asks no more of
any one exit.
[1.6.111] - 2026-07-14
A run that searched the wrong place now says so
When a Location resolves to somewhere you did not mean, the run tells you —
and tells you where your results actually were. A place name can match
something else that carries it: a hamlet, a railway station, a district in
another region. Written without the suffix the local map uses, it often does.
The search then runs on the wrong ground; Google answers it perfectly well; and
every result lands hundreds of kilometres from the area, so the guard that
drops unrelated places throws all of it away.
From the inside that is indistinguishable from an empty city — no rows, nothing
broken — and the run would finish SUCCEEDED with a handful of rows and no
complaint. What tells the two apart is what Google returned: an empty area
returns nothing, a wrong one returns a full page of good results that all sit
somewhere else.
Such a run now finishes INPUT_NEEDS_ATTENTION and names the towns the
results were really in — which is usually the exact Location you should have
typed — along with how to fix it.
A non-strict run is no longer told to turn off strict mode. The hint shown
when results land outside the search area was written for geoStrictMatch=true
and was shown regardless of the setting, so a run with strict mode off was
advised to switch off the thing that was already off. It now explains what
actually happened in each mode. The list of nearby towns holding the results is
also filled in whenever there are any, not only when a run saved nothing at
all — a run that saves almost nothing needs it most.
[1.6.109] - 2026-07-14
When Google reads a search as an address, ask again
A Location written out in full could return a single result: the place
itself, instead of the businesses in it. The Actor adds your Location to your
search term, which is what keeps a search on the place you asked for. Usually
the full spelling costs nothing. But for some places — small towns especially —
Google stops reading the query as a search and reads it as an address, and
answers with one result: the locality.
With one result there is nothing to subdivide, so the run would stop there and
report itself healthy: from the inside, nothing had failed.
No proxy IP can fix a query, so retrying on fresh ones (which is what used to
happen) only spent money proving it. Now a seed that comes back with a single
place is asked once more with only the most specific part of the Location — the
town rather than the town, its region and its country — and if that reads as a
search, the run keeps that wording and passes it down to every viewport under
it. In testing this turned single-result runs into full ones across several
languages and country conventions.
A Location with nothing to trim behaves exactly as before, and a run that was
never answered with a single place never re-queries.
OUTPUT.searchStats.placeLookupRequeries counts when it fires.
[1.6.108] - 2026-07-14
Distance to the search area is measured against the area
The guard that drops far-away junk works for country searches again. Two
checks still measured distance against the bounding box after 1.6.107 gave
countries a real boundary: the guard that drops a place sitting hundreds of
kilometres from anything you asked for, and the 25 km leash that lets a place
just past a city's official line count as inside when Google files it under
that city's name. Against a country's globe-wide box, every place on Earth
measured 0 km from the area — so the guard never fired and the leash was
not a leash. A business a thousand kilometres beyond a country's border was
being called zero kilometres outside it.
Both now measure against the boundary when there is one, fall back to the box
where there is none (a postal code, a coordinate, a Google Maps URL), and
return unknown rather than a confident zero when the box is one of the
globe-wide ones.
What this changes for you: a country search drops a small number of rows it
used to keep and charge for — the ones that sit in a different country
altogether. In testing this was around 1% of a country run's rows. Places just
over a border are kept, as before: the threshold is distance from the boundary,
not which side of it you are on, so a business a few dozen kilometres outside
still counts as nearby.
[1.6.107] - 2026-07-14
Searching a whole country now searches where its businesses are
A country-sized search area is no longer covered as if people lived in it
evenly. The Actor covers a search area by laying a grid of map viewports over
it. For a city, a region, or a country the size of Germany that is right —
every cell opens onto somebody. For a continent-sized country it is not: an
even grid over a country like Russia or Canada spends most of its cells on
forest, tundra and ocean, and hands the one city that holds the businesses a
single cell. Runs came back with a fraction of what was there.
Now a country-scale area sends its seed viewport in alone first, and lays the
grid on what comes back. Google ranks by prominence, so even a search centred
on empty land returns the capital and the big cities — that answer is a map of
where the supply is, and the grid follows it: a metro-sized viewport on each
cluster of results, and whatever the clusters do not claim is still searched
blind, so nothing is given up. Same number of searches, same cost per run.
A Location of "USA" searched nowhere in particular. Every other country is
sent to Google as part of the query — <term> Germany, <term> India — which
is what keeps a search on the country it was asked about. The United States was
stripped out by a stop-word list that only ever meant to trim the redundant
, USA off the end of a city, so the search went out as a bare term from a
viewport in the middle of the country and came back with businesses across the
border and rows with no city at all. USA / US / United States now works
like every other country. A city that names its country (Anchorage, USA) is
unaffected.
A country is now bounded by its border, not by a rectangle around the
planet. A search area larger than ~6° across used to be refused its exact
outline and filtered by a bounding box instead, on the reasoning that for
something that big a box is a fair approximation. For a country that crosses
the antimeridian it is not an approximation of anything: the box runs from
−180° to 180°. Every business on Earth in the right band of latitude counted as
inside, so a country search could label businesses in entirely different
countries insideSearchArea: true, and geoStrictMatch had nothing to enforce.
Country-sized areas now get their real boundary, simplified in transit so it
stays small (a country's outline costs about 50 KB). What this changes for
you:insideSearchArea tells the truth for country searches; geoStrictMatch=true
over a country now actually keeps the country; the "somewhere else nearby"
count on the results map is meaningful again. In the default (non-strict) mode
the number of rows you get is unchanged — out-of-area places were kept before
and are kept now, they are simply labelled honestly.
Limits and billing are unchanged.
[1.6.104] - 2026-07-14
Strict means strict: the fallback rows are gone
A strict run no longer saves places from outside the search area — ever.
When strict filtering would have saved nothing, the Actor used to save the
outside-area places it had already fetched (geoStrictFallback=true) so that
work already paid for produced something. What it produced was the one thing
the user had ruled out, charged as dataset rows.
Now: nothing outside the area is saved and nothing outside it is billed. A run
that finds nothing returns one diagnostic row, exactly like every other run
with no places to show — and names which towns the results were actually in
(OUTPUT.searchStats.nearbyCitiesWithResults, and in the log). A city's
official boundary is often far smaller than the city people mean (Mendoza's is
54 km², its suppliers are in Godoy Cruz, Guaymallén and Maipú), so that list is
usually the answer: search those towns as Location(s) of their own.
The geoStrictFallback and geoStrictFallbackReason dataset fields no longer
exist. Nothing else about strict mode changed: in-area places are saved,
out-of-area places are dropped, and one in-area place is enough to keep the run
going as before.
An empty area is abandoned, not searched to the end. A strict run sends its
seed in alone. If the seed comes back with a full page of results and not one
of them inside the area, the run stops there instead of opening viewport after
viewport that can only answer the same way: a hopeless search now costs one
map view instead of fifteen. If the seed does find something inside, the grid
is laid over the area at once.
[1.6.102] - 2026-07-14
A small run stops being a coin flip
The same search returned 5 places one time and 19 the next. A map view is
looked at more closely when Google fills it — that is Google saying it has more
than it will show. But "full" means "as many places as this run still wants",
so a run asking for 20 called a view full at 20 results, and whether Google put
25 or 6 on its first page is luck. A run that lost that toss never looked
closer anywhere, and returned a third of what was there.
A run that asks for fewer places than one map view can hold now covers its
search area from the start, instead of waiting for a signal it cannot trust.
Measured on one such search, four runs each: 5, 5, 18, 19 places before —
19, 19, 20, 20 after, and each place costs a third less than it did. Runs
that ask for more (the default is 50 per search term, and 250 or 400 is
common) are untouched: their first view fills up whenever the ground has
anything on it, so they already find their way. Nothing about them changed —
the same places, for the same money.
[1.6.100] - 2026-07-14
A city with an accent in its name is no longer searched as its whole province
"Neuquen, Argentina" could still resolve to the 5°-wide province, and a
strict run then filtered against half of Patagonia and saved six places. The
rule that takes the city when a name belongs to both the city and the region
around it only ever ran on the main geocoder. That one allows a single request
a second, so when it is busy the fallback answers instead — and the fallback
ranked the province first, because its name matches the query exactly. The
fallback now reads a location the same way, and the main geocoder is asked a
second time before it is given up on: it is the only one of the two with real
boundaries, and a miss from it usually means "busy", not "no such place".
Names are compared without their accents. The geocoders do not agree with
themselves about them: for "Córdoba" the fallback spells the city without its
accent and the province with it. Those read as two different names, so the
rule above never fired and Córdoba was searched as a 612 km-tall province.
Córdoba, Neuquén, São Paulo, Salta, Mendoza and Santa Fe now all resolve to the
city, as Google Maps reads them, while "California, United States" stays the
state.
The fallback no longer answers with a building when it does not know the
place: "Provincia del Neuquén" used to come back as a racetrack at street zoom.
A search area has to be somewhere people live, not an address.
[1.6.98] - 2026-07-14
The Actor tunes itself — the advanced settings are gone
Nine settings removed from the input: places per map view, subdivision
on/off and depth, zoom, zoom passes, worker count, request timeout, request
spacing, and address reverse-geocoding. Nobody should have to know what a
viewport or a subdivision depth is to get a good run, and a wrong guess at any
of them quietly cost coverage or money.
The Actor now decides all of it from the job in front of it: the grid over the
search area picks its own zoom from the size of the area, workers scale with
the queue, and map views are split where Google truncates them. Address
reverse-geocoding is gone for good — it invented addresses for businesses that
have none (see 🚚 Keep businesses with no address instead).
Runs saved before this that still pass those fields keep working exactly as
they did.
[1.6.97] - 2026-07-14
Keep the businesses that have no address, if you want them
New input: 🚚 Keep businesses with no address (keepUnverifiedLocations).
A mobile plumber, an electrician who drives to you, a studio run from home —
Google publishes no address for a business with no storefront, and fills its
coordinates with a placeholder that several unrelated firms can share. Strict
area matching has nothing to check such a place against, so it was dropped: one
real run dropped 7,315 of them.
They are not bad rows. Name, phone, website, category and reviews are all
there — for lead generation they are often the best rows in the dataset, and
they are exactly the leads a strict run could never see. Turn this on to keep
them while the rest of the run stays strict. They are saved with
locationUnverified=true, no insideSearchArea value,
and an empty address rather than an invented one.
Measured on a strict run of one city: 351 places without it, 372 with — and 91
of the 92 extra rows carry a phone number or a website. Off by default, so no
run changes and no bill changes unless you ask.
[1.6.96] - 2026-07-14
A map view is read in waves, not page by page
Google returns a view's places twenty at a time, and the Actor asked for each
twenty only after the last had arrived. The pages of one view are now read side
by side — but only once Google has filled the first, since most views hold
nothing else and reading ahead into an empty view is traffic paid for nothing.
Same places, same cost, a shorter run.
Pages are read on the same proxy IP that opened the view: Google hands the rest
of a result list to the session that started it, and pages asked for from other
IPs come back empty (which would look like a view with nothing left in it, and
quietly cost a quarter of the places).
[1.6.95] - 2026-07-14
Big runs stop queueing
Covering an area means many more searches than splitting one seed did, and
a run of 5 search terms across 10 cities now queues around 850 of them. On the
8 workers it was configured with, such a run spends most of its life waiting in
its own queue — the work is network-bound and the Actor's CPU sits at about 4%.
A run whose queue dwarfs its workers is now given more of them, up to three
times what it was configured with. Every search already holds its own
residential IP and requests are spaced per IP, so this asks no more of any one
exit: measured on the same input, 241s on 8 workers, 159s on 16 and 130s on 24
— the same places, the same cost, and no failed searches. A run configured with
very few workers on purpose stays that way.
[1.6.94] - 2026-07-14
Same places, half the time and half the cost
1.6.92's second look was paying full price. Where Google truncates a map
view, the Actor asks again from another IP — but it was re-reading the whole
view, page after page, every time. Two answers from Google differ in how they
are ranked, so a place missing from the first shows up near the top of the
second: reading the top of the list finds the same places for a fifth of the
traffic. The second look is also skipped where it cannot pay — when the search
term has nearly filled its maxCrawledPlacesPerSearch quota (whatever it found
would be dropped anyway), and deep in the map where a view is a few streets
across and there is no held-back tail.
On a run of 5 search terms across 2 cities capped at 250 places each: the same
work now takes 217s instead of 421s and costs half as much, for 9% fewer
places. Against the build before this coverage work: +33% places for +60%
time, where it had been +48% for +250%.
[1.6.93] - 2026-07-14
Sprawling cities are searched properly
A city that reaches out in arms was searched as if the arms were empty.
Oklahoma City's limits cross 1,600 km² of them. A map view laid over one arm is
mostly city, but wide enough to take in the countryside beside it — and Google,
which ranks a view's results outward from its center, fills it from the nearest
town: 120 results, 8 of them in the city. The Actor read those 8 as "there is
nothing here" and never looked closer, so the arm was never searched at a zoom
that could see it.
A view standing on the city is now looked at more closely even when it comes
back with a thin catch, as long as Google truncated it. A view that merely
reaches into the city from outside still has to show real local supply first,
so a search that has drifted into the next town does not start exploring it.
Measured on Oklahoma City, four runs: 146 → 207 places per run (+42%), and
36% more across the runs. Compact cities are unaffected — they have no arms.
[1.6.92] - 2026-07-14
The search now covers the city, and a run stops depending on luck
A run used to find 130 places or 300 for the same input, and which one was
chance. The search started from the viewport the geocoder returns, which is
framed around the city rather than on it — for a large city it reaches well
past the boundary. Quartering that viewport sent three of its four quarters
into the countryside, and Google, which ranks a map's results outward from its
center, answered them with the neighbouring towns. Those searches found almost
nothing in the city, so they were never taken further, and the whole search
then rested on whether one of them happened to land on something.
The search now starts from a grid laid over the requested area itself — as
close-up as it can be while still covering the city, spaced to reach its edges,
with every viewport centered inside it. Nothing starts out in a field.
A place Google leaves out of one answer is asked for again. Google does not
answer the same map view with the same list twice. Where it truncates a view —
telling us it has more than it will show — the Actor now asks a second time
from a different IP and keeps whatever the first answer left out.
Measured on two cities, four runs each: Nottingham 134 → 360 places per run
(and no more collapsing runs: 355-368 every time, against 130-318 before);
Oklahoma City +40% on everything found across the runs. Sprawling cities
whose limits reach out in arms still vary more than compact ones.
[1.6.91] - 2026-07-14
More places inside the search area, for the same money
The map is now cut where Google is holding places back. A viewport was
split into four smaller ones only when it showed a large number of rows from
the requested area — a guess at "there is more here" that refused areas Google
had plainly filled to its limit, while spending four searches on areas that
had already shown everything they had. Filling the viewport to the limit is
Google saying it has more than it will show at this zoom, so that is the
condition now. Measured on one city: +18% places found, and the extra
places cost a fraction of what they earn.
A search area no longer costs a search page. Every viewport fetched
Google's ~52 KB search page just to read one url out of it — a url that
carries the viewport inside it and can be re-pointed at any other. The page is
now fetched once per search term and reused, which is most of the cost of the
small viewports the Actor opens the most of. Same places, ~30% less proxy
traffic and a noticeably faster run; a term whose url stops working falls back
to fetching the page, so this can never cost a result.
Together: on the same input, +21% more places at the same cost per place.
[1.6.89] - 2026-07-13
Fixed: a long search term could lose a whole search area to the retry meant to save it
The retry added in 1.6.87 built a proxy session id the proxy would not
take. A search area is pinned to one residential exit IP by a session id
built from the search term, and when Google's first page came back thin, the
retry appended a counter to that id to ask for a fresh IP. For a long search
term the id was already at the 49-character mark, so the counter pushed it to
51 — one past what Apify Proxy accepts. The request was rejected before it
left the Actor with "session_id is longer than maximum allowed length 50",
and the search area died on the retry that existed to rescue it.
A search term long enough to fill the id was exposed whenever Google happened
to answer thin — a search that hit both could fail its viewport and save no
places at all.
Session ids are now brought into the proxy's limits at the single point where
they are handed over, so no caller can build one that overflows. An id too
long to keep whole keeps a readable head plus a digest of the rest, so the
same search area still maps to the same IP and two areas never collapse onto
one.
[1.6.87] - 2026-07-13
Fixed: a viewport could report "no places" while Google was handing them over
One odd reply from Google threw away a whole search area. Google normally
puts a viewport's results in one slot of its search response. Every so often
it answers the same query at the same viewport with a thin envelope
instead: that slot is absent, and a place sits deeper in the tree. The parser
read only the usual slot, so it saw nothing — and "nothing" is how a viewport
reports that it is empty, which then stopped its pagination and its
subdivision too. A search that would have returned a full page of places
returned none, and the run told the user to check their spelling.
Replaying one such search against Google 14 times, it answered with 13–18
places 11 times and with the thin envelope twice — the reply Google sends is
not deterministic, so this was quietly costing places in every run, not just
the ones that ended at zero.
The thin envelope is now read, and a first page that comes back thin or empty
is retried on a fresh proxy IP before the viewport is written off. Replaying
the same search 10 more times after the fix: 14–17 places every time, no
zeroes.
[1.6.86] - 2026-07-13
Fixed: a city that shares its province's name searched the whole province
"Mendoza, Argentina" resolved to the 619 km-wide province, not the city.
Nominatim ranks by importance, and a province always outranks its own
capital, so the Actor searched — and, under geoStrictMatch, filtered
against — half a province. Google Maps reads such a query as the city, and so
do users. When a name is both a region and a city inside it, the city now
wins: Mendoza, Neuquén, Córdoba, Salta, Santa Fe, San Juan.
Guarded in both directions. A capital whose name genuinely differs leaves its
province alone — Tucumán's capital is San Miguel de Tucumán, so "Tucumán"
still means the province. And a same-named place elsewhere cannot hijack a
region: "California, United States" is still the state, not the town of
California, Kentucky. Say "Provincia de Mendoza" (its local form) or pass
customGeolocation to search a whole province on purpose.
An oversized boundary is now simplified, not discarded. Boundaries above
25,000 points were thrown away, leaving a rectangle — Neuquén province has
44,927, so a strict run there matched against a 5°-wide box, i.e. half of
Patagonia. Such boundaries are now thinned to fit (44,927 → 22,464 points),
which costs tens of metres of accuracy and buys back the actual area.
Clearer
The run diagnosis no longer claims that dropped places "had no coordinates".
They have coordinates — a placeholder, because they are businesses with no
storefront. The hint now says so, and points out they are real businesses you
can keep by turning geoStrictMatch off.
additionalLanguages was undersold as uncovering "slightly different
translated categories and rankings". Measured, three extra languages find
20–35% more places than English alone, because each pass reshuffles
Google's ranking and surfaces a different long tail from under the
~120-per-viewport cap. The field now says that, and advises adding the local
language of the target city, which contributes the most.
[1.6.85] - 2026-07-13
Fixed: strict mode saved places from neighbouring towns
A province that shares its capital's name no longer lets other towns in.
A place outside the boundary counts as inside when Google files it under the
requested place name — but the check also read the state field, and a
province usually carries its city's name while covering far more ground.
Google writes Alcorcón's addresses as "28925 Alcorcón, Madrid", so a
strict Madrid search saved 87 places from Alcorcón, Leganés, Alcobendas and
other municipalities. The name match now reads only the settlement fields
(city/town/municipality). Verified against the reported 8,109-place run: 97
such rows are now correctly dropped, and Nottingham's postal suburbs still
count as Nottingham.