JobStreet Scraper - Jobs, Salaries, Companies & Apply Details avatar

JobStreet Scraper - Jobs, Salaries, Companies & Apply Details

Pricing

from $1.00 / 1,000 results

Go to Apify Store
JobStreet Scraper - Jobs, Salaries, Companies & Apply Details

JobStreet Scraper - Jobs, Salaries, Companies & Apply Details

From $1/1K. Scrape JobStreet listings across Malaysia, Singapore, Indonesia, and the Philippines. Extract titles, companies, salaries, locations, descriptions, company info, and apply details. Search with filters or paste JobStreet URLs directly. Fast and cost-efficient.

Pricing

from $1.00 / 1,000 results

Rating

5.0

(1)

Developer

Abot API

Abot API

Maintained by Community

Actor stats

1

Bookmarked

18

Total users

5

Monthly active users

4 days ago

Last modified

Share

JobStreet Jobs Scraper: Job Listings Across Malaysia, Singapore, the Philippines and Indonesia

JobStreet Jobs Scraper turns JobStreet into a clean job-search API across all four Southeast Asian markets it runs: Malaysia, Singapore, the Philippines and Indonesia. Search by keywords, location, category, work type, salary and listing date, or paste JobStreet search URLs directly and let the actor read the filters from the link. Get back structured job records with the full HTML description, company overview, salary and any contact details the ad exposes, then export to JSON, CSV or Excel, or pull results straight into your app through the API.

Why This Scraper?

  • All four JobStreet country sites from one actor. The country is auto-detected from each pasted URL, or picked from a dropdown when you search by filters.
  • Two ways to search. Fill in the filter fields (country, keywords, location, category, work type, salary range, listing date), or paste one or more JobStreet search URLs and mix all four markets in a single run.
  • 50-plus fields per job, including the full HTML description, plain-text body, bullet points, salary, company overview, and any phone numbers or emails found in the ad body.
  • Detail enrichment is batched. One extra request covers a whole page of up to 22 jobs, not one request per job, so a full run with descriptions costs far fewer requests than fetching each job's page individually.
  • Forward pagination across as many pages as you allow, with automatic de-duplication by job ID within a run.
  • Filter by category, work type, salary range and listing recency, in filter mode or through the URL itself.
  • Global caps on item count and run time, so a run never overshoots your budget, and whatever was already fetched is always saved even if a cap or deadline stops the run early.

Use Cases

  • Job boards and aggregators: mirror JobStreet listings into your own board or app across all four markets.
  • Recruiting and sourcing teams: monitor new roles in a category or location and route them into a pipeline or CRM.
  • Salary and market research: build salary benchmarks by market, category and work type from the structured salary and classification fields.
  • Lead generation and outreach: collect contact phone numbers and emails that employers left directly in the ad body.
  • Company intelligence: track a company's open roles and hiring pace over time.
  • Labor market analytics: build historical datasets of job counts, categories and locations across Malaysia, Singapore, the Philippines and Indonesia.

Data You Get

Sample shape: values are illustrative placeholders, not from a live listing.

FieldExample
id"00000001"
url"https://my.jobstreet.com/job/00000001"
applyLink"https://my.jobstreet.com/job/00000001/apply"
title"Software Engineer"
teaser"Join a growing team building cloud services."
companyName"Sample Company Sdn Bhd"
companyOverview"Sample Company is a leading provider of cloud services in the region." (when includeFullDescription is on)
advertiserName"Sample Company Sdn Bhd"
locationLabel"Kuala Lumpur"
countryCode"MY"
classificationInfo{ "classification": "Information & Communication Technology", "subClassification": "Developers/Programmers" }
workTypes["Full time"]
salaryLabel"RM 5,000 to RM 8,000 per month"
content"<p>Full job description HTML.</p>" (when includeFullDescription is on)
contentText"Full job description, plain text." (when includeFullDescription is on)
bulletPoints["Competitive salary", "Hybrid work", "Career growth"]
phoneNumbers["+60100000000"] (when includeFullDescription is on)
emails["careers@example.com"] (when includeFullDescription is on)
listingDate"2026-01-01T00:00:00Z"
listingDateDisplay"2d ago"
expiresAt"2026-02-01T00:00:00Z" (when includeFullDescription is on)
isVerifiedtrue (when includeFullDescription is on)
detailFetchedtrue
scrapedAt"2026-01-01T00:00:00.000Z"

