# Naukri Job Scraper (India) - Skills, Salary & Full Description (`lanternlab318/naukri-jobs`) Actor

- **URL**: https://apify.com/lanternlab318/naukri-jobs.md
- **Developed by:** [Lantern Lab](https://apify.com/lanternlab318) (community)
- **Categories:** Jobs
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.20 / 1,000 job adverts

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?

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

## Naukri Job Scraper (India) - Skills, Salary & Full Description

Search **Naukri.com** by keyword, city and years of experience, and get the public job adverts back as clean rows - including the **full advert description at no extra charge**.

### What you get

One row per job advert, with every field Naukri shows on its own search results page:

| Field | What it is |
| --- | --- |
| `jobId` | Naukri's own advert id - stable, and what rows are deduplicated on |
| `title` | Job title as posted |
| `companyName`, `companyId` | The hiring company and its Naukri id |
| `experienceText` | The experience line Naukri prints, e.g. `2-5 Yrs` |
| `minExperienceYears`, `maxExperienceYears` | The same range as numbers |
| `salaryText` | The salary line as shown, e.g. `3-5 Lacs PA` or `Not disclosed` |
| `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryDisclosed` | The figures when the advertiser published them, `null` when hidden |
| `locations[]` | Every city on the advert, as a list |
| `skills[]` | The full skill/tag list, as a list |
| `postedAt` | When Naukri says it was posted (ISO 8601 UTC; `publishedAt` is the same instant) |
| `postedAtDisplay` | The relative phrase on the card, e.g. `1 Day Ago` |
| `snippet` | A short plain-text teaser from the advert body |
| `description`, `descriptionText` | The complete advert body, HTML and plain text (when `includeDescription` is on) |
| `url` | The public job page |
| `companyRating`, `companyReviewsCount`, `vacancies` | Extras Naukri prints on the card |
| `collectedAt` | When this run read the advert, kept separate from `postedAt` |

**Not collected:** recruiter names, private phone numbers and e-mail addresses. Only what is publicly visible on Naukri's own search page is read, and no login or account is used.

### Price

**Pay per job row.** Status rows and empty results are **free** - you are charged only for job adverts you actually receive, and a job that matches several of your keywords is charged once. Turning on the full description costs nothing extra, because Naukri sends it with the search results.

### Input

```json
{
  "keywords": ["python developer", "data analyst"],
  "location": "Bengaluru",
  "experienceYears": 3,
  "maxJobs": 100,
  "includeDescription": false
}
```

| Input | Default | Notes |
| --- | --- | --- |
| `keywords` | required | One keyword per line; each is searched separately |
| `location` | all of India | A city as Naukri names it: `Bengaluru`, `Mumbai`, `Delhi / NCR`, `Pune` |
| `experienceYears` | any | Whole years, 0-30; keeps adverts whose range contains it |
| `maxJobs` | 100 | Rows per keyword. Naukri serves 20 per page, so this rounds up to whole pages |
| `includeDescription` | `false` | Adds the full advert body. No extra requests, just bigger rows |
| `maxUpstreamRequests` | derived | Hard ceiling on requests, so a run is never unbounded |
| `proxyConfiguration` | Apify residential, India | Naukri refuses datacenter IPs - leave this as it is |

### Output

A job row (shortened, `includeDescription` off):

```json
{
  "recordType": "job",
  "jobId": "240826031542",
  "title": "Python developer",
  "companyName": "Infosys",
  "companyId": "410527",
  "experienceText": "2-5 Yrs",
  "minExperienceYears": 2,
  "maxExperienceYears": 5,
  "salaryText": "Not disclosed",
  "salaryMin": null,
  "salaryMax": null,
  "salaryCurrency": "INR",
  "salaryDisclosed": false,
  "locations": ["Hyderabad", "Pune", "Bengaluru"],
  "skills": ["Django", "Python", "Flask", "Rest", "Pandas"],
  "postedAt": "2026-10-23T05:20:17.860Z",
  "publishedAt": "2026-10-23T05:20:17.860Z",
  "postedAtDisplay": "1 Day Ago",
  "snippet": "Roles & Responsibilities Design, develop, and maintain Python-based applications…",
  "url": "https://www.naukri.com/job-listings-python-developer-infosys-240826031542",
  "vacancies": 10,
  "keyword": "python developer",
  "searchLocation": "Bengaluru",
  "rank": 1,
  "partial": false,
  "partialReason": null,
  "collectedAt": "2026-10-25T04:12:09.113Z"
}
```

Besides job rows the dataset carries two kinds of row that are **never charged**:

- one `keyword_status` row per keyword, with `jobsReturned`, `jobsAvailable`, `duplicatesSkipped`, `partial`, `partialReason` and a plain-English `statusText`;
- one final `RUN_SUMMARY` row (also saved to the key-value store under `RUN_SUMMARY`) with the totals for the whole run.

#### An empty result is never confused with a failure

This is the point of the status rows. If Naukri genuinely has no job for a keyword, its status row says so with `partial: false`. If a keyword was cut short - the bot check refused us, a limit was reached, Naukri's own search service faulted - the row has `partial: true` and a `partialReason` (`blocked`, `upstream_request_cap`, `charge_limit_reached`, `upstream_error`, `warmup_failed`, `not_collected`). **A blocked or truncated keyword is never reported as zero jobs.**

### Limits worth knowing

- **20 jobs per page.** Naukri ignores a larger page size, so `maxJobs` rounds up to whole pages.
- **1000 jobs per keyword** is the deepest this Actor pages one search. A keyword that reaches it sets `resultDepthLimitReached` and `jobsAvailable` tells you how many Naukri claimed; narrow the keyword, city or experience to go further.
- **Naukri is behind Akamai Bot Manager.** The run opens a real browser once to pass the bot check, then sends ordinary requests. That one warm-up takes about 10 seconds; after it, paging is fast.
- **An Indian residential exit is required.** Datacenter IPs are refused with HTTP 406. The default proxy setting already handles this.
- **Naukri's search is fuzzy.** A very narrow keyword can be broadened by Naukri itself, so some rows may be looser matches than the keyword implies. `keyword` on every row records what was searched.
- **The default 1 GB of memory is what this Actor is measured at.** The browser warm-up fits in it; raising it only makes a run cost more.
- `salaryText` carries Naukri's own label, so an advert with no published salary reads `Not disclosed` - Naukri's search card simply prints nothing there. `salaryDisclosed: false` with `salaryMin`/`salaryMax` null is the machine-readable form of the same fact.
- `postedAt` is the exact instant Naukri reports, and `postedAtDisplay` is Naukri's own relative label for it. Naukri caps that label at `30+ Days Ago`, so a long-running or refreshed advert can carry a `postedAt` much older than the label suggests - trust `postedAt`.

# Actor input Schema

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

One Naukri search keyword per line, for example "python developer", "data analyst" or "sales manager". Every keyword is searched separately in the location below and its results are labelled with it. A job that matches several of your keywords is returned once and charged once.

## `location` (type: `string`):

A city as Naukri names it in its own search box: "Bengaluru", "Mumbai", "Delhi / NCR", "Pune", "Hyderabad", "Chennai". Leave it empty to search all of India. A spelling Naukri cannot match returns zero jobs rather than an error, and the keyword's status row shows what was searched.

## `experienceYears` (type: `integer`):

Optional. Naukri's own experience filter, as a whole number of years from 0 to 30: a job is kept when the number falls inside the advert's experience range. Leave it empty to accept every experience level.

## `maxJobs` (type: `integer`):

Upper bound on the rows one keyword may produce. Naukri serves 20 jobs per search page, so this rounds up to whole pages. 1000 is the deepest this Actor pages one search; a keyword that reaches it says resultDepthLimitReached, with jobsAvailable showing how many Naukri claimed. Narrow the keyword, the city or the experience to reach past it.

## `includeDescription` (type: `boolean`):

Also write each job's full advert body: description (the HTML Naukri serves) and descriptionText (the same text with the tags removed). Naukri sends the body with the search results, so this costs no extra request and no extra time - it only makes the rows bigger. Every row carries a short snippet either way.

## `maxUpstreamRequests` (type: `integer`):

Hard ceiling on the requests this run sends to Naukri: the browser warm-up that passes its bot check, every search page and every retry. At the ceiling collection stops - the keywords not reached are written with partialReason "upstream_request_cap" and the run then ends normally. Leave empty and the Actor works a ceiling out from your keywords and limits, so a run is never unbounded.

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

Where the requests to Naukri come from. Naukri is behind Akamai Bot Manager and refuses datacenter IPs, so the default is Apify Proxy residential with an Indian exit, which is what this Actor is measured on. Changing it to datacenter will make the run fail its bot check.

## Actor input object example

```json
{
  "keywords": [
    "python developer",
    "data analyst"
  ],
  "maxJobs": 100,
  "includeDescription": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "IN"
  }
}
```

# Actor output Schema

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

No description

## `runSummary` (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": [
        "python developer",
        "data analyst"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("lanternlab318/naukri-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 = { "keywords": [
        "python developer",
        "data analyst",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("lanternlab318/naukri-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 '{
  "keywords": [
    "python developer",
    "data analyst"
  ]
}' |
apify call lanternlab318/naukri-jobs --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,lanternlab318/naukri-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/5dWdWZ8tgVc9yCHla/builds/dYEXhv7RbFnRV1EUX/openapi.json
