본문 바로가기
아주 미비한 코딩/Python

[Python] 네이버 플레이스(naver place) 리뷰 크롤링

by 찌노오 2024. 8. 11.

 

 

 

 

 

 

 

지난 번 네이버 플레이스를 통해 크롤링을 해봤는데,

이번에는 리뷰를 가져오는 코드를 실습해보았다. 특히 수집된 데이터를 다시 자연어 처리해 다른 분석을 해볼 때도 유용할 것 같다.

 

 그리고 최근에 알게 된 사실인데, 크롤링(Crawling)과 스크래핑(Scraping)은 구분되는 개념이지만 그냥 크롤링이라는 명칭을 썼다. 사실 지금 하는 실습은 스크래핑이라는 것을 밝혀둔다.

 

1.탐색

네이버 리뷰는 어디서 볼 수 있을까?

 네이버 리뷰는 크게 2가지 방법으로 노출되고 있는데, 네이버 맵과 네이버 플레이스다.

네이버 맵과 네이버 플레이스

네이버 맵으로 접근하는 건 다른 개발블로그에서 많이 봤었고, 효율적인 코드가 많았다.

맵에서는 검색 기반이라 특정 페이지로 바로 들어가는 건 불가능하고, 오직 검색을 통해서만 접근 할 수 있다.

그런데 첫번째 검색결과만 가져온다고 해도 시간, 장소에 따라 결과값은 바뀔 거라 유지보수에 좋지 않아 보였다.

 

그래서 저번에도 네이버 플레이스로 접근해서 크롤링을 진행했었다.

 

[Python] 네이버 플레이스(NAVER place) 크롤링

특정 지역의 데이터셋을 가지고 공부해보고 싶은게 있어서 지역 정보가 필요했다. 그런데 무식하게 긁어올 수 없어서 웹크롤러를 만들어보기로 했다. 바로 떠오른 생각은 네이버 지도에서 가져

jinooh.tistory.com

 

리뷰를 어떻게 볼 수 있을까?

플레이스로 접근해서 리뷰 페이지를 살펴보았다.

우선 페이지를 내렸을 때, 자동으로 피드가 리프레시 되지 않고, 더보기 버튼을 눌러야 다른 리뷰를 볼 수 있었다.

 

 

2.기획

이번에는 특정 검색어나 카테고리별로 순환하는 코드는 따로 만들지 않고 개별 하나의 페이지의 내용을 가져오는 식으로 구상했다. 왜냐하면 지난 번 플레이스 코드와 결합시키면 얼마든지 다른 정보도 가져올 수 있기 때문에.

 

잠깐, 네이버 개별 플레이스 주소의 구조는 다음과 같다.

https://m.place.naver.com/restaurant/{$식별값}/review/visitor?entry=ple&reviewSort=recent 

그래서 식별값을 알면 특정 가게(점포)를 호출할 수 있다. 미니홈피 같은 개념으로 보면 될 것 같다.

 

특정 플레이스 주소 값으로 접속하면 소식, 메뉴, 리뷰 등등 가변적인 메뉴바들이 보이고 밑으로 내리면 리뷰들이 보인다.우선 리뷰를 더보기 버튼으로 강제로 눌러서 데이터를 가져오는 것을 구상했다.

그래서 네이버 플레이스 특정페이지로 들어가서 리뷰 부분에서 더보기 버튼이 나오지 않을 때까지 selenium으로 click한 뒤, 모든 데이터를 정제해서 가져오는 식으로 코드를 짜보기로 했다.

 

가져올 데이터는 다음과 같다.

① 닉네임

② 리뷰내용

③ 작성일

④ 재방문 횟수

 

3.구현

기본 설정

먼저 기본적인 크롤링에 필요한 기본적인 코드를 작성한다.

URL정보와 헤더, webdriver옵션 등을 설정해준다.

# url
url = 'https://m.place.naver.com/restaurant/1085956231/review/visitor?entry=ple&reviewSort=recent'

# Webdriver headless mode setting
options = webdriver.ChromeOptions()
options.add_argument('headless')
options.add_argument('window-size=1920x1080')
options.add_argument("disable-gpu")

