The Mine Works
← All posts
tutorial October 8, 2026 · 10 min read Updated October 8, 2026

Map Which Companies Are Hiring for a Tech Stack Across Indian Cities

Turn Hirist.tech job listings into a table of employers by city for one tech stack, with job counts, ratings and top skills, using Python.

Try the scraper

The actor referenced in this article. Pay only for results delivered.

View the scraper →

Which companies in Pune are hiring Spring Boot engineers this month, and are they the same ones hiring in Hyderabad? Recruiters ask it to find clients, sales teams ask it to find accounts, and engineers ask it before a move. Hirist.tech, the tech-only job board, has the answer spread across hundreds of listings, one job per card, with the same employer showing up under two names and the same job listed in five cities.

Try it live: Hirist Jobs Scraper: 147 Locations, 19 Fields, No Login. Pay per result delivered. Failed and empty results are never charged.

The Hirist Jobs Scraper returns one row per job with the company, every city the job lists, skill tags and the employer’s AmbitionBox rating. Fold those rows by company and by city and you have a hiring map for one stack.

What you get and who it is for

FieldWhat it holds
companyEmployer name exactly as Hirist stores it
titleThe role, often in the form “Company, role, stack”
locationsEvery city the job lists, such as Hyderabad or Gurgaon/Gurugram
remoteWhether Hirist marks the job work from home
skillsHirist’s skill tags, as a list
experience_min_years, experience_max_yearsThe experience band
company_rating, company_reviews_countAmbitionBox rating and review count, when shown
apply_countApplications Hirist reports
posted_at, job_id, urlWhen it was posted, its id and its page

The result is one row per employer with how many jobs they have open, in which cities, at what seniority and with which skills. It is for agency recruiters looking for companies to pitch, B2B sales teams selling to engineering orgs (a company hiring ten Kafka engineers is buying Kafka tooling), founders choosing where to open an office, and engineers checking who is hiring for their stack before they move.

The input

{
  "keyword": "Java Spring Boot Microservices",
  "locations": ["Bangalore", "Hyderabad", "Pune", "Chennai", "Gurgaon"],
  "postedWithinDays": 30,
  "maxJobs": 1000
}
  • keyword is free text sent to Hirist’s own search. Use the stack you care about. You can add a categorySlug such as backend-development-jobs or data-engineering-jobs to narrow it, or send only the category to map a whole field.
  • locations takes plain city names, old or new spellings (Gurgaon or Gurugram, Bangalore or Bengaluru). Several names mean “any of these”: a job is returned if it lists at least one.
  • postedWithinDays keeps the map current. Without it, keyword searches reach back months: in our Hyderabad run below, 6 of 15 jobs were more than 30 days old and the oldest had been up since July 14.
  • maxJobs is capped at 1,000 per run. If a run returns exactly that many, there were more jobs and you should split the run.

What the output looks like

These rows come from our run of "keyword": "Java Spring Boot Microservices" with "locations": ["Hyderabad"] and "maxJobs": 15 on October 2, 2026, trimmed:

[
  {
    "job_id": "1673871",
    "title": "Java Microservices Flowable Developer, Backend Technologies",
    "company": "Teamware Solutions",
    "company_rating": 4,
    "company_reviews_count": 874,
    "locations": ["Bangalore", "Hyderabad", "Pune", "Mumbai", "Chennai", "Noida", "Gurgaon/Gurugram", "Trivandrum/Thiruvananthapuram", "Cochin/Kochi", "Kerala"],
    "remote": true,
    "experience_min_years": 5,
    "experience_max_years": 10,
    "apply_count": 277,
    "posted_at": "2026-09-23T06:29:01.643Z"
  },
  {
    "job_id": "1655160",
    "title": "P99Soft, Senior Java Developer, Spring Boot/Microservices Architecture",
    "company": "P99soft",
    "company_rating": 3.9,
    "company_reviews_count": 47,
    "locations": ["Hyderabad"],
    "experience_min_years": 10,
    "experience_max_years": 12,
    "apply_count": 45,
    "posted_at": "2026-07-17T07:33:13.254Z"
  },
  {
    "job_id": "1674905",
    "title": "Java Developer, Spring Boot/Microservices Architecture",
    "company": "HiringBlaze",
    "locations": ["Hyderabad"],
    "experience_min_years": 5,
    "experience_max_years": 12,
    "apply_count": 1011,
    "posted_at": "2026-09-25T14:22:10.606Z"
  }
]

