qcoding

2) Python + Selenium + Beautiful Soup 통한 음식점 정보 웹크롤링 본문

[사이드 프로젝트]App개발 - 전국 국밥 찾기

2) Python + Selenium + Beautiful Soup 통한 음식점 정보 웹크롤링

Qcoding 2022. 1. 10. 11:29
반응형

크롤링 코드 구현

- 크롤링 사이트 확인

-> 음식점 정보를 얻기 위해서 여러 웹사이트를 통해서 크롤링이 가능하지만 평소 사용하는 DiningCode를 통해 음식점 정보를 얻기로 결정하였다. 검색어를 통해 지역별로 검색을 통해서 크롤링을 하여 정보를 가져오려고 한다.

https://www.diningcode.com/list.php?query=%EC%84%9C%EC%9A%B8%20%EA%B5%AD%EB%B0%A5 

 

'서울 국밥' 빅데이터 맛집 순위 Top100 - 다이닝코드

'서울 국밥' 맛집 애성회관 한우곰탕(곰탕, ★4.4), 중앙해장(해장국, ★4.3), 소문난 성수 감자탕(감자탕, ★4.1) 등 4,618곳의 전체 순위,식당정보,방문자리뷰,사

www.diningcode.com

다이닝 코드 '서울 국밥' 검색

 

 

 

우선 사이트에 들어가보면 왼쪽에 보이는 것과 같이 순위별로 음식점 정보가 나오게 된다. 

1) 음식점명 

2) 대표메뉴

3) 식당 키워드

4) 지역정보 (동/전체주소)

5) 평점

 

의 정보를 대표적으로 뽑을 수 있으며 해당 정보를 통해 크롤링을 통해 가져오려고 한다. 

 보통 크롤링을 진행할 때 Python Request + BetifulSoup의 조합을 사용하여 진행하는데, betifulSoup는 Request를 통해서 가져온 브라우저 페이지의 HTML 에서 우리가 필요한 부분을 선택하기 위해서 사용한다.

 그런데 이 사이트의 경우 맨처음 화면에서는 1~10위 까지의 10개 정도의 음식점만 보이게 되고, 옆의 사진처럼 더보기 버튼을 클릭할 때에 음식점이 10개씩 추가로 불러와지기 때문에 해당 방법을 사용하기 어렵다.

 

처음에는 쉽게 하기 위하여 더보기 버튼을 클릭을 다 한뒤 URL주소로 Request를 통해 HTML을 가져오려고 했지만, 더보기 버튼을 클릭해도 URL 주소를 변경되지 않으므로 그방법 또한 사용하기 어려웠다. 

이에 자동으로 브라우저를 조작해주는 Selinium을 사용하는 것으로 방법을 바꿧다.

 

- 코드 구현

-> 크롤링의 기능 구현 순서는 아래와 같다.

1) selinium을 통해 해당 url로 접속한뒤 더보기 버튼을 클릭 한후 렌더링 되는 것을 기다리고 다시 버튼을 클릭하는 것을 반복 한다.

2) 더보기 버튼을 계속 클릭하다가 더보기 버튼이 더이상 없으면 모든 페이지 정보를 다 보여준 것이기 떄문에 이때 html 문서를 가져온다.

3) 가져온 html 문서에서 원하는 음식점 정보에 대한 텍스트 값을 가져온다

4) 가져온 정보들을 엑셀의 형태로 저장한다.

 

위의 순서대로 url을 반복해서 크롤링을 하며, 각각의 정보는 엑셀파일 하나에 sheet를 생성하여 저장되게 한다. 

지역별로 구분하여 국밥집 정보를 얻기 위해서 서울 / 경기 / 강원 / 충청 / 전라/ 경상 / 제주 로 각각 검색한 url을 사용하기로 하였다. 