# BS4 setting for secondary access
session = requests.Session()
headers = {
    #사용자의 user-agent값
    "User-Agent": "$User value"}

retries = Retry(total=5,
                backoff_factor=0.1,
                status_forcelist=[500, 502, 503, 504])

session.mount('http://', HTTPAdapter(max_retries=retries))

 

xlsx 파일 열기

파일을 담을 코드를 구성한다.

nickname, content, date, revisit 이라는 컬럼명을 만들고, 파일을 열어둔다.

# New xlsx file
now = datetime.datetime.now()
xlsx = Workbook()
list_sheet = xlsx.create_sheet('output')
list_sheet.append(['nickname', 'content', 'date', 'revisit'])

 

웹드라이버 설정, 더보기 버튼 누르기

준비가 되면 이제 웹드라이버를 실행하고, 묵시적 대기를 걸어준다.

 

원래는 더보기를 클릭해서 모든 리뷰를 가져와야 하는데 웹드라이버 환경에서는 밑에 버튼까지 랜더링이 안되었다.

그래서 강제로 페이지 다운 버튼을 하나 눌러주는 작업이 필요했다.

body를 잡고 page_down을 한 번 눌러준다.

 

이제 더보기를 눌러주면 되는데, 가게마다 리뷰가 다를테니 더보기를 누를 횟수를 하드코딩을 하면 안된다.

그래서 오류가 뜨면 멈추는 반복문을 사용했다. 

# Start crawling/scraping!
driver = webdriver.Chrome(ChromeDriverManager().install(), chrome_options=options)
res = driver.get(url)
driver.implicitly_wait(30)

# Pagedown
driver.find_element(By.TAG_NAME, 'body').send_keys(Keys.PAGE_DOWN)

try:
    while True:
        driver.find_element(By.XPATH, '//*[@id="app-root"]/div/div/div/div[6]/div[2]/div[3]/div[2]/div/a').click()
        time.sleep(0.4)
except Exception as e:
    print('finish')

headless모드를 잠시 주석을 걸어놓고 어떻게 동작하는지 보면 다음과 같다.

Selenium이 실행되면 page down을 한 차례 한 뒤, 더 이상 '더보기'를 누를 수 없을 때까지 눌러준다.

더 이상 더보기를 누를 수 없을 때까지 클릭한다.

 

 

데이터 파싱하기 

모든 리뷰를 불러오면 이제 본격적으로 파싱을 한다.

데이터양이 많아지면 한 번에 가져오는 게 안될 때가 있어서 최대한 지연을 많이 주었다.

 

이제 컬럼별로 파싱을 하는데 이때 text로 변환이 안되는 데이터는 예외처리로 공백으로 반환한다.

마지막으로 반환된 데이터들은 시트에 순서대로 저장한다.

 

** 23년 11월 13일 기준 태그

더보기
time.sleep(25)
html = driver.page_source
bs = BeautifulSoup(html, 'lxml')
reviews = bs.select('li.YeINN')

for r in reviews:
    nickname = r.select_one('div.VYGLG')
    content = r.select_one('div.ZZ4OK.IwhtZ')
    date = r.select('div._7kR3e>span.tzZTd>time')[0]
    revisit = r.select('div._7kR3e>span.tzZTd')[1]

    # exception handling
    nickname = nickname.text if nickname else ''
    content = content.text if content else ''
    date = date.text if date else ''
    revisit = revisit.text if revisit else ''
    time.sleep(0.06)

	# append sheet
    print(nickname, '/', content, '/', date, '/', revisit)
    list_sheet.append([nickname, content, date, revisit])
    time.sleep(0.06)

** 24년 8월 11일 기준 태그

변경된 태그를 반영하고 좀 더 예외처리를 촘촘하게 변경하였다.

time.sleep(25)
html = driver.page_source
bs = BeautifulSoup(html, 'lxml')
reviews = bs.select('li.pui__X35jYm.EjjAW')