And one row from a separate run, our browse of the backend-development-jobs category on October 2, 2026, which shows why the company field needs a second look:

{
  "job_id": "1675004",
  "title": "EvoluteIQ, Technical Lead, Python",
  "company": "hirist.tech",
  "locations": ["Bangalore"],
  "experience_min_years": 12,
  "experience_max_years": 15,
  "premium": true
}

What these runs taught us about counting:

  • Jobs span cities. 7 of the 15 Hyderabad jobs also listed Bangalore, and the Teamware Solutions job listed ten places. A same-day Bangalore run with the same keyword shared 4 of its 15 jobs with the Hyderabad run.
  • The company field is not always the employer. In the backend category run, 4 of 30 rows had hirist.tech as the company, with the real employer at the start of the title. Elsewhere, staffing and hiring firms post for clients: in the Hyderabad run, HiringBlaze and SP Staffing Services appear as the company.
  • One employer, two names. Our 55 job run for java developer in the backend category in Bangalore on September 30 had Sapient (publicissapient) three times and Publicis Sapient once. That run held 46 company names, or 45 employers once the two Sapient names are merged. Accolite Digital and the merged Publicis Sapient led with 4 jobs each, and 41 of the 55 rows carried an AmbitionBox rating between 2.6 and 4.6.

Run it in Apify Console

  1. Open https://apify.com/themineworks/hirist-jobs-scraper and click Try for free.
  2. In Keyword, enter your stack. Set Category to (none) unless you want to narrow it.
  3. In Locations, enter one city per line.
  4. Set Posted within to 30 and Max jobs to 1000.
  5. Click Start. A 1,000 job run took 107 seconds in the actor’s own tests.
  6. Export as CSV or Excel. locations and skills arrive as numbered columns (locations/0, locations/1), which is awkward for a pivot table, so use the Python step below for the city counts.

Run it from Python

The script runs one multi-city search, so each job is fetched and charged once, then builds two files: one row per employer, and one row per employer and city.

pip install apify-client
import csv
import re
from collections import Counter, defaultdict
from statistics import median
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
CITIES = ["Bangalore", "Hyderabad", "Pune", "Chennai", "Gurgaon"]
MAX_JOBS = 1000

run = client.actor("themineworks/hirist-jobs-scraper").call(run_input={
    "keyword": "Java Spring Boot Microservices",
    "locations": CITIES,
    "postedWithinDays": 30,
    "maxJobs": MAX_JOBS,
})
jobs = [i for i in client.dataset(run["defaultDatasetId"]).iterate_items() if "job_id" in i]
if len(jobs) >= MAX_JOBS:
    print("Hit maxJobs: there are more jobs. Split by city or shorten postedWithinDays.")

ALIASES = {"sapient (publicissapient)": "Publicis Sapient"}
AGENCY = re.compile(r"staffing|recruit|hiring|manpower|placement|\bhr\b", re.I)

def employer(job):
    name = job["company"].strip()
    if name.lower() == "hirist.tech":
        name = re.split(r"\s+-\s+", job["title"], maxsplit=1)[0].strip()
    return ALIASES.get(name.lower(), name)

def cities_of(job):
    found = set()
    for loc in job["locations"]:
        for city in CITIES:
            if city.lower() in loc.lower():
                found.add(city)
    return found

by_company = defaultdict(list)
for j in jobs:
    by_company[employer(j)].append(j)

company_rows, city_rows = [], []
for name, js in by_company.items():
    city_count = Counter(c for j in js for c in cities_of(j))
    skills = Counter(s for j in js for s in j["skills"])
    rated = [j for j in js if "company_rating" in j]
    company_rows.append({
        "employer": name,
        "likely_agency": bool(AGENCY.search(name)),
        "open_jobs": len(js),
        "cities": "; ".join(f"{c} ({n})" for c, n in city_count.most_common()),
        "min_years_low": min(j["experience_min_years"] for j in js),
        "max_years_high": max(j["experience_max_years"] for j in js),
        "rating": rated[0]["company_rating"] if rated else None,
        "reviews": rated[0].get("company_reviews_count") if rated else None,
        "median_applications": median(j.get("apply_count", 0) for j in js),
        "top_skills": ", ".join(s for s, _ in skills.most_common(5)),
        "newest_post": max(j["posted_at"] for j in js)[:10],
    })
    for city, n in city_count.items():
        city_rows.append({"city": city, "employer": name, "jobs": n})

company_rows.sort(key=lambda r: r["open_jobs"], reverse=True)
city_rows.sort(key=lambda r: (r["city"], -r["jobs"]))

