# JobStreet Scraper — JobStreet & JobsDB Jobs, Salary, Companies (`logiover/jobstreet-scraper`) Actor

JobStreet & JobsDB scraper: export jobs from Malaysia, Singapore, Philippines, Indonesia, Hong Kong and Thailand with full description, parsed salary, work type, classification, applicant count and company profile (industry, size, website, address). Search, filter or export whole sites. No login.

- **URL**: https://apify.com/logiover/jobstreet-scraper.md
- **Developed by:** [Logiover](https://apify.com/logiover) (community)
- **Categories:** Jobs, Lead generation, 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.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## JobStreet Scraper

**Export jobs from JobStreet and JobsDB — Malaysia, Singapore, the Philippines, Indonesia, Hong Kong and Thailand — as clean, analysis-ready data: full descriptions, parsed salaries, work type and arrangement, applicant counts, application questions and the hiring company's profile (industry, size, website, address, registration number, rating).**

**JobStreet Scraper** is a fast **JobStreet API alternative** and **JobsDB scraper** in one Actor. It lets you **scrape my.jobstreet.com, sg.jobstreet.com, ph.jobstreet.com, id.jobstreet.com, hk.jobsdb.com and th.jobsdb.com** without a login, without cookies and without a browser. Search by keyword and location on one site or all six at once, filter by classification, sub-classification, work type, work arrangement (on-site, hybrid, remote), posting date or pay, follow one company, paste any JobStreet or JobsDB search URL — or **export every job a site lists** (all 20,583 jobs on JobsDB Thailand in 116 seconds, measured on 2026-10-20). It is the simplest way to build a **Southeast Asia jobs dataset**: a **Malaysia jobs scraper** for *jawatan kosong* and *kerja kosong*, an **Indonesia jobs scraper** for *lowongan kerja* (loker), a **Philippines jobs scraper** for *trabaho* and hiring posts, a **Singapore jobs scraper**, a **Hong Kong jobs scraper** for 搵工 / 職位空缺 listings and a **Thailand jobs scraper** for หางาน listings — **export to CSV, Excel or JSON**, push to Google Sheets or pull through the Apify API.

Every row is one complete job record: JobStreet / JobsDB job id and link, title, the **company and the advertiser**, location (as the job page shows it, split into locality and state), country and site, **classification and sub-classification** (SEEK's 30 classifications and 391 sub-classifications), **work type** (full time, part time, contract/temp, casual), **work arrangement**, the **salary as shown plus salaryMin, salaryMax, currency and period** parsed from it, posting and expiry dates, teaser and bullet points, the **full job description** (plain text and the original HTML), the **screening questions** the employer asks, the **applicant count**, e-mail addresses and phone numbers written in the ad, and the **company profile**: industry, size, website, office address, specialities, description, star rating, review count, recommend rate, perks and — on the Malaysian and Singapore sites — the company **registration number** and employment-agency licence. It is built for SEA and Hong Kong recruiters and staffing agencies, sales teams that sell to hiring companies, job aggregators, salary-benchmarking and labour-market analysts, HR-tech products and AI pipelines.

> Independent tool. Not affiliated with, endorsed by, or sponsored by JobStreet, JobsDB or SEEK Limited. "JobStreet", "JobsDB" and "SEEK" are trademarks of their owners and are used here only to describe the websites this Actor reads.

***

### Table of contents

1. [At a glance](#at-a-glance)
2. [What you get](#what-you-get)
3. [Who uses it — and how](#who-uses-it--and-how)
4. [How it works](#how-it-works)
5. [Cookbook: ready-to-run inputs](#cookbook-ready-to-run-inputs)
6. [Tutorial: your first run in the Apify Console](#tutorial-your-first-run-in-the-apify-console)
7. [Tutorial: run it through the API](#tutorial-run-it-through-the-api)
8. [Input reference](#input-reference)
9. [Output data dictionary](#output-data-dictionary)
10. [Output example](#output-example)
11. [Scale, speed and coverage](#scale-speed-and-coverage)
12. [What you pay](#what-you-pay)
13. [Integrations](#integrations)
14. [Performance tips](#performance-tips)
15. [Troubleshooting](#troubleshooting)
16. [Limits](#limits)
17. [Compared with other ways to get the data](#compared-with-other-ways-to-get-the-data)
18. [FAQ](#faq)
19. [Glossary](#glossary)
20. [Legal and data protection](#legal-and-data-protection)
21. [Changelog](#changelog)
22. [Support](#support)

***

### At a glance

| | |
|---|---|
| **Sources** | JobStreet Malaysia (my.jobstreet.com), JobStreet Singapore (sg.jobstreet.com), JobStreet Philippines (ph.jobstreet.com), JobStreet Indonesia (id.jobstreet.com), JobsDB Hong Kong (hk.jobsdb.com), JobsDB Thailand (th.jobsdb.com) |
| **Size of the six sites** | 309,082 open jobs on 2026-10-20: Philippines 75,794 · Singapore 67,433 · Indonesia 60,126 · Malaysia 54,162 · Hong Kong 30,953 · Thailand 20,614 |
| **Fields per job** | 70 columns: job, site and location, classification, work type and arrangement, salary (as shown + parsed), dates, description, questions, applicants, contacts in the ad, advertiser, company profile |
| **Search modes** | Keywords × locations on one or several sites, all six sites at once, whole-site export, companies, pasted search URLs, single job URLs |
| **Filters** | Classifications (30), sub-classifications (391), work types, work arrangements, posted within N days, salary range (monthly / annual / hourly), relevance or newest first |
| **Speed (measured 2026-10-20)** | First rows about 2 seconds after the run starts; 100 jobs from all six sites in 7 s; 1,000 jobs with full details in 20 s; all 20,583 jobs of JobsDB Thailand in 116 s |
| **De-duplication** | By job id across every site and search of a run — 0 duplicates in the 20,583-job run |
| **Output** | Apify dataset → JSON, CSV, Excel, XML, RSS, HTML table; API; integrations |
| **Login / cookies / browser** | None needed |
| **Price** | Fixed price per job saved, from $1.00 per 1,000 jobs — compute and proxies included |

***

### What you get

Fields are grouped below with real values from runs on 2026-10-20. The full list with types and measured fill rates is in the [data dictionary](#output-data-dictionary).

**The job**

- `id` `95072951`, `url` `https://my.jobstreet.com/job/95072951`, `title` "Production/ QC Engineer", `normalisedTitle` "Quality Control Engineer" (the site's own clean role name), `roleId` "quality-control-engineer".
- `teaser` (the one-line summary on the search card), `bulletPoints` (the three selling points the employer adds, e.g. "Free parking"), `description` (full text with paragraphs and • list items kept) and `descriptionHtml` (the original markup).
- `postedAt` `2026-10-06T01:16:08.000Z` (exact listing time, ISO 8601 UTC), `postedAgo` "14d ago" (as the site shows it), `expiresAt` `2026-11-05T12:59:59.999Z`.
- `status` "Active", `isExpired` false, `tags` such as "Urgently hiring", "Be an early applicant", "Expiring soon", `isFeatured`.

**Where and what kind**

- `location` "Shah Alam/Subang, Selangor" — the label the job page shows — with `locality` "Shah Alam/Subang", `state` "Selangor" and, on the Hong Kong, Thai and Philippine sites, `region` ("Kowloon", "Bangkok Metropolitan Region", "Calabarzon").
- `country` "Malaysia", `countryCode` "MY", `site` "JobStreet Malaysia", `siteDomain` "my.jobstreet.com".
- `classification` "Manufacturing, Transport & Logistics" (`classificationId` 6092), `subClassification` "Quality Assurance & Control" (`subClassificationId` 6106).
- `workType` "Full time" / "Part time" / "Contract/Temp" / "Casual/Vacation"; `workArrangement` "On-site" / "Hybrid" / "Remote".

**Pay**

- `salary` exactly as the ad shows it: "RM 3,850 – RM 4,130 per month", "₱25,000 – ₱30,000 per month", "Rp 6.000.000 – Rp 7.000.000 per month", "$55k - $65k p.m. + negotiable", "Up to PHP60K + Day 1 HMO, Weekends Off".
- Parsed from it: `salaryMin` 3850, `salaryMax` 4130, `salaryCurrency` "MYR" (ISO code — MYR, SGD, PHP, IDR, HKD, THB, or USD / AUD / SGD when an ad pays in another currency), `salaryPeriod` "month" / "year" / "hour" / "day" / "week".

**Applying**

- `applicationQuestions` — the screening questions the employer set, e.g. "What's your expected monthly basic salary?", "Which of the following statements best describes your right to work in Malaysia?".
- `applicantCount` 1567 with `applicantCountLabel` "More than 150 applicants" and `applicationVolume` "High application volume".
- `applyOnCompanySite` true when the Apply button leads to the employer's own careers site.
- `contactEmails` and `contactPhones` — addresses and numbers written in the ad text (e.g. `hr@recruit-expert.com`, `02-966-2192`), in full even where the page masks them.

**The hiring company**

- `companyName` "S.E.H. Malaysia" (the name on the ad), `advertiserName` "S.E.H. (Shah Alam) Sdn Bhd" (the account that posted it — often the legal entity or the recruitment agency), `advertiserId`, `isVerifiedAdvertiser`, `advertiserSince` (the account's registration date: 1997 here), `isConfidential` (the employer chose to stay anonymous).
- `companyId`, `companyProfileUrl` `https://my.jobstreet.com/companies/seh-malaysia-168558677326160`, `companyJobsUrl` (all its open jobs), `companyLogoUrl`, `coverImageUrl`, `videoUrl`.
- Profile: `companyIndustry` "Manufacturing, Transport & Logistics", `companySize` "101-1,000 employees", `companyWebsite` `http://www.sehmy.com`, `companyAddress` "S.E.H. (Shah Alam) Sdn Bhd, Jalan Sementa 27/91, Taman Bunga Negara, Shah Alam Selangor", `companySpecialities`, `companyDescription`, `companyPerks` \["Medical", "Dental", "Parking", …].
- Reviews: `companyRating` 4, `companyReviewCount` 7, `companyRecommendPercent` 86, `companySalaryRatingPercent` 85.
- Identifiers: `companyRegistrationNumber` "266571-M" (Malaysia's SSM / Singapore's UEN, shown on most Malaysian and Singapore ads), `employmentAgencyLicence` "14C7121" (Singapore MOM licence of recruitment agencies).

**Run context**

- `searchTerm` and `searchLocation` that found the job, `scrapedAt` timestamp.

***

### Who uses it — and how

#### Recruitment and staffing agencies in Malaysia and Singapore

Run your core roles ("accountant", "engineer", "sales executive") every morning with **Posted within = Last 24 hours** on JobStreet Malaysia and Singapore. Sort by `companyName`, drop rows where `employmentAgencyLicence` is set (other agencies), and call the direct employers the same day — the `companyWebsite`, `companyAddress` and `companyRegistrationNumber` columns save the research step.

#### Executive search firms in Hong Kong

Export JobsDB Hong Kong for "director", "head of", "general manager" and filter `salaryMin` ≥ 60,000 with `salaryPeriod` = month. The `applicantCount` column shows which searches the employer is struggling with — roles with a low count after two weeks are the ones that take a retained search.

#### B2B sales teams selling to companies that are hiring

A company that posts ten new jobs this month is buying: HR software, payroll, training, office space, recruitment services. Export all six sites, group by `companyName` and `companySize`, and feed the top movers to your CRM with industry, website and address already filled in.

#### Job boards and aggregators

Pull every job from the six sites into your own board with the full HTML description (`descriptionHtml`), logo, salary fields and the original job URL. One whole-site run per day plus a 24-hour delta keeps an index fresh; the job id is stable, so updates are easy to merge.

#### Labour-market researchers and economists

Count vacancies by `classification`, `subClassification`, `state` and `workType` for each country on the same day. Because all six sites share one classification tree, Malaysia, Indonesia and Thailand compare directly — no taxonomy mapping needed.

#### Salary benchmarking and compensation consultants

`salaryMin`, `salaryMax`, `salaryCurrency` and `salaryPeriod` are parsed from the label the site shows, in the local currency (RM, S$, ₱, Rp, HK$, ฿). Filter to `salaryPeriod` = month, group by `normalisedTitle` and `state`, and you have a live pay table for any role.

#### HR teams watching competitors

Follow your competitors with the **Companies** field ("Shopee", "Grab", "DKSH"): every open role, how long it has been up (`postedAt`), how many people applied, which perks they advertise and which screening questions they ask.

#### Remote-work and hybrid-work researchers

Set **Work arrangements** to Remote and Hybrid on all six sites: the share of hybrid ads per classification and country is one export away.

#### Relocation, visa and immigration consultancies

`applicationQuestions` reveals right-to-work requirements ("Which of the following statements best describes your right to work in Singapore?"); the description often mentions visa support. Filter jobs that ask about relocation or sponsorship and build a list of employers that hire from abroad.

#### HR-tech, ATS and job-matching products

Use the classification ids, the normalised title, the parsed salary and the screening questions as training and matching data. The questions are a ready-made library of what employers in each role actually ask.

#### Universities, polytechnics and bootcamps

Track "fresh graduate", "intern" and "management trainee" postings by classification and state to show students where demand is and which skills the descriptions ask for.

#### AI, RAG and LLM pipelines

Every description comes as clean text with paragraphs and list items kept, plus the HTML. Load the dataset into a vector store for a job-search assistant, or summarise demand for a skill across six countries.

#### Journalists and data reporters

Answer "how many jobs are open in Penang semiconductors?" or "which Hong Kong districts hire the most?" with a dated export you can cite.

#### Franchise, retail and F\&B operators

See which competitor outlets hire (location + company), what part-timers earn (`salaryPeriod` = hour) and how fast roles fill.

#### Investors and market-intelligence teams

Hiring is a leading signal: a weekly export of one company's or one sector's jobs shows expansion into new cities and functions early.

#### Government and training agencies

Map vacancies by sub-classification and region against training places; weekly runs give a vacancy index.

***

### How it works

1. **Search.** For each site (and each search term × location, company or pasted URL) the Actor calls the same search API the JobStreet and JobsDB web app uses, 99 jobs per page. A whole site is read by paging to the end: there is no depth limit (page 765 of the Philippine site answers). Page size 99 is deliberate: with 100 the site's pages overlap on searches without a keyword and 3–7 % of the jobs are never shown (measured on JobsDB Thailand).
2. **Details.** Each job's full record — description, expiry date, questions, applicant count, contacts written in the ad, company profile — comes from the site's own job-detail service, 20 jobs per request.
3. **Routes.** The sites allow each internet address a limited number of detail records a minute. The Actor paces its own connection under that limit and sends the rest through Apify's proxies, switching to fresh addresses when one has used its allowance. You do not need to configure anything; proxy costs are included in the price.
4. **Second sweep.** A list of tens of thousands of jobs changes while it is read (new jobs arrive, old ones expire), which can shift a job across a page boundary. When a run reads every job of a search, it re-reads the pages once at the end and picks up what moved — 249 jobs (1.2 %) in the 20,583-job JobsDB Thailand run.
5. **De-duplication.** A job id is unique across all six sites; every job is saved once, even if several searches find it. A location typed on several sites (e.g. "Kuala Lumpur" with Malaysia and Singapore selected) is read once, through the site of the country it belongs to.
6. **Stopping cleanly.** The run stops at **Max results**, at the run's time limit or at your **Maximum cost per run**, writes a `SUMMARY` record and a status message that says why.

***

### Cookbook: ready-to-run inputs

Copy any of these into the **JSON** tab of the input form (or send it to the API).

#### 1. A keyword on one site

```json
{
  "searchTerms": ["accountant"],
  "sites": ["MY"],
  "maxItems": 500
}
```

#### 2. The same keyword on all six sites

```json
{
  "searchTerms": ["data analyst"],
  "sites": ["MY", "SG", "PH", "ID", "HK", "TH"],
  "maxItems": 3000
}
```

With **Max results** set, every site gets a fair share first; unused shares go to the sites with more jobs.

#### 3. Keywords × cities

```json
{
  "searchTerms": ["software engineer", "devops"],
  "locations": ["Kuala Lumpur", "Penang"],
  "sites": ["MY"],
  "maxItems": 2000
}
```

#### 4. Every job in one country (whole-site export)

```json
{
  "sites": ["TH"],
  "maxItems": 0
}
```

`maxItems: 0` means no limit. Measured on 2026-10-20: 20,583 jobs in 116 s.

#### 5. Every job on all six sites

```json
{
  "sites": ["MY", "SG", "PH", "ID", "HK", "TH"],
  "maxItems": 0
}
```

About 309,000 jobs. Set **Maximum cost per run** high enough (see [What you pay](#what-you-pay)).

#### 6. Today's new jobs (daily monitoring)

```json
{
  "sites": ["SG", "HK"],
  "postedWithin": "1",
  "sortBy": "newest",
  "maxItems": 0
}
```

Schedule it every morning; `postedAt` tells you the exact listing time.

#### 7. One classification, one work type

```json
{
  "sites": ["PH"],
  "classifications": ["6281"],
  "workTypes": ["contract-temp"],
  "maxItems": 1000
}
```

6281 = Information & Communication Technology.

#### 8. A sub-classification by name

```json
{
  "sites": ["ID"],
  "subClassifications": ["Developers/Programmers"],
  "maxItems": 0
}
```

#### 9. Remote and hybrid jobs only

```json
{
  "sites": ["MY", "SG", "PH"],
  "workArrangements": ["remote", "hybrid"],
  "maxItems": 2000
}
```

#### 10. Jobs paying at least RM 8,000 a month

```json
{
  "searchTerms": ["manager"],
  "sites": ["MY"],
  "salaryMin": 8000,
  "salaryType": "monthly",
  "maxItems": 1000
}
```

The sites' pay filter also matches ads whose pay is set but hidden — those rows come without `salaryMin`.

#### 11. One company's open jobs

```json
{
  "companies": ["DKSH", "https://my.jobstreet.com/companies/elite-material-penang-173803483978583"],
  "sites": ["MY", "SG", "TH"],
  "maxItems": 0
}
```

A name is matched as the ads write it; a company page or "jobs at this company" URL is exact; a number is taken as an advertiser id.

#### 12. Paste search pages from the sites

```json
{
  "startUrls": [
    { "url": "https://my.jobstreet.com/engineer-jobs/in-Kuala-Lumpur?daterange=7" },
    { "url": "https://hk.jobsdb.com/jobs-in-accounting/full-time" },
    { "url": "https://th.jobsdb.com/th/jobs/in-Bangkok?sortmode=ListedDate" }
  ],
  "maxItems": 3000
}
```

#### 13. Enrich a list of job links

```json
{
  "startUrls": [
    { "url": "https://sg.jobstreet.com/job/95338971" },
    { "url": "https://my.jobstreet.com/job/95072951" }
  ]
}
```

Expired jobs still return their record with `isExpired: true`.

#### 14. Fastest bulk export (search cards only)

```json
{
  "sites": ["PH"],
  "includeDetails": false,
  "maxItems": 0
}
```

Title, company, location, classification, work type, arrangement, salary, teaser, bullet points and dates — without the description and company profile.

#### 15. Fresh-graduate and internship roles

```json
{
  "searchTerms": ["fresh graduate", "internship", "management trainee"],
  "sites": ["MY", "SG", "ID"],
  "postedWithin": "14",
  "maxItems": 3000
}
```

#### 16. Exact phrase

```json
{
  "searchTerms": ["\"data engineer\""],
  "sites": ["SG"],
  "maxItems": 500
}
```

Quotes match the phrase exactly (60 jobs instead of 2,562 for the loose search on JobStreet Malaysia on 2026-10-20).

***

### Tutorial: your first run in the Apify Console

1. Open the Actor and click **Try for free** (or **Start** if it is already in your account).
2. In **Search terms**, type one or more job titles, e.g. `accountant`.
3. In **Sites (countries)**, keep all six or pick the ones you need — JobStreet Malaysia, Singapore, Philippines, Indonesia, JobsDB Hong Kong, Thailand.
4. Optional: add **Locations**, **Classifications**, **Work types**, **Work arrangements**, **Posted within**, a salary range or **Companies**.
5. Set **Max results** (100 for a first look; 0 for everything).
6. Click **Start**. Rows appear in the **Output** tab within seconds.
7. Use the views — **Overview**, **Salaries**, **Hiring companies**, **Job details** — or click **Export** for CSV, Excel, JSON, XML or HTML.

***

### Tutorial: run it through the API

Start a run and wait for it (replace `<TOKEN>` with your Apify API token):

```bash
curl -X POST "https://api.apify.com/v2/acts/logiover~jobstreet-scraper/run-sync-get-dataset-items?token=<TOKEN>&format=json" \
  -H "Content-Type: application/json" \
  -d '{"searchTerms":["accountant"],"sites":["MY","SG"],"maxItems":200}'
```

For long runs, start asynchronously and download when it finishes:

```bash
## start
curl -X POST "https://api.apify.com/v2/acts/logiover~jobstreet-scraper/runs?token=<TOKEN>" \
  -H "Content-Type: application/json" -d '{"sites":["TH"],"maxItems":0}'
## poll the run until status is SUCCEEDED, then download all rows as CSV
curl "https://api.apify.com/v2/datasets/<DATASET_ID>/items?format=csv&token=<TOKEN>" -o jobs.csv
```

**Node.js** (apify-client):

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('logiover/jobstreet-scraper').call({
    searchTerms: ['software engineer'], sites: ['SG', 'MY'], postedWithin: '7', maxItems: 1000,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.length, items[0].title, items[0].salaryMin, items[0].companyName);
```

**Python** (apify-client):

```python
from apify_client import ApifyClient
client = ApifyClient("<TOKEN>")
run = client.actor("logiover/jobstreet-scraper").call(run_input={
    "searchTerms": ["nurse"], "sites": ["PH", "SG"], "maxItems": 500,
})
for job in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(job["title"], job["companyName"], job["salary"], job["url"])
```

***

### Input reference

| Field | Type | Default | What it does |
|---|---|---|---|
| `searchTerms` | array of strings | `[]` | Keywords, job titles or skills. Each term is one search on every chosen site. Quotes = exact phrase. Empty (with no locations) = every job on the chosen sites. |
| `sites` | array | `[]` = all six | `MY` JobStreet Malaysia, `SG` JobStreet Singapore, `PH` JobStreet Philippines, `ID` JobStreet Indonesia, `HK` JobsDB Hong Kong, `TH` JobsDB Thailand. |
| `locations` | array of strings | `[]` | City, state or district as the sites know it ("Kuala Lumpur", "Selangor", "Central Region", "Makati City", "Jakarta Selatan", "Kowloon", "Bangkok"). Combined with every search term. Unknown locations return 0 jobs — they are not widened. |
| `classifications` | array | `[]` | Any of SEEK's 30 classifications (ids such as 6281 = Information & Communication Technology; names work too). |
| `subClassifications` | array of strings | `[]` | Any of 391 sub-classifications by exact name ("Developers/Programmers") or id. |
| `workTypes` | array | `[]` | `full-time`, `part-time`, `contract-temp`, `casual-vacation`. |
| `workArrangements` | array | `[]` | `on-site`, `hybrid`, `remote`. |
| `postedWithin` | string | `any` | `1`, `3`, `7`, `14`, `31` days (any whole number works through the API). |
| `salaryMin` / `salaryMax` | integer | — | The sites' pay filter, in the site's currency. |
| `salaryType` | string | `monthly` | Period of the pay filter: `monthly`, `annual`, `hourly`. |
| `companies` | array of strings | `[]` | Company names as the ads show them, company page URLs, "jobs at this company" URLs or advertiser ids. |
| `startUrls` | array | `[]` | JobStreet / JobsDB search pages, job pages, company pages. Old domains (www.jobstreet.com.my, www.jobsdb.com) and language prefixes (/th/, /id/) are understood. If you give only URLs, only the URLs are scraped. |
| `sortBy` | string | `relevance` | `relevance` or `newest`. |
| `maxItems` | integer | `1000` | Stop after this many unique jobs across everything. `0` = no limit. |
| `includeDetails` | boolean | `true` | Read each job's full record (description, questions, applicants, contacts, company profile). Off = search cards only. |
| `proxyConfiguration` | object | Apify Proxy | Fallback only — the Actor connects directly first and uses Apify's proxies when the sites ask it to slow down. |

***

### Output data dictionary

Fill rates were measured on 1,000 jobs from all six sites (search term "engineer", 2026-10-20). Fields that depend on the employer (salary, bullet points, company profile) vary by country — see the [coverage table](#coverage-by-site).

| Field | Type | Meaning | Fill | Example |
|---|---|---|---|---|
| `id` | string | Job id, unique across all six sites | 100 % | `95072951` |
| `url` | string | Job page on the site of the job's country | 100 % | `https://my.jobstreet.com/job/95072951` |
| `title` | string | Job title as posted | 100 % | `Production/ QC Engineer` |
| `companyName` | string | Company name on the ad (the advertiser for confidential ads) | 100 % | `S.E.H. Malaysia` |
| `location` | string | Location label as the job page shows it | 100 % | `Shah Alam/Subang, Selangor` |
| `locality` | string | First part of a multi-part location | 68.4 % | `Shah Alam/Subang` |
| `state` | string | Broadest part of the location (state, district, region) | 98.1 % | `Selangor` |
| `region` | string | SEEK's region (Hong Kong, Thailand, Philippines) | 47.7 % | `Kowloon` |
| `country` / `countryCode` | string | Country of the job | 100 % | `Malaysia` / `MY` |
| `site` / `siteDomain` | string | Site the job belongs to | 100 % | `JobStreet Malaysia` / `my.jobstreet.com` |
| `classification` / `classificationId` | string | SEEK classification | 100 % | `Manufacturing, Transport & Logistics` / `6092` |
| `subClassification` / `subClassificationId` | string | SEEK sub-classification | 100 % | `Quality Assurance & Control` / `6106` |
| `workType` | string | Full time, Part time, Contract/Temp, Casual/Vacation | 100 % | `Full time` |
| `workArrangement` | string | On-site, Hybrid, Remote | 100 % | `On-site` |
| `salary` | string | Pay exactly as shown | 34.4 % | `RM 3,850 – RM 4,130 per month` |
| `salaryMin` / `salaryMax` | number | Parsed amounts (null for free text such as "Negotiable") | 33.8 % / 34.0 % | `3850` / `4130` |
| `salaryCurrency` | string | ISO currency | 34.0 % | `MYR` |
| `salaryPeriod` | string | hour, day, week, month, year | 33.7 % | `month` |
| `postedAt` | string (ISO) | Listing time, UTC | 100 % | `2026-10-06T01:16:08.000Z` |
| `postedAgo` | string | Age as the site shows it | 100 % | `14d ago` |
| `expiresAt` | string (ISO) | Closing time of the ad | 100 % | `2026-11-05T12:59:59.999Z` |
| `teaser` | string | One-line summary from the card | 100 % | `Responsibilities: Implement test plan …` |
| `bulletPoints` | array | Up to three selling points | 50.4 % | `["Free parking", …]` |
| `description` | string | Full description, plain text | 100 % | `Responsibilities:\n• Implement test plan …` |
| `descriptionHtml` | string | Full description, original HTML | 100 % | `<p><strong>Responsibilities:</strong></p><ul>…` |
| `applicationQuestions` | array | Employer's screening questions | 85.8 % | `["What's your expected monthly basic salary?", …]` |
| `applicantCount` | number | Applicants so far | 89.2 % | `1567` |
| `applicantCountLabel` | string | Count as the site words it | 89.2 % | `More than 150 applicants` |
| `applicationVolume` | string | Low / Medium / High application volume | 89.2 % | `High application volume` |
| `applyOnCompanySite` | boolean | Apply button leads to the employer's site | 100 % | `false` |
| `contactEmails` | array | E-mail addresses written in the ad | 3.8 % | `["hr@recruit-expert.com"]` |
| `contactPhones` | array | Phone numbers written in the ad | 3.9 % | `["02-966-2192"]` |
| `tags` | array | Card badges | 23.6 % | `["Urgently hiring"]` |
| `isFeatured` | boolean | Featured placement | 100 % | `false` |
| `isExpired` | boolean | Ad has expired (job URLs can be old) | 100 % | `false` |
| `status` | string | Ad status | 100 % | `Active` |
| `advertiserName` | string | Account that posted the ad | 100 % | `S.E.H. (Shah Alam) Sdn Bhd` |
| `advertiserId` | string | Advertiser id (usable in **Companies**) | 100 % | `60601680` |
| `isVerifiedAdvertiser` | boolean | Verified advertiser | 93.6 % | `true` |
| `advertiserSince` | string (ISO) | Advertiser account registered | 100 % | `1997-10-24T05:58:03.923Z` |
| `isConfidential` | boolean | Employer hidden ("private advertiser") | 100 % | `false` |
| `companyId` | string | Company profile id | 64.0 % | `168558677326160` |
| `companyProfileUrl` | string | Company page | 64.0 % | `https://my.jobstreet.com/companies/seh-malaysia-168558677326160` |
| `companyJobsUrl` | string | All the company's open jobs | 97.4 % | `https://my.jobstreet.com/S.E.H.-Malaysia-jobs/at-this-company` |
| `companyLogoUrl` | string | Logo | 84.9 % | `https://bx-branding-gateway.cloud.seek.com.au/…/jdpLogo` |
| `companyIndustry` | string | Industry from the profile | 63.4 % | `Manufacturing, Transport & Logistics` |
| `companySize` | string | Employee band | 61.7 % | `101-1,000 employees` |
| `companyWebsite` | string | Company website | 44.8 % | `http://www.sehmy.com` |
| `companyAddress` | string | Primary office address | 60.6 % | `… Jalan Sementa 27/91, Taman Bunga Negara, Shah Alam Selangor` |
| `companySpecialities` | string | Specialities | 30.9 % | `Swimming pool, Spa, Pump, Filter` |
| `companyDescription` | string | Company overview | 61.3 % | `More than half a century has passed …` |
| `companyRating` | number | Average review rating (1–5) | 32.0 % | `4` |
| `companyReviewCount` | number | Number of reviews | 32.0 % | `7` |
| `companyRecommendPercent` | number | % of reviewers who recommend | 32.0 % | `86` |
| `companySalaryRatingPercent` | number | % rating pay high or average | 32.0 % | `85` |
| `companyPerks` | array | Perks and benefits | 38.5 % | `["Medical", "Dental", "Parking"]` |
| `companyRegistrationNumber` | string | Company registration no. (MY, SG) | 32.5 % | `266571-M` |
| `employmentAgencyLicence` | string | SG employment agency licence | 2.9 % | `14C7121` |
| `coverImageUrl` | string | Cover image of branded ads | 60.3 % | `https://image-service-cdn.seek.com.au/…` |
| `videoUrl` | string | Video in the ad | 8.2 % | `https://www.youtube.com/embed/NkA3JbAwrW0` |
| `normalisedTitle` | string | The site's normalised role name | 99.6 % | `Quality Control Engineer` |
| `roleId` | string | Role slug | 99.6 % | `quality-control-engineer` |
| `searchTerm` / `searchLocation` | string | What found the job | — | `engineer` / `null` |
| `scrapedAt` | string (ISO) | When the row was saved | 100 % | `2026-10-20T12:34:03.385Z` |

#### Coverage by site

Share of the same 1,000 jobs (about 167 per site) with each field filled — the sites differ in what employers publish:

| Site | Salary | Industry | Website | Registration no. | Applicants | Bullet points | Rating | Region |
|---|---|---|---|---|---|---|---|---|
| JobStreet Malaysia | 51 % | 66 % | 49 % | 89 % | 98 % | 71 % | 47 % | — |
| JobStreet Singapore | 42 % | 75 % | 53 % | 96 % | 81 % | 41 % | 53 % | — |
| JobStreet Philippines | 43 % | 62 % | 40 % | 1 % | 89 % | 26 % | 48 % | 100 % |
| JobStreet Indonesia | 37 % | 65 % | 46 % | 4 % | 99 % | 48 % | 40 % | — |
| JobsDB Hong Kong | 11 % | 56 % | 33 % | 1 % | 79 % | 65 % | 1 % | 87 % |
| JobsDB Thailand | 22 % | 58 % | 47 % | 3 % | 89 % | 53 % | 4 % | 100 % |

***

### Output example

One complete row, unedited, from a run on 2026-10-20 (search term "engineer", all six sites):

```json
{
  "id": "95072951",
  "url": "https://my.jobstreet.com/job/95072951",
  "title": "Production/ QC Engineer",
  "companyName": "S.E.H. Malaysia",
  "location": "Shah Alam/Subang, Selangor",
  "locality": "Shah Alam/Subang",
  "state": "Selangor",
  "region": null,
  "country": "Malaysia",
  "countryCode": "MY",
  "site": "JobStreet Malaysia",
  "siteDomain": "my.jobstreet.com",
  "classification": "Manufacturing, Transport & Logistics",
  "classificationId": "6092",
  "subClassification": "Quality Assurance & Control",
  "subClassificationId": "6106",
  "workType": "Full time",
  "workArrangement": "On-site",
  "salary": "RM 3,850 – RM 4,130 per month",
  "salaryMin": 3850,
  "salaryMax": 4130,
  "salaryCurrency": "MYR",
  "salaryPeriod": "month",
  "postedAt": "2026-10-06T01:16:08.000Z",
  "postedAgo": "14d ago",
  "expiresAt": "2026-11-05T12:59:59.999Z",
  "teaser": "Responsibilities: Implement test plan (arrange and coordinate with the relevant departments for the use of necessary facilities or machines)....",
  "bulletPoints": [
    "Medical Benefit, LRT Station (10 Minutes), Attendance Allowance,",
    "In-house cafeteria, uniform provided",
    "Free parking"
  ],
  "description": "Responsibilities:\n• Implement test plan (arrange and coordinate with the relevant departments for the use of necessary facilities or machines).\n• Compare actual performance with the objectives and standards\n• Determine the gap.\n• Take the necessary actions to close the gap and make the necessary improvements.\n• Adhere to all quality, safety and environmental standards of the Company\n\nRequirements:\n• Candidates must possess at least a Bachelor's Degree, Post Graduate Diploma, Professional Degree in Chemistry, Engineering (Chemical), Mechanical Engineering, Material Science/Engineering, Physics, Chemical Engineering Analytical, Troubleshooting, Data Analytical Skills or equivalent.\n• Required language(s): Bahasa Malaysia, English\n• No work experience required. Training will be provided\n• Those with work / training experiences in any industry / manufacturing environment will have an advantage\n• Good interpersonal skill and team player.\n• Priority with semiconductor experiences.\n• Willing to hand-on work.\n• Basic computer skills, statistical analysis\n• Simple basic analysis & troubleshooting skills.\n• Applicants must be willing to work in Section 27, Shah Alam @ Ulu Klang.\n• Full-Time position(s) available.",
  "descriptionHtml": "<p><strong>Responsibilities:</strong></p><ul><li><p>Implement test plan (arrange and coordinate with the relevant departments for the use of necessary facilities or machines).</p></li><li><p>Compare actual performance with the objectives and standards</p></li><li><p>Determine the gap.</p></li><li><p>Take the necessary actions to close the gap and make the necessary improvements.</p></li><li><p>Adhere to all quality, safety and environmental standards of the Company</p></li></ul><p><br></p><p><strong>Requirements:</strong></p><ul><li><p>Candidates must possess at least a Bachelor's Degree, Post Graduate Diploma, Professional Degree in Chemistry, Engineering (Chemical), Mechanical Engineering, Material Science/Engineering, Physics, Chemical Engineering Analytical, Troubleshooting, Data Analytical Skills or equivalent.</p></li><li><p>Required language(s): Bahasa Malaysia, English</p></li><li><p>No work experience required. Training will be provided</p></li><li><p>Those with work / training experiences in any industry / manufacturing environment will have an advantage</p></li><li><p>Good interpersonal skill and team player.</p></li><li><p>Priority with semiconductor experiences.</p></li><li><p>Willing to hand-on work.</p></li><li><p>Basic computer skills, statistical analysis</p></li><li><p>Simple basic analysis &amp; troubleshooting skills.</p></li><li><p>Applicants must be willing to work in Section 27, Shah Alam @ Ulu Klang.</p></li><li><p>Full-Time position(s) available.</p></li></ul><p><br></p>",
  "applicationQuestions": [
    "Which of the following statements best describes your right to work in Malaysia?",
    "What's your expected monthly basic salary?",
    "How many years' experience do you have as a Quality Control Engineer?"
  ],
  "applicantCount": 1567,
  "applicantCountLabel": "More than 150 applicants",
  "applicationVolume": "High application volume",
  "applyOnCompanySite": false,
  "contactEmails": null,
  "contactPhones": null,
  "tags": null,
  "isFeatured": false,
  "isExpired": false,
  "status": "Active",
  "advertiserName": "S.E.H. (Shah Alam) Sdn Bhd",
  "advertiserId": "60601680",
  "isVerifiedAdvertiser": true,
  "advertiserSince": "1997-10-24T05:58:03.923Z",
  "isConfidential": false,
  "companyId": "168558677326160",
  "companyProfileUrl": "https://my.jobstreet.com/companies/seh-malaysia-168558677326160",
  "companyJobsUrl": "https://my.jobstreet.com/S.E.H.-Malaysia-jobs/at-this-company",
  "companyLogoUrl": null,
  "companyIndustry": "Manufacturing, Transport & Logistics",
  "companySize": "101-1,000 employees",
  "companyWebsite": "http://www.sehmy.com",
  "companyAddress": "S.E.H. (Shah Alam) Sdn Bhd, Jalan Sementa 27/91, Taman Bunga Negara, Shah Alam Selangor",
  "companySpecialities": null,
  "companyDescription": "More than half a century has passed since the invention of the transistor. The electronics industry led by innovations in microchip development has proven to be remarkable in terms of progress and technology.\n\nAs electronics make advances over each milestone, S.E.H. silicon wafers draw a parallel progress from small-scale micro usage to large-scale integrated circuits. S.E.H. silicon wafers have established worldwide recognition for their consistent high quality.\n\nWith headquarter and many manufacturing plants in Japan together with several manufacturing plants in the world, S.E.H. is able to maintain a constant and efficient distribution of its products to meet the exhilarating demand for high quality silicon wafers.\n\nToday, S.E.H. is building towards a new era of exploration based on silicon in anticipation of tomorrow's needs.\n\nS.E.H. Malaysia's plant was one of the earliest overseas plants established outside Japan in the early 70s located at the Ulu Klang Free Trade Zone in Selangor. It is still the major supplier of 150mm silicon wafers for export to overseas customers.\n\nS.E.H. Shah Alam, situated in Hicom Industrial Area in Shah Alam was set up in 1994 for the manufacturing of 200mm silicon wafers using the most advanced technology. This plant was officially opened on September 9, 1995 by our then Prime Minister, Dato Seri Dr Mahathir Mohamad. It is one of the major suppliers of 200mm silicon wafers to customers all over the world.",
  "companyRating": 4,
  "companyReviewCount": 7,
  "companyRecommendPercent": 86,
  "companySalaryRatingPercent": 85,
  "companyPerks": [
    "Medical",
    "Miscellaneous allowance",
    "Education support",
    "Dental",
    "Parking",
    "Air Conditioning, in-house cafeteria, in-house cli"
  ],
  "companyRegistrationNumber": "266571-M",
  "employmentAgencyLicence": null,
  "coverImageUrl": null,
  "videoUrl": null,
  "normalisedTitle": "Quality Control Engineer",
  "roleId": "quality-control-engineer",
  "searchTerm": "engineer",
  "searchLocation": null,
  "scrapedAt": "2026-10-20T12:34:03.385Z"
}
```

**CSV / Excel columns** follow the order of the [data dictionary](#output-data-dictionary) — 70 columns from `id` to `scrapedAt`. Lists become numbered columns in CSV and Excel (`bulletPoints/0`…`/2`, `applicationQuestions/0`…, `contactEmails/0`…, `contactPhones/0`…, `tags/0`…, `companyPerks/0`…); in JSON they stay arrays.

***

### Scale, speed and coverage

All numbers below were measured on the Apify platform on **2026-10-20** with the default 2 GB of memory.

| Run | Jobs saved | Time | Notes |
|---|---|---|---|
| Prefilled input ("accountant", all six sites, 100 jobs) | 100 | 7 s | First 20 rows about 2 s after the container started |
| Default settings, "engineer", all six sites | 1,000 | 20 s | Every row with its full detail record |
| Whole JobsDB Thailand | 20,583 | 116 s | 99.8 % of the 20,624 jobs the site listed during the run, 0 duplicates, every row with details |
| Whole JobStreet Malaysia (build 1.0.2) | 54,125 | 320 s | 99.9 % of the 54,172 jobs listed; the second sweep added 534 jobs that moved during the first |

**Hand check.** On 2026-10-20 eighteen jobs from all six sites were opened on the live job pages and compared field by field with the rows: 417 of 420 values matched. Two differences were one Philippine location shown in its long form on the page (the Actor now takes that label); the third was an e-mail address that the page masks until a visitor reveals it — the row has it in full.

**No depth limit, one page-size trap.** The search pages go all the way down (page 765 of the Philippine site answers). But a page size of 100 makes consecutive pages overlap on searches without a keyword — in three full sweeps of JobsDB Thailand 3–7 % of the jobs never appeared. The Actor reads 99 per page; the same sweep then returned 20,482 of the 20,484 jobs listed (99.99 %).

**Moving lists.** On big sites hundreds of jobs are added or removed while a run reads them. A run that asks for every job re-reads the pages once at the end — the second sweep picked up 249 jobs on JobsDB Thailand and 534 on JobStreet Malaysia that had shifted between pages.

#### Jobs per site

| Site | Jobs on 2026-10-20 | Brand |
|---|---|---|
| JobStreet Philippines — ph.jobstreet.com | 75,794 | JobStreet |
| JobStreet Singapore — sg.jobstreet.com | 67,433 | JobStreet |
| JobStreet Indonesia — id.jobstreet.com | 60,126 | JobStreet |
| JobStreet Malaysia — my.jobstreet.com | 54,162 | JobStreet |
| JobsDB Hong Kong — hk.jobsdb.com | 30,953 | JobsDB |
| JobsDB Thailand — th.jobsdb.com | 20,614 | JobsDB |
| **All six** | **309,082** | |

***

### What you pay

You pay a fixed price per job saved; compute, proxies and the detail requests are included.

| Apify plan | Price per 1,000 jobs |
|---|---|
| Free | $1.50 |
| Bronze | $1.33 |
| Silver | $1.17 |
| Gold, Platinum, Diamond | $1.00 |

Examples:

- **1,000 jobs**: $1.50 on the Free plan, $1.00 on Gold.
- **10,000 jobs**: $15.00 on Free, $10.00 on Gold.
- **100,000 jobs**: $150 on Free, $100 on Gold.
- **Every job on all six sites (~309,000)**: about $309 on Gold.

**Maximum cost per run.** Set it in the run options (or `maxTotalChargeUsd` in the API). The run stops when the next job would exceed it, keeps everything saved so far and ends with a status message such as "Saved 33 jobs — this run's maximum cost ($0.05) covers 33 results at the current price." (Free plan).

Turning **Include job details** off does not lower the price per job; it only makes huge runs faster.

***

### Integrations

#### API (curl)

Use `run-sync-get-dataset-items` for runs under five minutes, `runs` + `datasets/{id}/items` for anything bigger. Every dataset export supports `format=json|csv|xlsx|xml|html|rss`, `fields=` to choose columns and `omit=` to drop the long ones (`omit=descriptionHtml,description`).

#### Node.js and Python

The [`apify-client`](https://docs.apify.com/api/client/js) package (JavaScript and Python) starts runs, waits for them and pages through the dataset. Examples are in the [API tutorial](#tutorial-run-it-through-the-api).

#### Make, Zapier and n8n

Use the Apify app: "Run an Actor" with `logiover/jobstreet-scraper` and your input, then "Get dataset items". A common flow: run daily with **Posted within = Last 24 hours**, filter rows with `salaryMin` above your threshold, post them to Slack or e-mail.

#### Google Sheets

Use the Apify integration for Google Sheets, or `IMPORTDATA("https://api.apify.com/v2/datasets/<ID>/items?format=csv&fields=title,companyName,location,salary,url&token=<TOKEN>")` for a small, read-only sheet.

#### Airtable

Send rows to an Airtable base with Make, Zapier or n8n; use `id` as the primary key so repeated runs update instead of duplicating.

#### CRM via webhooks (HubSpot, Salesforce, Pipedrive, Zoho)

Add a webhook on run success; your endpoint downloads the dataset and upserts companies by `companyId` or `advertiserId` with `companyWebsite`, `companyAddress`, `companySize`, `companyIndustry` and `companyRegistrationNumber`.

#### Scheduling

Create a Schedule in the Apify Console (for example every day at 07:00 Asia/Kuala_Lumpur) with your input; each run is independent and has its own dataset.

#### MCP and AI agents

The Actor works as a tool through the Apify MCP server: an AI agent can call it with `searchTerms`, `sites` and `maxItems` and read the dataset. Keep `maxItems` small for interactive use; the `description` field is plain text and ready for a model.

#### Data warehouses and BI

Load the CSV or JSON export into BigQuery, Snowflake, Redshift, Postgres or DuckDB; `postedAt` and `expiresAt` are ISO timestamps, salaries are numbers, ids are strings.

***

### Performance tips

- **Ask for what you need.** `maxItems` stops paging early; with several searches the budget is shared fairly.
- **Whole-site exports:** leave **Search terms** empty and pick the sites — that is one complete read per site, with the second sweep included.
- **Fastest possible bulk export:** turn **Include job details** off. You keep the search-card fields (title, company, location, classification, work type, arrangement, salary, teaser, bullet points, dates).
- **Daily monitoring:** **Posted within = Last 24 hours** with **Order = Newest first** reads only what is new.
- **Filters beat keywords:** "Classifications = Information & Communication Technology" is exact; the keyword "IT" is not.
- **Exact phrases** in double quotes keep keyword searches tight.
- **Locations:** use the names the sites use ("Kuala Lumpur", "Central Region", "Makati City", "Kowloon"); an unknown name returns 0 jobs rather than a guess.

***

### Troubleshooting

| Status message | Meaning | What to do |
|---|---|---|
| "Saved N jobs (max results reached) in S s." | Finished at **Max results**. | Raise **Max results** for more. |
| "Saved N jobs (every matching job read) in S s." | Every matching job was read. | — |
| "Saved N jobs (stopped at the run time limit) in S s." | The run's timeout came first. | Raise the timeout in the run options or split the input. |
| "… N job details could not be read; those rows carry the search data only." | The site kept refusing the detail request for those jobs. | Rows are still saved with the search-card fields; re-run the job URLs later if you need the descriptions. |
| "… N job URLs could not be read — run them again later." | Pasted job URLs that kept being refused. | Run them again. |
| "… N search pages could not be read." | Pages that failed after every retry (status PARTIAL). | Re-run; the rest of the rows are complete. |
| "No jobs matched — the search ran fine but returned 0 results. …" | The site answered with 0 jobs. | Check spelling, try broader terms, another location, fewer filters. |
| "Saved N jobs — this run's maximum cost ($X) covers N results at the current price. Raise "Maximum cost per run" to get more." | Your maximum cost per run was reached. | Raise it. |
| "Saved 0 jobs — this run's maximum cost ($X) does not cover a single result …" | The maximum cost is below the price of one job. | Raise it. |
| "Nothing to scrape: …" (run fails) | The input has nothing usable (for example only non-JobStreet URLs). | Fix the input as the message says. |
| "No jobs collected: no page could be fetched (…)" (run fails) | The sites refused every route. | Re-run later; this is an access failure, not an empty result. |
| "No jobs collected and N page(s) could not be read …" (run fails) | Nothing was saved and pages failed, so "no results" cannot be confirmed. | Re-run. |

Warnings you may see in the log: "Unknown site …", "Unknown classification …", "Company "…" has no open jobs on …", "Not a JobStreet or JobsDB URL", "Job 123 was not found on … (removed, or not a job id)", "JobStreet refused the direct connection …; using the datacenter proxy …", "JobStreet limits job-detail requests per address …". None of them stop the run.

Every run also writes a **SUMMARY** record (key-value store) with the outcome (COMPLETED, PARTIAL, EMPTY_RESULT, BLOCKED, FETCH_FAILED), the searches and how many jobs each found and saved, pages read, details read, retries, duplicates skipped, the second sweep and timings.

***

### Limits

- **Public data only.** The Actor reads what anyone can see without an account: no applicant data, no candidate profiles, no recruiter-only fields.
- **Salary** is published on about a third to a half of the ads (Malaysia 51 %, Hong Kong 11 % in our sample); the rest say nothing or use free text ("Negotiable", "Competitive"). Free text keeps `salary` but leaves `salaryMin`/`salaryMax` empty. Amounts are the employer's own — an occasional "RM 5 – RM 7 per month" is reported as written.
- **The pay filter** matches the employer's hidden pay range too, so filtered runs include jobs without a visible salary.
- **Contacts** (`contactEmails`, `contactPhones`) exist only when the employer typed them into the ad (about 4 %).
- **Company profiles** exist for about two thirds of the employers; ratings only for companies with reviews.
- **Registration numbers** are shown mostly on the Malaysian and Singapore sites.
- **Work arrangement** and the search-card fields of a pasted job URL come from the detail record; `tags`, `isFeatured` and `bulletPoints` of old ads may be missing.
- **Language:** labels (work type, posted age, applicant labels) are in English; descriptions are as the employer wrote them — often Bahasa Indonesia, Thai or Chinese.
- **Moving data:** a job can expire between the search and the detail request — it is then saved with the search data only and counted in the SUMMARY.
- **Old JobStreet / JobsDB links** from before the 2024 platform move (long ids, old URL shapes) do not resolve to today's job ids.

***

### Compared with other ways to get the data

| | This Actor | Copying from the website | Partner / ATS feeds |
|---|---|---|---|
| Setup | Pick sites and keywords | None | Contract and integration with each site |
| Coverage | All public jobs on six sites, whole-site export | What you have time to open | Usually your own ads only |
| Fields | 70 per job incl. parsed salary, questions, applicants, company profile | Whatever you copy | Varies |
| De-duplication across sites | Automatic by job id | Manual | Not applicable |
| Repeatable / scheduled | Yes, API and schedules | No | Yes |
| Cost | Per job saved | Staff time | Licence fees |

***

### FAQ

#### What is JobStreet Scraper?

An Apify Actor that exports job listings from JobStreet (Malaysia, Singapore, Philippines, Indonesia) and JobsDB (Hong Kong, Thailand) into a dataset you can download as CSV, Excel or JSON or read through an API.

#### Is there an official JobStreet API?

JobStreet and JobsDB offer integration APIs for advertisers and ATS vendors to post and manage their own job ads; there is no public API for searching everyone's jobs. This Actor reads the public pages' own data and works as a JobStreet API alternative for search and export.

#### Does it scrape JobsDB too?

Yes — JobsDB Hong Kong (hk.jobsdb.com) and JobsDB Thailand (th.jobsdb.com) are built in. Choose them in **Sites**.

#### How do I scrape JobStreet jobs into Excel or CSV?

Run the Actor, open the **Output** tab and click **Export** → CSV or Excel. Through the API add `format=csv` or `format=xlsx` to the dataset items URL.

#### Can I export all jobs from JobStreet Malaysia?

Yes. Leave **Search terms** empty, choose **Malaysia** and set **Max results** to 0. On 2026-10-20 that was 54,162 jobs.

#### Can I scrape all six sites in one run?

Yes. Select all six sites (or leave **Sites** empty). Each job is saved once, labelled with its own country.

#### How do I scrape jobs in Kuala Lumpur (or Singapore, Makati, Jakarta, Bangkok)?

Add the city to **Locations** and pick the site of that country. Use the names the site uses; check them in the location box on the website if a run returns 0.

#### How do I get lowongan kerja from JobStreet Indonesia?

Choose **Indonesia** in **Sites**, add search terms in Indonesian or English ("admin", "staff gudang", "akuntan"), and optionally **Locations** such as "Jakarta Selatan" or "Surabaya".

#### Can I scrape JobStreet without logging in?

Yes. No account, cookie or browser is needed.

#### Do I need proxies?

No. The Actor connects directly and switches to Apify's proxies by itself when the sites ask it to slow down. The proxy costs are part of the price per job.

#### How fast is it?

About 1,000 jobs with full details in 20 seconds and a whole site of 20,000 jobs in about two minutes (measured 2026-10-20).

#### How much does it cost to scrape JobStreet?

From $1.00 per 1,000 jobs (Gold plan and above) to $1.50 on the Free plan. See [What you pay](#what-you-pay).

#### How do I get only jobs posted today?

Set **Posted within** to "Last 24 hours" and **Order** to "Newest first".

#### Can I get the salary of each job?

When the employer shows it: `salary` (as shown) plus `salaryMin`, `salaryMax`, `salaryCurrency` and `salaryPeriod`.

#### Why are some salary fields empty?

Many employers hide the pay or write free text such as "Negotiable". The Actor never guesses an amount.

#### Can I filter jobs by salary?

Yes — **Minimum salary**, **Maximum salary** and **Salary period**. The sites also match ads whose pay range is set but hidden.

#### Does it return the full job description?

Yes, as plain text (`description`) and as the original HTML (`descriptionHtml`).

#### Can I get the recruiter's e-mail or phone number?

Only when the employer wrote it into the ad — then it is in `contactEmails` / `contactPhones`. The sites do not publish recruiter contacts otherwise.

#### Can I scrape all jobs from one company?

Yes — put its name, company page URL, "jobs at this company" URL or advertiser id in **Companies**.

#### How do I find direct employers and leave out agencies?

Drop rows with an `employmentAgencyLicence` (Singapore) or with "Employment Services" / "Recruitment" in `companyIndustry`; `advertiserName` also shows when an agency posted the ad.

#### Does it return remote and hybrid jobs?

Yes — `workArrangement` is on every row, and **Work arrangements** filters them.

#### Can I scrape by classification, like "Information & Communication Technology"?

Yes — **Classifications** (30) and **Sub-classifications** (391, by name or id).

#### Can I paste a JobStreet or JobsDB search URL?

Yes. Keyword, location, classification, sub-classification, work type, arrangement, date range, salary and sort order in the URL are all understood. Old domains such as www.jobstreet.com.my redirect on the site and are accepted.

#### Can I enrich a list of job links?

Yes — paste the job URLs into **JobStreet / JobsDB URLs**; each becomes a full row. Expired jobs come back with `isExpired: true`.

#### How are duplicates handled?

By job id across all sites and searches in a run. The whole-site test run had 0 duplicates.

#### Can I monitor new jobs every day?

Schedule a run with **Posted within = Last 24 hours**; compare `id` with yesterday's dataset to find new and removed ads.

#### What does applicantCount mean?

The number of applications the site has counted for the ad; `applicantCountLabel` is the coarser text the site shows ("More than 150 applicants").

#### Does it scrape the Thai, Indonesian or Chinese versions of the sites?

The jobs are the same in every language version; the Actor reads them with English labels. Descriptions stay in the language the employer used.

#### How many jobs do JobStreet and JobsDB have?

309,082 across the six sites on 2026-10-20 (see [jobs per site](#jobs-per-site)).

#### Can I use the data in Google Sheets, Airtable or my CRM?

Yes — see [Integrations](#integrations).

#### Can an AI agent use this Actor?

Yes, through the Apify MCP server or the API; descriptions are plain text, salaries are numbers.

#### Why did my run stop before Max results?

Either every matching job was read, the run's time limit came first, or the maximum cost per run was reached — the status message says which.

#### What happens if the site blocks the run?

The Actor rests the refused connection and continues through other routes; if nothing at all can be fetched, the run fails with an explanation instead of returning an empty "success".

#### I need a field or a filter that is missing. Can you add it?

Probably — write to us (see [Support](#support)) with an example job.

***

### Glossary

- **JobStreet** — job sites for Malaysia, Singapore, the Philippines and Indonesia (my/sg/ph/id.jobstreet.com).
- **JobsDB** — job sites for Hong Kong and Thailand (hk/th.jobsdb.com). Both brands belong to SEEK and share one platform since 2024.
- **Classification / sub-classification** — SEEK's two-level job category tree (30 / 391), the same on all six sites.
- **Work type** — Full time, Part time, Contract/Temp, Casual/Vacation.
- **Work arrangement** — On-site, Hybrid, Remote.
- **Advertiser** — the account that posted the ad; can be the employer or a recruitment agency.
- **Confidential / private advertiser** — the employer chose not to show its name.
- **Teaser** — the one-line summary on a search card.
- **Bullet points** — up to three short selling points the employer adds to the card.
- **Application questions** — screening questions candidates answer when applying.
- **Applicant count** — applications received so far, as counted by the site.
- **Registration no.** — company registration number shown on Malaysian (SSM) and Singapore (UEN) profiles.
- **EA licence** — Singapore Ministry of Manpower licence number of an employment agency.
- **Second sweep** — the re-read at the end of a complete search that picks up jobs that moved between pages.
- **Dataset** — the table of results an Apify run produces; exportable in many formats.
- **Maximum cost per run** — the spending cap you set for one run.

***

### Legal and data protection

The Actor collects job advertisements that employers publish openly, without logging in and without bypassing access controls meant for registered users. Job ads can still contain personal data — a recruiter's name, an e-mail address or phone number written in the ad. If you store or use such data, you are the controller and must follow the laws that apply to you and to the people concerned: Malaysia's Personal Data Protection Act 2010, Singapore's Personal Data Protection Act 2012, the Philippines' Data Privacy Act of 2012, Indonesia's Personal Data Protection Law (Law No. 27 of 2022), Hong Kong's Personal Data (Privacy) Ordinance, Thailand's Personal Data Protection Act B.E. 2562 (2019) and, for people in the EU, the GDPR. Use contacts only for purposes compatible with why they were published (applying for or discussing the job), honour opt-outs, and respect the sites' terms. Republishing complete job ads may raise copyright questions; summaries, statistics and links are the usual safe ground. This is not legal advice.

***

### Changelog

- **1.0 (2026-10-20)** — First release: JobStreet Malaysia, Singapore, Philippines, Indonesia and JobsDB Hong Kong, Thailand; keyword × location searches, all sites at once, whole-site export with second sweep, companies, search / job / company URLs, classification, sub-classification, work type, work arrangement, posting date and salary filters; full job details in batches; parsed salary; clean stop at Max results or maximum cost with a SUMMARY record.

***

### Support

Questions, a missing field, a site change? Open an issue on the Actor's **Issues** tab — we reply within 24 hours. Please include the run ID so we can see exactly what happened.

*JobStreet Scraper is an independent tool and is not affiliated with, endorsed by, or sponsored by JobStreet, JobsDB or SEEK Limited.*

# Changelog

This Actor's version history is a separate document: https://apify.com/logiover/jobstreet-scraper/changelog.md

# Actor input Schema

## `searchTerms` (type: `array`):

Job titles, skills or keywords as you would type them on JobStreet or JobsDB, e.g. accountant, software engineer, nurse, sales executive. Put a phrase in double quotes for an exact match. Each term is one search on every chosen site. Leave empty to export every job on the chosen sites (combined with the filters below).

## `sites` (type: `array`):

Which job sites to search. JobStreet: Malaysia, Singapore, Philippines, Indonesia. JobsDB: Hong Kong, Thailand. Empty = all six. Every job is saved once, labelled with its own country.

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

Optional. City, state or district as the site knows it: "Kuala Lumpur", "Selangor", "Penang", "Central Region" (Singapore), "Makati City", "Metro Manila", "Jakarta Selatan", "Surabaya", "Kowloon", "Central and Western District", "Bangkok". Every search term is combined with every location. A location the site does not know returns 0 jobs (it is not widened). Empty = whole country.

## `classifications` (type: `array`):

Optional. JobStreet / JobsDB job classifications (the "All classifications" menu). Empty = all.

## `subClassifications` (type: `array`):

Optional. Narrower classifications by name or id, e.g. "Developers/Programmers", "Process Engineering", "Accounts Payable", "6281". Names are matched exactly as the sites write them (391 known).

## `workTypes` (type: `array`):

Optional. Any of these work types (empty = all).

## `workArrangements` (type: `array`):

Optional. On-site, hybrid or remote (empty = all).

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

Only jobs listed within this many days (the sites' "Listed" filter).

## `salaryMin` (type: `integer`):

Optional. The sites' own pay filter, in the site's currency (RM, S$, ₱, Rp, HK$, ฿) for the period below. Note: the sites also match jobs whose employer entered a pay range but hides it — those rows come without a salary amount.

## `salaryMax` (type: `integer`):

Optional. Upper end of the pay filter (same currency and period).

## `salaryType` (type: `string`):

Period of the minimum / maximum salary above.

## `companies` (type: `array`):

Optional. Get one company's open jobs: its name exactly as the job ads show it ("Grab", "Shopee Singapore Private Limited"), its company page (https://my.jobstreet.com/companies/…-123456789), its "jobs at this company" page, or an advertiser id. Combined with the search terms and locations.

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

Optional. Paste pages straight from the sites: search results with their filters (https://my.jobstreet.com/engineer-jobs/in-Kuala-Lumpur?daterange=7, https://hk.jobsdb.com/jobs-in-accounting/full-time), single jobs (https://sg.jobstreet.com/job/95385012), company pages or "jobs at this company" pages. If you give only URLs, only the URLs are scraped.

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

Relevance (the sites' default) or newest first. Asking for every result returns the same jobs either way.

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

Stop after this many unique jobs across all sites and searches. 0 = no limit (every matching job; the six sites together list about 300,000).

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

Reads each job's full record: complete description (text and HTML), expiry date, application questions, applicant count, contact e-mails written in the ad, and the company profile (industry, size, website, address, specialities, rating, perks, registration number). Read in batches of 20 jobs per request, so it costs little time. Off = the search card only (title, company, location, salary, teaser, bullet points).

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

Used only as a fallback. The scraper calls JobStreet and JobsDB directly first (no proxy cost); if that is refused it switches to Apify datacenter proxies (a new address per request) and then to the proxy given here.

## Actor input object example

```json
{
  "searchTerms": [
    "accountant"
  ],
  "sites": [
    "MY",
    "SG",
    "PH",
    "ID",
    "HK",
    "TH"
  ],
  "locations": [],
  "classifications": [],
  "subClassifications": [],
  "workTypes": [],
  "workArrangements": [],
  "postedWithin": "any",
  "salaryType": "monthly",
  "companies": [],
  "startUrls": [],
  "sortBy": "relevance",
  "maxItems": 100,
  "includeDetails": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

All jobs collected in this run.

# 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 = {
    "searchTerms": [
        "accountant"
    ],
    "sites": [
        "MY",
        "SG",
        "PH",
        "ID",
        "HK",
        "TH"
    ],
    "maxItems": 100,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("logiover/jobstreet-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 = {
    "searchTerms": ["accountant"],
    "sites": [
        "MY",
        "SG",
        "PH",
        "ID",
        "HK",
        "TH",
    ],
    "maxItems": 100,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("logiover/jobstreet-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 '{
  "searchTerms": [
    "accountant"
  ],
  "sites": [
    "MY",
    "SG",
    "PH",
    "ID",
    "HK",
    "TH"
  ],
  "maxItems": 100,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call logiover/jobstreet-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,logiover/jobstreet-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/v4fDMhx6CszYBfkgm/builds/cxX73wX5kiMjjWDHk/openapi.json
