DEV Community

Cover image for How to Scrape Turo Car Rental Listings and Daily Prices with Python
ReventaData
ReventaData

Posted on Originally published at apify.com

How to Scrape Turo Car Rental Listings and Daily Prices with Python

By the end of this tutorial, you will have a short Python script that turns a Turo search around any map point into structured rows: year, make and model, vehicle type, average daily price with its currency, rating, completed trips, the host's Turo page and a Google Maps link to the approximate pickup spot.

We use Python and the Apify Actor Turo Car Rental Scraper (reventadata/turo-scraper), an unofficial tool that reads the same public search endpoint a browser does. You will set a search point, run the Actor, and filter rows by make, vehicle type and daily price. The row shown below is a sample from a capture dated 2026-10-12, parsed with the Actor's own parser.

⏱️ Time estimate: one sitting: install one package, set one token, run three short Python blocks.


Prerequisites

To follow this tutorial, you will need:

  1. Python 3.11+ (apify-client 3.3.0 requires it).
  2. The Apify API client for Python.
  3. An Apify account and its API token, which sits on the API & Integrations page in Apify Console.

Install the client:

pip install "apify-client~=3.0"
Enter fullscreen mode Exit fullscreen mode

The code reads the token from an environment variable:

export APIFY_API_TOKEN="your_apify_token_here"
Enter fullscreen mode Exit fullscreen mode

What you'll achieve

You will turn one Turo search into one row per car. Each row carries the vehicle id, url and title (2023 Ford Escape), make, model, year, vehicleType, dailyPrice and currency, rating, completedTrips, the isAllStarHost and isNewListing flags, hostId and hostUrl, city, state, latitude and longitude, distance from your search point, Turo's tag labels and the first photo.

Note: Turo's search results carry a host id, not a host name, so rows have no host name.

Example use case: Suppose you want SUVs near downtown Los Angeles under a daily budget. You would set the search point, vehicleTypes: ["SUV"] and maxPrice, and every saved row would be an SUV whose average daily price is at or below your bound.


Step 1: Access Turo Car Rental Scraper

Go to the Turo Car Rental Scraper page on Apify Store. The README lists every input and output field. In this tutorial we drive the Actor from Python, so everything after this step happens in code.


Step 2: Configure the search point

The two required inputs are latitude and longitude: the centre of the search. Turo searches the area around that point. For downtown Los Angeles that is 34.0549 and -118.2426, the Actor's prefill.

Here is the setup. It searches around that point and saves at most 10 cars:

import os
from apify_client import ApifyClient

# 1. Initialize ApifyClient with the token from the environment
client = ApifyClient(os.environ["APIFY_API_TOKEN"])

# 2. Define the search: one map point, at most 10 cars
run_input = {
    "latitude": 34.0549,
    "longitude": -118.2426,
    "maxItems": 10,
}
Enter fullscreen mode Exit fullscreen mode

💡 Tip: the Actor sends the point to Turo with the country set to US, so pick a point in the United States.

Each run makes one search request and keeps up to maxItems cars (default 100) from what Turo returns. There are no pages to walk, so a larger maxItems cannot add cars beyond that one response.


Step 3: Add optional filters

Parameter Type What it keeps
makes array Only these makes, e.g. ["Ford", "Tesla"]. Sent to Turo and re-checked on each car, ignoring case and accents.
vehicleTypes array Only these Turo types, e.g. ["SUV"]. Same handling as makes.
minPrice number Cars whose average daily price is at least this.
maxPrice number Cars whose average daily price is at most this.
maxItems integer Cap on saved rows (minimum 1, default 100).

When minPrice or maxPrice is set, a car without a daily price is dropped. Prices are compared in the currency Turo returns (USD in the capture).

Three example combinations:

  • LA SUVs: the prefill point plus vehicleTypes: ["SUV"].
  • Teslas only: makes: ["Tesla"], no price bound.
  • Budget band: minPrice: 30, maxPrice: 60.

⚠️ A missing latitude or longitude, or a negative price bound, is refused before the run starts. A latitude outside -90..90, a longitude outside -180..180, or minPrice above maxPrice stops the run with an error at the start.


Step 4: Run the Actor

Now start the run with client.actor("reventadata/turo-scraper").call(run_input=run_input). The call starts the Actor on Apify and waits for it to finish before returning the run:

# 3. Start the Actor run and wait for it to finish
print("Starting Turo extraction...")
run = client.actor("reventadata/turo-scraper").call(run_input=run_input)
if run is None or run.status != "SUCCEEDED":
    status = run.status if run else "unknown"
    raise SystemExit(f"Run did not succeed (status: {status}).")

# 4. Fetch the saved cars
items = client.dataset(run.default_dataset_id).list_items().items
print(f"Retrieved {len(items)} cars.\n")
Enter fullscreen mode Exit fullscreen mode

Behind the scenes:

  • The Actor builds Turo's search request for your point (an area search sorted by relevance, daily rentals) with your makes and types, and sends it through Bright Data's Web Unlocker, the route that reached Turo for the Actor's published example tasks on 2026-10-13.
  • It checks that the answer is a vehicles list; a body without one counts as a failed fetch, even when it came back with HTTP 200.
  • It applies your filters, skips a car id it has already saved, and stops at maxItems.

