PythonでGlassdoorをスクレイピングする方法:求人・給与・レビューを取得する

最終更新日 June 9, 2026
PythonでGlassdoorをスクレイピングする方法:求人・給与・レビューを取得する

開発者フォーラムをのぞくと、「このスクレイパー、なんで急に動かなくなったの?」 という嘆きが延々と連なっています。2022年には問題なく動いていたGlassdoorスクレイパーが、いまや403ばかり突き返してくる——もしあなたが同じ壁にぶつかっているとしても、孤立しているわけではありません。

ではなぜこうなったのか。答えは、Glassdoor側が大きく様変わりしたからです。2025年7月、Recruit HoldingsはGlassdoorをIndeedへ統合し、あわせて1,300人の人員削減に踏み切りました。ボット対策も一段と引き締められ、素のSeleniumやrequestsで組んだスクレイパーは、HTMLの最初の1バイトを受け取る前に弾き返されるありさまです。さらに2026年2月の時点では、GlassdoorのログインがすべてIndeed Login経由に切り替わっており、Glassdoor固有のログインフォームを前提にした旧来のチュートリアルは、出発点からして成立しなくなっています。とはいえ、プラットフォームの内側には今もなお2.5百万社にわたる1.8億件超のレビュー、給与、インサイトが眠っています。きちんと取り出せさえすれば、HRのベンチマークにも、競合調査にも、営業リードの発掘にも、これ以上ない宝の山です。この記事は、ここまで挙げた変化をすべて織り込んだ「2025年でも現役で動く」版として用意しました。求人・レビュー・給与の3カテゴリを一本にまとめ、今動くPythonコード、ブロックの引き金になる要因とその対処法、そして実装ごとスキップしたい人に向けたノーコードの選択肢まで取り上げます。

2025年にPythonでGlassdoorをスクレイピングする理由

Glassdoorを単なる求人サイトと侮ってはいけません。Web上でも有数の企業インテリジェンス・データセットであり、Fortune 500企業の約3分の1が利用し、月間およそ5,500万人のユニーク訪問者を引き寄せています。ページの奥に控えるデータは、部門をまたいで現場の意思決定を下支えしているのです。

Glassdoorデータが活躍する代表的な場面を、次にまとめました。

ユースケース必要なデータ主な利用部門
給与ベンチマーク給与分布、サンプル数HR、報酬制度、オペレーション
競合の採用動向把握求人一覧、掲載頻度営業、戦略、VC/Corp Dev
企業ブランドのモニタリングレビュー本文、評価トレンド、CEO承認率HR、マーケティング、広報
リード獲得(成長企業の発見)求人一覧 + 企業情報営業チーム、SDR
市場調査・学術研究3種類すべてアナリスト、コンサルタント、研究者

glassdoor_stats_00937e478e.png

このデータがどれほど重く受け止められているかは、ひとつの出来事が物語っています。2025年10月の米国政府閉鎖でBLSが雇用統計を出せなくなった際、Glassdoorの経済調査チームは自社データを基にした代替雇用レポートを世に送り出したのです。

Pythonが今なお王道であり続けるのは、エコシステムの懐が桁違いに深いからです。Playwrightによるブラウザ自動化、parsel/lxmlによる解析、curl_cffiによるTLSフィンガープリント回避と、選択肢に事欠きません。つまり、つまずきの原因はPythonにあるのではなく、Glassdoorのスクレイピング難易度そのものが跳ね上がった点にあります。

AIでGlassdoorデータをスクレイピング Get Started Free

Glassdoorのデータをノーコードで手に入れたいなら、Thunderbitという手があります。自前のPythonスタックを組んで守り続けなくても、求人・レビュー・給与の各ページをそのままスクレイピングできます。

そもそもGlassdoorでは何がスクレイピングできるのか?

世に出回るチュートリアルの大半は、求人一覧しか相手にしません。ところがフォーラムの投稿やGitHubのIssue、Redditの質問を丹念に追っていくと、本当に渇望されているのは、誰もまともに解説しない レビュー と 給与 だと分かります。3つのカテゴリそれぞれで取れる項目を、以下に並べてみました。

求人一覧

3つの中ではいちばん攻略しやすいデータです。職種名、会社名、勤務地、給与目安、企業評価、掲載日、Easy Applyバッジ、求人リンクなどが手に入ります。ログインなしでも一部は閲覧できますが、数ページめくった先でGlassdoorがログインポップアップを差し込んでくることがあります。

企業レビュー

企業ブランド分析の核心がここです。総合評価、各種サブ評価(ワークライフバランス、文化・価値観、多様性と包摂、キャリア機会、報酬・福利厚生、経営陣)、長所、短所、投稿者の職種、レビュー日、在籍状況といった項目が取得対象になります。ただしレビュー全文はログイン必須で、冒頭の抜粋までは見えても、続きを読むには認証が要ります。

給与データ

