Zillow คือแหล่งรวมข้อมูลอสังหาฯ ในสหรัฐฯ กว่า 160 ล้านรายการ และการเอาข้อมูลพวกนี้ออกมาใช้ในระดับใหญ่ ๆ นี่แหละเป็นหนึ่งในงานที่คนสายข้อมูลอสังหาฯ ขอมากที่สุด — และก็ปวดหัวที่สุดเหมือนกัน ถ้าคุณเคยลอง scrape Zillow แล้วเจอแต่หน้า CAPTCHA แทนประกาศขายจริง ๆ ก็ไม่ต้องแปลกใจ เพราะคุณไม่ได้เป็นคนเดียวที่เจอปัญหานี้
ผมใช้เวลาพอสมควรในการลองผิดลองถูกกับวิธีดึงข้อมูลจาก Zillow หลายแบบ — ทั้งแบบใช้ Python และแบบ no-code ที่เราทำขึ้นที่ Thunderbit คู่มือนี้เลยรวบรวมไว้ทั้งสองทาง ไม่ว่าคุณจะอยากอ่านแบบลงลึกด้วย Python พร้อมเทคนิคหลบระบบกันบอท หรือแค่อยากได้ข้อมูล 200 รายการลงสเปรดชีตให้ทันเที่ยง วันนี้มีคำตอบให้ครบ เราจะคุยกันว่าทำไมข้อมูล Zillow ถึงสำคัญ โครงสร้างเว็บเป็นยังไง บทเรียน Python แบบทีละขั้นตอน สาเหตุที่ตัวดึงข้อมูลพัง และวิธีตั้งค่าให้ scrape ซ้ำอัตโนมัติสำหรับติดตามราคา
ทำไมต้องดึงข้อมูลจาก Zillow ตั้งแต่แรก?
Zillow คือคลังข้อมูลอสังหาฯ ที่อยู่อาศัยของสหรัฐฯ ที่ใหญ่ที่สุด แพลตฟอร์มนี้มียอดเข้าชมราว 210 ล้านครั้งต่อเดือน และมีประกาศขายที่ยัง active กว่า 750,000 รายการ พร้อมประกาศเช่าอีกประมาณ 1.9 ล้านรายการ แถมยังครองสัดส่วนทราฟฟิกจากพอร์ทัลอสังหาฯ ในสหรัฐฯ มากกว่า 50% — มากกว่าคู่แข่งอันดับถัดไปแบบทิ้งห่าง