The full record carries more than this table: classifications[] with category IDs, workArrangementLabels, salaryCurrency, shareLink, companyOpenJobs, branding, advertiser details, and raw passthrough blocks (advertiserRaw, employerRaw, companyProfileRaw) so no upstream field is dropped. content, contentText, contentSections, companyOverview, expiresAt, isVerified, salaryCurrency, phoneNumbers and emails only come from the detail fetch and stay empty when includeFullDescription is off, while detailFetched tells you, per job, whether that fetch actually succeeded. When incrementalMode is on, every row also carries changeType, changedFields, firstSeenAt and lastSeenAt, and a job that drops out of a fully scanned search can appear as a synthesized EXPIRED row built from its last known snapshot, see "Resume and recurring updates" below for exactly when that happens.

How to Use

  1. Pick how you want to search: fill in the filter fields (country, keywords, location, category, work type, salary, listing date) under Option 1, or paste one or more JobStreet search URLs under Option 2. If you set urls, every Option 1 field is ignored, and the country and filters come from each URL instead.
  2. Turn includeFullDescription on to get the full HTML description, company overview, expiry date and phone number for each job. Leave it off to get only what the search results themselves already carry, without the extra per-page request.
  3. Set maxItems and, optionally, maxTimeSec to cap the size and length of the run, then click Start.
  4. Download the dataset as JSON, CSV or Excel, or read it through the API.

Basic search (Malaysia):

{
"country": "MY",
"keywords": "software engineer",
"location": "Kuala Lumpur",
"maxItems": 50
}

Search with filters (Singapore, ICT, full time, salary range):

{
"country": "SG",
"keywords": "data engineer",
"classification": ["6281"],
"workType": ["242"],
"salaryMin": 5000,
"salaryMax": 12000,
"salaryType": "monthly",
"daterange": 14,
"sortmode": "ListedDate",
"maxItems": 100,
"includeFullDescription": true
}

Multiple countries via URL mode:

{
"urls": [
"https://my.jobstreet.com/it-jobs/in-Penang",
"https://sg.jobstreet.com/jobs-in-information-communication-technology",
"https://ph.jobstreet.com/accountant-jobs",
"https://id.jobstreet.com/marketing-jobs/in-Jakarta-Raya"
],
"maxItems": 200,
"includeFullDescription": false
}

Recent jobs only, sorted by date:

{
"country": "ID",
"keywords": "marketing",
"daterange": 7,
"sortmode": "ListedDate",
"maxItems": 30
}

Run it from your code

Python:

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/jobstreet-scraper").call(run_input={
"country": "MY",
"keywords": "software engineer",
"location": "Kuala Lumpur",
"maxItems": 50,
})
for job in client.dataset(run["defaultDatasetId"]).iterate_items():
print(job["title"], job["companyName"], job["salaryLabel"])

JavaScript:

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/jobstreet-scraper').call({
country: 'MY',
keywords: 'software engineer',
location: 'Kuala Lumpur',
maxItems: 50,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.

How caps shape a run

maxItems is a single budget shared across every URL and every page, not a per-URL limit: with 3 URLs and maxItems=50, the actor paginates the first URL until the budget is used up, and only moves to the second and third URL if room remains. Set maxItems to 0 for no cap. maxTimeSec is a wall-clock budget: once it elapses, the actor stops paginating, flushes whatever it already fetched, and ends the run cleanly instead of losing that work. Leaving both maxItems and maxTimeSec at 0 runs until the search itself runs out of pages, so set at least one of them for a predictable run.

Resume and recurring updates

Resume (resumeFromRunId) continues one interrupted run: paste its run ID or dataset ID and the actor skips every job it already collected there, so a retried run adds only what is still missing.