Step 5: View and download results

Loop over the rows to print the parts you need:

for item in items:
    print(item["title"], "-", item.get("vehicleType"))
    print(f"  Daily price: {item.get('dailyPrice')} {item.get('currency')}")
    print(f"  Rating: {item.get('rating')} over {item.get('completedTrips')} trips")
    print(f"  Where: {item.get('city')}, {item.get('state')}")
    print(f"  Map: {item.get('googleMapsUrl')}")
    print(f"  Listing: {item['url']}")
    print("-" * 40)
Enter fullscreen mode Exit fullscreen mode

Here is one row, a sample from a capture dated 2026-10-12 (a Los Angeles search) parsed with the Actor's own parser. A saved row also carries scrapedAt, added at save time:

{
    "id": "3905389",
    "title": "2023 Ford Escape",
    "url": "https://turo.com/us/en/vehicle/3905389",
    "make": "Ford",
    "model": "Escape",
    "year": 2023,
    "vehicleType": "SUV",
    "dailyPrice": 39.42,
    "currency": "USD",
    "rating": 5.0,
    "completedTrips": 6,
    "isAllStarHost": true,
    "isNewListing": false,
    "hostId": 42612460,
    "hostUrl": "https://turo.com/us/en/drivers/42612460",
    "seoCategory": "SUV",
    "city": "Los Angeles",
    "state": "CA",
    "country": "US",
    "latitude": 34.07593356671111,
    "longitude": -118.25088431942467,
    "distance": 1.5274172055900106,
    "distanceUnit": "MILES",
    "googleMapsUrl": "https://www.google.com/maps/search/?api=1&query=34.07593356671111,-118.25088431942467",
    "tags": [
        "Unlimited miles"
    ],
    "imageUrl": "https://images.turo.com/media/vehicle/images/9RjG2t7cSw-VjnV5orp5IA.jpg"
}
Enter fullscreen mode Exit fullscreen mode

Apify datasets export as JSON, CSV, XML, Excel, HTML table, RSS or JSONL.


Understanding your results

The fields that matter most:

  • dailyPrice and currency: Turo's own average daily price for the car, passed through as a number (39.42 USD in the sample). The Actor does not compute it.
  • rating and completedTrips: read them together. The sample car shows 5.0 over 6 trips.
  • isAllStarHost: Turo's All-Star Host badge. isNewListing: Turo's new-listing flag.
  • hostId and hostUrl: the host's public Turo page, turo.com/us/en/drivers/<id>. There is no host name.
  • latitude, longitude and googleMapsUrl: the car's approximate pickup point as Turo reports it, and a Google Maps link the Actor builds from those two numbers.
  • distance and distanceUnit: how far that point is from your search centre, in Turo's unit (MILES in the sample, about 1.5 miles).
  • vehicleType and seoCategory: Turo's type label and its category, both SUV in the sample.
  • tags: Turo's tag labels, such as Unlimited miles.
  • url and imageUrl: the car's page on turo.com and its first photo.

Pro tips

  1. Move the point, not the count. One run is one search around one point. To cover a whole metro, run several points (airport, downtown, suburbs) and merge the rows on id.
  2. Know where each filter runs. makes and vehicleTypes go into Turo's own search request and are re-checked on each car. Price bounds are applied only by the Actor, to each car after the response arrives.
  3. Use the type labels Turo uses. vehicleTypes matches Turo's label, ignoring case and accents. Check vehicleType in a first run before filtering on it.
  4. Track prices over time. Schedule the same input daily and keep id, dailyPrice and scrapedAt; the rows become a price history per car.
  5. Start small. Keep maxItems: 10 until your downstream code handles the fields, then raise it.
  6. Fetch every row of a large dataset. list_items() makes one API call; client.dataset(run.default_dataset_id).iterate_items() keeps calling until it has every item.

Troubleshooting

  • Run FAILED with zero rows: a response with no vehicles list ends FAILED; an over-strict filter that still returns cars ends SUCCEEDED with an empty dataset and a status message saying so. Widen the price band or check your makes spelling.
  • Run stops at once: the coordinates are out of range, or minPrice is above maxPrice.
  • Fewer rows than expected: read the log. It prints Search returned N vehicle(s)., one Rejected N: <reason> line per filter (make not requested, vehicle type not requested, no daily price, daily price below minPrice, daily price above maxPrice), then Pushed N row(s).. From Python: client.log(run.id).get().

  • Run waits to start: pass wait_for_resources=True to call() and the client retries the start while your account lacks memory or a free run slot.

For help, use the Actor's Store page.


Pricing and next steps

For current pricing and details, see the Store page.

To repeat a search, save the input as a task and add an Apify schedule, which runs an Actor or task on a cron expression (0 8 * * * is every day at 8 AM). To react when a run finishes, add a webhook on ACTOR.RUN.SUCCEEDED; its payload carries the run object, including defaultDatasetId.

Which cities or car types are you planning to track on Turo? Drop a comment! 💬

Top comments (0)