DEV Community

Cover image for How to Scrape Gumtree UK Car and Product Listings with Python
ReventaData
ReventaData

Posted on Edited on Originally published at apify.com

How to Scrape Gumtree UK Car and Product Listings with Python

By the end of this tutorial, you will have a working Python script that extracts Gumtree UK classified listings into structured JSON: listing title, asking price in GBP, location, vehicle attributes (year, mileage, engine size, fuel type, seller type), and search-card text (empty in this run's 200 car listings).

Gumtree lists second-hand vehicles, electronics, properties, and jobs across the UK. Scraping it manually is tedious, while unproxied requests were blocked after about five pages in August 2026 testing.

In this guide, we use Python and the Apify Actor Gumtree Scraper (reventadata/gumtree-scraper) with Apify residential proxies. Our benchmark run (configured with searchQueries: ["bmw"], category: "cars", maxPrice: 5000, and maxItems: 200) took 141.0 seconds to extract 200 vehicle listings into a clean dataset.

⏱️ Time estimate: About 30 minutes.


Prerequisites

To follow this tutorial, you will need:

  1. Python 3.11+ installed locally (apify-client 3.x requires it).
  2. The Apify API client for Python (pip install "apify-client~=3.0").
  3. An Apify account and API token.

Sign up for an Apify account. Copy your API token from API & Integrations in Apify Console. Install the client:

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

The code loads the token from an environment variable:

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

What you'll achieve

By completing this guide, you will extract live Gumtree UK classified listings into structured records: listing title, asking price in GBP, location, vehicle specs (year, mileage, engine displacement, fuel type), seller classification (Private vs Trade), photo count, and canonical URLs.

Note on Data Boundaries: This Actor extracts data directly from Gumtree UK search result listing tiles, including search-card text (empty in this run's 200 car listings) and parsed attributes. It operates on search result pages and respects site pagination boundaries.

Example use case: Suppose you are tracking the UK second-hand vehicle market to identify private sellers offering cars at £5,000 or less. Configuring category: "cars", maxPrice: 5000, and sellerType: "private" returns an automated dataset with vehicle year, mileage, engine displacement, and fuel type.


Step 1: Access Gumtree Scraper

Navigate to the Gumtree Scraper page on Apify Store.

You can inspect the documentation and review available input parameters. In this tutorial, we use Python to automate the process programmatically.


Step 2: Configure search parameters

The Actor accepts keyword queries via searchQueries, and supports narrowing by category slug (such as cars, phones, property-to-rent, or jobs) and UK location slugs (such as uk, london, or manchester).

When targeting cars, for instance, you specify "category": "cars" and provide your search terms in searchQueries. Gumtree server-renders search listings with about 30 listing tiles per page (up to about 1,500 ads per search across the 50-page site ceiling). Price limits (minPrice and maxPrice) go to Gumtree in the search URL, while filters like sellerType and damage exclusions are applied inside the Actor after fetching.

In our Python script, we initialize the Apify client and define our parameters in run_input:

import os
from apify_client import ApifyClient

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

# 2. Define the extraction parameters
run_input = {
    "searchQueries": ["bmw"],
    "category": "cars",
    "maxPrice": 5000,
    "maxItems": 20,
}
Enter fullscreen mode Exit fullscreen mode

(Note: This demonstration snippet uses maxItems: 20 for a fast initial test. The benchmark run cited in this guide ran with searchQueries: ["bmw"], category: "cars", maxPrice: 5000, and maxItems: 200.)


Step 3: Configure optional settings and filters

The Actor provides filtering options: minPrice and maxPrice go to Gumtree in the search URL, while item filters (sellerType, excludeDamaged, excludeAccessories, requireKeywordMatch) are applied inside the Actor after fetching. Filtered-out rows never reach your dataset:

Parameter Type Default Description
searchQueries array Required List of search keywords. Required, with no default.
category string "" Optional category slug (e.g., "cars", "phones", "for-sale", "property-to-rent", "jobs"). One of 11 fixed values.
location string "uk" Gumtree location slug (e.g., "uk", "london", "manchester"). Defaults to the entire UK.
minPrice & maxPrice integer None Price boundaries in GBP. Omitted from request when empty.
sellerType string "any" Private or Trade. Applied inside the Actor after fetching; on non-motoring categories it returns zero rows.
minBatteryHealth integer None Minimum battery percentage parsed from ad text. Drops ads without stated health.
minStorageGb integer None Minimum device storage capacity in GB.
excludeDamaged boolean true Drops ads admitting damage (cracked, faulty, spares or repairs, iCloud locked). Honours seller negations like "no cracks".
excludeAccessories boolean true Drops phone cases, screen protectors, and chargers.
excludeFeatured boolean false Drops paid promoted placements pinned at the top of pages.
requireKeywordMatch boolean true Ensures every search word is present in the listing title.
sortBy string "relevance" Sort order: "relevance", "date", "price_lowest_first", "price_highest_first", "distance".
maxItems integer 1000 Hard cap on total dataset records pushed before terminating.
maxPages integer 50 Maximum page depth per term, clamped to the site ceiling of 50 (about 1,500 ads).

Practical filtering recommendations:

  • maxItems acts as a budget ceiling: Setting maxItems: 50 guarantees the crawler stops immediately once 50 items have been stored.
  • sellerType filtering: Setting sellerType: "private" retains only private sellers and drops Trade listings in vehicle categories.
  • Motoring attribute support: When searching "cars", listings fill year, mileage, engineCc, fuelType, and sellerType when the listing card states them.
  • Attribute parsing: batteryHealth and storageGb are parsed with regex directly from search-card text.

Step 4: Run the Actor

With the configuration ready, trigger the run using client.actor("reventadata/gumtree-scraper").call(run_input=run_input). This command starts the Actor in Apify's cloud environment, streams log events, and waits until execution completes:

# 3. Start the Actor run and wait for it to complete
print("Starting Gumtree UK extraction...")
run = client.actor("reventadata/gumtree-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 resulting dataset items
dataset = client.dataset(run.default_dataset_id).list_items().items

print(f"Extraction complete! Retrieved {len(dataset)} listings.\n")
Enter fullscreen mode Exit fullscreen mode

Behind the scenes:

  • The Actor launches in a container using Apify residential proxies by default: residential completed 25 of 25 pages and datacenter 22 of 25 in August 2026 testing (unproxied requests were blocked after about five pages).
  • It parses listing cards server-rendered on the search result pages, extracts prices in GBP, captures vehicle specifications and seller types, and writes clean records to the Apify dataset.
  • In our live benchmark run, configured with searchQueries: ["bmw"], category: "cars", maxPrice: 5000, and maxItems: 200, the Actor collected 200 vehicle listings.

Step 5: View and download results

Once the run completes, iterate over dataset to inspect and format each record in Python, or export the data as JSON, CSV or Excel:

for item in dataset:
    title = item.get("title")
    price = item.get("price")
    location = item.get("location")
    year = item.get("year")
    mileage = item.get("mileage")
    seller_type = item.get("sellerType")

    print(f"Listing: {title}")
    if price is None:
        print("  Price: unpriced")
    else:
        print(f"  Price: £{price}")
    print(f"  Location: {location}")
    if year:
        miles = f"{mileage} miles" if mileage is not None else "mileage not stated"
        print(f"  Vehicle: {year} ({miles})")
    if seller_type:
        print(f"  Seller: {seller_type}")
    print("-" * 50)
Enter fullscreen mode Exit fullscreen mode

Here is a verbatim sample record extracted during our benchmark run:

{
    "id": "1802688843",
    "title": "BMW, X1, Estate, 2011, Manual, 1995 (cc), 5 doors",
    "description": "",
    "url": "https://www.gumtree.com/p/bmw/bmw-x1-estate-2011-manual-1995-cc-5-doors/1802688843",
    "imageUrl": "https://img.gumtree.com/ePR8PyKf84wPHx7_RYmEag/dc7ed13c2c2a4a2993_52814dfe9ce4/86",
    "price": 2250,
    "currency": "GBP",
    "location": "Ballyclare, County Antrim",
    "sellerType": "Private",
    "isFeatured": true,
    "photoCount": 19,
    "year": 2011,
    "mileage": 153948,
    "engineCc": 1995,
    "fuelType": "Diesel",
    "batteryHealth": null,
    "storageGb": null,
    "storageOptionsGb": [],
    "isMultiVariant": false,
    "matchedKeyword": "bmw"
}
Enter fullscreen mode Exit fullscreen mode

Understanding your results

Each record follows the Actor's dataset schema:

  • id: Gumtree listing identifier string.
  • title: Ad title published by the seller.
  • description: Search-card text, empty in this run's 200 car listings.
  • url: Canonical listing URL.
  • imageUrl: First thumbnail URL, or null without photos.
  • price: Asking price in GBP, or null when unpriced.
  • currency: Always "GBP".
  • location: Town and county shown on the tile.
  • sellerType: "Private" or "Trade" (motoring categories only).
  • isFeatured: Paid promoted ad (Featured, Urgent or Spotlight); see Pro tip 2.
  • photoCount: Number of photos on the ad.
  • year: Vehicle year. Motoring categories only.
  • mileage: Vehicle mileage in miles. Motoring categories only.
  • engineCc: Engine size in cc. Motoring categories only.
  • fuelType: Fuel type ("Diesel", "Petrol", or "Hybrid Electric" seen in this run). Motoring categories only.
  • batteryHealth: Battery percentage parsed from text, or null.
  • storageGb: Storage in GB for single-capacity ads, or null.
  • storageOptionsGb: Array of capacities mentioned in ad copy.
  • isMultiVariant: True when one ad sells several capacities at several prices, so the tile price belongs to one unstated variant.
  • matchedKeyword: Search keyword that matched this ad.

Pro tips

  1. Ceiling awareness on deep pagination: Gumtree serves at most 50 pages per search (about 30 per page, capping results at about 1,500 ads per search). For large queries yielding thousands of ads, segment your sweep by specific location slugs like "london" or narrower price brackets.
  2. Promoted placements handling: Gumtree pins five promoted ads at the top of each page (measured on pages 1, 2, 3 and 5), regardless of sorting; the Actor flags Featured, Urgent and Spotlight labels as promoted. Set excludeFeatured: true if you wish to drop these promoted placements.
  3. Private versus Trade filtering: When auditing used vehicles, set sellerType: "private" to exclude Trade sellers, as Gumtree labels them, and focus exclusively on private sales. The Actor applies this filter after fetching, dropping Trade listings before dataset storage.
  4. Keyword match enforcement: requireKeywordMatch is enabled by default to drop irrelevant search matches where listing titles omit any word from your query.
  5. Start small with item caps: When developing automation pipelines, begin with maxItems: 20 to verify data mappings before running larger sweeps.

Troubleshooting

  • Empty dataset / Run marked FAILED: The Actor deliberately fails if zero rows are saved, preventing silent downstream failures in production pipelines. If this happens, verify your search term spelling and ensure your price or location filters are not overly restrictive.
  • Seller type returning null: Remember that sellerType is only published by Gumtree for motoring categories (such as "cars"). Non-motoring categories do not publish seller classifications on listing cards.
  • Handling invalid categories: category accepts one of 11 fixed slugs; for any other value the platform refuses the input before the run starts.
  • Reading the log: the last summary lines are Rejected N: reason (only when something was rejected) and Pushed N row(s).; read it from Python with client.log(run.id).get(). For help, use the Actor's Store page (https://apify.com/reventadata/gumtree-scraper).

Pricing and Next Steps

For current pricing and details, see the Store page.

To repeat a search automatically, save it as a task and add an Apify schedule, which starts an Actor or task on a cron expression.

Links:

What categories or vehicle deals are you planning to track on Gumtree UK? Let us know in the comments below!

Top comments (0)