大概在我把 Indeed 上第 50 個職稱複製貼到試算表的時候,我開始懷疑自己的人生選擇。如果你也曾試著用程式方式從 Indeed 擷取結構化資料,你一定懂這個笑點:403 錯誤不是 bug,而是 Indeed 防禦系統的一部分。
Indeed 是全球最大的求職平台,目前大約有 、任何時點約 ,業務遍及 。這也讓它成為全世界最有價值的職場資料來源之一,同時也是最難抓的網站之一。開源爬蟲 JobFunnel(GitHub 上曾有數千顆星)甚至在 2025 年 12 月因長期敗給反機器人攻防戰,而被維護者。維護者自己都說過:「所有使用者都能抓到一些職缺,但很快就會碰到驗證碼,接著抓取失敗,最後什麼工作資料都拿不到。」 另一位貢獻者甚至回報,自己在就遇到 CAPTCHA。沒錯,這絕對不是一個輕鬆的抓取目標。本指南會帶你走過所有實際可行的 Python 抓取方法,示範如何真正扛住 403 大魔王;如果你想直接跳過除錯,我也會介紹一個用 的免寫程式替代方案。
用 Python 抓取 Indeed 是什麼意思?
網頁爬蟲的核心,就是自動從網頁中提取結構化資料。當我們說要用 Python 抓取 Indeed 時,指的是撰寫一支程式去造訪 Indeed 的搜尋結果頁與職缺詳情頁,讀取底層 HTML(或內嵌資料),再把職稱、公司、地點、薪資、描述等欄位整理成可用格式,例如 CSV、資料庫或 Google 試算表。
常見會用到的 Python 套件包括 Requests(發送 HTTP 請求)、BeautifulSoup(解析 HTML),以及 Selenium 或 Playwright(做瀏覽器自動化)。但 Indeed 不是單純的靜態網站。它是個混合型網站:伺服器先渲染 HTML,裡面還嵌有 JSON 狀態區塊,外層則有 Cloudflare Bot Management 防護。也就是說,你的爬蟲得先應付 JavaScript 動態內容、輪換的 CSS class 名稱,以及強硬的反機器人機制,才能真正開始解析職稱。
到了 2026 年,也沒有官方、免費、唯讀的 Indeed API。舊的 Publisher Jobs API 大約在 2020 年已被停用,現在剩下的只限雇主端功能(Job Sync、Sponsored Jobs)。所以,實際上你只能選擇抓取,或付費向第三方資料供應商購買資料。
為什麼要抓取 Indeed 的職缺資料?
抓取 Indeed 的商業價值其實很直接:手動瀏覽成千上萬筆職缺根本不切實際,而這些職缺裡的資料也真的很有價值。

