# PNet Scraper — South Africa Jobs & Salaries, No Phones (`highbrow_fame/pnet-jobs`) Actor

South African jobs from PNet: title, company, location, salary as text and as min/max/period, contract type, EE/AA, posted date and the full job ad. No recruiter names or phones.

- **URL**: https://apify.com/highbrow_fame/pnet-jobs.md
- **Developed by:** [yestrue](https://apify.com/highbrow_fame) (community)
- **Categories:** Jobs
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.90 / 1,000 job delivereds

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

## PNet Scraper — South Africa Jobs & Salaries, No Phones

Get South African job listings from PNet (pnet.co.za): job title, company, location, the salary as the employer wrote it plus the parsed minimum, maximum, currency and period, whether the job is partly or fully remote, the posting date, the date the ad runs until and a short snippet — and, if you want, the full ad as plain text, the contract type, full or part time, the candidate groups the employer names (EE/AA, Non EE/AA and others), industry and city. Paste your PNet search links — or just type a keyword and a place.

**Why this one**

- 🔗 **Your search, as you set it up.** Set every filter you like on pnet.co.za and paste the link. Or type keywords here — accountant, registered nurse, sales representative — with a place and a radius, and choose posted within, full or part time, contract types, experience, partially remote, Easy Apply and newest first below.
- ⚡ **Fast.** A live test run read four searches — accountants within 50 km of Johannesburg, every job in Cape Town from the last 7 days, full-time permanent engineering jobs newest first, permanent sales representative jobs around Durban from the last 14 days — 3,937 jobs in 136 seconds.
- 💰 **Salaries, parsed.** 1,204 of those 3,937 jobs showed a salary. "R 30 000 - R 35 000 per month", "R450 000 - R550 000 p.a.", "R25k - R30k CTC per month" become `salaryMin`, `salaryMax`, `salaryCurrency` and `salaryPeriod` (494 jobs); the rest mostly said "Market related" (494) or "Negotiable" (93).
- 🇿🇦 **EE/AA at a glance.** Tick **Open every job ad** and `candidateTypes` lists the groups the employer names: in a live run of 360 ads, 89 said EE/AA and 94 Non EE/AA.
- 📄 **Full ads when you need them.** The full ad text, the contract type (Permanent, Fixed Term, Temporary …), full or part time and industry. A live run read 360 jobs with their ads in 9 minutes.
- 🔒 **No recruiter names, no phone numbers.** Contact persons are not output, and phone numbers, e-mail addresses, WhatsApp links, recruiters' LinkedIn profiles and recruiters' names written into the ads are replaced with `[phone removed]`, `[e-mail removed]`, `[profile link removed]` and `[name removed]` (in a live run of 240 recruitment and call centre ads: 19 phone numbers, 27 e-mail addresses and 7 names).
- 💸 **You pay only for jobs delivered.** A link that is not a PNet search, or a place PNet does not know, comes back as a free record with the reason.

### What you get

For every job:

| Field | What it is |
|---|---|
| `jobId`, `url`, `title` | the job ad |
| `company`, `companyId`, `companyUrl`, `companyLogo` | the employer or recruitment agency |
| `location` | where the job is, as PNet shows it |
| `remote` | `partly` or `fully` when the ad says so, otherwise empty |
| `salary`, `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod` | the salary as written, and parsed (`year`, `month`, `week`, `day` or `hour`; currency and period only when the text names them) |
| `postedAt`, `validThrough` | when it was posted, and the date the ad runs until |
| `match`, `snippet` | `keyword` or `related` (related jobs PNet adds to some searches), and the start of the ad text |
| `input`, `position`, `scrapedAt`, `status`, `error` | which search it came from, and why a search gave nothing |

With **Open every job ad** also: `description` (the full ad as plain text), `contractType`, `workType` ("Full Time", "Part time", "Work from Home"), `candidateTypes` (\["EE/AA", "Non EE/AA"] …), `employmentType`, `industry`, `city`, `countryCode`.

#### Example

A real record from a live run with the ads opened — recruitment consultant jobs, 26 September 2026 (the description is cut here):

```json
{
  "jobId": 3799961,
  "url": "https://www.pnet.co.za/jobs--Internal-Recruitment-Consultant-Pretoria-Prosperitas-Personnel--3799961-inline.html",
  "title": "Internal Recruitment Consultant",
  "company": "Prosperitas Personnel",
  "companyId": 13396,
  "companyUrl": "https://www.pnet.co.za/cmp/en/prosperitas-personnel-13396/jobs",
  "location": "Pretoria",
  "remote": null,
  "salary": "R 30 000 - R 35 000 per month",
  "salaryMin": 30000,
  "salaryMax": 35000,
  "salaryCurrency": "ZAR",
  "salaryPeriod": "month",
  "postedAt": "2026-09-23T10:48:24+02:00",
  "validThrough": "2026-10-23T08:48:24.397Z",
  "match": "keyword",
  "snippet": "-Reporting: Tracking and analyzing recruitment metrics and data - Minimum of 4 years experience in recruitment.",
  "contractType": "Permanent",
  "workType": "Full Time",
  "candidateTypes": ["EE/AA", "Non EE/AA"],
  "employmentType": "FULL_TIME",
  "industry": "Human Resources, Human Resources-Recruitment",
  "city": "Wierdapark, Centurion",
  "countryCode": "ZA",
  "description": "Introduction\nOur client is a leader within the agriculture industry. They are looking for a driven and presentable individual to join their team. …",
  "status": "OK"
}
```

### How to use it

1. Paste **Search links** from pnet.co.za, one per line — or type **Keywords** and a **Place** and choose the filters.
2. Set **Max jobs per search**, and tick **Open every job ad** if you need the full ads.
3. Press **Start**, and download the results from the **Output** tab as JSON, CSV or Excel.

To follow new jobs, save your input as a task with **Posted within (days)** set to 1 and schedule it daily.

You can also call it from the Apify API, from Make, Zapier or n8n, or from an AI agent through the Apify MCP server.

**Speed.** PNet turns away visitors who ask too fast (in our tests, about ten minutes of "Access Denied" after 35 job pages in 19 seconds), so the run paces itself: search pages go quickly (25 jobs each), while opened job ads go one every 1.5 seconds — about 40 a minute. In the live runs above not one request was refused.

### Pricing

You are charged **per job delivered** — see the price on this page. Opening the job ads costs nothing extra, only time. Links that are not PNet searches, and places PNet does not know, are free.

### FAQ

**How many jobs per search?** As many as PNet lists: the run reads page after page, to the last one (all of South Africa was 15,793 jobs on 632 pages). In the live run the engineering search stopped at the 1,500 jobs set as the maximum, of 3,363. Set **Max jobs per search** as high as you need.

**What are "related" jobs?** For some keywords PNet adds related jobs after the ones that match and counts them in its result number (for "C# developer": 88 matches and 784 related). They come with `match: "related"`; untick **Include related jobs** to leave them out.

**Why are some jobs outside the place I typed?** PNet searches a radius around the place (30 km unless you set **Radius**), so in the live run an accountant search within 50 km of Johannesburg also brought jobs in Centurion (91) and Midrand (66). PNet also lists some jobs abroad under South African places (a Cape Town search held 50 jobs in Australia). The `location` field says where each job is.

**Why no recruiter names or phone numbers?** They are personal data. Every job has its `url`; you apply on PNet.

**Is this affiliated with PNet or StepStone?** No. This is an independent tool that reads public PNet job listings.

**Like it?** A short review on the Store page helps other people find this Actor. Something missing or broken? Tell us on the Issues tab — we read every one.

# Actor input Schema

## `searchUrls` (type: `array`):

Set up a search on pnet.co.za with all the filters you want and paste the address of the results page. One per line.

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

Or search here without a link: job titles or keywords (accountant, registered nurse, java developer). One search per line; the place and filters below apply to each.

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

A city, town, suburb or province (Cape Town, Sandton, Gauteng). Empty: all of South Africa. With a place and no keywords, you get every job there.

## `radiusKm` (type: `integer`):

Optional: jobs this far around the place — 0, 5, 10, 20, 30, 40, 50, 75 or 100 km; other numbers round up. PNet's default is 30.

## `postedWithinDays` (type: `integer`):

Optional: only jobs from the last 1, 3, 7, 14 or 30 days; other numbers round up. 0: any date.

## `workType` (type: `string`):

Optional.

## `contractTypes` (type: `array`):

Optional: one or more. Empty: any.

## `experience` (type: `string`):

Optional.

## `remote` (type: `string`):

Optional: only jobs that can be done partly from home.

## `applyMethod` (type: `string`):

Optional: only jobs you apply for on PNet (Easy Apply), or only jobs you apply for on the company's own website.

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

For the keywords above.

## `includeRelatedJobs` (type: `boolean`):

PNet sometimes adds a few related jobs after the ones that match the keyword and counts them in its result number. Untick to keep only the jobs that match the keyword; the field match says which is which.

## `maxJobsPerSearch` (type: `integer`):

Stops each search after this many jobs.

## `includeDetails` (type: `boolean`):

Adds the full job ad as plain text, the contract type, full or part time, the candidate groups the employer names (EE/AA and others), industry, city, province and the date the ad runs until. One more page per job, so slower.

## `maxConcurrency` (type: `integer`):

How many searches to work on at the same time. PNet turns away visitors who ask too fast, so the run paces its requests either way.

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

The first try goes out directly; a retry goes through this proxy.

## Actor input object example

```json
{
  "searchUrls": [
    "https://www.pnet.co.za/jobs/accountant/in-johannesburg"
  ],
  "postedWithinDays": 0,
  "workType": "any",
  "experience": "any",
  "remote": "any",
  "applyMethod": "any",
  "sortBy": "relevance",
  "includeRelatedJobs": true,
  "maxJobsPerSearch": 30,
  "includeDetails": false,
  "maxConcurrency": 2,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

Every result as a JSON record, with a status for each input.

## `resultsCsv` (type: `string`):

The same records as CSV, for a spreadsheet.

# 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 = {
    "searchUrls": [
        "https://www.pnet.co.za/jobs/accountant/in-johannesburg"
    ],
    "maxJobsPerSearch": 30,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("highbrow_fame/pnet-jobs").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 = {
    "searchUrls": ["https://www.pnet.co.za/jobs/accountant/in-johannesburg"],
    "maxJobsPerSearch": 30,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("highbrow_fame/pnet-jobs").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 '{
  "searchUrls": [
    "https://www.pnet.co.za/jobs/accountant/in-johannesburg"
  ],
  "maxJobsPerSearch": 30,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call highbrow_fame/pnet-jobs --silent --output-dataset

```

## MCP server setup

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

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/AK0b2uSriV0uCqvdN/builds/1e7hvN25bJbkeghej/openapi.json
