พอถึงการคัดลอกชื่องานจาก Indeed ลงสเปรดชีตเป็นครั้งที่ห้าสิบ ผมก็เริ่มตั้งคำถามกับเส้นทางอาชีพตัวเองแล้ว ถ้าคุณเคยพยายามดึงข้อมูลแบบมีโครงสร้างจาก Indeed ด้วยโปรแกรมมาก่อน คุณคงรู้คำตอบอยู่แล้วว่า ข้อผิดพลาด 403 ไม่ใช่บั๊ก แต่มันคือฟีเจอร์ของระบบป้องกันของ Indeed
Indeed คือเว็บหางานที่ใหญ่ที่สุดในโลก มีผู้เข้าชมไม่ซ้ำรายเดือนราว 350 ล้านคน, มีรายการงานราว 130 ล้านตำแหน่ง ในช่วงเวลาใดเวลาหนึ่ง และดำเนินงานในกว่า 60 ประเทศ นั่นทำให้มันเป็นหนึ่งในแหล่งข้อมูลตลาดแรงงานที่อุดมที่สุดในโลก — และเป็นหนึ่งในเป้าหมายที่ขูดข้อมูลยากที่สุดด้วย สคริปต์โอเพ่นซอร์ส JobFunnel (มีดาวบน GitHub หลายพันดวง) ถึงขั้นถูก ผู้ดูแลโครงการเก็บเข้าคลัง ในเดือนธันวาคม 2025 หลังสู้ศึกกับระบบป้องกันบอตมาเป็นเวลาหลายปี คำพูดของผู้ดูแลเองคือ "ผู้ใช้ทุกคนสามารถขูดงานได้บ้าง แต่จะชน CAPTCHA อย่างรวดเร็ว และการขูดก็ล้มเหลวโดยไม่ดึงงานออกมาเลย" อีกผู้ร่วมพัฒนารายงานว่าโดน CAPTCHA ตั้งแต่คำขอแรก เลยทีเดียว ดังนั้นใช่ — นี่ไม่ใช่เป้าหมายที่ขูดง่าย ในคู่มือนี้ ผมจะพาคุณไล่ดูทุกวิธีที่ใช้งานได้จริงสำหรับการดึงข้อมูล Indeed ด้วย Python แสดงให้เห็นว่ารอดจากด่าน 403 ได้อย่างไร และสำหรับคนที่อยากข้ามการดีบักไปเลย ก็จะสาธิตทางเลือกแบบไม่ต้องเขียนโค้ดด้วย Thunderbit
การดึงข้อมูล Indeed ด้วย Python คืออะไร?
หัวใจของการทำเว็บสแครปปิงคือการดึงข้อมูลที่มีโครงสร้างออกมาจากหน้าเว็บโดยอัตโนมัติ เมื่อเราพูดถึงการดึงข้อมูล Indeed ด้วย Python เราหมายถึงการเขียนสคริปต์ที่เข้าไปยังหน้าผลการค้นหาและหน้ารายละเอียดงานของ Indeed อ่าน HTML ด้านใน (หรือข้อมูลที่ฝังอยู่) แล้วดึงฟิลด์อย่างชื่อตำแหน่งงาน บริษัท ที่ตั้ง เงินเดือน และคำอธิบาย ออกมาเป็นรูปแบบที่ใช้งานได้ เช่น CSV ฐานข้อมูล หรือ Google Sheet
ไลบรารี Python ที่มักเกี่ยวข้องมี Requests (สำหรับเรียก HTTP), BeautifulSoup (สำหรับแยกวิเคราะห์ HTML) และ Selenium หรือ Playwright (สำหรับทำงานอัตโนมัติในเบราว์เซอร์) แต่ Indeed ไม่ใช่เว็บสถิตธรรมดา มันเป็นเว็บแบบผสม: HTML ที่เรนเดอร์ฝั่งเซิร์ฟเวอร์พร้อมก้อนสถานะ JSON ที่ฝังอยู่ภายใน และมี Cloudflare Bot Management อยู่ชั้นหน้า นั่นแปลว่าสคริปต์ของคุณต้องรับมือทั้งคอนเทนต์ที่เรนเดอร์ด้วย JavaScript ชื่อคลาส CSS ที่หมุนเปลี่ยน และระบบป้องกันบอตที่ดุดัน — ก่อนจะได้อ่านชื่อตำแหน่งงานสักชื่อด้วยซ้ำ
และในปี 2026 ก็ยังไม่มี Indeed API แบบอ่านอย่างเดียวฟรีที่เป็นทางการอีกด้วย API Jobs ของ Publisher แบบเดิมถูกยกเลิกไปราวปี 2020 ที่เหลืออยู่คือเครื่องมือฝั่งนายจ้างเท่านั้น (Job Sync, Sponsored Jobs) ดังนั้นการขูดข้อมูลหรือจ่ายให้ผู้ให้บริการข้อมูลภายนอก จึงเป็นสองทางเลือกที่ใช้งานได้จริงเท่านั้น
ทำไมต้องดึงข้อมูลงานจาก Indeed?
เหตุผลทางธุรกิจของการดึงข้อมูล Indeed นั้นตรงไปตรงมา: การไล่ดูรายการนับพันด้วยมือไม่คุ้ม และข้อมูลในประกาศงานเหล่านั้นมีคุณค่าจริง

