Proxies for Recruitment: Scraping Job Boards Ethically and at Scale
The labor market generates enormous volumes of structured data every day. Job postings, salary ranges, company reviews, skill requirements, location distributions, and hiring velocity are all publicly visible on job boards. For recruitment firms, HR tech companies, labor economists, and workforce planning teams, this data is a strategic asset -- if you can collect it reliably.
The challenge is that every major job board has invested heavily in anti-bot technology. Indeed, LinkedIn, Glassdoor, and ZipRecruiter all deploy sophisticated detection systems that identify and block automated access. A scraper that worked six months ago may be completely blocked today because these platforms continuously update their defenses.
Proxies are the infrastructure layer that sustains job data collection at scale. This guide covers the technical architecture, ethical considerations, proxy configuration by platform, and the operational patterns that keep data pipelines running reliably. For proxy fundamentals, see our job board monitoring use case and recruiting industry page.
The Job Board Data Landscape
Platform Protection Levels
| Platform | Protection Technology | Detection Sophistication | Success Rate (Residential) |
|---|---|---|---|
| Custom + Cloudflare | Very high | 75-85% | |
| Indeed | PerimeterX | High | 88-93% |
| Glassdoor | Cloudflare Enterprise | High | 85-90% |
| ZipRecruiter | Moderate Cloudflare | Medium | 92-96% |
| Dice | Basic rate limiting | Low | 97-99% |
| AngelList/Wellfound | Basic Cloudflare | Low-Medium | 94-97% |
| Government portals (USAJobs, etc.) | Minimal | Low | 99%+ |
| Niche industry boards | Minimal to basic | Low | 97-99% |
LinkedIn stands out as the most challenging target. Its custom anti-bot system combines IP analysis, behavioral scoring, browser fingerprinting, and account-level tracking into a multi-layered defense that achieves detection rates above 90% against naive scraping attempts.
What Data Is Collectible
Job postings: Title, description, company, location, salary range (when listed), posting date, application URL, required skills, experience level, employment type.
Company data: Company size, industry, headquarters location, benefits, employee reviews, salary surveys, interview process details.
Market intelligence: Job volume trends by skill/role/location, salary range distributions, time-to-fill estimates (inferred from posting duration), demand signals for emerging skills.
Proxy Configuration by Platform
Indeed
Indeed uses PerimeterX with moderate JavaScript challenges. The key to sustained access:
Proxy type: Residential proxies with per-request rotation. ISP proxies work for low-volume queries but get flagged at scale.
Request pacing: 2-4 seconds between requests. Indeed's rate limiting is per-IP but also tracks patterns across IPs from the same subnet.
Geographic targeting: Indeed localizes results by IP. To see jobs in Chicago, use a US residential IP. To see jobs in London, use a UK IP. Use Hex Proxies' geo-targeting to match the target market.
import requests
import random
import time
def search_indeed(
query: str,
location: str,
country: str = "us",
max_pages: int = 20,
) -> list:
"""
Search Indeed for job listings with residential proxy rotation.
Each page load uses a fresh IP to avoid rate limiting.
"""
proxy = {
"http": f"http://USER-country-{country}:PASS@gate.hexproxies.com:8080",
"https": f"http://USER-country-{country}:PASS@gate.hexproxies.com:8080",
}
all_jobs = []
for page in range(max_pages):
params = {
"q": query,
"l": location,
"start": page * 10,
"fromage": 14, # Last 14 days
}
headers = {
"User-Agent": random.choice(BROWSER_USER_AGENTS),
"Accept-Language": "en-US,en;q=0.9",
}
response = requests.get(
"https://www.indeed.com/jobs",
params=params,
proxies=proxy,
headers=headers,
timeout=20,
)
if response.status_code != 200:
break
jobs = parse_indeed_results(response.text)
if not jobs:
break
all_jobs.extend(jobs)
time.sleep(random.uniform(2, 5))
return all_jobs
LinkedIn requires the most careful approach. Their anti-bot system evaluates:
- IP reputation (datacenter and ISP IPs are immediately suspicious)
- Login state (many data points require authentication)
- Browser fingerprint consistency
- Navigation patterns (directly accessing a search results page without first visiting linkedin.com is suspicious)
Session strategy: Use sticky sessions with a 10-15 minute duration for each browsing "session." Within a session, maintain cookies and navigate naturally (homepage, then search, then results).
Authentication consideration: LinkedIn's richest data (full job descriptions, company insights, salary data) requires a logged-in session. Scraping authenticated LinkedIn at scale carries risk of account bans. For many use cases, public LinkedIn data (job titles, company names, locations from search results) is sufficient.
import requests
import uuid
def create_linkedin_session(country: str = "us") -> requests.Session:
"""
Create a LinkedIn-optimized session with sticky residential IP.
The session ID ensures the same IP persists for the session duration.
"""
session_id = uuid.uuid4().hex[:12]
session = requests.Session()
session.proxies = {
"http": f"http://USER-session-{session_id}-country-{country}:PASS@gate.hexproxies.com:8080",
"https": f"http://USER-session-{session_id}-country-{country}:PASS@gate.hexproxies.com:8080",
}
session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/124.0.0.0 Safari/537.36",
"Accept-Language": "en-US,en;q=0.9",
})
# Warm the session by visiting the homepage first
session.get("https://www.linkedin.com/", timeout=15)
time.sleep(random.uniform(2, 4))
return session
Glassdoor
Glassdoor uses Cloudflare Enterprise and requires JavaScript rendering for most of its content (reviews, salary data, interview questions).
Proxy type: Residential proxies. Glassdoor actively blocks ISP ranges.
Browser rendering: Required for most Glassdoor data. Use Playwright with residential proxy configuration.
Rate limiting: Glassdoor is aggressive about rate limiting. Keep requests to 1-2 per minute per session. Use many concurrent sessions with different IPs rather than fast sequential requests.
Government and Niche Boards
USAJobs, state job portals, and niche industry boards (Dice for tech, HealthcareJobSite for medical, etc.) typically have minimal anti-bot protection.
Proxy type: ISP proxies for speed and cost efficiency. Unlimited bandwidth means you can collect data aggressively without per-GB cost concerns.
Rate limiting: Respect the sites' robots.txt and implement 1-2 second delays as a courtesy, even if the sites do not enforce rate limits.
Ethical Scraping: A Framework
Job data collection raises ethical questions that are worth addressing directly, not as legal disclaimers but as engineering principles.
Principle 1: Collect Only Public Data
If a data point requires a login to access, it is not public data. Scraping behind authentication walls (logging in with fake accounts, bypassing paywalls) is ethically distinct from collecting data that is freely visible to any visitor.
For recruitment data collection, most valuable data points (job titles, companies, locations, posting dates, and often salary ranges) are available on public search result pages without authentication.
Principle 2: Respect Rate Limits
Just because your proxy infrastructure can sustain 10,000 requests per minute does not mean you should. Excessive request rates impose real costs on the target sites (server load, bandwidth, CDN charges).
A reasonable rate: 3-5 requests per minute to any single domain, distributed across IPs via rotation. This mimics the traffic pattern of a few dozen real users and imposes negligible server load.
Principle 3: Honor robots.txt When Reasonable
Check the target site's robots.txt for explicit disallow rules. While robots.txt is advisory (not legally binding in most jurisdictions), respecting it demonstrates good faith.
Exception: some sites use overly broad robots.txt rules that disallow all automated access, including search engine crawlers. In these cases, practical judgment applies. If Google can crawl the data, it is public.
Principle 4: Do Not Republish Raw Data
Collecting data for analysis (market trends, salary benchmarks, demand signals) is different from republishing the data verbatim. Aggregated, transformed, and analyzed data creates new value. A direct copy of Indeed's listings does not.
Architecture: Production Job Data Pipeline
Pipeline Components
Scheduler → Job Board Crawlers → Proxy Layer → Raw Data Store →
Deduplication → Enrichment → Analytics Database → API/Dashboard
Deduplication Strategy
The same job posting often appears on multiple boards. A software engineer role at Google might be listed on LinkedIn, Indeed, Glassdoor, Google Careers, and multiple aggregator sites. Without deduplication, your dataset inflates by 3-5x with duplicates.
Deduplication keys: company name (normalized) + job title (normalized) + location + posting date. Hash these four fields to create a unique identifier:
import hashlib
def generate_listing_id(company: str, title: str, location: str, date: str) -> str:
"""Generate a deterministic unique ID for deduplication."""
normalized = f"{company.lower().strip()}|{title.lower().strip()}|{location.lower().strip()}|{date}"
return hashlib.sha256(normalized.encode()).hexdigest()[:16]
Data Freshness and Refresh Strategy
Job postings are ephemeral. The average job posting is active for 30-45 days. A data pipeline must both discover new postings and detect removed postings.
Discovery: Run search result crawls daily for each target market and role category. New listings appear in search results automatically.
Removal detection: Re-check known listing URLs every 3-7 days. If a listing returns 404 or redirects to a "this job is no longer available" page, mark it as closed with the detection date.
Cost-efficient refresh: Only re-check listings that are still active. A listing confirmed active yesterday does not need re-checking today. Focus refresh bandwidth on listings last checked 3+ days ago.
Cost Analysis by Use Case
Recruitment Agency: Competitive Intelligence
A recruitment agency monitoring job openings across 500 target companies in a specific vertical (e.g., fintech), tracking 10 job boards, refreshing weekly.
- ~15,000 job listings to monitor
- 10 source sites × weekly refresh = 150,000 requests/month
- Average 100 KB per request = 15 GB/month
- Split: 10 GB residential (protected sites), 5 GB ISP (unprotected sites)
- Residential cost: $42.50-$47.50/month
- ISP cost: $20.80-$24.70/month (10 IPs, unlimited bandwidth)
- Total: $63.30-$72.20/month
HR Tech Company: Market Analytics Platform
An HR tech platform providing labor market analytics across all US markets, tracking 50 job categories, refreshing daily.
- ~500,000 active listings to track
- 5 major source sites × daily refresh = 2,500,000 requests/month
- Average 80 KB per request (API-targeted) = 200 GB/month
- Primarily residential (major portals dominate volume)
- Total: $850-$950/month
Labor Economist: Research Data Set
An academic or research team building a longitudinal job market dataset, sampling 100 markets monthly.
- ~200,000 listings per monthly sample
- 3 source sites per market = 600,000 requests/month
- Average 100 KB per request = 60 GB/month
- Total: $255-$285/month
Monitoring and Reliability
Success Rate Tracking
Track HTTP status codes by source platform:
# Track success rates per platform
metrics = {
"indeed": {"success": 0, "blocked": 0, "error": 0},
"linkedin": {"success": 0, "blocked": 0, "error": 0},
"glassdoor": {"success": 0, "blocked": 0, "error": 0},
}
def record_result(platform: str, status_code: int):
if status_code == 200:
metrics[platform]["success"] += 1
elif status_code in (403, 429):
metrics[platform]["blocked"] += 1
else:
metrics[platform]["error"] += 1
Alert thresholds: If any platform's success rate drops below 85% over a 24-hour window, investigate. Common causes: platform updated their anti-bot system, your request headers are outdated, or you are hitting rate limits too aggressively.
Data Quality Checks
Beyond HTTP success rates, validate that the data you collect is actually correct:
- Price/salary sanity checks: If a parsed salary is $0 or $10,000,000, the parser is likely broken.
- Location validation: Cross-check parsed locations against a known city/state database.
- Duplicate rate monitoring: If your deduplication rate suddenly spikes above 80%, a source may be serving cached or stale results.
Frequently Asked Questions
Is scraping job boards legal?
Publicly accessible job listing data is generally collectible under US law, particularly after the hiQ v. LinkedIn decision. However, scraping behind authentication, violating explicit terms of service, or republishing raw data may cross legal lines. Consult legal counsel for your specific use case.
Should I use headless browsers or raw HTTP requests?
Use raw HTTP requests wherever possible -- they are 10-50x more bandwidth-efficient than headless browsers. Only use headless browsers for platforms that render job data entirely in JavaScript (Glassdoor is the main example). For Indeed and most other platforms, raw HTTP requests with proper headers return complete data.
How do I handle LinkedIn's rate limiting?
LinkedIn's rate limiting is the most aggressive in the industry. Keep to 1-2 requests per minute per session, use sticky sessions with 10-15 minute durations, and warm sessions by visiting the homepage before search queries. Accept that LinkedIn data collection will be slower than other platforms.
What about using official APIs instead of scraping?
Indeed, LinkedIn, and others offer official APIs with various levels of access. These APIs are often limited in scope (fewer data points), require approval processes, and have usage caps. For comprehensive market data, web collection through proxies provides fuller coverage. Many organizations use both: official APIs for baseline data and proxy-based collection for supplementary intelligence.
Build your recruitment data pipeline with Hex Proxies. Residential proxies at $4.25-$4.75/GB handle protected platforms like Indeed and LinkedIn. ISP proxies at $2.08-$2.47/IP with unlimited bandwidth cover government boards and niche platforms. Visit our job board monitoring use case for more architecture patterns.