最も要望が集まり、それでいて最も手強いのがこのデータです。職種名、基本給レンジ、総報酬レンジ、給与レポート数、勤務地などを取れますが、給与ページは丸ごとログイン必須。しかも「貢献して解除」の仕組みが重なり、自分の給与を差し出さないと他人の給与が見られないケースすらあります。これに実用コードで応えている競合記事はまず見当たりませんが、本記事ではこの課題に正面から取り組みます。

ログイン必須かどうかの違い

下の表に目を通せば、どのページが空データに陥りやすいかで悩まずに済みます。

データ種類ログインなしで取得可能?補足
求人タイトル・基本情報ほぼ可数ページ後にポップアップが出る場合あり
完全な求人詳細一部のみ2〜3回閲覧後に制限されることが多い
企業レビュー(全文)いいえ、ログイン必須抜粋は表示、全文は制限
給与データいいえ、ログイン必須"contribute to unlock" が求められる場合あり

なぜ昔のGlassdoorスクレイパーは壊れている可能性が高いのか

glassdoor_defense_82a26cd8bd.png

包み隠さず言いましょう。2021〜2023年頃のチュートリアルをそのまま貼り付けても、もう動きません。GitHubで最もスターを集めてきた旧式のGlassdoor Seleniumスクレイパー(MatthewChatham/glassdoor-review-scraper、約1.4kスター)には、「Glassdoorの新UI」「Cloudflareのボット対策」「NoSuchElementException」といった未解決Issueが12件以上ぶら下がったまま、事実上の塩漬け状態です。Apifyのglassdoor-jobs-scraper actorもDEPRECATED扱いになっています。ScrapeOpsはGlassdoorのスクレイピング難易度を総合9/10 、回避難易度を8/10と見積もっています。

何がどう変わり、古いコードがなぜ崩れるのか。整理すると、こうなります。

防御レイヤー変更点旧スクレイパーへの影響
Cloudflare Bot Management2024年以降、JA3/JA4フィンガープリント検知が厳格化単純なrequests/Seleniumは即403
動的なCSSクラス名ビルドごとにクラス名をランダム化古いCSSセレクタが静かに無効化
レート制限 + セッション追跡IP単位・セッション単位の制限が強化少ないページ数でブロックされる
CAPTCHAチャレンジ(Cloudflare Turnstileの可能性)ページ送り時に出やすくなったヘッドレスブラウザが引っかかりやすい
ログイン壁の拡大認証が必要なページが増加給与・レビューが空になる
Indeed Loginへの移行(2026年2月)Glassdoorのログインフォーム自体が置き換え旧DOMを対象にしたコードは完全に無効

Scrapflyの2026年版ガイド は、こんな釘を刺しています。「Glassdoorはブロック率が高いことで知られているため、Pythonコードを実行中に None が返ってきたら、ブロックされている可能性が高い。」2026年3月のDEV.to記事 も歯に衣着せず、こう言い切っています。「requests や httpx による単純なHTTPリクエストは即座にブロックされる。」

このあと紹介する一連の対策——Patchright(Playwrightのステルス派生版)、data-test属性のセレクタ、ローテーション式の住宅系プロキシ、認証済みの永続セッション——は、まさにこれら各レイヤーを一つずつ崩すために設計されています。

Glassdoor APIとPythonスクレイピング、最初にどちらを選ぶべきか

「Glassdoor APIを叩けば済む話では?」——フォーラムでお決まりのこの問いに、まず結論を返しておきます。それは無理です。

旧Glassdoor Partner APIは新規申請を受け付けていません。開発者ポータルそのものは残っているものの、HTTP 403を返すだけです。公開レビュー向けのエンドポイントは端から存在せず、そもそもMatthewChathamのスクレイパーが生まれたのも「Glassdoorにはレビュー用APIがない」という事情ゆえでした。IndeedのPublisher APIにも、レビューや給与を引き継ぐ仕掛けは用意されていません。

正直なところを比較すると、こうなります。

項目Glassdoor Partner API v1PythonスクレイピングThunderbit(ノーコード)
アクセス新規申請不可可(自分で実装)Chrome拡張
求人一覧限定的 / 終了傾向工夫すれば可可
企業レビュー公開APIは存在しない可(ログイン必要)可(Browser Mode)
給与データ公開APIは存在しない可(ログイン必要)可
レート制限非公開自分で制御クレジット制
初期設定の手間新規登録不可数時間〜数日約2分
保守負担対象外大きい(HTML変更で壊れる)小さい(AIがフィールドを再提案)

レビューや給与のデータが欲しい——この記事にたどり着いた人の多くはそうでしょう——なら、現実に取りうる道は、Pythonスクレイピングかノーコードツールの二択になります。