| กรณีใช้งาน | ใครได้ประโยชน์ | ตัวอย่าง |
|---|---|---|
| การหาลีด | ทีมขายและทีมสรรหา | สร้างรายชื่อบริษัทที่กำลังรับสมัครพร้อมข้อมูลติดต่อ |
| วิจัยตลาดแรงงาน | นักวิเคราะห์ ทีม HR | ระบุทักษะที่กำลังมาแรง และเกณฑ์เงินเดือนตามภูมิภาค |
| วิเคราะห์คู่แข่ง | นายจ้าง เอเจนซี่จัดหางาน | ติดตามรูปแบบการจ้างงานและข้อเสนอเงินเดือนของคู่แข่ง |
| ทำงานอัตโนมัติสำหรับการหางานส่วนตัว | ผู้สมัครงาน | รวมรายการงานที่ตรงกับเงื่อนไขจากหลายพื้นที่ |
| ข้อมูลฝึกสอนสำหรับโมเดล ML | นักวิทยาศาสตร์ข้อมูล | สร้างโมเดลทำนายเงินเดือนจากข้อมูลย้อนหลัง |
งานวิจัยของ Indeed Hiring Lab เองก็ ยืนยัน ว่าข้อมูลประกาศงานติดตาม BLS JOLTS ได้ใกล้เคียง และใช้เป็นตัวแทนสภาพตลาดแรงงานสหรัฐแบบเกือบเรียลไทม์ได้ กองทุนเฮดจ์ฟันด์ใช้ความเร็วในการลงประกาศงานเป็นสัญญาณข้อมูลทางเลือก ทีม HR ใช้ข้อมูลเงินเดือนที่ดึงมาเทียบเกณฑ์ค่าตอบแทน และรีครูตเตอร์ก็สร้างรายชื่อผู้มีโอกาสเป็นลูกค้าจากบริษัทที่กำลังรับคนจริงๆ
มีข้อสังเกตที่สำคัญ: ข้อมูลเงินเดือนบน Indeed ดีขึ้นแต่ก็ยังไม่ครบถ้วน ในช่วงกลางปี 2025 ประมาณ 59% ของประกาศงานในสหรัฐ มีข้อมูลเงินเดือน แต่มีเพียงราว 22% ที่ระบุตัวเลขชัดเจน ที่เหลือเป็นช่วงเงินเดือน การวิเคราะห์เงินเดือนที่สร้างจากข้อมูล Indeed จึงควรคำนึงถึงความไม่สมบูรณ์นี้ด้วย
วิธีเลือกแนวทางในการดึงข้อมูล Indeed ด้วย Python
ไม่มีวิธีเดียวที่ “ถูกต้อง” สำหรับการขูด Indeed แนวทางที่ดีที่สุดขึ้นอยู่กับระดับทักษะของคุณ ต้องการข้อมูลมากแค่ไหน และยอมรับภาระการดูแลรักษาได้เท่าไร ผมทดลองทั้ง 4 วิธีหลักมาแล้ว และนี่คือการเปรียบเทียบ:
| เกณฑ์ | BS4 + Requests | Selenium | JSON ที่ซ่อนอยู่ (window.mosaic) | แบบไม่ต้องเขียนโค้ด (Thunderbit) |
|---|---|---|---|---|
| ความยาก | มือใหม่ | ระดับกลาง | ระดับกลางถึงสูง | ไม่มี (2 คลิก) |
| ความเร็ว | เร็ว | ช้า (ต้องเรนเดอร์เบราว์เซอร์) | เร็ว | เร็ว (ขูดบนคลาวด์) |
| คอนเทนต์ที่เรนเดอร์ด้วย JS | ไม่ได้ | ได้ | ได้ (ข้อมูลฝังอยู่) | ได้ |
| ความทนต่อบอต | ต่ำ | ปานกลาง (ตรวจจับได้) | ปานกลางถึงสูง | สูง (จัดการให้อัตโนมัติ) |
| ภาระดูแลเมื่อ HTML เปลี่ยน | สูง (ตัวเลือกพัง) | สูง | ปานกลาง (โครงสร้าง JSON เสถียรกว่า) | ไม่มี (AI ปรับตัว) |
| เหมาะกับ | ทดลองเร็วๆ | หน้าไดนามิก, ต้องล็อกอิน | ข้อมูลจำนวนมากแบบมีโครงสร้าง | คนไม่เขียนโค้ด, ต้องการผลเร็ว |
คู่มือนี้จะไล่ทีละวิธี ถ้าคุณเป็นนักพัฒนา Python คุณควรอ่านส่วน BS4, Hidden JSON และ Selenium ถ้าคุณไม่เขียนโค้ดเลย (หรือแค่เบื่อกับการดีบัก 403) ให้ข้ามไปยังส่วน Thunderbit ได้เลย
ก่อนเริ่ม
- ระดับความยาก: มือใหม่ถึงระดับกลาง (ส่วน Python); ไม่มี (ส่วน Thunderbit)
- เวลาที่ต้องใช้: ประมาณ 20–60 นาทีสำหรับการตั้งค่า Python และการสแครปครั้งแรก; ประมาณ 2 นาทีด้วย Thunderbit
- สิ่งที่ต้องมี: Python 3.9+, โปรแกรมแก้ไขโค้ด, เบราว์เซอร์ Chrome และ (สำหรับเส้นทางไม่ต้องเขียนโค้ด) Thunderbit Chrome Extension
ตั้งค่าสภาพแวดล้อม Python สำหรับการขูด Indeed
ก่อนจะเขียนโค้ดขูดข้อมูล ให้เตรียมสภาพแวดล้อมให้พร้อมก่อน
ติดตั้งไลบรารีที่ต้องใช้
สร้าง virtual environment แล้วติดตั้งแพ็กเกจที่ต้องใช้:
python -m venv indeed_env
source indeed_env/bin/activate # บน Windows: indeed_env\Scripts\activate
# สำหรับแนวทาง HTTP + แยกวิเคราะห์
pip install requests beautifulsoup4 lxml httpx
# สำหรับแนวทาง Hidden JSON (แนะนำ)
pip install curl_cffi parsel tenacity
# สำหรับแนวทางทำงานอัตโนมัติด้วยเบราว์เซอร์
pip install selenium
มีข้อสังเกตเล็กน้อย:
curl_cffiคือค่าเริ่มต้นในปี 2026 สำหรับการสแครปเว็บที่ป้องกันด้วย Cloudflare มันเลียนแบบ TLS fingerprint ของเบราว์เซอร์จริง ซึ่งrequestsและhttpxแบบธรรมดาทำไม่ได้ เดี๋ยวจะอธิบายว่าทำไมเรื่องนี้สำคัญในส่วนป้องกันบอต- Selenium 4.6+ มาพร้อม Selenium Manager ดังนั้นคุณไม่ต้องดาวน์โหลด ChromeDriver เองอีกแล้ว มันจัดการไบนารีของเบราว์เซอร์ให้โดยอัตโนมัติ
- ใช้
lxmlเป็น backend สำหรับ BeautifulSoup จะเร็วกว่า stdlibhtml.parserประมาณ 1.5 เท่า
สร้างโครงสร้างโปรเจ็กต์ของคุณ
ให้เรียบง่าย:
indeed_scraper/
├── scraper.py
├── requirements.txt
└── output/
ตัวอย่างโค้ดทั้งหมดด้านล่างจะต่อยอดจาก scraper.py
วิธีใช้ BeautifulSoup ดึงข้อมูล Indeed ด้วย Python
นี่คือแนวทางสำหรับมือใหม่: ใช้ requests ดึงหน้าเว็บและใช้ BeautifulSoup แยกวิเคราะห์ HTML ตั้งค่าได้เร็วที่สุด แต่ก็เปราะบางที่สุดบน Indeed เช่นกัน
ขั้นตอนที่ 1: สร้าง URL สำหรับค้นหา Indeed
URL ค้นหาของ Indeed มีรูปแบบที่คาดเดาได้:
https://www.indeed.com/jobs?q=<query>&l=<location>&start=<offset>
ตัวอย่างเช่น ค้นหา “data analyst” ใน “Austin, TX” เริ่มจากหน้แรก:
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 แบ่งหน้าเป็นช่วงละ 10 รายการ และจำกัดผลลัพธ์สูงสุดที่ 1,000 รายการ (start <= 990) ถ้าใช้ offset เกิน 990 ก็จะได้หน้าเดิมกลับมาแบบเงียบๆ
ขั้นตอนที่ 2: ส่งคำขอ HTTP พร้อมส่วนหัวที่เหมาะสม
Indeed จะบล็อกคำขอที่ใช้ user-agent เริ่มต้นของ Python ทันที คุณต้องใช้ headers ที่สมจริง:
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)
ถ้าได้ 200 แปลว่าผ่านไปได้ — อย่างน้อยตอนนี้ ถ้าได้ 403 แปลว่า Cloudflare จับได้แล้ว (เดี๋ยวจะพูดถึงวิธีรอดจากด่านนี้)
ขั้นตอนที่ 3: แยกรายการงานจาก HTML
ใช้ BeautifulSoup เพื่อเลือกองค์ประกอบของการ์ดงาน ตั้งเป้าไปที่แอตทริบิวต์ data-testid เพราะเสถียรกว่าชื่อคลาส 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"Found {len(jobs)} jobs")
ขั้นตอนที่ 4: จัดการการแบ่งหน้า
วนลูปผ่านหน้าต่างๆ โดยเพิ่มค่า start:
import time, random
all_jobs = []
for page in range(0, 50, 10): # 5 หน้าแรก
params["start"] = page
url = f"https://www.indeed.com/jobs?{urlencode(params)}"
response = requests.get(url, headers=headers, timeout=30)
# ... แยกวิเคราะห์ตามด้านบน ...
all_jobs.extend(jobs)
time.sleep(random.uniform(3, 6))
ข้อจำกัดของแนวทางนี้
ขอพูดตรงๆ เลย: BS4 + Requests เป็นวิธีที่อ่อนที่สุดสำหรับ Indeed ในปี 2026 requests แบบธรรมดาใช้ TLS library ของ stdlib ของ Python ซึ่งสร้าง JA3 fingerprint ที่ Cloudflare มองออกทันทีว่า “ไม่ใช่เบราว์เซอร์” มันยังไม่รองรับ HTTP/2 ซึ่งเป็นสิ่งที่ Indeed เสิร์ฟอยู่ด้วย คุณจะถูกบล็อกหลังจากไม่กี่หน้า และตัวเลือก CSS ล่ะ? Indeed หมุนชื่อคลาสอย่าง css-1m4cuuf และ jobsearch-JobComponent-embeddedBody-1n0gh5s บ่อยครั้ง ดังนั้นตัวเลือกที่อ้างอิงคลาสเหล่านี้จึงเป็นระเบิดเวลาที่รอวันพัง
ใช้วิธีนี้สำหรับทดลองเร็วๆ กับหน้าหนึ่งหน้าเท่านั้น ถ้าจะทำระดับใช้งานจริง ให้ใช้แนวทาง Hidden JSON
วิธีใช้ Hidden JSON ดึงข้อมูล Indeed ด้วย Python
นี่คือวิธีที่ผมแนะนำสำหรับนักพัฒนา Python ส่วนใหญ่ แทนที่จะไปแยกวิเคราะห์ HTML ที่เปราะบาง คุณจะดึงข้อมูลแบบมีโครงสร้างจากตัวแปร JavaScript ที่ฝังอยู่ในซอร์สของหน้า Indeed: window.mosaic.providerData["mosaic-provider-jobcards"]
ทุกฟิลด์ที่คุณต้องการ — ชื่อตำแหน่ง บริษัท ที่ตั้ง เงินเดือน รหัสงาน วันที่ลงประกาศ ธงงานระยะไกล — มีอยู่ในก้อน JSON นี้แล้ว ไม่ต้องรัน JavaScript โครงสร้างสคีมา เสถียรอย่างน้อยตั้งแต่ปี 2023 จึงทนทานกว่าตัวเลือก DOM มาก
ขั้นตอนที่ 1: ดึง HTML ของหน้าเว็บ
ใช้ curl_cffi แทน requests — มันเลียนแบบ TLS fingerprint ของเบราว์เซอร์จริง ซึ่งสำคัญมากสำหรับการผ่าน 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))
ทำไมต้อง curl_cffi? เพราะมันคือ Python binding บน curl-impersonate ที่จำลองทั้ง ClientHello ของ TLS, เฟรม HTTP/2 SETTINGS และลำดับการส่ง header ให้เหมือนเบราว์เซอร์จริง เป็นไลบรารี HTTP ฝั่ง Python ที่ยังดูแลอยู่ตัวเดียวที่หลบได้ทั้ง JA3/JA4 และ Akamai H2 fingerprint ในคำสั่งเดียว เป้าหมายการปลอมตัวที่รองรับมีทั้ง chrome120, chrome124, chrome131, Safari และ Edge รุ่นต่างๆ
ขั้นตอนที่ 2: ดึง JSON ด้วย Regular Expression
ก้อน JSON ถูกฝังอยู่ในแท็ก <script> ดึงออกมาด้วย regex:
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"Found {len(results)} jobs in hidden JSON")
else:
print("ไม่พบ Hidden JSON — อาจถูกบล็อกหรือหน้าเว็บเปลี่ยนไป")
ขั้นตอนที่ 3: แยกฟิลด์งานจาก JSON
แต่ละรายการใน results มีข้อมูลมากกว่าที่แสดงบนหน้าเว็บ:
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 มักจะมีทั้งประมาณการเงินเดือน แอตทริบิวต์หมวดหมู่ (แท็กทักษะ) และคะแนนบริษัท ซึ่งไม่จำเป็นต้องแสดงใน HTML ที่เรนเดอร์เสมอไป
ขั้นตอนที่ 4: ขูดหลายหน้า
ใช้ tierSummaries ใน JSON เพื่อดูจำนวนผลลัพธ์ทั้งหมด แล้ววนลูป:
import time, random
all_jobs = []
for start in range(0, 50, 10): # 5 หน้าแรก
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"ทั้งหมด: ดึงข้อมูลงานได้ {len(all_jobs)} รายการ")
ทำไม Hidden JSON ถึงทนทานกว่า
โครงสร้าง window.mosaic.providerData เปลี่ยนบ่อยน้อยกว่าชื่อคลาส CSS คุณจะได้ข้อมูลที่สะอาดและมีโครงสร้างโดยไม่ต้องแยก HTML ที่ยุ่งยาก ถึงอย่างนั้น คุณก็ยังต้องมีมาตรการรับมือบอตอยู่ดี (headers, delays, proxies) — ซึ่งเราจะพูดถึงต่อไป
วิธีใช้ Selenium ดึงข้อมูล Indeed ด้วย Python
Selenium คือแนวทางทำงานอัตโนมัติในเบราว์เซอร์ เหมาะเวลาที่คุณต้องโต้ตอบกับหน้าเว็บ — เช่น คลิกเข้าแผงรายละเอียดงาน รับมือคอนเทนต์ที่ต้องล็อกอิน หรือขูดคำอธิบายที่โหลดแบบไดนามิกและไม่อยู่ใน HTML แรกเริ่ม
เมื่อไรควรใช้ Selenium แทน HTTP client
- Indeed โหลดบางส่วนแบบไดนามิก (คำอธิบายงานเต็มในแผงด้านขวา)
- คุณต้องขูดหน้าที่ต้องอาศัย session หรือการล็อกอิน
- คุณทำงานขูดขนาดเล็กที่ความเร็วไม่ใช่เรื่องสำคัญ
เดินผ่านแบบเร็วๆ
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 ตรวจจับได้ง่ายกว่า — ใช้ด้วยความระวัง
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()
ข้อจำกัด
Selenium ช้า — แต่ละหน้าต้องเรนเดอร์เบราว์เซอร์เต็มรูปแบบ Chrome แบบ headless ตรวจจับได้โดยระบบป้องกันบอตของ Indeed (Cloudflare จะเช็ก navigator.webdriver, สตริง vendor ของ WebGL, จำนวนปลั๊กอิน และอีกมากมาย) แม้แต่ undetected-chromedriver ก็แค่ชะลอการตรวจจับ ไม่ได้ป้องกันได้ถาวร และเหมือน BS4 ตัวเลือกของคุณก็จะพังเมื่อ Indeed อัปเดต UI
สำหรับการใช้งานส่วนใหญ่ แนวทาง Hidden JSON ให้ข้อมูลชุดเดียวกันได้เร็วกว่าและดูแลง่ายกว่า เก็บ Selenium ไว้ใช้กับกรณีขอบเขตที่คุณจำเป็นต้องใช้เบราว์เซอร์จริงๆ
วิธีหลีกเลี่ยงข้อผิดพลาด 403 เมื่อดึงข้อมูล Indeed ด้วย Python
นี่คือส่วนที่สำคัญที่สุด ถ้าคุณมาถึงตรงนี้หลังจากค้นหา Google อย่างหงุดหงิด แปลว่ามาถูกที่แล้ว

