Internshala Scraper - India Jobs & Internships, Salary
Pricing
from $1.46 / 1,000 posting returneds
Internshala Scraper - India Jobs & Internships, Salary
Every job and internship on Internshala with the fields the listing hides: pay as numbers with currency and period (annual CTC for jobs, monthly stipend for internships), experience, the deadline as a real date, skills, duration and the full description. A posting delisted mid-run is never charged.
Pricing
from $1.46 / 1,000 posting returneds
Rating
0.0
(0)
Developer
NeverEmpty
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
0
Monthly active users
8 hours ago
Last modified
Categories
Share
Pull jobs and internships from Internshala with the fields the listing page does not give you: pay as two numbers with its currency and period, years of experience as a number, and the application deadline as a real date - plus skills, duration, start date, perks and the full description.
Jobs carry an annual CTC. Internships carry a monthly stipend. Both land in the same columns with salaryPeriod set to year or month, so a monthly figure is never mistaken for a yearly one.
Unofficial. This Actor is not affiliated with, endorsed by, or sponsored by Internshala. All product names are trademarks of their respective owners.
No API key. No proxy needed. Reads the same public pages a visitor sees.
Why this one is different
A delisted job is an expired row, and it is never charged
Jobs come off Internshala while a run is going. Measured on 2026-09-03: a job that was in the listing returned HTTP 404 a few minutes later.
Most scrapers parse that page anyway and hand you a row where every field is null - and charge you for it. This Actor checks the response first and splits the result three ways:
status | What it means | Charged? |
|---|---|---|
ok | The job page was read | Yes |
expired | The job is no longer listed (HTTP 404) | No |
unparsable | The page could not be read (any other status, or the job block is missing) | No |
You always know which of the three you got. An empty result and a broken fetch are never the same row.
Salary comes back as numbers, not as a string
The page shows ₹ 4,22,000 - 6,18,000 /year. You get:
{ "salaryMin": 422000, "salaryMax": 618000, "salaryCurrency": "INR", "salaryPeriod": "year","salaryRaw": "₹ 4,22,000 - 6,18,000 /year" }
Indian digit grouping (4,22,000) is parsed correctly. A monthly figure comes back as "salaryPeriod": "month" - it is never silently reported as a yearly one. When the page has no figure, you get null, not 0. The raw string is always kept so you can check the parsing yourself.
The deadline is a date you can sort on
Apply By 11 Oct' 26 becomes "applyBy": "2026-10-11". If the page writes it in a form this Actor cannot read, applyBy is null and applyByRaw still holds the original text. No date is ever guessed.
It returns this job's values, not the neighbour's
An Internshala job page carries similar jobs on the same HTML. Measured: one page had 4 different salary blocks and 4 company names in it. This Actor reads only the job's own block, so the salary you get belongs to the job in the row.
The same applies to skills: Learn MS-Excel, Learn Digital Marketing are Internshala's own course adverts sitting next to the real skill list. They are not returned as required skills.
A city Internshala does not have tells you so
Measured on 2026-09-03: /jobs/bangalore/, /bangalore-jobs/ and /jobs/in-bangalore/ all return HTTP 200 while quietly ignoring the city - all 50 results are identical to the unfiltered listing, starting with a job in Mumbai. Only /full-time-jobs-in-<city>/ actually filters.
Worse: a city that does not exist at all (atlantis, zzzznotacity) also answers HTTP 200 and returns the unfiltered list. Waiting for a 404 never tells you.
So this Actor checks the result instead of the status code: it fetches the unfiltered listing once and compares. If your filtered page comes back as the same jobs, you get one no-listing row saying the filter does not exist, and it is not charged - you are never handed all-India jobs while thinking you filtered by city.
Input
| Field | Type | Default | Description |
|---|---|---|---|
listing | select | jobs | 9 listings, each checked against the live site: jobs, internships, work-from-home-jobs, remote-jobs, fresher-jobs, part-time-jobs, and the three internship equivalents |
city | string | — | Jobs only. e.g. bangalore, delhi, mumbai. Takes priority over the others. Internshala publishes no working city filter for internships, so it is not offered there |
category | string | — | Jobs only. e.g. marketing, computer-science, finance. Used when city is empty |
maxJobs | integer | 30 | 1-1000. One job = one row = one charged event |
includeDescription | boolean | true | The responsibilities/requirements text. Same price either way |
maxPages | integer | 0 | Safety cap on listing pages. 0 = decide from maxJobs |
keywords | array | — | Keep only listings containing one of these words |
keywordMatch | string | any | any or all |
excludeKeywords | array | — | Drop listings containing one of these |
companyNames | array | — | Keep only these companies (partial match) |
minSalary | integer | 0 | A listing with no published pay is kept |
requireSalary | boolean | false | Drop listings that do not state the pay |
postedWithinDays | integer | 0 | Apply-by within N days. An unreadable date is kept |
monitoringMode | boolean | false | Return only listings not returned on a previous run |
resetMonitoringState | boolean | false | One-shot: forget what was already returned |
Filters are applied before you are charged
The filters narrow what was already read - nothing extra is fetched from Internshala - and a listing that does not match is never delivered and never charged. If a search returns listings but none match your filters you get one unpaid row saying exactly that (no-filter-match), so you can tell "the filter was too tight" apart from "there is nothing there".
minSalary keeps a listing whose pay is not stated rather than treating it as low. Remember that jobs carry a yearly CTC and internships carry a monthly stipend - salaryPeriod tells you which, so set the threshold with the listing type you chose in mind.
Monitoring mode turns this into a daily feed
Schedule it with monitoringMode: true and each run returns only the listings it has not returned before. Already-seen listings are skipped before their pages are opened, so a repeat run is fast as well as free.
⚠️ Measured on 2026-09-04: Internshala reorders its listing between requests - the first page is not stable. So the first few monitoring runs return listings that are new to you but not necessarily newly posted, as the shuffle brings different postings into the window you scan. It settles: four consecutive runs with maxJobs: 6 returned 6, 1, 3, then 0. Use a larger maxJobs if you want it to settle sooner.
The minimal call is an empty input: it returns 30 jobs from the full listing.
Output columns
| Column | Example |
|---|---|
jobId | 1788172448 |
postingType | job or internship |
url | the job page |
status | ok / expired / unparsable / no-listing |
title | Business Development Manager |
company | Swing Saga |
location | Ghaziabad, Greater Noida, Delhi, Noida, Gurgaon |
salaryMin / salaryMax | 422000 / 618000 |
salaryCurrency / salaryPeriod | INR / year |
salaryRaw | ₹ 4,22,000 - 6,18,000 /year |
experienceYears / experienceRaw | 1 / 1 year(s) |
duration | 4 Months (internships) |
applyBy / applyByRaw | 2026-10-11 / 11 Oct' 26 |
startDate | Starts Immediately |
skills | ["Business Development", "Client Interaction", ...] |
perks | ["Informal dress code", "5 days a week"] |
postedRaw | Posted 2 days ago |
description | the full responsibilities text |
scrapedAt | ISO timestamp of the fetch |
Measured, not estimated
Everything above was checked against the live site on 2026-09-03, not read off documentation:
- Listing paging: 8 pages in a row, all HTTP 200, no rate limiting, 329 job URLs, about 1 second per page.
- The 404 case: a job present in the listing returned HTTP 404 minutes later. That is where the
expiredrow comes from. - The silent-filter case:
/jobs/bangalore/returned the unfiltered listing with HTTP 200 (50 of 50 URLs identical to/jobs/)./full-time-jobs-in-bangalore/returned 42 of 50 in Bangalore. - The neighbour-jobs case: one job page contained 4 salary blocks and 4 company names.
The Actor ships with 22 checks run against saved copies of real pages (a job page, an internship page, a delisted page and a listing page), and a mutation suite of 20 deliberate breakages - all 20 are caught. Breakages that the checks do not catch are listed in test/mutate.mjs with the reason, rather than quietly counted as passes.
Typical uses
- Track which companies in India are hiring, and at what CTC band.
- Build a salary benchmark by role, city or experience level - the numbers are already parsed.
- Watch a category (
marketing,computer-science) and alert on new postings before the deadline. - Feed a job board or a newsletter with fresh postings that still have an open
applyBydate. - Compare fresher vs experienced pay for the same title.
Notes and limits
- Internshala publishes Indian jobs. Salaries are in INR.
perksis returned only when the job page has a perks block; otherwise it is an empty array. Nothing is filled in to make the row look complete.- Fresher postings usually have no experience line at all. Those rows come back with
experienceYears: null, not0- "no requirement stated" and "zero years required" are different answers. postedRawis Internshala's own wording (Posted 2 days ago). It is not converted to a date, because the site does not publish an exact timestamp and this Actor does not guess one.- The listing pages are read as a visitor sees them.
https://internshala.com/robots.txtdoes not restrict them.
Other tools by NeverEmpty
- Job Listing API - Career Site Jobs from Any ATS - open roles from company career sites on Greenhouse, Lever, Ashby, Workable and Workday.
- ATS Job Board Finder - turn a company domain into the job board it actually uses.
Support
Found a row that looks wrong? Open an issue on the Actor page with the url from the row - the raw fields (salaryRaw, applyByRaw, experienceRaw) are kept precisely so a mistake can be checked against the page.