始める前に

  • 難易度: 中級者向け(Pythonとターミナル操作に慣れていること)
  • 所要時間: 初回セットアップで約30〜60分、その後はデータ種別ごとに約10分
  • 必要なもの:
    • Python 3.10以上(3.11または3.12推奨)
    • Chromeブラウザのインストール
    • Glassdoorアカウント(無料。給与・レビュー取得に必要)
    • ローテーション式の住宅系プロキシ(数ページ以上スクレイピングする場合)
    • 任意: ノーコードで進めたいなら Thunderbit Chrome拡張機能

2025年にPythonでGlassdoorをスクレイピングするためのツールとライブラリ

ツール周りの様子は、かつてとは別物です。今のGlassdoorの守りを相手に、実戦で通用する構成を見ていきましょう。

GlassdoorにPatchrightが最適な理由

Patchright はPlaywrightのステルス派生版で、Cloudflare保護下のサイトで素のPlaywrightをつまずかせる元凶——Runtime.Enable のCDPリーク——を塞いでいます。APIはPlaywrightと共通なので、Playwrightを触ったことがあればそのまま乗りこなせます。最新版は1.58.2(2026年3月)で、手入れも途切れていません。

ほかの選択肢と並べると、違いはこうです。

  • 素のPlaywright: Runtime.Enableのリークにより、Glassdoorのログインページで検知されやすい
  • Selenium + undetected-chromedriver: undetected-chromedriverの最終リリースは2024年2月で、もはや旧世代。Scrapflyのベンチマークでは「テストしたすべてのドメインで失敗」とされています
  • requests + BeautifulSoup: JavaScriptを描画できず、CloudflareのTLSフィンガープリントで即ブロック
  • curl_cffi: 初期HTMLに __NEXT_DATA__ が含まれるページなら、高速経路としてかなり優秀(ブラウザより10〜20倍速い)ですが、ログインや中間チャレンジには対応できません

補助ライブラリ

  • parsel(1.11.0)または lxml(6.0.4): 高速なHTML/XPath解析
  • csv または pandas: データ書き出し
  • asyncio: より速いページ送りのための非同期処理

プロキシは住宅系一択

GlassdoorのCloudflare層は、データセンター系ASNに対して容赦なくチャレンジを浴びせます。住宅系プロキシならブロック率が20〜30%から5%未満まで下がるという報告も出ています。スタート価格の目安は、IPRoyalのプロモーションで1.75ドル/GB前後、あるいはDecodoで3.00ドル/GB前後。本番で回すなら、ボリュームに応じて3〜8ドル/GBを見込んでおくのが堅実です。

加えて、プロキシの質がどうであれ、リクエストとリクエストの間にはランダムな遅延(最低3〜8秒、長丁場なら5〜15秒)を必ず挟みましょう。

Step 1: Python環境をセットアップする

プロジェクト用のフォルダを切り、推奨スタックを入れていきます。

mkdir glassdoor-scraper && cd glassdoor-scraper
python3.11 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip

# Core stack
pip install patchright==1.58.2 parsel==1.11.0

# Install browser binaries
patchright install chromium

# Optional: fast path for __NEXT_DATA__ extraction
pip install "curl_cffi==0.15.0"

ここでPatchrightがChromiumバイナリを取得してくれるはずです。patchright install chromium がこけるようなら、空き容量が足りているか(約300MB)、Python 3.10以上かを確かめてください。

Step 2: Patchrightを起動してGlassdoorに移動する

Cloudflare層を相手に通用する、土台となる起動パターンがこちらです。

from patchright.sync_api import sync_playwright
import random, time

with sync_playwright() as p:
    browser = p.chromium.launch(
        headless=False,          # headless is still more detectable
        channel="chrome",        # use real Chrome, not bundled Chromium
    )
    context = browser.new_context(
        viewport={"width": 1440, "height": 900},
        locale="en-US",
        timezone_id="America/New_York",
        user_agent=(
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
            "AppleWebKit/537.36 (KHTML, like Gecko) "
            "Chrome/134.0.0.0 Safari/537.36"
        ),
    )
    page = context.new_page()
    page.goto(
        "https://www.glassdoor.com/Job/new-york-data-engineer-jobs-"
        "SRCH_IL.0,8_IC1132348_KO9,22.htm"
    )

    # Dismiss the login overlay — content is still in the DOM
    page.add_style_tag(content="""
        #HardsellOverlay, .LoginModal { display: none !important; }
        body { overflow: auto !important; position: initial !important; }
    """)

    page.wait_for_selector("[data-test='jobListing']")
    print("Page loaded — job listings visible.")