ทำไม Indeed ถึงบล็อกสคริปต์ของคุณ
Indeed ใช้ Cloudflare Bot Management ร่วมกับ Cloudflare Turnstile — ไม่ใช่ DataDome และไม่ใช่ PerimeterX ส่วนหัวตอบกลับบอกชัดเจน: server: cloudflare, cf-ray และคุกกี้ __cf_bm สำหรับจัดการบอต Cloudflare จะตรวจ TLS fingerprint (JA3/JA4), ลำดับ header บน HTTP/2, รูปแบบคำขอ และสัญญาณพฤติกรรมของเบราว์เซอร์ ถ้าอะไรดูไม่เหมือนมนุษย์ คุณจะได้ 403, 429, 503 หรือในกรณีที่แยบยลที่สุด — ได้ 200 OK แต่เป็นหน้า Turnstile challenge แทนข้อมูลงานจริง
หมุน User-Agent และ Request Headers
User-Agent เดิมๆ ตัวเดียวคือวิธีที่เร็วที่สุดที่จะโดนบล็อก ควรสลับจากชุดสตริงที่สมจริงและเป็นปัจจุบัน หมายเหตุสำคัญ: ฟิลด์ minor version ของ Chrome ถูก ตรึงไว้ที่ 0.0.0 ตั้งแต่มี User-Agent Reduction — อย่าแต่ง minor version ให้เป็นเลขอื่น ไม่งั้นระบบป้องกันบอตจะจับได้
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",
}
อย่าลืมด้วยว่า sec-ch-ua Client Hints ต้องสอดคล้องกับเวอร์ชันของ UA ถ้า sec-ch-ua: "Chrome";v="131" แต่ User-Agent บอกว่า Chrome 145 นั่นคือธงแดงทันที
ใส่ดีเลย์แบบสุ่มระหว่างคำขอ
ช่วงเวลาตายตัวจะถูกตรวจจับด้วยรูปแบบพฤติกรรม ใช้ความสุ่มเล็กน้อยแทน:
import time, random
# ระหว่างแต่ละคำขอ
time.sleep(random.uniform(3, 6))
# ตอน retry หลังโดนบล็อก
def backoff_sleep(attempt):
base = 4
sleep_time = base * (2 ** attempt) + random.uniform(0, 2)
time.sleep(min(sleep_time, 60))
ข้อสรุปจาก ScrapeOps และ WebScraping.AI คือควรเว้น 3–6 วินาทีต่อคำขอ ต่อ IP และควรจำกัดไว้ราว 100 คำขอต่อ IP ต่อเซสชันก่อนเปลี่ยน IP
ใช้การหมุน Proxy
นี่คือปัจจัยสำคัญที่สุดต่อความสำเร็จ Proxy แบบดาต้าเซ็นเตอร์จากช่วง IP ของ AWS/GCP จะสำเร็จราว 5–15% บนเป้าหมาย Cloudflare Enterprise — แทบใช้ไม่ได้บน Indeed แต่ถ้าใช้ residential proxies ร่วมกับ TLS fingerprint ที่ถูกต้อง อัตราความสำเร็จจะขึ้นไปถึง 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,
)
ราคาของ residential proxy ในปี 2026 อยู่ราว 4–8.50 ดอลลาร์ต่อ GB ขึ้นอยู่กับผู้ให้บริการและระดับสัญญา สำหรับ Indeed โดยเฉพาะ ควรเริ่มจากพูลเล็กๆ แล้วขยายตามความจำเป็น
จัดการรหัสสถานะ 403, 429 และ 503 อย่างสุภาพ
อย่ารีเทรย์แบบสุ่มสี่สุ่มห้า รหัสสถานะแต่ละแบบหมายถึงอะไรคนละอย่าง:
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,
)
# ตรวจกรณีแยบยล "200 แต่มี challenge"
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 — ถูกบล็อก เปลี่ยน proxy รอบที่ {attempt + 1}")
elif r.status_code == 429:
print(f"429 — ถูกจำกัดอัตรา ชะลอความเร็วลง")
elif r.status_code == 503:
print(f"503 — เซิร์ฟเวอร์มีภาระสูงหรือมี JS challenge")
backoff_sleep(attempt)
except Exception as e:
print(f"เกิดข้อผิดพลาดในการร้องขอ: {e}")
backoff_sleep(attempt)
raise RuntimeError(f"ล้มเหลวหลังลอง {max_retries} ครั้ง: {url}")
กรณี 200-with-challenge ยากที่สุด อย่าลืมสแกน body ของ response หา cf-turnstile หรือ Just a moment ก่อนจะถือว่า 200 คือสำเร็จจริง
ทางเลือกที่ง่ายกว่า: ให้ Thunderbit จัดการระบบป้องกันบอตให้คุณ
สำหรับผู้ใช้ที่ไม่อยากสร้างและดูแลพูล proxy หมุน header และปลอม TLS fingerprint เอง Thunderbit ใช้การขูดบนคลาวด์ที่จัดการ CAPTCHA, การหมุน proxy และระบบป้องกันบอตให้อัตโนมัติ ไม่ต้องตั้งค่า proxy, ไม่ต้องตั้งค่า curl_cffi, ไม่ต้องใช้ไลบรารีแก้ CAPTCHA มันคือเส้นทางที่ต้านทานน้อยที่สุดเมื่อคุณแค่อยากได้ข้อมูลเท่านั้น
ทำไมสคริปต์ Indeed ของคุณพังอยู่เรื่อยๆ (และจะแก้อย่างไร)
กำแพง 403 คือความเจ็บปวดเฉียบพลัน ส่วนความเจ็บปวดเรื้อรังคือการดูแลรักษา — สคริปต์ที่ใช้ได้วันนี้อาจพังในสัปดาห์หน้า แล้วเงียบๆ ส่งข้อมูลว่างหรือผลลัพธ์เก่า
Indeed ทำลายตัวเลือกของคุณอย่างไร
Indeed หมุนชื่อคลาส CSS อย่างดุดัน คู่มือของ Bright Data เตือนอย่างชัดเจน ว่าคลาสอย่าง css-1m4cuuf และ css-1rqpxry "ดูเหมือนถูกสร้างแบบสุ่ม — น่าจะสร้างตอน build time" การทดสอบ A/B หมายความว่าเซสชันต่างๆ อาจเห็นเลย์เอาต์คนละแบบ และโครงสร้าง DOM ก็ถูกปรับโดยไม่มีการแจ้งล่วงหน้า
กรณีของ JobFunnel สอนอะไรได้ดี ผู้ร่วมพัฒนารายหนึ่งรายงานว่า: "CaptchaBuster จัดการ CAPTCHA ได้สำเร็จแล้ว และเหตุผลที่ยังขูดหน้าเว็บไม่สำเร็จ [คือ] ตัวเลือก BeautifulSoup ล้าสมัย" นั่นแปลว่าสคริปต์ไม่ได้ถูกบล็อก — แต่มันกำลังอ่านองค์ประกอบผิดตัว
กลยุทธ์: เลือก Hidden JSON แทนการ parse DOM
ก้อน window.mosaic.providerData มีสคีมาที่เสถียรอย่างน้อยตั้งแต่ปี 2023 เส้นทาง metaData.mosaicProviderJobCardsModel.results[] ยังคงเป็นมาตรฐานหลัก ในปี 2026 ตัวเลือก DOM พังรายเดือน แต่การดึง JSON พังรายปี ถ้าพังเลย
กลยุทธ์: ใช้ data attribute แทนชื่อคลาส
เวลาคุณจำเป็นต้องแตะ DOM ให้เลือกแอตทริบิวต์ที่มีหน้าที่ชัดเจน:
| ตัวเลือก | วัตถุประสงค์ |
|---|---|
[data-testid="slider_item"] | คอนเทนเนอร์ของการ์ดงานแต่ละใบ |
[data-testid="job-title"] หรือ h2.jobTitle > a | ลิงก์ชื่อตำแหน่งงาน |
[data-testid="company-name"] | ชื่อบริษัท |
[data-testid="text-location"] | ข้อความตำแหน่งที่ตั้ง |
data-jk="<jobkey>" บนแต่ละการ์ด | จุดอ้างอิงที่เสถียรที่สุด — ไม่เปลี่ยนมาตั้งแต่ปี 2019 |
เพิ่มการตรวจสอบด้วย assertion เพื่อจับตัวเลือกที่ล้าสมัย
อย่าปล่อยให้สคริปต์รันเงียบๆ ทั้งที่ได้ผลลัพธ์เป็นศูนย์ ให้เช็กทุกครั้งหลังดึงหน้าเว็บ:
results = parse_hidden_json(html)
assert len(results) > 0, (
f"Indeed ส่งผลลัพธ์ว่างที่ start={start} — "
"อาจถูกบล็อก, เจอ CAPTCHA หรือ selector เปลี่ยนไปแล้ว "
f"อักขระ 500 ตัวแรกของ response: {html[:500]}"
)
ให้บันทึกอักขระ 500–2000 ตัวแรกของ response ดิบเมื่อเกิดความล้มเหลว จะได้รู้ทันทีว่าคุณเจอ Turnstile challenge, กำแพงให้ล็อกอิน หรือการเปลี่ยนสคีมา ตั้ง smoke test ระดับ CI รายวันด้วยคำค้นคงที่ เช่น q=python&l=remote แล้วตรวจว่าได้ผลลัพธ์ไม่เป็นศูนย์
ทางเลือกด้วย AI: สคริปต์ที่ไม่พังง่ายๆ
AI ของ Thunderbit อ่านโครงสร้างหน้าเว็บใหม่ทุกครั้ง — ไม่พึ่ง selector ที่เขียนตายตัวหรือแพตเทิร์น regex พอ Indeed เปลี่ยน HTML Thunderbit จะปรับตัวให้อัตโนมัติ นี่คือการแก้ปัญหาภาระการดูแลที่ผู้ใช้ในฟอรัมมักบ่นเป็นอันดับต้นๆ ถ้าคุณเคยตื่นมาแล้วเจอข้อความ Slack ว่า “สคริปต์คืนแถวว่างอีกแล้ว” คุณจะรู้ว่าการไม่ต้องแก้มันเองมีค่าขนาดไหน
ดึงข้อมูล Indeed โดยไม่ต้องเขียน Python: ทางเลือกแบบไม่ต้องเขียนโค้ด
คู่มือของคู่แข่งแทบทุกเจ้าเดาว่าคุณจะเขียน Python แต่ข้อมูลจากฟอรัมบอกอีกอย่าง ผู้ใช้พูดกันว่า "มันยากมากจริงๆ เพราะบั๊กกับข้อผิดพลาดเกิดตลอด" และบางคนถึงขั้นแนะนำให้จ้างคนใน Fiverr แค่เพื่อดึงข้อมูล ถ้าคุณรู้สึกแบบนั้น ส่วนนี้คือทางออกของคุณ
วิธีดึง Indeed ด้วย Thunderbit (ทีละขั้น)
ขั้นตอนที่ 1: ติดตั้ง Thunderbit Chrome Extension จาก Chrome Web Store เริ่มใช้งานได้ฟรี
ขั้นตอนที่ 2: ไปยังหน้าผลการค้นหาของ Indeed ในเบราว์เซอร์ของคุณ — ตัวอย่างเช่น https://www.indeed.com/jobs?q=data+analyst&l=Austin%2C+TX
ขั้นตอนที่ 3: คลิกไอคอน Thunderbit บนแถบเครื่องมือของเบราว์เซอร์ แล้วคลิก "AI Suggest Fields" AI ของ Thunderbit จะสแกนหน้าเว็บและตรวจจับคอลัมน์อย่าง Job Title, Company, Location, Salary, Job URL และ Posted Date ให้อัตโนมัติ คุณสามารถตรวจทานและปรับฟิลด์ที่แนะนำได้ — ลบคอลัมน์ที่ไม่ต้องการ หรือเพิ่มคอลัมน์แบบกำหนดเองด้วยการอธิบายเป็นภาษาอังกฤษง่ายๆ ว่าต้องการอะไร
ขั้นตอนที่ 4: คลิก "Scrape" Thunderbit จะดึงข้อมูลจากหน้าเว็บและแสดงผลเป็นตารางที่มีโครงสร้าง คุณจะเห็นแถวของรายการงานพร้อมฟิลด์ที่ตั้งค่าไว้
เพิ่มข้อมูลด้วยการสแครปหน้าย่อย
หลังจากขูดหน้ารายการแล้ว ให้คลิก "Scrape Subpages" เพื่อให้ Thunderbit เข้าไปยังหน้ารายละเอียดงานแต่ละหน้า มันจะดึงคำอธิบายงานแบบเต็ม คุณสมบัติ สวัสดิการ และลิงก์สมัครงาน — โดยไม่ต้องตั้งค่าเพิ่ม นี่เทียบเท่ากับการเขียนสคริปต์ Python ตัวที่สองเพื่อเข้าไปยัง URL /viewjob?jk=<jobkey> แต่ใช้แค่คลิกเดียว
จัดการการแบ่งหน้าอัตโนมัติ
Thunderbit จัดการการแบ่งหน้าแบบคลิกของ Indeed ให้อัตโนมัติ ไม่ต้องประกอบ URL offset เอง หรือเขียนลูป pagination เอง มันคลิกข้ามหน้าและรวมผลลัพธ์ให้
ส่งออกไปยังเครื่องมือที่คุณชอบ
ส่งออกข้อมูลที่ขูดได้ไปยัง CSV, Excel, Google Sheets, Airtable หรือ Notion — ฟรีทั้งหมด ไม่ต้องเขียนโค้ด csv.writer() หรือ pandas.to_csv() เอง
เมื่อไรควรใช้ Python กับ Thunderbit
| สถานการณ์ | เครื่องมือที่เหมาะกว่า |
|---|---|
| ไปป์ไลน์ข้อมูลแบบกำหนดเอง, ทำงานอัตโนมัติตามเวลา via cron/Airflow | Python |
| ต้องนำไปผสานกับโค้ดเบสขนาดใหญ่ | Python |
| ต้องการตรรกะแยกวิเคราะห์ที่ปรับแต่งสูงมาก | Python |
| งานวิจัยครั้งคราวหรือวิเคราะห์ตลาด | Thunderbit |
| สมาชิกทีมที่ไม่ถนัดเทคนิคต้องใช้ข้อมูล | Thunderbit |
| อยากได้ข้อมูลตอนนี้โดยไม่ต้องดีบัก 403 | Thunderbit |
| เพิ่มข้อมูลจากหน้าย่อยโดยไม่ต้องตั้งค่า | Thunderbit |
เทียบเวลา: ตั้งค่า Python + ดีบักระบบป้องกันบอต = หลายชั่วโมงถึงหลายวัน (โดยเฉพาะครั้งแรก) Thunderbit = ไม่ถึง 2 นาทีสำหรับข้อมูลชุดเดียวกัน ผมไม่ได้บอกว่า Python ผิด — ผมบอกว่ามันขึ้นอยู่กับสิ่งที่คุณต้องการ
การดึง Indeed ถูกกฎหมายไหม? สิ่งที่คุณควรรู้
คู่มือขูด Indeed ติดอันดับต้นๆ แทบไม่มีใครพูดเรื่องความถูกต้องทางกฎหมาย ซึ่งน่าแปลกเพราะคำถาม "ขูด Indeed ถูกกฎหมายไหม?" โผล่ในฟอรัมบ่อยมาก นี่ไม่ใช่คำแนะนำทางกฎหมาย แต่คือภาพรวมของสถานการณ์
ข้อกำหนดการใช้งานของ Indeed
ToS ของ Indeed (indeed.com/legal) ไม่มีข้อห้ามแบบครอบคลุมว่า “ห้ามขูดข้อมูล” ข้อห้ามเกี่ยวกับระบบอัตโนมัติที่ระบุชัดเจนมีเพียงหัวข้อ A.3.5 ซึ่งห้าม "การใช้ระบบอัตโนมัติ สคริปต์ หรือบอตใดๆ เพื่อทำให้กระบวนการ Indeed Apply เป็นอัตโนมัติ" ซึ่งจำกัดอยู่แค่กระบวนการ Apply เท่านั้น ไม่ได้ครอบคลุมการอ่านประกาศงานสาธารณะเฉยๆ เครื่องมือบังคับใช้หลักของ Indeed คือเชิงเทคนิค — challenge ของ Cloudflare, การแบน IP, และการสแกน device fingerprint — ไม่ใช่ในศาล
แบบอย่างทางกฎหมายที่เกี่ยวข้อง
คดีในสหรัฐที่ถูกอ้างถึงบ่อยที่สุดคือ hiQ Labs v. LinkedIn ศาลอุทธรณ์เขต 9 ตัดสินในเดือนเมษายน 2022 ว่าการขูดข้อมูลที่เข้าถึงได้สาธารณะ “น่าจะไม่ละเมิด CFAA” (Computer Fraud and Abuse Act) อย่างไรก็ตาม ภายหลัง hiQ ถูกพบว่า ละเมิดสัญญา เพราะพนักงานของบริษัทสร้างโปรไฟล์ LinkedIn ปลอมและยอมรับ ToS นั้น
เมื่อไม่นานมานี้ คดี Meta v. Bright Data (ศาล N.D. Cal., ม.ค. 2024) ให้คำตัดสินที่ชัดเจนยิ่งกว่า ผู้พิพากษา Chen ตัดสิน ว่าเงื่อนไขของ Facebook และ Instagram “ไม่ห้ามการขูดข้อมูลสาธารณะเมื่อไม่ได้ล็อกอิน” จากนั้น Meta ถอนฟ้องคดีที่เหลือในเดือนถัดมา
robots.txt ของ Indeed
robots.txt ของ Indeed โดยรวมไม่อนุญาต /jobs/ และ /job/ สำหรับ User-agent: * แต่อนุญาตให้ Googlebot และ Bingbot เข้าถึง /viewjob? ซึ่งเป็นหน้ารายละเอียดงานรายตัว AI training crawler อย่าง GPTBot, CCBot และ anthropic-ai ถูกจำกัดอย่างเข้มงวด robots.txt ไม่ได้มีผลผูกพันทางกฎหมายในสหรัฐ แต่การเคารพมันถือเป็นแนวปฏิบัติที่ดีและเป็นหลักฐานของเจตนาดี
แนวทางปฏิบัติสำหรับการขูดข้อมูลอย่างรับผิดชอบ
- ขูดเฉพาะข้อมูลที่เปิดสาธารณะ — ห้ามล็อกอิน ห้ามสร้างบัญชีปลอม
- เคารพ rate limit: 1 คำขอต่อ 3–6 วินาทีต่อ IP และความพร้อมกันในระดับหลักหน่วย
- อย่านำข้อมูลที่ขูดมาเผยแพร่ซ้ำราวกับเป็นบอร์ดหางานของคุณเอง
- ใช้ข้อมูลเพื่อการวิจัยส่วนตัวหรือภายในองค์กร ไม่ใช่ขายต่อเชิงพาณิชย์โดยไม่ได้รับอนุญาต
- ลบหรือแฮช PII ที่ไม่จำเป็น และกำหนดเพดานการเก็บข้อมูลที่เกี่ยวข้องกับข้อมูลส่วนบุคคล
- ถ้าคุณทำในระดับใหญ่หรือใน EU/UK ควรปรึกษาทนาย — ภาระหน้าที่ด้านความโปร่งใสของ GDPR มาตรา 14 ใช้กับข้อมูลส่วนบุคคลที่ขูดมา
ระดับความเสี่ยง: การทำงานอัตโนมัติสำหรับหางานส่วนตัวอยู่ปลายต่ำของสเปกตรัม ส่วนการขายต่อข้อมูล Indeed เชิงพาณิชย์ในวงกว้างอยู่ปลายสูง
บทสรุปและประเด็นสำคัญ
การดึงข้อมูล Indeed ด้วย Python ทำได้จริง แต่ไม่ใช่โปรเจ็กต์สุดสัปดาห์ที่ตั้งแล้วปล่อยทิ้งได้เลย การป้องกันด้วย Cloudflare, ตัวเลือกที่หมุนเปลี่ยน และมาตรการต้านบอตที่เข้มข้น หมายความว่าคุณต้องใช้เครื่องมือที่เหมาะสมและมีความคาดหวังที่ถูกต้อง
สิ่งที่ผมอยากให้คุณจำจากทั้งหมดนี้คือ:
- Indeed คือแหล่งข้อมูลตลาดแรงงานที่อุดมที่สุดบนเว็บ — ผู้เข้าชม 350 ล้านคนต่อเดือน, รายการงาน 130 ล้านตำแหน่ง — แต่ก็สู้กลับกับสคริปต์ขูดข้อมูลอย่างหนัก
- การดึง Hidden JSON (
window.mosaic.providerData) คือแนวทาง Python ที่ทนทานที่สุด สคีมาเสถียรมาหลายปี ขณะที่ตัวเลือก CSS พังเป็นรายเดือน curl_cffiที่ปลอมตัวเป็นเบราว์เซอร์คือ HTTP client มาตรฐานในปี 2026 สำหรับเว็บที่ป้องกันด้วย Cloudflarerequestsและhttpxแบบธรรมดาจะถูกบล็อกตั้งแต่ TLS fingerprint- ควรใช้ headers หมุนเวียน, ดีเลย์แบบสุ่ม และ residential proxies เสมอ เพื่อหลีกเลี่ยงข้อผิดพลาด 403 proxy แบบดาต้าเซ็นเตอร์แทบไม่มีประโยชน์ต่อ Cloudflare Enterprise
- เพิ่ม assertion checks เพื่อให้รู้ทันทีว่า selector พังหรือคุณกำลังถูกส่งหน้า challenge แทนข้อมูลงาน
- สำหรับผู้ใช้ที่ไม่ถนัดเทคนิค หรือใครก็ตามที่อยากได้ผลลัพธ์เร็วๆ, Thunderbit มีเส้นทางแบบไม่ต้องเขียนโค้ดที่ขับเคลื่อนด้วย AI และปรับตัวตามการเปลี่ยนแปลงของเว็บโดยอัตโนมัติ — ไม่ต้องมี proxy ไม่ต้องดีบัก ไม่ต้องดูแลรักษา
ถ้าอยากลองเส้นทางไม่ต้องเขียนโค้ด Thunderbit มีแพ็กเกจฟรี ให้คุณทดสอบกับ Indeed ได้โดยไม่ต้องผูกมัดอะไร และถ้าคุณเลือกทาง Python ตัวอย่างโค้ดข้างต้นก็เป็นจุดเริ่มต้นที่ดี — แค่จำไว้ว่าควรมองความทนทานต่อบอตเป็นเรื่องหลัก ไม่ใช่เรื่องที่ค่อยตามแก้ทีหลัง
สำหรับข้อมูลเพิ่มเติมเกี่ยวกับแนวทางและเครื่องมือเว็บสแครปปิง ดูคู่มือของเราเรื่อง วิธีดึงข้อมูลเว็บด้วย Python, เครื่องมือเว็บสแครปปิงอัตโนมัติที่ดีที่สุด และ เว็บสแครปปิงโดยไม่ให้ถูกบล็อก คุณยังสามารถดูวิดีโอสอนบน Thunderbit YouTube Channel ได้ด้วย
ลอง Thunderbit เพื่อดึงข้อมูล Indeed ได้เร็วขึ้น Get Started Free
คำถามที่พบบ่อย
ไลบรารี Python ไหนดีที่สุดสำหรับการขูด Indeed?
สำหรับคำขอ HTTP, curl_cffi เป็นตัวเลือกที่แข็งแกร่งที่สุดในปี 2026 เพราะมันเลียนแบบ TLS fingerprint ของเบราว์เซอร์จริง ซึ่งจำเป็นมากสำหรับการผ่าน Cloudflare httpx ที่ใช้ HTTP/2 เป็นตัวสำรองที่พอใช้ได้กับเป้าหมายที่ป้องกันน้อยกว่า สำหรับการแยกวิเคราะห์ HTML, BeautifulSoup4 ร่วมกับ lxml ยังเป็นมาตรฐาน ส่วนการทำงานอัตโนมัติของเบราว์เซอร์, Playwright (ร่วมกับ playwright-stealth) หรือ undetected-chromedriver ใช้งานได้ แม้ทั้งคู่จะถูกตรวจจับได้ง่ายขึ้นเรื่อยๆ แนวทาง regex จาก Hidden JSON (window.mosaic.providerData) ช่วยเลี่ยงการ parse หนักๆ ไปได้เลย
ทำไมฉันถึงโดนข้อผิดพลาด 403 ตลอดเวลาตอนขูด Indeed?
Indeed ใช้ Cloudflare Bot Management ซึ่งตรวจ TLS fingerprint (JA3/JA4), ลำดับ header ของ HTTP/2, รูปแบบคำขอ และพฤติกรรมของเบราว์เซอร์ ถ้าคุณใช้ requests ธรรมดา TLS fingerprint จะบอกทันทีว่าเป็นสคริปต์ Python — 403 จะมาก่อนที่ headers ของคุณจะถูกอ่านเสียอีก แก้โดยเปลี่ยนไปใช้ curl_cffi ที่ปลอมตัวเป็นเบราว์เซอร์ หมุน User-Agent ที่สมจริง ใส่ดีเลย์แบบสุ่ม (3–6 วินาที) และใช้ residential proxies อย่าลืมตรวจกรณี "200 แต่เป็น Turnstile challenge" ด้วย — สแกน body ของ response หา cf-turnstile
ฉันขูด Indeed ได้โดยไม่ต้องเขียนโค้ดไหม?
ได้ เครื่องมืออย่าง Thunderbit ให้คุณดึงรายการงานจาก Indeed ได้ในไม่กี่คลิก — ติดตั้งส่วนขยาย Chrome ไปยังหน้าค้นหาของ Indeed คลิก "AI Suggest Fields" แล้วกด "Scrape" AI ของ Thunderbit จะตรวจจับฟิลด์อย่างชื่อตำแหน่ง บริษัท ที่ตั้ง และเงินเดือนให้อัตโนมัติ มันจัดการ pagination, การเพิ่มข้อมูลจากหน้าย่อย (รายละเอียดงานเต็ม) และระบบป้องกันบอตให้อัตโนมัติ ส่งออกเป็น CSV, Google Sheets, Airtable หรือ Notion ได้ฟรี
Indeed เปลี่ยนโครงสร้าง HTML บ่อยแค่ไหน?
Indeed หมุนชื่อคลาส CSS เป็นประจำ (เช่น css-1m4cuuf, สตริงแฮชที่สุ่มขึ้น) และปรับโครงสร้างองค์ประกอบ DOM โดยไม่แจ้งล่วงหน้า การทดสอบ A/B หมายความว่าผู้ใช้แต่ละคนอาจเห็นเลย์เอาต์ต่างกันในเวลาเดียวกัน วิธี Hidden JSON (window.mosaic.providerData) มีความเสถียรกว่ามาก — สคีมามีความต่อเนื่องมาตั้งแต่ปี 2023 อย่างน้อย ถ้าจำเป็นต้องใช้ DOM selectors ให้ใช้ data-testid และ data-jk (job key) แทนคลาส CSS
การขูด Indeed ถูกกฎหมายไหม?
การขูดข้อมูล Indeed ที่เปิดให้เข้าถึงสาธารณะโดยไม่ได้ล็อกอิน ไม่น่าจะทำให้เกิดความรับผิดภายใต้ CFAA ในสหรัฐ โดยอ้างอิงจากคำตัดสิน hiQ v. LinkedIn ของศาลอุทธรณ์เขต 9 (2022) และคดี Meta v. Bright Data (2024) ข้อกำหนดของ Indeed ห้ามการทำให้กระบวนการ Apply เป็นอัตโนมัติโดยเฉพาะ ไม่ได้ห้ามการอ่านรายการงานสาธารณะแบบ passive ถึงอย่างนั้น ควรขูดอย่างรับผิดชอบเสมอ: ไม่ล็อกอิน ไม่สร้างบัญชีปลอม เคารพ rate limit ไม่เผยแพร่ข้อมูลซ้ำเป็นบอร์ดหางานของตัวเอง และจัดการข้อมูลส่วนบุคคล (ชื่อรีครูตเตอร์, อีเมล) อย่างระมัดระวังภายใต้ GDPR/CCPA ถ้าทำในเชิงพาณิชย์ระดับใหญ่ ควรปรึกษาทนาย
เรียนรู้เพิ่มเติม


