Kunci scraping job board dalam skala besar bukan menghindari CAPTCHA, melainkan menanganinya begitu muncul: deteksi tantangan di respons halaman, kirim ke API CaptchaAI untuk diselesaikan, lalu kirim ulang halaman dengan token yang valid. Dengan pola ini, satu scraper bisa terus berjalan di Indeed, LinkedIn, atau Glassdoor tanpa berhenti di setiap verifikasi.
Kebutuhan ini nyata di ekosistem data Indonesia — mulai dari pekerja lepas scraping di Fastwork dan Upwork, agensi pemantau gaji, hingga tim data startup yang menyusun laporan tren rekrutmen. Semuanya butuh pengumpulan data lowongan yang stabil, dan CAPTCHA adalah hambatan teknis terbesarnya. Panduan ini menunjukkan cara membangun scraper Python yang menyelesaikan hambatan itu secara otomatis.
Alur penanganan CAPTCHA saat scraping
Pola integrasinya selalu sama, apa pun job board-nya. Empat langkah ini yang perlu Anda pegang:
- Deteksi — periksa apakah respons halaman berisi penanda CAPTCHA (
data-sitekey,g-recaptcha, ataucf-turnstile) alih-alih data lowongan. - Kirim — ambil sitekey dari halaman dan kirim task ke
in.phpdengan method yang sesuai jenis CAPTCHA. - Polling — periksa
res.phpsecara berkala sampai token siap; CaptchaAI menyelesaikan reCAPTCHA v2 dan Turnstile dalam hitungan detik untuk tipe yang didukung. - Pakai token — sisipkan token ke field yang benar (
g-recaptcha-responseataucf-turnstile-response) dan kirim ulang request.
Memisahkan deteksi dari penyelesaian membuat kode lebih mudah dirawat: scraper Anda hanya memanggil layanan solver saat benar-benar bertemu tantangan, bukan di setiap request.
Jenis CAPTCHA di job board populer
Setiap platform memakai proteksi yang berbeda, dan strategi solve-nya mengikuti jenis tantangan yang dipasang. Tabel berikut merangkum apa yang biasa Anda temui:
| Platform | Jenis CAPTCHA | Pemicu | Data Tersedia |
|---|---|---|---|
| Indeed | reCAPTCHA v2 | Volume request tinggi | Daftar pekerjaan, gaji |
| Cloudflare Challenge | Deteksi bot | Pekerjaan, data perusahaan | |
| Glassdoor | reCAPTCHA v2 | Deteksi scraping | Ulasan, gaji, pekerjaan |
| ZipRecruiter | Cloudflare Turnstile | Akses otomatis | Daftar pekerjaan |
| Monster | reCAPTCHA v2 | Halaman pencarian | Daftar pekerjaan |
| CareerBuilder | reCAPTCHA v3 | Login, pencarian | Daftar pekerjaan, pencarian resume |
CaptchaAI menangani semua tipe di tabel ini — reCAPTCHA v2, reCAPTCHA v3, Cloudflare Turnstile, maupun Cloudflare Challenge — lewat satu endpoint yang sama.
Membangun scraper dengan deteksi CAPTCHA otomatis
Kelas JobBoardScraper di bawah menggabungkan keempat langkah tadi. Fungsi _has_captcha menandai halaman tantangan, dan _solve_and_retry memilih method yang tepat berdasarkan apakah halaman memuat penanda Turnstile atau reCAPTCHA sebelum mengirim ulang token:
import requests
import time
import re
from bs4 import BeautifulSoup
CAPTCHAAI_KEY = "YOUR_API_KEY"
CAPTCHAAI_URL = "https://ocr.captchaai.com"
def solve_captcha(method, sitekey, pageurl, **kwargs):
data = {
"key": CAPTCHAAI_KEY,
"method": method,
"googlekey": sitekey,
"pageurl": pageurl,
"json": 1,
}
data.update(kwargs)
resp = requests.post(f"{CAPTCHAAI_URL}/in.php", data=data)
task_id = resp.json()["request"]
for _ in range(60):
time.sleep(5)
result = requests.get(f"{CAPTCHAAI_URL}/res.php", params={
"key": CAPTCHAAI_KEY, "action": "get",
"id": task_id, "json": 1,
})
r = result.json()
if r["request"] != "CAPCHA_NOT_READY":
return r["request"]
raise TimeoutError("Solve timeout")
class JobBoardScraper:
def __init__(self, proxy=None):
self.session = requests.Session()
if proxy:
self.session.proxies = {"http": proxy, "https": proxy}
self.session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 Chrome/126.0.0.0 Safari/537.36",
"Accept-Language": "en-US,en;q=0.9",
})
def search_jobs(self, base_url, query, location, pages=5):
"""Search job listings across multiple pages."""
all_jobs = []
for page in range(pages):
url = f"{base_url}/jobs?q={query}&l={location}&start={page * 10}"
resp = self.session.get(url, timeout=30)
# Check for CAPTCHA
if self._has_captcha(resp.text):
resp = self._solve_and_retry(resp.text, url)
if resp.status_code == 200:
jobs = self._parse_listings(resp.text)
all_jobs.extend(jobs)
print(f"Page {page + 1}: {len(jobs)} jobs found")
else:
print(f"Page {page + 1}: Request failed ({resp.status_code})")
time.sleep(3) # Rate limit
return all_jobs
def _has_captcha(self, html):
indicators = [
'data-sitekey=',
'g-recaptcha',
'cf-turnstile',
'captcha-delivery',
]
return any(ind in html.lower() for ind in indicators)
def _solve_and_retry(self, html, url):
# Try reCAPTCHA first
match = re.search(r'data-sitekey="([^"]+)"', html)
if match:
sitekey = match.group(1)
# Detect Turnstile vs reCAPTCHA
if 'cf-turnstile' in html:
token = solve_captcha("turnstile", sitekey, url)
field = "cf-turnstile-response"
else:
token = solve_captcha("userrecaptcha", sitekey, url)
field = "g-recaptcha-response"
return self.session.post(url, data={field: token})
return self.session.get(url)
def _parse_listings(self, html):
soup = BeautifulSoup(html, "html.parser")
jobs = []
for card in soup.select(".job_seen_beacon, .jobsearch-ResultsList > li"):
title_el = card.select_one("h2 a, .jobTitle a")
company_el = card.select_one(".companyName, [data-testid='company-name']")
location_el = card.select_one(".companyLocation, [data-testid='text-location']")
salary_el = card.select_one(".salary-snippet, .estimated-salary")
if title_el:
jobs.append({
"title": title_el.get_text(strip=True),
"company": company_el.get_text(strip=True) if company_el else "",
"location": location_el.get_text(strip=True) if location_el else "",
"salary": salary_el.get_text(strip=True) if salary_el else "",
"url": title_el.get("href", ""),
})
return jobs
# Usage
scraper = JobBoardScraper(
proxy="http://user:pass@residential.proxy.com:5000"
)
jobs = scraper.search_jobs(
base_url="https://jobs.example.com",
query="python developer",
location="New York",
pages=10,
)
print(f"Total jobs collected: {len(jobs)}")
Mengumpulkan data gaji untuk analisis pasar
Setelah scraper dasar berjalan, kasus pakai paling umum adalah menyusun tabel gaji per jabatan dan lokasi. Fungsi berikut menjalankan pencarian untuk kombinasi judul dan lokasi, lalu menulis hasilnya ke CSV — format yang gampang dibawa ke spreadsheet atau notebook analisis:
import csv
def collect_salary_data(titles, locations, output_file):
"""Collect salary data across job titles and locations."""
scraper = JobBoardScraper(
proxy="http://user:pass@residential.proxy.com:5000"
)
results = []
for title in titles:
for location in locations:
try:
jobs = scraper.search_jobs(
"https://jobs.example.com",
title, location, pages=3,
)
salaries = [j["salary"] for j in jobs if j["salary"]]
results.append({
"title": title,
"location": location,
"listings": len(jobs),
"with_salary": len(salaries),
"salary_samples": "; ".join(salaries[:5]),
})
time.sleep(5)
except Exception as e:
results.append({
"title": title,
"location": location,
"error": str(e),
})
with open(output_file, "w", newline="") as f:
writer = csv.DictWriter(
f, fieldnames=["title", "location", "listings",
"with_salary", "salary_samples", "error"],
)
writer.writeheader()
writer.writerows(results)
return results
# Collect salary data for market analysis
collect_salary_data(
titles=["Data Engineer", "ML Engineer", "DevOps Engineer"],
locations=["San Francisco", "New York", "Austin", "Remote"],
output_file="salary_data.csv",
)
Menjaga scraper tetap stabil
CAPTCHA hanyalah satu lapis pertahanan. Agar scraper tidak terus-menerus memicu tantangan, atur ritme akses supaya menyerupai pola manusia:
| Teknik | Mengapa Membantu |
|---|---|
| Rotasi egress jaringan yang diotorisasi | Mendistribusikan request ke banyak IP nyata |
| Delay 3–5 detik antar halaman | Meniru kecepatan browsing manusia |
| User-Agent konsisten per session | Menghindari ketidakcocokan sinyal browser |
| Terima cookie | Job board melacak session via cookie |
| Acak urutan pencarian | Hindari pola halaman berurutan |
| Batasi hingga 200 halaman/hari per domain | Tetap di bawah threshold deteksi |
Untuk deployment, region cloud yang dekat dengan target memperkecil latensi — tim di Indonesia biasanya memakai AWS ap-southeast-1 (Singapura) atau ap-southeast-3 (Jakarta). Satu catatan kepatuhan: sesuai UU Pelindungan Data Pribadi (UU 27/2022), scraping sebaiknya dibatasi pada data lowongan publik dan menghindari data pribadi yang berada di balik login.
Pemecahan masalah umum
| Masalah | Penyebab | Solusi |
|---|---|---|
| CAPTCHA di setiap pencarian | IP di-flag atau rate limit terlampaui | Ganti IP, tambahkan delay lebih panjang |
| Halaman hasil kosong | CAPTCHA block dikembalikan | Deteksi CAPTCHA sebelum di-parse |
| "Harap verifikasi bahwa kamu manusia" | Deteksi bot terpicu | Gunakan egress jaringan yang diotorisasi + UA realistis |
| Login diperlukan untuk data gaji | Konten di-gate oleh platform | Implementasikan authenticated session |
| Hasil berbeda dari browser | Perbedaan lokasi/cookie | Cocokkan Accept-Language dan proxy geografi |
Biaya untuk scraping volume besar
Untuk operasi scraping berkelanjutan, model harga berbasis thread lebih mudah diprediksi ketimbang tarif per solve — poin yang penting bagi pekerja lepas dan agensi yang sensitif terhadap biaya. CaptchaAI menagih per thread bersamaan, dengan solve tak terbatas per thread selama sebulan. Paket BASIC ($15/bulan, 5 thread) cukup untuk scraper tunggal, sedangkan ADVANCE ($90/bulan, 50 thread) atau PREMIUM ($170/bulan, 100 thread) sesuai untuk beberapa job board yang di-scrape paralel. Berapa pun jumlah CAPTCHA yang muncul, biaya bulanan tetap.
Pertanyaan umum
Bagaimana cara mendeteksi CAPTCHA sebelum mem-parse halaman?
Periksa dulu apakah HTML respons memuat penanda seperti data-sitekey, g-recaptcha, atau cf-turnstile. Jika ada, halaman itu adalah tantangan, bukan data lowongan — selesaikan CAPTCHA lebih dulu sebelum menyerahkan HTML ke parser.
Apakah CaptchaAI bisa menangani Cloudflare Challenge di LinkedIn?
Ya. CaptchaAI mendukung Cloudflare Turnstile dan Cloudflare Challenge, serta reCAPTCHA v2 dan v3 yang dipakai job board lain. Satu integrasi API menangani seluruh tipe tersebut.
Berapa biaya untuk scraping job board volume tinggi?
Biaya mengikuti jumlah thread bersamaan, bukan jumlah solve. Mulai dari BASIC ($15/bulan, 5 thread); naikkan ke ADVANCE ($90/bulan, 50 thread) saat menjalankan banyak scraper sekaligus. Solve per thread tidak dibatasi.
Apakah scraping data lowongan diperbolehkan di Indonesia?
Sebagian besar job board memiliki ketentuan yang membatasi akses otomatis, dan penegakannya beragam. Fokuskan pada data lowongan yang bersifat publik, hindari data pribadi di balik login, dan perhatikan UU Pelindungan Data Pribadi (UU 27/2022). Panduan ini bukan nasihat hukum.
Panduan terkait
- Rotasi egress jaringan yang diotorisasi untuk penyelesaian CAPTCHA
- Sticky session vs rotating session untuk scraping