# CareerLink.vn Jobs Scraper (`premium_stapler/careerlink-jobs-scraper`) Actor

Scrapes job postings from CareerLink.vn by keyword, province and posting age, with company, salary, experience, contact and expiry fields, one row per posting.

- **URL**: https://apify.com/premium\_stapler/careerlink-jobs-scraper.md
- **Developed by:** [Vasso Vassiliades](https://apify.com/premium_stapler) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 job postings

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 does CareerLink.vn Jobs Scraper do?

**CareerLink.vn Jobs Scraper** turns a search on [CareerLink.vn](https://www.careerlink.vn), one of
Vietnam's larger job boards, into **structured data: one row per job posting**. You give it
keywords, a province and a posting age, and it returns the **job title, employer, province,
district or ward, salary, level, required experience, posting date, expiry date, contact name,
company website and company size**, plus the full description and requirements text.

It reads the board's own server-rendered pages and the JSON-LD `JobPosting` block on each job page,
so the data matches what a visitor sees. Because it runs on Apify, you get scheduling, an API,
integrations, run monitoring and proxy rotation without writing any of that yourself.

### Why scrape CareerLink.vn?

- **Recruitment lead generation.** Find the companies hiring right now in a province, with the
  contact name and company website printed on the ad.
- **Labour market research.** Track how many postings a keyword returns per province, and how
  salaries and experience requirements move over time.
- **Competitor hiring intelligence.** Watch which roles a competitor is opening and where.
- **Candidate sourcing tools.** Feed a job aggregator or an internal jobs board with fresh listings.
- **Vietnamese search works.** Keywords with diacritics, for example `xuất nhập khẩu` or
  `kho vận`, return the same results as they do on the site.

### What data does this CareerLink scraper extract?

| Field             | Type   | Description                                                                            |
| ----------------- | ------ | -------------------------------------------------------------------------------------- |
| `title`           | string | Job title as posted                                                                    |
| `company`         | string | Employer name                                                                          |
| `city`            | string | Province, as the board writes it. The province you asked for when the ad lists several |
| `locations`       | array  | Every province on the posting                                                          |
| `address`         | string | District or ward, as the ad gives it, for example `Tân Phú`                            |
| `salary`          | string | Salary text, `null` when the board says the salary is negotiable                       |
| `level`           | string | Career level, for example `Nhân viên`, `Trưởng nhóm / Giám sát`                        |
| `expFrom`         | number | Minimum experience in years, `0` for no experience required                            |
| `postedAt`        | string | Posting date, `yyyy-mm-dd`                                                             |
| `expiresAt`       | string | Application deadline, `yyyy-mm-dd`                                                     |
| `contactName`     | string | The person or department named on the ad, most often HR (`Phòng Nhân sự`)              |
| `website`         | string | Employer website from the company block                                                |
| `companySize`     | string | Staff-count band, for example `500 - 999 nhân viên`                                    |
| `companyIndustry` | string | Industry the board files the employer under                                            |
| `emails`          | array  | Email addresses found in the ad text, lowercased and deduplicated                      |
| `phones`          | array  | Vietnamese phone numbers found in the ad text                                          |
| `descr`           | string | Full description, HTML stripped                                                        |
| `req`             | string | Full requirements section, HTML stripped                                               |
| `url`             | string | Canonical link to the posting                                                          |

Every row also carries `source`, `sourceId`, `companyPage`, `employmentType`, `education`,
`applicationNote`, `cityCode` and `scrapedAt`. Two more fields exist for compatibility across our
job-board Actors: `category` repeats `companyIndustry`, and `applicants` is reserved and is always
`null` on CareerLink, because the board does not publish an applicant count.

### How to scrape CareerLink.vn step by step

1. Open the Actor and go to the **Input** tab.
2. Type one or more **search keywords**. Each keyword runs as its own search and the results are
   merged and deduplicated. Each keyword also gets its own share of **Maximum postings**, so three
   keywords with a cap of 60 return about 20 rows each rather than 60 rows from whichever search
   the board answered first. A keyword that runs out of results lends its unused share to the rest.
3. Pick a **province** from the list, or leave it on **All of Vietnam**.
4. Set **Posted within (days)** to the freshness you need, for example 7 for this week's ads.
5. Set **Maximum postings**. This is your cost control, because you pay per posting.
6. Leave **Open each posting for full details** on for contact, experience and expiry fields. Turn
   it off for a faster, cheaper listing-only run.
7. Click **Start**, then read the results in the **Output** tab or pull them from the API.

### How much does it cost to scrape CareerLink.vn?

This Actor is priced **pay per event**:

- **USD 0.003 per job posting** written to the dataset.
- **USD 0.00005 per run start**, which is a rounding error next to the postings.

So 100 postings cost about **USD 0.30**, and 1,000 postings cost about **USD 3.00**. You are
charged for rows you actually receive, so setting **Maximum postings** caps the bill exactly.
Apify's free tier includes monthly usage credit, which is enough to try the Actor and to run small
searches without paying anything.

**If you set `maxItems` to 0 there is no row cap, and the bill is bounded by pages instead.** An
uncapped run walks up to `maxPages` search result pages per keyword, 50 postings a page, so the
default `maxPages` of 20 is up to about **1,000 postings per keyword, roughly USD 3.00 per
keyword**, and the maximum of 200 pages is up to about 10,000 postings, roughly USD 30. Two
practical guards:

- Leave `maxItems` set. It is the only setting that caps the bill to an exact figure.
- Set a **max total charge** on the run or the schedule. Apify stops storing rows and aborts the
  run once that limit is reached, whatever the Actor asks for.

**The per-posting price already covers the proxy.** The honest detail, measured on 2026-09-10:

- CareerLink serves an **empty result page** to requests from outside Vietnam. Two identical
  listing-only runs on the platform proved it: Apify datacenter proxy returned 0 rows, residential
  Vietnam returned 10 of 10 rows from the same search, with the board's own total of 227 visible in
  the log.
- The Actor therefore defaults `proxyConfiguration` to **Apify Residential proxy, country VN**, and
  you should leave it there. Turning it off does not save you anything, it just returns nothing.
- Residential traffic measured at **about 42 KB per page fetched**. That is roughly
  **USD 0.0000064 per posting** on a listing-only run (one page carries 50 postings) and about
  **USD 0.00033 per posting** with `fetchDetails` on, which adds one page per posting.
- Residential proxy has to be available on your Apify account. If a run comes back with zero rows
  and the log says the search page carried no result cards, check the **Proxy** page of your Apify
  account first: that is almost always residential access, not a fault in the Actor.

### Input

See the **Input** tab for the full form. In JSON:

```json
{
    "keywords": ["logistics", "xuất nhập khẩu"],
    "city": "HCM",
    "postedWithinDays": 30,
    "maxItems": 100,
    "maxPages": 20,
    "fetchDetails": true,
    "maxConcurrency": 3,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "VN"
    }
}
```

- `keywords` is required. Vietnamese keywords with diacritics are supported and are encoded for you.
- `city` takes a CareerLink province code, for example `HCM`, `HN`, `BD`, `DNI`. The select list
  shows the English and Vietnamese name of each province.
- `postedWithinDays` accepts 1 to 90. The board's own filter only offers 1, 3, 7, 14 and 30 days,
  so a value in between is fetched with the next larger filter and then trimmed exactly in code.
  Above 30 days the board has no filter at all, so results are read unfiltered and trimmed in code,
  which costs more requests for the same rows.
- `maxItems` is the row cap, split evenly between the keywords. 0 means no row cap, and then
  `maxPages` is what bounds the run.
- `maxPages` is how many search result pages are walked per keyword, 1 to 200, default 20. One page
  is 50 postings. See the cost section for what an uncapped run can add up to.
- `maxConcurrency` is how many pages are fetched at once, 1 to 5, default 3. CareerLink is a small
  server and stops answering an IP that hits it in wide parallel bursts, so leave it low.
- `proxyConfiguration` must stay on **Residential, country VN**. See the cost section above for why.

### Output

Each posting is one dataset item. You can download the dataset in various formats such as JSON,
HTML, CSV, or Excel, or fetch it from the API.

```json
[
    {
        "source": "CareerLink",
        "sourceId": "3615540",
        "title": "Sales Executive (Textile/Garment/Apparel) - TIẾNG HÀN",
        "company": "SGS Vietnam Ltd.",
        "companySize": "1.000 - 4.999 nhân viên",
        "companyIndustry": "Bán hàng / Kinh doanh, Dệt may / Da giày, Hóa chất / Sinh hóa / Thực phẩm",
        "companyPage": "https://www.careerlink.vn/viec-lam-cua/sgs-vietnam-ltd-./80556",
        "website": null,
        "city": "Hồ Chí Minh",
        "address": "Tân Phú",
        "level": "Nhân viên",
        "salary": null,
        "postedAt": "2026-09-07",
        "expiresAt": "2026-10-07",
        "url": "https://www.careerlink.vn/tim-viec-lam/sales-executive-textile-garment-apparel-tieng-han/3615540",
        "contactName": "Phòng Nhân sự",
        "phones": [],
        "applicants": null,
        "descr": "- Identify potential clients and approach them for selling SGS services ...",
        "req": "- 2-3 years experience in related products or services, or in textile garment industry ...",
        "category": "Bán hàng / Kinh doanh, Dệt may / Da giày, Hóa chất / Sinh hóa / Thực phẩm",
        "expFrom": 2,
        "emails": [],
        "scrapedAt": "2026-09-10T06:42:49.411Z",
        "locations": ["Hồ Chí Minh"],
        "employmentType": "FULL_TIME",
        "education": "Cử nhân",
        "applicationNote": "- Please send your application by online, mail or direct to",
        "cityCode": "HCM"
    },
    {
        "source": "CareerLink",
        "sourceId": "3617563",
        "title": "Lab Technician (Chemical) (Open for Fresher)",
        "company": "SGS Vietnam Ltd.",
        "companySize": "1.000 - 4.999 nhân viên",
        "companyIndustry": "Mới tốt nghiệp / Thực tập, Hóa chất / Sinh hóa / Thực phẩm",
        "companyPage": "https://www.careerlink.vn/viec-lam-cua/sgs-vietnam-ltd-./80556",
        "website": "https://www.sgs.com/vi-vn",
        "city": "Hồ Chí Minh",
        "address": "Tây Thạnh",
        "level": "Nhân viên",
        "salary": null,
        "postedAt": "2026-09-09",
        "expiresAt": "2026-10-09",
        "url": "https://www.careerlink.vn/tim-viec-lam/lab-technician-chemical-open-for-fresher/3617563",
        "contactName": "Phòng Nhân sự",
        "phones": [],
        "applicants": null,
        "descr": "- Follow the practice of the Quality Assurance System ...",
        "req": "- College/ University Degree or above in chemistry ...",
        "category": "Mới tốt nghiệp / Thực tập, Hóa chất / Sinh hóa / Thực phẩm",
        "expFrom": 0,
        "emails": [],
        "scrapedAt": "2026-09-10T06:43:57.565Z",
        "locations": ["Hồ Chí Minh"],
        "employmentType": "FULL_TIME",
        "education": "Cao đẳng",
        "applicationNote": null,
        "cityCode": "HCM"
    }
]
```

### Tips for cheaper and better runs

- **Turn `fetchDetails` off** when you only need the list of who is hiring. It skips one request per
  posting, so runs finish faster, and the row still carries title, company, province, salary,
  posting date and link.
- **Use a short posted window.** `postedWithinDays: 7` on a weekly schedule keeps every run small
  and gives you only what is new.
- **One idea per keyword.** `["logistics", "kho vận", "xuất nhập khẩu"]` covers more of the board
  than one long phrase, because the board matches the phrase you type.
- **Set `maxItems`** on the first run of a new search to see how much the search returns before you
  open it up. The log prints the board's own result count for each keyword next to the rows kept.
- **Schedule it** and send new rows straight to a spreadsheet, a webhook or your CRM through an
  Apify integration.

### FAQ, disclaimer and support

#### Does CareerLink publish employer emails and phone numbers?

No. The board has no employer email or phone field. This Actor harvests addresses and numbers that
the employer typed into the ad text itself, so `emails` and `phones` are often empty. A number is
only reported when it fits Vietnam's number plan, so salary figures and tax codes written in the
same paragraph are not passed off as contacts. That is the
board's data, not a scraping failure.

#### Who is `contactName`?

Whoever the employer put in the contact block. Most often it is a department rather than a person,
for example `Phòng Nhân sự` (HR), and sometimes it is a named recruiter such as `Ms. Hoàng Oanh`.

#### Can I filter by district or by salary?

Not in this Actor. It filters by keyword, province and posting age, which is what the board's own
URL supports. Salary and level come back on every row, so you can filter them yourself afterwards.

#### Why are some rows unrelated to my keyword?

CareerLink pads its search result pages with promoted postings, and they sit at the top of the page.
A search whose own counter reads 46 results still returns 50 cards, so a small run picks up a few
postings outside your keyword. The share shrinks as you raise **Maximum postings**, because the
padding is a handful of cards per page rather than a proportion of the results.

#### Why did I get fewer rows than the number shown on the site?

Three reasons: `maxItems` caps the run, postings older than your window are dropped, and the board's
province filter sometimes leaks postings from other provinces, which this Actor removes. A posting
dropped for one of those reasons frees its slot and the Actor reads another result page to replace
it, so a capped run still returns the number of rows you asked for while the board has results left.
The run log prints the board's own count next to what was kept, so you can see the difference.

#### Is scraping CareerLink.vn legal?

Our Actors are ethical and do not extract any private user data, such as email addresses, gender, or
location. They only extract what the user has chosen to share publicly. We therefore believe that our
Actors, when used for ethical purposes by Apify users, are safe. However, you should be aware that
your results could contain personal data. Personal data is protected by the GDPR in the European
Union and by other regulations around the world. You should not scrape personal data unless you have
a legitimate reason to do so. If you're unsure whether your reason is legitimate, consult your
lawyers. This Actor reads pages that CareerLink.vn allows crawlers to read, and does not log in or
bypass any protection.

#### Something is broken or missing

Open the **Issues** tab on this Actor with the run ID and the input you used. Feature requests are
welcome there too. Programmatic access to runs and results is documented on the **API** tab.

# Actor input Schema

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

One search per keyword. Vietnamese keywords with diacritics work, for example "xuất nhập khẩu". Each keyword is searched separately and the results are merged and deduplicated. When you set a maximum, each keyword gets its own share of it, and a keyword that runs out of results lends its unused share to the others.

## `city` (type: `string`):

Limit the search to one province. Leave it on "All of Vietnam" to search the whole country. A posting that lists several provinces is kept when one of them is the province you picked.

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

Only keep postings published in the last N days. The board filters by 1, 3, 7, 14 or 30 days, so any value in between is fetched with the next larger filter and trimmed exactly in code. Above 30 days the board has no filter at all: results are read unfiltered and trimmed in code, which costs more requests.

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

Stop after this many rows across all keywords, split evenly between them. This is the main cost control: you pay per posting. Set 0 for no row cap, in which case "Maximum search pages per keyword" is what bounds the run, at 50 postings a page.

## `maxPages` (type: `integer`):

How many search result pages to walk per keyword, 50 postings a page. This is what bounds a run with no row cap: 20 pages is up to about 1,000 postings per keyword, which at USD 0.003 a posting is about USD 3.00 per keyword. Leave the row cap set if you want an exact bill.

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

On: open every job page for experience, expiry date, contact name, company website, company size, description and requirements. Off: use the result cards only (title, company, province, salary, posted date, link), which is faster and cheaper.

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

How many pages to fetch at once. CareerLink is a small server and stops answering an IP that hits it in wide parallel bursts, so keep this low. 1 to 5, default 3.

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

Keep this on Residential proxy in Vietnam. CareerLink serves an empty result page to requests from outside Vietnam, including Apify's datacenter IPs, so a run without a Vietnamese residential proxy returns zero rows. Measured on 2026-09-10: datacenter proxy 0 rows, residential Vietnam 10 of 10 rows from the same search.

## Actor input object example

```json
{
  "keywords": [
    "logistics"
  ],
  "city": "",
  "postedWithinDays": 30,
  "maxItems": 100,
  "maxPages": 20,
  "fetchDetails": true,
  "maxConcurrency": 3,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "VN"
  }
}
```

# Actor output Schema

## `results` (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 = {
    "keywords": [
        "logistics"
    ],
    "postedWithinDays": 30,
    "maxItems": 100,
    "maxPages": 20,
    "maxConcurrency": 3,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "VN"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("premium_stapler/careerlink-jobs-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 = {
    "keywords": ["logistics"],
    "postedWithinDays": 30,
    "maxItems": 100,
    "maxPages": 20,
    "maxConcurrency": 3,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "VN",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("premium_stapler/careerlink-jobs-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 '{
  "keywords": [
    "logistics"
  ],
  "postedWithinDays": 30,
  "maxItems": 100,
  "maxPages": 20,
  "maxConcurrency": 3,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "VN"
  }
}' |
apify call premium_stapler/careerlink-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,premium_stapler/careerlink-jobs-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/asupME018cDm94KPy/builds/uuGWk3BtJSYjCP8WK/openapi.json
