# 104.com.tw Jobs Scraper — Taiwan Job Listings (`noahadler/104-com-tw-jobs`) Actor

Scrape public 104.com.tw (104 人力銀行) job listings by keyword and optional area code. Title, company, location, industry, appear date, job URL. Salary label only when 104 discloses it. HTTP JSON search API — no login, no CVs.

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

## Pricing

from $1.40 / 1,000 jobs

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
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

## 104.com.tw Jobs Scraper

**104.com.tw** (104 人力銀行) Taiwan jobs scraper by **keyword** + optional **area** code: title, company, city, industry, appear date, and absolute job URL — one clean Dataset row per public vacancy. Salary label only when 104 discloses it.

Built for **Taiwan hiring intel**, **salary benchmarking** (when disclosed), **LATAM/Asia jobs portfolio**, and PPE-friendly exports. No browser automation — HTTP + Chrome TLS fingerprint against 104’s public JSON search API (`/jobs/search/api/jobs`). **No login. No CVs. No candidate PII.**

> 104 returns job cards as JSON (`data[]`, ~20 per page). This Actor calls the same endpoint the website uses, GETs with `curl_cffi` (`chrome124`), maps fields to a stable schema, and paginates with `page=N` until `maxItems`. **TW residential proxy is recommended** (often works without proxy from clean IPs).

**Why this Actor:** Keyword search · Clean schema · No Playwright · No CV / VIP scrape · Salary when disclosed · Cheap PPE · CSV / API / webhooks ready

***

### Table of contents

