Shine Jobs Scraper — India Jobs with Recruiter Emails & Phones
Pricing
from $2.00 / 1,000 job or recruiter scrapeds
Shine Jobs Scraper — India Jobs with Recruiter Emails & Phones
Scrape Shine.com jobs across India with the hiring recruiter's real email and phone — Shine's fake placeholder contact is detected and stripped, so every contact you get is genuine. Filter by experience, salary, industry and employment type. Schedule it for only-new postings.
Pricing
from $2.00 / 1,000 job or recruiter scrapeds
Rating
0.0
(0)
Developer
Muhamed Didovic
Maintained by CommunityActor stats
0
Bookmarked
3
Total users
1
Monthly active users
a day ago
Last modified
Categories
Share
Scrape jobs from Shine.com, one of India's largest job boards, and get the hiring recruiter's email address and phone number on every posting that publishes one — not as a paid enrichment step, but because Shine puts them in the search results themselves.
Why use this scraper
Most job scrapers give you a title, a company and a link, and leave you to find a human to contact. Shine attaches the hiring recruiter's contact details to the posting itself, and this Actor returns them wherever the recruiter published them — verified real, never a placeholder.
Measured across eight keywords and 480 jobs on 2026-09-05: a real recruiter email on 43% of postings, a phone on 38%.
Those numbers are lower than they first appear, and deliberately so. Shine does not return an empty field when a recruiter opts out of publishing contact details — it substitutes a working-looking Gmail address and a valid-looking mobile number, the same pair on 56% of jobs. This Actor detects that pair and returns null instead, so every contact in your output is a real one. See the FAQ.
That makes it two products in one. As a job scraper it gives you salary, experience, skills, industry, locations and the full description. As a lead source it gives you a named recruiter with an email and a phone, filterable by industry and city.
Supported inputs
| Input | What it does |
|---|---|
| Search keywords | ["python", "sales manager"] — each becomes a Shine search |
| Location | Restrict every keyword to one city, e.g. Bangalore |
| Start URLs | Paste shine.com/job-search/... URLs you already built |
| Maximum results | Row cap. 20 jobs per request, so 100 results is 5 requests |
| Years of experience | Jobs suited to a candidate with that much experience |
| Minimum salary | In lakh per year, e.g. 12 |
| Industry | One of Shine's 26 industries, by name — BFSI, Medical / Healthcare |
| Functional area | One of 37 job functions — Testing, Finance / Accounts / Tax |
| Employment type | Regular, Contractual, Internship or Work from home |
| Sort order | Shine's relevance ranking, or newest first |
| Posted within (days) | Only recent postings, and stop paging once past the window |
| Only new jobs since the last run | For schedules — returns just what you haven't had |
| Also scrape each job page | Adds apply counts, numeric salary, state and tags — see below |
| One row per recruiter | Group by contact instead of by job — see below |
| Only jobs with a contact | Drop postings with no email and no phone |
| Skip descriptions | Smaller exports when you only need the structured fields |
Use cases
Recruitment lead generation — pull every recruiter hiring for a skill in a city, with a working email and phone, then filter by industry.
Talent market research — track salary bands, required experience and in-demand skills across thousands of postings per keyword.
Competitive hiring intelligence — see which consultancies are staffing which industries, how many roles each is running, and how recently they posted.
Job board aggregation — feed Shine listings into your own board or ATS with a stable, typed schema.
Monitoring: only what's new
Run this on a schedule with Only new jobs since the last run on, and each run returns the postings you haven't received yet instead of the same feed again. The first run delivers everything it finds and becomes the baseline; after that a daily run on a keyword like python returns the day's new jobs rather than 30,000 rows you already have.
Two things it gets right that are easy to get wrong. A job is remembered only once it has actually been delivered, so anything cut off by your result limit still arrives on the next run rather than being marked seen and lost. And a run that finds nothing new succeeds and says so in the log — it doesn't report a failure that would invite a pointless retry.
Use Monitoring state key to keep separate schedules apart (nursing-delhi, python-blr), and Forget everything and start over after changing keywords, so the old memory doesn't hide jobs that now match.
One row per recruiter
Indian recruitment runs through consultancies that post the same contact against many vacancies. Across 480 jobs that was 1.8 postings per recruiter, with the busiest holding 8; a single sales manager run collapsed 60 jobs into 38 recruiters.
Paying per row for the same email eight times is waste. Switch on One row per recruiter and the output collapses to one row per contact:
{"recruiterEmail": "niyukti.nmc@gmail.com","recruiterPhone": "9990622996","companyName": "Niyukti Management Consultants","companyId": "166042","jobCount": 7,"jobTitles": ["Candidate Required For MNC Insurance Sales Manager salary 4Lac", "…"],"locations": ["Bareilly", "Varanasi", "Dehradun", "Kanpur", "Lucknow", "…"],"industries": ["Insurance"],"latestPostedDate": "2026-09-05T18:23:56","jobIds": ["19506520", "19506522", "19506523", "…"]}
latestPostedDate is what separates an active recruiter from a stale listing.
How it works
- Search — each keyword (plus optional city and any filters) becomes a Shine search URL.
- Read the page's own JSON — Shine is a Next.js site that ships its full result set inside the page, so there is no per-job request and no browser needed.
- Page through — 20 jobs per page, in parallel batches, stopping at your limit or the end of the results.
- Optionally open each job page — only when Also scrape each job page is on, and only for rows that survived your filters and your limit.
- Emit — one row per job, or one per recruiter if grouping is on.
You can also paste job URLs straight into Start URLs (shine.com/jobs/...) to scrape specific postings. Those always come back with the full field set.
Output
| Field | Description |
|---|---|
jobId | Shine's job identifier |
jobTitle | Job title |
companyName / companyId | Hiring company |
recruiterEmail | Recruiter's email address |
recruiterPhone | Recruiter's phone number |
salary | Salary band as published, e.g. Rs 9 - 12 Lakh/Yr |
experience | Experience band, e.g. 5 to 10 Yrs |
locations | Array — a job may list several cities |
industry | Industry category |
skills | Array of required skills |
jobDescription / jobDescriptionHtml | Description as text and as HTML |
postedDate / closingDate | ISO timestamps |
jobUrl | Link to the posting |
vacancies | Openings for this posting |
employmentType | Regular, Contractual, Internship or Work from home |
isPremium | Whether the posting is boosted — useful for filtering recruiter spam |
searchKeyword | Which search produced the row |
With Also scrape each job page switched on
These come from the job's own page, so they cost one extra request per row.
| Field | Description |
|---|---|
applyCount | How many people have already applied. Observed from 0 on a fresh posting to 991 on a saturated one |
salaryMin / salaryMax | Salary band as integers in ₹/year, so you can sort and filter on it |
locationStates | Each city paired with its state, e.g. Bangalore → Karnataka |
companyDescription | The employer's own description of itself |
tags | Shine's category tags for the role |
functionalArea / roleCategories | Shine's higher-level job taxonomy |
Sample output
One row, exactly as the Actor emitted it with Also scrape each job page switched on:
{"jobId": "19086960","jobTitle": "Developers (.NET / Java / Python) React.js & Ai Exposure B'lore & Hyd 2+Yrs","companyName": "WHITE HORSE MANPOWER CONSULTANCY (P) LTD","recruiterEmail": "whmshineprofiles@gmail.com","recruiterPhone": "8884572014","salary": "Rs 4.0 - 6 Lakh/Yr","salaryMin": 400000,"salaryMax": 600000,"experience": "2 to 8 Yrs","locations": ["Hyderabad"],"locationStates": [{ "location": "Hyderabad", "state": "Telangana" }],"industry": "IT Services & Consulting","employmentType": "Regular","skills": ["Java", "Spring Boot", "NET", "Python", "Microservices", "ReactJS", "REST APIs"],"applyCount": 991,"postedDate": "2026-06-05T15:26:05","vacancies": 99,"isPremium": true,"jobUrl": "https://www.shine.com/jobs/developers-net-java-python-reactjs-ai-exposure-blore-hyd-2yrs/white-horse-manpower-consultancy-p-ltd/19086960","searchKeyword": "python-jobs"}
Without that option the row is the same minus applyCount, salaryMin, salaryMax, locationStates, companyDescription, tags, functionalArea and roleCategories. jobDescription and jobDescriptionHtml are omitted above for length.
FAQ
Do I need a proxy? No — the Actor handles it. It requests directly first, which is fastest, and automatically retries through a residential exit when Shine withholds the data. You can supply your own proxy in the Advanced section if you'd rather route all traffic yourself, but you don't need to.
Does monitoring cost extra? It saves. The Actor still reads the search pages, but only new jobs become rows — and rows are what you pay for. On a daily schedule that's the difference between paying for the whole feed every day and paying for the handful that are new.
Are the filters applied by Shine or after the fact? By Shine, on its own server, so a filtered run reads far fewer pages rather than downloading everything and discarding most of it. Experience, salary, industry, functional area, employment type and sort order are all Shine's own search parameters. Industry and functional area are matched against the live list Shine returns for your search, so a partial name works and the run logs exactly what it matched.
How does "Posted within days" work if Shine has no date filter? It doesn't have one, so the Actor sorts newest-first and walks until two pages in a row fall outside your window. Two rather than one because Shine's first page carries promoted listings that sit outside the sort — on a live test, page 1 was entirely stale while page 2 held jobs posted that morning. On a keyword with 1,500 pages and a 2-day window this still costs only a handful of requests. If your filters exclude everything, the run says so and finishes successfully rather than reporting a failure.
What does "Also scrape each job page" actually add?
Six fields the search listing doesn't carry, the useful one being applyCount — how many candidates have already applied. It also gives salary as integers rather than as the string "Rs 9 - 12 Lakh/Yr". The trade is speed: the Actor goes from one request per 20 jobs to one request per job.
Can I combine it with One row per recruiter? No. A recruiter row covers many jobs, and an apply count or a salary band belongs to one of them, so the option is ignored in that mode and the run says so in the log.
Can it reveal a hidden salary?
Sometimes. About 61% of postings publish salary as [Salary Hidden], and for roughly 3 in 10 of those the job page still returns a real band. The rest fall back to what looks like Shine's floor placeholder of 200000–500000, so treat a number as informative only where salary itself was hidden and the band isn't that default.
Do I need to log in? No. Everything scraped here is public.
How many jobs can I get?
Popular keywords run deep — python returns over 30,000 jobs across 1,500+ pages, sales over 65,000. Your result limit is the practical ceiling.
Is the recruiter email always there?
No, and be careful with any tool that claims it is. Across 480 jobs, a genuine email was on 43% and a phone on 38%. Coverage is Shine's to decide and varies a lot by keyword — sales manager and accountant publish contacts far more often than python. Use Only jobs with a contact if you need every row to have one.
What is the placeholder contact you strip?
When a recruiter opts out of publishing their details, Shine's search results don't return an empty field — they return shinetest12345@gmail.com and 8765243096, the same pair every time, on 56% of the jobs we sampled. The job's own page is honest about it and says hidden_email / hidden_mobile for exactly those postings, which is how we identified the substitution. Left in, it would mean mailing one dead address on most of your rows, Only jobs with a contact keeping everything because every row "has" a contact, and one-row-per-recruiter collapsing hundreds of jobs into a single phantom. This Actor returns null for both fields instead.
Why do the same email and company repeat? Recruitment consultancies post many vacancies under one contact. Use One row per recruiter to collapse them.
Support
Found a bug or need another India job board? Open an issue on the Actor's Issues tab, or email muhamed.didovic@gmail.com.
Explore more scrapers
Other India and Gulf job boards: Naukri, Instahyre, iimjobs, CutShort, Foundit, Internshala, Naukrigulf.
🤖 For AI Agents & LLM Apps
Purpose. Search Shine.com (India) and return structured job rows, including the hiring recruiter's real email and phone where published.
Minimal tested input
{ "keywords": ["python"], "location": "Bangalore", "maxItems": 50 }
Output fields (one row per job): jobId, jobTitle, companyName, companyId, recruiterEmail, recruiterPhone, salary, experience, locations, industry, employmentType, skills, jobDescription, jobDescriptionHtml, postedDate, closingDate, jobUrl, vacancies, isPremium, searchKeyword.
With scrapeJobDetails: true, each row also carries applyCount, salaryMin, salaryMax, locationStates, companyDescription, tags, functionalArea, roleCategories — at the cost of one request per job instead of one per twenty.
With uniqueRecruiters: true the shape changes to one row per contact: recruiterEmail, recruiterPhone, companyName, companyId, jobCount, jobTitles, locations, industries, latestPostedDate, jobIds.
Billing. Pay-per-event: one charge per row written to the dataset, plus one per run start. Fewer rows means a smaller bill, so use requireContact, postedWithinDays and monitorMode to narrow before paying.
Behaviours worth knowing
recruiterEmailandrecruiterPhonearenullon roughly 57% of jobs. That is correct: Shine substitutes a fixed fake pair for opted-out recruiters and this Actor strips it. Do not treatnullas a scrape failure.- An empty dataset can be a valid result — with
monitorModeorpostedWithinDays, "nothing new" and "nothing recent" both finish as SUCCEEDED and explain themselves in the log. monitorModekeeps state across runs in a named key-value store, so repeated calls with the samemonitorStateKeyreturn only what you have not already received.- Filters are applied by Shine's own search, not after the fact, so narrowing costs fewer requests rather than more.
⚠️ Disclaimer
This Actor collects only data that Shine.com publishes publicly, without logging in and without bypassing any access control. Recruiter contact details are published by Shine and the recruiters themselves as part of each job advert, for the purpose of being contacted about that role.
You are responsible for how you use the output. If you contact recruiters, applicable law may apply to you — including India's DPDP Act, and the GDPR or ePrivacy rules where recipients are in the EU/EEA or UK. Honour opt-outs, and do not use this data for unsolicited bulk marketing unrelated to recruitment. This Actor is not affiliated with, endorsed by, or connected to Shine.com or HT Media.
SEO Keywords
shine.com scraper, shine jobs scraper, india job scraper, indian job board scraper, recruiter email scraper, recruiter contact scraper, job leads india, shine job listings api, bangalore jobs scraper, mumbai jobs scraper, delhi jobs scraper, india recruitment leads, hiring data india, job posting scraper india, salary data india, tech jobs india scraper, consultancy contacts india, apify shine actor