Gdzieś około pięćdziesiątego kopiuj-wklejania nazwy stanowiska z Indeed do arkusza kalkulacyjnego zacząłem kwestionować swoje wybory zawodowe. Jeśli kiedykolwiek próbowałeś pobierać ustrukturyzowane dane z Indeed programowo, znasz już pointę: błąd 403 to nie usterka — to element systemu obronnego Indeed.
Indeed to największy na świecie portal z ofertami pracy, z około 350 milionami unikalnych użytkowników miesięcznie, 130 milionami ofert pracy w danym momencie i działalnością w ponad 60 krajach. To czyni go jednym z najbogatszych źródeł danych o rynku pracy na świecie — i jednym z najtrudniejszych do scrapowania. Otwarty scraper JobFunnel (tysiące gwiazdek na GitHubie) został dosłownie zarchiwizowany przez opiekuna projektu w grudniu 2025 roku po latach przegrywania wyścigu z zabezpieczeniami anty-bot. Słowa samego maintenera: „Wszyscy użytkownicy mogą scrapować pewną liczbę ofert, ale bardzo szybko trafiają na captcha, a scraping się nie udaje i nie zwraca żadnych ofert.” Inny współtwórca zgłosił CAPTCHA już przy pierwszym żądaniu. Tak więc — to nie jest banalny cel do scrapowania. W tym przewodniku pokażę każdy praktyczny sposób scrapowania Indeed w Pythonie, wyjaśnię, jak faktycznie przejść przez gąszcz błędów 403, a dla tych, którzy wolą całkiem pominąć debugowanie, pokażę alternatywę no-code z Thunderbit.
Co oznacza scrapowanie Indeed w Pythonie?
Web scraping to w swojej istocie automatyczne pobieranie ustrukturyzowanych danych ze stron internetowych. Gdy mówimy o scrapowaniu Indeed w Pythonie, mamy na myśli napisanie skryptu, który odwiedza wyniki wyszukiwania i strony szczegółów ofert pracy na Indeed, odczytuje bazowy HTML (lub osadzone dane) i wyciąga pola takie jak nazwa stanowiska, firma, lokalizacja, wynagrodzenie i opis do użytecznego formatu — CSV, baza danych, Arkusz Google.
Najczęściej używane biblioteki Pythona to Requests (do wywołań HTTP), BeautifulSoup (do parsowania HTML) oraz Selenium lub Playwright (do automatyzacji przeglądarki). Ale Indeed nie jest prostą statyczną stroną. To hybryda: HTML renderowany po stronie serwera z osadzonym blobem stanu JSON, chroniona przez Cloudflare Bot Management. Oznacza to, że Twój scraper musi poradzić sobie z treściami renderowanymi przez JavaScript, rotującymi nazwami klas CSS i agresywną ochroną anty-bot — zanim jeszcze przeanalizujesz choćby jedno stanowisko.
W 2026 roku nie istnieje też oficjalne, darmowe, tylko do odczytu API Indeed. Stare Publisher Jobs API zostało wycofane około 2020 roku, a to, co pozostało, jest przeznaczone wyłącznie dla pracodawców (Job Sync, Sponsored Jobs). Tak więc scrapowanie albo płatny zewnętrzny dostawca danych to jedyne realne opcje.
Po co scrapować dane o ofertach pracy z Indeed?
Biznesowy sens scrapowania Indeed jest prosty: ręczne przeglądanie tysięcy ogłoszeń jest niepraktyczne, a dane zawarte w tych ogłoszeniach są naprawdę wartościowe.