ก่อนจะลงมือเขียน Python ควรรู้ไว้ก่อนว่า การ scrape Zillow ด้วย Python ไม่ใช่ทางเลือกเดียว และถ้าเลือกวิธีผิด คุณอาจเสียเวลาไปหลายชั่วโมง เครื่องมือ Python อย่าง httpx และ BeautifulSoup ต้องมีทักษะระดับกลาง ต้องจัดการ headers กับ proxies เอง ความเร็วอยู่ระดับปานกลาง (1–3 วินาทีต่อหน้า) และต้องคอยดูแลตลอด แม้จะใช้ฟรีก็ตาม; ส่วน Selenium หรือ Playwright ช่วยเรื่องหลบระบบกันบอทได้ดีขึ้นเพราะเรนเดอร์ JavaScript ได้ แต่จะช้ากว่า (5–15 วินาทีต่อหน้า) และยังต้องบำรุงรักษาอยู่เรื่อย ๆ; บริการ scraping API อย่าง ScraperAPI หรือ ScrapFly เร็วกว่าและมีระบบกันบอทในตัว พร้อมภาระดูแลระดับกลาง แต่มีค่าใช้จ่ายราว $30–599 ต่อเดือน; API ทางการของ Zillow ผ่าน Bridge Interactive เร็วและดูแลง่าย แต่ใช้งานได้จำกัด และมีค่าใช้จ่ายประมาณ $500 ต่อเดือน; ส่วนเครื่องมือ no-code อย่าง Thunderbit ใช้งานง่ายสำหรับมือใหม่ เร็ว ไม่ต้องดูแลเพราะ AI ปรับตัวตามหน้าเว็บได้ และมักมีแพ็กเกจฟรีเมียมให้เริ่มใช้งาน
ถ้ามองเรื่องประหยัดเวลาจะเห็นชัดมาก การค้นคว้าแบบ manual ครอบคลุมรหัสไปรษณีย์ 50+ พื้นที่อาจกินเวลา 15–20 ชั่วโมงต่อสัปดาห์ แต่ถ้าใช้การดึงข้อมูลอัตโนมัติ งานเดียวกันนี้ทำได้ในไม่กี่นาที — ลดเวลาได้สูงสุดถึง 99.7%
วิธีดึงข้อมูล Zillow ทุกแบบ: Python vs. API vs. No-Code (เปรียบเทียบ)
ก่อนเริ่มเขียน Python ต้องเข้าใจก่อนว่า "scrape Zillow ด้วย Python" ไม่ใช่ทางเลือกเดียว ถ้าเลือกวิธีไม่เหมาะ คุณจะเสียเวลาฟรี ๆ นี่คือภาพเปรียบเทียบตรง ๆ เพื่อช่วยให้เลือกแนวทางที่เหมาะกับคุณ:
| วิธีการ | ระดับทักษะ | การรับมือบอท | ความเร็ว | การดูแลรักษา | ค่าใช้จ่าย |
|---|---|---|---|---|---|
| Python + httpx/BeautifulSoup | ปานกลาง | ต้องจัดการเอง (headers, proxies) | ปานกลาง (1–3 วินาที/หน้า) | สูง (selector พังบ่อย) | ฟรี |
| Python + Selenium/Playwright | ปานกลาง | ดีกว่า (เรนเดอร์ JS) | ช้า (5–15 วินาที/หน้า) | สูง | ฟรี |
| Scraping API (ScraperAPI, ScrapFly) | ปานกลาง | มีในตัว | เร็ว | ปานกลาง | $30–599/เดือน |
| Zillow Official API (Bridge Interactive) | มือใหม่–ปานกลาง | ไม่มีให้ต้องจัดการ | เร็ว | ต่ำ | ประมาณ $500/เดือน, จำกัดสิทธิ์ใช้งาน |
| เครื่องมือ No-Code (Thunderbit) | มือใหม่ | มีในตัว (AI ปรับตามหน้าเว็บ) | เร็ว | แทบไม่มี (AI อ่านหน้าใหม่ทุกครั้ง) | ฟรีเมียม |
ถ้าคุณอยากได้ข้อมูลเดี๋ยวนี้โดยไม่อยากเขียนโค้ด เริ่มที่ Thunderbit ได้เลย แต่ถ้าอยากเข้าใจระบบเบื้องหลังหรืออยากปรับแต่งเองเต็มที่ อ่านต่อเพื่อดูวิธีด้วย Python
วิธีเร็วใน 2 นาที: ดึงข้อมูล Zillow ด้วย Thunderbit (ไม่ต้องเขียนโค้ด)
ก่อนจะลงลึก Python นี่คือทางลัดสำหรับคนที่อยากได้ข้อมูล Zillow แบบไว ๆ — ไม่ต้องตั้งค่า Python ไม่ต้องคอนฟิก proxy ไม่ต้องมานั่งแก้ selector เราสร้างเวิร์กโฟลว์นี้ที่ Thunderbit มาเพื่อดึงข้อมูลอสังหาฯ แบบเป็นโครงสร้าง โดยไม่ต้องพึ่งทีมวิศวกรรม
ระดับความยาก: มือใหม่ เวลาที่ต้องใช้: ประมาณ 2 นาที สิ่งที่ต้องมี: Chrome browser, Thunderbit Chrome Extension (ใช้แพ็กเกจฟรีได้)
ขั้นตอนที่ 1: ติดตั้ง Thunderbit และเปิด Zillow
ติดตั้งส่วนขยาย Thunderbit จาก Chrome Web Store จากนั้นเข้าไปยังหน้าผลการค้นหาของ Zillow — เช่น ค้นหาบ้านใน Houston, TX
ขั้นตอนที่ 2: คลิก "AI Suggest Fields"
เปิดแถบด้านข้างของ Thunderbit แล้วคลิก "AI Suggest Fields" จากนั้น AI จะอ่านหน้าเว็บและแนะนำคอลัมน์ให้อัตโนมัติ เช่น ราคา ที่อยู่ จำนวนห้องนอน ห้องน้ำ พื้นที่ใช้สอย Zestimate URL ของประกาศ และอื่น ๆ จากที่ผมทดสอบ ส่วนใหญ่จะตรวจจับได้มากกว่า 20 ฟิลด์โดยแทบไม่ต้องตั้งค่าเอง
ขั้นตอนที่ 3: คลิก "Scrape"
กดปุ่ม Scrape ข้อมูลจะถูกจัดลงตารางในส่วนขยาย Thunderbit ระบบจะจัดการ pagination ของ Zillow ให้อัตโนมัติ — ทั้งแบบกดเปลี่ยนหน้าและแบบเลื่อนต่อเนื่อง (infinite scroll)
ขั้นตอนที่ 4: เพิ่มข้อมูลจากหน้ารายละเอียดด้วย Subpage Scraping
ถ้าคุณต้องการข้อมูลเชิงลึกอย่างประวัติภาษี คะแนนโรงเรียน หรือประวัติราคา ให้ใช้ "Scrape Subpages" เพื่อเติมข้อมูลลงในตาราง Thunderbit จะตามไปยัง URL ของแต่ละประกาศและดึงฟิลด์เพิ่มเติมมาให้ — ไม่ต้องเขียนโค้ดเพิ่ม
ขั้นตอนที่ 5: Export
ส่งออกไป Google Sheets, Excel, Airtable หรือ Notion ได้ฟรี
ทำไม Thunderbit ถึงเหมาะกับ Zillow
จุดแข็งจริง ๆ คือความทนทาน Thunderbit ใช้ AI อ่านโครงสร้างหน้าเว็บใหม่ทุกครั้งที่คุณ scrape ดังนั้นเวลา Zillow เปลี่ยน layout ซึ่งเกิดขึ้นบ่อย ก็จะไม่มี CSS selector เปราะบางให้ต้องตามแก้ AI จะปรับตัวเองให้อัตโนมัติ ซึ่งช่วยแก้ปัญหา "พังง่ายโดยธรรมชาติ" ของสคริปต์ดึงข้อมูลแบบเขียนโค้ดได้จริง และลดความหงุดหงิดของผู้ใช้ไปได้เยอะ
คุณดึงข้อมูลอะไรจาก Zillow ได้บ้าง? (20+ ฟิลด์)
คู่มือส่วนใหญ่ดึงแค่ราคาและที่อยู่แล้วจบ แต่จริง ๆ แล้วประกาศ Zillow มีข้อมูลให้ดึงมากกว่าที่หลายคนคิด — นี่คือตารางอ้างอิง:
| ฟิลด์ | อยู่ตรงไหน | ความยากในการดึง |
|---|---|---|
| ราคาขาย | หน้าค้นหา + หน้ารายละเอียด | ง่าย |
| ที่อยู่ / รหัสไปรษณีย์ | หน้าค้นหา + หน้ารายละเอียด | ง่าย |
| Zestimate | หน้าค้นหา + หน้ารายละเอียด | ง่าย |
| ประวัติราคา (แต่ละเหตุการณ์) | หน้ารายละเอียด | ยาก (nested JSON) |
| ประวัติภาษี | หน้ารายละเอียด | ยาก (nested JSON) |
| จำนวนห้องนอน / ห้องน้ำ / พื้นที่ | หน้าค้นหา + หน้ารายละเอียด | ง่าย |
| ปีที่สร้าง | หน้ารายละเอียด | ง่าย |
| ค่าธรรมเนียม HOA | หน้ารายละเอียด | ปานกลาง |
| Walk Score / Transit Score | หน้ารายละเอียด (iframe) | ยาก (ต้องเรนเดอร์ JS) |
| คะแนนโรงเรียน | หน้ารายละเอียด | ปานกลาง |
| ขนาดที่ดิน | หน้ารายละเอียด | ง่าย |
| จำนวนวันที่อยู่บน Zillow | หน้าค้นหา | ง่าย |
| เอเจนต์ / บริษัทนายหน้า | หน้าค้นหา + หน้ารายละเอียด | ปานกลาง |
| MLS # | หน้ารายละเอียด | ง่าย |
| ประเภทอสังหาฯ | หน้าค้นหา + หน้ารายละเอียด | ง่าย |
| ละติจูด / ลองจิจูด | JSON __NEXT_DATA__ | ปานกลาง |
| คำอธิบายประกาศ | หน้ารายละเอียด | ง่าย |
| URL รูปภาพ | หน้าค้นหา + หน้ารายละเอียด | ปานกลาง |
| Rent Zestimate | หน้ารายละเอียด | ปานกลาง |
| ยอดขายเปรียบเทียบใกล้เคียง | หน้ารายละเอียด | ยาก |
ฟิลด์ที่ "ยาก" อย่างประวัติราคา ประวัติภาษี และยอดขายเปรียบเทียบ จะอยู่ใน JSON ซ้อนหลายชั้นบนหน้ารายละเอียด ส่วนโค้ด Python ด้านล่างจะสาธิตวิธีดึงมันออกมาให้ชัด ๆ และถ้าคุณไม่อยากจับโค้ด Thunderbit ก็มี AI Suggest Fields ที่ช่วยตรวจจับคอลัมน์ส่วนใหญ่ได้อัตโนมัติ พร้อม Subpage Scraping ที่ดึงฟิลด์จากหน้ารายละเอียดให้เลย
เตรียมสภาพแวดล้อม Python สำหรับดึงข้อมูล Zillow
ระดับความยาก: ปานกลาง เวลาที่ต้องใช้: ประมาณ 5 นาทีสำหรับติดตั้ง และราว 30 นาทีสำหรับทำตามบทเรียนทั้งหมด สิ่งที่ต้องมี: Python 3.8+, Chrome browser (สำหรับตรวจหน้าเว็บ), และ text editor หรือ IDE
ติดตั้งไลบรารีที่จำเป็น:
pip install httpx beautifulsoup4 pandas lxml
หน้าที่ของแต่ละตัวคือ:
- httpx — HTTP client ที่เร็วกว่า
requestsและรองรับ async - beautifulsoup4 + lxml — ใช้แยกวิเคราะห์ HTML
- pandas — ส่งออกข้อมูลเป็น CSV/Excel
- ถ้าจำเป็น: selenium หรือ playwright หากต้องเรนเดอร์หน้าเว็บที่พึ่ง JavaScript หนัก ๆ
ทำความเข้าใจโครงสร้างหน้า Zillow ก่อนลงมือ scrape