urls = ['https://www.diningcode.com/list.php?query=%EC%84%9C%EC%9A%B8%20%EA%B5%AD%EB%B0%A5',
        'https://www.diningcode.com/list.php?query=%EA%B2%BD%EA%B8%B0%20%EA%B5%AD%EB%B0%A5',
        'https://www.diningcode.com/list.php?query=%EA%B0%95%EC%9B%90%20%EA%B5%AD%EB%B0%A5',
        'https://www.diningcode.com/list.php?query=%EC%B6%A9%EC%B2%AD%EB%8F%84%20%EA%B5%AD%EB%B0%A5',
        'https://www.diningcode.com/list.php?query=%EC%A0%84%EB%9D%BC%EB%8F%84%20%EA%B5%AD%EB%B0%A5',
        'https://www.diningcode.com/list.php?query=%EA%B2%BD%EC%83%81%EB%8F%84%20%EA%B5%AD%EB%B0%A5',
        'https://www.diningcode.com/list.php?query=%EC%A0%9C%EC%A3%BC%EB%8F%84%20%EA%B5%AD%EB%B0%A5'
        ]

* 코드를 각 부분별로 살펴본 후 글 하단에 최종 코드를 업로드 시켰습니다.

필요한 라이브러리 정보

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import time
import requests
from bs4 import BeautifulSoup
from openpyxl import Workbook
import json

1) selinium을 통한 브라우저 더보기 클릭

-> selinium은 기본적으로 chrome을 통해 동작된다. 해당 실행법은 인터넷에 너무 많으니 검색하면 된다.

   코드를 살펴보면 위의 urls에서 url를 한개씩 가져오고 url 배열의 길이만큼 반복을 하게 된다. 맨처음 실행하면 더보기 버튼이 있으므로 css selector 메서드 를 이용하여 클릭해준 뒤 렌더링 되기를 일정한 시간정보 기다린다. 후의 스크롤을 내리는 데 이과정은 굳이 포함안해도 될 듯하다. 더보기 버튼이 없을 시 except를 통해서 해당 while문이 나가지게 된다.

def get_food_information(urls, SCROLL_PAUSE_TIME, URL_length, FILE_NAME):
    BASE_URL = 'https://www.diningcode.com/'
    count = 0
    for url in urls:
        count += 1
        if count <= URL_length:
            address = 'chromedriver.exe'
            driver = webdriver.Chrome(address)
            driver.get(url)
            while True:
                try:
                    # 더보기 버튼 클릭
                    driver.find_element_by_css_selector(
                        "#div_list_more").click()
                    # 몇 초간 기다린다.
                    time.sleep(SCROLL_PAUSE_TIME)
                    # 스크롤을 내린다.
                    driver.execute_script(
                        "window.scrollTo(0, document.body.scrollHeight);")
                except:
                    # 더보기 버튼이 없을 때 while문이 끝남.
                    break

2) BeautifulSoup를 통한 html 문서내 정보 확인

-> 이제 부터 본격적으로 가져온 html 문서내에서 필요한 정보를 찾는 과정이다. 위에 언급한 것처럼 필요한 정보는 

1) 음식점명 2) 대표메뉴 3) 식당 키워드 4) 지역정보 (동/전체주소) 5) 평점로 html을 보면서 각각의 정보를 찾아 내는 과정이 필요하다. 여기서 fin / fin_all / has_attr/ split 함수를 사용하여 해당 정보를 찾아 냈다. 

            html = driver.page_source
            soup = BeautifulSoup(html, 'html.parser')
            li_list = soup.find("ul", attrs={"id": "div_list"}).find_all('li')
            for list in li_list:
                # 여기서 onouseenter의 속성이 있는 것만이 정보가 들어있는 tag이다.
                if list.has_attr('onmouseenter'):
                    # 음식점에 대한정보
                    url_tag = list.find('a', attrs={"class": "blink"})
                    url = f'{BASE_URL}{url_tag["href"]}'
                    img_url = list.find("span", attrs={"class": "img"})[
                        'style']
                    url_image = img_url.split(
                        "background:url('")[1].split("no-repeat")[0][:-3]
                    name = list.find(
                        "span", attrs={"class": "btxt"}).text.split(".")[1]
                    best_menu = list.find("span", attrs={"class": "stxt"}).text
                    key_word = list.find_all(
                        "span", attrs={"class": "ctxt"})[0].text
                    loc_list = list.find_all(
                        "span", attrs={"class": "ctxt"})[1]
                    for loc in loc_list:
                        if len(loc) < 5:
                            loc_dong = loc.text
                        else:
                            loc_address = loc.text
                            lat, lon = getLatLng(loc_address)
                            time.sleep(1)
                    #  평점에 대한정보
                    p_list = list.find_all(
                        "p", attrs={"class": "favor-review"})
                    for list in p_list:
                        score = int(list.find("span", attrs={
                                    "class": "point"}).text[0:2])
                    print(score, url, name, best_menu,
                          key_word, loc_dong, loc_address, lat, lon, url_image)