Incremental mode (incrementalMode) is for a search you run on a schedule against the same filters or URLs. Turn it on and the actor remembers what that exact search returned last time, keyed by stateKey (or an automatic key derived from your filters, so two differently configured searches never share a baseline by accident). Every job scraped this run is then classified:

  • NEW: not seen in a previous run.
  • UPDATED: content changed since last time, with changedFields naming exactly what.
  • UNCHANGED: identical to last time. Suppressed by default; turn on emitUnchanged to also push (and pay for) these rows.
  • EXPIRED: was tracked before and is missing from this run. This only fires when the run reached the natural end of every search it ran, with no maxItems cap, no maxTimeSec deadline and no resumeFromRunId cutting it short, since a partial scan cannot tell a job is gone from a job it simply has not reached yet. It also requires emitExpired to be on; otherwise the previous state for those jobs is left untouched and nothing is emitted.
  • REAPPEARED: a job that was previously marked EXPIRED shows up again. Because a job can only become EXPIRED when emitExpired was on for a prior complete scan, REAPPEARED can only occur under that same condition. If you never turn emitExpired on, a job that drops out of the results and later comes back is simply classified UNCHANGED or UPDATED against its last snapshot, never REAPPEARED or NEW.

Incremental mode is off by default, so existing scheduled runs are unaffected. maxItems still counts rows actually pushed, not rows scanned: if most of a tracked search is UNCHANGED and suppressed, the actor keeps paginating to backfill your budget with jobs that did change.

Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, through Model Context Protocol (MCP) connectors. This is an extra delivery step after the scrape. The Apify dataset itself is never changed.

What gets written to the connector: a condensed, human-readable summary of each record, not the full JSON. Each job becomes one entry with a title and its key fields flattened to plain text. The complete record always stays in the Apify dataset.

  1. Authorize a connector once under Apify → Settings → Integrations (Notion, Linear, Airtable, or Apify).
  2. Select it in the "Pipe results into your apps" input field. If the picker is empty, you have not authorized a connector yet.
  3. For Notion, also set notionParentPageUrl to the page where job pages should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

Input Parameters

ParameterTypeDefaultDescription
countrystring"MY"Market to search: MY, SG, PH, or ID. Applies in filter mode only.
keywordsstringnone (prefill: "software engineer")Job title or keywords. Empty matches all jobs in the location.
locationstringnoneLocation within the country, for example Kuala Lumpur or Jakarta Raya. Empty searches the whole country.
classificationarray[]One or more top-level category IDs. Empty means all categories.
workTypearray[]Work types: 242 Full time, 243 Part time, 244 Contract/Temp, 245 Casual/Vacation.
salaryMinintegernoneMinimum salary in the local currency.
salaryMaxintegernoneMaximum salary in the local currency.
salaryTypestring"monthly"Whether the salary filter values are monthly or annual.
daterangeintegernoneListed within N days. Accepted: 1, 3, 7, 14, 31. Other values are dropped with a warning.
sortmodestring"ListedDate"ListedDate newest first, or KeywordRelevance best match.
urlsarray[]One or more JobStreet search URLs. Overrides every filter field above. Country is auto-detected per URL.
includeFullDescriptionbooleantrueFetch the full HTML description, company overview, expiry date and phone via the job-detail endpoint. Batched per page.
maxItemsinteger20Global cap across all URLs and pages. 0 means unlimited.
maxTimeSecinteger0Global wall-clock cap in seconds. 0 means no limit. Partial results are flushed when the cap is reached.
resumeFromRunIdstringnoneContinue one specific interrupted run. See "Resume and recurring updates" above.
incrementalModebooleanfalseTrack a scheduled search across runs and classify changes. See above.
stateKeystringnoneOptional name for the Incremental mode baseline. Auto-derived from your filters when empty.
emitUnchangedbooleanfalseAlso push rows unchanged since the last Incremental mode run. Bills extra rows.
emitExpiredbooleanfalsePush a synthetic row for jobs that dropped out of a fully scanned search. Bills extra rows.
proxyConfigurationobjectApify proxy, default connection groupProxy settings. See "Plan Requirement" below.
mcpConnectorsarraynoneOptional: send a summary of each job to apps you authorized under Integrations.
notionParentPageUrlstringnoneNotion connector only: the page under which job pages are created.
maxNotifyListingsinteger50Cap on items written to each connector per run. Does not affect the dataset.

Output Example

Sample shape: values are illustrative placeholders, not from a live listing.