要になるのは channel="chrome" の指定です。同梱のChromiumではなくインストール済みのChrome本体を使うことで、ブラウザの指紋がぐっと自然になります。add_style_tag の小技は、Glassdoorのログインモーダル(#HardsellOverlay)をクリックすることなく覆い隠すためのもの。Scrapflyも 「データ自体はDOM内に残っていて、ただオーバーレイに覆われているだけ」と裏づけています。要するに、モーダルが出ていてもデータはHTMLの中に居続けるわけです。

Chromeウィンドウが立ち上がり、Glassdoorの求人検索ページへ移り、ログインポップアップに遮られることなく求人カードが並べば、成功の合図です。

Step 3: Glassdoorの求人一覧をスクレイピングする

安定したセレクタを見つける

GlassdoorはビルドごとにCSSクラス名を入れ替えるため、2023年のチュートリアルに載っている .jobCard_xyz123 のようなセレクタは、今では何も言わずに空振りすることがあります。頼るべきは data-test 属性です。これはGlassdoor社内のQA用に振られた命名で、デプロイをまたいでも比較的崩れにくい性質を持ちます。

求人一覧の各項目に当たるセレクタは、次の通りです。

項目セレクタ
求人カードのコンテナ[data-test="jobListing"]
職種名[data-test="job-title"]
求人リンクa[data-test="job-link"]
会社名[data-test="employer-name"]
勤務地[data-test="emp-location"]
給与レンジ[data-test="detailSalary"]
企業評価[data-test="rating"]
掲載日[data-test="job-age"]
次ページボタン[data-test="pagination-next"]

求人データを抽出する

from parsel import Selector
import csv, random, time

def scrape_jobs(page, max_pages=5):
    all_jobs = []

    for page_num in range(1, max_pages + 1):
        html = page.content()
        sel = Selector(text=html)
        cards = sel.css('[data-test="jobListing"]')

        if not cards:
            print(f"Page {page_num}: No cards found — possible block or selector change.")
            break

        for card in cards:
            job = {
                "title": card.css('[data-test="job-title"]::text').get("").strip(),
                "company": card.css('[data-test="employer-name"]::text').get("").strip(),
                "location": card.css('[data-test="emp-location"]::text').get("").strip(),
                "salary": card.css('[data-test="detailSalary"]::text').get("").strip(),
                "rating": card.css('[data-test="rating"]::text').get("").strip(),
                "link": card.css('a[data-test="job-link"]::attr(href)').get(""),
                "posted": card.css('[data-test="job-age"]::text').get("").strip(),
            }
            if job["link"] and not job["link"].startswith("http"):
                job["link"] = "https://www.glassdoor.com" + job["link"]
            all_jobs.append(job)

        print(f"Page {page_num}: scraped {len(cards)} jobs")

        # Pagination
        next_btn = page.query_selector('[data-test="pagination-next"]')
        if next_btn and page_num < max_pages:
            next_btn.click()
            time.sleep(random.uniform(3, 8))
            page.wait_for_selector("[data-test='jobListing']")
        else:
            break

    return all_jobs

CSVに保存する

def save_to_csv(jobs, filename="glassdoor_jobs.csv"):
    if not jobs:
        print("No jobs to save.")
        return
    keys = jobs[0].keys()
    with open(filename, "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=keys)
        writer.writeheader()
        writer.writerows(jobs)
    print(f"Saved {len(jobs)} jobs to {filename}")

ページ送りの上限にも注意しておきましょう。Glassdoorの検索結果は、総件数がいくつあろうと、おおむね30ページで頭打ちになります。もっと広く拾いたいときは、無理に先のページへ突き進むより、勤務地・職種・給与レンジで検索条件を絞り込むほうが、はるかに現実的です。

私の手元で試した限りでは、求人一覧5ページ(約75件)の取得に、ランダムな待機込みで45秒ほど。これを人力でやれば、コピー&ペーストだけで少なく見積もっても20分は溶けていきます。

Step 4: Glassdoorの企業レビューをスクレイピングする

他のどのチュートリアルも、実用コードをろくに載せていない領域がここです。レビューこそ企業インテリジェンスの中核であり、感情分析の材料も、企業文化の手がかりも、マネジメントの赤信号も、すべてこの中に凝縮されています。

レビューページへ移動する

レビューのURLは /Reviews/{Company}-Reviews-E{id}.htm という形をとります。Glassdoorで会社名を検索し、表示されたURLをのぞけば employer ID を割り出せます。

def navigate_to_reviews(page, company_reviews_url):
    page.goto(company_reviews_url)
    page.add_style_tag(content="""
        #HardsellOverlay, .LoginModal { display: none !important; }
        body { overflow: auto !important; position: initial !important; }
    """)
    page.wait_for_selector('[data-test="review"]', timeout=15000)

隠しBFFエンドポイント(最短ルート)

調べ進めるなかで最大の収穫だったのは、Glassdoorのレビューには 内部JSON API が存在し、HTML解析をまるごと迂回できるという事実でした。Scrapflyのスクレイパーrepo でもこのエンドポイントが文書化されており、DOMをかき分けるより段違いに安定しています。

import json, re, requests

def get_review_ids(page):
    """レビュー詳細ページのHTMLから employerId と dynamicProfileId を取得する。"""
    html = page.content()
    sel = Selector(text=html)
    script_text = sel.xpath(
        "//script[contains(text(), 'profileId')]/text()"
    ).get("")
    employer_match = re.search(r'"employer"\s*:\s*(\{[^}]+\})', script_text)
    if employer_match:
        meta = json.loads(employer_match.group(1))
        return meta.get("id"), meta.get("profileId")
    return None, None

def fetch_reviews_bff(page, employer_id, profile_id, max_pages=5):
    """Glassdoorの内部BFFエンドポイントから構造化レビューを取得する。"""
    all_reviews = []
    cookies = {c["name"]: c["value"] for c in page.context.cookies()}

    for pg in range(1, max_pages + 1):
        payload = {
            "applyDefaultCriteria": True,
            "employerId": employer_id,
            "dynamicProfileId": profile_id,
            "employmentStatuses": ["REGULAR", "PART_TIME"],
            "language": "eng",
            "onlyCurrentEmployees": False,
            "page": pg,
            "pageSize": 10,
            "sort": "DATE",
            "textSearch": "",
        }
        resp = requests.post(
            "https://www.glassdoor.com/bff/employer-profile-mono/employer-reviews",
            json=payload,
            cookies=cookies,
            headers={"Content-Type": "application/json"},
        )
        if resp.status_code != 200:
            print(f"BFF returned {resp.status_code} on page {pg}")
            break

        data = resp.json()
        reviews = data.get("data", {}).get("employerReviews", {}).get("reviews", [])
        total_pages = data.get("data", {}).get("employerReviews", {}).get("numberOfPages", 1)

        for r in reviews:
            all_reviews.append({
                "title": r.get("summary", ""),
                "rating": r.get("ratingOverall"),
                "pros": r.get("pros", ""),
                "cons": r.get("cons", ""),
                "author_role": r.get("jobTitle", {}).get("text", ""),
                "date": r.get("reviewDateTime", ""),
                "recommend": r.get("isRecommend"),
            })

        print(f"Reviews page {pg}/{total_pages}: got {len(reviews)} reviews")
        if pg >= total_pages:
            break
        time.sleep(random.uniform(3, 6))

    return all_reviews

このBFFエンドポイントを経由すれば、レビュー項目が整然としたJSONで返ってきます。HTML解析もCSSセレクタの破損も無縁です。用意すべきは、Step 6で扱う認証済みPlaywrightコンテキストから引き出したセッションCookieと、レビューHTMLから先回りして抜いておく employerId・dynamicProfileId だけ。これさえそろえば十分です。

レビュー用HTMLフォールバックセレクタ

BFFの仕様が動いたとき、あるいはDOMパースのほうが性に合うときのために、安定して使える data-test セレクタを挙げておきます。

項目セレクタ
レビューコンテナ[data-test="review"]
見出し[data-test="review-title"]
総合評価[data-test="overall-rating"]
長所[data-test="pros"]
短所[data-test="cons"]
日付[data-test="review-date"]
投稿者の職種[data-test="author-jobTitle"]

Step 5: Glassdoorの給与データをスクレイピングする

給与ページは丸ごとログイン必須です。次のコードに実データを返させるには、その前に認証済みセッション(Step 6)を整えておく必要があります。

給与ページへ移動する

給与URLは /Salary/{Company}-Salaries-E{id}.htm、ページ送りは _P{n}.htm の形式です。

def scrape_salaries(page, salary_url, max_pages=3):
    all_salaries = []

    for pg in range(1, max_pages + 1):
        url = salary_url if pg == 1 else salary_url.replace(".htm", f"_P{pg}.htm")
        page.goto(url)
        page.add_style_tag(content="""
            #HardsellOverlay { display: none !important; }
            body { overflow: auto !important; position: initial !important; }
        """)
        time.sleep(random.uniform(3, 7))

        html = page.content()
        sel = Selector(text=html)
        items = sel.css('[data-test="salary-item"]')

        if not items:
            print(f"Salary page {pg}: No items — possible login gate or block.")
            break

        for item in items:
            salary = {
                "job_title": item.css('[class*="SalaryItem_jobTitle__"]::text').get("").strip(),
                "salary_range": item.css('[class*="SalaryItem_salaryRange__"]::text').get("").strip(),
                "count": item.css('[class*="SalaryItem_salaryCount__"]::text').get("").strip(),
            }
            all_salaries.append(salary)

        print(f"Salary page {pg}: scraped {len(items)} entries")

    return all_salaries

ここで目を留めてほしいのが、[class*="SalaryItem_jobTitle__"] という前方一致のパターンです。Glassdoorの給与ページでは SalaryItem_jobTitle__XWGpT のように、CSSモジュール由来でハッシュが付いたクラス名が並びますが、末尾のハッシュ部分はデプロイのたびに姿を変えます。前半は安定していても、決して完全一致で固定名を書き込んではいけません。

Step 6: Glassdoorのログイン壁を突破する

給与データとレビュー全文を手にするための、いちばんの肝がこのステップです。やり方自体はいたって素直で、まず目に見えるブラウザで一度だけ手動ログインを済ませ、その認証済みセッションを保存し、以降のスクレイピングで使い回すだけです。

認証済みセッションを保存する

下のスクリプトを一度だけ走らせます。Chromeウィンドウが開いてGlassdoorのログインページ(現状はIndeed Loginへ転送されます)に移るので、あとはブラウザ上で自分の手でログインしてください。

import asyncio
from pathlib import Path
from patchright.async_api import async_playwright

STATE_FILE = Path("glassdoor_state.json")

async def login_and_save():
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=False, channel="chrome")
        context = await browser.new_context(
            viewport={"width": 1366, "height": 800},
            locale="en-US",
        )
        page = await context.new_page()
        await page.goto("https://www.glassdoor.com/profile/login_input.htm")
        print("ブラウザでログインし、完了したらここでEnterを押してください...")
        input()
        await context.storage_state(path=str(STATE_FILE))
        print(f"Session saved to {STATE_FILE}")
        await browser.close()

asyncio.run(login_and_save())

ログインしてEnterを押すと、PatchrightがCookieとlocal storageを残らず glassdoor_state.json へ書き出します。このファイルの中には、gdId、GSESSIONID、cf_clearance、そして認証トークンがまとめて収まります。

スクレイピング時にセッションを再利用する

二度目以降の実行では、保存したこの状態を読み込むだけで済みます。毎回ログインし直す手間はもう要りません。

async def scrape_with_auth(target_url):
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True, channel="chrome")
        context = await browser.new_context(
            storage_state="glassdoor_state.json"
        )
        page = await context.new_page()
        await page.goto(target_url)
        await page.add_style_tag(
            content="#HardsellOverlay{display:none!important}"
        )
        await page.wait_for_load_state("networkidle")
        html = await page.content()
        await browser.close()
        return html

