# CWJobs Scraper — UK Tech Jobs, Salaries & Postcodes (`yugenox/cwjobs-jobs-scraper`) Actor

Scrape CWJobs (cwjobs.co.uk) UK IT and tech jobs by keyword and location: title, company, location with postcode, salary text plus parsed range and period, posted and closing dates, description snippet, skills and labels. Permanent/contract/work-from-home filters. No login.

- **URL**: https://apify.com/yugenox/cwjobs-jobs-scraper.md
- **Developed by:** [Yugenox Corp](https://apify.com/yugenox) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 jobs

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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## CWJobs Scraper: UK tech jobs, salaries and postcodes

Scrape **CWJobs** (cwjobs.co.uk), the UK job board for IT and tech roles, by keyword and location. Every job comes with the **title, company, location with postcode, the salary as shown plus a parsed min/max/period, posted and closing dates, a description snippet, skills, labels** (Premium, Featured, New) and the job link. Search permanent, contract or work-from-home jobs, filter by date posted, sort by newest. No login, no cookies, no CWJobs account.

### What you get

| | |
|---|---|
| **Job** | Title, job URL, job ID, posted date, listing end date, labels (PREMIUM, FEATURED, NEW), sponsored flag |
| **Company** | Company or agency name, CWJobs company ID, logo, the company's job list on CWJobs (empty for confidential employers) |
| **Location** | Location as shown (often with the postcode district, e.g. "Central London (WC1)"), full postcode when the employer gives one |
| **Salary** | The salary as shown ("£45000 - £55000 per annum", "£60 - 65 per hour + Outside IR35", "Competitive") plus parsed **min, max, currency and period** |
| **Description** | The opening of the description shown in the results (about 300 characters) |
| **Source** | Whether the job was posted on CWJobs or cross-posted from its sister site Totaljobs |

### How to use it

1. Enter one or more **keywords**, e.g. `data engineer`, `devops`, `c#`.
2. Optionally enter a **location**: a UK town, city, county or region such as `London`, `Manchester` or `Scotland`. Leave it empty to search the whole UK.
3. Set **Max jobs** and any filters, then run.

Each keyword is searched in each location (and each job type you pick). **Max jobs** is the total for the run, shared fairly between your searches.

#### How many jobs per search?

CWJobs' robots.txt asks crawlers to read **only the first results page (25 jobs) of a search**, and this scraper follows it. To get more than 25 jobs for a keyword, keep **Search nearby places for more results** on (the default): the scraper then runs the same search in the places CWJobs itself suggests for it. For a London search those are areas such as the City, Canary Wharf or West London; for a UK-wide search they're the biggest job markets, such as the South East, Manchester or Scotland. It stops at **Max jobs** and removes duplicates. You can also add more keywords, locations or job types.

#### Input examples

Data engineering jobs in London and Manchester, newest first:

```json
{
  "keywords": ["data engineer"],
  "location": "London",
  "locations": ["Manchester"],
  "sortBy": "date",
  "maxItems": 150
}
```

Contract DevOps roles posted in the last 3 days, UK-wide:

```json
{
  "keywords": ["devops", "platform engineer"],
  "contractType": ["contract"],
  "postedWithin": "3",
  "maxItems": 200
}
```

Work-from-home Python jobs:

```json
{
  "keywords": ["python"],
  "workFromHome": true,
  "maxItems": 50
}
```

You can also paste CWJobs search URLs, e.g. `https://www.cwjobs.co.uk/jobs/contract/devops/in-leeds`, into **CWJobs search URLs**.

### Output example

```json
{
  "site": "cwjobs",
  "jobId": "107988457",
  "title": "Software Engineer",
  "company": "Morson Edge",
  "companyId": "1448981",
  "companyLogoUrl": "https://tjgliveassets.s3.eu-west-1.amazonaws.com/company-logos/8457063eca9a4f25a0cc289998816542.png",
  "companyUrl": "https://www.cwjobs.co.uk/jobs/morson-edge?cmpId=1448981&cmp=1",
  "location": "Fawdon, Newcastle Upon Tyne (NE3)",
  "postcode": "NE3 3AF",
  "country": "United Kingdom",
  "salaryText": "£70 per hour",
  "salaryMin": 70,
  "salaryMax": 70,
  "salaryCurrency": "GBP",
  "salaryPeriod": "hour",
  "postedDate": "2026-09-16T07:36:11.997Z",
  "expiresDate": "2026-10-28T08:36:11.997Z",
  "snippet": "Java Software Engineer – Active SC\n£70 per hour Umbrella | Inside IR35 | 12 Month Contract | Newcastle upon Tyne (4 days per week onsite, 1 remote working) | …",
  "skills": [],
  "labels": ["PREMIUM"],
  "sourceSite": "Totaljobs.com",
  "sponsored": false,
  "url": "https://www.totaljobs.com/job/software-engineer/morson-edge-job107988457",
  "searchKeyword": "software engineer",
  "searchLocation": null,
  "searchArea": null,
  "position": 1,
  "scrapedAt": "2026-10-01T22:30:00.000Z"
}
```

Every row has the same columns; a value the job doesn't state is `null`. `searchArea` names the nearby place a job was found in when the search was split by place.

### Use cases

- **Tech recruiting and agency leads**: see which companies and agencies are hiring for a stack, where, and at what rate.
- **Contractor rate tracking**: day and hourly rates for contract roles by skill and city.
- **Salary benchmarking**: permanent salary ranges by title and location.
- **Job alerts**: schedule a daily run with **Date posted: Last 24 hours**.

### FAQ

**Do I need a CWJobs account, cookies or a login?** No. The scraper reads the same public search pages any visitor sees.

**Why at most 25 jobs per search page, and no full descriptions?** The scraper follows CWJobs' robots.txt, which asks crawlers not to page through results. It also reads search pages only, not individual job pages, so each job comes with the snippet CWJobs shows in the results (about 300 characters). The job link opens the full advert.

**Why do job links point to totaljobs.com?** CWJobs and Totaljobs belong to the same group and share listings. Many jobs on CWJobs were posted on Totaljobs, and CWJobs links to them there. `sourceSite` tells you where each job came from.

**Can I combine filters?** You can combine job types (permanent, contract, work from home) with keywords and locations. CWJobs' robots.txt allows only one other filter per search page, so **Date posted** wins over **Sort by** and **Radius**; the run log says when one was dropped. Work-from-home jobs are searched UK-wide, because robots.txt doesn't allow combining them with a location.

**Why was a location skipped?** If CWJobs doesn't recognise a place, the scraper skips that search, doesn't charge for it, and says so in the run's status message. Use a UK town, city, county or region.

**Which proxy should I use?** Keep the default (Apify Proxy, datacenter). It comes with every Apify plan, including the free one. If CWJobs refuses datacenter IPs for a while, the run switches to residential proxies when your account has them, and ends cleanly with a plain message if CWJobs keeps refusing.

**What does a run cost?** You pay per job saved, per search, and a small fee per run. See the Pricing tab. Extra nearby-place pages aren't charged as searches, and jobs found twice are saved and charged once.

**Is it legal to scrape CWJobs?** This Actor only collects publicly available data: job listings that anyone can see on CWJobs without an account, such as the job title, employer, location, salary and a description snippet. Collecting publicly available data is generally legal, but you're responsible for how you use it. Job listings describe roles, not people; the scraper removes personal e-mail addresses and phone numbers from snippets, and leaves out confidential employers' names. You must follow privacy laws such as UK GDPR, GDPR, PIPEDA and CCPA, as well as CWJobs' terms. If you're unsure, check with a lawyer. More on this: [Is web scraping legal?](https://blog.apify.com/is-web-scraping-legal/)

**Does it access any private data?** No. Everything comes from search pages CWJobs shows to any visitor without logging in. It never uses a login, never touches candidate profiles, CVs or applications, and follows CWJobs' robots.txt.

This Actor is not affiliated with, endorsed by or sponsored by CWJobs, Totaljobs or StepStone. All trademarks belong to their owners.

# Actor input Schema

## `keywords` (type: `array`):

Job titles or skills to search on CWJobs, e.g. "data engineer", "devops", "c#". Each keyword is searched separately (and in each location and job type).

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

A UK town, city, county or region, e.g. "London", "Manchester", "Scotland". Leave empty to search the whole UK. A place CWJobs doesn't recognise is skipped (and not charged).

## `locations` (type: `array`):

Run the same keywords in several places, e.g. "Leeds", "Bristol".

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

Maximum number of jobs for the whole run, shared fairly between your searches. 0 = as many as the searches can reach.

## `splitByLocation` (type: `boolean`):

CWJobs' robots.txt lets crawlers read only the first page (25 jobs) of a search. With this on, a search with more results is also run in the places CWJobs itself suggests for it (the areas of your location, or the UK's biggest job markets for a UK-wide search), until Max jobs is reached. Duplicates are removed.

## `startUrls` (type: `array`):

Optional: CWJobs search URLs, e.g. https://www.cwjobs.co.uk/jobs/contract/devops/in-leeds. The first page is read; at most one filter in the URL is kept (robots.txt). Job pages are not read.

## `contractType` (type: `array`):

Permanent and/or contract jobs (each is a separate search). Empty = all.

## `workFromHome` (type: `boolean`):

Also search CWJobs' work-from-home jobs (UK-wide: robots.txt does not allow combining it with a location).

## `postedWithin` (type: `string`):

Only jobs posted in this period. CWJobs' robots.txt allows one filter per search: date posted wins over sort order and radius.

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

Relevance (CWJobs' default) or newest first. Ignored when "Date posted" is set (one filter per search).

## `radiusMiles` (type: `integer`):

How far from the location to search (CWJobs' default is 20 miles). Ignored when "Date posted" or "Newest first" is set.

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

Apify Proxy (datacenter) is the default and comes with every Apify plan. If CWJobs refuses datacenter IPs for a while, the run switches to residential proxies when your account has them.

## Actor input object example

```json
{
  "keywords": [
    "data engineer"
  ],
  "location": "London",
  "maxItems": 20,
  "splitByLocation": true,
  "workFromHome": false,
  "postedWithin": "any",
  "sortBy": "relevance",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (type: `string`):

All scraped jobs.

## `run` (type: `string`):

Status and statistics for this run.

## `report` (type: `string`):

RUN_REPORT: how the run ended, counts, requests and proxy use.

# 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 = {
    "keywords": [
        "data engineer"
    ],
    "location": "London",
    "maxItems": 20,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("yugenox/cwjobs-jobs-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 = {
    "keywords": ["data engineer"],
    "location": "London",
    "maxItems": 20,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("yugenox/cwjobs-jobs-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 '{
  "keywords": [
    "data engineer"
  ],
  "location": "London",
  "maxItems": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call yugenox/cwjobs-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,yugenox/cwjobs-jobs-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/GlOFcP6Zdkk1r9QQk/builds/6byTVc0tXUDKuN8kw/openapi.json
