PythonでZillowをスクレイピングする方法(ブロックされないコツ付き)

最終更新日 June 9, 2026
PythonでZillowをスクレイピングする方法(ブロックされないコツ付き)

物件のデータが欲しくてZillowにアクセスしたのに、返ってきたのはCAPTCHAページだった——そんな苦い経験に覚えがあるなら、つまずいているのは決してあなた一人ではありません。なにしろZillowには米国の不動産レコードが1億6,000万件も蓄えられており、これをまとめて取り出す作業は、不動産データの仕事の中でも引き合いが多く、かつ最も骨が折れるタスクの代表格なのです。

私はこれまで、Zillowスクレイピングのやり方を片っ端から掘り下げ、検証してきました。Pythonで一から書く方法と、私たちがThunderbitで仕立てたノーコードツールを使う方法、その両方をです。本稿ではどちらの道筋も取り上げます。ボット対策まで踏み込んだPythonの本格手順を求める方にも、昼休み前に200件の物件をスプレッドシートへ流し込みたい方にも、持ち帰れるものがあるはずです。Zillowのデータがなぜ価値を持つのか、サイトの内部はどう組まれているのか、Pythonでの具体的な手順、スクレイパーが壊れる原因、そして価格モニタリングのために定期実行を自動化する手立てまで、ひと通り押さえていきます。

そもそも、なぜ Zillow のデータをスクレイピングするのか?

Zillowは、米国の住宅不動産データを束ねる最大級の情報源です。月間の訪問数は2億1,000万回に達し、売り出し中の物件はおよそ75万件超、賃貸物件は190万件が掲載されています。米国の不動産ポータルへの流入のうち5割以上を独り占めしており、これは2番手の競合の2倍を上回る数字です。

zillow_stats_998c14e590.png

Pythonのコードに踏み込む前に、肝に銘じておきたいことがあります。「PythonでZillowをスクレイピングする」やり方だけが唯一の正解ではない、という点です。入り口を間違えると、何時間も棒に振りかねません。たとえばhttpxとBeautifulSoupを組み合わせるPython方式は、中級者向けで、ヘッダーやプロキシを手で管理する必要があり、速度はページあたり1〜3秒と中庸、保守の手間はかさみますが費用はかかりません。SeleniumやPlaywrightはJavaScriptを描画する分ボット対策には強くなる反面、ページあたり5〜15秒と遅く、保守の重さは変わりません。ScraperAPIやScrapFlyのようなスクレイピングAPIは、ボット対策が最初から組み込まれていて速度も出ますが、月額30〜599ドルの出費が伴い、保守は中程度です。Bridge Interactive経由のZillow公式APIは、速くて保守も軽い一方、利用に制限があり、料金は月500ドル前後。そしてThunderbitに代表されるノーコードツールは、初心者にも扱いやすく動作も速く、AIがページの変化に追従するため保守が要らず、しかも多くはフリーミアムで使えます。

時間の節約という点でも効果は絶大です。50を超える郵便番号エリアを手作業で当たれば、週に15〜20時間はたちまち溶けてなくなります。これを自動スクレイピングに任せれば、同じ作業がほんの数分で片づき、時間コストを99.7%も圧縮できる計算になります。

Zillow をスクレイピングする方法:Python・API・ノーコードを比較

繰り返しになりますが、Pythonのコードに入る前に、「PythonでZillowをスクレイピングする」だけが手段ではないと心得ておきましょう。選び方をしくじれば、それだけで何時間も無駄になります。まずは下の比較表で全体像を掴み、自分の状況に合う方法を選んでください。