保存したセッションが生きているのは、おおむね20〜30分ほどのアクティブ利用までで、それを過ぎると再チャレンジを食らうことがあります。長時間のスクレイピングを回すなら、データがあるはずのページで結果がゼロになった瞬間にログインスクリプトを再実行し、stateファイルを更新する仕掛けを仕込んでおくと安心です。

ログインポップアップの検出と消し方

部分的に制限されるページ——データ自体は表示されるのにモーダルが上に重なるタイプ——では、先ほどのCSS注入が効きます。

page.add_style_tag(content="""
    #HardsellOverlay, .LoginModal { display: none !important; }
    body { overflow: auto !important; position: initial !important; }
""")

ただしこれは、データがすでにHTMLの中に存在している場合に限って通用する手です。給与や踏み込んだレビューのように、サーバー側で丸ごと締め出されているページでは、Step 6の認証済みセッションだけが唯一の突破口になります。

Glassdoorスクレイパーを壊れにくく保つコツ

Glassdoorはフロントエンドをひっきりなしに作り替えます。スクレイパーに粘り強さを持たせたいなら、次の点が効いてきます。

クラス名より data-test 属性を優先する

GlassdoorはCSSクラス名をランダムに振り直す一方で、data-test 属性のほうは比較的安定したまま残ります。だからこそ、.jobCard_abc123 よりも [data-test="jobListing"] を常に優先しましょう。data-test が見当たらない場合(給与のフィールドなどがそうです)は、[class*="SalaryItem_jobTitle__"] のような部分一致でしのぎます。

