Type something to search...

02 네이버 영화 리뷰 크롤링

1. 파이썬 가상환경 설정

  1. 프로젝트 폴더를 만들고 해당 폴더로 이동한다.
Terminal window
1
mkdir crawling_project
2
cd crawling_project
  1. 현재 폴더에 .venv 이름으로 가상환경을 생성한다.
Terminal window
1
python -m venv .venv
  1. 가상환경을 활성화한다.

Windows (cmd/PowerShell):

Terminal window
1
.venv\Scripts\activate

Mac/Linux (bash):

Terminal window
1
source .venv/bin/activate

가상환경이 활성화되면 터미널의 프롬프트 앞에 (.venv)가 표시된다. 이후 설치하는 모든 패키지는 이 가상환경 안에만 설치된다.

2. 필요한 패키지 설치

  1. 다음 명령어를 실행하여 크롤링에 필요한 패키지들을 설치한다.
Terminal window
1
pip install requests beautifulsoup4 lxml pandas
패키지설명
requests웹 서버에 HTTP 요청을 보내고 응답(HTML)을 받아오는 라이브러리다.
beautifulsoup4받아온 HTML을 파싱(분석)하여 원하는 태그를 쉽게 찾아주는 라이브러리다.
lxmlBeautifulSoup이 사용하는 빠르고 안정적인 HTML 파서다.
pandas데이터프레임 형식으로 CSV 파일을 읽고 데이터를 분석하는 라이브러리다.

3. BeautifulSoup 기초

crawling.py
1
from bs4 import BeautifulSoup
코드설명
1from bs4 import BeautifulSoupbs4 라이브러리에서 BeautifulSoup 클래스를 가져온다. HTML/XML을 파싱(분석)하는 도구다.

crawling.py
1
html_doc = """
2
<html><head><title>MangoTitle</title></head>
3
<body>
4
<p class="title">Mango</p>
5
6
<p class="story">Once upon a time there were three little sisters;
7
and their names were
8
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
9
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a>
10
and <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
11
and they lived at the bottom of a well.</p>
12
<p class="story">...</p>
13
"""
코드설명
1html_doc파싱할 HTML 문서를 담는 변수다.
2-13"""..."""삼중 따옴표로 여러 줄을 하나의 문자열로 저장한다.
3<title>MangoTitle</title>브라우저 탭에 표시되는 페이지 제목 태그다.
5<p class="title">class 속성이 "title"인 단락(<p>) 태그다.
9<a href="..." class="sister" id="link1">링크 태그다. href는 이동할 주소, class는 분류명, id는 고유 식별자다.

crawling.py
1
soup = BeautifulSoup(html_doc, 'lxml')
코드설명
1soup파싱된 HTML 트리 전체를 담는 객체다. 이후 모든 탐색은 이 객체를 통해 한다.
1BeautifulSoup(html_doc, 'lxml')html_doc 문자열을 lxml 파서로 분석해 탐색 가능한 객체를 만든다.
1'lxml'HTML을 해석하는 파서의 종류. 빠르고 안정적이다. (pip install lxml 필요)

crawling.py
1
print("soup.body.p의 결과 : ", soup.body.p)
코드설명
1soup.body<body> 태그에 접근한다.
1soup.body.p<body> 안의 첫 번째 <p> 태그를 반환한다.
1print(...)결과를 화면에 출력한다.

crawling.py
1
print("soup.a['href']의 결과 : ", soup.a['href'])
코드설명
1soup.a문서 전체에서 첫 번째 <a> 태그에 접근한다.
1['href']태그의 속성값을 딕셔너리처럼 꺾쇠 괄호로 꺼낸다. href 속성의 URL을 반환한다.

crawling.py
1
print("soup.title.name의 결과 : ", soup.title.name)
코드설명
1soup.title<title> 태그에 접근한다.
1.name태그명 문자열을 반환한다.

crawling.py
1
print("soup.title.string의 결과 : ", soup.title.string)
코드설명
1.string태그 안의 텍스트 내용을 반환한다. 반환 타입은 NavigableString(문자열과 동일하게 사용 가능)이다.

crawling.py
1
print("soup.contents의 결과 : ", soup.contents)
코드설명
1soup.contentssoup 객체의 직접 자식 요소들을 리스트로 반환한다.

