# Shine.com Jobs Scraper: India Job Listings by Role and City (`titian_fluorite/shine-jobs-scraper`) Actor

Shine jobs scraper for India job listings: search any role and city, get one clean row per job with salary band, experience, location and description. Shine.com jobs to JSON. Pay per job.

- **URL**: https://apify.com/titian\_fluorite/shine-jobs-scraper.md
- **Developed by:** [Gerald Dobin](https://apify.com/titian_fluorite) (community)
- **Categories:** Jobs, Lead generation, Business
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.00 / 1,000 scraped jobs

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

## Shine.com Jobs Scraper: India Job Listings

Give this Actor a list of job titles or skills, and optionally a list of Indian cities, and it
returns every matching job advertised on Shine.com as clean rows in one schema. Shine is one of
India's largest job portals, and each row carries the job title, the hiring company, the public
job page link, every city the role is advertised in, the salary band parsed into numbers, the
experience band parsed into years, the employment type, the industry, the employer's own skill
tags, the posting and expiry dates, and the full job description as both cleaned HTML and plain
text. Jobs are deduplicated by Shine's own job id, so a role that appears under two of your
keywords arrives once and is billed once.

### Who it is for

**Recruiters and staffing agencies sourcing in India.** Watch "python developer in Bangalore" and
"data analyst in Pune" on a daily schedule and see every new opening with the pay band and the
experience band already in the row, instead of clicking through a results page. The company id
groups every listing from one poster, so you can tell a direct employer from an agency at a glance.

**Job boards and aggregators that need an India feed.** Point the Actor at fifty role and city
combinations, run it nightly, and load the rows straight into your index. Every row has a stable
`jobId` to upsert on and an `expiresAt` date, so yesterday's index and today's diff cleanly.

**Market and salary research across roles and cities.** `salaryMinLakh` and `salaryMaxLakh` are
real numbers in lakh per year, parsed out of the text Shine prints, so you can pivot pay against
experience, city and industry without writing a parser. The same search run against several cities
gives you a like for like comparison.

### Sample output

One row per job. Here is a real row from a test run, with the two long description fields
shortened for readability:

```json
{
  "jobId": "19443568",
  "title": "Python Developer",
  "company": "SP Staffing Services Private Limited Hiring For Leading MNC Company",
  "companyId": 734032,
  "url": "https://www.shine.com/jobs/python-developer/sp-staffing-services-private-limited/19443568",
  "locations": ["Hyderabad"],
  "location": "Hyderabad",
  "salaryText": "Rs 26  - 38 Lakh/Yr",
  "salaryMinLakh": 26,
  "salaryMaxLakh": 38,
  "salaryPeriod": "year",
  "experienceText": "5 to 10 Yrs",
  "experienceMinYears": 5,
  "experienceMaxYears": 10,
  "employmentType": "Regular, Full time",
  "jobType": "Regular",
  "industry": "IT Services & Consulting",
  "keywords": ["rest api", "python", "flask", "django", "gcp", "fast api"],
  "postedAt": "2026-08-11T07:12:45.000Z",
  "postedText": "2026-08-11T12:42:45",
  "expiresAt": "2026-10-09T18:30:00.000Z",
  "descriptionHtml": "<h2><strong>Warm Greetings!</strong></h2><p><strong>Role:</strong> Python Developer ...</p>",
  "descriptionText": "Warm Greetings!\nRole: Python Developer\nExperience: 5 to 10 yrs ...",
  "searchKeyword": "python developer",
  "searchLocation": null,
  "sourcePage": "https://www.shine.com/job-search/python-developer-jobs",
  "scrapedAt": "2026-09-12T15:44:10.311Z",
  "error": null
}
```

Every field is present on every row. A field is `null` when Shine does not publish it, never
missing and never an empty string. Dates are ISO 8601 in UTC, converted from the India Standard
Time the site publishes them in, and `postedText` keeps the original for reference. A date that
does not exist, such as a 31st of February, comes back empty rather than quietly sliding into March.

Salary numbers are always annual and always in lakh. A monthly band is multiplied by twelve, a
crore figure is multiplied by a hundred, and a figure Shine writes in rupees, such as
"Rs 50,000 - 3.0 Lakh/Yr", is converted, so every row is comparable with every other row.
`salaryPeriod` records what the employer actually quoted, `year` or `month`. When the employer
hides the pay, or writes a number with no unit at all, `salaryText` keeps whatever was published
and the three salary fields are `null`: a wrong number in a salary dataset is worse than a missing
one.

**Contact details are removed.** Employers routinely paste a recruiter's mobile number or email
address into the description, the job title, or the company line. Every string in every row is
checked and any email address or Indian phone number is replaced with `[contact removed]`, and
`mailto:` and `tel:` links are stripped out of the description entirely. The rest of the sentence
is left alone, so the row is still readable. There is no setting to turn this off.

If a search page cannot be read, you get an error row carrying `searchKeyword`, `searchLocation`,
`sourcePage`, `error` and `scrapedAt`, with every other field empty. The `error` column holds one of
a short fixed set of codes, so you can filter on it: `blocked`, `redirected to page 1`,
`unrecognised city`, `network error`, `unreadable page`, `page not found`, `address refused`,
`response too large` or `unexpected error`. Error rows are never charged for.

### Input reference

| Input | What it does |
|---|---|
| `keywords` | Job titles or skills to search for, one per line, for example `python developer`. Each keyword becomes one search. Optional if you give `searchUrls` instead, but a run needs at least one of the two. |
| `locations` | Indian cities, one per line, for example `Bangalore`, `Mumbai`, `Delhi NCR`, `Hyderabad`, `Pune`, `Chennai`. Every keyword is searched in every city. Leave empty to search all of India. |
| `searchUrls` | Shine job search addresses to use exactly as they are, for example `https://www.shine.com/job-search/python-developer-jobs-in-bangalore`. Paste a page two address and paging starts there. |
| `maxJobsPerSearch` | How many jobs to take from each search. Shine returns 20 per page. Default 100. Set 0 for everything Shine will serve, up to a safety cap of 250 pages, about 5,000 jobs, per search. |
| `postedWithinDays` | Keep only jobs posted in the last N days. |
| `experienceMin`, `experienceMax` | Keep only jobs whose published experience band overlaps this range, in years. A job that publishes no band is kept. |
| `includeDescription` | Include the full description as HTML and text. On by default. Turning it off makes the dataset several times smaller. |
| `maxConcurrency` | Requests in flight at once. Capped at 2. |

One run collects at most **50 searches** (keywords times cities, plus any search URLs) and at most
**20,000 jobs**. A run that asks for more searches than that is refused before it makes a single
request, with a message telling you to split it, rather than being silently trimmed to a number you
did not choose. A single search stops after 250 pages, or after ten pages in a row that produce no
new rows, whichever comes first.

Common city names are mapped to the ones Shine's own URLs use, so `Bengaluru` finds Bangalore and
`Delhi NCR` finds Delhi. If Shine does not recognise a city at all it quietly answers with the
all India result set, which would mean paying for rows from the wrong place. This Actor spots that,
warns you and skips that search rather than billing you for it.

#### How deep a single search goes

Shine tells you how many pages a search has, and it serves the first few of them to anyone. Past
that it answers a page number with a redirect back to page one, and how far you get varies by
search and by day: a broad all India search often serves two pages, while a narrower city search
usually serves more. This Actor watches for that redirect and stops paging there, so you never
receive, or pay for, the same twenty jobs a second time. The run log says which page it stopped on.

The way to get more jobs is therefore to go wider rather than deeper: more keywords, and each
keyword split across cities. `python developer` in eight cities will return far more distinct jobs
than `python developer` across all India ever will, and the rows carry `searchLocation` so you can
tell them apart.

### Pricing

Pay per result. You are charged once for each job row delivered, and nothing else: no charge for a
page that fails, no charge for an error row, and no charge for a duplicate a search returns twice.
A run with the default input, two broad all India keywords, delivers around 60 rows, because Shine
serves only the first pages of a broad search. Add cities to get more. Set `maxJobsPerSearch` to
put a ceiling on what any one search can cost.

### What it does not do

- **No candidate or recruiter contact data.** Shine's listing data includes a recruiter phone
  number and email address in their own fields, and those are never read into a row. Contact
  details that employers paste into the description or the title are found and replaced with
  `[contact removed]`, and `mailto:` and `tel:` links are removed. You get the company name, the
  job fields and the public job page link. There is no option to get anything else.
- **No login and no private pages.** Only pages any visitor can open, with no account and no
  session.
- **Only what robots.txt allows, and only search pages.** Shine's terms permit automated access as
  its robots.txt permits, so the file is fetched at the start of every run and every address is
  checked against it. On top of that there is a positive rule: the only addresses this Actor will
  request at all are `https://www.shine.com/job-search/...` search pages and `robots.txt`. Every
  redirect is put through both checks again, so nothing can walk the Actor onto another part of the
  site. If robots.txt ever stops allowing the job search pages, the run stops with a clear message
  instead of scraping.
- **No job detail pages.** The search listing already carries the description, the salary band and
  the dates, so there is nothing to gain from twenty extra requests per page.
- **No browser.** Plain HTTP, at most two requests at a time, with a one to two second pause
  between any two requests anywhere in the run, not per search. If the site pushes back with a 403,
  a 429 or a challenge page, that refusal is retried once and no more, and three refusals in a row
  stop the whole run rather than pushing harder at a site that has said no.

### Freshness

Rows are read live from Shine on every run, never from a cache or a stored copy, and `scrapedAt`
records the moment each row was read. Shine timestamps each listing itself, and `postedAt` carries
that, so you can tell a job posted this morning from one that has been up for a month. New jobs
appear on the site continuously, so a daily or hourly schedule with `postedWithinDays` set to 1
gives you a clean stream of what is new without paying for the same rows again.

### What people search for

If you arrived looking for a Shine jobs scraper, this is it. The same Actor answers searches for: Shine jobs scraper, Shine.com jobs, Shine job listings, India jobs scraper, Indian job portal scraper, jobs in Bangalore scraper, Mumbai jobs scraper, Hyderabad jobs scraper, Pune jobs scraper, Delhi NCR jobs scraper, Chennai jobs scraper.

# Actor input Schema

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

Job titles or skills to search for on Shine.com, one per line. Each keyword becomes one search, and each keyword is searched in every city you list below. Leave this empty only if you are giving Shine search URLs instead.

## `locations` (type: `array`):

Optional. Indian cities to search in, one per line, for example Bangalore, Mumbai, Delhi NCR, Hyderabad, Pune or Chennai. Leave this empty to search all of India. A city Shine does not recognise is skipped with a warning rather than quietly returning all India results.

## `searchUrls` (type: `array`):

Optional. Paste Shine job search addresses to use exactly as they are, for example https://www.shine.com/job-search/python-developer-jobs-in-bangalore. Only job search pages are accepted, and an address with a query string is refused because robots.txt disallows several of them.

## `maxJobsPerSearch` (type: `integer`):

How many jobs to collect from each search before moving on. Shine returns 20 jobs per page. Set 0 to take everything Shine will serve, up to a safety cap of 250 pages per search. A run collects at most 50 searches and 20,000 jobs in total. Every job row you receive is billed.

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

Optional. Keep only jobs posted in the last N days. Leave empty to keep everything Shine returns.

## `experienceMin` (type: `integer`):

Optional. Keep only jobs whose published experience band reaches at least this many years. A job that publishes no band is kept.

## `experienceMax` (type: `integer`):

Optional. Keep only jobs whose published experience band starts at or below this many years. A job that publishes no band is kept.

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

Add the full job description as cleaned HTML and as plain text. Descriptions are long, so turning this off makes the dataset several times smaller.

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

How many requests may be in flight at once. Capped at 2 on purpose: this Actor reads a public site politely and will not be turned into a hammer.

## Actor input object example

```json
{
  "keywords": [
    "python developer",
    "data analyst"
  ],
  "locations": [],
  "searchUrls": [],
  "maxJobsPerSearch": 100,
  "includeDescription": true,
  "maxConcurrency": 2
}
```

# Actor output Schema

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

Every row with the full description fields.

## `jobsCsv` (type: `string`):

The same rows as CSV for spreadsheets.

## `jobsShortCsv` (type: `string`):

A compact sheet: title, company, city, salary, experience, type, industry, posting date and job link.

# 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"
    ],
    "locations": [],
    "searchUrls": [],
    "maxJobsPerSearch": 100,
    "maxConcurrency": 2
};

// Run the Actor and wait for it to finish
const run = await client.actor("titian_fluorite/shine-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": [
        "python developer",
        "data analyst",
    ],
    "locations": [],
    "searchUrls": [],
    "maxJobsPerSearch": 100,
    "maxConcurrency": 2,
}

# Run the Actor and wait for it to finish
run = client.actor("titian_fluorite/shine-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": [
    "python developer",
    "data analyst"
  ],
  "locations": [],
  "searchUrls": [],
  "maxJobsPerSearch": 100,
  "maxConcurrency": 2
}' |
apify call titian_fluorite/shine-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,titian_fluorite/shine-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/XIC9CH2RFmJmJYUyw/builds/3Z2glj5QSFzRdEtwR/openapi.json