3)  위에서 찾은 정보를 엑셀 파일로 저장

--> openpyxl을 통해서 위에서 찾은 정보를 sheet별로 저장한다.

 "diningCode_url", "평점", "음식점명", "추천메뉴", "키워드", "동", "주소", "위도", "경도", "img주소"

순서로 저장이 된다. 여기서 아직 위도 / 경도에 대한 말은 언급하지 않았는데 이는 다음 글에서 크롤링한 주소를 가지고 위도 / 경도를 어떻게 변환했는 지에 대해서 설명할 예정이다.

	            if count == 1:
                        ws1.append([url, score, name, best_menu,
                                    key_word, loc_dong, loc_address, lat, lon, url_image])
                    elif count == 2:
                        ws2.append([url, score, name, best_menu,
                                    key_word, loc_dong, loc_address, lat, lon, url_image])
                    elif count == 3:
                        ws3.append([url, score, name, best_menu,
                                    key_word, loc_dong, loc_address, lat, lon, url_image])
                    elif count == 4:
                        ws4.append([url, score, name, best_menu,
                                    key_word, loc_dong, loc_address, lat, lon, url_image])
                    elif count == 5:
                        ws5.append([url, score, name, best_menu,
                                    key_word, loc_dong, loc_address, lat, lon, url_image])
                    elif count == 6:
                        ws6.append([url, score, name, best_menu,
                                    key_word, loc_dong, loc_address, lat, lon, url_image])
                    elif count == 7:
                        ws7.append([url, score, name, best_menu,
                                    key_word, loc_dong, loc_address, lat, lon, url_image])
                    wb.save(filename=FILE_NAME)


# 엑셀파일 불러오기
# 엑셀파일불러오기
FILE_NAME = 'C:/Users/min21/Desktop/coding/python/web_scraping/다이닝코드_전국국밥.xlsx'
wb = Workbook()
ws1 = wb.active
# sheet 1 생성
ws1.title = "서울국밥"
ws1.append(["diningCode_url", "평점", "음식점명",
           "추천메뉴", "키워드", "동", "주소", "위도", "경도", "img주소"])

# sheet 2 생성
ws2 = wb.create_sheet()
ws2.title = '경기국밥'
ws2.append(["diningCode_url", "평점", "음식점명",
           "추천메뉴", "키워드", "동", "주소", "위도", "경도", "img주소"])

# sheet 3 생성
ws3 = wb.create_sheet()
ws3.title = '강원국밥'
ws3.append(["diningCode_url", "평점", "음식점명",
           "추천메뉴", "키워드", "동", "주소", "위도", "경도", "img주소"])

# sheet 4 생성
ws4 = wb.create_sheet()
ws4.title = '충청국밥'
ws4.append(["diningCode_url", "평점", "음식점명",
           "추천메뉴", "키워드", "동", "주소", "위도", "경도", "img주소"])

# sheet 5 생성
ws5 = wb.create_sheet()
ws5.title = '전라국밥'
ws5.append(["diningCode_url", "평점", "음식점명",
           "추천메뉴", "키워드", "동", "주소", "위도", "경도", "img주소"])

# sheet 6 생성
ws6 = wb.create_sheet()
ws6.title = '경상국밥'
ws6.append(["diningCode_url", "평점", "음식점명",
           "추천메뉴", "키워드", "동", "주소", "위도", "경도", "img주소"])

# sheet 7 생성
ws7 = wb.create_sheet()
ws7.title = '제주국밥'
ws7.append(["diningCode_url", "평점", "음식점명",
           "추천메뉴", "키워드", "동", "주소", "위도", "경도", "img주소"])

 