方法スキルレベルボット対策速度保守コスト
Python + httpx/BeautifulSoup中級者向け手動(ヘッダー、プロキシ)中程度(1〜3秒/ページ)高い(セレクタが壊れやすい)無料
Python + Selenium/Playwright中級者向けより強い(JS を描画)遅い(5〜15秒/ページ)高い無料
スクレイピング API(ScraperAPI、ScrapFly)中級者向け組み込み速い中程度月額 $30〜599
Zillow 公式 API(Bridge Interactive)初級〜中級者該当なし速い低い約 $500/月、利用制限あり
ノーコードツール(Thunderbit初級者組み込み(AI が適応)速いほぼ不要(AI がページを再認識)フリーミアム

コードを書かずに今すぐデータが必要なら、まずはThunderbitから入るのが手っ取り早いです。一方、仕組みそのものを理解したい、あるいは細部まで自分でカスタマイズしたいなら、この先のPython解説へ進んでください。

2分でできる方法:Thunderbit で Zillow をスクレイピングする(コード不要)

Pythonの詳しい話に入る前に、今この瞬間にZillowのデータが欲しい人向けのショートカットを紹介します。Pythonの環境構築も、プロキシの設定も、セレクタの保守も、いっさい要りません。不動産データを構造化された形で取り出せるよう、Thunderbitではこのワークフローを念入りに作り込みました。

難易度: 初級 所要時間: 約 2 分 必要なもの: Chrome ブラウザ、Thunderbit Chrome 拡張機能(無料プランで可)

ステップ 1:Thunderbit をインストールして Zillow を開く

まずはChrome Web StoreからThunderbit拡張機能を入れます。続いてZillowの検索結果ページを開きましょう。例として、テキサス州ヒューストンの物件を検索してみてください。

ステップ 2:「AI Suggest Fields」をクリック

Thunderbitのサイドバーを開いて、「AI Suggest Fields」を押します。するとAIがページを読み解き、価格・住所・ベッド数・バス数・面積・Zestimate・物件URLといった列を自動で提案してくれます。私が試した際は、手動の設定を一切せずとも20を超える項目を拾い上げました。

ステップ 3:「Scrape」をクリック

Scrapeボタンを押せば、データが拡張機能内の構造化テーブルに並びます。Zillowのページ送りにもThunderbitは自動で追従するので、クリック式のページネーションでも無限スクロールでも問題ありません。

ステップ 4:サブページも取得して情報を拡張

税履歴や学校評価、価格推移といった詳細ページ側の情報まで欲しいときは、「Scrape Subpages」でテーブルを肉付けします。Thunderbitが物件URLを一つずつたどり、追加の項目を集めてきます。ここでもコードは一切不要です。

ステップ 5:エクスポート

出力先はGoogle Sheets、Excel、Airtable、Notionから選べます。エクスポートはすべて無料です。

Thunderbit が Zillow に強い理由

真価が問われるのは、壊れにくさです。ThunderbitのAIは、スクレイプを実行するたびにページ構造をその場で読み直します。Zillowがレイアウトを変えても——そしてこれは実によく起こります——修正に追われる脆弱なCSSセレクタは存在しません。AIがそのつど自動で適応するため、コード型スクレイパーにつきまとう「すぐ壊れる」という宿痾を、根本から取り除けるのです。

Zillow から取得できるデータは?(20項目以上)

価格と住所を取って終わり——多くのガイドはそこで筆を置きますが、Zillowの物件ページには、実のところもっと多彩なデータが眠っています。参考までに一覧にまとめておきます。

項目取得元抽出難易度
表示価格検索結果 + 詳細ページ簡単
住所 / 郵便番号検索結果 + 詳細ページ簡単
Zestimate検索結果 + 詳細ページ簡単
価格履歴(各イベント)詳細ページ難しい(ネストされた JSON)
税履歴詳細ページ難しい(ネストされた JSON)
ベッド数 / バス数 / 面積検索結果 + 詳細ページ簡単
築年数詳細ページ簡単
HOA 料金詳細ページ中程度
Walk Score / Transit Score詳細ページ(iframe)難しい(JS 描画が必要)
学校評価詳細ページ中程度
敷地面積詳細ページ簡単
Zillow 上の掲載日数検索結果簡単
担当エージェント / 仲介会社検索結果 + 詳細ページ中程度
MLS 番号詳細ページ簡単
物件タイプ検索結果 + 詳細ページ簡単
緯度 / 経度__NEXT_DATA__ JSON中程度
説明文詳細ページ簡単
画像 URL検索結果 + 詳細ページ中程度
Rent Zestimate詳細ページ中程度
近隣の類似売買事例詳細ページ難しい

「難しい」に分類した価格履歴・税履歴・比較売買事例は、いずれも詳細ページ内のネストされたJSONに格納されています。その取り出し方は、後続のPythonセクションで具体的に示します。コードを書きたくない場合でも心配は要りません。ThunderbitのAI Suggest Fieldsがこれらの列の大半を自動で見つけ出し、さらにSubpage Scrapingが詳細ページの項目まで自動で拾ってくれます。

Zillow をスクレイピングするための Python 環境を準備する

難易度: 中級 所要時間: セットアップ約 5 分、チュートリアル全体で約 30 分 必要なもの: Python 3.8 以上、Chrome ブラウザ(ページ確認用)、テキストエディタまたは IDE

まず、必要なライブラリを入れます。

pip install httpx beautifulsoup4 pandas lxml

それぞれが担う役割は次のとおりです。

  • httpxrequests より高性能で、非同期処理にも対応する HTTP クライアント
  • beautifulsoup4 + lxml — HTML パース用
  • pandas — CSV / Excel への出力用
  • 必要に応じて selenium または playwright — JavaScript が多いページを描画したい場合

スクレイピング前に Zillow のページ構造を理解する

zillow_structure_046e848770.png

コードに手をつける前に、ここを押さえておくことが何より大切です。ZillowはNext.js アプリケーションとして作られており、これはZillow 社員のエンジニアリング投稿でも裏付けられています。何を意味するかというと、欲しいデータの多くは目に見えるHTML要素の側ではなく、<script id="__NEXT_DATA__"> の中のJSONの塊に埋め込まれている、ということです。

試しにZillowの物件ページを開き、F12を押してElementsを表示し、__NEXT_DATA__ を検索してみてください。価格、座標、物件の詳細、価格履歴、税情報、学校評価まで、物件データ一式をまるごと抱えた巨大なJSONオブジェクトが目に飛び込んでくるはずです。

では、なぜこれが効いてくるのでしょうか。理由は、Zillow の CSS クラス名がハッシュ化されている点にあります(styled-componentsが生成します)。これらは公開のたびに姿を変えます。たとえば StyledPropertyCardHomeDetailsList-c11n-8-109-3__sc-1j0som5-0 のようなクラス名は、来週にはまるで別のハッシュに化けているでしょう。CSSセレクタを頼りにしたスクレイパーが、周期的に壊れていく所以です。

その点、__NEXT_DATA__ のJSONを使うやり方は、HTML構造に縛られないため、格段に安定しています。

検索結果でよく使うことになるJSONパスは、以下のとおりです。

パス内容
props.pageProps.searchPageState.cat1.searchResults.listResults検索結果の配列
props.pageProps.searchPageState.cat1.searchResults.mapResults地図表示の結果
props.pageProps.searchPageState.cat1.searchList.totalPages利用可能な総ページ数

詳細ページについては、__NEXT_DATA__ を使うケースもあれば、hdpApolloPreloadedData という別のscriptタグを使うケースもあります。以下のコードは、その双方に対応させてあります。

Python で Zillow をスクレイピングする手順

ステップ 1:即ブロックを避けるために HTTP ヘッダーを設定する

素のままの httpx.get() をZillowへ投げると、物件データではなくCAPTCHAページが戻ってきます。ZillowはCloudflareに加えて**PerimeterX(HUMAN Security)**を組み込んでおり、スクレイピングの難易度は8/10と評価されるほどです。この仕掛けは、TLSフィンガープリント、HTTPヘッダー、IPの信頼度を一つひとつ照合します。

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 の検索結果をスクレイピングする

Zillowの検索URLは、規則性のある読みやすいパターンになっています。テキサス州ヒューストンを例に取ると、次のとおりです。

  • 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/

1ページにつき物件は約41件。そしてZillowは結果を20ページ(およそ820件)でぴたりと打ち切ります。それ以上の規模のデータが要るなら、対象エリアを分割するしかありません(後ほど触れます)。

下記が、__NEXT_DATA__ のJSONからデータを抜き出して検索結果をスクレイピングするコードです。

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

複数ページをまとめて取得したいときは、間隔を空けながらループを回します。

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 に、構造化された物件データが順々に積み上がっていくはずです。もし空の結果ばかりが返ってくるなら、後述の「スクレイパーが壊れる理由」に目を通してください。

ステップ 3:Zillow の物件詳細ページをスクレイピングする

検索結果から得られるのは、あくまで基本情報どまりです。価格履歴、税履歴、学校評価、担当エージェントの情報、物件の説明文といった一段深いデータは、詳細ページの側に収まっています。ステップ2で拾った物件URLは、それぞれこの詳細ページを指しています。

Zillowの詳細ページには2通りのデータ形式があり、以下のコードはそのどちらにも対応します。

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):
    """Zillow の物件 JSON から構造化データを取り出す。"""
    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))