プロキシをローテーションし、待機時間をランダム化する

使うべきはローテーション式の住宅系プロキシです。データセンターIPはほぼ瞬時にチャレンジを突きつけられます。ページの読み込みの合間には3〜8秒、長丁場のジョブでは5〜15秒のランダムな待機を挟みましょう。可能であれば、Cloudflareの挙動検知が最も神経質になりがちな米国の営業時間帯を外すのも一手です。

壊れたかどうかを監視する

スクレイパーには、ささやかなチェックを組み込んでおきましょう。データがあるはずのページで抽出件数がゼロだったなら、それは「空の結果」ではなく「セレクタが壊れた」とみなしてアラートを上げるべきです。Glassdoorは予告なくフロントエンドを更新するので、週に一度、小さなテストスクレイプを回しておくと、異変を早めに掴めます。

可能なら __NEXT_DATA__ の高速経路を使う

GlassdoorはNext.js + Apollo GraphQLで組まれたアプリです。多くのページでは、<script id="__NEXT_DATA__"> タグの内側に、GraphQLキャッシュ一式がJSONとして埋め込まれています。これを読み解くやり方はDOMをかき分けるより格段に頑丈で、Hardsellオーバーレイも完全に回避できます。

import json

def extract_next_data(html):
    sel = Selector(text=html)
    raw = sel.css("script#__NEXT_DATA__::text").get()
    if raw:
        return json.loads(raw)["props"]["pageProps"].get("apolloCache", {})
    return None

これで、求人・レビュー・給与の各項目を含んだApolloキャッシュを、構造化された形で取り出せます。CSSセレクタの出番はありません。GlassdoorのReactフロントエンドを動かしているのと同じデータを直接読むぶん、現時点ではかなり崩れにくい取得手段だと言えます。

