2022년에 잘 돌던 Glassdoor 스크래퍼가 지금은 403만 뱉는다면, 혼자 겪는 일이 아니에요. 개발자 포럼마다 똑같은 하소연이 반복돼요. "이거 왜 갑자기 안 되는지 아시는 분?"
원인은 간단해요. Glassdoor가 판을 통째로 갈아엎었거든요. Recruit Holdings가 2025년 7월 Glassdoor를 Indeed에 합치면서 직원 1,300명을 정리했고, 봇 차단도 한층 빡빡하게 조였어요. 그 바람에 옛날식 Selenium이나 requests 스크래퍼는 HTML 첫 바이트도 못 받고 막히기 일쑤예요. 2026년 2월부터는 Glassdoor 로그인이 전부 Indeed Login으로 넘어갔아요. Glassdoor 전용 로그인 폼을 박아 둔 튜토리얼은 출발선부터 깨져 있다는 뜻이죠. 그래도 플랫폼 안에는 여전히 리뷰·연봉·인사이트 1억 8천만 건과 고용주 250만 곳 정보가 쌓여 있어요. HR 벤치마킹, 경쟁사 분석, 영업 타깃 발굴에 더없이 좋은 데이터지만, 손에 넣을 수 있어야 쓸모가 있겠죠. 이 글은 그 변화 이후에도 실제로 돌아가는 버전이에요. Glassdoor의 세 가지 데이터, 즉 채용 공고·리뷰·연봉을 한 번에 다루고, Python 코드는 2025년 기준으로 새로 짠 것만 써요. 무엇이 막고 어떻게 빠져나가는지 짚어 드리고, 코드가 아예 싫은 분을 위한 노코드 길도 끝에 소개할게요.
2025년에 Python으로 Glassdoor를 크롤링해야 하는 이유
Glassdoor는 그냥 채용 게시판이 아니에요. 웹에서 손에 꼽는 고용주 인텔리전스 창고예요. Fortune 500 중 약 3분의 1이 활용하고, 월 순방문자도 약 5,500만 명에 달해요. 이 페이지 뒤의 데이터가 여러 팀의 실제 결정을 움직여요.
팀별로 어떻게 쓰는지 정리하면 이래요.
| 활용 사례 | 필요한 데이터 유형 | 주요 수혜자 |
|---|---|---|
| 연봉 벤치마킹 | 연봉 분포, 표본 수 | HR, 총보상, 운영팀 |
| 경쟁사 채용 추적 | 채용 공고, 게시 빈도 | 영업, 전략, VC/Corp Dev |
| 고용주 브랜드 모니터링 | 리뷰 텍스트, 평점 추이, CEO 승인율 | HR, 마케팅, 커뮤니케이션 |
| 리드 발굴(성장 중인 기업) | 채용 공고 + 회사 정보 | 영업팀, SDR |
| 시장/학술 리서치 | 세 가지 모두 | 분석가, 컨설턴트, 연구자 |