4) 전체코드 ( 카카오 지도 API를 활용한 주소 --> 위도 / 경도 변환 코드 포함 , 다음글에서 설명)

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import time
import requests
from bs4 import BeautifulSoup
from openpyxl import Workbook
import json


# kakao api
# 아래 사이트보고 방법확인
# https://john-analyst.medium.com/%ED%8C%8C%EC%9D%B4%EC%8D%AC%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%B9%B4%EC%B9%B4%EC%98%A4-api%EB%A1%9C-%EC%9C%84%EA%B2%BD%EB%8F%84-%EA%B5%AC%ED%95%98%EA%B8%B0-69bc51697753
# 여기서 아래의 Authoriztion 은 KakaoAK + REST API키 를 사용하면 된다.


def getLatLng(addr):
    url = 'https://dapi.kakao.com/v2/local/search/address.json?query=' + addr
    headers = {
        "Authorization": "API 키 입력"}
    # get 방식으로 주소를 포함한 링크를 헤더와 넘기면 result에 json형식의 주소와 위도경도 내용들이 출력된다.
    result = json.loads(str(requests.get(url, headers=headers).text))
    status_code = requests.get(url, headers=headers).status_code
    if(status_code != 200):
        print(
            f"ERROR: Unable to call rest api, http_status_coe: {status_code}")
        return 0

    # print(requests.get(url, headers=headers))
    # print(result)

    try:
        match_first = result['documents'][0]['address']
        lon = match_first['x']
        lat = match_first['y']
        # print(lon, lat)
        # print(match_first)

        return float(lat), float(lon)
    except IndexError:  # match값이 없을때
        return 0, 0
    except TypeError:  # match값이 2개이상일때
        return 2, 2


# 함수
def get_food_information(urls, SCROLL_PAUSE_TIME, URL_length, FILE_NAME):
    BASE_URL = 'https://www.diningcode.com/'
    count = 0
    for url in urls:
        count += 1
        if count <= URL_length:
            address = 'chromedriver.exe'
            driver = webdriver.Chrome(address)
            driver.get(url)
            while True:
                try:
                    # 더보기 버튼 클릭
                    driver.find_element_by_css_selector(
                        "#div_list_more").click()
                    # 몇 초간 기다린다.
                    time.sleep(SCROLL_PAUSE_TIME)
                    # 스크롤을 내린다.
                    driver.execute_script(
                        "window.scrollTo(0, document.body.scrollHeight);")
                except:
                    # 더보기 버튼이 없을 때 while문이 끝남.
                    break
            html = driver.page_source
            soup = BeautifulSoup(html, 'html.parser')
            li_list = soup.find("ul", attrs={"id": "div_list"}).find_all('li')
            for list in li_list:
                # 여기서 onouseenter의 속성이 있는 것만이 정보가 들어있는 tag이다.
                if list.has_attr('onmouseenter'):
                    # 음식점에 대한정보
                    url_tag = list.find('a', attrs={"class": "blink"})
                    url = f'{BASE_URL}{url_tag["href"]}'
                    img_url = list.find("span", attrs={"class": "img"})[
                        'style']
                    url_image = img_url.split(
                        "background:url('")[1].split("no-repeat")[0][:-3]
                    name = list.find(
                        "span", attrs={"class": "btxt"}).text.split(".")[1]
                    best_menu = list.find("span", attrs={"class": "stxt"}).text
                    key_word = list.find_all(
                        "span", attrs={"class": "ctxt"})[0].text
                    loc_list = list.find_all(
                        "span", attrs={"class": "ctxt"})[1]
                    for loc in loc_list:
                        if len(loc) < 5:
                            loc_dong = loc.text
                        else:
                            loc_address = loc.text
                            lat, lon = getLatLng(loc_address)
                            time.sleep(1)
                    #  평점에 대한정보
                    p_list = list.find_all(
                        "p", attrs={"class": "favor-review"})
                    for list in p_list:
                        score = int(list.find("span", attrs={
                                    "class": "point"}).text[0:2])
                    print(score, url, name, best_menu,
                          key_word, loc_dong, loc_address, lat, lon, url_image)
                    if count == 1:
                        ws1.append([url, score, name, best_menu,
                                    key_word, loc_dong, loc_address, lat, lon, url_image])
                    elif count == 2:
                        ws2.append([url, score, name, best_menu,
                                    key_word, loc_dong, loc_address, lat, lon, url_image])
                    elif count == 3:
                        ws3.append([url, score, name, best_menu,
                                    key_word, loc_dong, loc_address, lat, lon, url_image])
                    elif count == 4:
                        ws4.append([url, score, name, best_menu,
                                    key_word, loc_dong, loc_address, lat, lon, url_image])
                    elif count == 5:
                        ws5.append([url, score, name, best_menu,
                                    key_word, loc_dong, loc_address, lat, lon, url_image])
                    elif count == 6:
                        ws6.append([url, score, name, best_menu,
                                    key_word, loc_dong, loc_address, lat, lon, url_image])
                    elif count == 7:
                        ws7.append([url, score, name, best_menu,
                                    key_word, loc_dong, loc_address, lat, lon, url_image])
                    wb.save(filename=FILE_NAME)