コードを書かずにGlassdoorをスクレイピングする:Thunderbitを使う方法

この記事の読者が、みな開発者というわけではないでしょう。HRチームも、採用担当も、営業企画のアナリストも、市場調査の担当者も、Glassdoorのデータを必要としています。そして彼らが、Playwrightのコンテキスト管理やプロキシのローテーションを習得する義理はありません。

Thunderbit は、コードを一行も書かずに、同じ求人・レビュー・給与データを抜き出せるAI Web ScraperのChrome拡張です。私自身がThunderbitチームの一員である点は、はっきり申し添えておきます。それでもここで取り上げるのは、Glassdoorスクレイピングで最大の難所となる2つの課題を、これが本当に解いてくれるからです。

Glassdoor上でThunderbitが動く仕組み

操作はわずか2クリックです。

  1. Chromeで任意のGlassdoorページを開く(求人検索、企業レビュー、給与ページなど)
  2. Thunderbitのサイドバーで AI Suggest Fields をクリックすると、AIがページDOMを読み取り、列候補(職種名、会社名、評価、給与レンジ、長所、短所など)を提案します
  3. Scrape をクリックすると、CSSセレクタやブラウザ自動化コードなしでテーブルに抽出されます

ThunderbitにはGlassdoor企業スクレイパーのテンプレートが用意されていて、一度の実行で企業ごとに23項目以上をまとめて取得できます。求人一覧やレビュー、給与ページについても、汎用のAI Suggest Fieldsワークフローを使えば、任意のGlassdoor URLに対応できます。

コードなしでログイン壁を越える

Glassdoor攻略におけるThunderbitの真骨頂が、まさにこの点です。Browser Mode は、あなた自身のChromeセッションの内側で動きます。すでにChromeでGlassdoorにログイン済みなら、ThunderbitがそのCookieを自動で受け継ぎます。サーバー側スクレイパーを跳ね返してきた給与・レビューのログイン壁が、ここではそもそも障害になりません。Cookie管理も、永続コンテキストも、セッション用のコードも、いっさい不要です。

サブページスクレイピングで情報を深掘りする

まずは一覧ページ(たとえば検索で出てきた30社)を起点に据えます。Thunderbitに行を洗い出させたうえで、subpage scraping を有効にすると、各社のレビューや給与ページを順に巡回し、詳細な説明文やレビュー本文、給与情報までテーブルに継ぎ足してくれます。

業務ツールへエクスポートする

CSVやJSONを書き出すPythonスクリプトと違って、ThunderbitはGoogle Sheets、Airtable、Notion、Excelへ直接エクスポートできます。それも全プランで無料です。チームで共有して分析にかけたいとき、この手軽さがじわじわ効いてきます。

PythonとThunderbit、どちらを使うべきか

シナリオおすすめの方法
定期実行するデータパイプラインを作るPython + Patchright
単発の調査や小規模チーム案件Thunderbit
すべての項目を細かくプログラム制御したいPython
今日すぐGlassdoorデータが必要な非開発者Thunderbit
1回で1,000ページ以上をスクレイピングしたいPython + プロキシ
30社分を補完情報つきで取得したいどちらでも可 — 設定はThunderbitの方が速い

Thunderbitの料金は無料プランから始められ、Proプランは月額38ドル で3,000クレジットが付きます。1出力行あたり1クレジット(サブページスクレイピングなら2クレジット)の換算なので、補完付き30社の実行を月に33回ほどこなせる計算です。

GlassdoorスクレイピングでThunderbitを試す

Glassdoorをスクレイピングするのは合法?

ここは余計な装飾を省き、事実だけを並べます。Glassdoorの利用規約は、自動スクレイピングをはっきり禁じています。「当社の明示的な書面許可なしに、いかなる目的であってもロボット、スパイダー、スクレイパー等を使ってサービスにアクセスしてはならない」というのがその文言です。

とはいえ、法的な見取り図は、ひとつの規約だけで割り切れるほど単純ではありません。

  • Meta v. Bright Data(米カリフォルニア北部地区、2024年1月): ログインしていなければ規約に同意していないため、公開ページのログアウト状態でのスクレイピングは規約違反ではないと判断
  • hiQ Labs v. LinkedIn(第9巡回): CFAAは、一般公開されているデータの自動収集には適用されないとされた。ただし、偽アカウントやログイン後のスクレイピングは別問題
  • Van Buren v. United States(最高裁、2021年): CFAAの「許可されたアクセスを超える」の解釈が狭められた

実務に落とすと、こう整理できます。ログインせず公開求人だけを取りに行くなら、比較的安全な法的圏内にいます。逆にログイン済みセッションでのスクレイピングは、登録時点で利用規約に同意したことを意味し、しかもその規約が明確に禁じている行為です。これはPythonスクリプトでも、ThunderbitのBrowser Modeでも、まったく同じく当てはまります。

