Short-Term Rental Market Research: Using Airbnb Data for Dynamic Pricing
How STR hosts and investors use Airbnb comp data to benchmark nightly rates, spot underpriced markets and adjust pricing by season and neighborhood.
The actor referenced in this article. Pay only for results delivered.
Most short-term rental pricing tools — PriceLabs, Wheelhouse, Beyond — sit on top of the same underlying problem: to price a unit competitively, you need to know what comparable units in the same market are actually charging, right now, for the same date window. That’s a data problem before it’s a pricing-algorithm problem, and it’s one you can solve directly with a scraper instead of paying a subscription for a black-box multiplier applied to your base rate.
The workflow below uses the Airbnb Scraper to pull live comparable listings for a target market and date range, then benchmarks a unit’s asking price against that comp set — the same first step any STR revenue tool runs internally, made transparent and queryable.
Step 1: Define your comp set
A “comp” for STR pricing means a listing that competes for the same booking: similar property type, similar guest capacity, roughly the same neighborhood, and — critically — the same date window, because Airbnb pricing is dynamic by season and day of week in a way monthly rental pricing isn’t.
import os
from apify_client import ApifyClient
apify = ApifyClient(os.environ["APIFY_TOKEN"])
run = apify.actor("themineworks/airbnb-scraper").call(run_input={
"location": "New Orleans, LA",
"checkIn": "2026-10-24", # target booking window
"checkOut": "2026-10-27",
"adults": 4,
"maxItems": 300,
})
listings = list(apify.dataset(run["defaultDatasetId"]).iterate_items())
Pulling personCapacity alongside price lets you filter to units that actually compete with yours rather than averaging across studios and 6-bedroom houses in the same city, which would produce a meaningless blended number.
Step 2: Filter to true comparables
my_capacity = 6
my_property_type = "Entire home"
comps = [
l for l in listings
if l.get("personCapacity") and abs(l["personCapacity"] - my_capacity) <= 1
and l.get("propertyType", "").lower().find("entire") != -1
and l.get("price")
]
print(f"{len(comps)} comparable listings found")
Step 3: Benchmark price against quality signals
Price alone doesn’t tell you whether you’re competitive — a $180/night listing with a 4.95 rating and 200 reviews is a much stronger competitor than a $180/night listing with no reviews. The rating, reviewsCount and isSuperhost fields let you separate “cheap because new” from “cheap because it’s actually the market price.”
import statistics
prices = [c["price"] for c in comps]
median_price = statistics.median(prices)
# Strong comps: established listings with real review volume
established = [c for c in comps if c.get("reviewsCount", 0) >= 20]
established_median = statistics.median([c["price"] for c in established]) if established else None
print(f"Market median (all comps): ${median_price:.0f}/night")
print(f"Established-listing median: ${established_median:.0f}/night" if established_median else "Not enough established comps")
superhost_prices = [c["price"] for c in comps if c.get("isSuperhost")]
if superhost_prices:
print(f"Superhost median: ${statistics.median(superhost_prices):.0f}/night "
f"({len(superhost_prices)}/{len(comps)} of comps are superhosts)")
The gap between the “all comps” median and the “established listings” median is usually where the real pricing signal lives. New, unreviewed listings often price aggressively low to win their first bookings — pricing against them directly will underprice a unit that has its own track record.
Step 4: Map price by location, not just city average
latitude and longitude come back on every listing, so you can bucket comps by proximity instead of treating “New Orleans” as one market. A unit three blocks from the French Quarter and a unit in Algiers are not competing for the same guest, even though both show up under the same city search.
from math import radians, sin, cos, sqrt, atan2
def distance_miles(lat1, lon1, lat2, lon2):
R = 3959
dlat, dlon = radians(lat2 - lat1), radians(lon2 - lon1)
a = sin(dlat/2)**2 + cos(radians(lat1))*cos(radians(lat2))*sin(dlon/2)**2
return R * 2 * atan2(sqrt(a), sqrt(1 - a))
my_lat, my_lon = 29.9584, -90.0644 # unit's coordinates
nearby = [c for c in comps if distance_miles(my_lat, my_lon, c["latitude"], c["longitude"]) <= 0.5]
print(f"{len(nearby)} comps within 0.5 miles — median ${statistics.median([c['price'] for c in nearby]):.0f}/night" if nearby else "No close comps, widen radius")
Step 5: Repeat across date windows to build a pricing calendar
Because Airbnb pricing moves with season, weekday/weekend and local events, one pull only tells you about one window. Running the same location query across a rolling set of date ranges — say, every weekend for the next twelve weeks — turns a single comp check into an actual pricing calendar you can act on, closer to what a paid dynamic-pricing tool generates but built from data you queried yourself and can inspect field by field.
from datetime import date, timedelta
base = date(2026, 8, 1)
windows = [(base + timedelta(weeks=i), base + timedelta(weeks=i, days=3)) for i in range(12)]
calendar = {}
for check_in, check_out in windows:
run = apify.actor("themineworks/airbnb-scraper").call(run_input={
"location": "New Orleans, LA",
"checkIn": check_in.isoformat(),
"checkOut": check_out.isoformat(),
"adults": 4,
"maxItems": 150,
})
week_comps = [i for i in apify.dataset(run["defaultDatasetId"]).iterate_items() if i.get("price")]
calendar[check_in.isoformat()] = statistics.median([c["price"] for c in week_comps]) if week_comps else None
for wk, price in calendar.items():
print(f"{wk}: median comp price ${price:.0f}" if price else f"{wk}: no data")
What this doesn’t replace
This gives you the comp side of pricing — what the market is charging right now for comparable units. It doesn’t give you your own booking history, occupancy rate, or lead time, which are the other half of any real pricing decision and live in your own PMS or Airbnb host dashboard, not in scraped search data. Treat comp pricing as one input alongside your own performance data, not a complete pricing engine on its own.
Related reading
For the mechanics of running the scraper itself — full field list, output format, MCP integration — see Airbnb Scraper: Listing Prices, Ratings, and Availability Without the API. If you’re also evaluating long-term rental comps for the same property (in case a 12-month lease outperforms STR in a given market), Airbnb vs Zillow Rental Listings covers how nightly STR data and monthly rental data differ field by field.
Explore the scraper referenced in this article — see inputs, outputs, and pricing, then run it on Apify.
I Automated My AI Job Hunt: 8 Tools, One Claude Agent, Under $1
Most job hunting is spent on the wrong problem. Here is the exact end-to-end flow — find who is actually hiring, rank by real fit, research the company, find the human, and write an application grounded in facts — run entirely from Claude via MCP.
Ask an AI for a Company's SEC CIK and It Will Lie to You. Here Is the Fix.
An AI that remembers is not an AI that verifies. This is the exact diligence flow — legal identity from GLEIF, filings from EDGAR, funding, engineering activity and hiring — run from Claude via MCP, grounded in real registries, for about fifteen cents a company.
Stop Cold Pitching: Find Local Businesses With a Problem You Can Actually Fix — Under $4
Cold outreach fails because it is generic. Here is the exact flow — build a prospect universe, diagnose the real problem from their own reviews, qualify who can pay, find the human, and pitch with evidence — run entirely from Claude via MCP. No code.