Compare Pay for a Role by Experience Level on Shine
Run one Shine.com search per experience band, keep the listings that show pay, and turn them into salary ranges in lakhs for each band.
The actor referenced in this article. Pay only for results delivered.
What does a Python developer with four years of experience get offered in Bangalore? Salary surveys answer once a year, for a broad job family, from people who chose to fill in a form. Job listings answer every day, for the exact title and city, from the people doing the hiring. The catch is that only some listings show pay, the pay is written as text, and a job board’s experience filter is looser than it looks.
Try it live: Shine.com Jobs Scraper. Pay per result delivered. Failed and empty results are never charged.
The Shine.com Jobs Scraper parses both halves for you. Shine’s pay text for a 10 to 22 lakh range becomes salary_min_lakhs: 10 and salary_max_lakhs: 22, and “4 to 6 Yrs” becomes experience_min_years: 4 and experience_max_years: 6. Run one search per experience band, keep the rows that show pay, and you have a pay ladder for the role.
What you get and who it is for
| Field | What it holds |
|---|---|
salary_min_lakhs, salary_max_lakhs | The posted range in lakhs a year, only when pay is shown |
salary_text | Shine’s own pay text, or [Salary Hidden] |
experience_min_years, experience_max_years | The experience range as numbers |
experience_text | The range as Shine shows it, such as “4 to 6 Yrs” |
title, company | Who is advertising the range |
skills | Shine’s keyword tags, to check the role matches |
employment_type, shift_type | Regular, contractual, internship or work from home; full or part time |
job_id, apply_url | The listing on shine.com |
This is for HR and compensation teams pricing a new opening, staffing firms quoting clients, candidates checking whether an offer is in line before they negotiate, and analysts tracking how advertised pay for a role moves between cities.
The input
One run per experience band, with everything else the same. This is the run for the 3 to 5 year band:
{
"searchKeywords": ["python developer"],
"location": "Bangalore",
"experienceLevel": "3to5",
"employmentType": "regular",
"maxJobs": 300
}
Then repeat it with experienceLevel set to each other band you care about. Shine’s bands are lt1, 1to2, 3to5, 6to8, 9to10, 11to15 and gt15.
experienceLeveltakes one band per run, which is why the Python script below loops. The filter matches any job whose experience range overlaps the band, so bands overlap too.employmentType: "regular"keeps internships and contract roles out. Their pay follows different rules and would pull the low end down.maxJobsshould be generous. Only a share of rows show pay, so 300 listings in a band may give you a few dozen offers or fewer. One keyword can reach 800 jobs, and one run can reach 1,000.- Leave out
salaryRange. It is Shine’s own pay filter, and it is too loose to measure pay with; see the limits below. locationtakes one city. Leave it empty to compare pay across all of India, then group by thelocationsfield yourself.
What the output looks like
[
{
"job_id": "19550923",
"title": "Urgent Hiring for Python Developer with AWS",
"company": "VIZLOGIC DIGITAL SOLUTIONS PRIVATE LIMITED",
"locations": ["Bangalore"],
"experience_min_years": 3,
"experience_max_years": 6,
"experience_text": "3 to 6 Yrs",
"salary_min_lakhs": 9,
"salary_max_lakhs": 14,
"employment_type": "regular",
"skills": ["python", "devops", "aws"],
"posted_days_ago": 15
},
{
"job_id": "19623932",
"title": "Python Developer",
"company": "REIS Staffing & HR Services Pvt Ltd",
"locations": ["Bangalore", "Gurugram", "Mumbai City", "Pune", "Chennai"],
"experience_min_years": 4,
"experience_max_years": 6,
"experience_text": "4 to 6 Yrs",
"salary_min_lakhs": 10,
"salary_max_lakhs": 20,
"employment_type": "regular",
"skills": ["python", "api", "pl"],
"posted_days_ago": 1
},
{
"job_id": "19654215",
"title": "Senior Python Developer",
"company": "Integrated Personnel Services Limited",
"locations": ["Bangalore", "Pune"],
"experience_min_years": 5,
"experience_max_years": 10,
"experience_text": "5 to 10 Yrs",
"salary_min_lakhs": 1,
"salary_max_lakhs": 26,
"employment_type": "regular",
"skills": ["python", "django", "microservices", "rest api", "fastapi"],
"posted_days_ago": 0
}
]
These rows come from our September 30, 2026 run of python developer in Bangalore with the 3to5 band, regular and full time, 100 jobs. It matches the input above except for maxJobs and the full time shift filter. We trimmed the description, salary_text (Shine’s original Lakh/Yr text), the dates and the recruiter name, which is often a person’s name.
Here is what those 100 rows gave once processed with the script below:
| Step | Rows |
|---|---|
| Listings returned | 100 |
| Listings showing pay | 14 |
| Distinct offers after merging reposts | 10 |
| Offers with a usable range (low at least a fifth of high) | 7 |
| Companies behind those 7 | 3 |
| Median range of the 7 | 10 to 20 lakhs |
Two things stand out. Four of the seven usable offers came from one staffing firm, so one recruiter’s brief sets most of the picture. And the third row above, 1 to 26 lakhs for a 5 to 10 year role, says almost nothing about pay; the script flags ranges like that instead of averaging them in.
Run it in Apify Console
- Open https://apify.com/themineworks/shine-jobs-scraper and click Try for free.
- In Search keywords, enter the role. Keep it to the title people use on Shine.
- Enter the city in Location, or clear it for all of India.
- Pick the first Experience level band, set Employment type to Regular and Salary range to Any.
- Set Max jobs to 300 and click Start.
- Change Experience level to the next band and start again. Each run has its own dataset.
- Export each dataset as CSV or Excel from the Storage tab, or use the script below to do all bands at once and merge them.
Run it from Python
pip install apify-client
import csv
import statistics
from apify_client import ApifyClient
client = ApifyClient("YOUR_APIFY_TOKEN")
ROLE = "python developer"
CITY = "Bangalore"
BANDS = ["1to2", "3to5", "6to8", "9to10", "11to15"]
jobs = {} # job_id -> row, so a job found in two bands counts once
for band in BANDS:
run = client.actor("themineworks/shine-jobs-scraper").call(run_input={
"searchKeywords": [ROLE],
"location": CITY,
"experienceLevel": band,
"employmentType": "regular",
"maxJobs": 300,
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
if "_type" in item:
continue # free summary and info rows
jobs.setdefault(item["job_id"], item)
def bucket(min_years):
for top, label in [(0, "0 yrs"), (2, "1 to 2 yrs"), (5, "3 to 5 yrs"),
(8, "6 to 8 yrs"), (10, "9 to 10 yrs"), (15, "11 to 15 yrs")]:
if min_years <= top:
return label
return "16+ yrs"
offers = {}
for job in jobs.values():
low, high = job.get("salary_min_lakhs"), job.get("salary_max_lakhs")
if low is None or high is None:
continue # pay hidden, or only one figure shown
# The same role reposted by the same recruiter is one offer, not five.
key = (job["title"].strip().lower(), job["company"], low, high,
job["experience_min_years"], job["experience_max_years"])
offers.setdefault(key, {
"band": bucket(job["experience_min_years"]),
"title": job["title"],
"company": job["company"],
"experience": job["experience_text"],
"low_lakhs": low,
"high_lakhs": high,
"wide_range": low * 5 < high, # e.g. 1 to 26 lakhs says little
"apply_url": job["apply_url"],
})
rows = sorted(offers.values(), key=lambda r: (r["band"], r["low_lakhs"]))
if rows:
with open("shine_pay_by_experience.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=list(rows[0].keys()))
writer.writeheader()
writer.writerows(rows)
print(f"{len(jobs)} listings, {len(offers)} distinct offers with pay")
for band in sorted({r["band"] for r in rows}):
usable = [r for r in rows if r["band"] == band and not r["wide_range"]]
if not usable:
continue
lows = [r["low_lakhs"] for r in usable]
highs = [r["high_lakhs"] for r in usable]
firms = len({r["company"] for r in usable})
print(f"{band:>12}: {len(usable):3} offers from {firms:2} companies, "
f"median {statistics.median(lows)} to {statistics.median(highs)} lakhs")
The script does three things a plain export does not:
- Groups by the parsed experience, not the filter. Our 3 to 5 year run returned 22 rows listed as “0 to 4 Yrs” and 24 as “5 to 9 Yrs”. Bucketing by
experience_min_yearsputs each offer where its own minimum says it belongs. - Merges reposts. Staffing firms post one brief several times with new job ids. Same title, company, pay and experience counts once.
- Flags placeholder ranges. A range whose low end is under a fifth of its high end is kept in the CSV but left out of the medians.
The printout ends with one line per band: how many usable offers, from how many companies, and the median low and high. Always read the company count next to the median. Seven offers from three companies is a signal, not a benchmark.
Refresh it every month
Pay ranges on Shine change with demand, so a monthly snapshot is enough. Save one task per band in Apify Console, open Schedules, and add all of them to one schedule with a cron expression such as 0 6 1 * * (06:00 on the first of each month) and your time zone set to Asia/Kolkata. Each run gets its own dataset. If you prefer code, skip the Console schedule and run the Python script once a month from cron on your own machine; it starts the five runs itself.
To build a trend, append each month’s band medians to a Google Sheet with a date column, or send the datasets there with Apify’s Google Sheets integration and chart low and high per band over time. After a few months of bands sitting next to each other, it is a short step to a hiring dashboard built from free job data. Leave monitor mode off for this job: you want the whole current market each month, not only what is new.
What it costs
Billing is per job delivered: $2.00 per 1,000 jobs on the Bronze plan, $1.75 on Silver, and $1.50 on Gold and above. There is no start fee and no charge per run, and summary rows and empty searches are free.
- Our 100-job run for the 3 to 5 year band cost $0.20 on Bronze.
- The five-band script above is at most 1,500 jobs: $3.00 on Bronze, about $2.63 on Silver and $2.25 on Gold. Because each band is its own run, a job that overlaps two bands is charged in both runs, even though the script counts it once.
- A monthly refresh of the same five bands is at most $36 a year on Bronze.
- The cheaper option is one run with no
experienceLevelandmaxJobs: 800, bucketed by the same script: at most $1.60 on Bronze. You lose control over how many rows each band gets, since Shine ranks by relevance, not by experience.
Limits worth knowing
- Most rows have no pay. About a third of Shine listings show a figure, and for some roles far fewer: 14 of 100 in our python developer run. Pay comparisons need volume, so widen with more keywords for the same role, such as
python developeranddjango developer, or with a largermaxJobs. - A few recruiters can dominate a band. Four of our seven usable 3 to 5 year offers came from one staffing firm. Check the company count before you quote a median.
- The salary filter is loose. Our 5-job test with
salaryRange: "6to8"for python developer in Bangalore, 3 to 5 years, returned rows titled Business Analyst, Data Analyst, Data Engineer and Software Developer, and the ranges that showed pay were 4 to 6, 8 to 14 and 7 to 10 lakhs. Use the parsed numbers, not the filter. - Experience bands overlap. The filter returns any overlapping range, so the same job can come back in two bands. The script keeps one copy by
job_id. - Single figures are left out. When Shine shows only a lower figure,
salary_max_lakhsis missing and the script skips the row. - Search matching can drift. Relevance sorting returns jobs whose title or skills match. In our run, 94 of 100 titles named Python, and the rest were roles like Android Developer that list Python as a skill. Filter on
titleif you want the exact role only. - Posted ranges, not closed offers. The data shows what recruiters advertise on Shine, much of it through agencies, not what people accept.
Related
Explore the scraper referenced in this article: inputs, outputs, and pricing, then run it on Apify.
Frequently asked questions
Why do so few Shine listings show pay? +
Recruiters choose whether to publish a figure. Across 1,000 rows in our tests about a third showed pay; in a Bangalore python developer search with the 3 to 5 year band it was 14 of 100. Hidden pay reads [Salary Hidden] and the number fields are left out.
Can I use the salaryRange filter to find what a role pays? +
Not for benchmarking. Shine's salary band matches loosely: our 6 to 8 lakh test for python developer returned analyst and data engineer roles with ranges such as 4 to 6 and 8 to 14 lakhs, and it does not raise the share of rows that show pay.
Does the experienceLevel filter return only that band? +
No. It returns jobs whose experience range overlaps the band. Our 3 to 5 year search included 0 to 4 year and 5 to 9 year listings, so this guide groups rows by the parsed experience_min_years instead of by the filter.
What does a salary_min_lakhs of 0.5 mean? +
Shine writes its lowest band in rupees, such as under Rs 50,000. The actor converts it to 0.5 lakhs. When Shine's text starts with a less-than sign, read 0.5 as up to Rs 50,000.
Are these the salaries people actually earn? +
No. They are the ranges recruiters post on Shine, often by staffing firms for a client. They tell you what the market is advertising for a role and band, not what offers close at.
Track New Shine.com Jobs for a Role Every Day
Get only the Shine.com jobs posted since your last check, for one role and city, using freshness sort, monitor mode and a daily schedule.
Compare Startup Salary and Equity Ranges by Role on Wellfound
Pull Wellfound jobs for several roles and cities, then compute median salary and equity ranges per role with the Wellfound Jobs Scraper and Python.
Compare Salary Bands in Lakhs by City and Experience on Foundit
Build a salary table for one role across Indian cities and experience bands from Foundit.in listings that publish pay, and drop the placeholder figures.
Post a Daily Feed of New Tech Jobs to Slack or Telegram
Send each morning's new Hirist.tech jobs for your stack and cities to a Slack or Telegram channel, with repeats filtered out by job id, using Python.