守っておきたい倫理上の指針としては、次のようなものが挙げられます。

  • 人間の閲覧速度を大きく下回るレート制御をする
  • レビュアーの個人識別情報をスクレイピングまたは再販しない
  • robots.txt の指示を尊重する
  • 本当に必要な項目だけを取得する

まとめ:どの方法があなたに合うか?

本ガイドでは、Indeed Loginへの移行、Cloudflare Bot Management、そして過去のチュートリアルを軒並み壊したCSSモジュールのクラス名ローテーションに目を配りながら、求人・レビュー・給与の3種類すべてを、2025年でも動くコードとともに解説してきました。

判断の手がかりは、次の通りです。

あなたの状況最適な方法
データパイプラインを作る開発者Python + Patchright(上記の手順どおり)
単発の調査、または繰り返しの小規模取得Thunderbit(ノーコード、ブラウザベース)
小規模な求人一覧だけ欲しいまずGlassdoor APIがまだ使えるか確認(たぶん不可)
特に給与やレビューが必要PythonスクレイピングかThunderbitが必須 — APIは対象外
データ共有が必要な非開発チームThunderbit → Google Sheetsへエクスポート

Glassdoorの守りは、これからも進化の手を止めないでしょう。セレクタは壊れ、新手のチャレンジも次々と現れます。だからこそ、このガイドはブックマークしておいてください。Webスクレイピングのツールや手法をもっと深く掘りたい方は、おすすめの自動Webスクレイピングツール、PythonでWebサイトからデータをスクレイピングする方法、おすすめの求人スクレイピングツール もあわせてどうぞ。Thunderbit YouTube Channel には、操作の流れを追える動画もそろっています。

Glassdoorデータ抽出にThunderbitを試す Get Started Free

よくある質問

1. Glassdoorはログインなしでスクレイピングできますか?

はい、求人一覧の多くと、概要レベルの企業評価は可能です。いいえ、完全な給与内訳や、最初の数ページを超えるレビュー全文は取得できません。#HardsellOverlay はCSSだけのモーダルなので、基礎HTMLには1ページ目のデータが残っていますが、深い内容はGlassdoorの「見せる代わりに条件を満たしてね」という壁の向こうでサーバー側に制限されています。

2. 2025年にGlassdoorスクレイピングでいちばん相性の良いPythonライブラリは?

Patchright(Playwrightのステルス派生版)が第一候補です。素のPlaywrightにある Runtime.Enable のCDPリークを修正しており、Cloudflareがチェックしているポイントにも対応しています。初期HTMLに __NEXT_DATA__ を含む一覧ページなら、impersonate="chrome124" を付けた curl_cffi が10〜20倍高速ですが、ログイン必須ページには対応できません。

3. Glassdoorでブロックされないためにはどうすればいいですか?

Patchright か rebrowser-playwright を使ってください(素のPlaywrightやSeleniumは避ける)。住宅系プロキシをローテーションし、データセンターIPは使わないこと。ページ間には3〜8秒のランダム遅延を入れ、Cookie(gdId、cf_clearance、GSESSIONID)はリクエスト間で維持してください。再チャレンジまでのセッション有効時間は20〜30分程度と見込んでください。

4. スクレイピングの代わりに使えるGlassdoor APIはありますか?

実質的にはありません。旧Partner APIは新規申請停止で、公開レビュー用エンドポイントも存在せず、IndeedのPublisher APIにも移行手段がありません。レビューと給与データを扱うなら、スクレイピングかThunderbitのようなノーコードツールが現実的な選択です。

5. Glassdoorスクレイパーはどのくらいの頻度で壊れますか?

かなり頻繁です。Glassdoorは予告なくフロントエンドを更新し、CSSモジュールのクラス名ハッシュはビルドごとに変わります。いちばん安定している取得方法は、(1) data-test属性セレクタ、(2) __NEXT_DATA__ のJSON、(3) 内部BFFレビューエンドポイントです。ゼロ件チェックを入れ、毎週小さくテストスクレイプして早期に壊れを検知してください。

さらに詳しく知る

Ke
Ke
Thunderbit の CTO | シニアデータサイエンティスト & ML エキスパート 機械学習とデータサイエンスで約10年の経験を持つ Ke Shen は、コロンビア大学の卒業生であり、Walmart Labs の元シニアデータサイエンティストです。Python、R、Java、統計学における深い専門性は同業者からも高く評価されており、理論段階の複雑な AI アルゴリズムを本番運用レベルのアーキテクチャへと落とし込むための、実践に裏打ちされた知見を共有しています。
目次
Thunderbit · AIウェブデータエージェント

1クリックであらゆるページからデータを抽出

25万人以上に選ばれています
無料プランあり
Webページからスプレッドシートへ
必要な内容を伝えるだけ。ThunderbitのAIエージェントが収集し、Excel、Google Sheets、Airtable、Notionへ出力します。無料で始められます。
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week