최근에 "Python으로 IMDb 크롤링"을 검색해 봤다면, 아마 이런 느낌을 받았을 겁니다. 여기저기 돌아다니는 튜토리얼들이 죄다 제대로 안 돌아간다는 거죠. 그냥 조금 오래된 정도가 아니라, 결과는 0건이고 NoneType 오류만 쏟아지는 식으로 아예 망가져 있는 경우가 많습니다.
저는 지난 몇 주 동안 GeeksforGeeks, Medium, freeCodeCamp, Kaggle 노트북 등에서 찾을 수 있는 주요 IMDb 크롤링 튜토리얼을 전부 직접 테스트해 봤습니다. IMDb 크롤링용으로 태그된 도 살펴봤는데, 상당수가 td.titleColumn, td.ratingColumn 같은 CSS 선택자를 쓰고 있었습니다. 문제는 이런 선택자들이 IMDb가 Top 250 페이지를 개편한 2023년 6월 이후로는 더 이상 존재하지 않는다는 점입니다. 그 결과는 뻔하죠. 포럼에는 "왜 제 코드는 빈 값만 돌려주나요?"라고 묻는 개발자들로 넘쳐나고, 같은 인기 라이브러리의 유지보수자들조차 "모든 파서를 하나씩 손보는 수밖에 없다"고 인정하는 상황입니다. 이 가이드에서는 지금 당장 실제로 잘 돌아가는 두 가지 Python 방식, 페이지네이션과 자주 터지는 오류 처리법, Python이 아예 안 맞는 경우, 그리고 스크래퍼가 '무덤'으로 가지 않게 미래에 대비하는 방법까지 전부 다룹니다.
Python으로 IMDb를 크롤링한다는 건 무슨 뜻일까?
웹 크롤링은 웹페이지에서 데이터를 프로그램으로 긁어오는 작업입니다. 손으로 복사해서 붙여넣는 대신, 스크립트가 그 일을 대신하게 만드는 거죠. 여기서 "IMDb를 크롤링한다"는 말은 Python으로 IMDb 웹페이지에서 영화 제목, 평점, 장르, 출연진, 상영 시간, 투표 수 같은 구조화된 데이터를 가져오는 걸 뜻합니다.
이 작업에 가장 자주 쓰이는 Python 스택은 세 가지입니다. 웹페이지를 불러오는 requests, HTML을 파싱하고 원하는 데이터를 찾는 BeautifulSoup, 그리고 결과를 정리하고 내보내는 pandas입니다. 어떤 튜토리얼은 JavaScript 렌더링이 필요한 페이지를 처리하려고 Selenium이나 Playwright를 쓰기도 하지만, 조금만 보면 더 빠른 방법도 있습니다.
중요한 점 하나만 짚고 넘어가겠습니다. 이 글의 내용은 2025년 중반 기준 IMDb의 현재 페이지 구조를 바탕으로 검증했습니다. IMDb는 대략 6~12개월마다 구조를 바꾸기 때문에, 2027년에 이 글을 보고 있다면 일부 선택자가 달라졌을 수도 있습니다. 다만 그런 경우에 어떻게 대응할지도 같이 설명하겠습니다.
왜 Python으로 IMDb를 크롤링할까? 실제 활용 사례
코드를 한 줄도 쓰기 전에, IMDb 데이터로 실제로 뭘 할 수 있을까요? 답은 생각보다 다양합니다.
IMDb 리뷰 데이터셋은 세계에서 가장 널리 쓰이는 NLP 벤치마크 중 하나입니다. Maas et al. (2011)의 기초 논문은 되었고, 이 데이터셋은 TensorFlow, Keras, PyTorch에 기본 탑재되어 있습니다. Hugging Face에서 stanfordnlp/imdb 데이터셋은 월 213,321회 다운로드되며 1,500개 이상의 모델 학습에 활용됐습니다. 즉, 머신러닝을 한다면 IMDb 데이터는 이미 익숙할 가능성이 높습니다.
하지만 활용 범위는 학계에만 머물지 않습니다.
| 활용 사례 | 대상 | 필요한 데이터 항목 |
|---|---|---|
| 영화 추천 엔진 | 데이터 사이언티스트, 개인 프로젝트 개발자 | 제목, 장르, 평점, 출연진 |
| 스트리밍 플랫폼 콘텐츠 전략 | 제품/콘텐츠 팀 | 평점, 투표 수, 개봉 연도, 장르 |
| 감성 분석 / NLP 학습 | ML 연구자, 학생 | 리뷰, 평점 |
| 경쟁 콘텐츠 분석 | 엔터테인먼트 산업 분석가 | 박스오피스, 개봉일, 평점 추이 |
| 영화 관광 연구 | 관광청, 여행사 | 촬영지, 인기 지표 |
| 학술 연구 | 대학 연구자 | 각종 구조화된 영화 메타데이터 |
영화 관광 시장만 해도 2025년 전 세계 기준 약 규모로 추정됩니다. Netflix는 2024년에 콘텐츠에 170억 달러 이상을 썼고, 가 개인화 추천에서 비롯됐습니다. 즉, IMDb 데이터는 여러 산업의 실제 의사결정에 쓰이고 있습니다.
코드를 쓰기 전에 알아야 할 IMDb 데이터 수집 방법
대부분의 튜토리얼이 아예 건너뛰는 부분이 바로 여기입니다. pip install beautifulsoup4부터 바로 시작하면서, 정작 Python 크롤링이 지금 상황에 맞는 선택인지부터 따져보지는 않죠.
전체 선택지는 다음과 같습니다.
| 방법 | 추천 대상 | 장점 | 단점 |
|---|---|---|---|
| Python + BeautifulSoup | 학습용, 맞춤 추출 | 완전한 제어, 유연함 | 선택자에 취약, 자주 깨짐 |
JSON-LD / __NEXT_DATA__ 추출 | 안정성이 중요한 개발자 | JS 콘텐츠 처리 가능, 더 견고함 | JSON 구조 이해 필요 |
| IMDb 공식 데이터셋 | 대규모 분석, 학술용 | 합법적, 완전한 데이터, 2,600만+ 타이틀, 일일 업데이트 | TSV 형식, 리뷰/이미지 없음 |
| Cinemagoer (IMDbPY) 라이브러리 | 타이틀별 프로그래밍 조회 | Python 친화적 API, 풍부한 필드 | 열린 이슈 88개, 마지막 릴리스 2023년 5월 |
| TMDb API | 영화 메타데이터 + 이미지 | 무료 API 키, JSON, 문서화 우수 | 데이터 출처가 IMDb가 아님(평점 체계 다름) |
| Thunderbit (노코드) | 비개발자, 빠른 내보내기 | 2번 클릭 크롤링, AI가 필드 추천, Excel/Sheets로 내보내기 | 대량 수집 시 크레딧 기반 |
몇 가지 참고할 점이 있습니다. Cinemagoer는 2023년 5월 이후 PyPI 릴리스가 없었고, IMDb의 2025년 6월 개편 이후 대부분의 파서가 깨졌습니다. 지금 당장 운영 환경에 쓰기엔 추천하기 어렵습니다. TMDb는 훌륭하지만 자체 평점 체계를 쓰기 때문에 IMDb 평점과는 다릅니다. 그리고 IMDb의 공식 엔터프라이즈 API는 AWS Data Exchange를 통해 연간 이 들어가므로, 대부분의 사람들에게는 현실적인 선택이 아닙니다.
코드를 아예 쓰고 싶지 않은 분이라면, 가 IMDb 페이지를 읽고, 제목·평점·연도·장르 같은 추출 필드를 자동으로 추천한 뒤, 두 번의 클릭만으로 Excel, Google Sheets, Airtable, Notion으로 내보낼 수 있습니다. IMDb 레이아웃이 바뀌어도 AI가 구조를 다시 읽기 때문에 따로 관리할 선택자가 없습니다. 이 부분은 뒤에서 더 자세히 설명하겠습니다.
이제 Python으로 직접 해보고 싶은 분들을 위해, 실제로 잘 작동하는 두 가지 방법을 소개하겠습니다.
방법 1: BeautifulSoup으로 IMDb 크롤링하기(전통적인 방식)
이 방식은 대부분의 튜토리얼에서 보는 가장 기본적인 접근법입니다. 작동은 합니다. 다만 먼저 말씀드리자면, 제가 소개할 방식들 중 가장 취약합니다. IMDb의 CSS 클래스명은 자동 생성되고, 리디자인 때마다 바뀔 수 있기 때문입니다. 그래도 웹 크롤링의 기초를 익히기에는 가장 좋은 방법입니다.
1단계: Python 라이브러리 설치 및 불러오기
필요한 패키지는 네 가지입니다.
1pip install requests beautifulsoup4 pandas lxml
각각의 역할은 다음과 같습니다.
requests— 웹페이지를 가져오기 위해 HTTP 요청을 보냄beautifulsoup4— HTML을 파싱해서 특정 요소를 찾을 수 있게 함pandas— 추출한 데이터를 표로 정리하고 CSV/Excel로 내보냄lxml— 빠른 HTML 파서(BeautifulSoup의 백엔드로 사용 가능)
가져오기 코드는 다음과 같습니다.
1import requests
2from bs4 import BeautifulSoup
3import pandas as pd
2단계: IMDb에 HTTP 요청 보내기
초보자들이 가장 먼저 걸리는 지점입니다. IMDb는 올바른 User-Agent 헤더가 없는 요청을 막습니다. 그러면 403 Forbidden 오류가 뜹니다. Python Requests의 기본 user-agent 문자열(python-requests/2.31.0)은 바로 차단됩니다.
1url = "https://www.imdb.com/chart/top/"
2headers = {
3 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
4 "Accept-Language": "en-US,en;q=0.9"
5}
6response = requests.get(url, headers=headers)
7if response.status_code != 200:
8 print(f"페이지를 가져오지 못했습니다: {response.status_code}")
9else:
10 print("페이지를 성공적으로 가져왔습니다")
Accept-Language 헤더도 꽤 중요합니다. 이 값이 없으면 IMDb가 IP의 지리적 위치에 따라 다른 언어로 콘텐츠를 내려줄 수 있습니다.
3단계: BeautifulSoup으로 HTML 파싱하기
HTML을 가져왔다면 BeautifulSoup 객체를 만들고, 원하는 요소를 찾으면 됩니다. Chrome에서 IMDb Top 250 페이지를 열고, 영화 제목을 우클릭한 뒤 "Inspect"를 눌러 실제 HTML 구조를 확인해 보세요.
1soup = BeautifulSoup(response.text, "lxml")
2025년 중반 기준 Top 250 페이지는 다음 선택자를 사용합니다.
- 영화 컨테이너:
li.ipc-metadata-list-summary-item - 제목:
h3.ipc-title__text - 연도:
span.cli-title-metadata-item(첫 번째 span) - 평점:
span.ipc-rating-star--rating
주의할 점은, ipc-로 시작하는 클래스명은 IMDb의 컴포넌트 시스템에서 자동 생성된다는 것입니다. 2023년 6월 리디자인 이후로는 비교적 안정적이었지만, 다시 바뀌지 않는다는 보장은 없습니다.
4단계: 영화 데이터 추출하기(제목, 연도, 평점)
대부분의 튜토리얼과 다른 점이 하나 있습니다. 저는 try/except 예외 처리를 넣습니다. 제가 검토한 다른 가이드들 대부분은 이 부분이 빠져 있었고, 그래서 선택자가 바뀌었을 때 조용히 깨지는 경우가 많았습니다.
1movies = []
2movie_items = soup.select("li.ipc-metadata-list-summary-item")
3for item in movie_items:
4 try:
5 title_tag = item.select_one("h3.ipc-title__text")
6 title = title_tag.text.strip() if title_tag else "N/A"
7 year_tag = item.select_one("span.cli-title-metadata-item")
8 year = year_tag.text.strip() if year_tag else "N/A"
9 rating_tag = item.select_one("span.ipc-rating-star--rating")
10 rating = rating_tag.text.strip() if rating_tag else "N/A"
11 movies.append({
12 "title": title,
13 "year": year,
14 "rating": rating
15 })
16 except Exception as e:
17 print(f"영화 파싱 중 오류 발생: {e}")
18 continue
19print(f"{len(movies)}편의 영화를 추출했습니다")
5단계: Pandas로 CSV 또는 Excel 저장하기
1df = pd.DataFrame(movies)
2df.to_csv("imdb_top_250.csv", index=False)
3df.to_excel("imdb_top_250.xlsx", index=False)
4print(df.head())
예시 출력:
1 title year rating
20 1. The Shawshank Redemption 1994 9.3
31 2. The Godfather 1972 9.2
42 3. The Dark Knight 2008 9.0
53 4. The Godfather Part II 1974 9.0
64 5. 12 Angry Men 1957 9.0
작동은 합니다. 하지만 언제든 깨질 수 있는 CSS 선택자에 기대고 있다는 게 문제입니다. 그래서 좀 더 추천할 만한 방법으로 넘어가겠습니다.
방법 2: JSON-LD 활용하기 — HTML 파싱을 아예 건너뛰는 방식
이 방법은 경쟁 글에서 거의 다루지 않지만, 진짜 프로젝트라면 제가 가장 먼저 쓰는 방식입니다. IMDb는 모든 페이지에 <script type="application/ld+json"> 태그 안에 (Linked Data용 JSON)를 넣어 둡니다. 이 데이터는 Schema.org 표준을 따르고, Google의 리치 검색 결과에도 쓰이며, CSS 클래스명보다 훨씬 덜 자주 바뀝니다.
프로덕션 수준의 도구인 Apify IMDb Scraper도 추출 우선순위를 "JSON-LD > NEXT_DATA > DOM"으로 둡니다. 저도 이 순서를 추천합니다.
JSON-LD가 CSS 선택자보다 더 신뢰할 수 있는 이유
| 접근 방식 | JS 콘텐츠 처리 가능? | UI 변경에 강한가? | 속도 | 복잡도 |
|---|---|---|---|---|
| BeautifulSoup + CSS 선택자 | ❌ 아니오 | ⚠️ 취약(클래스명 변경) | 빠름 | 낮음 |
| JSON-LD 추출 | ✅ 예 | ✅ Schema.org 표준 준수 | 빠름 | 낮음~중간 |
__NEXT_DATA__ JSON 추출 | ✅ 예 | ✅ 비교적 안정적 | 빠름 | 낮음~중간 |
| Selenium / Playwright | ✅ 예 | ⚠️ 취약 | 느림 | 중간~높음 |
| Thunderbit (노코드, 2클릭) | ✅ 예(AI가 페이지를 읽음) | ✅ AI가 자동 적응 | 빠름 | 없음 |
ipc-metadata-list-summary-item 같은 CSS 클래스명은 IMDb의 React 컴포넌트 시스템에서 자동 생성되며, 리디자인 때마다 바뀔 수 있습니다. 반면 JSON-LD 스키마는 화면에 어떻게 보이느냐가 아니라 실제 데이터 모델을 나타냅니다. 책의 글자 크기로 챕터를 맞히는 것과 목차를 보는 것의 차이처럼 생각하면 됩니다.

