세조목

최종 프로젝트 4일차(24.03.29) 본문

데이터 분석 공부/프로젝트

최종 프로젝트 4일차(24.03.29)

세조목 2024. 3. 29. 21:38

최종 프로젝트 4일차입니다.

금일은 어제에 이어 네이버 지도 리뷰 데이터 크롤링 코드 작성을 했습니다.

다행히 구글링을 통해 확인한 코드들을 조합하여 코드를 작성할 수 있었고,

결과값 확인까지 했습니다.

def switch_left():
    ############## iframe으로 왼쪽 포커스 맞추기 ##############
    #driver.switch_to.parent_frame()
    driver.switch_to.default_content()
    iframe = driver.find_element(By.XPATH,'//*[@id="searchIframe"]')
    driver.switch_to.frame(iframe)
    
def switch_right():
    ############## iframe으로 오른쪽 포커스 맞추기 ##############
    #driver.switch_to.parent_frame()
    driver.switch_to.default_content()
    iframe = driver.find_element(By.XPATH,'//*[@id="entryIframe"]')
    driver.switch_to.frame(iframe)

def page_down(num):
    body = driver.find_element(By.CSS_SELECTOR, 'body')
    body.click()
    for i in range(num):
        body.send_keys(Keys.PAGE_DOWN)

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.chrome.options import Options

from time import sleep
import random
import re

from selenium import webdriver
import sys

options = Options()
options.add_argument('user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36')
options.add_argument('window-size=1380,850')
driver = webdriver.Chrome(options=options)

# 대기 시간
driver.implicitly_wait(time_to_wait=3)

# 반복 종료 조건
loop = True

URL = 'https://map.naver.com/p/search/%EC%A2%85%EB%A1%9C%EA%B5%AC%20%EB%B9%B5?c=13.00,0,0,0,dh'
driver.get(url=URL)

