# Shine Jobs Scraper · India, salary filters · $1/1K (`mlg14/shine-scraper`) Actor

Scrape Shine job listings in India for job market research, recruiting and salary benchmarking. Filters: keyword, city, experience, salary band, job type. Titles, employers, skills, descriptions, dates. $1/1K jobs.

- **URL**: https://apify.com/mlg14/shine-scraper.md
- **Developed by:** [MLG Data](https://apify.com/mlg14) (community)
- **Categories:** Jobs, Automation
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.00 / 1,000 results

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

## Shine Jobs Scraper

Scrape Shine jobs by keyword and location, then export Shine data to CSV, JSON, or Excel. This Shine API alternative collects public job search results with titles, organizations, locations, experience, salary information when published, descriptions, skills, dates, and direct listing links.

Search several phrases in one run. The actor merges duplicate job IDs, applies the site's own search filters, and stops at the result limit you set. A role appearing under two phrases is delivered once, with `searchKeyword` showing the phrase that found it first.

### What data can you extract from Shine?

Each dataset row represents one public job listing. The columns below are stable even when a listing has missing details; unavailable scalar values appear as `null`. Arrays remain arrays. Dates are copied from the search response, while `scrapedAt` records collection time in UTC.

| Field | Description | Example |
| --- | --- | --- |
| `jobId` | Stable listing ID used for deduplication | `19574136` |
| `title` | Published role title | `Embedded Linux Application Developer` |
| `company` | Organization or posting consultancy | A public organization name |
| `companyId` | Numeric organization ID in the search response | `792074` |
| `industry` | Industry category | `Auto Components` |
| `locations` | Every listed location | `["Bangalore"]` |
| `location` | Locations joined for spreadsheet use | `Bangalore` |
| `experienceMinYears` | Lower end of stated experience | `4` |
| `experienceMaxYears` | Upper end of stated experience | `8` |
| `experienceText` | Experience wording as published | `4 to 8 Yrs` |
| `salaryText` | Published pay text or a hidden marker | `[Salary Hidden]` |
| `salaryMinLakhs` | Lower annual pay in lakh rupees, if explicitly stated | `6.0` |
| `salaryMaxLakhs` | Upper annual pay in lakh rupees, if explicitly stated | `8.0` |
| `employmentType` | Regular, contractual, internship, or work from home | `regular` |
| `shiftType` | Full time or part time | `full-time` |
| `skills` | Comma-separated source tags as an array | `["linux", "bsp"]` |
| `description` | Job description with markup removed | `Design and develop applications...` |
| `applyUrl` | Direct public listing link | A Shine job URL |
| `externalApplyUrl` | External application link, if supplied | `null` |
| `recruiterName` | Public recruiter name, if supplied | `null` |
| `postedDate` | Source posting timestamp | `2026-09-25T05:35:12` |
| `postedDaysAgo` | Days since the posting date at collection | `2` |
| `expiryDate` | Source listing expiry timestamp | `2026-11-24T05:35:14` |
| `earlyApplicant` | Whether the early applicant badge appears | `true` |
| `searchKeyword` | First input phrase that found this job | `python developer` |
| `scrapedAt` | UTC collection timestamp | An ISO timestamp |

A hidden salary is represented by `salaryText: "[Salary Hidden]"` and null numeric bounds. The salary search filter can still return such a listing because the site filters against information that is not necessarily displayed in the public salary text. Numeric pay is never inferred from hidden values or internal facet IDs.

### How to scrape Shine

1. Enter one or more phrases in `searchKeywords`, such as `python developer` or `data analyst`.
2. Add a city and optional experience, salary, employment, or shift filters. Leave a field blank to search without that filter.
3. Set `maxJobs` to the number of unique listings you need. Select `freshness` to start with newer results.
4. Run the actor and open the default dataset. Download the rows as JSON, CSV, or Excel for analysis.

A broad first run with 40 to 100 jobs is useful for checking whether the site's search interpretation matches your intent. A narrow city, salary, and experience combination can contain fewer rows than a broad search, even when the result limit is higher.

### Input

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `searchKeywords` | array of strings | Required | Separate search phrases; duplicates are merged by job ID. |
| `location` | string | All India | A city name. Exact location facet IDs are used when the city appears among the search facets; otherwise the site’s city search URL form is used. |
| `experienceLevel` | string | Any | One of under 1, 1–2, 3–5, 6–8, 9–10, 11–15, or over 15 years. |
| `salaryRange` | string | Any | Annual pay band in lakh rupees: up to 2, 3–5, 6–8, 9–12, 13–16, 17–25, or over 25. |
| `employmentType` | string | Any | Regular, contractual, internship, or work from home. |
| `shiftType` | string | Any | Full time or part time. |
| `sortBy` | string | `relevance` | Use `freshness` for most recent first. |
| `maxJobs` | integer | `100` | Total unique results across phrases, from 1 to 1,000. |
| `includeJobDescription` | boolean | `false` | Keep the full available description; otherwise keep its first 500 characters. |
| `proxyConfiguration` | object | Platform proxy | Override the proxy if your run needs a different proxy configuration. |

Example input for a focused search:

```json
{
  "searchKeywords": ["python developer"],
  "location": "Bangalore",
  "experienceLevel": "3to5",
  "salaryRange": "6to8",
  "sortBy": "freshness",
  "maxJobs": 40,
  "includeJobDescription": true
}
```

The salary filter asks the site to select a pay band; it does not promise a public number on every returned listing. If you need numeric compensation data, keep rows where `salaryMinLakhs` or `salaryMaxLakhs` is present and report the number of excluded rows alongside any aggregate.

### Output example

This abbreviated item is based on a public search response for the example query. Organization and listing path are omitted from this excerpt; the dataset includes them when the source provides them.

```json
{
  "jobId": "19574136",
  "title": "Embedded Linux Application Developer",
  "industry": "Auto Components",
  "locations": ["Bangalore"],
  "location": "Bangalore",
  "experienceMinYears": 4,
  "experienceMaxYears": 8,
  "experienceText": "4 to 8 Yrs",
  "salaryText": "[Salary Hidden]",
  "salaryMinLakhs": null,
  "salaryMaxLakhs": null,
  "employmentType": "regular",
  "shiftType": "full-time",
  "skills": ["linux", "bsp", "image sensors", "camera"],
  "postedDate": "2026-09-25T05:35:12",
  "expiryDate": "2026-11-24T05:35:14",
  "earlyApplicant": true,
  "searchKeyword": "python developer"
}
```

The `description` field in a full row contains text from the public listing with tags removed. `applyUrl` points to the job detail page, not necessarily the final employer application form. If the response includes a separate application destination, it appears in `externalApplyUrl`.

### Use cases

- **Hiring demand research:** Track how many public roles mention a skill or job title in a city. Save the query, date, and result limit with every extract so later counts remain comparable.
- **Recruiting operations:** Build a working queue of newly posted roles by using `freshness`, then review `company`, `locations`, `experienceText`, and the listing link before outreach.
- **Salary analysis:** Extract published pay ranges and group by city, industry, or experience. Keep hidden-salary jobs in a separate count instead of treating them as zero-pay roles.
- **Skills planning:** Count the `skills` tags associated with a role and compare how often particular technologies appear across search terms.
- **Local market reports:** Search the same role across several cities in separate runs. Exact facet matching helps keep major-city searches consistent with the site's own filters.
- **Job alerts:** Schedule a freshness-sorted search and compare `jobId` values between datasets to identify listings not present in a previous run.

For reporting, a narrow query can be more informative than a very large search. Save the filters with the exported dataset; a city or salary change alters the population you are measuring. Results are current public postings, not a historical archive of all roles ever advertised.

### How much does it cost to scrape Shine?

The configured result event price is **$1.00 per 1,000 delivered jobs**, or $0.001 per delivered row. Examples at that rate: 40 rows cost $0.04, 100 rows cost $0.10, and 1,000 rows cost $1.00 in result events. Check the run's pricing display for the rate attached to your account and any platform usage rules.

`maxJobs` controls the upper bound on result events for a run. Duplicate IDs are skipped before a row is written, so they do not add dataset rows. A search with no matching results has no delivered rows. The actor stops writing when the platform reports that the run’s charge limit has been reached.

The default limit of 100 jobs is useful for a first look. Increase it only after checking that the location and other filters produce the intended set. A broad search can have many pages, while a narrow search may finish well below your requested maximum.

### Tips for best results

Start with the job title people actually put in a listing. A phrase such as `data analyst` is usually more targeted than `data`. Search several related phrases when the role is advertised under multiple names; the actor removes repeated IDs from the combined output.

Use `location` for geographic targeting. The actor first reads the search response's location facets and uses the matching location ID when it finds the exact city name. For a city absent from those facets, it uses the site's city-search phrase. Review a sample of `locations` in the output because a job can name several cities, nearby areas, or a broader region.

Apply experience and salary bands only when those distinctions matter to the work. Each band is sent to the site's search service, so a filter can remove many otherwise relevant postings. If a result set is too small, remove one filter at a time to see which one causes the drop.

Use `sortBy: "freshness"` for a daily review or alert workflow. Keep a local history of `jobId` values if you need only new postings, since each actor run is an independent snapshot. For a single analysis export, relevance ranking may surface more useful roles near the start.

Set `includeJobDescription: true` when the full available text is needed for classification or review. The default trims text after 500 characters to keep exports smaller. Markup is removed in both modes, but the original wording may contain formatting artifacts or recruiter boilerplate.

### Limits

The source publishes 20 jobs per search page in the observed response. The actor follows up to 50 pages per phrase, stopping sooner at the requested number, the site's end of results, or an identical repeated page. The 50-page bound covers 1,000 positions per phrase before cross-phrase deduplication. Search counts displayed by the site can change while a run is in progress, and duplicate or removed records can make the delivered count lower than the displayed total.

Some fields are genuinely absent. Recruiter names and external application links are often null. Salary text was hidden in the sampled search results, even where the salary band filter was active. The output preserves that distinction and does not substitute internal numeric facet values for published pay.

`postedDate` and `expiryDate` retain the source timestamp text. The source examples did not include a timezone suffix, so downstream users should avoid assuming that those strings are UTC. `postedDaysAgo` compares calendar dates at collection time and is useful for rough sorting rather than a precise elapsed-hours calculation.

A listing may be posted by a consultancy. In that case, `company` reflects the organization shown in the public search response; it may differ from the end employer. `applyUrl` is the listing page, and any further application steps on the site depend on the posting.

The actor reads public search data. It does not sign in or retrieve private applicant information. Site availability, search ranking, filters, and field coverage can change without notice. A temporary block may lead to proxy rotation and slower pages; persistent failures are reported in the run log.

### Use with AI agents (MCP)

Add this actor to any MCP client (Claude, Cursor, VS Code, n8n...) with one URL:

`https://mcp.apify.com?tools=mlg14/shine-scraper`

An agent gets back a flat dataset of Shine jobs: title, company, location, experience, salary text, skills, description, posting date and listing link. Always set `maxJobs` so results stay bounded; treat null fields as unknown.

**Prompt 1:** "Find 40 recent Python developer jobs in Bangalore on Shine and list the links."

```json
{"searchKeywords": ["python developer"], "location": "Bangalore", "sortBy": "freshness", "maxJobs": 40}
```

**Prompt 2:** "Get internship listings for data analysts across India from Shine."

```json
{"searchKeywords": ["data analyst"], "employmentType": "internship", "maxJobs": 30}
```

**Prompt 3:** "Collect Shine jobs in Mumbai paying 9 to 12 lakh for software engineers, with full descriptions."

```json
{"searchKeywords": ["software engineer"], "location": "Mumbai", "salaryRange": "9to12", "includeJobDescription": true, "maxJobs": 25}
```

### FAQ

**Is this legal to use?** The actor reads public job listings without an account. Follow the site's terms and applicable privacy rules. Do not misuse public recruiter information or combine it with unrelated personal data for harmful outreach.

**Do I need a proxy?** The default uses the platform proxy with datacenter IPs first. During source checks, the public search endpoint returned results without an anti-bot challenge. Proxy escalation is available if later requests are blocked.

**How fast is a run?** The actor requests search pages of 20 jobs and writes each unique result. Time depends on the number of pages, site response time, retries, and the result limit. A 40-job query needs at least two filtered result pages plus an initial facet lookup when a location is set.

**Can I schedule searches or monitor new jobs?** You can schedule repeated runs with the same input. Each run produces a fresh snapshot; compare `jobId` against a saved prior dataset to identify new listings. The actor does not persist a seen-ID history across runs.

**Can I export to a spreadsheet?** Yes. Download the default dataset as CSV or Excel, or retrieve JSON for a data pipeline. `location` is convenient for a single spreadsheet cell, while `locations` preserves all locations as an array in JSON.

**Why are salary fields empty after using `salaryRange`?** The search filter and the public salary text are different source fields. The site can match a band while hiding the posted amount. Numeric output is populated only when a salary amount is explicitly published.

**Why is `company` different from the employer I expected?** A consultancy may publish a job on behalf of another organization. The actor returns the name shown in the source record and does not infer an undisclosed client.

### Integrations

Use the dataset endpoint for scheduled exports, webhooks, spreadsheet imports, or workflow automation. The same input can be saved and rerun on a schedule, and the `jobId` field supports downstream deduplication. Keep a copy of the query with each export to make repeated analyses auditable.

### Support

Open an issue on the Issues tab; we reply within 24h and add fields on request.

# Actor input Schema

## `searchKeywords` (type: `array`):

Job search phrases, one per entry; each is searched separately and unique jobs are merged. e.g. \["python developer", "data analyst"]. Use one entry per role you want to track.

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

Indian city name as plain text, e.g. Bangalore, Mumbai, Delhi, Pune, Hyderabad. Use it to restrict results to one city. Leave empty for all India.

## `experienceLevel` (type: `string`):

Filter by experience band. Allowed values: lt1, 1to2, 3to5, 6to8, 9to10, 11to15, gt15, or empty for any. Example: 3to5 for mid-level roles. Leave empty for all levels.

## `salaryRange` (type: `string`):

Filter by annual salary band in lakh rupees. Allowed values: 0to2, 3to5, 6to8, 9to12, 13to16, 17to25, gt25, or empty for any. Example: 9to12. Use for salary benchmarking; some listings still hide salary.

## `employmentType` (type: `string`):

Filter by employment type. Allowed values: regular, contractual, internship, work-from-home, or empty for any. Example: internship for fresher roles. Leave empty for all job types.

## `shiftType` (type: `string`):

Filter by working hours. Allowed values: full-time, part-time, or empty for any. Example: part-time. Leave empty to include both full time and part time jobs.

## `sortBy` (type: `string`):

Result order. Allowed values: relevance (best keyword match) or freshness (newest first). Example: freshness. Use freshness for monitoring runs that look for new jobs.

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

Maximum number of unique jobs to return across all keywords, 1 to 1000. Example: 50 for a quick test. Use a small value to keep runs fast and cheap; default is 100.

## `includeJobDescription` (type: `boolean`):

Whether to return the full job description text instead of the first 500 characters. Allowed values: true or false. Example: true for skills extraction. Use false for faster, smaller output.

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

Use Apify Proxy for requests; the default starts with datacenter IPs.

## Actor input object example

```json
{
  "searchKeywords": [
    "python developer"
  ],
  "location": "Bangalore",
  "experienceLevel": "",
  "salaryRange": "",
  "employmentType": "",
  "shiftType": "",
  "sortBy": "relevance",
  "maxJobs": 100,
  "includeJobDescription": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

Collected job listings in the default dataset.

# 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 = {
    "searchKeywords": [
        "python developer"
    ],
    "location": "Bangalore"
};

// Run the Actor and wait for it to finish
const run = await client.actor("mlg14/shine-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 = {
    "searchKeywords": ["python developer"],
    "location": "Bangalore",
}

# Run the Actor and wait for it to finish
run = client.actor("mlg14/shine-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 '{
  "searchKeywords": [
    "python developer"
  ],
  "location": "Bangalore"
}' |
apify call mlg14/shine-scraper --silent --output-dataset

```

## MCP server setup

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