I run a visa agency in Bali. The other half of my week goes into Apify Actors, and one of them reads Airbnb search pages: you give it a place, two dates and a party size, and it returns one flat row per listing — total price for the stay, price per night, rating, review count, beds, coordinates.

The question people actually buy that for is a pricing question. What does a two-bedroom in Seminyak go for in mid-November? I live twenty minutes from Seminyak, which gave me an advantage over most of my own users: I could tell that several villas in the answer were not in Seminyak at all.

Here is what is going on, measured, because it changes every average you compute downstream.

The place you type is not the place that gets searched

Three scheduled runs, 16, 17 and 18 October 2026, same input every time: Seminyak, Bali, Indonesia, a three-night stay a month out, two adults, prices in USD, 60 listings. 180 rows, 116 unique listings across the three days.

Every row carries both the string I asked for and the place Airbnb decided I meant:

query : "Seminyak, Bali, Indonesia" place : "Kuta, Indonesia" 

Kuta is the kecamatan — the district. Seminyak is one village inside it. The search ran across the whole district, and the row says so, in a field most people never read.

What that does to the result set, counting the area label on all 116 unique listings:

Kuta 37 Seminyak 26 Kuta Utara 24 Kecamatan Kuta Utara 15 South Kuta 3 Umalas 3 Kabupaten Badung 3 Pererenan 2 Denpasar 1 Berawa 1 Kecamatan Denpasar Barat 1 

Twenty-six of 116. Pererenan and Berawa are Canggu — a different beach, a different market, a 25-minute drive in traffic. Denpasar is the provincial capital, inland.

Coordinates confirm it. Taking Seminyak Beach as −8.6900 / 115.1685 and measuring great-circle distance per run:

Run n median p90 max beyond 5 km
16 Oct 60 1.5 km 4.0 km 7.4 km 3
17 Oct 60 1.3 km 3.7 km 7.1 km 3
18 Oct 60 1.4 km 3.9 km 7.1 km 3

Stable across days, so this isn't one odd page. The two farthest rows:

 7.4 km Pererenan $116.00/night Villa Kayu: Chic Minimalist Retreat Near Beach 7.1 km Pererenan $186.33/night Super Unique & Stunning Architecture @Bocoa Villas 

Airbnb obfuscates coordinates slightly before you book, so treat the sub-kilometre figures as soft. It does not obfuscate by seven kilometres.

The text label is the wrong filter

The obvious fix is to throw away everything where area != "Seminyak". That is worse than doing nothing, and the same 116 listings show why.

Sort by distance and look at the three closest rows in the whole sample:

 0.2 km Kuta $152.00/night One Bedroom Villa with Private Pool in Seminyak 0.2 km Kuta $ 78.33/night The Catur Villa - Macaca 2BR Villa 0.3 km Kuta $122.33/night Calladi Villa 2BR Luxury with 300m² Big Garden 

The nearest listing to the centre of Seminyak is labelled Kuta and has the word Seminyak in its own title. The label is whatever administrative string Airbnb happened to render for that card, and the list above shows it is not even one consistent level: village (Seminyak, Umalas, Pererenan), district (Kuta Utara), the same district again with the Indonesian prefix attached (Kecamatan Kuta Utara — 24 rows one way, 15 the other), and regency (Kabupaten Badung, which contains all of the above). You cannot GROUP BY that column.

So filter on geometry instead. Same 116 listings, two definitions of "in Seminyak":

Filter n median $/night mean $/night
everything returned 116 112.00 113.48
area == "Seminyak" 26 127.67 137.47
within 1.5 km of the beach 68 108.33 105.32
beyond 1.5 km 48 116.00 125.03
beyond 3 km 18 119.33 131.69

The label filter reports a median 18% above the coordinate filter, on one dataset, for the same week and the same party size. It keeps 26 rows where the honest answer keeps 68, and the 26 are not a random subsample — Airbnb's label skews toward the expensive main-strip inventory. If you are a host setting a rate off that number you are pricing against a market that does not exist.

