Zillow đang nắm giữ 160 triệu hồ sơ bất động sản tại Mỹ, và việc khai thác dữ liệu này ở quy mô lớn là một trong những đầu việc được săn đón nhất — đồng thời cũng là thứ khiến dân dữ liệu bất động sản đau đầu nhất. Nếu bạn từng thử scrape Zillow rồi chỉ nhận về một trang CAPTCHA thay vì dữ liệu tin đăng, thì yên tâm là bạn không hề đơn độc.
Tôi đã dành khá nhiều thời gian để nghiên cứu và thử đủ kiểu cách khác nhau nhằm scrape Zillow — cả bằng Python lẫn các công cụ no-code mà chúng tôi xây tại Thunderbit. Hướng dẫn này sẽ đi qua cả hai hướng đó. Dù bạn muốn xem trọn quy trình Python với các chiến lược chống bot, hay chỉ cần 200 tin đăng đổ thẳng vào bảng tính trước giờ ăn trưa, ở đây đều có phần dành cho bạn. Chúng ta sẽ lần lượt đi qua vì sao dữ liệu Zillow quan trọng, cấu trúc bên trong của website này, hướng dẫn Python từng bước, các nguyên nhân chính khiến scraper bị gãy, và cách tự động hóa những lần scrape định kỳ để theo dõi biến động giá.
Vì sao nên scrape dữ liệu Zillow?
Zillow là kho dữ liệu bất động sản nhà ở lớn nhất tại Mỹ. Nền tảng này thu hút 210 triệu lượt truy cập mỗi tháng và hiện có khoảng hơn 750.000 tin bán đang hoạt động cùng 1,9 triệu tin cho thuê. Zillow chiếm hơn 50% tổng lưu lượng truy cập của các cổng bất động sản tại Mỹ — nhiều hơn gấp đôi đối thủ đứng ngay sau.