1. [What this Actor does](#what-this-actor-does)
2. [What you get](#what-you-get)
3. [Features](#features)
4. [Input](#input)
5. [Output example](#output-example)
6. [Output fields](#output-fields)
7. [Quick start (API)](#quick-start-api)
8. [Use cases](#use-cases)
9. [Limitations](#limitations)
10. [FAQ](#faq)
11. [Keywords](#keywords)

***

### What this Actor does

| Step | Action |
|------|--------|
| 1 | Accept `keyword` + optional `area` / `maxItems` |
| 2 | `GET /jobs/search/api/jobs?keyword=…&kwop=7&page=N&pagesize=20&order=16` |
| 3 | Parse JSON `data[]` |
| 4 | Map each card → Dataset row (`jobId`, `title`, `companyName`, `salaryLabel`, `jobUrl`, …) |
| 5 | Paginate until `maxItems`, empty page, or partial page |

**Input:** keyword, area, max items, proxy.\
**Output:** one Dataset row per public job.

***

### What you get

**Job**

- `jobId`, `title`, `companyName`, `companyUrl`, `location`, `address`, `teaser`
- `industry`, `jobType`, `experienceCode`, `remoteWorkType` / `remoteLabel`, `employeeCount`

**Compensation / timing**

- `salaryLow`, `salaryHigh`, `salaryLabel` (only when 104 discloses numbers; otherwise null)
- `appearDate`

**Links / meta**

- `jobUrl` (`https://www.104.com.tw/job/{slug}`)
- `keyword`, `area`, `scrapedAt`, `error`, `errorMessage`

***

### Features

| Capability | Detail |
|------------|--------|
| **Keyword search** | e.g. `工程師`, `Python`, `護理師` |
| **Optional area** | 104 area codes (Taipei, Kaohsiung, …) |
| **No browser** | `curl_cffi` impersonate `chrome124` + `browserforge` headers |
| **Public JSON API** | Same endpoint as 104’s job list UI |
| **Proxy-aware** | TW RESIDENTIAL recommended |
| **PPE-friendly** | Public vacancies only — no CV harvesting |

***

### Input

| Field | Required | Description |
|-------|----------|-------------|
| `keyword` | Yes | Search term on 104.com.tw |
| `area` | No | Optional 104 area code |
| `maxItems` | No | Default 40, max 500 |
| `proxyConfiguration` | No | Default RESIDENTIAL + TW |

#### Example

```json
{
  "keyword": "工程師",
  "maxItems": 20,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": ["RESIDENTIAL"],
    "apifyProxyCountry": "TW"
  }
}
```

***

### Output example

```json
{
  "jobId": "7923632",
  "title": "設備工程師(嘉積廠)",
  "companyName": "嘉晶電子股份有限公司",
  "companyUrl": "https://www.104.com.tw/company/7erm71c",
  "location": "新竹市",
  "address": "創新一路17號",
  "salaryLow": 0,
  "salaryHigh": 0,
  "salaryLabel": null,
  "jobUrl": "https://www.104.com.tw/job/4ptww",
  "appearDate": "20261002",
  "industry": "半導體製造業",
  "jobType": 1,
  "experienceCode": 0,
  "remoteWorkType": 0,
  "remoteLabel": "on-site",
  "employeeCount": 700,
  "teaser": "半導體機台維護、異常處理及改善",
  "keyword": "工程師",
  "area": null,
  "scrapedAt": "2026-10-02T12:00:00Z",
  "error": false,
  "errorMessage": null
}
```

***

### Output fields

| Field | Notes |
|-------|--------|
| `salaryLabel` | Null when 104 shows 待遇面議 (`salaryLow=salaryHigh=0`) |
| `jobType` / `experienceCode` | Raw 104 codes from the card |
| `remoteLabel` | `on-site` / `remote` / `hybrid` when mappable |

***

### Quick start (API)

```bash
apify call noahadler/104-com-tw-jobs -i '{"keyword":"工程師","maxItems":20}'
```

***

### Use cases

- Track Taiwan hiring volume by keyword / city
- Benchmark disclosed salaries on 104
- Feed ATS / BI pipelines with public vacancy URLs

***

### Limitations

- **Public vacancies only** — does not scrape résumés, VIP dashboards, or post-login candidate data
- Salary often undisclosed (~⅓ of cards) — then `salaryLabel` is null
- Cloudflare may require TW residential proxy from some IPs
- Area filter needs 104’s numeric area codes (not free-text city names)
- Company contact phones/emails are not extracted as a product field

***

### FAQ

**Does this scrape CVs?**\
No. VIP clause (十) forbids automated collection of candidate personal data. This Actor only reads public job cards.

**Why is salary often empty?**\
104 allows 待遇面議. When both `salaryLow` and `salaryHigh` are 0, we leave `salaryLabel` null instead of inventing a value.

**Do I need a 104 account?**\
No. Public search JSON only.

***

### Keywords

`104.com.tw scraper`, `taiwan 104 jobs`, `104 人力銀行`, `taiwan job scraper`, `104 job listings`, `104.com.tw API`, `Taiwan jobs API`

# Actor input Schema

## `keyword` (type: `string`):

Search term as on 104.com.tw (e.g. 工程師, Python, 護理師, software engineer).

## `area` (type: `string`):

Optional 104 area code (e.g. 6001001000 Taipei City). Leave empty for all Taiwan. Codes come from 104’s public Area.json.

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

Maximum job rows to collect (1–500). Paginated via 104 API page=N (~20 results per page).

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

Recommended: Apify RESIDENTIAL + country TW. Direct access often works; proxy helps if Cloudflare blocks.

## Actor input object example

```json
{
  "keyword": "工程師",
  "maxItems": 40,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "TW"
  }
}
```

# Actor output Schema

## `jobs` (type: `string`):

No description

# 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 = {
    "keyword": "工程師"
};

// Run the Actor and wait for it to finish
const run = await client.actor("noahadler/104-com-tw-jobs").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "keyword": "工程師" }

# Run the Actor and wait for it to finish
run = client.actor("noahadler/104-com-tw-jobs").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "keyword": "工程師"
}' |
apify call noahadler/104-com-tw-jobs --silent --output-dataset

```

## MCP server setup

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

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/WVCRVWKKmQLTNVKRC/builds/iQfyqqyd894UbhIUd/openapi.json