この処理を終えると、検索レベルと詳細レベルの両方のデータを兼ね備えた、辞書のリストが手元に出来上がります。

ステップ 4:ページネーションを処理して複数ページをスクレイピングする

820件(20ページの上限)を超えるエリアでは、地理的に区切る必要が出てきます。Zillowの内部APIは mapBounds パラメータを受け付けるので、地図を四つの区画に割り、それぞれを個別にスクレイピングする、という戦略が取れます。

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件を見張るような大半のケースでは、通常のURLページネーションで事足ります。四分割の手法は、市全体や州全体をまるごと対象に取るような場面で初めて出番が来ます。

ステップ 5:Zillow のスクレイピングデータをエクスポートする

pandasを使ってCSVに書き出します。

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")

JSON形式で残したい場合は、こちらです。

with open("zillow_houston_listings.json", "w") as f:
    json.dump(detail_data, f, indent=2)

エクスポートの手間そのものを省きたいなら、ThunderbitがGoogle Sheets・Airtable・Notionへ無料で書き出してくれます。データをすぐ共同編集できる形で扱いたいときに重宝します。

Zillow スクレイパーが壊れる理由と、壊れにくく作る方法

ここからは“サバイバルガイド”です。

私自身の経験を振り返ると、Zillowでスクレイパーが壊れる原因は大きく3つに集約され、そのどれにも明快な打ち手があります。