# 엑셀파일 불러오기
# 엑셀파일불러오기
FILE_NAME = 'C:/Users/min21/Desktop/coding/python/web_scraping/다이닝코드_전국국밥.xlsx'
wb = Workbook()
ws1 = wb.active
# sheet 1 생성
ws1.title = "서울국밥"
ws1.append(["diningCode_url", "평점", "음식점명",
           "추천메뉴", "키워드", "동", "주소", "위도", "경도", "img주소"])

# sheet 2 생성
ws2 = wb.create_sheet()
ws2.title = '경기국밥'
ws2.append(["diningCode_url", "평점", "음식점명",
           "추천메뉴", "키워드", "동", "주소", "위도", "경도", "img주소"])

# sheet 3 생성
ws3 = wb.create_sheet()
ws3.title = '강원국밥'
ws3.append(["diningCode_url", "평점", "음식점명",
           "추천메뉴", "키워드", "동", "주소", "위도", "경도", "img주소"])

# sheet 4 생성
ws4 = wb.create_sheet()
ws4.title = '충청국밥'
ws4.append(["diningCode_url", "평점", "음식점명",
           "추천메뉴", "키워드", "동", "주소", "위도", "경도", "img주소"])

# sheet 5 생성
ws5 = wb.create_sheet()
ws5.title = '전라국밥'
ws5.append(["diningCode_url", "평점", "음식점명",
           "추천메뉴", "키워드", "동", "주소", "위도", "경도", "img주소"])

# sheet 6 생성
ws6 = wb.create_sheet()
ws6.title = '경상국밥'
ws6.append(["diningCode_url", "평점", "음식점명",
           "추천메뉴", "키워드", "동", "주소", "위도", "경도", "img주소"])

# sheet 7 생성
ws7 = wb.create_sheet()
ws7.title = '제주국밥'
ws7.append(["diningCode_url", "평점", "음식점명",
           "추천메뉴", "키워드", "동", "주소", "위도", "경도", "img주소"])


urls = ['https://www.diningcode.com/list.php?query=%EC%84%9C%EC%9A%B8%20%EA%B5%AD%EB%B0%A5',
        'https://www.diningcode.com/list.php?query=%EA%B2%BD%EA%B8%B0%20%EA%B5%AD%EB%B0%A5',
        'https://www.diningcode.com/list.php?query=%EA%B0%95%EC%9B%90%20%EA%B5%AD%EB%B0%A5',
        'https://www.diningcode.com/list.php?query=%EC%B6%A9%EC%B2%AD%EB%8F%84%20%EA%B5%AD%EB%B0%A5',
        'https://www.diningcode.com/list.php?query=%EC%A0%84%EB%9D%BC%EB%8F%84%20%EA%B5%AD%EB%B0%A5',
        'https://www.diningcode.com/list.php?query=%EA%B2%BD%EC%83%81%EB%8F%84%20%EA%B5%AD%EB%B0%A5',
        'https://www.diningcode.com/list.php?query=%EC%A0%9C%EC%A3%BC%EB%8F%84%20%EA%B5%AD%EB%B0%A5'
        ]


get_food_information(urls, 1.5, 7, "전국국밥.xlsx")

 

반응형
Comments