# Gumtree UK Scraper — All Categories, Any Keyword (`reventadata/gumtree-scraper`) Actor

Scrapes any Gumtree UK category by keyword: cars, phones, property, furniture, jobs. Returns price, location, photos and seller type, plus year, mileage and fuel for vehicles, and battery health and storage parsed from the ad's own text. Optional filters drop damaged items and accessories.

- **URL**: https://apify.com/reventadata/gumtree-scraper.md
- **Developed by:** [reventadata](https://apify.com/reventadata) (community)
- **Categories:** E-commerce, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

> **Unofficial.** This Actor is not affiliated with, authorised by, endorsed by, or in any
> way officially connected to Gumtree. "Gumtree" is a trademark of its respective owner and
> is used here only to describe what this tool reads. It reads the same public pages a
> browser does.

Gumtree Scraper searches **any category** on [Gumtree UK](https://www.gumtree.com) - cars,
phones, property, furniture, jobs - and returns priced listings with the structured vehicle
attributes Gumtree publishes, plus battery health, storage and damage wording parsed out of
the seller's own free text.

### What does it do?

Gumtree server-renders its search results, so this Actor reads the tiles directly rather
than driving a browser. It sends the price, category and location filters Gumtree itself
supports, then applies seller-type, battery-health, storage, damage and accessory filters
after fetching, which the site does not offer at all.

### Why use it?

- **Every category, one Actor.** Vehicle ads return year, mileage, engine size, fuel type
  and Private/Trade; phone ads return battery health and storage. Nothing is category-locked.
- **Built to stay up.** Gumtree blocks an unproxied client after about five pages; we route
  through Apify's residential proxy and measured it completing 25 of 25 sequential result
  pages with no block.
- **Battery health from free text.** UK sellers write it nine different ways — `BH85%`,
  "battery health is 78%", "88% battery", "Battery 83 per cent", even "%90". All of them
  are parsed into one integer field.
- **Cases and chargers filtered out.** A large minority of results for any phone search are
  accessories whose titles contain the search term. "Case for iPhone 12" is not an iPhone.
- **Paid placements are labelled.** Exactly five promoted ads occupy the first five slots
  of every page and ignore the sort order - measured on pages 1, 2, 3 and 5. `isFeatured`
  marks them, and `excludeFeatured` drops them (off by default, so they are returned).
- **Multi-variant shop ads are flagged, not mangled.** A phone shop advertising
  "64gb £70 256gb £80" is one tile with two units. Reporting it as a 64 GB phone at the
  tile price would be wrong, so `isMultiVariant` is set and `storageOptionsGb` lists both.
- **Honest sellers are not punished.** "no cracks", "no scratches whatsoever" and similar
  negations are understood, so advertising the absence of damage does not get an ad dropped.

### How to use it

1. Put one or more search terms in `searchQueries`. There is no default.
2. Optionally narrow with `category`, `location`, a price range, `sellerType`,
   `minBatteryHealth` or `minStorageGb`.
3. Run it. Results land in the dataset.

#### Input

| Field | Type | Meaning |
|---|---|---|
| `searchQueries` | array | One search per term. Required, no default. |
| `category` | string | Optional Gumtree category slug. Validated before the first request, so an unknown slug fails immediately. |
| `location` | string | Location slug, e.g. `uk`, `london`. Defaults to the whole UK. |
| `minPrice` / `maxPrice` | integer | Optional. Omitted from the request entirely when empty — never defaulted. |
| `sellerType` | string | `any` | `private` | `trade`. Applied after fetching. Only motoring categories publish it, so setting it elsewhere returns **zero** rows. |
| `minBatteryHealth` | integer | Rejects ads that never state a figure, not just ads below the floor. |
| `minStorageGb` | integer | TB is converted, so 1TB counts as 1024. Rejects ads that never state a capacity. |
| `excludeDamaged` | boolean | Drops ads admitting damage. Default on. |
| `excludeAccessories` | boolean | Drops cases, chargers, screen protectors and spare parts. Default on. |
| `excludeFeatured` | boolean | Drops paid promoted placements. Off by default. |
| `requireKeywordMatch` | boolean | Drop items whose title is missing **any word** of the query. Default on. |
| `sortBy` | string | `relevance` | `date` | `price_lowest_first` | `price_highest_first` | `distance`. |
| `maxItems` | integer | Hard cap on rows pushed. Default 1000. |
| `maxPages` | integer | Pages per term, clamped to the site's ceiling of 50. |
| `proxyConfiguration` | object | Residential by default. Gumtree blocks an unproxied client after about five pages. |

```json
{
    "searchQueries": ["iphone 13"],
    "category": "mobile-phones",
    "minBatteryHealth": 85,
    "minStorageGb": 128,
    "maxPrice": 400,
    "maxItems": 200
}
```

#### Output

```json
{
    "id": "1801736377",
    "title": "Apple iPhone 13 128GB Unlocked BH92%",
    "description": "Comes with box and charging cable. No scratches.",
    "url": "https://www.gumtree.com/p/apple/apple-iphone-13-128gb/1801736377",
    "imageUrl": "https://img.gumtree.com/…/86",
    "price": 300,
    "currency": "GBP",
    "location": "Enfield, London",
    "sellerType": null,
    "isFeatured": false,
    "photoCount": 4,
    "year": null,
    "mileage": null,
    "engineCc": null,
    "fuelType": null,
    "batteryHealth": 92,
    "storageGb": 128,
    "storageOptionsGb": [128],
    "isMultiVariant": false,
    "matchedKeyword": "iphone 13"
}
```

For motoring searches `year`, `mileage`, `engineCc`, `fuelType` and `sellerType` are filled
from Gumtree's own structured attributes and `batteryHealth` / `storageGb` are null.

### Notes and limits

- **50 pages per search, about 1,500 ads.** Gumtree's own ceiling, visible in its page
  title (`Page 5/50`), regardless of how many ads it says match. Narrow with `category`,
  `location` or a price range to get past it.
- **A proxy is not optional.** Gumtree challenges an unproxied client after about five
  pages. Measured over 25 sequential pages: no proxy 5, datacenter 22, residential 25.
- **Seller identity is never collected.** Gumtree's search tiles publish only Private or
  Trade, never a name, and this Actor does not fetch listing pages to find one.
- **Ad descriptions are reproduced exactly as the seller wrote them** and are not filtered.
  UK sellers frequently put a phone number or a WhatsApp handle in that free text. Handle
  the `description` field accordingly.
- **Battery health and storage are seller claims**, parsed from prose, not verified facts.
  `sellerType`, `year`, `mileage`, `engineCc` and `fuelType` come from Gumtree's own
  structured fields and are more reliable.
- **A run that returns zero rows fails rather than succeeding empty.** If every listing is
  filtered out - an over-strict `minBatteryHealth`, or `sellerType` on a non-motoring
  category - the Actor raises instead of writing an empty dataset, so a silent
  misconfiguration cannot look like "no stock today".
- **A keyword can end early without failing the run.** If Gumtree keeps challenging after
  three proxy rotations, that keyword stops and the run continues with what it has. The log
  records how many pages were challenged; check it before treating a small result set as
  the whole market.
- **`searchOptionsExactMatch` is not offered.** Gumtree's own UI emits it, but it changes
  nothing: measured on `iphone 13 mini`, results were identical with and without it - same
  26 tiles, same first listing, same total. No input is exposed for a control that does
  nothing.
- Reads the same public page a browser does. See the disclaimer at the top.

# Actor input Schema

## `searchQueries` (type: `array`):

One search per term. Required, with no default — the Actor never guesses what to look for.

## `category` (type: `string`):

Optional Gumtree category slug. Validated against the site's own list; an unknown slug fails the run rather than silently returning everything.

## `location` (type: `string`):

Gumtree location slug, e.g. uk, london, manchester. Defaults to the whole UK.

## `sortBy` (type: `string`):

Order only; changes nothing about which ads exist. Five paid Featured ads are pinned above the sorted results on every page.

## `minPrice` (type: `integer`):

Optional. Omitted from the request entirely when empty — never defaulted to 0, which would drop every free listing.

## `maxPrice` (type: `integer`):

Optional. Omitted from the request entirely when empty.

## `sellerType` (type: `string`):

Private or Trade. Applied after fetching, not by Gumtree. Only motoring categories publish it, so setting this on any other category returns ZERO rows, not an unfiltered list.

## `minBatteryHealth` (type: `integer`):

Parsed from the ad's own wording, including the UK shorthand 'BH85%'. Rejects ads that never state a figure, not just ads below the floor.

## `minStorageGb` (type: `integer`):

Parsed from title and description; TB is converted, so 1TB counts as 1024. Rejects ads that never state a capacity, not just ads below the floor.

## `excludeDamaged` (type: `boolean`):

Drops ads admitting damage — cracked, faulty, spares or repairs, iCloud locked. Honours seller negations like 'no cracks'.

## `excludeAccessories` (type: `boolean`):

A large minority of results for any phone search are cases and chargers whose titles contain the search term.

## `excludeFeatured` (type: `boolean`):

Exactly five promoted ads sit at the top of every page and ignore the sort order (measured on pages 1, 2, 3 and 5). Off by default, so they are returned and labelled with isFeatured.

## `requireKeywordMatch` (type: `boolean`):

Drop items whose title is missing any word of the query. Gumtree search is fuzzy and returns loosely related ads.

## `maxItems` (type: `integer`):

Hard cap on rows pushed across all search terms.

## `maxPages` (type: `integer`):

Gumtree serves at most 50 pages per search, about 1,500 ads, and this is clamped to that ceiling.

## `proxyConfiguration` (type: `object`):

Gumtree blocks an unproxied client after about five pages. Residential completed 25 of 25 pages in testing where datacenter reached 22.

## Actor input object example

```json
{
  "searchQueries": [
    "iphone"
  ],
  "category": "",
  "location": "uk",
  "sortBy": "relevance",
  "sellerType": "any",
  "excludeDamaged": true,
  "excludeAccessories": true,
  "excludeFeatured": false,
  "requireKeywordMatch": true,
  "maxItems": 1000,
  "maxPages": 50,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `matches` (type: `string`):

Matching listings: price, location, battery health, storage, vehicle attributes, and a link to the ad.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searchQueries": [
        "iphone"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("reventadata/gumtree-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "searchQueries": ["iphone"] }

# Run the Actor and wait for it to finish
run = client.actor("reventadata/gumtree-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searchQueries": [
    "iphone"
  ]
}' |
apify call reventadata/gumtree-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,reventadata/gumtree-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/5gqOu5bEaHwvINWBl/builds/YcMM98lGoIjfhLqNk/openapi.json