PerimeterX と CAPTCHA:なぜ空のデータが返ってくるのか

ZillowのPerimeterXは、TLSフィンガープリント、HTTPヘッダー、IPの信頼度、リクエストの挙動を、同時並行で監視します。自動化された動きだと見なされた瞬間、物件データの代わりに「Press & Hold」式のCAPTCHAページが突き返されます。

典型的な失敗パターン: デフォルトのPythonヘッダーのままリクエストを送ると、返ってくるHTMLには物件データではなくPerimeterXのチャレンジスクリプトが仕込まれています。その結果、BeautifulSoupで解析しても __NEXT_DATA__ タグが見当たらない、という事態に陥ります。

対策: ステップ1で示した、ブラウザを模した完全なヘッダーを使ってください。加えて、数十件を超えるリクエストを飛ばすなら、プロキシのローテーションも欠かせません(後述)。大量のスクレイピングを行う場合は、impersonate="chrome" を指定できる curl_cffi のようなライブラリも候補に入れましょう。本物のChromeに近いTLSフィンガープリントを再現できる、Python製HTTPクライアントの中でも頼れる一手です。

動的 CSS セレクタ:なぜ BeautifulSoup が None を返すのか

.list-card-price のようなCSSセレクタや、ハッシュ込みのクラス名に頼っていると、Zillowが新しいコードをデプロイするたびにスクレイパーは息絶えます。

Zillowはstyled-componentsを採用しており、StyledPropertyCardHomeDetailsList-c11n-8-109-3__sc-1j0som5-0 のようなクラス名を吐き出します。このハッシュの部分が、ビルドのたびに入れ替わるのです。

対策: 先ほどのコードで示したとおり、CSSセレクタには頼らず、__NEXT_DATA__ のJSONブロックから取り出してください。この方法なら、HTMLマークアップよりもJSON構造のほうが変化に乏しいため、何年にもわたって安定し続けます。

どうしてもHTMLを解析せざるを得ないなら、data-test 属性(例: data-test="property-card")を手がかりにするか、[class*="PropertyCard"] のような部分一致を使う手があります。とはいえ、信頼性で言えばJSON抽出に軍配が上がります。

プロキシローテーションと指数バックオフ:IP ブロックに耐えるコード

Zillowは、データセンター系のIPを問答無用でブラックリスト入りさせます。腰を据えて安定アクセスしたいなら、住宅用プロキシが必須です。安全圏とされるペースは、1つのIPにつき3〜8秒に1リクエスト、かつ1時間あたりおよそ500リクエスト以下です。

下記は、指数バックオフとジッターを組み込んだ再試行デコレータです。

import random
import time