{
"id": "00000001",
"url": "https://my.jobstreet.com/job/00000001",
"applyLink": "https://my.jobstreet.com/job/00000001/apply",
"title": "Software Engineer",
"teaser": "Join a growing team building cloud services.",
"companyName": "Sample Company Sdn Bhd",
"companyId": "00000000",
"companyOverview": "Sample Company is a leading provider of cloud services in the region.",
"advertiserName": "Sample Company Sdn Bhd",
"locationLabel": "Kuala Lumpur",
"locationSeoHierarchy": ["Kuala Lumpur", "Wilayah Persekutuan"],
"countryCode": "MY",
"classificationInfo": {
"classification": "Information & Communication Technology",
"subClassification": "Developers/Programmers"
},
"workTypes": ["Full time"],
"salaryLabel": "RM 5,000 to RM 8,000 per month",
"content": "<p>Full job description HTML appears here when includeFullDescription is true.</p>",
"contentText": "Full job description plain text appears here.",
"bulletPoints": ["Competitive salary", "Hybrid work", "Career growth"],
"phoneNumbers": ["+60100000000"],
"emails": ["careers@example.com"],
"listingDate": "2026-01-01T00:00:00Z",
"listingDateDisplay": "2d ago",
"expiresAt": "2026-02-01T00:00:00Z",
"isVerified": true,
"detailFetched": true,
"scrapedAt": "2026-01-01T00:00:00.000Z"
}

Plan Requirement

The default proxy setting works out of the box for JobStreet's public search and detail endpoints, and the actor automatically tries a backup connection if the primary one is refused, then continues directly as a last resort, so a run does not fail purely because of a blocked connection. For large or frequent runs across many pages, the residential proxy group gives more headroom; pick it under Connection and optionally pin the country to match the market you are scraping.

FAQ

How much does it cost?

You pay per job returned, with the full-detail fetch billed only when includeFullDescription is on, and Incremental mode's emitUnchanged or emitExpired billed only for the extra rows those toggles add. The Pricing tab shows the current rates. Use maxItems to cap the cost of any run.

This actor collects only publicly available job listing data. You are responsible for how you use it: follow JobStreet's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Job listings are generally public information, but company logos and branding may carry separate rights.

Can I get only new or changed jobs on a schedule?

Yes. Schedule the actor from the Schedules tab and turn on incrementalMode. Each run then classifies every job as NEW, UPDATED, UNCHANGED, REAPPEARED or EXPIRED against what the same search returned last time, and unchanged jobs are not billed unless you turn emitUnchanged on.

Why are my filter fields being ignored?

URL mode always overrides filter mode. If urls has any entries, every field under Option 1 (country, keywords, location, category, work type, salary, date range) is ignored, and the actor reads the country and filters from each URL's own path and query string instead. Clear urls to go back to filter search.

Why did my run fail instead of returning an empty dataset?

A search that legitimately matches nothing exits cleanly with a status message explaining why, and pushes no billable row, so "no jobs found" is never confused with a broken run. A run actually fails, instead, when resumeFromRunId points at a run or dataset ID that cannot be read, or when incrementalMode and resumeFromRunId are combined on a search that already has saved Incremental mode state: the actor stops with a clear message telling you to drop one of the two rather than silently mixing them.

Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.

🔗 Want more JobStreet data?

Pair this actor with these related scrapers from the same team:

📇 JobStreet
From $1/1K. Pull JobStreet company profiles and employee reviews across Malaysia...
💼 Jora Jobs Scraper
Scrape Jora.com job listings across supported countries by keyword, filters, or URL...
💼 SEEK Jobs Scraper
From $0.7/1K. Scrape SEEK.com.au and SEEK.co.nz jobs by keyword, location, or filters...
💼 ZipRecruiter Scraper
From $1/1K. Scrape ZipRecruiter jobs with titles, companies, salaries, locations...
💼 HH.ru Jobs Scraper
From $1/1K. Scrape HH.ru job listings with 50+ structured fields. Search by filters or...
💼 Workday Jobs Scraper
Scrape job postings from any Workday-hosted careers site on Myworkdayjobs.com. Paste...

👉 Browse all abotapi scrapers

💬 Support & custom scrapers

  • 🐞 Found a bug or a missing field? Open a ticket on the Issues tab. We usually reply within hours.
  • 🛠️ Need another site, extra fields or a private build? Email abotapi@proton.me or message Telegram @abotapi.
  • ⭐ Enjoying it? A quick review on the actor page helps other users find it.