| 使用情境 | 受益對象 | 範例 |
|---|---|---|
| 潛在客戶開發 | 業務與招募團隊 | 建立正在招募的公司名單與聯絡資訊 |
| 職場市場研究 | 分析師、HR 團隊 | 找出熱門技能、各地區薪資基準 |
| 競爭情報 | 雇主、人資顧問公司 | 觀察競爭對手的招募節奏與薪資條件 |
| 個人求職自動化 | 求職者 | 彙整符合條件、跨地區的職缺列表 |
| 機器學習訓練資料 | 資料科學家 | 以歷史職缺資料建立薪資預測模型 |
Indeed Hiring Lab 自己的研究也職缺資料和美國 BLS JOLTS 指標高度相關,可作為美國勞動市場近即時的代理訊號。對沖基金會把職缺發布速度當作另類資料訊號。HR 團隊會用抓下來的薪資區間做薪酬基準比較,而招募人員則會從正在大舉徵才的公司中建立名單。
不過有一點要注意:Indeed 上的薪資資料雖然持續改善,但仍不完整。到 2025 年中,大約 會顯示薪資資訊,但只有約 是精確數字,其餘多半只是區間。任何基於 Indeed 薪資資料的分析,都必須把這種缺漏情況考慮進去。
用 Python 抓取 Indeed:該怎麼選方法?
抓取 Indeed 沒有唯一的「正解」。最佳做法取決於你的技術程度、需要多少資料,以及你願意承擔多少維護成本。我測過四種主流方法,差異如下:
This paragraph contains content that cannot be parsed and has been skipped.
本指南會逐一說明每種方法。如果你是 Python 開發者,建議看 BS4、隱藏 JSON 和 Selenium 這幾段;如果你不是工程背景,或只是受夠 403 除錯,直接跳到 Thunderbit 那一段即可。
開始前你需要知道的事
- 難度: 初學者到中級(Python 部分);幾乎沒有(Thunderbit 部分)
- 所需時間: Python 環境設定與第一次抓取約 20–60 分鐘;Thunderbit 約 2 分鐘
- 你需要準備: Python 3.9+、程式編輯器、Chrome 瀏覽器,以及(免寫程式路線)
為 Indeed 抓取設定 Python 環境
在寫任何爬蟲程式之前,先把環境準備好。
安裝必要套件
先建立虛擬環境,再安裝所需套件:
1python -m venv indeed_env
2source indeed_env/bin/activate # Windows 請用:indeed_env\Scripts\activate
3# HTTP + 解析方案
4pip install requests beautifulsoup4 lxml httpx
5# 隱藏 JSON 方案(建議)
6pip install curl_cffi parsel tenacity
7# 瀏覽器自動化方案
8pip install selenium
幾個補充說明:
curl_cffi是 2026 年抓取受 Cloudflare 保護網站的預設選擇。它能模擬真實瀏覽器的 TLS 指紋,而一般requests和httpx做不到。這也是為什麼它在反機器人場景特別重要,後面會再說明。- Selenium 4.6+ 已內建 Selenium Manager,所以你不再需要手動下載 ChromeDriver,它會自動管理瀏覽器元件。
BeautifulSoup的解析後端建議用lxml,速度大約比標準庫的html.parser快 。
建立專案結構
保持簡單即可:
1indeed_scraper/
2├── scraper.py
3├── requirements.txt
4└── output/
以下範例都會以 scraper.py 為基礎。
如何用 BeautifulSoup 抓取 Indeed
這是最適合初學者的方法:用 requests 抓頁面,再用 BeautifulSoup 解析 HTML。設定最快,但在 Indeed 上也最脆弱。
步驟 1:建立 Indeed 搜尋網址
Indeed 的搜尋網址格式很固定:
1https://www.indeed.com/jobs?q=<query>&l=<location>&start=<offset>
例如,搜尋 Austin, TX 地區的「data analyst」,並從第一頁開始:
1from urllib.parse import urlencode
2> This paragraph contains content that cannot be parsed and has been skipped.
3> This paragraph contains content that cannot be parsed and has been skipped.
4### 步驟 2:加入正確標頭發送 HTTP 請求
5如果你直接用 Python 預設的 user-agent,Indeed 會立刻封鎖。你需要看起來像真的瀏覽器:
6```python
7import requests
8> This paragraph contains content that cannot be parsed and has been skipped.
9response = requests.get(url, headers=headers, timeout=30)
10print(response.status_code)
如果你拿到 200,代表暫時過關了。如果是 403,就表示被 Cloudflare 擋下來了。(後面會說怎麼提高存活率。)
步驟 3:從 HTML 中解析職缺列表
用 BeautifulSoup 選取職缺卡片元素。盡量抓 data-testid 屬性,因為它比 Indeed 會隨機變動的 CSS class 名稱穩定得多:
1from bs4 import BeautifulSoup
2> This paragraph contains content that cannot be parsed and has been skipped.
3> This paragraph contains content that cannot be parsed and has been skipped.
4> This paragraph contains content that cannot be parsed and has been skipped.
5print(f"Found {len(jobs)} jobs")
步驟 4:處理分頁
透過遞增 start 參數來翻頁:
1import time, random
2all_jobs = []
3for page in range(0, 50, 10): # 前 5 頁
4 params["start"] = page
5 url = f"https://www.indeed.com/jobs?{urlencode(params)}"
6 response = requests.get(url, headers=headers, timeout=30)
7 # ... 依上面方式解析 ...
8 all_jobs.extend(jobs)
9 time.sleep(random.uniform(3, 6))
這種方法的限制
我先講白一點:到 2026 年,BS4 + Requests 是抓 Indeed 最弱的方法。普通 requests 使用的是 Python 標準庫 TLS,會產生 Cloudflare 一眼就能辨識的 ,直接被判定成「不是瀏覽器」。它也不支援 Indeed 目前使用的 HTTP/2。你很可能抓到幾頁之後就被擋。再加上 CSS 選擇器呢?Indeed 常常輪換像 css-1m4cuuf、jobsearch-JobComponent-embeddedBody-1n0gh5s 這類 class 名稱——任何直接依賴這些 class 的 selector,都像是埋了定時炸彈。
這種方法只適合單頁快速原型測試。若要規模化抓取,建議改用隱藏 JSON 方案。
如何用 Python 抓取 Indeed 的隱藏 JSON 資料
這是我最推薦給大多數 Python 開發者的方法。與其解析脆弱的 HTML 元素,不如直接從 Indeed 頁面原始碼中的 JavaScript 變數擷取結構化資料:window.mosaic.providerData["mosaic-provider-jobcards"]。
你在乎的欄位——職稱、公司、地點、薪資、job key、刊登時間、是否遠端——其實都已經在這個 JSON 區塊裡了,不需要執行 JavaScript。這個 schema 至少自 就相當穩定,比 DOM selector 可靠得多。
步驟 1:抓取頁面 HTML
這裡建議用 curl_cffi 代替 requests,因為它能模擬真實瀏覽器的 TLS 指紋,對抗 Cloudflare 很關鍵:
1from curl_cffi import requests as cffi_requests
2> This paragraph contains content that cannot be parsed and has been skipped.
3為什麼用 `curl_cffi`?它是建立在 curl-impersonate 之上的 Python 封裝,能重現真實瀏覽器的 TLS ClientHello、HTTP/2 SETTINGS frame,以及標頭順序。它是目前少數能同時對抗 [JA3/JA4 與 Akamai H2 指紋](https://github.com/lexiforest/curl_cffi)的 Python HTTP 客戶端,而且只要一次呼叫就能完成。支援的模擬目標包含 `chrome120`、`chrome124`、`chrome131`、Safari 與 Edge 版本。
4### 步驟 2:用正規表示式擷取 JSON
5這段 JSON 會包在 `<script>` 標籤裡。你可以用 regex 把它抓出來:
6```python
7import re, json
8MOSAIC_RE = re.compile(
9 r'window\.mosaic\.providerData\["mosaic-provider-jobcards"\]=(\{.+?\});',
10 re.DOTALL,
11)
12match = MOSAIC_RE.search(response.text)
13if match:
14 data = json.loads(match.group(1))
15 results = data["metaData"]["mosaicProviderJobCardsModel"]["results"]
16 print(f"Found {len(results)} jobs in hidden JSON")
17else:
18 print("找不到隱藏 JSON——可能是被擋,或頁面結構有變動")
步驟 3:從 JSON 解析職缺欄位
results 裡的每一筆資料,通常比頁面上直接看到的內容還完整:
1jobs = []
2for job in results:
3 jobs.append({
4 "jobkey": job["jobkey"],
5 "title": job["title"],
6 "company": job.get("company"),
7 "location": job.get("formattedLocation"),
8 "remote": job.get("remoteLocation"),
9 "salary": (job.get("salarySnippet") or {}).get("text"),
10 "posted": job.get("formattedRelativeTime"),
11 "job_type": job.get("jobTypes"),
12 "easy_apply": job.get("indeedApplyEnabled"),
13 "url": f"https://www.indeed.com/viewjob?jk={job['jobkey']}",
14 })
這份 JSON 常常還包含薪資估算、分類屬性(技能標籤)以及公司評分,而這些資訊不一定會在渲染後的 HTML 上完整顯示。
步驟 4:抓取多頁資料
可透過 JSON 裡的 tierSummaries 理解總結果數,再迴圈抓取:
1import time, random
2> This paragraph contains content that cannot be parsed and has been skipped.
3print(f"總共抓到:{len(all_jobs)} 筆職缺")
為什麼隱藏 JSON 比較穩
window.mosaic.providerData 的結構變動頻率,遠低於 CSS class 名稱。你可以直接拿到乾淨的結構化資料,不必去處理雜亂的 HTML。當然,你還是需要做反機器人處理(標頭、延遲、代理),下一節會說明。
如何用 Selenium 抓取 Indeed
Selenium 是瀏覽器自動化方案。當你需要跟頁面互動時特別有用,例如點進職缺詳情面板、處理需要登入才看得到的內容,或抓取初始 HTML 裡沒有、但後來動態載入的描述。
什麼情況該用 Selenium,而不是 HTTP 客戶端?
- Indeed 會動態載入部分內容(例如右側面板中的完整職缺描述)
- 你需要抓取需要 session 狀態或登入的頁面
- 你只是做小規模抓取,速度不是最重要的
快速範例
1from selenium import webdriver
2from selenium.webdriver.common.by import By
3from selenium.webdriver.chrome.options import Options
4import time
5options = Options()
6options.add_argument("--disable-blink-features=AutomationControlled")
7# options.add_argument("--headless=new") # 無頭模式更容易被偵測,請謹慎使用
8driver = webdriver.Chrome(options=options)
9driver.get("https://www.indeed.com/jobs?q=data+engineer&l=New+York")
10time.sleep(3)
11cards = driver.find_elements(By.CSS_SELECTOR, "[data-testid='slider_item']")
12for card in cards:
13 try:
14 title = card.find_element(By.CSS_SELECTOR, "h2.jobTitle").text
15 company = card.find_element(By.CSS_SELECTOR, "[data-testid='company-name']").text
16 location = card.find_element(By.CSS_SELECTOR, "[data-testid='text-location']").text
17 print(f"{title} | {company} | {location}")
18 except Exception:
19 continue
20driver.quit()
限制
Selenium 速度慢——每一頁都要完整渲染瀏覽器。無頭 Chrome 也很容易被 Indeed 的反機器人系統(Cloudflare 會檢查 navigator.webdriver、WebGL vendor 字串、插件數量等)。就算是 undetected-chromedriver,也只是延後被發現,並不能永久避免。而且和 BS4 一樣,Indeed 一更新介面,你的 selector 也可能跟著壞掉。
對大多數需求來說,隱藏 JSON 的方法可以更快拿到相同資料,而且維護成本更低。Selenium 建議只留給那些真的非得用瀏覽器不可的特殊情境。
如何避免在抓取 Indeed 時遇到 403 錯誤
這一段最重要。如果你是因為卡在這裡才搜尋到本文,那你來對地方了。

