본문 바로가기

데이터 수집 프로젝트

2_웹 크롤링

728x90

데이터 수집을 하기 위해 웹 크롤링을 사용했습니다.

 

크롤링이란?

  • 프로그램이 웹 사이트를 정기적으로 돌며 정보를 추출하는 기술
  • 이런 프로그램을 크롤러(Crawler) 또는 스파이더(Spider)라고 부르기도 한

 

저희는 네이버 지도의 플레이스 화면에 나와있는 정보를 크롤링으로 수집해 CSV파일로 저장 후

얻은 데이터를 시각화 하는 프로젝트를 하기로 결정 했습니다.

 

먼저 네이버 지도 플레이스의 화면 입니다.

네이버 지도의 검색화면과 플레이스

 

경로를 지정해 줘야하기 때문에 웹 사이트를 살펴보았습니다.

먼저 검색창에 카페의 이름을 검색 해줘야하고

2번 공간을 맨밑까지 스크롤을 해줘야합니다. (스크롤 하면 계속 업체가 추가되서 나옴)

다시 맨 위부터 카페정보를 순서대로 크롤링 하고

다음 페이지로 넘어가서 같은 작업을 반복 합니다.

크롤링이 끝나면 CSV로 파일을 저장.

 

자 이제 이렇게 코드를 적고 크롤링을 시작 해 보겠습니다.

먼저 실행 화면 입니다.

 

지정해준 경로를 따라 크롤링을 하는 모습입니다.

 

 

코드를 첨부 하겠습니다.

from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from datetime import datetime
import time
from urllib.parse import quote, unquote
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import re

import pandas as pd
def switch_frame(frame):
    browser.switch_to.default_content()  # frame 초기화
    browser.switch_to.frame(frame)  # frame 변경

ls2 = ['강남구', '강동구', '강북구', '강서구', '관악구', '광진구', '구로구',' 금천구', '노원구', '도봉구', '동대문구', '동작구', '마포구',
     '서대문구', '서초구', '성동구', '성북구', '송파구', '양천구', '영등포구', '용산구', '은평구', '종로구', '중구', '중랑구']
# -----------------------------------25개의 구를 한번에 하면 인터넷 문제로 오류가 자주남-----------------------------------
# -----------------------------------5개 구로 쪼개서 하나씩 돌리며 파일을 만듬-----------------------------------
ls = ['강동구','강남구']

cafe_name_list = []
cafe_classification_list = []
cafe_review_count_list = []
cafe_address_list = []
cafe_review_list = []
background_image_url_list = []
cafe_menu_ls_list = []

# 처음 모든구를 한번에 돌릴려고 했으나 인터넷문제로 에러가 자주남. for문 제거
for i in range(len(ls)):  # 모든 자치구에 있는 카페를 알아보기 위해 반복
    # 브라우저 열기
    keyword = quote(f'{ls[i]} 카페')
    url = 'https://map.naver.com/p/search/' + keyword  
    browser = webdriver.Chrome()
    browser.get(url)
    browser.implicitly_wait(10)

# 스위치 변경
switch_frame("searchIframe")
time.sleep(1)
# 버튼 변수들
next_btn = browser.find_elements(By.CSS_SELECTOR, '.zRM9F> a')
browser.implicitly_wait(10)

# 버튼길이만큼 반복 첫번재는 이전버튼 이므로 제외
for btn in range(len(next_btn))[1:]:
    print(f'------------------------------------{btn}/{len(next_btn)-1}------------------------------------')

    #body부분을 잡기 위해 쓸데없는 버튼을 클릭해줌
    browser.find_element(By.XPATH, '//*[@id="_pcmap_list_scroll_container"]/ul/li[1]/div[1]/div[1]/div').click()

    #검색결과가 모두 보이지 않기 때문에 page down을 눌러 끝까지 펼쳐준다.
    for scroll_1 in range(8):
        browser.find_element(By.TAG_NAME, 'body').send_keys(Keys.END)

    resp = browser.page_source     # 처음 브라우저 소스와, 버튼을 누를때마다 그것에 관한 페이지 소스 가져옴
    soup = BeautifulSoup(resp, 'html.parser') # 브라우저소스를 html로 변환하여 가져옴
    length = len(soup.select('#_pcmap_list_scroll_container > ul > li'))+1  # 한페이지에 보이는 카페들의 개수 반환

    # 카페들의 개수만큼 반복
    for j in range(1, length+1):
        # 스위치 변경
        switch_frame("searchIframe")

        # 해당하는 카페에 정보를 알기위해 그 카페를 클릭해줌
        # 구마다 /html/body/div[3]/div/div[3]....../sapn[1] or /html/body/div[3]/div/div[4]....../sapn[1] 가 있음
        try:
            browser.find_element(By.XPATH, f'/html/body/div[3]/div/div[3]/div[1]/ul/li[{j}]/div[1]/a/div/div/span[1]').click()
        except NoSuchElementException:
            browser.find_element(By.XPATH, f'/html/body/div[3]/div/div[4]/div[1]/ul/li[{j}]/div[1]/a/div/div/span[1]').click()
        finally:
            # 카페정보를 받아오는 시간 충분하게 받아줌 인터넷이 느려 충분히 시간 줘야됌
            time.sleep(3)

        # 스위치 변경
        switch_frame("entryIframe")

        # 버튼이 누를 필요 없어서 제거
