

# 如何透過 API 收集 Naukri 公開職位列表

用 Apify API 執行一次公開 Naukri 搜尋。設定一個固定的已儲存列數上限。等到該次執行結束。分頁讀取已儲存的列。把每個來源職位 ID 和網址與執行紀錄一起保留。執行紀錄是已儲存的證明，說明該次執行做了甚麼。你會得到你所要求的 Naukri 樣本。印度職位的完整列表不在這個樣本之內。

API 是你的程式向另一項服務發出的請求。Apify 是啟動工作並儲存結果的託管服務。Actor 是一個託管的數據收集程式。[Naukri Jobs Actor](https://apify.com/chronometrica/naukri-jobs-scraper) 就是此方法中的 Actor。它讀取公開的 Naukri 頁面。它不使用 Naukri 登入。Task 是已儲存的輸入預設。[目前的公開 Task](https://apify.com/chronometrica/naukri-jobs-scraper/examples/naukri-jobs-api-dataset) 只是起始輸入。它不會為你分析職位。Actor 儲存來源列。你核對執行紀錄，並為你的專案選擇列。

此路徑使用 Apify 的 API。它不是官方 Naukri API。它不是私人公司資料庫。它不是印度職位的完整索引。它展示的是這一個公開 Actor。

## 1. 設定一次搜尋和列數上限

開始之前，先寫下你想要的列。選擇一個職位。選擇一個印度城市，或把城市留空。選擇來源刊登應有多新。選擇一個較小的已儲存列數上限。

目前的公開 Actor 接受以下輸入：

- `searchTerm` 每一行接受一個關鍵字。
- `location` 可選。
- `maxItems` 是列數上限。它限制整次執行所儲存的列。這個上限不是每個關鍵字行各自計算。如果你省略它，數字上限便不存在。此方法一定要傳送一個數字。
- `postedWithinDays` 是文字。使用 "0"、"1"、"3"、"7"、"15" 或 "30"。文字 "0" 表示任何刊登日期。
- `experienceYears` 可選。它是 0 至 30 的整數。Fresher 指該搜尋設定中先前工作經驗很少或沒有。數值 0 要求 Fresher 搜尋設定。此方法不判斷是否適合該職位。
- `sortBy` 是 `relevance` 或 `date`。
- `excludeCompanies` 可選。下面的例子不使用它。
- `searchDepth` 是 `standard`、`extended` 或 `deep`。`standard` 是範圍較窄的第一次檢查。它讀取的來源頁少於另外兩項。此方法不需要更深的搜尋。

目前的公開 Task 儲存 `searchTerm` "software engineer"、`postedWithinDays` "7"、`sortBy` "date" 和 `searchDepth` "standard"。把你的職位放進 `searchTerm`。如果你只要一個城市，把城市放進 `location`。設定你自己的 `maxItems`。

一次已核對的對照測試使用了不同的值。它使用一個關鍵字 "software engineer"。它沒有傳送地點。它使用 `postedWithinDays` "0"、`maxItems` 2、`sortBy` "relevance" 和 `searchDepth` "standard"。該次執行儲存了兩個互不相同的 Naukri 職位 ID。每一列都有職稱、公司名稱和地點。每一列都有直接的 Naukri `jobUrl`。每個 `sourceUrl` 都符合所請求的查詢。`source` 是 naukri.com。每一列都有 `observedAt`。兩列都包含 `descriptionText`。兩列都包含 `experienceRaw`。兩個數字範圍是 0-5 和 0-2。這些 0 是觀察到的最低年數。它們不是空白欄位。該對照測試沒有傳送 `experienceYears`。它不能證明已套用 Fresher 篩選。

該次已核對的執行以成功結束。它的 OUTPUT 紀錄顯示這些已知值。`rowsSaved` 是 2。`requestedMaxItems` 是 2。`pagesRequested` 是 1。執行紀錄報告已見到 20 個候選。`failedRequests` 是 0。`blockedRequests` 是 0。`stopReason` 是 `max_items_reached`。該次執行達到上限。兩列已儲存的資料不是每一個符合的職位。該對照測試沒有寫明 `searchesQueued`、`searchesFinished` 或 `sourceStates`。這三個值未知。未知不等於 0。

已儲存的列可以包含下列欄位。

- `schemaVersion`
- `jobId`
- `title`
- `companyName`
- `location`
- `experienceRaw`
- `experienceMinYears`
- `experienceMaxYears`
- `skills`
- `descriptionText`
- `postedAt`
- `postedAtRaw`
- `jobUrl`
- `searchTerm`
- `searchLocation`
- `sourceUrl`
- `pageNumber`
- `rankOnPage`
- `status`
- `statusReason`
- `source`
- `observedAt`

可選的值可以不存在。不存在就是未知。它不是 0。`descriptionText` 可以存在，但仍缺少部分僱主文字。它也可以缺少一項要求。`postedAt` 和經驗欄位是來源所顯示的內容。它們不是僱主將會聘用的承諾。`observedAt` 是 Actor 見到該列的時間。它不是 Naukri 首次刊登該職位的時間。把這兩個時間分開保留。

`searchTerm`、`searchLocation` 和 `observedAt` 屬於原始來源列。如果你日後加入自己的 query 欄，那個欄是你建立的。它不是名為 query 的來源欄位。

## 2. 啟動執行並保留執行 ID

使用你自己的 Apify 帳戶。把令牌放在你的專案已經用來存放秘密的安全位置。令牌是一項秘密，證明該呼叫屬於你的帳戶。不要把真實令牌放進文章。不要放進瀏覽器程式碼。不要放進日誌。不要放進一張含有網址的圖片。這次公開讀取不需要 Naukri 登入。

啟動呼叫傳回關於該次執行的資訊。它不傳回職位列。一次執行是 Actor 的一次啟動。以 JSON 傳送輸入。JSON 是用於結構化數據的文字格式。加入標頭 `Content-Type: application/json`。在 `Authorization` 標頭傳送令牌。寫下 `Bearer`、一個空格，然後是令牌。下面的內容是原創教學輸入。它不是即時結果。

```http
POST https://api.apify.com/v2/acts/chronometrica~naukri-jobs-scraper/runs
Authorization: Bearer <APIFY_TOKEN>
Content-Type: application/json

{
  "searchTerm": "data analyst",
  "location": "Bengaluru",
  "maxItems": 2,
  "postedWithinDays": "7",
  "sortBy": "date",
  "searchDepth": "standard"
}
```

2. 啟動執行並保留執行 ID

此內容要求的職位是數據分析師。它把地點限制為 Bengaluru。它最多儲存 2 列。它要求最近 7 日的刊登。它按日期排序。它使用 standard 深度。[啟動執行的說明文件](https://docs.apify.com/api/v2/actors-runs-post) 描述此端點。同一個 API 讓你可以設定逾時和費用上限。費用上限會在你選擇的成本限制停止該次執行。第一次執行時，兩個限制都要設定。本文不提供價格。

從回應中儲存 `data.id`。那就是執行 ID。同時儲存該服務所儲存的輸入和選項。執行 ID、資料集 ID 和儲存 ID 是三個不同的值。保持它們互不混淆。

## 3. 等待終結狀態

用同一個私密標頭讀取該次執行。每次讀取之間等待一段短時間。狀態成為終結狀態時便停止。終結狀態是已結束的狀態。已結束的值是 `SUCCEEDED`、`FAILED`、`TIMED-OUT` 和 `ABORTED`。

```http
GET https://api.apify.com/v2/actor-runs/<RUN_ID>
Authorization: Bearer <APIFY_TOKEN>
```

3. 等待終結狀態

儲存 `status`、`buildId`、`startedAt`、`finishedAt`、`defaultDatasetId` 和 `defaultKeyValueStoreId`。`SUCCEEDED` 表示 Actor 已完成。它不表示來源已被完整覆蓋。`FAILED`、`TIMED-OUT` 和 `ABORTED` 在你信任這些列之前需要查看。[執行狀態說明文件](https://docs.apify.com/api/v2/actor-run-get) 描述這次讀取。

## 4. 讀取 OUTPUT 執行紀錄

OUTPUT 是 Actor 的摘要紀錄。從鍵值儲存讀取它。鍵值儲存為該次執行保存具名紀錄。使用 `defaultKeyValueStoreId` 作為儲存 ID。

```http
GET https://api.apify.com/v2/key-value-stores/<STORE_ID>/records/OUTPUT
Authorization: Bearer <APIFY_TOKEN>
```

4. 讀取 OUTPUT 執行紀錄

讀取這些欄位：

- `rowsSaved` 是已儲存的列數。
- `requestedMaxItems` 是你要求的上限。
- `searchesQueued` 是已排入隊列的搜尋工作。
- `searchesFinished` 是已完成的搜尋工作。
- `pagesRequested` 是所請求的來源頁數。來源頁是一個 Naukri 結果頁。它不是對已儲存資料集的一次讀取。
- `blockedRequests` 是被封鎖的來源請求數目。
- `failedRequests` 是失敗的來源請求數目。
- `sourceStates` 說明來源如何結束。
- `stopReason` 說明 Actor 為何停止。

如果 `rowsSaved` 等於 `requestedMaxItems`，而且 `stopReason` 是 `max_items_reached`，上限已停止該次執行。這些列仍然可以有用。它們不是完整搜尋。一個被點算為 0 的值表示 Actor 報告沒有。缺少的欄位是未知。不要把未知改成 0。

在你解釋一個空檔案之前，先使用 `sourceStates`。空結果可以表示來源沒有傳回任何列。也可以表示未能到達來源。這兩個結果不同。如果 `failedRequests` 或 `blockedRequests` 高於 0，不要把該城市當成沒有資料。如果缺少 `sourceStates`，來源結果便是未知。無論哪一種情況，都保留執行紀錄。

## 5. 在執行結束後讀取資料集

資料集是已儲存的列項目集合。在執行成為終結狀態之後才讀取它。仍在增長的資料集並不穩定。先完成該次執行。如果你需要在結束前取得列，要有意地設計那種情況。不要把中間的一頁當成最終集合。

```http
GET https://api.apify.com/v2/datasets/<DATASET_ID>/items?format=json&offset=0&limit=2
Authorization: Bearer <APIFY_TOKEN>
```

5. 在執行結束後讀取資料集

回應是一個 JSON 陣列。`offset` 是要跳過的已儲存列數。`limit` 是這次讀取要傳回的列數。這次讀取是資料集的一頁。它不是 Naukri 來源頁。[資料集項目說明文件](https://docs.apify.com/api/v2/dataset-items-get) 描述此呼叫。點算陣列中的列數。把該數目加到 `offset`。再讀取一次。為每一次讀取保留一份小執行紀錄。該紀錄需要 offset、limit、傳回數目，以及任何讀取錯誤。

對於一個已完成、有兩列的資料集，第一次讀取可以傳回兩列。下一次讀取使用 offset 2 和 limit 2。它可以傳回一個空陣列。空陣列的傳回數目是 0。那個 0 表示此頁沒有更多列。它不表示較早的列已消失。已讀列數是 2。如果 `rowsSaved` 是 2，這些數目相符。

如果一次資料集讀取失敗，把該失敗保留在執行紀錄中。不要用空陣列取代失敗的讀取。尚未讀取的列仍然未知。

## 6. 篩選之前先儲存身分

儲存原始列。把 `jobId` 儲存為文字。保留前導零。如果程式把 ID 儲存為數字，可能會去掉這些零。在職位 ID 前加入來源前綴 `naukri:`。該前綴是命名空間。命名空間顯示哪個網站建立了該 ID。另一個職位網站可以用相同數字表示不同職位。它的命名空間保持分開。

同時儲存 `jobUrl`、`sourceUrl`、原始 `searchTerm`、原始 `searchLocation`、`observedAt` 和執行 ID。檢查身分是否存在。檢查你預期的類型。如果缺少 `jobId`，該列保持未能對應。未能對應表示你尚未能把該列連到一個來源職位。不要為它建立來源鍵。

一次觀察是對一個來源列的一次已儲存所見紀錄。同一刊登在較後的執行中，是對一個已知來源職位的新觀察。它不會自動成為一個新的在招職位。保持[重複列](/zh-Hant/articles/%E7%A7%BB%E9%99%A4%E9%87%8D%E8%A4%87%E8%81%B7%E4%BD%8D%E5%88%8A%E7%99%BB%E4%B8%A6%E6%AD%B8%E7%B5%84%E5%8F%AF%E8%83%BD%E9%87%8D%E6%96%B0%E5%88%8A%E7%99%BB%E7%9A%84%E8%81%B7%E4%BD%8D/)可見。保持同一個身分的兩列之間的衝突可見。然後套用你的專案規則。當你選出有用的列時，把失敗的列保留在執行紀錄中。

薪酬的含義不在此方法之內。關於是否適合職位的規則不在此方法之內。此方法不提交申請。日後的排程是另一個選擇。本文不啟動重複收集。

## 7. 整理一份已完成的執行紀錄

本節的故事是原創虛構。它不是即時測試輸出。它不是產品結果。ID、網址和時間是為教學而編造的。不要請求這些故事網址。不要把這些列當成真實的 Naukri 列。

虛構執行 R1 使用啟動一節的教學內容。故事中的請求是一個 POST，送往 `https://api.apify.com/v2/acts/chronometrica~naukri-jobs-scraper/runs` ，並附上 `Content-Type: application/json`。故事的啟動回應包含 `data.id` "R1"。它不包含職位列。

R1 其後以狀態 `SUCCEEDED` 結束。故事的資料集 ID 是 D1。故事的儲存 ID 是 K1。故事中已儲存的執行紀錄如下：

```json
{
  "data": {
    "id": "R1",
    "status": "SUCCEEDED",
    "buildId": "fiction-build",
    "startedAt": "2026-04-01T09:00:00Z",
    "finishedAt": "2026-04-01T09:05:00Z",
    "defaultDatasetId": "D1",
    "defaultKeyValueStoreId": "K1"
  }
}
```

7. 整理一份已完成的執行紀錄

故事的 OUTPUT 紀錄是：

```json
{
  "rowsSaved": 2,
  "requestedMaxItems": 2,
  "failedRequests": 0,
  "stopReason": "max_items_reached"
}
```

7. 整理一份已完成的執行紀錄

在這個故事中，該 OUTPUT 紀錄沒有給出 `blockedRequests`、`searchesQueued`、`searchesFinished`、`pagesRequested` 和 `sourceStates`。讓它們保持未知。不要為它們寫下 0。`failedRequests` 是 0，因為故事說明沒有失敗的請求。那個 0 是已知的。

第一次資料集讀取使用 offset 0 和 limit 2。它傳回這個陣列：

```json
[
  {
    "jobId": "000101",
    "jobUrl": "https://www.naukri.com/example-fiction/000101",
    "sourceUrl": "https://www.naukri.com/example-fiction/search",
    "searchTerm": "data analyst",
    "searchLocation": "Bengaluru",
    "source": "naukri.com",
    "observedAt": "2026-04-01T09:04:00Z"
  },
  {
    "jobId": "000102",
    "jobUrl": "https://www.naukri.com/example-fiction/000102",
    "sourceUrl": "https://www.naukri.com/example-fiction/search",
    "searchTerm": "data analyst",
    "searchLocation": "Bengaluru",
    "source": "naukri.com",
    "observedAt": "2026-04-01T09:04:00Z"
  }
]
```

7. 整理一份已完成的執行紀錄

傳回數目是 2。這些故事列不包含 `postedAt`。刊登時間未知。它不是 0。下一次讀取使用 offset 2 和 limit 2。它傳回：

```json
[]
```

7. 整理一份已完成的執行紀錄

傳回數目是 0。已讀列數是 2。`rowsSaved` 是 2。數目相符：2 等於 2。已知來源 ID 是 `naukri:000101` 和 `naukri:000102`。那是兩個已知 ID。前導零保留在文字中。兩列都保留故事的來源網址、原始查詢欄位和 `observedAt`。上限表示兩個被觀察到的刊登。它不表示 Bengaluru 只有兩個在招職位。

同一虛構的後段是執行 R2。故事說 R2 再次傳回 `000102`，並傳回 `000103`。它沒有給出另外一份 OUTPUT 紀錄。不要為 R2 編造 `stopReason` 或請求數目。它的兩列故事資料是：

```json
[
  {
    "jobId": "000102",
    "jobUrl": "https://www.naukri.com/example-fiction/000102",
    "sourceUrl": "https://www.naukri.com/example-fiction/search",
    "searchTerm": "data analyst",
    "searchLocation": "Bengaluru",
    "source": "naukri.com",
    "observedAt": "2026-04-08T09:04:00Z"
  },
  {
    "jobId": "000103",
    "jobUrl": "https://www.naukri.com/example-fiction/000103",
    "sourceUrl": "https://www.naukri.com/example-fiction/search",
    "searchTerm": "data analyst",
    "searchLocation": "Bengaluru",
    "source": "naukri.com",
    "observedAt": "2026-04-08T09:04:00Z"
  }
]
```

7. 整理一份已完成的執行紀錄

把兩列 R2 都儲存為新的觀察。只有 `naukri:000103` 是新的已知來源 ID。`naukri:000102` 已經已知。

算術使用普通計數：

- R1 觀察 = 2。
- R2 觀察 = 2。
- 全部觀察 = 2 + 2 = 4。
- R1 之後的已知 ID = 2。
- R2 上的新已知 ID = 1。
- R2 上重複的已知 ID = 1。
- 兩次執行之後的已知 ID = 2 + 1 = 3。
- 這四列中未能對應的 ID = 0，因為每一列故事資料都有 `jobId`。

四次觀察不是四個職位。三個已知來源 ID 不是三個僱主職位。故事沒有僱主之間的獨立配對。它不能證明有三個空缺。

| 檢查 | 數目 |
| --- | --- |
| R1 觀察 | 2 |
| R2 觀察 | 2 |
| 全部觀察 | 4 |
| 已知來源 ID | 3 |
| R2 中的新已知 ID | 1 |
| R2 中重複的已知 ID | 1 |
| 四列中未能對應的 ID | 0 |
| 已證明的僱主職位 | 未顯示 |

還有一次額外的虛構檢查，它不屬於那四列。如果一次下載在同一頁重複 `000102`，記錄該重複。不要建立第二個身分。已知 ID 仍然是 `naukri:000102`。如果另一列故事資料有職稱但沒有 `jobId`，把未能對應加 1。不要建立例如 `naukri:unknown` 的鍵。

把原始列、執行 ID 和這份執行紀錄交給你的專案。加上簡短說明：這些列來自公開 Naukri 頁面，並經由此 Actor 取得。加上 `observedAt` 中的時間。然後專案便可以使用這個樣本。上限不是完整市場。

## 下一步

整理一次設有上限的執行，並儲存把每一列連到其來源的執行紀錄。在使用這些列之前先做這件事。從 [Naukri Jobs Actor](https://apify.com/chronometrica/naukri-jobs-scraper) 和[目前的公開 Task](https://apify.com/chronometrica/naukri-jobs-scraper/examples/naukri-jobs-api-dataset) 開始。

## 來源與延伸閱讀

- [Naukri Jobs Actor](https://apify.com/chronometrica/naukri-jobs-scraper)
- [目前的公開 Naukri Jobs Task](https://apify.com/chronometrica/naukri-jobs-scraper/examples/naukri-jobs-api-dataset)
- [啟動 Actor 執行](https://docs.apify.com/api/v2/actors-runs-post)
- [取得 Actor 執行](https://docs.apify.com/api/v2/actor-run-get)
- [取得資料集項目](https://docs.apify.com/api/v2/dataset-items-get)
- [關於印度職位列表 API 的公開問題](https://www.reddit.com/r/developersIndia/comments/1szvizh/is_there_any_public_api_for_indian_job_listing/)