| Przypadek użycia | Kto zyskuje | Przykład |
|---|---|---|
| Generowanie leadów | Zespoły sprzedaży i rekrutacji | Tworzenie list firm rekrutujących wraz z danymi kontaktowymi |
| Badanie rynku pracy | Analitycy, zespoły HR | Identyfikacja trendów kompetencyjnych, benchmarków płacowych według regionu |
| Analiza konkurencji | Pracodawcy, agencje rekrutacyjne | Monitorowanie wzorców zatrudniania i oferowanych wynagrodzeń konkurencji |
| Automatyzacja osobistych poszukiwań pracy | Osoby szukające pracy | Zbieranie ofert spełniających kryteria z wielu lokalizacji |
| Dane treningowe dla modeli ML | Data scientists | Budowanie modeli predykcji wynagrodzeń na podstawie danych historycznych |
Badania samego Indeed Hiring Lab potwierdzają, że dane o ogłoszeniach ściśle śledzą statystyki BLS JOLTS i mogą służyć jako niemal bieżący wskaźnik warunków na amerykańskim rynku pracy. Fundusze hedgingowe wykorzystują tempo publikacji ofert jako alternatywny sygnał danych. Zespoły HR porównują wynagrodzenia na podstawie zeskrapowanych widełek płacowych. A rekruterzy budują listy potencjalnych klientów z firm, które aktywnie zatrudniają.
Jedna praktyczna uwaga: dane o wynagrodzeniach na Indeed są coraz lepsze, ale nadal niepełne. Od połowy 2025 roku około 59% ofert w USA zawiera informacje o płacy, ale tylko około 22% podaje dokładną kwotę — reszta to widełki. Każda analiza wynagrodzeń oparta na danych Indeed powinna brać pod uwagę tę niepełność.
Jak wybrać metodę scrapowania Indeed w Pythonie
Nie ma jednej „właściwej” metody scrapowania Indeed. Najlepsze podejście zależy od Twoich umiejętności, ilości potrzebnych danych i tego, ile utrzymania jesteś gotów zaakceptować. Przetestowałem wszystkie cztery główne podejścia i porównałem je tak:
| Kryterium | BS4 + Requests | Selenium | Ukryty JSON (window.mosaic) | No-code (Thunderbit) |
|---|---|---|---|---|
| Trudność | Początkujący | Średniozaawansowany | Średniozaawansowany–zaawansowany | Brak (2 kliknięcia) |
| Szybkość | Szybka | Wolna (renderowanie w przeglądarce) | Szybka | Szybka (scraping w chmurze) |
| Treści renderowane przez JS | Nie | Tak | Tak (osadzone dane) | Tak |
| Odporność na boty | Niska | Średnia (wykrywalne) | Średnio-wysoka | Wysoka (obsługiwane automatycznie) |
| Utrzymanie przy zmianach HTML | Wysokie (selektory się psują) | Wysokie | Średnie (struktura JSON jest stabilniejsza) | Brak (AI się adaptuje) |
| Najlepsze do | Szybkich prototypów | Dynamicznych stron, treści za logowaniem | Hurtowych danych strukturalnych | Osób nietechnicznych, szybkich wyników |
Ten przewodnik przeprowadzi Cię przez każdą metodę. Jeśli jesteś developerem Pythona, przydadzą Ci się sekcje o BS4, ukrytym JSON-ie i Selenium. Jeśli nie programujesz albo po prostu masz dość debugowania błędów 403, przejdź od razu do sekcji o Thunderbit.
Zanim zaczniesz
- Poziom trudności: od początkującego do średniozaawansowanego (sekcje Python); brak (sekcja Thunderbit)
- Szacowany czas: około 20–60 minut na konfigurację Pythona i pierwszy scraping; około 2 minuty z Thunderbit
- Czego potrzebujesz: Python 3.9+, edytor kodu, przeglądarka Chrome oraz — dla ścieżki no-code — rozszerzenie Thunderbit do Chrome
Konfiguracja środowiska Python do scrapowania Indeed
Zanim napiszesz jakikolwiek kod do scrapowania, przygotuj środowisko.
Zainstaluj wymagane biblioteki
Utwórz środowisko wirtualne i zainstaluj potrzebne pakiety:
python -m venv indeed_env
source indeed_env/bin/activate # W systemie Windows: indeed_env\Scripts\activate
# Do podejścia HTTP + parsowanie
pip install requests beautifulsoup4 lxml httpx
# Do podejścia z ukrytym JSON-em (zalecane)
pip install curl_cffi parsel tenacity
# Do podejścia automatyzacji przeglądarki
pip install selenium
Kilka uwag:
curl_cffito domyślny wybór w 2026 roku do scrapowania stron chronionych przez Cloudflare. Naśladuje prawdziwe odciski TLS przeglądarki, czego zwykłerequestsihttpxnie potrafią. Więcej o tym, dlaczego to ważne, w sekcji o anty-botach.- Selenium 4.6+ zawiera Selenium Manager, więc nie musisz już ręcznie pobierać ChromeDrivera — binaria przeglądarki są zarządzane automatycznie.
- Użyj
lxmljako backendu parsera dla BeautifulSoup. Jest mniej więcej 1,5x szybszy niż standardowyhtml.parserz biblioteki standardowej.
Utwórz strukturę projektu
Zachowaj prostotę:
indeed_scraper/
├── scraper.py
├── requirements.txt
└── output/
Wszystkie przykłady kodu poniżej opierają się na scraper.py.
Jak scrapować Indeed w Pythonie przy użyciu BeautifulSoup
To podejście przyjazne początkującym: użyj requests do pobrania strony i BeautifulSoup do parsowania HTML. Jest najszybsze do uruchomienia, ale na Indeed także najbardziej kruche.
Krok 1: Zbuduj adres URL wyszukiwania Indeed
Adresy wyszukiwania Indeed mają przewidywalny format:
https://www.indeed.com/jobs?q=<query>&l=<location>&start=<offset>
Na przykład wyszukiwanie hasła „data analyst” w „Austin, TX” od pierwszej strony:
from urllib.parse import urlencode
params = {
"q": "data analyst",
"l": "Austin, TX",
"start": 0,
}
url = f"https://www.indeed.com/jobs?{urlencode(params)}"
print(url)
# https://www.indeed.com/jobs?q=data+analyst&l=Austin%2C+TX&start=0
Indeed paginuje co 10 wyników, z twardym limitem 1000 rezultatów (start <= 990). Każda wartość start powyżej 990 po cichu zwraca tę samą stronę.
Krok 2: Wyślij żądanie HTTP z poprawnymi nagłówkami
Indeed natychmiast blokuje żądania z domyślnym ciągiem user-agent Pythona. Potrzebujesz realistycznych nagłówków:
import requests
headers = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36"
),
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"Referer": "https://www.indeed.com/",
}
response = requests.get(url, headers=headers, timeout=30)
print(response.status_code)
Jeśli dostaniesz 200, na razie jesteś w środku. Jeśli zobaczysz 403, Cloudflare Cię wykrył. (Więcej o przetrwaniu tego etapu poniżej.)
Krok 3: Wyodrębnij oferty pracy z HTML
Użyj BeautifulSoup, aby wybrać elementy kart ofert. Celuj w atrybuty data-testid — są stabilniejsze niż losowo zmieniane nazwy klas CSS Indeed:
from bs4 import BeautifulSoup
soup = BeautifulSoup(response.text, "lxml")
cards = soup.find_all("div", attrs={"data-testid": "slider_item"})
jobs = []
for card in cards:
title_el = card.find("h2", class_="jobTitle")
title = title_el.get_text(strip=True) if title_el else None
company = card.find(attrs={"data-testid": "company-name"})
location = card.find(attrs={"data-testid": "text-location"})
link = title_el.find("a")["href"] if title_el and title_el.find("a") else None
jobs.append({
"title": title,
"company": company.get_text(strip=True) if company else None,
"location": location.get_text(strip=True) if location else None,
"url": f"https://www.indeed.com{link}" if link else None,
})
print(f"Znaleziono {len(jobs)} ofert")
Krok 4: Obsłuż paginację
Przechodź po stronach, zwiększając parametr start:
import time, random
all_jobs = []
for page in range(0, 50, 10): # Pierwsze 5 stron
params["start"] = page
url = f"https://www.indeed.com/jobs?{urlencode(params)}"
response = requests.get(url, headers=headers, timeout=30)
# ... parsowanie jak wyżej ...
all_jobs.extend(jobs)
time.sleep(random.uniform(3, 6))
Ograniczenia tego podejścia
Będę szczery: BS4 + Requests to w 2026 roku najsłabsza metoda dla Indeed. Zwykłe requests używa bibliotecznego TLS Pythona, który tworzy odcisk JA3, natychmiast rozpoznawany przez Cloudflare jako „to nie jest przeglądarka”. Nie obsługuje też HTTP/2, z którego korzysta Indeed. Prawdopodobnie zostaniesz zablokowany po kilku stronach. A selektory CSS? Indeed często rotuje nazwy klas, takie jak css-1m4cuuf czy jobsearch-JobComponent-embeddedBody-1n0gh5s bardzo często — więc każdy selektor oparty na nich to tykająca bomba.
Użyj tej metody do szybkiego prototypowania na jednej stronie. Do czegokolwiek na większą skalę lepszy będzie ukryty JSON.
Jak scrapować Indeed w Pythonie z użyciem ukrytych danych JSON
To metoda, którą polecam większości developerów Pythona. Zamiast parsować kruche elementy HTML, wyciągasz ustrukturyzowane dane z zmiennej JavaScript osadzonej w źródle strony Indeed: window.mosaic.providerData["mosaic-provider-jobcards"].
Każde pole, które Cię interesuje — nazwa stanowiska, firma, lokalizacja, wynagrodzenie, klucz oferty, data publikacji, flaga pracy zdalnej — już znajduje się w tym blobie JSON. Nie potrzeba uruchamiania JavaScriptu. Schemat jest stabilny co najmniej od 2023 roku, dzięki czemu jest znacznie odporniejszy niż selektory DOM.
Krok 1: Pobierz HTML strony
Użyj curl_cffi zamiast requests — naśladuje prawdziwe odciski TLS przeglądarki, co ma kluczowe znaczenie przy obchodzeniu Cloudflare:
from curl_cffi import requests as cffi_requests
response = cffi_requests.get(
"https://www.indeed.com/jobs?q=python+developer&l=Remote&start=0",
impersonate="chrome124",
headers={
"Accept-Language": "en-US,en;q=0.9",
"Referer": "https://www.indeed.com/",
},
timeout=30,
)
print(response.status_code, len(response.text))
Dlaczego curl_cffi? To binding Pythona do curl-impersonate, który odtwarza dokładnie TLS ClientHello, ramkę HTTP/2 SETTINGS i kolejność nagłówków prawdziwych przeglądarek. To jedyny aktywnie rozwijany klient HTTP w Pythonie, który jednym wywołaniem omija zarówno JA3/JA4, jak i odcisk HTTP/2 Akamai. Obsługiwane cele impersonacji to m.in. chrome120, chrome124, chrome131, Safari i warianty Edge.
Krok 2: Wyodrębnij JSON wyrażeniem regularnym
Blob JSON jest osadzony w tagu <script>. Wyciągnij go za pomocą regexa:
import re, json
MOSAIC_RE = re.compile(
r'window\.mosaic\.providerData\["mosaic-provider-jobcards"\]=(\{.+?\});',
re.DOTALL,
)
match = MOSAIC_RE.search(response.text)
if match:
data = json.loads(match.group(1))
results = data["metaData"]["mosaicProviderJobCardsModel"]["results"]
print(f"Znaleziono {len(results)} ofert w ukrytym JSON-ie")
else:
print("Nie znaleziono ukrytego JSON-a — możliwa blokada lub zmiana strony")
Krok 3: Parsuj pola ofert z JSON-a
Każdy element w results zawiera więcej danych niż to, co widać na stronie:
jobs = []
for job in results:
jobs.append({
"jobkey": job["jobkey"],
"title": job["title"],
"company": job.get("company"),
"location": job.get("formattedLocation"),
"remote": job.get("remoteLocation"),
"salary": (job.get("salarySnippet") or {}).get("text"),
"posted": job.get("formattedRelativeTime"),
"job_type": job.get("jobTypes"),
"easy_apply": job.get("indeedApplyEnabled"),
"url": f"https://www.indeed.com/viewjob?jk={job['jobkey']}",
})
JSON często zawiera też szacunki wynagrodzeń, atrybuty taksonomiczne (tagi umiejętności) i oceny firm, które nie zawsze są widoczne w renderowanym HTML.
Krok 4: Scrapuj wiele stron
Użyj tierSummaries w JSON-ie, aby zrozumieć liczbę wyników, a następnie wykonaj pętlę:
import time, random
all_jobs = []
for start in range(0, 50, 10): # Pierwsze 5 stron
url = f"https://www.indeed.com/jobs?q=python+developer&l=Remote&start={start}&sort=date"
response = cffi_requests.get(
url,
impersonate="chrome124",
headers={"Accept-Language": "en-US,en;q=0.9", "Referer": "https://www.indeed.com/"},
timeout=30,
)
match = MOSAIC_RE.search(response.text)
if match:
data = json.loads(match.group(1))
results = data["metaData"]["mosaicProviderJobCardsModel"]["results"]
all_jobs.extend([{
"jobkey": j["jobkey"],
"title": j["title"],
"company": j.get("company"),
"location": j.get("formattedLocation"),
"salary": (j.get("salarySnippet") or {}).get("text"),
"url": f"https://www.indeed.com/viewjob?jk={j['jobkey']}",
} for j in results])
time.sleep(random.uniform(3, 7))
print(f"Łącznie: {len(all_jobs)} zeskrapowanych ofert")
Dlaczego ukryty JSON jest bardziej odporny
Struktura window.mosaic.providerData zmienia się rzadziej niż nazwy klas CSS. Otrzymujesz czyste, ustrukturyzowane dane bez parsowania chaotycznego HTML-a. Mimo to nadal potrzebujesz zabezpieczeń anty-bot (nagłówki, opóźnienia, proxy) — omówimy je za chwilę.
Jak scrapować Indeed w Pythonie z użyciem Selenium
Selenium to podejście z automatyzacją przeglądarki. Przydaje się, gdy musisz wchodzić w interakcję ze stroną — klikać do paneli szczegółów ofert, obsługiwać treści dostępne po zalogowaniu lub scrapować dynamicznie ładowane opisy, których nie ma w początkowym HTML-u.
Kiedy wybrać Selenium zamiast klienta HTTP
- Indeed ładuje część treści dynamicznie (pełne opisy ofert w panelu po prawej)
- Musisz scrapować strony wymagające sesji lub logowania
- Robisz małową skalę scrapingu, gdzie szybkość nie jest krytyczna
Szybki przykład
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
import time
options = Options()
options.add_argument("--disable-blink-features=AutomationControlled")
# options.add_argument("--headless=new") # Headless jest łatwiejszy do wykrycia — używaj ostrożnie
driver = webdriver.Chrome(options=options)
driver.get("https://www.indeed.com/jobs?q=data+engineer&l=New+York")
time.sleep(3)
cards = driver.find_elements(By.CSS_SELECTOR, "[data-testid='slider_item']")
for card in cards:
try:
title = card.find_element(By.CSS_SELECTOR, "h2.jobTitle").text
company = card.find_element(By.CSS_SELECTOR, "[data-testid='company-name']").text
location = card.find_element(By.CSS_SELECTOR, "[data-testid='text-location']").text
print(f"{title} | {company} | {location}")
except Exception:
continue
driver.quit()
Ograniczenia
Selenium jest wolne — każda strona wymaga pełnego renderowania przeglądarki. Headless Chrome jest wykrywalny przez system anty-bot Indeed (Cloudflare sprawdza navigator.webdriver, ciągi vendor dla WebGL, liczbę wtyczek i więcej). Nawet undetected-chromedriver tylko opóźnia wykrycie; nie zapobiega mu na stałe. A jak w BS4, selektory psują się, gdy Indeed aktualizuje interfejs.
W większości zastosowań ukryty JSON daje te same dane szybciej i przy mniejszym nakładzie utrzymania. Selenium zostaw na przypadki graniczne, gdy naprawdę potrzebujesz przeglądarki.
Jak unikać błędów 403 podczas scrapowania Indeed w Pythonie
To najważniejsza sekcja. Jeśli trafiłeś tutaj po sfrustrowanym wyszukiwaniu w Google, jesteś we właściwym miejscu.