while(True):
    switch_left()

    # 페이지 숫자를 초기에 체크 [ True / False ]
    # 이건 페이지 넘어갈때마다 계속 확인해줘야 함 (페이지 새로 로드 될때마다 버튼 상태 값이 바뀜)
    next_page = driver.find_element(By.XPATH,'//*[@id="app-root"]/div/div[2]/div[2]/a[7]').get_attribute('aria-disabled')
    
    if(next_page == 'true'):
        break

    ############## 맨 밑까지 스크롤 ##############
    scrollable_element = driver.find_element(By.CLASS_NAME, "Ryr1F")

    last_height = driver.execute_script("return arguments[0].scrollHeight", scrollable_element)

    while True:
        # 요소 내에서 아래로 600px 스크롤
        driver.execute_script("arguments[0].scrollTop += 600;", scrollable_element)

        # 페이지 로드를 기다림
        sleep(1)  # 동적 콘텐츠 로드 시간에 따라 조절

        # 새 높이 계산
        new_height = driver.execute_script("return arguments[0].scrollHeight", scrollable_element)

        # 스크롤이 더 이상 늘어나지 않으면 루프 종료
        if new_height == last_height:
            break

        last_height = new_height


    ############## 현재 page number 가져오기 - 1 페이지 ##############

    page_no = driver.find_element(By.XPATH,'//a[contains(@class, "mBN2s qxokY")]').text

    # 현재 페이지에 등록된 모든 가게 조회
    # 첫페이지 광고 2개 때문에 첫페이지는 앞 2개를 빼야함
    if(page_no == '1'):
        elemets = driver.find_elements(By.XPATH,'//*[@id="_pcmap_list_scroll_container"]//li')[2:]
    else:
        elemets = driver.find_elements(By.XPATH,'//*[@id="_pcmap_list_scroll_container"]//li')

    print('현재 ' + '\033[95m' + str(page_no) + '\033[0m' + ' 페이지 / '+ '총 ' + '\033[95m' + str(len(elemets)) + '\033[0m' + '개의 가게를 찾았습니다.\n')

    for index, e in enumerate(elemets, start=1):
        final_element = e.find_element(By.CLASS_NAME,'CHC5F').find_element(By.XPATH, ".//a/div/div/span")

        print(str(index) + ". " + final_element.text)

    print("-"*50)

    switch_left()

    sleep(2)

    for index, e in enumerate(elemets, start=1):
        store_name = '' # 가게 이름
        category = '' # 카테고리
        new_open = '' # 새로 오픈
        
        rating = 0.0 # 평점
        visited_review = 0 # 방문자 리뷰
        blog_review = 0 # 블로그 리뷰

        switch_left()

        # 순서대로 값을 하나씩 클릭
        #e.find_element(By.CLASS_NAME,'CHC5F').find_element(By.XPATH, ".//a/div/div/span").click() ← 원본
        try:
            e.find_element(By.CLASS_NAME,'ZUcHo').find_element(By.XPATH, ".//div/a").click()
            sleep(2)

            switch_right()

            # 우측 프레임 스크롤 다운 & 더보기 버튼 클릭
            for i in range(10):
                page_down(8)
                sleep(2)

                driver.find_element(By.CLASS_NAME,'NSTUp').find_element(By.XPATH, ".//div/a/span").click()
                sleep(2)

        except:
            e.find_element(By.CLASS_NAME,'ZUcHo').find_element(By.XPATH, ".//div/a").click()
            sleep(2)

            switch_right()

            # 우측 프레임 스크롤 다운 & 더보기 버튼 클릭
            for i in range(10):
                page_down(8)
                sleep(2)

                driver.find_element(By.CLASS_NAME,'NSTUp').find_element(By.XPATH, ".//div/a/span").click()
                sleep(2)

        ################### 여기부터 크롤링 시작 ##################
        # 리뷰글 外 수집용
        title = driver.find_element(By.XPATH,'//div[@class="zD5Nm undefined"]')
        store_info = title.find_elements(By.XPATH,'//div[@class="YouOG DZucB"]/div/span')
        # 리뷰글 수집용
        #review_content = driver.find_element(By.XPATH,'//div[@class="place_section_content"]')
        #real_reviews = review_content.find_element(By.XPATH,'//ul')

        # 가게 이름                                      
        store_name = title.find_element(By.XPATH,'.//div[1]/div[1]/span[1]').text

        # 카테고리
        category = title.find_element(By.XPATH,'.//div[1]/div[1]/span[2]').text

        if(len(store_info) > 2):
            # 새로 오픈
            new_open = title.find_element(By.XPATH,'.//div[1]/div[1]/span[3]').text

        ###############################

        review = title.find_elements(By.XPATH,'.//div[2]/span')

        # 인덱스 변수 값
        _index = 1

        # 리뷰 ROW의 갯수가 3개 이상일 경우 [별점, 방문자 리뷰, 블로그 리뷰]
        if len(review) > 2:
            rating_xpath = f'.//div[2]/span[{_index}]'
            rating_element = title.find_element(By.XPATH, rating_xpath)
            rating = rating_element.text.replace("\n", " ")

            _index += 1

        try:
            # 방문자 리뷰
            visited_review = title.find_element(By.XPATH,f'.//div[2]/span[{_index}]/a').text

            # 인덱스를 다시 +1 증가 시킴
            _index += 1

            # 블로그 리뷰
            blog_review = title.find_element(By.XPATH,f'.//div[2]/span[{_index}]/a').text

            # 리뷰 내용
            review_box = []
            review_nums = driver.find_elements(By.CLASS_NAME,'owAeM')
            for review_num in review_nums:
                review_content = review_num.find_element(By.CLASS_NAME,'zPfVt').text
                review_box.append(review_content)


        except:
            print('------------ 리뷰 없음 ------------')


        try:
            print(f'{index}. ' + str(store_name) + ' · ' + str(category) + str(new_open))
            print('평점 ' + str(rating) + ' / ' + visited_review + ' · ' + blog_review)
            print(review_box)
            print("-"*50)
        
        except:
            print(f'{index}. ' + str(store_name) + ' · ' + str(category) + str(new_open))            
            print('------------ 리뷰 없음 ------------')
    switch_left()
        
    # 페이지 다음 버튼이 활성화 상태일 경우 계속 진행
    if(next_page == 'false'):
        driver.find_element(By.XPATH,'//*[@id="app-root"]/div/div[2]/div[2]/a[7]').click()
    # 아닐 경우 루프 정지
    else:
        loop = False

 

그러나 네이버 지도에서 '더보기'를 클릭했을때 '거리뷰'가 함께 클릭되는 오류가 발생하고 있으며

지금 작성한 코드로는 한 가게에 대해 작성된 리뷰들이 하나의 리스트에 들어가서 출력되고 있으므로

오류 해결 및 리뷰들을 각각의 행에 저장하도록 코드를 수정해야합니다.

 * 위에 작성한 코드에 대해서는 코드가 최종적으로 작성 완료된 후 하나 하나 뜯어보며 설명하겠습니다.

 

 

크롤링 코드 참조 블로그

1. https://baka9131.tistory.com/14

2. https://velog.io/@kimdy0915/Selenium%EC%9C%BC%EB%A1%9C-%EB%84%A4%EC%9D%B4%EB%B2%84-%EC%A7%80%EB%8F%84-%ED%81%AC%EB%A1%A4%EB%A7%81%ED%95%98%EA%B8%B0

3. https://jinooh.tistory.com/89

4. https://dutchcoffee.tistory.com/30

5. https://jenn1won.tistory.com/15