crawling.py
1
print("soup.find()의 결과 : ", soup.find('a', attrs={'class' : 'sister'}))
코드설명
1find()조건에 맞는 태그를 딱 하나만 찾아서 반환한다. 여러 개여도 첫 번째만 반환한다.
1'a'찾을 태그 이름. <a> 태그를 찾겠다는 의미다.
1attrs={'class': 'sister'}속성 조건. class"sister"인 태그만 찾는다. class는 파이썬 예약어라 attrs 딕셔너리로 전달한다.

crawling.py
1
print("soup.find_all()의 결과 : ", soup.find_all('a', limit=2))
코드설명
1find_all()조건에 맞는 태그를 모두 찾아 리스트로 반환한다.
1'a'<a> 태그를 모두 찾겠다는 의미다.
1limit=2최대 몇 개까지 찾을지 제한한다. 여기서는 2개만 반환한다.

find() vs find_all() 비교

find()find_all()
반환값태그 1개 (없으면 None)태그 리스트 (없으면 빈 리스트 [])
용도첫 번째 결과만 필요할 때모든 결과를 가져올 때
limit 옵션없음있음 (개수 제한)

4. 네이버 영화 리뷰 크롤링

4.1. 크롤링 대상 확인

네이버 영화 페이지에서 영화 코드를 확인한다.

1
https://movie.naver.com/movie/bi/mi/basic.naver?code=XXXXXX
2
3
이 숫자가 영화 코드

참고

영화 리뷰 목록 URL 구조: https://movie.naver.com/movie/bi/mi/pointWriteFormList.naver?pointMovieCode=영화코드&page=페이지번호

4.2. 단일 페이지 리뷰 수집

  1. crawling.py 파일을 생성하고 아래 코드를 작성한다.
crawling.py
1
import requests
2
from bs4 import BeautifulSoup
3
4
MOVIE_CODE = "17909"
5
PAGE = 1
6
7
url = (
8
f"https://movie.naver.com/movie/bi/mi/pointWriteFormList.naver"
9
f"?pointMovieCode={MOVIE_CODE}&type=after&isActualPointView=true&page={PAGE}"
10
)
11
12
headers = {
13
"User-Agent": (
14
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
15
"AppleWebKit/537.36 (KHTML, like Gecko) "
16
"Chrome/120.0.0.0 Safari/537.36"
17
)
18
}
19
20
response = requests.get(url, headers=headers)
21
response.encoding = "utf-8"
22
23
soup = BeautifulSoup(response.text, "lxml")
24
25
reviews = soup.select("div.score_reple p")
26
scores = soup.select("div.star_score em")
27
28
for score, review in zip(scores, reviews):
29
print(f"[{score.text}점] {review.text.strip()}")
코드설명
2import requestsHTTP 요청을 보내는 라이브러리다.
5MOVIE_CODE = "17909"크롤링할 영화 코드다. (아바타)
8~11url = (...)크롤링할 URL을 구성한다.
13~19headers = {...}요청 헤더다. User-Agent를 설정하여 봇으로 인식되지 않도록 한다.
21requests.get(url, headers=headers)지정한 URL에 GET 요청을 보낸다. headers로 봇 차단을 피한다.
22response.encoding = "utf-8"한글이 깨지지 않도록 인코딩을 명시적으로 지정한다.
24soup = BeautifulSoup(response.text, "lxml")HTML을 파싱하여 탐색 가능한 객체로 만든다.
26~27reviews, scores — CSS 선택자로 리뷰 텍스트와 별점을 가져온다.CSS 선택자로 리뷰 텍스트와 별점을 가져온다.
29~30for 루프별점과 리뷰를 쌍으로 묶어 동시에 순회한다.

4.3. 여러 페이지 리뷰 수집 + CSV 저장

  1. crawling.py 파일을 생성하고 아래 코드를 작성한다.