for r in reviews:
    # nickname
    nickname = r.select_one('div.pui__JiVbY3 > span.pui__uslU0d')

    # content
    content = r.select_one('div.pui__vn15t2 > a.pui__xtsQN-')

    # date
    date_elements = r.select('div.pui__QKE5Pr > span.pui__gfuUIT > time')
    date = date_elements[0] if date_elements else 'N/A'

    # revisit
    revisit_span = r.select('div.pui__QKE5Pr > span.pui__gfuUIT')
    revisit = revisit_span[1] if len(revisit_span) > 1 else 'N/A'

    # exception handling
    nickname = nickname.text if nickname else ''
    content = content.text if content else ''
    date = date.text if date else ''
    revisit = revisit.text if revisit else ''
    time.sleep(0.06)

    # append sheet
    print(nickname, '/', content, '/', date, '/', revisit)
    list_sheet.append([nickname, content, date, revisit])
    time.sleep(0.06)

 

 

 

4. 최종 완성 코드

완성된 코드는 다음과 같다.

 

** 23년 11월 13일 기준

더보기

**  코드 수정

from selenium.webdriver.common.by import By
from urllib3.util.retry import Retry
from requests.adapters import HTTPAdapter
from openpyxl import Workbook
from bs4 import BeautifulSoup
from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.keys import Keys
import time
import datetime
import requests

# url
url = 'https://m.place.naver.com/restaurant/1085956231/review/visitor?entry=ple&reviewSort=recent'

# Webdriver headless mode setting
options = webdriver.ChromeOptions()
#options.add_argument('headless')
options.add_argument('window-size=1920x1080')
options.add_argument("disable-gpu")

# BS4 setting for secondary access
session = requests.Session()
headers = {
    "User-Agent": "user value"}

retries = Retry(total=5,
                backoff_factor=0.1,
                status_forcelist=[500, 502, 503, 504])

session.mount('http://', HTTPAdapter(max_retries=retries))

# New xlsx file
now = datetime.datetime.now()
xlsx = Workbook()
list_sheet = xlsx.create_sheet('output')
list_sheet.append(['nickname', 'content', 'date', 'revisit'])

# Start crawling/scraping!
try:
    driver = webdriver.Chrome(ChromeDriverManager().install(), options=options)
    res = driver.get(url)
    driver.implicitly_wait(30)

    # Pagedown
    driver.find_element(By.TAG_NAME, 'body').send_keys(Keys.PAGE_DOWN)

    try:
        while True:
            driver.find_element(By.XPATH, '//*[@id="app-root"]/div/div/div/div[6]/div[2]/div[3]/div[2]/div/a').click()
            time.sleep(0.4)
    except Exception as e:
        print('finish')

    time.sleep(25)
    html = driver.page_source
    bs = BeautifulSoup(html, 'lxml')
    reviews = bs.select('li.YeINN')

    for r in reviews:
        nickname = r.select_one('div.VYGLG')
        content = r.select_one('div.ZZ4OK.IwhtZ')
        date = r.select('div._7kR3e>span.tzZTd>time')[0]
        revisit = r.select('div._7kR3e>span.tzZTd')[1]

        # exception handling
        nickname = nickname.text if nickname else ''
        content = content.text if content else ''
        date = date.text if date else ''
        revisit = revisit.text if revisit else ''
        time.sleep(0.06)

        print(nickname, '/', content, '/', date, '/', revisit)
        list_sheet.append([nickname, content, date, revisit])
        time.sleep(0.06)
    # Save the file
    file_name = 'naver_review_' + now.strftime('%Y-%m-%d_%H-%M-%S') + '.xlsx'
    xlsx.save(file_name)

except Exception as e:
    print(e)
    # Save the file(temp)
    file_name = 'naver_review_' + now.strftime('%Y-%m-%d_%H-%M-%S') + '.xlsx'
    xlsx.save(file_name)

 

** 24년 8월 11일 기준

변경된 태그와 '더보기' 클릭수를 사용자가 직접 값을 넣을 수도 있도록 변경하였다.

from selenium.webdriver.common.by import By
from urllib3.util.retry import Retry
from requests.adapters import HTTPAdapter
from openpyxl import Workbook
from bs4 import BeautifulSoup
from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.keys import Keys
import time
import datetime
import requests
from selenium.webdriver.chrome.service import Service

