# Dealls Jobs Scraper Indonesia (`produkdigitalali/dealls-jobs-scraper-indonesia`) Actor

Unofficial Dealls jobs scraper. Extract salary, company, location, work mode, experience, benefits, and public hiring signals for recruitment and labour-market intelligence. Export JSON/CSV/Excel and monitor NEW, UPDATED, UNCHANGED, and EXPIRED jobs. Not affiliated with or endorsed by Dealls.

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

## Pricing

from $4.00 / 1,000 results

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

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

## What's an Apify Actor?

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

## How to integrate an Actor?

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

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

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

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

# README

### What is Dealls Jobs Scraper Indonesia?

**Dealls Jobs Scraper Indonesia** is an **unofficial, independent Actor** that extracts public job listings from [Dealls](https://dealls.com/en) into structured data with salary, company, location, experience, work-mode, and public hiring signals. **It is not affiliated with, endorsed by, or operated by Dealls.**

Use it for **job aggregation, recruiter and hiring intelligence, salary research, labour-market analysis, university or bootcamp insights, and AI job-matching workflows**. Start with the default Dealls page for a quick run, or paste a focused Dealls listing/filter URL for a specific market slice.

For recurring workflows, **monitor mode** compares repeat runs and classifies jobs as `NEW`, `UPDATED`, `UNCHANGED`, or—after verification—`EXPIRED`.

### Why use Dealls Jobs Scraper Indonesia?

Use this Actor when you need to:

- collect structured Indonesian job-market data without copying vacancies manually;
- monitor new and changed vacancies from the same Dealls search on a schedule;
- compare salary ranges across roles, locations, employers, and work arrangements;
- identify companies with public actively-hiring signals;
- build job datasets for recruiters, staffing teams, researchers, universities, or bootcamps;
- feed clean job records into spreadsheets, databases, APIs, analytics pipelines, or AI workflows.

The Actor uses a **hybrid HTTP-first architecture**. It reads public Dealls pages with lightweight HTTP requests and only launches Playwright for additional listing discovery when necessary. Detail enrichment remains HTTP-first to keep runs efficient.

### What data can you extract from Dealls?

| Field | Meaning |
|---|---|
| `jobId` | Stable Actor-generated ID derived from the canonical Dealls job URL |
| `jobSlug` | Public Dealls job slug |
| `title` | Job title |
| `companyName` | Employer/company name |
| `companyUrl` | Public company profile URL when available |
| `jobUrl` | Canonical Dealls job URL |
| `sourceListUrl` | Listing/filter page where the job was discovered |
| `location` | Job location |
| `employmentType` | Full-Time, Contract, Internship, Freelance, etc. |
| `workMode` | On-site, Hybrid, or Remote |
| `experience` | Public experience/candidate-level requirement |
| `salaryMin`, `salaryMax` | Normalized numeric salary range when publicly displayed |
| `salaryCurrency` | Salary currency, normally `IDR` for Rp listings |
| `salaryNegotiable` | Whether salary is shown as negotiable |
| `recruiterActivity` | Public recruiter activity signal |
| `activelyHiring` | Boolean normalized from the public activity signal |
| `applicantSignal` | Public signal such as Fewer Applicants when shown |
| `status` | Normalized vacancy status when determinable |
| `description` | Public job description with detail enrichment |
| `requirements` | Public qualifications/requirements |
| `industry` | Public company industry after normalization |
| `companyLocation` | Public company location |
| `companySize` | Public company-size text |
| `benefits` | Publicly displayed benefits |
| `changeType` | Monitoring classification: NEW, UPDATED, UNCHANGED, or EXPIRED |
| `observedAt` | UTC monitoring observation timestamp |
| `scrapedAt` | UTC scrape timestamp |
| `fingerprint` | Hash used for meaningful change detection |

Fields can be `null` when Dealls does not expose that information publicly for a vacancy.

### How to scrape Dealls jobs

1. Add one or more public Dealls URLs in **Dealls job URLs**. The default `https://dealls.com/en` is the recommended first run.
2. Set **Maximum jobs**. Keep the default while testing, then increase it for larger scans.
3. Keep **Fetch full job details** enabled when you need descriptions, requirements, benefits, and company metadata.
4. Run the Actor and inspect the **Jobs**, **Hiring intelligence**, or **Monitoring** dataset view.
5. Export the dataset to JSON, CSV, or Excel, consume it through the Apify API, or schedule repeat runs.

#### Quick-start input

```json
{
  "startUrls": [
    { "url": "https://dealls.com/en" }
  ],
  "maxItems": 18,
  "fetchDetails": true,
  "useBrowserForMore": true,
  "monitorMode": false
}
```

#### Recurring monitoring input

```json
{
  "startUrls": [
    { "url": "https://dealls.com/en" }
  ],
  "maxItems": 100,
  "fetchDetails": true,
  "useBrowserForMore": true,
  "monitorMode": true,
  "emitChangesOnly": true,
  "detectExpired": true,
  "maxExpiredChecks": 50,
  "monitorKey": "daily-dealls-jobs"
}
```

For repeat comparisons, keep the same `startUrls` and `monitorKey`.

### Important input options

- `startUrls` — public Dealls listing/filter URLs or direct job-detail URLs.
- `maxItems` — maximum unique jobs selected across all start URLs.
- `fetchDetails` — enrich jobs from their public detail pages.
- `useBrowserForMore` — use Playwright only when additional listing discovery is required.
- `maxLoadMoreClicks` — safety limit for See More / Lebih Banyak interactions.
- `concurrency` — simultaneous detail HTTP requests.
- `monitorMode` — persist state and compare repeat runs.
- `emitChangesOnly` — emit only NEW, UPDATED, and EXPIRED records.
- `detectExpired` — verify missing jobs before declaring them expired.
- `maxExpiredChecks` — cap expiry-verification work per run.
- `monitorKey` — isolate independent monitoring workflows.
- `proxyConfiguration` — optional; direct connections are the default.

### Output example

A real production-style result looks like this:

```json
{
  "jobId": "b3cb4ea9513522b41493",
  "jobSlug": "operations-supervisor-expansion-city-1~pt-xanhsm-green-and-smart",
  "title": "Operations Supervisor (Expansion City)",
  "companyName": "PT XanhSM Green & Smart Mobility",
  "jobUrl": "https://dealls.com/en/loker/operations-supervisor-expansion-city-1~pt-xanhsm-green-and-smart",
  "employmentType": "Full-Time",
  "workMode": "On-site",
  "location": "Indonesia",
  "experience": "Min. 1 years of experience",
  "salaryMin": 8000000,
  "salaryMax": 10000000,
  "salaryCurrency": "IDR",
  "salaryNegotiable": false,
  "activelyHiring": true,
  "industry": "Taxi Listrik",
  "companyLocation": "Jakarta Selatan",
  "changeType": "NEW"
}
```

Public listings change over time, so sample values are illustrative of the schema rather than a promise that the vacancy remains available.

### Monitoring and incremental mode

Monitoring is designed for scheduled repeat runs using the same scope.

- `NEW` — the stable job ID was not present in previous state.
- `UPDATED` — meaningful normalized fields changed since the previous run.
- `UNCHANGED` — the current meaningful fingerprint matches the previous state.
- `EXPIRED` — a previously seen job is missing from the current listing **and** its detail page confirms that it is closed or unavailable.

A job that disappears from the first page is **not** automatically expired. The Actor verifies missing candidates when `detectExpired` is enabled. Temporary network or parsing failures during expiry verification are non-destructive and do not remove the saved job from state.

#### Why can a successful monitoring run show "No results"?

With `emitChangesOnly: true`, `UNCHANGED` records are intentionally not written to the dataset. If 18 jobs are seen and all 18 are unchanged, a successful run can therefore produce an empty dataset.

Open **Run summary** and check values such as:

```json
{
  "discoveredJobs": 18,
  "emittedResults": 0,
  "changeCounts": {
    "NEW": 0,
    "UPDATED": 0,
    "UNCHANGED": 18,
    "EXPIRED": 0
  }
}
```

This is expected incremental-monitor behavior, not a scraping failure.

### How much does it cost to scrape Dealls jobs?

The Actor uses transparent **pay-per-event pricing** based primarily on dataset results. The launch price is **$0.004 per dataset item ($4.00 per 1,000 results)**, plus Apify's standard synthetic Actor-start event when enabled in the Store pricing configuration.

Examples at the launch per-result price:

| Results | Per-result charge |
|---:|---:|
| 100 | $0.40 |
| 500 | $2.00 |
| 1,000 | $4.00 |

Platform usage is intended to be included in the event pricing rather than passed through as a separate usage charge. Actual totals can differ if the run emits fewer records than requested or if Store pricing is changed later. Always check the Actor's **Pricing** tab for the current price.

With `emitChangesOnly: true`, unchanged jobs are not written to the default dataset. This means recurring monitoring can stay low-noise and per-result charges apply only to records actually emitted, such as `NEW`, `UPDATED`, or verified `EXPIRED` items.

### API, schedules, and automation

The Actor can be run manually, through the Apify API, from a saved task, or on a schedule. A common recurring workflow is:

`Dealls search URL → scheduled Actor run → NEW/UPDATED/EXPIRED records → database, webhook, spreadsheet, or AI workflow`

The stable IDs, normalized fields, and explicit change semantics are designed to be easy to consume programmatically.

### FAQ

#### Can I scrape Dealls jobs for one location or role?

Yes. Open the relevant public Dealls page, copy its URL, and use it in `startUrls`. This keeps the Actor aligned with the current public site rather than relying on undocumented query parameters.

#### Can I scrape a direct Dealls job URL?

Yes. Add the public job-detail URL to `startUrls`. With `fetchDetails` enabled, the Actor extracts the detail page directly.

#### Why did I get fewer results than `maxItems`?

The source page may expose fewer unique jobs, the See More control may not be available, some jobs may be duplicated across start URLs, or Dealls may have changed the current public page structure. Check `RUN_SUMMARY` for warnings.

#### Does monitor mode mark every missing job as expired?

No. `detectExpired` verifies the saved job's detail page before emitting `EXPIRED`. Missing-from-listing alone is intentionally insufficient.

#### Why is the dataset empty in changes-only mode?

If every seen job is `UNCHANGED`, `emitChangesOnly: true` emits no rows. Open `RUN_SUMMARY` to see the discovered count and change classification.

#### Are salary fields always available?

No. Some listings expose a numeric range, some show Negotiable, and some expose no salary. Missing values are returned as `null` rather than guessed.

#### Does the Actor access private candidate or employer data?

No. It is designed around publicly accessible job and company information and does not require candidate or employer login credentials.

### Limitations

- Dealls can change its public HTML, copy, filters, or load-more behavior without notice.
- Field availability varies between vacancies.
- Browser fallback depends on the current See More / Lebih Banyak interface.
- The Actor does not log in or access private candidate/employer data.
- Large crawls should be scaled gradually while checking reliability and current unit economics.
- `EXPIRED` detection is conservative by design; candidates that cannot be verified safely remain in state rather than being removed.

### Responsible use and support

This is an **unofficial, independent Actor** and is **not affiliated with, endorsed by, sponsored by, or operated by Dealls**. Dealls is referenced only to identify the public website and data source this Actor works with.

This Actor is intended for public business/listing data and legitimate use cases such as recruitment analytics, job aggregation, salary research, labour-market research, and automation. Users are responsible for ensuring that their use of the data complies with applicable laws, contractual obligations, and the target site's current terms.

When reporting an issue, include the Dealls URL, Apify run ID, approximate time, expected result, and the relevant warning/error from the run log. Do not include credentials or private data.

# Actor input Schema

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

One or more public Dealls listing/filter URLs or direct job-detail URLs. For a broad first run, keep the default https://dealls.com/en. For focused monitoring, paste the exact Dealls page for the role, location, category, industry, or other market slice you want to track.

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

Maximum number of unique jobs selected across all start URLs. The default 18 is intentionally small for a fast first run; increase it for larger market scans.

## `fetchDetails` (type: `boolean`):

Enrich each job from its public detail page with description, requirements, company information, benefits, and more reliable metadata. Keep this enabled for the richest dataset.

## `useBrowserForMore` (type: `boolean`):

If the initial HTML does not expose enough jobs, use Playwright only for listing discovery and Dealls' See More / Lebih Banyak interaction. Detail extraction remains HTTP-first.

## `maxLoadMoreClicks` (type: `integer`):

Safety limit for browser load-more interactions per listing URL. Lower values reduce browser work; higher values may be needed for larger maxItems targets.

## `concurrency` (type: `integer`):

Maximum number of job-detail HTTP requests processed at the same time. The default balances speed and source stability.

## `monitorMode` (type: `boolean`):

Persist state for this scope and classify repeat-run jobs as NEW, UPDATED, or UNCHANGED. When expiry verification is enabled, confirmed closed or unavailable jobs can also be emitted as EXPIRED.

## `emitChangesOnly` (type: `boolean`):

When monitor mode is enabled, write only NEW, UPDATED, and EXPIRED records. A successful run can therefore have an empty dataset when every seen job is UNCHANGED; check RUN\_SUMMARY for the comparison counts.

## `detectExpired` (type: `boolean`):

Verify previously seen jobs that are missing from the current listing before marking them EXPIRED. Missing-from-listing alone is not treated as expiry, which helps avoid false positives from pagination or changing listing order.

## `maxExpiredChecks` (type: `integer`):

Maximum number of missing previously seen jobs whose detail URLs can be verified in one run. Extra candidates are kept in state and deferred rather than marked expired.

## `monitorKey` (type: `string`):

Optional label used to isolate persistent state for different recurring workflows. Keep the same monitorKey and the same startUrls for repeat runs that should be compared with each other.

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

Optional Apify Proxy or custom proxy settings. Direct connections are the default to keep runs simple and economical; enable proxying only when needed for reliability.

## `requestTimeoutSeconds` (type: `integer`):

Maximum time for each listing or detail HTTP request before the Actor retries or fails that request gracefully.

## `maxRetries` (type: `integer`):

Number of retries for temporary network errors, timeouts, or retryable HTTP responses.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://dealls.com/en"
    }
  ],
  "maxItems": 18,
  "fetchDetails": true,
  "useBrowserForMore": true,
  "maxLoadMoreClicks": 20,
  "concurrency": 8,
  "monitorMode": false,
  "emitChangesOnly": false,
  "detectExpired": false,
  "maxExpiredChecks": 50,
  "monitorKey": "default",
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "requestTimeoutSeconds": 30,
  "maxRetries": 3
}
```

# Actor output Schema

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

Structured job records produced by this run. With emitChangesOnly enabled, this dataset can be empty when all currently seen jobs are UNCHANGED; use Run summary to confirm comparison counts.

## `summary` (type: `string`):

Discovery count, emitted-result count, NEW/UPDATED/UNCHANGED/EXPIRED totals, state sizes, expiry verification diagnostics, and warnings.

# 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 = {
    "startUrls": [
        {
            "url": "https://dealls.com/en"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("produkdigitalali/dealls-jobs-scraper-indonesia").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 = { "startUrls": [{ "url": "https://dealls.com/en" }] }

# Run the Actor and wait for it to finish
run = client.actor("produkdigitalali/dealls-jobs-scraper-indonesia").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 '{
  "startUrls": [
    {
      "url": "https://dealls.com/en"
    }
  ]
}' |
apify call produkdigitalali/dealls-jobs-scraper-indonesia --silent --output-dataset

```

## MCP server setup

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

```

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/QN6EW0tQxSRm8iTOl/builds/8AwM1jFKk1XKtgAkr/openapi.json