단계별: JSON-LD에서 IMDb 데이터 추출하기
1단계: 페이지 불러오기
앞과 동일하게, 적절한 User-Agent 헤더를 넣어 requests를 사용합니다.
1import requests
2from bs4 import BeautifulSoup
3import json
4import pandas as pd
5url = "https://www.imdb.com/chart/top/"
6headers = {
7 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
8 "Accept-Language": "en-US,en;q=0.9"
9}
10response = requests.get(url, headers=headers)
11soup = BeautifulSoup(response.text, "lxml")
2단계: JSON-LD 스크립트 태그 찾기
1script_tag = soup.find("script", {"type": "application/ld+json"})
2if not script_tag:
3 print("이 페이지에서 JSON-LD를 찾지 못했습니다")
4else:
5 data = json.loads(script_tag.string)
6 print(f"JSON-LD 유형을 찾았습니다: {data.get('@type', 'unknown')}")
3단계: 구조화된 데이터 파싱하기
Top 250 페이지의 JSON-LD에는 250편의 영화가 들어 있는 itemListElement 배열이 있습니다. 각 항목에는 순위, 이름, URL, aggregateRating, 개봉일, 장르, 설명, 감독, 배우 배열 등이 포함됩니다.
1movies = []
2for item in data.get("itemListElement", []):
3 movie = item.get("item", {})
4 rating_info = movie.get("aggregateRating", {})
5 movies.append({
6 "rank": item.get("position"),
7 "title": movie.get("name"),
8 "url": movie.get("url"),
9 "rating": rating_info.get("ratingValue"),
10 "vote_count": rating_info.get("ratingCount"),
11 "date_published": movie.get("datePublished"),
12 "genre": ", ".join(movie.get("genre", [])),
13 "description": movie.get("description"),
14 })
4단계: CSV로 내보내기
1df = pd.DataFrame(movies)
2df.to_csv("imdb_top_250_json_ld.csv", index=False)
3print(df.head())
예시 출력:
1 rank title url rating vote_count date_published genre
20 1 The Shawshank Redemption https://www.imdb.com/title/tt0111161/ 9.3 2900000 1994-10-14 Drama
31 2 The Godfather https://www.imdb.com/title/tt0068646/ 9.2 2000000 1972-03-24 Crime, Drama
42 3 The Dark Knight https://www.imdb.com/title/tt0468569/ 9.0 2800000 2008-07-18 Action, Crime, Drama
250편 전부를 깔끔하게, 구조적으로 가져올 수 있습니다. CSS 선택자랑 씨름할 필요도 없습니다. 게다가 이 데이터는 Google이 검색 결과에 활용하는 Schema.org 표준을 따르기 때문에, 화면 레이아웃보다 훨씬 덜 자주 바뀝니다.
보너스: 개별 영화 페이지의 __NEXT_DATA__
개별 타이틀 페이지에서 상영 시간, 전체 출연진, 줄거리 요약, 포스터 이미지 같은 더 풍부한 정보를 원한다면 IMDb는 __NEXT_DATA__ JSON 객체도 넣어 둡니다. React가 페이지를 하이드레이션할 때 쓰는 데이터라서, 사이트를 크게 뜯지 않는 이상 쉽게 없어지지 않습니다.
1# /title/tt0111161/ 같은 개별 영화 페이지에서
2next_data_tag = soup.find("script", {"id": "__NEXT_DATA__"})
3if next_data_tag:
4 next_data = json.loads(next_data_tag.string)
5 above_fold = next_data["props"]["pageProps"]["aboveTheFoldData"]
6 title = above_fold["titleText"]["text"]
7 year = above_fold["releaseYear"]["year"]
8 rating = above_fold["ratingsSummary"]["aggregateRating"]
9 runtime_seconds = above_fold.get("runtime", {}).get("seconds", 0)
10 genres = [g["text"] for g in above_fold["genres"]["genres"]]
11 plot = above_fold["plot"]["plotText"]["plainText"]
차트/목록 페이지에는 JSON-LD를, 개별 타이틀 페이지에는 __NEXT_DATA__를 쓰면 됩니다. 이게 프로덕션용 접근 방식입니다.
왜 IMDb 스크래퍼는 계속 깨질까? 그리고 어떻게 고칠까?
IMDb 크롤링 포럼에서 가장 자주 나오는 하소연이 바로 이겁니다. 사용자들은 "UI가 바뀌어서 코드 일부가 깨졌어요" 또는 *"2024년부터는 안 됩니다!"*라고 말하지만, 돌아오는 답변은 대개 침묵이거나 "Selenium을 써보세요" 정도입니다.
근본 원인은 IMDb가 React/Next.js 프런트엔드로 계속 옮겨가고 있기 때문입니다. 주요 변경 타임라인은 다음과 같습니다.
| 날짜 | 변경 내용 | 깨진 항목 |
|---|---|---|
| 2022년 11월 | 이름 페이지 개편 | 기존 이름 페이지 스크래퍼 |
| 2023년 6월 | Top 250 페이지 개편 | 모든 td.titleColumn / td.ratingColumn 선택자 |
| 2023년 4월 | 타이틀 하위 페이지 개편 | 소개, 수상, 뉴스 스크래퍼 |
| 2023년 10월 | 고급 검색 개편 | 검색 기반 스크래퍼 |
| 2025년 6월 | /reference 페이지 개편 | Cinemagoer 라이브러리 (대부분의 파서) |
대략 6~12개월마다 큰 변화가 한 번씩 있는 셈입니다. CSS 클래스명에 기대고 있다면, 끊임없이 따라잡아야 하는 러닝머신 위에 올라탄 것과 같습니다.
흔한 오류와 해결법
빈 결과 / NoneType 오류
가장 흔한 오류입니다. AttributeError: 'NoneType' object has no attribute 'text'가 뜰 겁니다. 이는 BeautifulSoup이 찾으려는 요소를 못 찾았다는 뜻입니다. 보통 CSS 클래스명이 바뀌었거나, 해당 콘텐츠가 JavaScript로 렌더링되기 때문입니다.
해결: JSON-LD 추출(위 방법 2)로 바꾸세요. 데이터가 초기 HTML 응답 안에 있어서 JavaScript가 필요 없습니다.
403 Forbidden
IMDb는 를 사용해 봇을 탐지하고 차단합니다. 가장 큰 원인은 없거나 너무 티 나는 User-Agent 헤더입니다. 오픈소스 프로젝트와 에서도 이 문제가 언급됐고, IMDb 직원도 이를 인정한 바 있습니다.
해결: 실제 브라우저처럼 보이는 User-Agent와 Accept-Language: en-US 헤더를 항상 포함하세요. 연결 재사용을 위해 requests.Session()도 쓰면 좋습니다.
결과가 25개만 반환됨
IMDb 검색 페이지와 "Most Popular" 목록은 지연 로딩을 사용합니다. 처음에는 약 25개 정도만 렌더링되고, 스크롤할 때 AJAX로 더 불러옵니다.
해결: URL 매개변수 페이지네이션(다음 섹션 참고)을 사용하거나, 한 번의 응답으로 250편을 모두 불러오는 Top 250 페이지를 사용하세요.
선택자가 갑자기 작동하지 않음
예전 선택자 중 더 이상 작동하지 않는 것들: td.titleColumn, td.ratingColumn, .lister-item-header, .inline-block.ratings-imdb-rating. 코드가 이 중 하나라도 쓰고 있다면 이미 깨진 상태입니다.
해결: 자동 생성 클래스명보다 data-testid 속성(예: h1[data-testid="hero-title-block__title"])을 우선 사용하세요. 더 좋은 방법은 JSON-LD를 쓰는 것입니다.
판단 기준: 단기 해결 vs 장기 해결
- 빠른 해결책: 모든 선택자에
try/except를 넣고, HTTP 상태 코드를 확인하고, 오류를 내지 말고 로그만 남기기 - 중간 단계 해결책: CSS 선택자에서 JSON-LD 추출로 전환하기(방법 2)
- 장기 해결책: 대규모 분석에는 을 사용하거나, 페이지 구조를 매번 새로 읽는 AI 기반 도구인 을 쓰기. 유지할 선택자가 없고, AI가 레이아웃 변경에 자동으로 적응합니다.
25개 결과 제한을 넘어서: IMDb 페이지네이션과 대용량 데이터 크롤링
제가 확인한 경쟁 튜토리얼들은 거의 전부 정확히 한 페이지만 가져옵니다. 페이지네이션을 다루는 글은 거의 없었죠. 하지만 한 목록 이상이 필요해지는 순간, 금방 한계에 부딪힙니다.
페이지네이션이 필요 없는 페이지
좋은 소식은 Top 250 페이지가 서버 렌더링 응답 한 번으로 250편 전부를 불러온다는 점입니다. JSON-LD와 __NEXT_DATA__ 둘 다 전체 데이터셋을 담고 있습니다. 페이지네이션은 필요 없습니다.
IMDb 검색 페이지의 페이지네이션 방식
IMDb 검색 페이지는 start= URL 매개변수를 사용하며, 50씩 증가합니다.
1https://www.imdb.com/search/title/?groups=top_1000&start=1
2https://www.imdb.com/search/title/?groups=top_1000&start=51
3https://www.imdb.com/search/title/?groups=top_1000&start=101
아래는 결과를 넘기면서 수집하는 Python 루프입니다.
1import time
2all_movies = []
3for start in range(1, 1001, 50): # Top 1000 페이지를 순회
4 url = f"https://www.imdb.com/search/title/?groups=top_1000&start={start}"
5 response = requests.get(url, headers=headers)
6 if response.status_code != 200:
7 print(f"start={start}에서 실패: {response.status_code}")
8 break
9 soup = BeautifulSoup(response.text, "lxml")
10 # 원하는 방식으로 영화 데이터 추출
11 # ...
12 print(f"start={start}부터 시작하는 페이지를 크롤링했습니다")
13 time.sleep(3) # 예의를 지키세요 — IMDb는 빠른 요청 약 50회 후 차단할 수 있습니다
여기서 time.sleep(3)은 중요합니다. 커뮤니티 보고에 따르면 IMDb는 빠른 요청이 약 50회 정도 쌓이면 IP를 막기 시작합니다. 2~5초 사이의 랜덤 지연을 두는 게 좋습니다.
아예 크롤링을 건너뛰어도 되는 경우: IMDb 공식 벌크 데이터셋
정말 대규모가 필요하다면 IMDb는 에서 매일 갱신되는 7개의 무료 TSV 파일을 제공합니다.
| 파일 | 내용 | 용량 | |---|---|---|---| | title.basics.tsv.gz | 제목, 유형, 장르, 상영 시간, 연도 | 약 800MB | | title.ratings.tsv.gz | 평균 평점, 투표 수 | 약 25MB | | title.crew.tsv.gz | 감독, 작가 | 약 300MB | | title.principals.tsv.gz | 주요 출연/스태프 | 약 2GB | | title.akas.tsv.gz | 지역별 대체 제목 | 약 1.5GB | | title.episode.tsv.gz | TV 에피소드 정보 | 약 200MB | | name.basics.tsv.gz | 인물: 이름, 출생 연도, 대표 작품 | 약 700MB |
Pandas로 불러오는 것도 어렵지 않습니다.
1ratings = pd.read_csv("title.ratings.tsv.gz", sep="\t", compression="gzip")
2basics = pd.read_csv("title.basics.tsv.gz", sep="\t", compression="gzip", low_memory=False)
3# tconst(IMDb 타이틀 ID) 기준 병합
4merged = basics.merge(ratings, on="tconst")
5top_movies = merged[merged["titleType"] == "movie"].nlargest(250, "averageRating")
이 데이터셋은 2,600만 개가 넘는 타이틀을 다룹니다. 페이지네이션도 없고, 선택자도 없고, 403 오류도 없습니다. 다만 라이선스는 개인 및 비상업적 용도로만 허용되며, 데이터를 재배포하거나 재판매할 수는 없습니다.
노코드 지름길: Thunderbit가 페이지네이션까지 처리
페이지네이션된 IMDb 데이터가 필요하지만 직접 페이지 이동 로직을 만들고 싶지 않다면, 이 클릭 기반 페이지네이션과 무한 스크롤을 기본으로 지원합니다. 크롤링하라고만 하면 나머지는 Thunderbit이 알아서 처리합니다. 지연 로딩 콘텐츠 스크롤까지 포함해서요.
Python으로 IMDb 크롤링하기: 완전한 작동 코드(복붙용)
지금 바로 실행할 수 있는 독립형 스크립트 두 개를 소개합니다.
스크립트 A: BeautifulSoup 방식(CSS 선택자)
1import requests
2from bs4 import BeautifulSoup
3import pandas as pd
4url = "https://www.imdb.com/chart/top/"
5headers = {
6 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
7 "Accept-Language": "en-US,en;q=0.9"
8}
9response = requests.get(url, headers=headers)
10if response.status_code != 200:
11 print(f"오류: {response.status_code}")
12 exit()
13soup = BeautifulSoup(response.text, "lxml")
14movie_items = soup.select("li.ipc-metadata-list-summary-item")
15movies = []
16for item in movie_items:
17 try:
18 title = item.select_one("h3.ipc-title__text")
19 year = item.select_one("span.cli-title-metadata-item")
20 rating = item.select_one("span.ipc-rating-star--rating")
21 movies.append({
22 "title": title.text.strip() if title else "N/A",
23 "year": year.text.strip() if year else "N/A",
24 "rating": rating.text.strip() if rating else "N/A",
25 })
26 except Exception as e:
27 print(f"오류로 인해 영화 건너뜀: {e}")
28df = pd.DataFrame(movies)
29df.to_csv("imdb_top250_bs4.csv", index=False)
30print(f"{len(df)}편의 영화를 저장했습니다")
31print(df.head())
스크립트 B: JSON-LD 방식(권장)
1import requests
2from bs4 import BeautifulSoup
3import json
4import pandas as pd
5url = "https://www.imdb.com/chart/top/"
6headers = {
7 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
8 "Accept-Language": "en-US,en;q=0.9"
9}
10response = requests.get(url, headers=headers)
11if response.status_code != 200:
12 print(f"오류: {response.status_code}")
13 exit()
14soup = BeautifulSoup(response.text, "lxml")
15script_tag = soup.find("script", {"type": "application/ld+json"})
16if not script_tag:
17 print("JSON-LD 데이터를 찾지 못했습니다")
18 exit()
19data = json.loads(script_tag.string)
20movies = []
21for item in data.get("itemListElement", []):
22 movie = item.get("item", {})
23 rating_info = movie.get("aggregateRating", {})
24 directors = movie.get("director", [])
25 director_names = ", ".join(
26 d.get("name", "") for d in (directors if isinstance(directors, list) else [directors])
27 )
28 movies.append({
29 "rank": item.get("position"),
30 "title": movie.get("name"),
31 "url": movie.get("url"),
32 "rating": rating_info.get("ratingValue"),
33 "votes": rating_info.get("ratingCount"),
34 "year": movie.get("datePublished", "")[:4],
35 "genre": ", ".join(movie.get("genre", [])),
36 "director": director_names,
37 "description": movie.get("description"),
38 })
39df = pd.DataFrame(movies)
40df.to_csv("imdb_top250_jsonld.csv", index=False)
41print(f"{len(df)}편의 영화를 저장했습니다")
42print(df.head())
두 스크립트 모두 예외 처리가 포함되어 있고, 깔끔한 CSV를 만들어 줍니다. 스크립트 B는 감독, 설명, URL까지 더 풍부한 데이터를 주고, 레이아웃 변경에도 더 강합니다.
코딩 없이 IMDb를 크롤링하는 방법(Thunderbit 사용)
모두가 Python을 쓰고 싶어 하는 건 아닙니다. 이번 주 최고 평점 영화를 스프레드시트에 바로 넣고 싶은 운영 분석가일 수도 있고, 연도별 장르 추이를 비교하려는 콘텐츠 전략 담당자일 수도 있습니다. 이런 경우라면 스크래퍼를 직접 만드는 건 과한 일입니다.
으로 같은 데이터를 가져오는 방법은 다음과 같습니다.
시작하기 전에:
- 난이도: 초급
- 소요 시간: 약 2분
- 준비물: Chrome 브라우저, (무료 플랜 사용 가능)
1단계: 크롤링할 IMDb 페이지로 이동합니다. Chrome에서 IMDb Top 250 또는 원하는 IMDb 목록/검색 페이지를 엽니다.
2단계: Thunderbit 사이드바에서 "AI 필드 추천"을 클릭합니다. AI가 페이지를 스캔하고 열 이름을 제안합니다. 보통 제목, 연도, 평점, 장르, 그리고 페이지에 따라 몇 가지 항목이 더 나옵니다. 제안된 필드 미리보기 표도 볼 수 있습니다.
3단계: 필요하면 필드를 조정합니다. 필요 없는 열은 지우고, "+ 열 추가"를 눌러 "감독 이름"이나 "투표 수"처럼 자연어로 원하는 내용을 입력해 맞춤 열을 추가할 수 있습니다.
4단계: "크롤링"을 클릭합니다. Thunderbit이 데이터를 추출합니다. 무한 스크롤이나 페이지네이션이 있는 페이지도 자동으로 처리합니다.
5단계: 내보내기 버튼을 눌러 Excel, Google Sheets, CSV, Airtable, Notion 중 원하는 형식을 선택하세요. 데이터는 몇 초 안에 목적지에 들어갑니다.
핵심 장점은 단순한 편의성만이 아닙니다. Thunderbit의 AI는 매번 페이지 구조를 새로 읽기 때문에, IMDb가 레이아웃을 바꿔도 AI가 알아서 적응합니다. 업데이트할 선택자도, 고칠 코드도 없습니다. 마감 직전 새벽 2시에 스크래퍼가 깨져본 적이 있는 사람이라면 이 장점이 얼마나 큰지 바로 알 겁니다.
Thunderbit은 하위 페이지 크롤링도 지원합니다. 각 영화의 상세 페이지로 들어가 출연진, 감독, 상영 시간, 리스트 페이지에서는 보이지 않는 다른 필드까지 더 풍부하게 보강할 수 있습니다. 실제 동작이 궁금하다면 을 확인해 보세요.
IMDb 크롤링은 합법일까? 알아야 할 것들
포럼에서는 이런 질문이 종종 직접적으로 올라옵니다. "이런 거 합법인가요? IMDb는 사람들이 자기 웹사이트를 크롤링하는 걸 원하지 않잖아요." 충분히 나올 만한 질문이고, 다른 글들은 이 부분을 거의 다루지 않습니다.
IMDb robots.txt: Top 250 차트(/chart/top/), 개별 타이틀 페이지(/title/ttXXXXXXX/), 이름 페이지(/name/nmXXXXXXX/)는 robots.txt에서 차단되어 있지 않습니다. 차단된 경로에는 /find, /_json/*, /search/name-text, /user/ur*/ratings, 그리고 여러 AJAX 엔드포인트가 포함됩니다. Crawl-delay 지시문은 따로 없습니다.
IMDb 이용 약관: 관련 조항에는 다음과 같이 적혀 있습니다. "귀하는 당사의 명시적인 서면 동의 없이는 이 사이트에서 데이터 마이닝, 로봇, 화면 스크래핑 또는 이와 유사한 데이터 수집 및 추출 도구를 사용할 수 없습니다." 또 스크랩한 데이터의 재판매나 상업적 이용을 금지하는 조항도 있습니다.
실무적으로는 무엇을 뜻할까? 2024년의 최근 판결들(Meta v. Bright Data, X Corp v. Bright Data)에 따르면, 실제로 약관에 동의한 적이 없는 사용자에게는 ToS가 법적으로 구속력을 가지지 않을 수도 있습니다. 로그인하지 않고 공개 데이터를 수집하는 경우라면 ToS 집행 가능성은 논쟁적입니다. 다만 이 부분은 계속 바뀌고 있습니다.
안전한 대안: IMDb의 은 개인 및 비상업적 용도로 명시적으로 허용됩니다. TMDb API도 무료 키만 있으면 비교적 자유롭게 사용할 수 있습니다. 명확하게 안전한 선택지를 원한다면 둘 다 좋은 대안입니다.
실무 가이드: 크롤링을 한다면 요청 간 time.sleep(3) 정도는 두고, 적절한 헤더를 설정하고, robots.txt에서 차단된 경로는 건드리지 마세요. 상업 프로젝트라면 법률 전문가와 상의하거나 공식 데이터셋/API를 쓰는 편이 낫습니다.
Thunderbit 블로그에서는 을 자세히 다뤘습니다.
결론: Python으로 IMDb를 크롤링하는 올바른 방법을 고르자
짧게 정리하면 이렇습니다.
- BeautifulSoup + CSS 선택자: 기본기를 배우기엔 좋습니다. 다만 6~12개월마다 깨질 가능성을 염두에 두세요. 예외 처리는 꼭 넣으세요.
- JSON-LD 추출: 계속 운영할 Python 프로젝트라면 제가 가장 추천하는 방식입니다. Schema.org 표준을 따르고, CSS 클래스보다 훨씬 덜 바뀌며, JavaScript 렌더링 없이도 깔끔한 구조화 데이터를 얻을 수 있습니다.
__NEXT_DATA__JSON: 개별 타이틀 페이지에서 더 풍부한 정보(상영 시간, 전체 출연진, 줄거리, 포스터 이미지)를 추가로 가져올 때 쓰면 좋습니다.- IMDb 공식 데이터셋: 대규모 분석에는 가장 좋은 선택입니다. 2,600만+ 타이틀, 일일 업데이트, 스크래핑 불필요. 개인/비상업적 사용만 가능합니다.
- : 코딩을 원하지 않거나, 코드를 유지보수하지 않고 빠르게 데이터를 받고 싶은 사람에게 가장 잘 맞는 선택입니다. AI가 레이아웃 변경에 적응하고, 페이지네이션을 처리하며, Excel/Sheets/Airtable/Notion으로 내보낼 수 있습니다.
이 가이드를 북마크해 두세요. IMDb 구조가 다음에 바뀌면 업데이트하겠습니다. 그리고 코드를 완전히 건너뛰고 싶다면 IMDb 페이지에서 깔끔한 스프레드시트까지 얼마나 빨리 갈 수 있는지 확인해 보세요. 다른 사이트도 함께 다룬다면, 가이드에서 더 넓은 작업 흐름도 확인할 수 있습니다.
자주 묻는 질문
IMDb 크롤링은 합법인가요?
IMDb의 이용 약관은 동의 없는 크롤링을 금지하지만, 2024년의 최근 판결 이후 공개 접근 데이터에 대한 ToS의 집행 가능성은 법적으로 논쟁의 여지가 있습니다. 가장 안전한 선택은 IMDb의 (개인/비상업적 용도) 또는 TMDb API(무료 키)입니다. 직접 크롤링할 경우 robots.txt를 준수하고, 요청 사이에 적절한 지연을 두며, 차단된 경로는 피하세요. 상업적 사용이라면 법률 전문가와 상담하세요.
IMDb 스크래퍼가 왜 빈 결과를 반환하나요?
대부분 원인은 오래된 CSS 선택자입니다. td.titleColumn이나 td.ratingColumn 같은 클래스는 2023년 6월 이후 더 이상 존재하지 않습니다. 해결책은 JSON-LD 추출(<script type="application/ld+json"> 태그 파싱)으로 바꾸거나, 현재의 ipc- 접두사 클래스로 선택자를 업데이트하는 것입니다. 또한 올바른 User-Agent 헤더가 들어 있는지도 확인하세요. 헤더가 없으면 403 오류가 나고, 이게 빈 결과처럼 보일 수 있습니다.
IMDb에서 25개보다 많은 결과를 어떻게 가져오나요?
Top 250 페이지는 250편을 한 번에 전부 불러오므로 페이지네이션이 필요 없습니다. 검색 결과의 경우 start= URL 매개변수를 사용해 50개씩 넘기면 됩니다. 예: start=1, start=51, start=101. 요청 사이에 time.sleep(3)을 넣어 차단을 피하세요. 또는 의 IMDb 공식 데이터셋을 사용하면 2,600만+ 타이틀을 페이지네이션 없이 다룰 수 있습니다.
__NEXT_DATA__는 무엇이고, IMDb 크롤링에 왜 써야 하나요?
__NEXT_DATA__는 IMDb의 React/Next.js 페이지에 있는 <script id="__NEXT_DATA__"> 태그 안의 JSON 객체입니다. 제목, 평점, 출연진, 장르, 상영 시간 등 React가 페이지를 렌더링하는 데 쓰는 완전한 구조화 데이터를 담고 있습니다. 시각적 레이아웃이 아니라 내부 데이터 모델을 나타내기 때문에, CSS 선택자보다 UI 개편에 훨씬 강합니다. JSON-LD와 함께 사용하면 가장 견고한 추출 방식이 됩니다.
코딩 없이 IMDb를 크롤링할 수 있나요?
네. 두 가지 주요 방법이 있습니다. (1) IMDb의 을 다운로드하세요. 2,600만+ 타이틀을 담은 7개의 TSV 파일이며, 일일 업데이트되고 비상업적 용도로 무료입니다. (2) 를 사용하세요. IMDb 페이지를 읽고, 추출 필드를 자동으로 추천하며, Excel, Google Sheets, CSV로 두 번의 클릭만에 내보낼 수 있습니다. 코드도 없고, 유지할 선택자도 없습니다.
더 알아보기