2025년 10월 미국 정부 셧다운으로 BLS가 고용 데이터를 못 냈을 때, Glassdoor의 Economic Research 팀은 자체 데이터셋으로 대체 일자리 보고서를 발표했어요. 기관 분석가들이 이 데이터를 얼마나 진지하게 보는지 보여 주는 장면이에요.
Python이 여전히 표준인 건 생태계가 압도적이기 때문이에요. 브라우저 자동화는 Playwright, 파싱은 parsel/lxml, TLS 지문 우회는 curl_cffi, 거기에 실전 패턴을 공유하는 거대한 커뮤니티까지. 발목을 잡는 건 Python이 아니라, 크롤링하기 훨씬 까다로워진 Glassdoor 쪽이에요.
AI로 Glassdoor 데이터 크롤링하기 Get Started Free
노코드로 가고 싶다면 Thunderbit으로 채용 공고, 리뷰, 연봉 페이지를 긁을 수 있어요. Python 스택을 직접 짜거나 떠안고 관리할 필요가 없죠.
실제로 어떤 Glassdoor 데이터를 크롤링할 수 있나?
대부분의 튜토리얼은 채용 공고만 만져요. 그런데 포럼 글, GitHub 이슈, Reddit 질문을 쭉 따라가 보면 정작 수요가 몰리는 건 아무도 제대로 안 알려주는 두 가지예요. 바로 리뷰와 연봉예요. 세 카테고리별로 뭘 뽑을 수 있는지 정리해 볼게요.
채용 공고
가장 만만한 유형이에요. 직무명, 회사명, 위치, 연봉 추정치, 회사 평점, 게시일, Easy Apply 배지, 채용 링크를 뽑을 수 있어요. 로그인 없이도 일부는 보이지만, 페이지를 몇 장 넘기다 보면 로그인 팝업이 튀어나오곤 해요.
회사 리뷰
고용주 브랜드 분석에서 제일 재미있는 영역이에요. 전체 평점, 세부 평점(워크라이프 밸런스, 문화 및 가치, 다양성 및 포용, 커리어 기회, 보상 및 복리후생, 경영진), 장점 텍스트, 단점 텍스트, 작성자 직무명, 리뷰 날짜, 고용 상태를 뽑을 수 있어요. 다만 전체 리뷰 텍스트는 로그인 영역이라 요약만 보이고, 장단점 전체는 인증해야 열려요.
연봉 데이터
가장 찾는 사람은 많은데 가장 골치 아픈 유형이에요. 직무명, 기본 연봉 범위, 총 보상 범위, 연봉 보고 수, 위치를 뽑을 수 있어요. 그런데 연봉 페이지는 통째로 로그인 뒤에 있고, 때로는 남의 연봉을 보려면 내 연봉부터 제출하라는 "제출 후 열람" 흐름까지 붙어요. 기존 튜토리얼 중 여기까지 멀쩡히 도는 코드는 거의 없어요. 이 글에서 그 부분을 풀어 볼게요.
로그인 필요 여부와 비로그인 접근 가능 여부
아래 표만 보면 어떤 페이지가 빈 데이터만 돌려주는지 미리 알 수 있어서 헛수고를 줄일 수 있어요.
| 데이터 유형 | 로그인 없이 가능? | 비고 |
|---|---|---|
| 채용 공고 제목 및 기본 정보 | 대부분 가능 | 여러 페이지 후 팝업이 뜰 수 있음 |
| 전체 채용 공고 설명 | 부분 가능 | 보통 2~3회 조회 후 차단됨 |
| 회사 리뷰(전체 텍스트) | 불가 — 로그인 필요 | 요약은 보이지만 전체는 잠김 |
| 연봉 데이터 | 불가 — 로그인 필요 | "제출 후 열람"이 추가로 필요할 수 있음 |
기존 Glassdoor 스크래퍼가 깨졌을 가능성이 높은 이유