Mean rating barely moves between the bands (4.90 against 4.93) — the tell that the price gap is geography, not a quality split.

It isn't a Bali thing

Two more searches, 18 October 2026, same shape, from a throwaway account:

query : "Shoreditch, London, United Kingdom" place : "Greater London, United Kingdom" 

Greater London is roughly 1,570 km². Those 20 rows are much tighter than Bali's — median 0.4 km from Shoreditch High Street, 18 of 20 inside 3 km — but the two that escaped are the two that matter:

11.8 km London $ 36.33 A home away from home, 6.9 km Newham $ 40.00 3 bedroom house 0.7 km London $341.00 2BR Shoreditch Flat with Balcony, near the tube 0.6 km London $297.67 Stylish 2-Bed Apartment In Shoreditch 

Ten percent of the rows, and they set your floor: $36 against a Shoreditch core around $275–340. Any "cheapest available in Shoreditch" built on this is answering a question about Newham.

And the one that surprised me most:

query : "Williamsburg, Brooklyn, New York" place : "New York City, NY" 

A neighborhood of Brooklyn widened to the entire city. Five of those ten rows were not homes at all:

{ "name": "Pod Brooklyn Hotel", "propertyType": "Pod Brooklyn Hotel", "area": null, "priceTotal": null, "pricePerNight": null, "badges": ["Featured hotel"], "bedInfo": [], "rating": 4.76, "reviewCount": 629 } 

Airbnb's hotel inventory. No price in the search card, no area, no beds — and one of them, Pod 39, sits in Murray Hill in Manhattan, 4.3 km from Williamsburg. I deliberately emit null rather than 0 for those, because a zero would quietly drag an average down and nobody would notice. But len(dataset) is still not your sample size: asking for ten listings got me five priced homes.

What to actually do with the rows

Three lines, in this order, before any aggregation:

import math, requests TOKEN = "<your apify token>" r = requests.post( "https://api.apify.com/v2/acts/lergassy~airbnb-scraper/run-sync-get-dataset-items", params={"token": TOKEN}, json={ "locationQueries": ["Seminyak, Bali, Indonesia"], "checkIn": "2026-11-17", "checkOut": "2026-11-20", "adults": 2, "currency": "USD", "maxListings": 60, }, ).json() def km(lat, lon, alat=-8.6900, alon=115.1685): p = math.pi / 180 return 2 * 6371 * math.asin(math.sqrt( math.sin((lat - alat) * p / 2) ** 2 + math.cos(alat * p) * math.cos(lat * p) * math.sin((lon - alon) * p / 2) ** 2)) homes = [x for x in r if x.get("priceTotal") is not None # drops hotel cards  and "Featured hotel" not in (x.get("badges") or []) and km(x["latitude"], x["longitude"]) <= 1.5] # drops the next district  print(len(r), "rows ->", len(homes), "in scope") 
  1. Read place, not just query. If they differ, the search was widened and you now need a geographic cut. In my runs they differed in all three cities.
  2. Cut on coordinates, with a radius you chose. Not on area, ever.
  3. Drop the hotel cards before you average anything, or report them separately.

Pasting an Airbnb search URL into startUrls instead of typing a place does not save you — Airbnb resolves the place server-side either way, and the URL's own map bounds are advisory. The only honest boundary is the one you draw on the coordinates you got back. Actor and full field list: Airbnb Scraper.

When this is the wrong tool

If you need the exact street address, stop here — Airbnb only reveals it after booking, and no scraper changes that. If you need historic nightly rates, also stop: Airbnb quotes live availability, so a price series only exists if you start collecting now and keep the snapshots. And if your question is a single listing's amenities rather than a market's prices, run the listing URL directly — a search is the one thing that can hand you a price, and the one thing that will hand you the wrong neighborhood while doing it.

The general lesson has nothing to do with Airbnb. Any search endpoint that accepts free-text place names is doing geocoding you cannot see, and the polite ones tell you what they decided. Read that field. It is the difference between a market report and a confident average of the wrong town.