Trước khi đụng vào code Python, bạn nên biết rằng scrape Zillow bằng Python không phải là con đường duy nhất, và chọn sai cách có thể khiến bạn mất hàng giờ. Các công cụ Python như httpx và BeautifulSoup đòi hỏi trình độ trung cấp, phải tự xử lý headers và proxies, tốc độ ở mức vừa phải (1–3 giây/trang), và dù miễn phí thì vẫn cần bảo trì thường xuyên; Selenium hoặc Playwright giúp chống bot tốt hơn nhờ render JavaScript, nhưng lại chậm hơn (5–15 giây/trang) và vẫn khá tốn công chăm sóc; các scraping API như ScraperAPI hoặc ScrapFly nhanh hơn, có sẵn hỗ trợ chống bot và mức bảo trì trung bình, nhưng chi phí khoảng 30–599 USD mỗi tháng; API chính thức của Zillow qua Bridge Interactive thì rất nhanh, ít phải bảo trì nhưng bị giới hạn và giá khoảng 500 USD/tháng; còn các công cụ no-code như Thunderbit thì dễ dùng cho người mới, nhanh, không cần bảo trì nhờ AI tự thích ứng, và thường có mô hình freemium.
Chỉ riêng khoản tiết kiệm thời gian đã là một con số rất đáng kể. Nghiên cứu thủ công trên hơn 50 mã ZIP có thể ngốn 15–20 giờ mỗi tuần. Tự động hóa việc scrape có thể kéo công việc đó xuống còn vài phút — tức giảm tới 99,7% thời gian.
Mọi cách scrape Zillow: Python vs. API vs. No-Code (so sánh)
Trước khi vào phần code Python, hãy nhớ rằng “scrape Zillow bằng Python” không phải là lối đi duy nhất. Chọn sai phương pháp sẽ tốn rất nhiều thời gian. Dưới đây là bảng so sánh để bạn tự cân nhắc:
| Phương pháp | Mức độ kỹ năng | Khả năng chống bot | Tốc độ | Bảo trì | Chi phí |
|---|---|---|---|---|---|
| Python + httpx/BeautifulSoup | Trung cấp | Thủ công (headers, proxies) | Trung bình (1–3s/trang) | Cao (selector dễ hỏng) | Miễn phí |
| Python + Selenium/Playwright | Trung cấp | Tốt hơn (render JS) | Chậm (5–15s/trang) | Cao | Miễn phí |
| Scraping API (ScraperAPI, ScrapFly) | Trung cấp | Tích hợp sẵn | Nhanh | Trung bình | 30–599 USD/tháng |
| API chính thức của Zillow (Bridge Interactive) | Người mới–Trung cấp | Không áp dụng | Nhanh | Thấp | ~500 USD/tháng, quyền truy cập hạn chế |
| Công cụ No-Code (Thunderbit) | Người mới | Tích hợp sẵn (AI tự thích ứng) | Nhanh | Không cần | Freemium |
Nếu bạn cần dữ liệu ngay mà không muốn viết code, hãy bắt đầu với Thunderbit. Nếu bạn muốn hiểu toàn bộ cơ chế phía sau hoặc cần tuỳ chỉnh sâu hơn, hãy đọc tiếp phần hướng dẫn Python.
Cách nhanh nhất trong 2 phút: Scrape Zillow bằng Thunderbit (không cần code)
Trước khi đi sâu vào Python, đây là lộ trình dành cho bất kỳ ai chỉ cần dữ liệu Zillow thật nhanh — không cần cài Python, không cần cấu hình proxy, không phải ôm mớ selector đi bảo trì. Chúng tôi xây dựng quy trình này tại Thunderbit специально để lấy dữ liệu bất động sản có cấu trúc mà không tốn công kỹ thuật.
Mức độ khó: Người mới
Thời gian cần thiết: ~2 phút
Bạn cần gì: Trình duyệt Chrome, Thunderbit Chrome Extension (bản miễn phí vẫn dùng được)
Bước 1: Cài Thunderbit và mở Zillow
Cài tiện ích Thunderbit từ Chrome Web Store. Sau đó mở một trang kết quả tìm kiếm của Zillow — ví dụ, tìm nhà ở Houston, TX.
Bước 2: Bấm “AI Suggest Fields”
Mở thanh bên của Thunderbit và bấm “AI Suggest Fields.” AI sẽ đọc trang và tự đề xuất các cột như: giá, địa chỉ, số phòng ngủ, số phòng tắm, diện tích, Zestimate, URL tin đăng, và nhiều hơn nữa. Trong quá trình thử nghiệm của tôi, nó thường nhận diện hơn 20 trường dữ liệu mà không cần bạn cấu hình thủ công.
Bước 3: Bấm “Scrape”
Nhấn nút Scrape. Dữ liệu sẽ được đưa vào một bảng có cấu trúc ngay trong extension. Thunderbit tự xử lý phân trang của Zillow — cả kiểu bấm nút lẫn cuộn vô hạn.
Bước 4: Làm giàu dữ liệu bằng Scrape Subpages
Bạn muốn lấy thêm dữ liệu từ trang chi tiết như lịch sử thuế, xếp hạng trường học hoặc lịch sử giá? Hãy dùng “Scrape Subpages” để mở rộng bảng dữ liệu. Thunderbit sẽ đi theo từng URL tin đăng và kéo thêm các trường bổ sung — không cần viết thêm code.
Bước 5: Xuất dữ liệu
Xuất sang Google Sheets, Excel, Airtable hoặc Notion. Việc xuất dữ liệu hoàn toàn miễn phí.
Vì sao Thunderbit hoạt động tốt với Zillow
Điểm mạnh lớn nhất ở đây là độ bền. AI của Thunderbit đọc lại cấu trúc trang mới hoàn toàn mỗi lần bạn scrape. Khi Zillow thay đổi layout — chuyện này diễn ra khá thường xuyên — bạn không cần sửa những CSS selector dễ vỡ. AI sẽ tự thích ứng. Nói đơn giản, nó xử lý gọn cái vấn đề “rất mong manh” của các scraper viết bằng code, thứ khiến rất nhiều người dùng phải bực mình.
Bạn có thể lấy những dữ liệu nào từ Zillow? (hơn 20 trường)
Phần lớn các bài hướng dẫn chỉ lấy giá và địa chỉ rồi dừng lại. Thực tế, tin đăng Zillow chứa nhiều dữ liệu hơn rất nhiều so với mọi người thường nghĩ — dưới đây là bảng tham khảo:
| Trường dữ liệu | Nằm ở đâu | Mức độ khó khi trích xuất |
|---|---|---|
| Giá niêm yết | Search + Detail | Dễ |
| Địa chỉ / Mã ZIP | Search + Detail | Dễ |
| Zestimate | Search + Detail | Dễ |
| Lịch sử giá (mỗi lần thay đổi) | Detail | Khó (JSON lồng nhau) |
| Lịch sử thuế | Detail | Khó (JSON lồng nhau) |
| Số phòng ngủ / phòng tắm / diện tích | Search + Detail | Dễ |
| Năm xây dựng | Detail | Dễ |
| Phí HOA | Detail | Trung bình |
| Walk Score / Transit Score | Detail (iframe) | Khó (cần render JS) |
| Xếp hạng trường học | Detail | Trung bình |
| Diện tích đất | Detail | Dễ |
| Số ngày trên Zillow | Search | Dễ |
| Đại lý niêm yết / công ty môi giới | Search + Detail | Trung bình |
| Mã MLS | Detail | Dễ |
| Loại bất động sản | Search + Detail | Dễ |
| Vĩ độ / Kinh độ | JSON __NEXT_DATA__ | Trung bình |
| Mô tả | Detail | Dễ |
| URL ảnh | Search + Detail | Trung bình |
| Rent Zestimate | Detail | Trung bình |
| Giao dịch so sánh gần đó | Detail | Khó |
Các trường “khó” — như lịch sử giá, lịch sử thuế, giao dịch so sánh — nằm trong JSON lồng nhau ở trang chi tiết. Phần Python bên dưới sẽ chỉ rõ cách trích xuất chúng. Và nếu bạn muốn bỏ qua luôn phần code, AI Suggest Fields của Thunderbit có thể tự phát hiện hầu hết các cột này, còn Scrape Subpages sẽ tự lấy những trường ở trang chi tiết.
Thiết lập môi trường Python để scrape Zillow
Mức độ khó: Trung cấp
Thời gian cần: ~5 phút để cài đặt, ~30 phút cho toàn bộ hướng dẫn
Bạn cần gì: Python 3.8+, trình duyệt Chrome (để kiểm tra trang), một trình soạn thảo văn bản hoặc IDE
Cài các thư viện cần thiết:
pip install httpx beautifulsoup4 pandas lxml
Vai trò của từng thư viện:
- httpx — HTTP client có hiệu năng tốt hơn
requestsvà hỗ trợ async - beautifulsoup4 + lxml — phân tích HTML
- pandas — xuất dữ liệu ra CSV/Excel
- Tùy chọn: selenium hoặc playwright nếu bạn cần render các trang nặng JavaScript
Hiểu cấu trúc trang của Zillow trước khi scrape