#---------------------------------------------------------------------------------------
#             # 가끔 버튼을 찾지 못함
#             try:
#                 time.sleep(1)
#                 browser.find_element(By.CLASS_NAME,'_UCia').click()
#                 browser.implicitly_wait(10)
#                 time.sleep(1)
#             # 같은 버튼에 이름만 다른 클래스임
#             except Exception as e:
#                 browser.find_element(By.CLASS_NAME,'PkgBl').click()
#                 browser.implicitly_wait(10)
#                 time.sleep(1)
#---------------------------------------------------------------------------------------

        print(f'-------------------------------{j}번째-------------------------------')

        # 카페 주소 저장
        cafe_addr = WebDriverWait(browser,1000).until(EC.presence_of_element_located((By.XPATH,'/html/body/div[3]/div/div/div/div[5]/div/div[2]/div[1]/div/div[1]/div/a/span[1]')))

        cafe_address = cafe_addr.text


        # 이미지 가져오기
        background_image_style = browser.find_element(By.XPATH,'/html/body/div[3]/div/div/div/div[1]/div/div[1]/div/a/div').get_attribute('style')
        background_image_url = re.search(r'url\("([^"]+)"\)', background_image_style).group(1)

        # 위에서 버튼을 누르지 않으니 스크롤이 제대로 돌아가지 않음 entryIframe 내의 element 클릭 그래야 스크롤이 내려감
        browser.find_element(By.ID, '_title').click()
        for scroll in range(0,10):
            browser.find_element(By.TAG_NAME, 'body').send_keys(Keys.PAGE_DOWN)

        resp = browser.page_source
        soup = BeautifulSoup(resp, 'html.parser')

        cafe_menu_ls = []
        cafe_menu = soup.select('div.MN48z > div.erVoL > div.MENyI > span.VQvNX')
        if len(cafe_menu) == 0:
            cafe_menu = soup.select('div.YzCTi > div.Fi0vA > div.RhpMT > a.place_bluelink.ihmWt')
            for menu in cafe_menu:
                cafe_menu_ls.append(menu.text)
        else:
            for menu in cafe_menu:
                cafe_menu_ls.append(menu.text)

        # 리뷰 높은순 3개 가져오기.
        cafe_review = []
        try:
            for k in range(1,4):
                cafe_review.append(soup.select(f'div.place_section_content > div.iqjjt > a > ul > li:nth-child({k}) > div.CsBE9 > span.nWiXa')[0].text[1:-1])
        except:
            cafe_review.append('리뷰 참여자가 10명이 되지 않습니다.')

        # 스위치 변경
        switch_frame("searchIframe")

        resp = browser.page_source
        soup = BeautifulSoup(resp, 'lxml')

# --------------------------------------------------------평점이 없는 매장이 훨신 많아 평점 제거--------------------------------------------------
        # 딕셔너리에 담을 변수들 생성
        cafe_name = soup.select(f'#_pcmap_list_scroll_container > ul > li:nth-child({j}) > div.CHC5F > a.tzwk0 > div > div > span.TYaxT')[0].text
        cafe_classification = soup.select(f'#_pcmap_list_scroll_container > ul > li:nth-child({j}) > div.CHC5F > a.tzwk0 > div > div > span.KCMnt')[0].text
        cafe_review_count = soup.select(f'#_pcmap_list_scroll_container > ul > li:nth-child({j}) > div.CHC5F > div > div > span:nth-child(2)')[0].text
        if cafe_review_count[:2] == '리뷰':
            cafe_review_count = cafe_review_count[3:]
        elif cafe_review_count[:2] != '리뷰':
            try:
                cafe_review_count = soup.select(f'#_pcmap_list_scroll_container > ul > li:nth-child({j}) > div.CHC5F > div > div > span:nth-child(3)')[0].text[3:]
            except:

                cafe_review_count = soup.select(f'#_pcmap_list_scroll_container > ul > li:nth-child({j}) > div.CHC5F > div > div > span:nth-child(1)')[0].text[3:]
        else :
            cafe_review_count = '0'

        cafe_name_list.append(cafe_name)
        cafe_classification_list.append(cafe_classification)
        cafe_review_count_list.append(cafe_review_count)
        cafe_address_list.append(cafe_address)
        cafe_review_list.append(cafe_review)
        background_image_url_list.append(background_image_url)
        cafe_menu_ls_list.append(cafe_menu_ls)

        dict_temp = {
            'name':cafe_name_list,
            'class':cafe_classification_list,
            'review_count':cafe_review_count_list,
            'address':cafe_address_list,
            'review_top3':cafe_review_list,
            'background_image_url':background_image_url_list,
            'cafe_menu':cafe_menu_ls_list        
        }    
       
    next_btn[-1].click() # 다음 버튼 클릭
    time.sleep(5)
    # 버튼이 눌리지 않으면 종료
    if not next_btn[-1].is_enabled():
        break

browser.implicitly_wait(10)
browser.close()

df = pd.DataFrame(dict_temp)
df.to_csv('C:/Users/user/crawling_project/crawling_강남구.csv',index = False, encoding='utf-8-sig')
728x90