def backoff_with_jitter(attempt, base_delay=2, max_delay=60):
    """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

ごく簡単なプロキシのローテーションプールも、こうして用意できます。

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",
])

プロキシの調達先としては、DataImpulse が住宅用プロキシをおよそ$1/GBで出しており、最安の部類です。中堅どころでは、$4〜7/GBのIPRoyalやSmartproxyも使い勝手のよい選択肢になります。

保守ゼロの代替案

Zillowを定期的にスクレイピングしていて、壊れたセレクタの手直しやプロキシプールの面倒に嫌気が差しているなら、ThunderbitのAIはスクレイプのたびにページ構造を新しく読み込み直します。セレクタの保守も、プロキシの設定も要りません。コード型スクレイパーを長年悩ませてきた脆さの問題に、実際に終止符を打ってくれます。

Zillow スクレイピングを自動化する:スケジューリングと価格モニタリング

私が話を聞いた不動産投資家は、誰もがこれを欲しがっていました。それでいて、ほかのZillowスクレイピングガイドはここまで踏み込んでくれません。価格を追いかけるための、定期自動スクレイピングです。

Python ユーザー向け:cron と価格変動検出

スクレイパーを毎週走らせるcronジョブを組み、価格の動きを検知させます。

import pandas as pd
from datetime import datetime

def detect_price_changes(new_data, historical_file, threshold=0.05):
    """新しいスクレイプ結果と履歴データを比較し、しきい値超過の変化を検出する。"""
    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]

    # タイムスタンプ付きで新データを追記
    new_data["scraped_at"] = datetime.now().isoformat()
    new_data.to_csv(historical_file, mode="a", header=False, index=False)

    return alerts

これを毎週月曜の午前6時に動かすには、crontabへ次の一行を加えます。

0 6 * * 1 cd /path/to/scraper && python zillow_monitor.py

実際の使いどころとして、テキサス州オースティンの50件の物件を毎週見張るケースを思い描いてみてください。月曜が来るたびにスクリプトが現在価格を取りに行き、前週の数字と突き合わせ、5%を上回る値下げをCSVに書き出してくれます。

非エンジニア向け:Thunderbit の Scheduled Scraper

ThunderbitのScheduled Scraperを使えば、実行間隔を自然な言葉で指定し(「毎週月曜の9時」など)、Zillowの検索URLを入れて、Scheduleを押すだけです。実行が走るたびに、結果がGoogle Sheetsへ自動で書き出されます。Pythonもcronもサーバーの管理も、いっさい不要です。エンジニアの手を借りずに安定した価格監視を回したい不動産エージェントや運用チームには、とりわけありがたい機能でしょう。

Zillow を責任ある形でスクレイピングするための注意点

一線を越えないために、押さえておきたいポイントをいくつか挙げておきます。

  • 公開されているデータのみを取得する。 ログインが必要なページや認証の裏側には入らない。
  • 無理のないリクエスト頻度にする。 1 リクエストごとに 3〜8 秒空ける。サーバーに負荷をかけすぎない。
  • 個人・非公開データを取得しない。 掲載されているエージェント名や仲介会社情報は公開情報ですが、ユーザーアカウント情報は対象外です。
  • データは倫理的に扱う。 市場調査、投資分析、リード獲得は正当な用途ですが、スパム目的は不適切です。
  • 法的な文脈: hiQ v. LinkedIn の判決は、公開アクセス可能なデータのスクレイピングは CFAA に違反しないことを示しました。Meta v. Bright Data(2024 年)の判決も同様の原則を支持しています。ただし、Zillow の利用規約は自動アクセスを制限しており、法的措置ではなく IP ブロックや CAPTCHA で対処しています。最新のガイダンスを確認し、robots.txt を尊重してください。

Python で Zillow をスクレイピングするなら、最適な方法を選ぼう

何が最適かは、置かれた状況によって変わります。

今すぐデータが欲しい、コードは書きたくない? Thunderbit なら、Zillow の検索ページから構造化されたスプレッドシートまで約 2 分で到達できます。AI がレイアウト変更に対応し、ページネーションも処理し、エクスポートも無料です。Chrome 拡張機能 を入れて、Zillow の検索ページで試してみてください。

完全な制御が欲しい? このガイドの Python コードを使ってください。安定性のため、CSS セレクタではなく __NEXT_DATA__ JSON から取得しましょう。ブラウザを模した適切なヘッダーを設定し、住宅用プロキシと指数バックオフで信頼性を高めます。

大規模化したい? ScrapFly(Zillow で 99% の成功率)や ScraperAPI のようなスクレイピング API なら、プロキシと CAPTCHA の仕組みを代行してくれます。料金は量に応じて月額 $30〜599 です。

時間を追って価格を追跡したい? 価格変動検出スクリプトを使って cron ジョブを設定するか、保守不要の方法として Thunderbit の Scheduled Scraper を使ってください。

データそのものは、すでにそこに揃っています。残る問いは、それを引き出すためにどれだけのエンジニアリング時間を割きたいか、その一点だけです。Webデータをスプレッドシートへ流し込む手立てをもっと知りたい方は、ウェブサイトから Excel へデータをスクレイピングする方法 や、最新のプラットフォーム動向をまとめた Zillow 統計まとめ も合わせてご覧ください。Thunderbit YouTube チャンネル でもチュートリアルを公開しています。

Zillow スクレイピングに Thunderbit を試す Get Started Free

FAQ

Python で Zillow を無料でスクレイピングできますか?

できます。httpx、BeautifulSoup、pandas はいずれも無料のオープンソースです。ただし、そのぶん時間という代償が伴います。ヘッダーの設定、プロキシのローテーション、セレクタの保守を、すべて自分で抱え込むことになるからです。初期セットアップに 4〜8 時間、Zillow がサイトに手を加えた際の月次保守には 4〜10 時間ほどを見積もっておきましょう。コード作業を丸ごと避けたいなら、Thunderbit にも無料プランが用意されています。

Zillow に公式 API はありますか?

Zillow は 2021 年 9 月をもって、無料の公開 API を打ち切りました。現在のアクセス経路は Bridge Interactive 経由で、利用には承認が要り、料金は月 500 ドル前後、対象はライセンスを持つ不動産業者に限られます。投資家や研究者、市場分析に取り組むエージェントなど、それ以外の多くの人にとっては、スクレイピングが現実的な代替手段になります。なお Zillow は、zillow.com/research/data/ にて Zillow Home Value Index や Zillow Observed Rent Index を含む研究用 CSV を無料で公開しています。

Zillow をスクレイピングしてブロックされないようにするには?

押さえるべき勘どころは 3 つです。(1) Sec-Ch-Ua を含む、本物らしいブラウザヘッダーを使うこと。多くのチュートリアルが見落とす項目であり、PerimeterX が真っ先に確認する箇所です。(2) 住宅用プロキシをローテーションすること。データセンター IP はあっという間に弾かれます。(3) HTML セレクタではなく __NEXT_DATA__ JSON から抽出すること。これでレイアウト変更による破損を回避できます。ペースは 1 IP あたり 3〜8 秒に 1 リクエストを目安に。あるいは、Thunderbit のようにボット対策を自動でこなすツールに任せるのも一手です。

コードなしで Zillow をスクレイピングする最善の方法は?

Thunderbit の AI Web Scraper が最も手っ取り早い手段です。Chrome 拡張機能 を入れ、Zillow の検索ページを開いて、「AI Suggest Fields」で列を自動検出させたうえで「Scrape」を押すだけ。Google Sheets、Excel、Airtable、Notion へ、コードなしで書き出せます。AI が毎回ページを新しく読み取るため、Zillow がレイアウトを更新しても壊れにくいのが強みです。

Zillow のサイト構造はどのくらいの頻度で変わり、スクレイパーにどう影響しますか?

Zillow は頻繁に、ときには週単位で手を加えてきます。styled-components を使っている関係で、CSS クラス名はデプロイのたびに変化し、CSS セレクタ頼みのスクレイパーは周期的に壊れます。Python で最も安定するのは、変化の少ない __NEXT_DATA__ JSON ブロックから取得する方法です。保守ゼロで運用したいなら、Thunderbit の AI が毎回ページ構造を読み直し、レイアウト変更へ自動で適応します。

さらに詳しく知る

Shuai Guan
Shuai Guan
Thunderbit の CEO | AIデータ自動化のエキスパート Shuai Guan は Thunderbit の CEO であり、ミシガン大学工学部の卒業生です。テクノロジーと SaaS アーキテクチャの分野で約10年にわたる経験をもとに、複雑な AI モデルを、実務で使えるノーコードのデータ抽出ツールへと落とし込むことを得意としています。このブログでは、ウェブスクレイピングや自動化戦略について、実践で磨かれた率直な知見を共有し、より賢くデータ主導のワークフローを構築できるよう支援しています。データワークフローの最適化から離れているときは、同じこだわりと観察眼を写真への情熱にも注いでいます。
目次
Thunderbit · AI web data agent

Extract data from any page in 1 click

Trusted by 250,000+ users
free plan available
AIでデータを抽出
Google Sheets、Airtable、Notionへ簡単にデータを移行できます
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week