นี่คือจุดที่สำคัญที่สุดก่อนจะเขียนโค้ด Zillow เป็น แอป Next.js — ยืนยันได้จาก โพสต์ของวิศวกร Zillow นั่นหมายความว่าข้อมูลส่วนใหญ่ที่คุณต้องการไม่ได้อยู่ใน HTML ที่มองเห็นตรง ๆ แต่ถูกฝังอยู่ใน JSON blob ภายใน <script id="__NEXT_DATA__">
ลองเปิดหน้าประกาศขายใดก็ได้ของ Zillow กด F12 ไปที่ Elements แล้วค้นหา __NEXT_DATA__ คุณจะเจอออบเจ็กต์ JSON ขนาดใหญ่ที่มีข้อมูลประกาศทั้งหมด — ราคา พิกัด รายละเอียดอสังหาฯ ประวัติราคา บันทึกภาษี คะแนนโรงเรียน และอื่น ๆ
ทำไมจุดนี้ถึงสำคัญ? เพราะ ชื่อ CSS class ของ Zillow ถูกแฮช (สร้างโดย styled-components) และเปลี่ยนทุกครั้งที่มีการ deploy ตัวอย่างเช่น class อย่าง StyledPropertyCardHomeDetailsList-c11n-8-109-3__sc-1j0som5-0 อาจเปลี่ยน hash ไปเป็นค่าอื่นในสัปดาห์หน้าเลยก็ได้ ดังนั้น scraper ที่พึ่ง CSS selector จึงพังบ่อยมาก
วิธีดึงจาก JSON __NEXT_DATA__ จึงเสถียรกว่ามาก เพราะไม่ต้องพึ่งโครงสร้าง HTML โดยตรง
เส้นทาง JSON หลักสำหรับหน้าผลการค้นหาคือ:
| Path | เนื้อหา |
|---|---|
props.pageProps.searchPageState.cat1.searchResults.listResults | อาร์เรย์ของผลการค้นหา |
props.pageProps.searchPageState.cat1.searchResults.mapResults | ผลลัพธ์ในมุมมองแผนที่ |
props.pageProps.searchPageState.cat1.searchList.totalPages | จำนวนหน้าทั้งหมดที่มี |
สำหรับหน้ารายละเอียด บางหน้าจะใช้ __NEXT_DATA__ และบางหน้าจะใช้ script tag ทางเลือกชื่อ hdpApolloPreloadedData โค้ดด้านล่างรองรับทั้งสองแบบ
ทีละขั้นตอน: วิธีดึงข้อมูล Zillow ด้วย Python
ขั้นตอนที่ 1: ตั้งค่า HTTP headers เพื่อหลบการบล็อกทันที
ถ้าส่ง httpx.get() แบบเปล่า ๆ ไปที่ Zillow คุณจะได้หน้า CAPTCHA ไม่ใช่ข้อมูลประกาศ Zillow ใช้ PerimeterX (HUMAN Security) ร่วมกับ Cloudflare ซึ่งถูกมองว่า ยากระดับ 8/10 ตาม benchmark ของสาย scraping ระบบจะตรวจสอบ TLS fingerprint, HTTP headers และ reputation ของ IP คุณ
นี่คือ headers ขั้นต่ำที่ใช้ได้ ณ ปี 2025:
import httpx
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,"
"image/avif,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"Sec-Ch-Ua": '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"',
"Sec-Ch-Ua-Platform": '"Windows"',
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"Upgrade-Insecure-Requests": "1",
}
หัวข้อ Sec-Ch-Ua สำคัญมาก หลายบทเรียนมักข้ามมันไป — และนั่นคือเหตุผลว่าทำไมโค้ดของบทเรียนพวกนั้นถึงใช้ไม่ได้กับ PerimeterX
ขั้นตอนที่ 2: ดึงข้อมูลจากหน้าผลการค้นหาของ Zillow
URL สำหรับค้นหา Zillow มีรูปแบบค่อนข้างตายตัว ตัวอย่าง Houston, TX:
- หน้า 1:
https://www.zillow.com/houston-tx/ - หน้า 2:
https://www.zillow.com/houston-tx/2_p/ - หน้า 3:
https://www.zillow.com/houston-tx/3_p/
แต่ละหน้ามีประกาศประมาณ 41 รายการ Zillow จำกัดผลลัพธ์ไว้ที่ 20 หน้า หรือประมาณ 820 รายการ ถ้าต้องการข้อมูลมากกว่านั้น คุณจะต้องแบ่งพื้นที่ตามภูมิศาสตร์ (อธิบายต่อด้านล่าง)
โค้ดต่อไปนี้ใช้ดึงผลการค้นหาโดยแยกข้อมูลจาก JSON __NEXT_DATA__:
from bs4 import BeautifulSoup
import json
import time
import random
def scrape_zillow_search(url):
"""ดึงข้อมูลประกาศจากหน้าผลการค้นหาของ Zillow"""
response = httpx.get(url, headers=headers, timeout=15)
if response.status_code != 200:
print(f"Got status {response.status_code} for {url}")
return []
soup = BeautifulSoup(response.text, "lxml")
script_tag = soup.find("script", {"id": "__NEXT_DATA__"})
if not script_tag:
print("No __NEXT_DATA__ found — likely blocked by CAPTCHA")
return []
next_data = json.loads(script_tag.string)
try:
results = (
next_data["props"]["pageProps"]["searchPageState"]
["cat1"]["searchResults"]["listResults"]
)
except KeyError:
print("Unexpected JSON structure — Zillow may have changed its format")
return []
listings = []
for item in results:
listing = {
"zpid": item.get("zpid"),
"address": item.get("addressStreet"),
"city": item.get("addressCity"),
"state": item.get("addressState"),
"zipcode": item.get("addressZipcode"),
"price": item.get("unformattedPrice") or item.get("price"),
"beds": item.get("beds"),
"baths": item.get("baths"),
"sqft": item.get("area"),
"zestimate": item.get("zestimate"),
"days_on_zillow": item.get("daysOnZillow"),
"listing_url": item.get("detailUrl"),
"img_src": item.get("imgSrc"),
"property_type": item.get("hdpData", {}).get("homeInfo", {}).get("homeType"),
"latitude": item.get("latLong", {}).get("latitude"),
"longitude": item.get("latLong", {}).get("longitude"),
}
listings.append(listing)
return listings
ถ้าจะ scrape หลายหน้า ให้ใช้ลูปพร้อมหน่วงเวลา:
all_listings = []
base_url = "https://www.zillow.com/houston-tx/"
for page in range(1, 6): # 5 หน้าแรก
url = base_url if page == 1 else f"{base_url}{page}_p/"
print(f"Scraping page {page}...")
page_listings = scrape_zillow_search(url)
all_listings.extend(page_listings)
# หน่วงเวลาแบบสุ่ม 3-7 วินาที
delay = random.uniform(3, 7)
time.sleep(delay)
print(f"Total listings scraped: {len(all_listings)}")
คุณควรจะเห็นข้อมูลประกาศที่เป็นโครงสร้างถูกเก็บรวมไว้ใน all_listings ถ้าได้ผลลัพธ์ว่าง ให้ไปดูหัวข้อ "ทำไมตัว scrape ถึงพัง" ด้านล่าง
ขั้นตอนที่ 3: ดึงข้อมูลจากหน้ารายละเอียดของประกาศ Zillow
หน้าผลการค้นหาจะให้ข้อมูลพื้นฐาน แต่หน้ารายละเอียดจะมีข้อมูลเชิงลึกกว่า เช่น ประวัติราคา ประวัติภาษี คะแนนโรงเรียน ข้อมูลเอเจนต์ และคำอธิบายอสังหาฯ แต่ละ URL จากขั้นตอนที่ 2 จะชี้ไปยังหน้ารายละเอียด
หน้ารายละเอียดของ Zillow มีรูปแบบข้อมูลได้สองแบบ โค้ดด้านล่างรองรับทั้งคู่:
def scrape_zillow_detail(url):
"""ดึงข้อมูลเชิงลึกของอสังหาฯ จากหน้าประกาศ Zillow"""
response = httpx.get(url, headers=headers, timeout=15)
if response.status_code != 200:
return None
soup = BeautifulSoup(response.text, "lxml")
# ลอง __NEXT_DATA__ ก่อน (พบบ่อยที่สุด)
script_tag = soup.find("script", {"id": "__NEXT_DATA__"})
if script_tag:
next_data = json.loads(script_tag.string)
try:
cache_str = next_data["props"]["pageProps"]["componentProps"]["gdpClientCache"]
cache = json.loads(cache_str)
first_key = next(iter(cache))
prop = cache[first_key]["property"]
return extract_property_fields(prop)
except (KeyError, StopIteration):
pass
# ทางเลือกสำรอง: hdpApolloPreloadedData
apollo_tag = soup.find("script", {"id": "hdpApolloPreloadedData"})
if apollo_tag:
raw = json.loads(apollo_tag.string)
api_cache = json.loads(raw["apiCache"])
for key, value in api_cache.items():
if "ForSale" in key or "property" in str(value)[:100]:
prop = value.get("property", value)
return extract_property_fields(prop)
return None
def extract_property_fields(prop):
"""ดึงฟิลด์ที่เป็นโครงสร้างจาก JSON ของ property ใน Zillow"""
return {
"zpid": prop.get("zpid"),
"zestimate": prop.get("zestimate"),
"rent_zestimate": prop.get("rentZestimate"),
"description": prop.get("description"),
"year_built": prop.get("yearBuilt"),
"lot_size": prop.get("lotSize"),
"hoa_fee": prop.get("monthlyHoaFee"),
"mls_id": prop.get("mlsid"),
"broker_name": prop.get("brokerName") or prop.get("attributionInfo", {}).get("brokerName"),
"price_history": [
{
"date": event.get("date"),
"event": event.get("event"),
"price": event.get("price"),
}
for event in prop.get("priceHistory", [])
],
"tax_history": [
{
"year": record.get("time"),
"tax_paid": record.get("taxPaid"),
"value": record.get("value"),
}
for record in prop.get("taxHistory", [])
],
"schools": [
{
"name": school.get("name"),
"rating": school.get("rating"),
"distance": school.get("distance"),
}
for school in prop.get("schools", [])
],
}
วนลูปตาม URL ของประกาศพร้อมหน่วงเวลา:
detail_data = []
for listing in all_listings[:10]: # เริ่มจาก 10 รายการเพื่อทดสอบ
detail_url = listing.get("listing_url")
if not detail_url:
continue
if not detail_url.startswith("http"):
detail_url = f"https://www.zillow.com{detail_url}"
print(f"Scraping detail: {detail_url}")
detail = scrape_zillow_detail(detail_url)
if detail:
detail_data.append({**listing, **detail})
time.sleep(random.uniform(3, 8))
พอจบขั้นตอนนี้ คุณจะได้รายการ dictionaries ที่มีทั้งข้อมูลระดับหน้าค้นหาและระดับหน้ารายละเอียดสำหรับแต่ละ property
ขั้นตอนที่ 4: จัดการ pagination เพื่อดึงข้อมูลหลายหน้า
สำหรับพื้นที่ที่มีประกาศมากกว่า 820 รายการ (ข้อจำกัด 20 หน้า) คุณต้องแบ่งพื้นที่ตามภูมิศาสตร์ Zillow มี internal API ที่รองรับพารามิเตอร์ mapBounds กลยุทธ์คือแบ่งแผนที่เป็นสี่ส่วนแล้ว scrape ทีละส่วน
def split_bounds(bounds):
"""แบ่งขอบเขตแผนที่ออกเป็น 4 ควอดแรนต์"""
mid_lat = (bounds["north"] + bounds["south"]) / 2
mid_lng = (bounds["east"] + bounds["west"]) / 2
return [
{"north": bounds["north"], "south": mid_lat, "east": bounds["east"], "west": mid_lng},
{"north": bounds["north"], "south": mid_lat, "east": mid_lng, "west": bounds["west"]},
{"north": mid_lat, "south": bounds["south"], "east": bounds["east"], "west": mid_lng},
{"north": mid_lat, "south": bounds["south"], "east": mid_lng, "west": bounds["west"]},
]
สำหรับการใช้งานส่วนใหญ่ — เช่น ติดตาม 50–200 ประกาศในพื้นที่เฉพาะ — การ paginate ด้วย URL ปกติก็เพียงพอแล้ว วิธีแบ่งสี่ส่วนเหมาะกับงานที่ต้อง scrape ทั้งระดับเมืองหรือทั้งรัฐ
ขั้นตอนที่ 5: Export ข้อมูล Zillow ที่ดึงมาได้
บันทึกทุกอย่างเป็น CSV ด้วย pandas:
import pandas as pd
df = pd.DataFrame(detail_data)
df.to_csv("zillow_houston_listings.csv", index=False)
print(f"Exported {len(df)} listings to zillow_houston_listings.csv")
ถ้าต้องการ export เป็น JSON:
with open("zillow_houston_listings.json", "w") as f:
json.dump(detail_data, f, indent=2)
ถ้าคุณอยากข้ามขั้นตอน export ไปเลย Thunderbit สามารถส่งออกไป Google Sheets, Airtable และ Notion ได้ฟรี — เหมาะมากถ้าคุณต้องการข้อมูลในรูปแบบที่เอาไปใช้งานต่อได้ทันที
ทำไมตัว scrape Zillow ถึงพังบ่อย และจะทำอย่างไรให้ทนทาน
นี่คือคู่มือเอาตัวรอด
จากประสบการณ์ของผม ตัว scrape บน Zillow พังด้วยเหตุผลหลัก 3 ข้อ — และแต่ละข้อก็มีทางแก้ชัดเจน
PerimeterX และ CAPTCHA: ทำไม request ของคุณถึงได้ข้อมูลว่าง
ระบบ PerimeterX ของ Zillow ตรวจหลายสัญญาณพร้อมกัน ได้แก่ TLS fingerprint, HTTP headers, reputations ของ IP และรูปแบบการ request พอมันจับได้ว่ากำลังเป็น automation มันจะส่งหน้า CAPTCHA แบบ "Press & Hold" แทนข้อมูลประกาศ
สถานการณ์ที่พังแบบตรง ๆ: คุณส่ง request ด้วย headers มาตรฐานของ Python แล้ว HTML ที่ได้กลับมามีสคริปต์ challenge ของ PerimeterX แทนข้อมูลอสังหาฯ จากนั้น BeautifulSoup ก็หาแท็ก __NEXT_DATA__ ไม่เจอ
วิธีแก้: ใช้ headers แบบเลียนแบบเบราว์เซอร์เต็มชุดจากขั้นตอนที่ 1 ถ้าคุณยิง request มากกว่าสองสามสิบครั้ง คุณต้องหมุน proxy ด้วย (จะอธิบายต่อด้านล่าง) ถ้าจะทำ scrape หนัก ๆ ควรพิจารณาไลบรารีอย่าง curl_cffi พร้อม impersonate="chrome" เพราะเป็น HTTP client ของ Python ที่ใกล้เคียง TLS fingerprint ของ Chrome จริงที่สุด
Dynamic CSS selectors: ทำไม BeautifulSoup ถึงได้ค่า None
ถ้าคุณใช้ CSS selector อย่าง .list-card-price หรือ class ที่มี hash ตัว scraper จะพังทุกครั้งที่ Zillow deploy โค้ดใหม่
Zillow ใช้ styled-components ซึ่งสร้างชื่อ class แบบ StyledPropertyCardHomeDetailsList-c11n-8-109-3__sc-1j0som5-0 โดยส่วน hash จะเปลี่ยนทุกครั้งที่ build ใหม่
วิธีแก้: อย่าใช้ CSS selector เลย ให้ดึงข้อมูลจาก JSON blob __NEXT_DATA__ ตามตัวอย่างโค้ดด้านบน วิธีนี้เสถียรกว่ามาก เพราะโครงสร้าง JSON เปลี่ยนน้อยกว่า markup ของ HTML เยอะ
ถ้าจำเป็นต้อง parse HTML จริง ๆ ให้มองหา data-test attributes เช่น data-test="property-card" หรือใช้ class แบบ substring matching เช่น [class*="PropertyCard"] แต่การดึงจาก JSON ยังน่าเชื่อถือกว่า
การหมุน proxy และ exponential backoff: โค้ดที่ทนต่อการโดนแบน IP
IP แบบ datacenter ถูก Zillow บล็อกทันที คุณจำเป็นต้องใช้ residential proxies เพื่อเข้าถึงได้อย่างเสถียร อัตราที่ปลอดภัยคือ 1 request ทุก 3–8 วินาทีต่อ IP และควรอยู่ต่ำกว่า ~500 requests/ชั่วโมง
นี่คือ retry decorator พร้อม exponential backoff และ jitter:
import random
import time
def backoff_with_jitter(attempt, base_delay=2, max_delay=60):
"""Exponential backoff แบบ full jitter สไตล์ AWS"""
delay = min(max_delay, base_delay * (2 ** attempt))
return random.uniform(0, delay)
def fetch_with_retry(url, max_retries=5):
for attempt in range(max_retries):
try:
response = httpx.get(url, headers=headers, timeout=15)
if response.status_code == 200:
return response
if response.status_code in (403, 429):
delay = backoff_with_jitter(attempt, base_delay=5)
print(f"Blocked ({response.status_code}). Retrying in {delay:.1f}s...")
time.sleep(delay)
continue
except Exception as e:
if attempt == max_retries - 1:
raise
time.sleep(backoff_with_jitter(attempt))
return None
และนี่คือ proxy pool แบบง่าย:
class ProxyPool:
def __init__(self, proxies):
self.proxies = proxies
self.index = 0
self.failures = {}
def get_next(self):
proxy = self.proxies[self.index % len(self.proxies)]
self.index += 1
return {"http://": proxy, "https://": proxy}
def report_failure(self, proxy):
self.failures[proxy] = self.failures.get(proxy, 0) + 1
if self.failures[proxy] > 3:
self.proxies.remove(proxy)
# วิธีใช้:
pool = ProxyPool(proxies=[
"http://user:pass@residential1.example.com:8080",
"http://user:pass@residential2.example.com:8080",
])
สำหรับผู้ให้บริการ proxy, DataImpulse มี residential proxies ราคาเริ่มประมาณ ~$1/GB (เป็นตัวเลือกที่ถูกที่สุด) ส่วน IPRoyal และ Smartproxy ก็เป็นตัวเลือกระดับกลางที่ดี อยู่ราว $4–7/GB
ทางเลือกที่ไม่ต้องดูแลเลย
ถ้าคุณต้อง scrape Zillow เป็นประจำ และเบื่อกับการต้องแก้ selector ที่พังหรือคุม proxy pool เอง Thunderbit ใช้ AI อ่านโครงสร้างหน้าเว็บใหม่ทุกครั้งที่ scrape ไม่ต้องดูแล selector ไม่ต้องตั้งค่า proxy ปัญหาเรื่องความเปราะบางที่ทำให้ scraper แบบเขียนโค้ดกลายเป็นงานจุกจิกประจำจึงหายไปจริง
ทำให้การ scrape Zillow เป็นอัตโนมัติ: การตั้งเวลาและติดตามราคา
นักลงทุนอสังหาฯ เกือบทุกคนที่ผมคุยด้วยอยากได้ฟีเจอร์นี้ และไม่มีคู่มือ scrape Zillow เล่มไหนอธิบายเรื่องนี้ครบ: การ scrape อัตโนมัติแบบซ้ำ ๆ สำหรับติดตามราคา
สำหรับคนใช้ Python: Cron job และการตรวจจับการเปลี่ยนราคา
ตั้ง cron job ให้รัน scraper ทุกสัปดาห์และ flag การเปลี่ยนแปลงราคา:
import pandas as pd
from datetime import datetime
def detect_price_changes(new_data, historical_file, threshold=0.05):
"""เปรียบเทียบข้อมูลที่ scrape ใหม่กับข้อมูลย้อนหลัง แล้ว flag การเปลี่ยนแปลงที่เกิน threshold"""
try:
old = pd.read_csv(historical_file)
except FileNotFoundError:
new_data.to_csv(historical_file, index=False)
print("First run — saved baseline data.")
return pd.DataFrame()
merged = new_data.merge(old, on="zpid", suffixes=("_new", "_old"))
merged["price_change_pct"] = (
(merged["price_new"] - merged["price_old"]) / merged["price_old"]
)
alerts = merged[merged["price_change_pct"].abs() > threshold]
# เพิ่มข้อมูลใหม่พร้อม timestamp
new_data["scraped_at"] = datetime.now().isoformat()
new_data.to_csv(historical_file, mode="a", header=False, index=False)
return alerts
เพิ่มบรรทัดนี้ใน crontab เพื่อรันทุกวันจันทร์เวลา 6 โมงเช้า:
0 6 * * 1 cd /path/to/scraper && python zillow_monitor.py
ตัวอย่างใช้งานจริง: ติดตาม 50 รายการใน Austin, TX รายสัปดาห์ ทุกวันจันทร์สคริปต์จะ scrape ราคาปัจจุบัน เปรียบเทียบกับสัปดาห์ก่อน และส่งออก CSV ที่ไฮไลต์รายการที่ราคาลดลงมากกว่า 5%
สำหรับคนไม่เขียนโค้ด: Thunderbit Scheduled Scraper
Thunderbit's Scheduled Scraper ให้คุณกำหนดรอบเวลาเป็นภาษาง่าย ๆ เช่น "every Monday at 9am" ใส่ URL ค้นหา Zillow แล้วกด Schedule ระบบจะส่งออกข้อมูลไป Google Sheets อัตโนมัติทุกครั้งที่รัน ไม่ต้อง Python ไม่ต้อง cron ไม่ต้องดูแลเซิร์ฟเวอร์ เหมาะมากสำหรับเอเจนต์อสังหาฯ หรือทีมปฏิบัติการที่ต้องติดตามราคาอย่างสม่ำเสมอโดยไม่ต้องพึ่งทีมวิศวกรรม
เคล็ดลับการ scrape Zillow อย่างรับผิดชอบ
ขอฝากข้อควรระวังเล็กน้อยเพื่อให้อยู่ในขอบเขตที่เหมาะสม:
- ดึงเฉพาะข้อมูลสาธารณะที่เข้าถึงได้ อย่าเข้าหน้าหลังล็อกอินหรือหน้าที่ต้องยืนยันตัวตน
- ใช้ความถี่ในการ request ที่เหมาะสม เว้น 3–8 วินาทีระหว่างแต่ละ request อย่ายิงถี่เกินไป
- อย่าดึงข้อมูลส่วนตัวของผู้ใช้ ชื่อเอเจนต์และข้อมูลบริษัทนายหน้าบนประกาศเป็นข้อมูลสาธารณะ แต่ข้อมูลบัญชีผู้ใช้ไม่ใช่
- จัดเก็บและใช้ข้อมูลอย่างมีจริยธรรม การวิจัยตลาด วิเคราะห์การลงทุน และสร้างลีดเป็นการใช้งานที่ถูกต้อง แต่การ spam ไม่ใช่
- บริบททางกฎหมาย: คำตัดสิน hiQ v. LinkedIn ระบุว่าการ scrape ข้อมูลที่เข้าถึงได้สาธารณะไม่ถือว่าละเมิด CFAA และคำตัดสิน Meta v. Bright Data (2024) ก็ยืนยันหลักการใกล้เคียงกัน อย่างไรก็ตาม ข้อกำหนดการใช้งานของ Zillow ยังจำกัดการเข้าถึงแบบอัตโนมัติ และพวกเขาใช้การแบน IP กับ CAPTCHA เป็นตัวบังคับใช้นโยบายแทนการฟ้องร้อง โปรดตรวจสอบแนวทางล่าสุดเสมอ และเคารพ robots.txt
เลือกแนวทางที่เหมาะกับการ scrape Zillow ด้วย Python
เส้นทางที่ดีที่สุดขึ้นอยู่กับสถานการณ์ของคุณ:
ต้องการข้อมูลเร็ว ไม่อยากเขียนโค้ด? Thunderbit ช่วยให้คุณไปจากหน้าค้นหา Zillow สู่สเปรดชีตที่เป็นระบบได้ในเวลาประมาณ 2 นาที AI จะปรับตัวตามการเปลี่ยนแปลงของ layout จัดการ pagination และส่งออกให้ฟรี ติดตั้ง Chrome Extension แล้วลองใช้กับหน้าค้นหา Zillow ได้เลย
อยากคุมทุกอย่างเอง? ใช้โค้ด Python ในคู่มือนี้ ดึงจาก JSON __NEXT_DATA__ แทน CSS selector เพื่อความเสถียร ตั้ง headers ให้เหมือนเบราว์เซอร์จริง หมุน residential proxies และใช้ exponential backoff เพื่อความทนทาน
ต้องการสเกลใหญ่ขึ้น? Scraping API อย่าง ScrapFly ซึ่งมีอัตราสำเร็จบน Zillow ถึง 99% หรือ ScraperAPI จะจัดการ proxy และระบบ CAPTCHA ให้คุณ โดยมีค่าใช้จ่ายประมาณ $30–599 ต่อเดือนตามปริมาณใช้งาน
อยากติดตามราคาตามเวลา? ตั้ง cron job ด้วยสคริปต์ตรวจจับการเปลี่ยนราคา หรือใช้ Scheduled Scraper ของ Thunderbit เพื่อแนวทางที่ไม่ต้องดูแลเลย
ข้อมูลมีอยู่แล้ว คำถามมีแค่ว่าคุณอยากใช้เวลาเขียนระบบเพื่อดึงมันออกมามากแค่ไหน ถ้าอยากอ่านเพิ่มเกี่ยวกับการเอาข้อมูลเว็บลงสเปรดชีต ลองดูคู่มือ scraping data from websites into Excel หรือ Zillow statistics roundup สำหรับข้อมูลล่าสุดของแพลตฟอร์ม คุณยังเข้าไปดูบทเรียนเพิ่มเติมได้ที่ Thunderbit YouTube Channel
ลองใช้ Thunderbit สำหรับการดึงข้อมูล Zillow Get Started Free
คำถามที่พบบ่อย
ใช้ Python scrape Zillow ฟรีได้ไหม?
ได้ — httpx, BeautifulSoup และ pandas เป็นเครื่องมือฟรีและโอเพนซอร์สทั้งหมด แต่สิ่งที่ต้องแลกคือเวลา: คุณต้องจัดการ headers, การหมุน proxy และดูแล selector เอง คาดว่าใช้เวลาตั้งต้น 4–8 ชั่วโมง และต้องดูแลต่อเดือนอีก 4–10 ชั่วโมงเมื่อ Zillow เปลี่ยนหน้าเว็บ Thunderbit ก็มีแพ็กเกจฟรีถ้าคุณอยากเลี่ยงงานเขียนโค้ดทั้งหมด
Zillow มี API ทางการไหม?
Zillow ยกเลิก API สาธารณะฟรีไปตั้งแต่กันยายน 2021 ตอนนี้การเข้าถึงต้องผ่าน Bridge Interactive ซึ่งต้องได้รับอนุมัติ มีค่าใช้จ่ายประมาณ $500/เดือน และออกแบบมาสำหรับมืออาชีพอสังหาฯ ที่มีใบอนุญาต สำหรับผู้ใช้ส่วนใหญ่ — นักลงทุน นักวิจัย หรือเอเจนต์ที่ทำ market analysis — การ scrape จึงยังเป็นทางเลือกที่ใช้งานได้จริง Zillow ยังเผยแพร่ข้อมูลวิจัยฟรีในรูปแบบ CSV ดาวน์โหลดได้ที่ zillow.com/research/data/ รวมถึง Zillow Home Value Index และ Zillow Observed Rent Index
จะหลีกเลี่ยงการโดนบล็อกตอน scrape Zillow ได้อย่างไร?
มี 3 เรื่องหลัก: (1) ใช้ browser headers ให้สมจริง รวมถึง Sec-Ch-Ua — เป็น header ที่หลายบทเรียนมักลืม แต่ PerimeterX ตรวจเป็นอันดับแรก ๆ; (2) หมุน residential proxies — IP แบบ datacenter จะถูกบล็อกทันที; (3) ดึงข้อมูลจาก JSON __NEXT_DATA__ แทน HTML selector เพื่อเลี่ยงปัญหาเมื่อ layout เปลี่ยน รักษาอัตราการ request ไว้ที่ 1 ครั้งต่อ 3–8 วินาทีต่อ IP หรือใช้เครื่องมืออย่าง Thunderbit ที่จัดการ anti-bot ให้อัตโนมัติ
วิธีที่ดีที่สุดในการ scrape Zillow โดยไม่ต้องเขียนโค้ดคืออะไร?
Thunderbit AI Web Scraper คือทางลัดที่เร็วที่สุด ติดตั้ง Chrome Extension แล้วเข้าไปยังหน้าค้นหา Zillow คลิก "AI Suggest Fields" เพื่อให้ระบบตรวจจับคอลัมน์อัตโนมัติ จากนั้นคลิก "Scrape" ส่งออกไป Google Sheets, Excel, Airtable หรือ Notion ได้โดยไม่ต้องเขียนโค้ด AI จะอ่านหน้าเว็บใหม่ทุกครั้ง จึงไม่พังเมื่อ Zillow อัปเดต layout
Zillow เปลี่ยนโครงสร้างเว็บบ่อยแค่ไหน และมีผลต่อ scraper อย่างไร?
Zillow deploy อัปเดตบ่อย — บางครั้งเป็นรายสัปดาห์ เพราะใช้ styled-components ชื่อ CSS class จึงเปลี่ยนทุกครั้งที่ deploy ทำให้ scraper ที่อาศัย CSS selector พังเป็นประจำ วิธีที่ทนทานที่สุดสำหรับ Python คือดึงข้อมูลจาก JSON blob __NEXT_DATA__ ซึ่งเปลี่ยนโครงสร้างน้อยกว่าอย่างมาก สำหรับแนวทางที่ไม่ต้องดูแลเลย Thunderbit จะอ่านโครงสร้างหน้าใหม่ทุกครั้งที่ scrape และปรับตามการเปลี่ยนแปลงของ layout ให้อัตโนมัติ
อ่านเพิ่มเติม