# url
url = 'https://m.place.naver.com/restaurant/1085956231/review/visitor?entry=ple&reviewSort=recent'

# Webdriver headless mode setting
options = webdriver.ChromeOptions()
# options.add_argument('headless')
options.add_argument('window-size=1920x1080')
options.add_argument("disable-gpu")

# BS4 setting for secondary access
session = requests.Session()
headers = {
    "User-Agent": "user value"}

retries = Retry(total=5,
                backoff_factor=0.1,
                status_forcelist=[500, 502, 503, 504])

session.mount('http://', HTTPAdapter(max_retries=retries))

# New xlsx file
now = datetime.datetime.now()
xlsx = Workbook()
list_sheet = xlsx.create_sheet('output')
list_sheet.append(['nickname', 'content', 'date', 'revisit'])

# Start crawling/scraping!
try:
    # driver = webdriver.Chrome(executable_path=ChromeDriverManager().install(), options=options)
    driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options)
    res = driver.get(url)
    driver.implicitly_wait(30)

    # Pagedown
    driver.find_element(By.TAG_NAME, 'body').send_keys(Keys.PAGE_DOWN)

    try:
        for i in range(30):  # 최대 30번 반복
            driver.find_element(By.XPATH, '//*[@id="app-root"]/div/div/div/div[6]/div[2]/div[3]/div[2]/div/a').click()
            time.sleep(0.4)
    except Exception as e:
        print('finish')
    else :
        print('30times_finish')

    time.sleep(25)
    html = driver.page_source
    bs = BeautifulSoup(html, 'lxml')
    reviews = bs.select('li.pui__X35jYm.EjjAW')

    for r in reviews:
        # nickname
        nickname = r.select_one('div.pui__JiVbY3 > span.pui__uslU0d')

        # content
        content = r.select_one('div.pui__vn15t2 > a.pui__xtsQN-')

        # date
        date_elements = r.select('div.pui__QKE5Pr > span.pui__gfuUIT > time')
        date = date_elements[0] if date_elements else 'N/A'

        # revisit
        revisit_span = r.select('div.pui__QKE5Pr > span.pui__gfuUIT')
        revisit = revisit_span[1] if len(revisit_span) > 1 else 'N/A'

        # exception handling
        nickname = nickname.text if nickname else ''
        content = content.text if content else ''
        date = date.text if date else ''
        revisit = revisit.text if revisit else ''
        time.sleep(0.06)

        # append sheet
        print(nickname, '/', content, '/', date, '/', revisit)
        list_sheet.append([nickname, content, date, revisit])
        time.sleep(0.06)
    # Save the file
    file_name = 'naver_review_' + now.strftime('%Y-%m-%d_%H-%M-%S') + '.xlsx'
    xlsx.save(file_name)

except Exception as e:
    print(e)
    # Save the file(temp)
    file_name = 'naver_review_' + now.strftime('%Y-%m-%d_%H-%M-%S') + '.xlsx'
    xlsx.save(file_name)

 

 

 

 

 

 

 

 

 

# 23-10-18 수정 / webdriver-manager version 관련

webdriver-manager의 라이브러리 버전이 맞지 않아 실행이 되지 않는 이슈가 있다.

ValueError: There is no such driver by url https://chromedriver.storage.googleapis.com/

 

터미널에서 아래와 같이 webdriver-manager를 업그레이드 해주면 코드 수정없이 다시 실행된다.

 

pip install webdriver-manager --upgrade

 

 

# 23-11-13 수정 / 스크래핑 코드 내 태그 주소 수정, 일부 코드 수정 

고맙게도 코드가 동작하지 않는다는 제보를 해주셔서 태그명 수정과 미뤄왔던 코드를 수정했다.

 

# 24-08-11 수정 / 스크래핑 코드 내 태그 주소 수정, 사용자가 '더보기' 클릭 수를 설정할 수 있도록 코드 수정

이번엔 chat GPT와 함께 코드를 즐겁게 수정해봤다.

 

 

 

 

 

 

** 사실과 다른 내용이 있을 수 있습니다. 언제든지 피드백 부탁드립니다!

 

 

반응형

댓글