Dlaczego Indeed blokuje Twój scraper
Indeed używa Cloudflare Bot Management wraz z Cloudflare Turnstile — nie DataDome i nie PerimeterX. Potwierdzają to nagłówki odpowiedzi: server: cloudflare, cf-ray oraz cookie __cf_bm do zarządzania botami. Cloudflare analizuje odcisk TLS (JA3/JA4), kolejność nagłówków HTTP/2, wzorce żądań i sygnały zachowania przeglądarki. Jeśli którykolwiek z nich wygląda nieludzko, dostaniesz 403, 429, 503 albo — najbardziej podstępny przypadek — odpowiedź 200 OK z wyzwaniem Turnstile zamiast prawdziwych danych o ofertach.
Rotuj User-Agent i nagłówki żądań
Jeden stały User-Agent to najszybszy sposób na blokadę. Rotuj go z puli aktualnych, realistycznych ciągów. Ważne: pola wersji pobocznej w Chrome są zamrożone na 0.0.0 po redukcji User-Agent — nie wymyślaj niezerowych wersji pobocznych, bo systemy anty-bot to wychwycą.
import random
USER_AGENTS = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36 Edg/145.0.3800.97",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 "
"(KHTML, like Gecko) Version/17.4 Safari/605.1.15",
]
headers = {
"User-Agent": random.choice(USER_AGENTS),
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br, zstd",
"Referer": "https://www.indeed.com/",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "same-origin",
}
Upewnij się też, że Twoje klientowe wskazówki sec-ch-ua pasują do wersji UA. sec-ch-ua: "Chrome";v="131" obok User-Agenta podającego Chrome 145 to natychmiastowy sygnał ostrzegawczy.
Dodawaj losowe opóźnienia między żądaniami
Stałe odstępy są wykrywane przez analizę wzorców. Użyj losowego jittera:
import time, random
# Między każdym żądaniem
time.sleep(random.uniform(3, 6))
# Przy ponownej próbie po blokadzie
def backoff_sleep(attempt):
base = 4
sleep_time = base * (2 ** attempt) + random.uniform(0, 2)
time.sleep(min(sleep_time, 60))
Wnioski z ScrapeOps i WebScraping.AI są zgodne: 3–6 sekund między żądaniami na jeden adres IP, z twardym limitem około 100 żądań na IP na sesję przed rotacją.
Użyj rotacji proxy
To najważniejszy pojedynczy czynnik sukcesu. Proxy datacenter z zakresów AWS/GCP osiągają około 5–15% skuteczności na celach Cloudflare Enterprise — praktycznie bezużyteczne na Indeed. Proxy residential wraz z poprawnym fingerprintingiem TLS podnoszą skuteczność do 80–95%.
PROXIES = [
"http://user:pass@us.residential.example:7777",
"http://user:pass@us.residential.example:7778",
"http://user:pass@us.residential.example:7779",
]
proxy = random.choice(PROXIES)
response = cffi_requests.get(
url,
impersonate="chrome124",
headers=headers,
proxies={"https": proxy},
timeout=30,
)
Ceny proxy residential w 2026 roku wynoszą około $4–8,50 za GB, zależnie od dostawcy i poziomu zobowiązania. W przypadku Indeed zacznij od małej puli i skaluj w razie potrzeby.
Elegancko obsługuj statusy 403, 429 i 503
Nie próbuj po prostu ponawiać bezmyślnie. Różne kody oznaczają różne rzeczy:
def fetch_with_retry(url, proxy_pool, max_retries=5):
for attempt in range(max_retries):
proxy = random.choice(proxy_pool)
headers["User-Agent"] = random.choice(USER_AGENTS)
try:
r = cffi_requests.get(
url,
impersonate=random.choice(["chrome124", "chrome120", "edge101"]),
headers=headers,
proxies={"https": proxy},
timeout=30,
)
# Sprawdź podstępny przypadek „200 z wyzwaniem”
if r.status_code == 200 and "cf-turnstile" not in r.text and "Just a moment" not in r.text:
return r
if r.status_code == 403:
print(f"403 — zablokowano. Rotacja proxy, próba {attempt + 1}")
elif r.status_code == 429:
print(f"429 — limit żądań. Zwolnij tempo.")
elif r.status_code == 503:
print(f"503 — serwer przeciążony lub wyzwanie JS.")
backoff_sleep(attempt)
except Exception as e:
print(f"Błąd żądania: {e}")
backoff_sleep(attempt)
raise RuntimeError(f"Nie udało się po {max_retries} próbach: {url}")
Przypadek 200-z-wyzwaniem jest najtrudniejszy. Zawsze skanuj treść odpowiedzi pod kątem znaczników cf-turnstile lub Just a moment, zanim uznasz 200 za sukces.
Łatwiejsza alternatywa: pozwól Thunderbit zająć się anty-botem
Dla użytkowników, którzy nie chcą budować i utrzymywać puli proxy, rotacji nagłówków i imitacji odcisków TLS, chmurowe scrapowanie Thunderbit obsługuje CAPTCHA, rotację proxy i zabezpieczenia anty-bot automatycznie. Bez konfiguracji proxy, bez ustawiania curl_cffi, bez bibliotek do rozwiązywania CAPTCHA. To najprostsza droga, gdy po prostu potrzebujesz danych.
Dlaczego Twój scraper Indeed ciągle się psuje (i jak to naprawić)
Mur 403 to ostry ból. Przewlekły ból to utrzymanie — scrape’y działające dziś, w przyszłym tygodniu zwracają puste dane albo przestarzałe wyniki.
Jak Indeed psuje Twoje selektory
Indeed agresywnie rotuje nazwy klas CSS. Przewodnik Bright Data wprost ostrzega, że klasy takie jak css-1m4cuuf i css-1rqpxry „wydają się być generowane losowo — prawdopodobnie podczas kompilacji”. Testy A/B sprawiają, że różni użytkownicy widzą różne układy strony. A restrukturyzacja DOM odbywa się bez zapowiedzi.
Historia JobFunnel jest tu pouczająca. Jeden ze współtwórców zgłosił: „CaptchaBuster skutecznie złagodził problem captcha, a powodem, dla którego nadal nie da się skutecznie scrapować strony, są nieaktualne selektory BeautifulSoup.” Scraper nie był blokowany — po prostu parsował niewłaściwe elementy.
Strategia: preferuj ukryty JSON zamiast parsowania DOM
Blob window.mosaic.providerData ma stabilny schemat co najmniej od 2023 roku. Ścieżka metaData.mosaicProviderJobCardsModel.results[] pozostaje kanoniczna także w 2026 roku. Selekory DOM psują się co miesiąc. Ekstrakcja JSON-a psuje się najwyżej raz do roku — jeśli w ogóle.
Strategia: używaj atrybutów danych zamiast nazw klas
Gdy musisz pracować z DOM-em, celuj w atrybuty funkcyjne:
| Selektor | Zastosowanie |
|---|---|
[data-testid="slider_item"] | Kontener każdej karty oferty |
[data-testid="job-title"] lub h2.jobTitle > a | Link z tytułem oferty |
[data-testid="company-name"] | Nazwa pracodawcy |
[data-testid="text-location"] | Tekst lokalizacji |
data-jk="<jobkey>" na każdej karcie | Najbardziej stabilny punkt zaczepienia — niezmienny od 2019 roku |
Dodaj asercje, by wykrywać przestarzałe selektory
Nigdy nie pozwól, aby scraper działał po cichu z zerowym wynikiem. Dodaj kontrolę po każdym pobraniu:
results = parse_hidden_json(html)
assert len(results) > 0, (
f"Indeed zwrócił pusty zestaw wyników przy start={start} — "
"możliwa blokada, CAPTCHA lub rozjazd selektorów. "
f"Pierwsze 500 znaków odpowiedzi: {html[:500]}"
)
W razie błędu zapisuj pierwsze 500–2000 znaków surowej odpowiedzi. Dzięki temu od razu rozpoznasz, czy otrzymałeś wyzwanie Turnstile, ścianę logowania czy zmianę schematu. Uruchamiaj codzienny test dymny na poziomie CI z ustalonym zapytaniem (np. q=python&l=remote) i sprawdzaj, czy wyników jest więcej niż zero.
Alternatywa AI: scrapery, które się nie psują
AI Thunderbit czyta strukturę strony od nowa za każdym razem — nie opiera się na twardo zakodowanych selektorach ani wzorcach regex. Gdy Indeed zmienia HTML, Thunderbit dostosowuje się automatycznie. To bezpośrednio rozwiązuje problem utrzymania, który użytkownicy forów konsekwentnie wskazują jako największą bolączkę. Jeśli kiedykolwiek obudziłeś się z wiadomością na Slacku: „scraper znowu zwraca puste wiersze”, wiesz, jaką wartość ma brak konieczności naprawiania tego ręcznie.
Scrapuj Indeed bez pisania Pythona: alternatywa no-code
Każdy konkurencyjny przewodnik zakłada, że napiszesz kod w Pythonie. Ale dane z forów mówią coś innego. Użytkownicy piszą rzeczy w stylu „to po prostu strasznie trudne przez ciągłe błędy i awarie”, a niektórzy sugerują zatrudnienie kogoś na Fiverr tylko po to, by zdobyć dane. Jeśli brzmi to znajomo, ta sekcja jest dla Ciebie.
Jak scrapować Indeed z Thunderbit (krok po kroku)
Krok 1: Zainstaluj rozszerzenie Thunderbit do Chrome z Chrome Web Store. Start jest darmowy.
Krok 2: Otwórz w przeglądarce stronę wyników wyszukiwania Indeed — na przykład https://www.indeed.com/jobs?q=data+analyst&l=Austin%2C+TX.
Krok 3: Kliknij ikonę Thunderbit na pasku narzędzi przeglądarki, a następnie kliknij „AI Suggest Fields.” AI Thunderbit przeskanuje stronę i automatycznie wykryje kolumny takie jak Tytuł stanowiska, Firma, Lokalizacja, Wynagrodzenie, URL oferty i Data publikacji. Możesz przejrzeć i dopasować sugerowane pola — usunąć niepotrzebne kolumny albo dodać własne, opisując po prostu, czego potrzebujesz.
Krok 4: Kliknij „Scrape.” Thunderbit wyciągnie dane ze strony i wyświetli je w ustrukturyzowanej tabeli. Powinieneś zobaczyć wiersze ofert pracy z polami, które skonfigurowałeś.
Wzbogacanie danych przez scraping podstron
Po zeskrapowaniu strony listy kliknij „Scrape Subpages”, aby Thunderbit odwiedził każdą indywidualną stronę szczegółów oferty. Pobiera pełne opisy stanowisk, wymagania, benefity i linki do aplikacji — bez dodatkowej konfiguracji. To odpowiednik napisania drugiego scrapersa w Pythonie, który odwiedza każdy URL /viewjob?jk=<jobkey>, tylko że zajmuje jedno kliknięcie.
Automatyczna obsługa paginacji
Thunderbit automatycznie obsługuje paginację Indeed opartą na kliknięciach. Nie musisz ręcznie konstruować adresów URL z offsetami ani pisać pętli paginacji. Program sam przechodzi przez strony i agreguje wyniki.
Eksport do ulubionych narzędzi
Eksportuj zeskrapowane dane do CSV, Excela, Arkuszy Google, Airtable lub Notion — całkowicie za darmo. Nie trzeba pisać kodu z csv.writer() ani pandas.to_csv().
Kiedy wybrać Python, a kiedy Thunderbit
| Scenariusz | Najlepsze narzędzie |
|---|---|
| Niestandardowe pipeline’y danych, automatyzacja według harmonogramu przez cron/Airflow | Python |
| Integracja z większą bazą kodu | Python |
| Silnie spersonalizowana logika parsowania | Python |
| Jednorazowe badania lub analiza rynku | Thunderbit |
| Dane są potrzebne członkom zespołu nietechnicznym | Thunderbit |
| Chcesz dane od razu, bez debugowania błędów 403 | Thunderbit |
| Wzbogacanie podstron bez żadnej konfiguracji | Thunderbit |
Porównanie czasu: konfiguracja Pythona + debugowanie anty-bot = od godzin do dni (zwłaszcza za pierwszym razem). Thunderbit = mniej niż 2 minuty na te same dane. Nie mówię, że Python jest zły — mówię, że wszystko zależy od tego, czego potrzebujesz.
Czy scrapowanie Indeed jest legalne? Co warto wiedzieć
Żaden z czołowych przewodników o scrapowaniu Indeed nie omawia legalności, co jest zaskakujące, biorąc pod uwagę, jak często w forach pojawia się pytanie „Czy scrapowanie Indeed jest legalne?”. To nie jest porada prawna, ale oto ogólny obraz.
Warunki korzystania z Indeed
Regulamin Indeed (indeed.com/legal) nie zawiera ogólnego zakazu „no scraping”. Jedyny wyraźny zakaz automatyzacji to sekcja A.3.5, która zabrania „używania automatyzacji, skryptów lub botów do automatyzacji procesu Indeed Apply”. To jest wąsko ograniczone do procesu aplikowania, a nie do biernego odczytywania publicznych ofert pracy. Głównym narzędziem egzekwowania po stronie Indeed są środki techniczne — wyzwania Cloudflare, bany IP, fingerprinting urządzeń — a nie sala sądowa.
Istotne precedensy prawne
Najczęściej przywoływaną sprawą w USA jest hiQ Labs v. LinkedIn. Dziewiąty Okręg orzekł w kwietniu 2022 roku, że scrapowanie publicznie dostępnych danych „prawdopodobnie nie narusza CFAA” (Computer Fraud and Abuse Act). Jednak później hiQ uznano za odpowiedzialne za naruszenie umowy, ponieważ jego pracownicy tworzyli fałszywe profile LinkedIn i akceptowali regulamin.
Niedawniej sprawa Meta v. Bright Data (N.D. Cal., styczeń 2024) przyniosła jeszcze jaśniejsze rozstrzygnięcie. Sędzia Chen uznał, że regulaminy Facebooka i Instagrama „nie zakazują scrapowania publicznych danych po wylogowaniu”. Meta wycofała pozostałe roszczenia w następnym miesiącu.
robots.txt w Indeed
robots.txt w Indeed szeroko blokuje /jobs/ i /job/ dla domyślnego User-agent: *, ale wyraźnie pozwala Googlebotowi i Bingbotowi na dostęp do /viewjob? — indywidualnych stron szczegółów ofert. Boty do trenowania AI (GPTBot, CCBot, anthropic-ai) są mocno ograniczone. robots.txt nie jest w USA prawnie wiążący, ale jego respektowanie jest dobrą praktyką i dowodem działania w dobrej wierze.
Praktyczne wskazówki odpowiedzialnego scrapowania
- Scrapuj wyłącznie publicznie dostępne dane — nigdy się nie loguj, nigdy nie twórz fałszywych kont
- Szanuj limity: 1 żądanie co 3–6 sekund na IP, mała liczba równoległych połączeń
- Nie publikuj zeskrapowanych danych jako własnego portalu z ofertami
- Używaj danych do osobistych lub wewnętrznych analiz, nie do komercyjnej odsprzedaży bez zgody
- Usuwaj lub haszuj dane osobowe, których nie potrzebujesz; ustal limit retencji dla danych powiązanych z osobami
- Jeśli działasz na dużą skalę lub w UE/Wielkiej Brytanii, skonsultuj się z prawnikiem — obowiązki informacyjne z art. 14 RODO dotyczą także zeskrapowanych danych osobowych
Spektrum ryzyka: automatyzacja osobistego szukania pracy jest na niskim końcu. Komercyjna odsprzedaż danych Indeed na dużą skalę jest na wysokim końcu.
Podsumowanie i najważniejsze wnioski
Scrapowanie Indeed w Pythonie jest możliwe, ale nie jest to projekt na weekend, który ustawiasz raz i zapominasz. Ochrona Cloudflare, rotujące selektory i agresywne zabezpieczenia anty-bot oznaczają, że trzeba podejść do tematu z odpowiednimi narzędziami i oczekiwaniami.
Oto, co zabieram z tego wszystkiego:
- Indeed to najbogatsze źródło danych o rynku pracy w sieci — 350 mln odwiedzin miesięcznie, 130 mln ofert — ale mocno walczy ze scraperami.
- Ekstrakcja ukrytego JSON-a (
window.mosaic.providerData) to najbardziej odporne podejście w Pythonie. Schemat jest stabilny od lat, podczas gdy selektory CSS psują się co miesiąc. curl_cffiz impersonacją przeglądarki to domyślny klient HTTP w 2026 roku dla stron chronionych przez Cloudflare. Zwykłerequestsihttpxsą blokowane już na poziomie odcisku TLS.- Zawsze używaj rotujących nagłówków, losowych opóźnień i proxy residential, aby uniknąć błędów 403. Proxy datacenter są praktycznie bezużyteczne przeciwko Cloudflare Enterprise.
- Dodawaj asercje, żeby od razu wiedzieć, kiedy selektory się psują albo gdy zamiast danych o ofertach serwowany jest ekran wyzwania.
- Dla użytkowników nietechnicznych lub każdego, kto po prostu chce szybkich rezultatów, Thunderbit oferuje ścieżkę no-code opartą na AI, która automatycznie dostosowuje się do zmian na stronie — bez proxy, bez debugowania, bez utrzymania.
Jeśli chcesz wypróbować ścieżkę no-code, Thunderbit oferuje darmowy plan, więc możesz przetestować go na Indeed bez żadnych zobowiązań. A jeśli wybierasz Pythona, przykłady kodu powyżej to solidny punkt startowy — pamiętaj tylko, aby traktować odporność na anty-bot jako wymaganie pierwszoplanowe, a nie późniejszy dodatek.
Więcej o podejściach i narzędziach do web scrapingu znajdziesz w naszych poradnikach: jak scrapować strony w Pythonie, najlepsze automatyczne narzędzia do web scrapingu oraz web scraping bez blokad. Możesz też obejrzeć poradniki na kanale Thunderbit na YouTube.
Wypróbuj Thunderbit, aby szybciej scrapować dane z Indeed Get Started Free
FAQ
Jakie biblioteki Pythona są najlepsze do scrapowania Indeed?
Do żądań HTTP najsilniejszym wyborem w 2026 roku jest curl_cffi — naśladuje prawdziwe odciski TLS przeglądarki, co jest kluczowe przy obchodzeniu Cloudflare. httpx z HTTP/2 to sensowny plan awaryjny dla mniej chronionych celów. Do parsowania HTML standardem pozostaje BeautifulSoup4 z lxml. Do automatyzacji przeglądarki działają Playwright (z playwright-stealth) albo undetected-chromedriver, choć oba są coraz łatwiejsze do wykrycia. Podejście z ukrytym JSON-em (window.mosaic.providerData) pozwala w ogóle uniknąć ciężkiego parsowania.
Dlaczego ciągle dostaję błędy 403 podczas scrapowania Indeed?
Indeed używa Cloudflare Bot Management, które analizuje odcisk TLS (JA3/JA4), kolejność nagłówków HTTP/2, wzorce żądań i zachowanie przeglądarki. Jeśli używasz zwykłego requests, odcisk TLS od razu zdradza, że to skrypt Pythona — 403 pojawia się zanim nagłówki zostaną w ogóle odczytane. Napraw to, przechodząc na curl_cffi z impersonacją przeglądarki, rotując realistyczne ciągi User-Agent, dodając losowe opóźnienia (3–6 sekund) i używając proxy residential. Sprawdzaj też przypadek „200 z wyzwaniem Turnstile” — skanuj treść odpowiedzi pod kątem znaczników cf-turnstile.
Czy mogę scrapować Indeed bez kodowania?
Tak. Narzędzia takie jak Thunderbit pozwalają wyciągać oferty pracy z Indeed w kilku kliknięciach — zainstaluj rozszerzenie Chrome, przejdź do strony wyszukiwania Indeed, kliknij „AI Suggest Fields”, a potem „Scrape”. AI Thunderbit automatycznie wykrywa pola takie jak nazwa stanowiska, firma, lokalizacja i wynagrodzenie. Obsługuje paginację, wzbogacanie podstron (pełne opisy stanowisk) oraz zabezpieczenia anty-bot automatycznie. Eksport do CSV, Arkuszy Google, Airtable lub Notion jest darmowy.
Jak często Indeed zmienia strukturę HTML?
Indeed regularnie rotuje nazwy klas CSS (np. css-1m4cuuf, losowo haszowane ciągi) i zmienia strukturę elementów DOM bez ostrzeżenia. Testy A/B oznaczają, że różni użytkownicy mogą widzieć różne układy jednocześnie. Podejście z ukrytym JSON-em (window.mosaic.providerData) jest znacznie stabilniejsze — schemat pozostaje spójny co najmniej od 2023 roku. Gdy musisz używać selektorów DOM, celuj w atrybuty data-testid i data-jk (klucz oferty), a nie klasy CSS.
Czy scrapowanie Indeed jest legalne?
Scrapowanie publicznie dostępnych URL-i ofert Indeed po wylogowaniu prawdopodobnie nie powoduje odpowiedzialności na gruncie CFAA w USA, biorąc pod uwagę wyrok 9. Okręgu w sprawie hiQ v. LinkedIn (2022) oraz decyzję Meta v. Bright Data (2024). Regulamin Indeed zabrania konkretnie automatyzacji procesu Apply, a nie biernego odczytu publicznych ogłoszeń. Mimo to zawsze scrapuj odpowiedzialnie: nie loguj się, nie twórz fałszywych kont, respektuj limity, nie publikuj danych jako własnego portalu z ofertami i ostrożnie obchodź się z danymi osobowymi (nazwiska rekruterów, e-maile) zgodnie z RODO/CCPA. W przypadku działań komercyjnych na dużą skalę skonsultuj się z prawnikiem.
Dowiedz się więcej