for path, rows in (("employers.csv", company_rows), ("employers_by_city.csv", city_rows)):
    if rows:
        with open(path, "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)} jobs from {len(by_company)} employers")
for city in CITIES:
    top = [r for r in city_rows if r["city"] == city][:5]
    print(city, ", ".join(f"{r['employer']} {r['jobs']}" for r in top))

employers.csv is sorted by open jobs, with the cities each employer hires in and a count per city, the experience range they span, their rating, the median applications their jobs draw and their five most common skill tags. employers_by_city.csv is long format, ready for a pivot table or a chart with one bar per employer per city.

Two parts need your judgment. ALIASES maps name variants to one employer; add to it as you spot them. likely_agency is a crude name check for words such as staffing, recruit and hiring. It misses firms with neutral names, so look over the top of the list by hand before you send it to anyone. A job that lists a city outside CITIES still counts toward its employer’s open jobs, but not toward any city column.

Refresh it every month

Save the input as a task in Apify Console, then open Schedules and add a monthly schedule, for example 0 9 1 * * with the timezone set to Asia/Kolkata. With postedWithinDays: 30, each run is a clean snapshot of the last month. Keep each month’s employers.csv and compare open_jobs per employer: a company that went from 2 to 9 jobs is scaling, and one that dropped out may have paused hiring. To send the rows somewhere without code, add Apify’s Google Sheets integration to the task, or use a webhook to call your own endpoint when the run finishes.

If one run hits the 1,000 cap, run one task per city instead. Remember that jobs listing several cities then come back in several runs and are charged in each; the 4 shared jobs between our Hyderabad and Bangalore runs would have been paid for twice. Deduplicate on job_id before counting.

What it costs

Billing is per job delivered: $4.20 per 1,000 jobs on the Bronze plan, $3.55 on Silver, and $3.00 on Gold and above, plus a flat $0.005 per run. Ratings and skill tags cost nothing extra, and repeated listings inside one run are removed before they are charged.

A monthly five city map that fills the 1,000 job cap costs $4.20 on Bronze plus $0.005, $3.555 on Silver and $3.005 on Gold. If you split it into five city runs of 200 jobs each, the job price is the same per job delivered, plus $0.025 in start fees, but multi-city jobs are paid for once per run they appear in. A smaller map is cheap: our 15 job Hyderabad run cost $0.063 on Bronze plus the $0.005 start fee.

Limits worth knowing

  • Agencies sit in the company field. Expect a share of your employer list to be staffing and hiring firms posting for clients they do not name.
  • Name variants split employers. Sapient (publicissapient) and Publicis Sapient were both in one run. Company names can also carry a trailing space, which the script strips.
  • Ratings are not on every row. 41 of 55 rows had one in our Bangalore run, and 14 of 26 in the combined Hyderabad and Bangalore runs. The rating is AmbitionBox’s, shown by Hirist, not a Hirist score.
  • Counts are listings, not headcount. One job can be for several hires, and one hire can be posted in ten cities.
  • Hirist is one board. Employers who recruit on Naukri or LinkedIn and not on Hirist will not appear, so read the map as Hirist’s view of the market. Naukri’s listings can be pulled in Python if you want a second board on the same map.
  • Salary is rare. Most recruiters hide it; when they do, the salary fields are left out of the row.
Related Actor

Explore the scraper referenced in this article: inputs, outputs, and pricing, then run it on Apify.

Frequently asked questions

Why does a Hyderabad search return jobs in Bangalore? +

Many Hirist jobs list several cities, and a location filter keeps any job that lists the city you asked for. In our October 2, 2026 Hyderabad run, 7 of 15 jobs also listed Bangalore. Count cities from each job's locations list, not from the filter you sent.

Some rows say the company is hirist.tech. Who is hiring? +

Hirist posts some featured listings under its own name and puts the employer at the start of the title, as in the EvoluteIQ Technical Lead row shown below. The script in this guide takes the employer from the title in that case.

Are the companies the actual employers? +

Not always. Staffing and hiring firms post on Hirist for their clients, and their name is what appears in the company field. Treat them as a separate group when you count.

Can I get every job in a city? +

Up to 1,000 jobs per run. If a run returns exactly your maxJobs, there were more; split the run by city or shorten postedWithinDays.

Apify Store

Find a ready-made scraper for your job

Apify has over 80,000 scrapers and automations, ours included. Start free with $5 of platform credit every month.