為什麼 Indeed 會封鎖你的爬蟲?
Indeed 使用的是 ,不是 DataDome,也不是 PerimeterX。回應標頭會直接露餡:server: cloudflare、cf-ray,以及 __cf_bm 這個 bot management cookie。Cloudflare 會檢查你的 TLS 指紋(JA3/JA4)、HTTP/2 標頭順序、請求模式,以及瀏覽器行為訊號。只要其中任何一項看起來不像真人,就可能收到 403、429、503,甚至更陰險的情況——回傳 200 OK,但內容其實是 Turnstile 驗證頁,而不是職缺資料。
輪換 User-Agent 與請求標頭
固定不變的 User-Agent,是最快被封鎖的方式。請從一組真實、最新的字串中輪換。注意:自從 User-Agent Reduction 之後,Chrome 的小版本欄位已經;不要自己編造非零小版本,不然反機器人系統很容易抓到。
1import random
2USER_AGENTS = [
3 "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
4 "(KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36",
5 "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 "
6 "(KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36",
7 "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
8 "(KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36 Edg/145.0.3800.97",
9 "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0",
10 "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 "
11 "(KHTML, like Gecko) Version/17.4 Safari/605.1.15",
12]
13> This paragraph contains content that cannot be parsed and has been skipped.
14另外也要確保你的 `sec-ch-ua` Client Hints 和 UA 版本一致。`sec-ch-ua: "Chrome";v="131"` 搭配聲稱自己是 Chrome 145 的 User-Agent,會是一個非常明顯的警訊。
15### 在請求之間加入隨機延遲
16固定間隔很容易被模式偵測抓到。請使用隨機抖動:
17```python
18import time, random
19# 每次請求之間
20time.sleep(random.uniform(3, 6))
21# 重試時的退避
22def backoff_sleep(attempt):
23 base = 4
24 sleep_time = base * (2 ** attempt) + random.uniform(0, 2)
25 time.sleep(min(sleep_time, 60))
根據 與 的實務共識,每個 IP 的請求間隔建議控制在 3–6 秒,而且在單一 session 中每個 IP 大約送出 100 次請求後就應考慮輪換。
使用代理輪換
這通常是成功與否的最大關鍵。來自 AWS/GCP 的資料中心代理,在 Cloudflare Enterprise 目標上的成功率大約只有 5–15%,幾乎無法用在 Indeed。住宅代理搭配正確的 TLS 指紋,成功率可提升到 80–95%。
1PROXIES = [
2 "http://user:pass@us.residential.example:7777",
3 "http://user:pass@us.residential.example:7778",
4 "http://user:pass@us.residential.example:7779",
5]
6> This paragraph contains content that cannot be parsed and has been skipped.
7到了 2026 年,住宅代理的價格大約落在每 GB [4–8.50 美元](https://aimultiple.com/proxy-pricing),視供應商與方案而定。對 Indeed 來說,建議先從小型代理池開始,再視需求擴充。
8### 妥善處理 403、429 與 503 狀態碼
9不要盲目重試。不同狀態碼代表不同問題:
10```python
11def fetch_with_retry(url, proxy_pool, max_retries=5):
12 for attempt in range(max_retries):
13 proxy = random.choice(proxy_pool)
14 headers["User-Agent"] = random.choice(USER_AGENTS)
15 try:
16 r = cffi_requests.get(
17 url,
18 impersonate=random.choice(["chrome124", "chrome120", "edge101"]),
19 headers=headers,
20 proxies={"https": proxy},
21 timeout=30,
22 )
23 # 注意那種「200 但其實是驗證頁」的狡猾情況
24 if r.status_code == 200 and "cf-turnstile" not in r.text and "Just a moment" not in r.text:
25 return r
26 if r.status_code == 403:
27 print(f"403 — 已被封鎖。切換代理,嘗試第 {attempt + 1} 次")
28 elif r.status_code == 429:
29 print(f"429 — 被限流。放慢速度。")
30 elif r.status_code == 503:
31 print(f"503 — 伺服器過載,或遇到 JS 驗證。")
32 backoff_sleep(attempt)
33 except Exception as e:
34 print(f"請求錯誤:{e}")
35 backoff_sleep(attempt)
36 raise RuntimeError(f"重試 {max_retries} 次後仍失敗:{url}")
最麻煩的就是 200 但內容是驗證頁這種情況。只要看到 cf-turnstile 或 Just a moment,就不要把它當成成功回應。
更省事的做法:讓 Thunderbit 幫你處理反機器人機制
如果你不想自己維護代理池、標頭輪換和 TLS 指紋模擬, 的雲端抓取會自動處理 CAPTCHA、代理輪換和反機器人保護。你不需要設定代理、不需要設定 curl_cffi,也不需要 CAPTCHA 解題套件。當你只是想快點拿到資料時,這是阻力最小的路線。
為什麼你的 Indeed 爬蟲老是壞掉,以及怎麼修正
403 封鎖是急性疼痛;真正的慢性問題是維護——今天能跑的爬蟲,下週就可能默默回傳空資料或過期結果。
Indeed 是怎麼把你的 selector 弄壞的
Indeed 會大量輪換 CSS class 名稱。Bright Data 的指南像 css-1m4cuuf、css-1rqpxry 這種 class,「看起來像是隨機生成的——很可能是在建置時產生」。A/B test 也會讓不同 session 看到不同版面。再加上 DOM 結構變動通常不會事先通知。
JobFunnel 的經驗很值得參考。有一位貢獻者提到:「CaptchaBuster 已成功緩解 captcha,而頁面仍無法成功抓取的原因,是因為 beautiful soup 的 selector 已經過時。」 也就是說,問題不在於被封鎖,而是你在解析錯誤元素。
策略:優先使用隱藏 JSON,而不是 DOM 解析
window.mosaic.providerData 這個區塊至少從 2023 年以來就相對穩定。metaData.mosaicProviderJobCardsModel.results[] 這條路徑到了 2026 年依然是。DOM selector 每個月都可能壞,JSON 擷取就算會壞,也通常是按年計算。
策略:用 data 屬性,不要依賴 class 名稱
當你真的需要碰 DOM 時,請優先選功能型屬性:
| Selector | 用途 |
|---|---|
[data-testid="slider_item"] | 每個職缺卡片容器 |
[data-testid="job-title"] 或 h2.jobTitle > a | 職稱連結 |
[data-testid="company-name"] | 雇主名稱 |
[data-testid="text-location"] | 地點文字 |
每張卡片上的 data-jk="<jobkey>" | 最穩定的定位點,從 2019 年以來幾乎沒變 |
加上斷言檢查,及早發現 selector 過時
不要讓爬蟲悄悄跑完卻抓到 0 筆資料。每次抓完都加一個檢查:
1results = parse_hidden_json(html)
2assert len(results) > 0, (
3 f"Indeed 在 start={start} 時回傳空結果集——"
4 "可能是被封鎖、遇到 CAPTCHA,或 selector 已經失效。"
5 f"回應前 500 個字元:{html[:500]}"
6)
失敗時,請記錄原始回應的前 500–2000 個字元。這樣你可以立刻判斷是 Turnstile 驗證頁、登入牆,還是 schema 改版。最好再針對固定查詢(例如 q=python&l=remote)建立每天執行的 CI smoke test,確認結果不是 0。
AI 的替代方案:不會壞掉的爬蟲
Thunderbit 的 AI 每次都會重新理解頁面結構,不依賴死板的 selector 或 regex。當 Indeed 的 HTML 改版時,Thunderbit 會自動適應。這正好解決了論壇使用者最常抱怨的維護痛點。你如果曾經一早醒來看到 Slack 訊息說「爬蟲又回傳空白列了」,就會懂得不用手動修它有多珍貴。
不寫 Python 也能抓 Indeed:免寫程式替代方案
幾乎所有競爭文章都預設你一定會寫 Python。但論壇上的聲音其實不一樣。很多使用者會說,「一直有 bug 和錯誤,真的太難用了」,甚至有人建議直接找 Fiverr 上的外包來幫忙拿資料。如果你也有同感,這一段就是你的出口。
用 Thunderbit 抓 Indeed 的方法(逐步教學)
步驟 1: 從 Chrome 線上應用程式商店安裝 。可免費開始使用。
步驟 2: 在瀏覽器中打開 Indeed 的搜尋結果頁,例如:https://www.indeed.com/jobs?q=data+analyst&l=Austin%2C+TX。
步驟 3: 點擊瀏覽器工具列上的 Thunderbit 圖示,再按 「AI Suggest Fields」。Thunderbit 的 AI 會掃描頁面,自動辨識像 Job Title、Company、Location、Salary、Job URL、Posted Date 這些欄位。你可以檢視並調整系統建議的欄位——刪掉不需要的欄位,或用自然語言新增自訂欄位。
步驟 4: 點擊 「Scrape」。Thunderbit 會把頁面資料擷取成結構化表格,你應該會看到符合設定欄位的職缺列表。
用子頁面抓取做延伸補充
完成列表頁抓取後,點 「Scrape Subpages」,Thunderbit 就會逐一造訪每個職缺詳情頁。它可以再抓完整職缺描述、資格條件、福利和申請連結,而且不需要額外設定。這等於你要再寫一支 Python 爬蟲去逐一拜訪 /viewjob?jk=<jobkey>,但現在只要按一下。
自動處理分頁
Thunderbit 會自動處理 Indeed 的點擊式分頁。不需要自己拼 offset URL,也不用寫翻頁迴圈。它會自動一路翻頁並彙整結果。
匯出到你熟悉的工具
你可以把抓下來的資料免費匯出到 CSV、Excel、Google Sheets、Airtable 或 Notion。。不用自己寫 csv.writer() 或 pandas.to_csv()。
什麼情況該用 Python,什麼情況該用 Thunderbit
| 情境 | 最佳工具 |
|---|---|
| 客製化資料管線、透過 cron/Airflow 排程自動化 | Python |
| 要整合進大型程式碼庫 | Python |
| 需要高度客製化的解析邏輯 | Python |
| 一次性的研究或市場分析 | Thunderbit |
| 非技術團隊也需要使用資料 | Thunderbit |
| 想立刻拿到資料,不想調 403 | Thunderbit |
| 零設定的子頁面補充抓取 | Thunderbit |
時間比較:Python 設定加上反機器人除錯,通常是數小時到數天,尤其是第一次做的時候。Thunderbit:同樣的資料,兩分鐘內就能拿到。我不是說 Python 不好,而是要看你的需求。
抓取 Indeed 合法嗎?你需要知道的事
大多數排名前面的 Indeed 抓取教學,都完全沒碰合法性問題,這點其實滿讓人意外的,因為論壇上經常有人問「抓 Indeed 合法嗎?」。以下不是法律意見,但可以幫你快速理解整體情況。
Indeed 的服務條款
Indeed 的 ToS()並沒有一條全面性的「禁止抓取」條款。唯一明確的自動化限制,是第 A.3.5 條,禁止*「使用任何自動化、腳本或機器人來自動化 Indeed Apply 流程」*。這個範圍很窄,只針對 Apply 申請流程,不包含對公開職缺列表的被動讀取。Indeed 主要靠技術手段執行限制——Cloudflare 驗證、IP 封鎖、裝置指紋辨識——而不是走法院途徑。
相關法律先例
最常被引用的美國案例是 hiQ Labs v. LinkedIn。第九巡迴上訴法院在 裁定,抓取公開可存取的資料「很可能不構成 CFAA(Computer Fraud and Abuse Act)違反」。不過,hiQ 後來又因為,原因是其員工建立了假的 LinkedIn 個人檔案,並接受了服務條款。
最近的 Meta v. Bright Data(加州北區,2024 年 1 月)則給出了更明確的判決。Chen 法官Facebook 與 Instagram 的條款「並不禁止登出狀態下對公開資料的抓取」。Meta 之後在隔月自願撤回剩餘主張。
Indeed 的 robots.txt
Indeed 的 對預設的 User-agent: * 大範圍禁止 /jobs/ 和 /job/,但又明確允許 Googlebot 與 Bingbot 存取 /viewjob?——也就是單一職缺詳情頁。AI 訓練爬蟲(GPTBot、CCBot、anthropic-ai)則受到嚴格限制。robots.txt 在美國不具法律約束力,但尊重它仍是最佳實務,也能作為善意的證明。
負責任抓取的實務原則
- 只抓公開資料——不要登入,也不要建立假帳號
- 遵守頻率限制:每個 IP 每 3–6 秒 1 個請求,並保持低併發
- 不要把抓來的資料重新包裝成自己的求職網站公開
- 資料只用於個人或內部研究,不要未經許可進行商業轉售
- 刪除或雜湊你不需要的個資;對接近個人的資料設定保留上限
- 如果你的規模很大,或你在歐盟/英國營運,請諮詢律師——GDPR 第 14 條的透明告知義務可能適用於抓取到的個人資料
風險層級來說:個人求職自動化屬於低風險;大規模商業轉售 Indeed 資料則屬於高風險。
結論與重點整理
用 Python 抓 Indeed 是可行的,但它不是那種週末寫完就能放著跑的專案。Indeed 的 Cloudflare 防護、輪換 selector,以及強勢的反機器人機制,代表你必須用對工具,也要有正確預期。
我會從這篇文章帶走的重點是:
- Indeed 是網路上最豐富的職場資料來源之一——3.5 億月訪客、1.3 億職缺,但它也會強力反擊爬蟲。
- 隱藏 JSON 擷取(
window.mosaic.providerData)是最穩定的 Python 方法。 這個 schema 多年來都很穩,而 CSS selector 幾乎每個月都可能壞。 curl_cffi搭配瀏覽器模擬,是 2026 年受 Cloudflare 保護網站的 HTTP 客戶端首選。 單靠requests或httpx,TLS 指紋就可能先被擋下來。- 一定要使用輪換標頭、隨機延遲與住宅代理,才能降低 403 錯誤。資料中心代理幾乎無法對抗 Cloudflare Enterprise。
- 加上斷言檢查,才能第一時間知道 selector 壞掉,或是你其實收到的是驗證頁,不是真正的職缺資料。
- 如果你不是技術背景,或只是想快速拿結果, 提供一條免寫程式、AI 驅動的路線,能自動適應網站變動——不用代理、不用除錯、也不用長期維護。
如果你想直接走免寫程式路線,,可以先在 Indeed 上試用,不用承諾任何費用。如果你打算走 Python 路線,上面的程式範例會是很好的起點——但請記得把反機器人韌性當成核心設計,而不是事後補救。
如果你想了解更多網頁爬蟲的方式與工具,可以參考我們的指南:、、以及。你也可以到 觀看教學影片。
常見問題
用來抓 Indeed 的 Python 套件,哪些最好?
如果是 HTTP 請求,到了 2026 年,curl_cffi 是最強選擇——它能模擬真實瀏覽器的 TLS 指紋,這對繞過 Cloudflare 很重要。httpx 加上 HTTP/2 則可作為較弱保護目標的備案。HTML 解析方面,BeautifulSoup4 搭配 lxml 仍是標準配置。若是瀏覽器自動化,Playwright(搭配 playwright-stealth)或 undetected-chromedriver 都能用,但也越來越容易被偵測。若採用隱藏 JSON regex 方式(window.mosaic.providerData),則幾乎不需要繁重的解析工作。
為什麼我抓 Indeed 時一直遇到 403?
Indeed 使用 Cloudflare Bot Management,它會檢查你的 TLS 指紋(JA3/JA4)、HTTP/2 標頭順序、請求模式,以及瀏覽器行為。如果你用的是普通 requests,你的 TLS 指紋會立刻暴露你是 Python 腳本——在你的標頭甚至還沒被讀到之前,403 就先來了。解法是改用 curl_cffi 模擬瀏覽器、輪換真實的 User-Agent、加入隨機延遲(3–6 秒),以及使用住宅代理。也別忘了檢查「200 但其實是 Turnstile 驗證頁」的情況——看到 cf-turnstile 就不能算成功。
可以不寫程式抓 Indeed 嗎?
可以。像 這類工具,讓你只要幾個點擊就能擷取 Indeed 職缺:安裝 Chrome 擴充功能、打開 Indeed 搜尋頁、點「AI Suggest Fields」,再按「Scrape」即可。Thunderbit 的 AI 會自動偵測職稱、公司、地點、薪資等欄位。它也會自動處理分頁、子頁面補充抓取(完整職缺描述)與反機器人保護。你可以免費匯出到 CSV、Google Sheets、Airtable 或 Notion。
Indeed 多久會改一次 HTML 結構?
Indeed 會定期輪換 CSS class 名稱(例如 css-1m4cuuf 這種隨機雜湊字串),也會不通知就重組 DOM 元素。A/B 測試還會讓不同使用者同時看到不同版面。相比之下,隱藏 JSON 的方法(window.mosaic.providerData)穩定得多——這個 schema 至少自 2023 年以來就相當一致。當你非得用 DOM selector 時,請優先用 data-testid 與 data-jk(job key),不要依賴 CSS class。
抓 Indeed 合法嗎?
根據第九巡迴上訴法院在 hiQ v. LinkedIn(2022)以及 Meta v. Bright Data(2024)的判決,對公開可存取的 Indeed 職缺網址進行登出狀態下的抓取,在美國不太可能構成 CFAA 責任。Indeed 的 ToS 是明確禁止自動化 Apply 流程,而不是禁止被動閱讀公開列表。即便如此,仍應負責任地抓取:不要登入、不要建立假帳號、遵守頻率限制、不要把資料重新包裝成自己的求職網站,並依照 GDPR/CCPA 謹慎處理任何個人資料(如招募人姓名、電子郵件)。若是商業級規模,請先諮詢律師。
延伸閱讀