crawling.py
1
import requests
2
import csv
3
import time
4
from bs4 import BeautifulSoup
5
6
MOVIE_CODE = "17909"
7
MAX_PAGE = 5
8
9
headers = {
10
"User-Agent": (
11
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
12
"AppleWebKit/537.36 (KHTML, like Gecko) "
13
"Chrome/120.0.0.0 Safari/537.36"
14
)
15
}
16
17
results = []
18
19
for page in range(1, MAX_PAGE + 1):
20
url = (
21
f"https://movie.naver.com/movie/bi/mi/pointWriteFormList.naver"
22
f"?pointMovieCode={MOVIE_CODE}&type=after&isActualPointView=true&page={page}"
23
)
24
25
response = requests.get(url, headers=headers)
26
response.encoding = "utf-8"
27
soup = BeautifulSoup(response.text, "lxml")
28
29
reviews = soup.select("div.score_reple p")
30
scores = soup.select("div.star_score em")
31
32
for score, review in zip(scores, reviews):
33
text = review.text.strip()
34
if text:
35
results.append({"score": score.text, "review": text})
36
37
print(f"{page}페이지 완료 ({len(results)}건 누적)")
38
time.sleep(1)
39
40
with open("naver_movie_reviews.csv", "w", newline="", encoding="utf-8-sig") as f:
41
writer = csv.DictWriter(f, fieldnames=["score", "review"])
42
writer.writeheader()
43
writer.writerows(results)
44
45
print(f"\n저장 완료! 총 {len(results)}건 → naver_movie_reviews.csv")
코드설명
7MOVIE_CODE = "17909"크롤링할 영화 코드다.
8MAX_PAGE = 5수집할 최대 페이지 수다.
10~16headers봇으로 인식되지 않도록 User-Agent를 설정한다.
18results = []수집한 리뷰를 담을 리스트다.
20for page in range(1, MAX_PAGE + 1)1페이지부터 MAX_PAGE까지 반복한다.
37time.sleep(1)각 요청 사이에 1초 대기. 서버에 과부하를 주지 않기 위한 예의다.
40encoding="utf-8-sig"엑셀에서 한글이 깨지지 않도록 BOM이 포함된 UTF-8로 저장한다.
41csv.DictWriter딕셔너리 형태의 데이터를 CSV로 저장하는 클래스다.

주의

크롤링 시 주의사항:

  • 서버에 과도한 요청을 보내지 않도록 time.sleep()으로 간격을 둔다.
  • 네이버 서비스 약관을 확인하고 학습 목적으로만 사용한다.
  • 사이트 구조가 변경되면 CSS 선택자를 수정해야 할 수 있다.

4.4. 수집 결과 확인

  1. 수집한 CSV 파일의 내용을 확인하기 위해 아래 코드를 작성한다.
crawling.py
1
import pandas as pd
2
3
df = pd.read_csv("naver_movie_reviews.csv", encoding="utf-8-sig")
4
print(df.head())
5
print(df["score"].value_counts())
  1. 수집한 CSV 파일을 실행하여 결과를 확인한다.
Terminal window
1
python crawling.py
코드설명
3pd.read_csv("naver_movie_reviews.csv", encoding="utf-8-sig")CSV 파일을 읽어 DataFrame 객체로 변환한다.
5df.head()상위 5개 행을 출력한다.
6df["score"].value_counts()별점 분포를 확인한다.

5. 실행 결과

수집된 데이터 예시

1
score review
2
0 10 영화 정말 재미있게 봤습니다. 추천합니다.
3
1 9 액션도 좋고 스토리도 탄탄해요.
4
2 10 최고의 영화! 여러 번 봐도 좋다.
5
3 8 기대 이상으로 잘 만들었네요.
6
4 9 볼 만한 가치가 있는 영화입니다.

별점 분포

1
score
2
10 45
3
9 32
4
8 28
5
7 15
6
6 8
7
5 4

6. 정리

이 프로젝트를 통해 배운 내용:

  • BeautifulSoup: HTML을 파싱하고 원하는 요소를 추출하는 방법
  • CSS 선택자: .class, #id 등을 사용해 특정 태그 찾기
  • 크롤링 윤리: time.sleep()으로 서버에 부담을 주지 않기
  • 데이터 저장: 수집한 데이터를 CSV 파일로 저장하기
  • 데이터 분석: pandas를 사용해 수집 결과 확인하기

더 배워볼 것들

  • Selenium: 자바스크립트로 동적으로 로드되는 콘텐츠 크롤링
  • Scrapy: 대규모 크롤링 프레임워크
  • 정규표현식: 더 복잡한 텍스트 패턴 매칭
  • 데이터 시각화: matplotlib/seaborn으로 수집 데이터 시각화