돌려 말하지 않을게요. 2021~2023년 튜토리얼 코드를 그대로 복붙하고 있다면 안 도와요. GitHub에서 별을 가장 많이 받은 기존 Glassdoor Selenium 스크래퍼(MatthewChatham/glassdoor-review-scraper, 약 1.4k stars)만 봐도 "Glassdoor 새 UI", "Cloudflare 안티봇", "NoSuchElementException" 같은 미해결 이슈가 12개 넘게 쌓여 있어요. 사실상 방치된 저장소죠. Apify의 glassdoor-jobs-scraper 액터도 DEPRECATED 딱지가 붙었어요. ScrapeOps는 Glassdoor 스크래핑 난이도를 10점 만점에 9점, 우회 난이도는 8점으로 매겼고요.
뭐가 바뀌었고 왜 옛 코드가 깨지는지 살펴볼게요.
| 방어 레이어 | 변경 사항 | 기존 스크래퍼에 미치는 영향 |
|---|---|---|
| Cloudflare Bot Management | 2024년부터 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 vs. Python 스크래핑: 먼저 올바른 방식을 선택하자
포럼마다 "그냥 Glassdoor API 쓰면 안 되나요?"라는 질문이 올라오는데, 답은 사실상 "안 돼요."
기존 Glassdoor Partner API는 신규 신청을 안 받아요. 개발자 포털은 형태만 남아 있고 HTTP 403을 돌려줘요. 공개 리뷰 엔드포인트는 처음부터 없었고요. MatthewChatham의 스크래퍼도 "Glassdoor에 리뷰 API가 없어서" 만든 거예요. Indeed의 Publisher API로도 리뷰나 연봉을 끌어올 길은 없어요.
솔직하게 비교하면 이래요.
| 항목 | Glassdoor Partner API v1 | Python 스크래핑 | Thunderbit(노코드) |
|---|---|---|---|
| 접근성 | 신규 신청 불가 | 가능(직접 구현) | 크롬 확장 프로그램 |
| 채용 공고 | 제한적/종료 수순 | 노력하면 가능 | 가능 |
| 회사 리뷰 | 공개 API로는 존재한 적 없음 | 가능(로그인 필요) | 가능(브라우저 모드) |
| 연봉 데이터 | 공개 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의 스텔스 포크로, Runtime.Enable CDP 누출을 막아 줘요. 바로 이 누출이 일반 Playwright가 Cloudflare 보호 사이트에서 들통나는 직접적인 기술 원인이에요. 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 프로모션 기준 1GB당 $1.75(약 2,400원), 또는 Decodo 기준 1GB당 $3.00(약 4,100원) 정도예요. 실제 운영이라면 물량에 따라 1GB당 $38(약 4,1001만 1천 원) 정도를 예산으로 잡는 게 좋아요.
프록시가 아무리 좋아도 요청 사이에 무작위 지연(최소 38초, 긴 작업은 515초)은 꼭 넣으세요.
1단계: Python 환경 설정하기
프로젝트 폴더를 만들고 권장 스택을 설치해요.
mkdir glassdoor-scraper && cd glassdoor-scraper
python3.11 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
# 핵심 스택
pip install patchright==1.58.2 parsel==1.11.0
# 브라우저 바이너리 설치
patchright install chromium
# 선택 사항: __NEXT_DATA__ 추출용 빠른 경로
pip install "curl_cffi==0.15.0"
Patchright가 Chromium 바이너리를 내려받는 게 보여야 정상이에요. patchright install chromium이 실패하면 디스크 여유 공간(약 300MB)과 Python 버전(3.10 이상)을 확인하세요.
2단계: Patchright를 실행하고 Glassdoor로 이동하기
Glassdoor의 Cloudflare 레이어를 상대로 잘 먹히는 기본 실행 패턴이에요.
from patchright.sync_api import sync_playwright
import random, time
with sync_playwright() as p:
browser = p.chromium.launch(
headless=False, # 헤드리스는 여전히 더 잘 탐지됨
channel="chrome", # 번들 Chromium이 아니라 실제 Chrome 사용
)
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"
)
# 로그인 오버레이 숨기기 — 실제 데이터는 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("페이지 로드 완료 — 채용 공고가 표시됩니다.")
여기서 챙길 포인트가 몇 개 있어요. channel="chrome" 플래그는 번들 Chromium 대신 실제 설치된 Chrome 바이너리를 쓰게 해서 지문이 더 자연스러워져요. add_style_tag 트릭은 아무것도 클릭하지 않고 Glassdoor 로그인 모달(#HardsellOverlay)을 가려요. Scrapfly도 "실제 데이터는 그대로 있고 오버레이가 덮을 뿐"이라고 확인했죠. 즉, 모달이 떠 있어도 HTML 안에는 데이터가 살아 있어요.
Chrome 창이 열려 Glassdoor 채용 검색 페이지로 넘어가고, 로그인 팝업에 가리지 않은 채 채용 카드가 보이면 성공이에요.
3단계: Glassdoor 채용 공고 크롤링하기
안정적인 셀렉터 찾기
Glassdoor는 빌드마다 CSS 클래스명을 무작위로 돌려요. 그래서 2023년 튜토리얼의 .jobCard_xyz123 같은 셀렉터는 오늘 아무것도 안 돌려주기 십상이에요. 대신 Glassdoor 내부 QA 관례인 data-test 속성을 쓰세요. 배포가 바뀌어도 비교적 안정적이에요.
채용 공고 필드용 셀렉터는 다음과 같아요.
| 필드 | 셀렉터 |
|---|---|
| 채용 카드 컨테이너 | [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_num}페이지: 카드가 없습니다 — 차단되었거나 셀렉터가 바뀌었을 수 있습니다.")
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_num}페이지: {len(cards)}개 채용 공고 추출")
# 페이지네이션
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("저장할 채용 공고가 없습니다.")
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"{filename}에 {len(jobs)}개의 채용 공고를 저장했습니다")
페이지네이션 한도도 알아 두세요. Glassdoor는 결과 수와 상관없이 검색 결과를 대략 30페이지로 끊어요. 더 넓게 봐야 한다면 끝까지 넘기려 애쓰지 말고, 위치·직무 유형·연봉 범위 같은 필터로 검색을 잘게 쪼개는 편이 나아요.
제가 테스트했을 때 채용 공고 5페이지(약 75개)를 무작위 지연과 함께 긁는 데 약 45초 걸렸어요. 같은 일을 손으로 하면 최소 20분은 복붙해야 해요.
4단계: Glassdoor 회사 리뷰 크롤링하기
다른 튜토리얼이 실제로 도는 코드를 거의 안 주는 부분이 바로 여기예요. 진짜 고용주 인텔리전스는 리뷰에 담겨 있어요. 감성, 문화적 신호, 경영 리스크까지 읽어낼 수 있죠.
리뷰 페이지로 이동하기
리뷰 URL 패턴은 /Reviews/{Company}-Reviews-E{id}.htm 예요. Glassdoor에서 회사를 검색한 뒤 URL을 보면 고용주 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 리뷰에 HTML 파싱을 통째로 건너뛰게 해 주는 내부 JSON API가 실제로 있다는 사실이에요. Scrapfly의 스크래퍼 저장소에도 이 엔드포인트가 문서화돼 있고, 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가 {pg}페이지에서 {resp.status_code}를 반환했습니다")
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"리뷰 {pg}/{total_pages}페이지: {len(reviews)}개 리뷰 수집")
if pg >= total_pages:
break
time.sleep(random.uniform(3, 6))
return all_reviews
BFF 엔드포인트를 쓰면 모든 리뷰 필드가 깔끔한 JSON으로 떨어져요. HTML 파싱도, CSS 셀렉터 깨질 걱정도 없죠. 대신 인증된 Playwright 컨텍스트의 세션 쿠키가 있어야 하고(아래 6단계 참고), 먼저 리뷰 페이지 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"] |
5단계: Glassdoor 연봉 데이터 크롤링하기
연봉 페이지는 통째로 로그인 뒤에 있어요. 이 코드가 실제 데이터를 돌려주려면 먼저 인증된 세션(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"연봉 페이지 {pg}: 항목이 없습니다 — 로그인 벽 또는 차단일 수 있습니다.")
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"연봉 페이지 {pg}: {len(items)}개 항목 추출")
return all_salaries
여기서 [class*="SalaryItem_jobTitle__"] 같은 prefix-match 패턴을 눈여겨보세요. Glassdoor 연봉 페이지는 SalaryItem_jobTitle__XWGpT처럼 CSS 모듈 해시 클래스명을 써요. 이 해시 접미사는 배포마다 바뀌지만 앞부분은 그대로 유지돼요. 전체 클래스를 하드코딩하면 안 돼요.
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"세션이 {STATE_FILE}에 저장되었습니다")
await browser.close()
asyncio.run(login_and_save())
로그인하고 Enter를 누르면 Patchright가 모든 쿠키와 로컬 스토리지를 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분쯤 지나면 Glassdoor가 재검증을 요구할 수 있어요. 긴 작업을 돌릴 때는, 데이터가 있어야 할 페이지에서 결과가 0개로 나오면 로그인 스크립트를 다시 돌려 상태 파일을 갱신하는 체크를 넣어 두세요.
로그인 팝업 감지 및 닫기
데이터는 보이는데 모달만 덮은 페이지에서는 앞서 본 CSS 주입이 잘 먹혀요.
page.add_style_tag(content="""
#HardsellOverlay, .LoginModal { display: none !important; }
body { overflow: auto !important; position: initial !important; }
""")
이건 HTML 안에 실제 데이터가 이미 있을 때만 통해요. 서버 측에서 완전히 막은 페이지(연봉, 깊은 리뷰)는 6단계의 인증 세션이 사실상 유일한 해법이에요.
Glassdoor 스크래퍼를 오래 살려두는 팁
Glassdoor는 프론트엔드를 자주 갈아엎어요. 스크래퍼에 맷집을 더하는 방법을 정리해요.
클래스명보다 data-test 속성을 우선하라
Glassdoor는 CSS 클래스명은 무작위로 돌려도 data-test 속성은 비교적 안정적으로 둬요. 늘 .jobCard_abc123보다 [data-test="jobListing"]을 먼저 쓰세요. data-test가 없는 경우(연봉 필드 클래스처럼)에는 [class*="SalaryItem_jobTitle__"] 같은 prefix-match 패턴을 쓰면 돼요.
프록시를 순환하고 지연 시간을 무작위화하라
리버설 프록시를 쓰세요. 데이터센터 IP는 거의 즉시 챌린지를 받아요. 페이지 로드 사이에는 38초 무작위 지연을 넣고, 오래 도는 작업이면 515초까지 늘리세요. 가능하면 미국 업무 시간대는 피하는 게 좋아요. 그 시간엔 Cloudflare의 행동 기반 탐지가 더 공격적이에요.
깨짐을 감시하라
스크래퍼에 간단한 체크를 넣으세요. 데이터가 있어야 할 페이지에서 추출 결과가 0개라면, 빈 데이터가 아니라 셀렉터 실패로 보고 알림을 띄워야 해요. 매주 작은 테스트 크롤링을 돌려 이상 징후를 일찍 잡는 것도 좋아요. Glassdoor는 프론트엔드 변경을 예고 없이 배포하곤 하니까요.
가능하면 __NEXT_DATA__ 빠른 경로를 써라
Glassdoor는 Next.js + Apollo GraphQL 앱이에요. 많은 페이지가 초기 HTML에 전체 GraphQL 캐시를 JSON으로 담은 <script id="__NEXT_DATA__"> 태그를 줘요. 이걸 파싱하는 게 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 프론트엔드를 실제로 돌리는 그 데이터를 그대로 쓰기 때문에 맷집이 가장 좋아요.
코드는 건너뛰고 Thunderbit으로 Glassdoor를 크롤링하기(Python 불필요)
이 글을 읽는 모두가 개발자는 아니죠. HR 팀, 리크루터, 세일즈 오퍼레이션 분석가, 시장조사 담당자도 Glassdoor 데이터가 필요한데, 그분들한테 Playwright 컨텍스트나 프록시 순환을 떠안길 이유는 없어요.
Thunderbit은 AI 웹 스크래퍼 크롬 확장 프로그램으로, 코드 한 줄 없이 똑같은 채용 공고·리뷰·연봉 데이터를 뽑아요. 저는 Thunderbit 팀에서 일하니 그 점은 분명히 밝혀 둘게요. 그래도 소개하는 이유는, Glassdoor 스크래핑에서 가장 까다로운 두 가지를 실제로 풀어 주기 때문이에요.
Thunderbit을 Glassdoor에서 사용하는 방법
작업은 클릭 두 번이면 끝나요.
- Chrome에서 아무 Glassdoor 페이지나 엽니다(채용 검색, 회사 리뷰, 연봉 페이지)
- Thunderbit 사이드바에서 AI 필드 추천을 클릭해요. AI가 페이지 DOM을 읽고 직무명, 회사명, 평점, 연봉 범위, 장점, 단점 같은 컬럼을 제안해요.
- 스크랩을 클릭하면 CSS 셀렉터나 브라우저 자동화 코드 없이 데이터가 표로 떨어져요.
Thunderbit에는 Glassdoor 회사 스크래퍼 템플릿이 내장돼 있어, 회사당 23개 이상 필드를 한 번에 뽑을 수 있어요. 채용·리뷰·연봉도 일반 AI 필드 추천 워크플로우로 어떤 Glassdoor URL이든 처리할 수 있고요.
코드 없이 로그인 벽 처리하기
Thunderbit의 구조적 강점이 Glassdoor에서 가장 또렷하게 드러나는 지점이에요. 브라우저 모드는 사용자의 Chrome 세션 안에서 돌아가요. 이미 Chrome에서 Glassdoor에 로그인돼 있으면 Thunderbit이 그 쿠키를 자동으로 물려받아요. 서버 측 스크래퍼를 막던 연봉/리뷰 로그인 벽이 사실상 무력해지죠. 쿠키 관리도, 영구 컨텍스트도, 세션 코드도 필요 없어요.
하위 페이지 스크래핑으로 데이터 확장하기
목록 페이지(예: 검색 결과의 회사 30곳)를 시작점으로 잡아 Thunderbit이 행을 읽게 한 뒤, 하위 페이지 스크래핑을 켜면 각 회사의 리뷰나 연봉 페이지를 돌며 전체 설명, 리뷰 텍스트, 연봉 세부를 테이블로 펼쳐 줘요.
비즈니스 도구로 내보내기
CSV나 JSON만 뱉는 Python 스크립트와 달리, Thunderbit은 Google Sheets, Airtable, Notion, Excel로 바로 내보낼 수 있고 모든 플랜에서 무료예요. 팀끼리 데이터를 나누고 분석해야 할 때 특히 좋아요.
Python vs. Thunderbit: 언제 무엇을 써야 할까?
| 상황 | 권장 방식 |
|---|---|
| 반복되는 데이터 파이프라인 구축 | Python + Patchright |
| 일회성 리서치 또는 소규모 팀 프로젝트 | Thunderbit |
| 모든 필드를 프로그래밍 방식으로 세밀하게 제어해야 함 | Python |
| 오늘 당장 Glassdoor 데이터가 필요한 비개발자 | Thunderbit |
| 1,000페이지 이상을 한 번에 크롤링 | Python + 프록시 |
| 30개 회사를 확장 정보와 함께 크롤링 | 둘 다 가능 — Thunderbit이 설정이 더 빠름 |
Thunderbit 가격은 무료(월 6페이지)부터 시작하고, Pro 플랜은 월 $38(약 5만 2천 원)에 3,000 크레딧예요. 출력 행당 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 모듈 클래스명 회전까지 반영한 2025년 기준 작동 코드로, Glassdoor의 세 데이터 — 채용 공고·리뷰·연봉 — 를 모두 다뤘어요.
선택 기준은 이렇게 잡으세요.
| 당신의 상황 | 최적의 선택 |
|---|---|
| 데이터 파이프라인을 만드는 개발자 | Python + Patchright(위의 단계별 안내 따르기) |
| 일회성 리서치 또는 반복적인 소규모 수집 | Thunderbit(노코드, 브라우저 기반) |
| 기본 채용 공고만 소규모로 필요 | 먼저 Glassdoor API 접근이 아직 가능한지 확인(아마도 불가) |
| 연봉 또는 리뷰 데이터가 특히 필요 | Python 스크래핑 또는 Thunderbit만 가능 — API는 이를 다루지 않음 |
| 공유 데이터가 필요한 비개발자 팀 | Thunderbit → Google Sheets로 내보내기 |
Glassdoor의 방어는 앞으로도 계속 진화할 거예요. 셀렉터는 깨지고 새 챌린지도 등장하겠죠. 이 글을 북마크해 두세요. 웹 스크래핑 도구와 기법을 더 깊이 보고 싶다면 최고의 자동 웹 스크래핑 도구, Python으로 웹사이트에서 데이터 크롤링하는 방법, 최고의 채용 크롤링 도구 글도 참고하세요. Thunderbit YouTube 채널에 실습 영상도 있어요.
Glassdoor 데이터 추출에 Thunderbit 사용해 보기 Get Started Free
자주 묻는 질문(FAQ)
1. 로그인 없이 Glassdoor를 크롤링할 수 있나요?
채용 공고 데이터 대부분과 핵심 회사 평점은 돼요. 하지만 전체 연봉 상세나 첫 몇 페이지를 넘는 리뷰 텍스트는 안 돼요. #HardsellOverlay는 CSS 모달이라 겉으로 가려져도 첫 페이지 데이터는 HTML에 남지만, 더 깊은 콘텐츠는 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는 바로 챌린지를 받는다고 생각하세요. 페이지 사이에 38초 무작위 지연을 넣고, 쿠키(30분쯤 지나면 재검증이 걸릴 수 있어요.gdId, cf_clearance, GSESSIONID)를 요청 간 유지하세요. 세션은 보통 20
4. 스크래핑 대신 쓸 수 있는 Glassdoor API가 있나요?
사실상 없어요. 기존 Partner API는 신규 신청이 막혔고, 공개 리뷰 엔드포인트는 처음부터 없었으며, Indeed의 Publisher API로도 옮길 길이 없어요. 리뷰와 연봉은 스크래핑이나 Thunderbit 같은 노코드 도구가 현실적인 선택이에요.
5. Glassdoor 스크래퍼는 얼마나 자주 깨지나요?
꽤 자주요. Glassdoor는 프론트엔드 변경을 예고 없이 배포하고, CSS 모듈 클래스명 해시는 빌드마다 바뀌어요. 가장 안정적인 추출 전략은 (1) data-test 속성 셀렉터, (2) __NEXT_DATA__ JSON 블록, (3) 내부 BFF 리뷰 엔드포인트예요. 결과 0개 체크를 넣고, 매주 작은 테스트 크롤링으로 조기 파손을 잡으세요.
더 알아보기