Đây là điều quan trọng nhất bạn cần nắm trước khi viết code. Zillow là một ứng dụng Next.js — điều này đã được xác nhận qua các bài đăng kỹ thuật từ nhân viên Zillow. Điều đó có nghĩa là phần lớn dữ liệu bạn cần không nằm trong HTML hiển thị bên ngoài. Nó được nhúng trong một blob JSON ở thẻ <script id="__NEXT_DATA__">.
Hãy mở bất kỳ trang bất động sản nào của Zillow, nhấn F12, vào tab Elements và tìm __NEXT_DATA__. Bạn sẽ thấy một đối tượng JSON rất lớn chứa toàn bộ dữ liệu tin đăng — giá, tọa độ, chi tiết bất động sản, lịch sử giá, hồ sơ thuế, xếp hạng trường học, v.v.
Vì sao điều này quan trọng? Tên class CSS của Zillow được băm ngẫu nhiên (tạo bằng styled-components) và thay đổi mỗi khi họ deploy. Một class như StyledPropertyCardHomeDetailsList-c11n-8-109-3__sc-1j0som5-0 có thể có hash hoàn toàn khác vào tuần sau. Bất kỳ scraper nào phụ thuộc vào CSS selector đều sẽ dễ hỏng liên tục.
Cách lấy dữ liệu từ JSON __NEXT_DATA__ ổn định hơn nhiều vì nó không phụ thuộc vào cấu trúc HTML.
Các đường dẫn JSON quan trọng cho trang kết quả tìm kiếm:
| Path | Nội dung |
|---|---|
props.pageProps.searchPageState.cat1.searchResults.listResults | Mảng kết quả tìm kiếm |
props.pageProps.searchPageState.cat1.searchResults.mapResults | Kết quả hiển thị trên bản đồ |
props.pageProps.searchPageState.cat1.searchList.totalPages | Tổng số trang khả dụng |
Ở trang chi tiết, một số trang dùng __NEXT_DATA__ và một số khác dùng thẻ script thay thế tên là hdpApolloPreloadedData. Đoạn code bên dưới sẽ xử lý cả hai.
Từng bước: Cách scrape Zillow bằng Python
Bước 1: Thiết lập HTTP headers để tránh bị chặn ngay lập tức
Gửi một httpx.get() trống trơn tới Zillow gần như chắc chắn sẽ chỉ nhận về trang CAPTCHA chứ không phải dữ liệu tin đăng. Zillow sử dụng PerimeterX (HUMAN Security) cùng với Cloudflare — cả hai đều được đánh giá độ khó 8/10 trong các benchmark scraping. Hệ thống sẽ kiểm tra TLS fingerprint, HTTP headers và độ uy tín IP của bạn.
Dưới đây là bộ headers tối thiểu có thể hoạt động tính đến năm 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",
}
Các header Sec-Ch-Ua là cực kỳ quan trọng. Nhiều bài hướng dẫn bỏ qua chúng — và đó chính là lý do code trong các bài đó không thể qua nổi PerimeterX.
Bước 2: Scrape trang kết quả tìm kiếm Zillow
URL tìm kiếm của Zillow có cấu trúc khá dễ đoán. Ví dụ với Houston, TX:
- Trang 1:
https://www.zillow.com/houston-tx/ - Trang 2:
https://www.zillow.com/houston-tx/2_p/ - Trang 3:
https://www.zillow.com/houston-tx/3_p/
Mỗi trang có khoảng 41 tin đăng. Zillow giới hạn tối đa 20 trang (~820 tin). Với tập dữ liệu lớn hơn, bạn sẽ cần chia theo khu vực địa lý (phần sau sẽ nói rõ hơn).
Dưới đây là code để scrape kết quả tìm kiếm bằng cách trích xuất dữ liệu từ JSON __NEXT_DATA__:
from bs4 import BeautifulSoup
import json
import time
import random
def scrape_zillow_search(url):
"""Scrape dữ liệu tin đăng từ một trang kết quả tìm kiếm của Zillow."""
response = httpx.get(url, headers=headers, timeout=15)
if response.status_code != 200:
print(f"Nhận mã trạng thái {response.status_code} cho {url}")
return []
soup = BeautifulSoup(response.text, "lxml")
script_tag = soup.find("script", {"id": "__NEXT_DATA__"})
if not script_tag:
print("Không tìm thấy __NEXT_DATA__ — có thể đã bị CAPTCHA chặn")
return []
next_data = json.loads(script_tag.string)
try:
results = (
next_data["props"]["pageProps"]["searchPageState"]
["cat1"]["searchResults"]["listResults"]
)
except KeyError:
print("Cấu trúc JSON không như mong đợi — có thể Zillow đã thay đổi định dạng")
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 nhiều trang, hãy lặp qua các trang với độ trễ:
all_listings = []
base_url = "https://www.zillow.com/houston-tx/"
for page in range(1, 6): # 5 trang đầu tiên
url = base_url if page == 1 else f"{base_url}{page}_p/"
print(f"Đang scrape trang {page}...")
page_listings = scrape_zillow_search(url)
all_listings.extend(page_listings)
# Delay ngẫu nhiên 3-7 giây
delay = random.uniform(3, 7)
time.sleep(delay)
print(f"Tổng số tin đăng đã scrape: {len(all_listings)}")
Bạn sẽ thấy dữ liệu tin đăng được gom lại trong all_listings. Nếu nhận được kết quả rỗng, hãy xem phần “Vì sao scraper bị hỏng” bên dưới.
Bước 3: Scrape trang chi tiết bất động sản trên Zillow
Trang kết quả chỉ cho bạn phần cơ bản. Trang chi tiết chứa dữ liệu sâu hơn: lịch sử giá, lịch sử thuế, xếp hạng trường học, thông tin đại lý và mô tả bất động sản. Mỗi URL tin đăng từ Bước 2 sẽ dẫn đến một trang chi tiết.
Trang chi tiết Zillow có thể dùng một trong hai định dạng dữ liệu. Dưới đây là code xử lý cả hai:
def scrape_zillow_detail(url):
"""Scrape dữ liệu chi tiết của bất động sản từ một trang tin đăng Zillow."""
response = httpx.get(url, headers=headers, timeout=15)
if response.status_code != 200:
return None
soup = BeautifulSoup(response.text, "lxml")
# Thử __NEXT_DATA__ trước (phổ biến nhất)
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
# Phương án dự phòng: 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):
"""Trích xuất các trường có cấu trúc từ JSON của một bất động sản 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", [])
],
}
Lặp qua các URL tin đăng với khoảng nghỉ:
detail_data = []
for listing in all_listings[:10]: # Bắt đầu với 10 tin để test
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"Đang scrape trang chi tiết: {detail_url}")
detail = scrape_zillow_detail(detail_url)
if detail:
detail_data.append({**listing, **detail})
time.sleep(random.uniform(3, 8))
Sau bước này, bạn sẽ có một danh sách các dictionary chứa cả dữ liệu cấp tìm kiếm lẫn dữ liệu cấp chi tiết cho từng bất động sản.
Bước 4: Xử lý phân trang để scrape nhiều trang
Với các khu vực có hơn 820 tin đăng (giới hạn 20 trang), bạn cần chia nhỏ theo địa lý. API nội bộ của Zillow chấp nhận tham số mapBounds. Chiến lược là chia bản đồ thành các phần tư và scrape từng phần riêng.
def split_bounds(bounds):
"""Chia giới hạn bản đồ thành 4 phần tư."""
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"]},
]
Với hầu hết nhu cầu sử dụng — theo dõi 50–200 tin đăng trong một khu vực cụ thể — phân trang bằng URL tiêu chuẩn là đủ. Cách chia theo phần tư chỉ thật sự cần khi scrape ở quy mô thành phố hoặc toàn bang.
Bước 5: Xuất dữ liệu Zillow đã scrape
Lưu mọi thứ thành CSV bằng pandas:
import pandas as pd
df = pd.DataFrame(detail_data)
df.to_csv("zillow_houston_listings.csv", index=False)
print(f"Đã xuất {len(df)} tin đăng sang zillow_houston_listings.csv")
Xuất sang JSON:
with open("zillow_houston_listings.json", "w") as f:
json.dump(detail_data, f, indent=2)
Nếu bạn muốn bỏ qua luôn bước xuất dữ liệu, Thunderbit có thể xuất miễn phí sang Google Sheets, Airtable và Notion — rất tiện nếu bạn muốn có dữ liệu ở định dạng cộng tác ngay lập tức.
Vì sao scraper Zillow thường bị hỏng (và cách xây dựng scraper bền hơn)
Đây là phần “sổ tay sinh tồn”.
Theo kinh nghiệm của tôi, scraper thường hỏng trên Zillow vì ba lý do cụ thể — và mỗi lý do đều có cách xử lý rõ ràng.
PerimeterX và CAPTCHA: Vì sao request của bạn trả về dữ liệu rỗng
Tích hợp PerimeterX của Zillow kiểm tra đồng thời nhiều tín hiệu: TLS fingerprint, HTTP headers, độ uy tín IP và mẫu request. Khi phát hiện hành vi tự động hóa, hệ thống sẽ trả về trang CAPTCHA kiểu “Press & Hold” thay vì dữ liệu tin đăng.
Tình huống lỗi cụ thể: Bạn gửi request với headers mặc định của Python. HTML phản hồi chứa script thử thách PerimeterX thay vì dữ liệu bất động sản — và BeautifulSoup của bạn không tìm thấy thẻ __NEXT_DATA__.
Cách khắc phục: Dùng bộ headers mô phỏng trình duyệt đầy đủ như ở Bước 1. Nếu bạn thực hiện nhiều hơn vài chục request, bạn cũng cần xoay vòng proxy (phần bên dưới sẽ nói). Với scraping nặng, hãy cân nhắc thư viện như curl_cffi với impersonate="chrome" — đây là HTTP client Python hiếm hoi có thể mô phỏng TLS fingerprint giống Chrome thật.
Dynamic CSS selectors: Vì sao BeautifulSoup trả về None
Nếu bạn đang dùng các CSS selector như .list-card-price hoặc các class có hash, scraper của bạn sẽ hỏng mỗi khi Zillow triển khai code mới.
Zillow dùng styled-components, tạo ra các class như StyledPropertyCardHomeDetailsList-c11n-8-109-3__sc-1j0som5-0. Phần hash sẽ thay đổi sau mỗi lần build.
Cách khắc phục: Đừng dùng CSS selector nữa. Hãy lấy dữ liệu từ blob JSON __NEXT_DATA__ như trong code phía trên. Cách này đã ổn định nhiều năm vì cấu trúc JSON ít thay đổi hơn rất nhiều so với HTML markup.
Nếu buộc phải parse HTML, hãy tìm các thuộc tính data-test (ví dụ: data-test="property-card") hoặc dùng so khớp class theo chuỗi như [class*="PropertyCard"]. Nhưng trích xuất từ JSON vẫn là lựa chọn đáng tin cậy hơn.
Xoay vòng proxy và exponential backoff: code có thể sống sót sau khi bị chặn IP
IP datacenter sẽ bị Zillow đưa vào danh sách chặn ngay lập tức. Bạn cần residential proxy để truy cập ổn định. Tốc độ an toàn: mỗi IP chỉ 1 request trong 3–8 giây, và không vượt quá khoảng 500 request/giờ.
Dưới đây là một decorator retry với exponential backoff và jitter:
import random
import time
def backoff_with_jitter(attempt, base_delay=2, max_delay=60):
"""Exponential backoff kiểu AWS với full jitter."""
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"Bị chặn ({response.status_code}). Thử lại sau {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
Và đây là một pool xoay vòng proxy đơn giản:
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)
# Cách dùng:
pool = ProxyPool(proxies=[
"http://user:pass@residential1.example.com:8080",
"http://user:pass@residential2.example.com:8080",
])
Với nhà cung cấp proxy, DataImpulse có residential proxy khoảng 1 USD/GB — là lựa chọn rẻ nhất — trong khi IPRoyal và Smartproxy là những lựa chọn tầm trung tốt, khoảng 4–7 USD/GB.
Lựa chọn không cần bảo trì
Nếu bạn scrape Zillow thường xuyên và chán việc phải sửa selector hỏng hoặc quản lý pool proxy, AI của Thunderbit sẽ đọc lại cấu trúc trang mới ngay mỗi lần scrape. Không cần bảo trì selector, không cần cấu hình proxy. Nó thực sự giải quyết được cái vấn đề fragility vốn khiến scraper viết code trở thành một vòng lặp mệt mỏi.
Tự động hóa scrape Zillow: lập lịch và theo dõi giá
Hầu như mọi nhà đầu tư bất động sản tôi từng nói chuyện đều muốn điều này, nhưng không có nhiều hướng dẫn scrape Zillow nào đề cập đến: các lần scrape tự động định kỳ để theo dõi giá.
Với người dùng Python: cron job và phát hiện thay đổi giá
Thiết lập một cron job chạy scraper hàng tuần và đánh dấu những biến động giá:
import pandas as pd
from datetime import datetime
def detect_price_changes(new_data, historical_file, threshold=0.05):
"""So sánh lần scrape mới với dữ liệu lịch sử, đánh dấu thay đổi vượt ngưỡng."""
try:
old = pd.read_csv(historical_file)
except FileNotFoundError:
new_data.to_csv(historical_file, index=False)
print("Lần chạy đầu tiên — đã lưu dữ liệu nền.")
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]
# Ghi thêm dữ liệu mới kèm timestamp
new_data["scraped_at"] = datetime.now().isoformat()
new_data.to_csv(historical_file, mode="a", header=False, index=False)
return alerts
Thêm đoạn này vào crontab để chạy vào mỗi thứ Hai lúc 6 giờ sáng:
0 6 * * 1 cd /path/to/scraper && python zillow_monitor.py
Một ví dụ thực tế: theo dõi 50 tin đăng ở Austin, TX mỗi tuần. Mỗi thứ Hai, script sẽ scrape giá hiện tại, so sánh với tuần trước và xuất CSV đánh dấu bất kỳ tin nào giảm giá hơn 5%.
Với người không biết code: Thunderbit Scheduled Scraper
Thunderbit Scheduled Scraper cho phép bạn mô tả khoảng thời gian bằng ngôn ngữ tự nhiên (“mỗi thứ Hai lúc 9 giờ sáng”), nhập các URL tìm kiếm Zillow của bạn, rồi bấm Schedule. Dữ liệu sẽ tự động xuất sang Google Sheets sau mỗi lần chạy. Không cần Python, không cần cron, không cần tự quản lý server. Đây là giải pháp đặc biệt hữu ích cho môi giới bất động sản hoặc đội vận hành cần theo dõi giá đều đặn mà không có đội kỹ thuật hỗ trợ.
Mẹo scrape Zillow một cách có trách nhiệm
Một vài lưu ý để giữ mọi thứ trong giới hạn phù hợp:
- Chỉ scrape dữ liệu công khai. Đừng truy cập các trang yêu cầu đăng nhập hay xác thực.
- Dùng tốc độ request hợp lý. Giữ khoảng 3–8 giây giữa các request. Đừng bắn quá dồn dập lên server.
- Đừng scrape dữ liệu cá nhân/riêng tư của người dùng. Tên đại lý và thông tin môi giới trên tin đăng là công khai; dữ liệu tài khoản người dùng thì không.
- Lưu trữ và sử dụng dữ liệu một cách có đạo đức. Nghiên cứu thị trường, phân tích đầu tư và tạo khách hàng tiềm năng là các mục đích hợp lệ. Spam thì không.
- Bối cảnh pháp lý: Phán quyết hiQ v. LinkedIn khẳng định rằng việc scrape dữ liệu công khai không vi phạm CFAA. Phán quyết Meta v. Bright Data (2024) cũng củng cố các nguyên tắc tương tự. Tuy vậy, điều khoản sử dụng của Zillow vẫn hạn chế truy cập tự động, và họ thực thi bằng cách chặn IP và CAPTCHA hơn là khởi kiện. Luôn kiểm tra hướng dẫn hiện hành và tôn trọng robots.txt.
Chọn hướng phù hợp để scrape Zillow bằng Python
Cách tốt nhất sẽ phụ thuộc vào tình huống của bạn:
Cần dữ liệu nhanh, không muốn code? Thunderbit có thể đưa bạn từ trang tìm kiếm Zillow đến một bảng tính có cấu trúc chỉ trong khoảng 2 phút. AI tự thích ứng với thay đổi layout, xử lý phân trang và xuất dữ liệu miễn phí. Cài Chrome Extension và thử ngay trên một trang tìm kiếm Zillow.
Muốn toàn quyền kiểm soát? Dùng đoạn code Python trong bài này. Hãy trích xuất từ JSON __NEXT_DATA__ (không phải CSS selector) để tăng độ ổn định. Thiết lập headers mô phỏng trình duyệt đúng chuẩn. Xoay vòng residential proxy và dùng exponential backoff để đảm bảo độ tin cậy.
Cần scale lớn hơn? Các scraping API như ScrapFly (tỷ lệ thành công 99% trên Zillow) hoặc ScraperAPI sẽ lo phần proxy và CAPTCHA cho bạn, với chi phí 30–599 USD/tháng tuỳ khối lượng.
Theo dõi giá theo thời gian? Thiết lập cron job với script phát hiện thay đổi giá, hoặc dùng Scheduled Scraper của Thunderbit để có cách làm không cần bảo trì.
Dữ liệu thì đã ở đó rồi. Câu hỏi duy nhất là bạn muốn bỏ bao nhiêu công sức kỹ thuật để lấy nó ra. Để tìm hiểu thêm về cách đưa dữ liệu web vào bảng tính, hãy xem hướng dẫn của chúng tôi về scraping dữ liệu từ website sang Excel hoặc bản tổng hợp thống kê Zillow để xem dữ liệu nền tảng mới nhất. Bạn cũng có thể xem các video hướng dẫn trên kênh YouTube của Thunderbit.
Dùng thử Thunderbit cho việc scrape Zillow Get Started Free
Câu hỏi thường gặp
Có thể scrape Zillow bằng Python miễn phí không?
Có — httpx, BeautifulSoup và pandas đều miễn phí và mã nguồn mở. Đổi lại, bạn sẽ phải tự quản lý headers, xoay vòng proxy và bảo trì selector. Hãy chuẩn bị dành 4–8 giờ cho lần thiết lập ban đầu và 4–10 giờ mỗi tháng cho việc bảo trì khi Zillow thay đổi website. Thunderbit cũng có gói miễn phí nếu bạn muốn tránh hoàn toàn phần gánh nặng viết code.
Zillow có API chính thức không?
Zillow đã ngừng API công khai miễn phí vào tháng 9 năm 2021. Hiện việc truy cập phải thông qua Bridge Interactive, cần được phê duyệt, có giá khoảng 500 USD/tháng và hướng tới các chuyên gia bất động sản có лицензия. Với phần lớn người dùng — nhà đầu tư, nhà nghiên cứu, môi giới làm phân tích thị trường — scraping là lựa chọn thực tế hơn. Zillow vẫn công bố một số dữ liệu nghiên cứu miễn phí dưới dạng CSV tải về tại zillow.com/research/data/, bao gồm Zillow Home Value Index và Zillow Observed Rent Index.
Làm sao để tránh bị chặn khi scrape Zillow?
Có ba việc: (1) dùng headers trình duyệt trông thật, bao gồm Sec-Ch-Ua — đây là header mà hầu hết hướng dẫn hay bỏ sót, và cũng là thứ PerimeterX kiểm tra đầu tiên; (2) xoay vòng residential proxy — IP datacenter sẽ bị chặn ngay; (3) lấy dữ liệu từ JSON __NEXT_DATA__ thay vì selector HTML để tránh lỗi do layout thay đổi. Giữ tốc độ ở mức 1 request mỗi 3–8 giây trên mỗi IP. Hoặc dùng công cụ như Thunderbit, vốn tự xử lý chống bot.
Cách tốt nhất để scrape Zillow mà không cần code là gì?
AI Web Scraper của Thunderbit là đường đi nhanh nhất. Cài Chrome Extension, mở trang tìm kiếm Zillow, bấm “AI Suggest Fields” để tự nhận diện cột, rồi bấm “Scrape.” Xuất sang Google Sheets, Excel, Airtable hoặc Notion mà không cần viết code. AI đọc lại trang từ đầu mỗi lần, nên không bị hỏng khi Zillow cập nhật layout.
Zillow thay đổi cấu trúc website thường xuyên thế nào, và điều đó ảnh hưởng gì đến scraper?
Zillow triển khai cập nhật khá thường xuyên — đôi khi là hàng tuần. Vì họ dùng styled-components, tên class CSS sẽ thay đổi sau mỗi lần deploy, khiến scraper dựa vào CSS selector dễ hỏng liên tục. Cách bền nhất trong Python là lấy dữ liệu từ blob JSON __NEXT_DATA__, vốn thay đổi cấu trúc ít hơn nhiều. Nếu muốn không phải bảo trì, AI của Thunderbit sẽ đọc lại cấu trúc trang ở mỗi lần scrape và tự thích ứng với thay đổi layout.
Tìm hiểu thêm


