Warning
Under Development — The scraping pipeline (src/, scripts/, GitHub Actions CI) is complete and production-ready.
The EDA notebook and interactive dashboard are planned but not yet finished — see steps 4 & 5 below.
An end-to-end data pipeline that:
- ✅ 🔍 Discovers company slugs from AmbitionBox listing pages
- ✅ 💸 Scrapes per-company salary data by role, experience band, and city — bypassing JS rendering and bot protections
- ✅ 🧹 Merges batch CSVs from parallel GitHub Actions runs into a single clean dataset
- 📅 📊 Analyzes 21 EDA questions (planned —
notebooks/eda.pyunder development) - 📅 🎛️ Visualizes an interactive 4-tab dashboard (planned —
notebooks/dashboard.pyunder development)
Portfolio highlight: The scraper reverse-engineers AmbitionBox's Nuxt.js SSR
__NUXT__IIFE payload — extracting averaged salary figures that the site intentionally hides from the HTML.
salary-benchmark/
│
├── src/
│ ├── config.py # All project-wide constants & env-var overrides
│ └── scraper/
│ ├── discover.py # Crawl company listing pages → company slugs
│ ├── salary.py # Scrape per-company salary tables
│ ├── parser.py # __NUXT__ IIFE parser + HTML fallback
│ └── utils.py # HTTP session, UA/TLS rotation, retries, backoff
│
├── scripts/
│ ├── run_discover.py # CLI: discover all companies
│ ├── run_scrape.py # CLI: scrape salaries in configurable batches
│ └── merge_data.py # CLI: merge batch CSVs → salaries.csv
│
├── notebooks/
│ ├── eda.py # Marimo: 21 exploratory analysis questions
│ └── dashboard.py # Marimo: interactive 4-tab salary dashboard
│
├── tests/ # Pytest test suite
├── data/
│ ├── companies.csv # Discovered company slugs
│ ├── raw/ # Per-batch CSVs (git-ignored)
│ └── salaries.csv # Final merged dataset (git-ignored)
│
├── .github/workflows/
│ └── scrape.yml # GitHub Actions: discover / scrape / scrape-all / merge
├── pyproject.toml # uv project config + ruff linting rules
└── .python-version # Pinned: Python 3.12
# Install uv (fast Python package manager)
# macOS / Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows (PowerShell)
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"git clone https://github.com/<your-username>/salary-benchmark.git
cd salary-benchmark
uv sync # installs all dependencies into .venvCrawls AmbitionBox listing pages and saves company slugs to data/companies.csv.
# Default — metro cities, up to 500 pages
uv run python scripts/run_discover.py
# Quick test run (5 pages)
uv run python scripts/run_discover.py --max-pages 5
# Custom city list
uv run python scripts/run_discover.py --locations "mumbai,bengaluru,pune"
# No location filter (all of India)
uv run python scripts/run_discover.py --no-locations# Scrape companies 0–9
uv run python scripts/run_scrape.py --start 0 --size 10
# Include per-city breakdown (slower, enables geographic analysis)
uv run python scripts/run_scrape.py --start 0 --size 10 --with-locations
# Filter to CS/IT/Data roles only
uv run python scripts/run_scrape.py --start 0 --size 10 --cs-only💡 Set
LOG_LEVEL=DEBUGto see detailed request logs.
uv run python scripts/merge_data.pyCaution
WIP — notebooks/eda.py is still being refined. Charts may be incomplete or missing.
uv run marimo edit notebooks/eda.pyCaution
WIP — notebooks/dashboard.py is still being refined. Filters and tabs are subject to change.
uv run marimo run notebooks/dashboard.pyAll tunables are in src/config.py and can be overridden via environment variables — no code changes needed.
| Environment Variable | Default | Description |
|---|---|---|
DELAY_MIN |
1.0 |
Min seconds between requests |
DELAY_MAX |
3.0 |
Max seconds between requests |
MAX_RETRIES |
5 |
Retry attempts per URL |
RETRY_BACKOFF |
2.0 |
Exponential backoff multiplier |
REQUEST_TIMEOUT |
60 |
HTTP request timeout (seconds) |
MAX_LISTING_PAGES |
500 |
Max company listing pages to crawl |
MAX_SALARY_PAGES |
50 |
Max salary pages per company |
BATCH_SIZE |
10 |
Default companies per batch |
SCRAPER_PROXY |
(none) | Proxy URL (HTTP/HTTPS) |
DISCOVERY_LOCATIONS |
Metro cities | Comma-separated location slugs |
RATE_LIMIT_THROTTLE_THRESHOLD |
12 |
Extra sleep kicks in after N 429s |
RATE_LIMIT_EXTRA_SLEEP |
8 |
Extra sleep duration (seconds) |
The scraper is designed to run on GitHub Actions, which provides clean IPs and avoids local throttling.
Go to Actions → Scrape AmbitionBox Salaries → Run workflow
| Phase | What it does |
|---|---|
discover |
Crawl listing pages, save data/companies.csv |
scrape |
Scrape a single batch of companies |
scrape-all |
Compute matrix → parallel scrape all companies → auto-merge |
merge |
Combine all data/raw/batch_*.csv → data/salaries.csv |
Data is auto-committed back to the repo after each run via git-auto-commit-action.
Key inputs:
| Input | Default | Description |
|---|---|---|
batch_start |
0 |
Starting company index |
batch_size |
25 |
Companies per batch |
with_locations |
true |
Enable per-city salary breakdown |
cs_only |
false |
Filter to CS/IT/Data roles only |
max_pages |
500 |
Max listing pages for discovery |
all_locations |
false |
Scrape all cities, not just metros |
AmbitionBox uses Nuxt.js SSR — salary data is bundled into an obfuscated JavaScript self-executing function. The HTML blurs the avgCtc figure. parser.py uses brace-counting to extract the IIFE body and arguments, then resolves variable cross-references to recover the exact numeric salary values.
| Technique | Implementation |
|---|---|
| TLS fingerprint impersonation | curl-cffi impersonating Chrome 131, Firefox 135, Edge 101 |
| Request delays | Random uniform sleep between DELAY_MIN and DELAY_MAX |
| Exponential backoff | On 429 / 503 with jitter |
| Adaptive throttling | Extra sleep after N rate-limit hits |
| Bot challenge handling | Auto-follows bm-verify meta-refresh redirects |
| HTTP/2 stream recovery | Session reset on stream errors |
scrape-all computes a dynamic GitHub Actions matrix from data/companies.csv, spawning up to 10 parallel jobs each scraping 10 companies — the full dataset scraped in a single workflow run.
Click to expand
Salary Structure
- Which roles have the highest salaries across companies?
- Average salary by experience level
- Salary growth from 0–2 to 5–8 years
- Salary spread per role
- Median salary by role
- Overall salary distribution
Company Comparison 7. Which companies pay the most for the same role? 8. Data Analyst salary across companies 9. Salary distribution per company 10. Top-paying companies overall 11. Salary variance within companies
Industry Analysis 12. Which industries pay the highest? 13. IT vs Non-IT salary comparison 14. Finance vs Tech salary trends 15. Industry-wise salary growth
Experience Insights 16. Salary vs experience correlation 17. Highest paying roles at entry level 18. Mid-career salary leaders 19. Senior-level salary trends
Geographic Insights 20. Which cities offer the highest salaries? 21. Salary variation by location
| Tab | Description |
|---|---|
| Role Comparison | Top 25 roles by median salary with sidebar filters |
| Company Heatmap | Company × Role salary heatmap |
| Experience Curve | Median / mean salary vs years of experience |
| Industry Ranking | Industries ranked by median pay |
All tabs respond to unified sidebar filters: company, role, industry, experience band, and location.
| Column | Type | Description |
|---|---|---|
company_slug |
str |
URL slug |
company_name |
str |
Display name |
industry |
str |
Industry category |
job_role |
str |
Role / designation |
avg_salary |
float |
Average CTC (INR / year) |
min_salary |
float |
Minimum CTC |
max_salary |
float |
Maximum CTC |
typical_min_salary |
float |
Typical range lower bound |
typical_max_salary |
float |
Typical range upper bound |
min_experience |
int |
Min years of experience |
max_experience |
int |
Max years of experience |
datapoints |
int |
Number of salary reports |
location |
str |
City (populated with --with-locations) |
salary_lakhs |
float |
Derived: avg_salary / 100 000 |
mid_experience |
float |
Derived: (min + max) / 2 |
salary_range_width |
float |
Derived: max_salary - min_salary |
uv run pytest# With verbose output
uv run pytest -v
# Run linting
uv run ruff check .
# Auto-fix lint issues
uv run ruff check --fix .
# Format code
uv run ruff format .This project is for educational and portfolio purposes only.
Salary data is sourced from AmbitionBox and belongs to them.
Please review their Terms of Service before using this tool.