brunch

You can make anything
by writing

C.S.Lewis

by FameLee Dec 31. 2022

데이터를 뜯어보자! 2022년 서울 맛집 배틀

회고만큼 맛집에도 진심입니다.

목차
1. 맛집에 진심이기에
2. 맛집 분석 환경
3. 맛집 씹고 뜯고 맛보기
4. 2022 서울 맛집 지역 배틀!
5. 서울 최고의 맛집 지역은?
6. 레퍼런스


맛집에 진심이기에

 나의 취미 중 하나는 홀로 맛집 탐방하기다. 유튜브, 잡지 등에서 맛집 정보를 많이 얻는데, 특히 망고플레이트를 가장 많이 애용한다. 퇴근길에 인스타보다 망고플레이트를 더 자주 둘러볼 때도 있다. 망고플레이트에서 리뷰를 많이 쓰다 보니 망고홀릭(a.k.a 전문 리뷰어)에 선정되기도 했다.

망고홀릭에게 제공하는 리뷰어 뱃지


 이제 2022년이 끝나간다. 회고도 중요하지만, 맛집에도 진심이기에 문득 망고 플레이트의 2022년 맛집은 어느 곳에 많을지 궁금해졌다. 최근에 회사에서 기획과 전략 업무만 주로 하다 보니, 생활 코딩을 안 한지가 오래돼서 오랜만에 공부할 겸 망고플레이트의 데이터를 뒤져보기로 했다. 이른바, 데이터 기반의 2022년 서울 맛집 배틀! 야매로 분석한 만큼, 순수히 재미로 읽어보자

분석 목표 : 서울의 각 지역 별 맛집 현황 알아내기
사용 코드
- Python : 기본 언어
- Pandas : 데이터 정리
- Selenium & BeautifulSoup : 데이터 크롤링
- folium : 지도 기반 데이터 시각화
아아니...! 이 식당은! (출처 : <요리왕 비룡>)





맛집 분석 환경

1. 원천 데이터 찾기

 우선 맛집 정보를 어디서 수집할지 결정해야 한다. 다행히, 망고플레이트는 앱뿐만 아니라, 웹에서도 접속할 수 있다. 앱만큼의 기능은 제공하는 건 아니지만, 웹에서도 다양한 식당 정보를 충분히 확인할 수 있다.



2. 페이지 규칙 확인하기

 데이터를 수집할 곳을 찾았으니, 이제 어떻게 수집(크롤링)할지 고민해볼 차례다. 가장 먼저, 크롤링할 페이지의 규칙을 확인해야 한다. 아래는 망고플레이트의 검색창 이미지다. URL을 보면, "mangoplate.com/search/서울?keyword=서울&page=1"라고 적혀있다. "?" 앞의  "mangoplate.com/search/서울"는 현재 페이지 경로를 뜻한다. 그리고 "?" 뒤에 있는 "keyword=서울&page=1"은 현재 페이지에 적용된 설정값이라고 보면 된다. utm과 마찬가지라고 보면 된다.


 즉, "mangoplate.com/search/서울"라는 페이지에서 "keyword" 변수값으로 "서울"을, "page" 변수값으로 "1"이 적용 됐음을 뜻한다. 바꿔 말해, 망고플레이트 검색 페이지의 규칙은 (1) 키워드 검색값이 "keyword"에, 페이지 값은 "page"에 적용 됨 을 알 수 있다.

 

 그렇다면, 몇 번째 페이지까지 데이터를 수집해야 할까? 10번 째까지? 100번 째까지? 가장 빠르게 파악하는 방법은 URL의 page 변수값을 직접 바꿔보는 것이다. 하단 우측 이미지를 보면, "page=51"을 입력한 결과 관련 정보가 없다고 나온다. 반면, "page=50"을 입력하면, 식당 정보가 나온다. 즉, 현재 검색 결과는 50 page까지만 있다는 걸 알 수 있다. 또한, 호출된 페이지를 보면, 하나의 페이지에서 확인 가능한 식당 개수는 20개임을 알 수 있다.

(좌) 50번 째 페이지 (우) 51번 째 페이지


 결과적으로, 아래처럼 웹사이트의 규칙을 정의할 수 있다.

- 기본 페이지 경로 : mangoplate.com/search/서울
- 검색 키워드 값 : keyword=서울
- 페이지 값 : page=1
- 최대 페이지 수는 50개까지
- 하나의 페이지에 20개의 식당 정보가 호출



3. 서버와 통신하기

 규칙을 알았으니, 이제 서버와 통신해서 전달받은 웹사이트 정보를 크롤링하면 된다. 이때, 주로 (1) requests와 (2) BeatifulSoup 파이썬 패키지를 사용한다. requests 패키지는 서버와 통신을 해서 웹사이트의 HTML 정보를 불러오는 역할을 하고, BeautifulSoup는 HTML 구문을 분석하는 역할을 한다. 대략 아래와 같은 느낌이랄까?

1. requests로 서버와 통신
2. 서버가 요청에 맞춰서 관련 정보를 클라이언트로 전달
3. 전달받은 정보 중 HTML을 BeautifulSoup에게 전달
4. BeautifulSoup로 필요한 데이터를 찾기


 requests.get(URL)을 입력해 해당 URL의 서버와 통신을 하고, 생성된 request 객체의 content 속성에서 관련된 홈페이지 HTML 값을 받아올 수 있다. 하지만, resquest 객체의 raise_for_status 속성을 출력하니 500 에러가 나왔다. raise_for_status는 서버와의 통신 결과를 보여주는데, 정상적으로 통신되면 200이 나와야 한다. 다만, 서버에서 문제가 발생하였으나 문제의 구체적인 내용을 표시할 수 없을 때, 500이란 결과가 나온다.

아앗... 왜?


 해당 문제의 원인은 주로 동적 웹사이트적 요소 때문이다. 뇌피셜임. 아니면 말고... 동적 웹사이트는 requests와 BeautifulSoup만으로 서버와 통신하기 힘들다. 동적 정보는 클라이언트 측에서 인터렉션을 줘야지, 관련 데이터를 불러온다. 가장 대표적인 예시가 유튜브와 같은 무한 스크롤 웹사이트가 있는데, 해당 사이트는 (1) 처음에 콘텐츠 정보를 불러오지 않다가, (2) 스크롤을 하면 그제야 콘텐츠 정보를 불러온다.

왜 안 돼! 도움!!!


 동적 사이트의 크롤링은 requests 대신, Selenium을 사용해야 한다. Selenium은 (1) 가상의 브라우저를 실행시키고, (2) 웹사이트의 자바스크립트나 HTML 요소를 동작시키는 파이썬 패키지다. Selenium을 이용하기 위해서, 가상 브라우저를 제어하는 드라이버가 필수적이다. 크롬, 파이어폭스 등 각 브라우저마다 서로 다른 드라이버가 필요한데, 나 같은 경우는 크롬 드라이버를 사용한다. 크. 롬. 조. 아.


 아래와 같이 selenium으로 크롬 드라이버를 실행시키면, (1) 브라우저 객체가 생성되고 (2) 가상의 크롬 브라우저가 열린다. 해당 객체에 get(URL) 메소드를 실행하면, 가상 브라우저가 해당 URL로 이동된다. 이때, 브라우저 상단에 "Chrome이 자동화된 테스트 소프트웨어에 의해 제어되고 있습니다."라는 안내창이 뜨는데, 이는 드라이버로 직접 실행한 브라우저임을 뜻한다.


 selenium을 사용하면, 더 이상 requests를 사용할 필요가 없다. 앞서 말했듯, requests는 서버와 클라이언트의 통신을 위한 용도로 사용하는데, selenium으로 브라우저를 직접 실행시켜서 서버와 통신을 하기 때문이다. browser 객체의 page_source 속성에 저장된 HTML 정보가 있으며, 이를 BeautifulSoup로 전달하면 된다.

1. selenium로 브라우저 실행
2. 브라우저에서 특정 웹사이트 이동 (서버 통신)
3. 서버가 요청에 맞춰서 관련 정보를 브라우저로 전달 (클라이언트에게 정보 전달)
4. 전달받은 정보 중 HTML을 BeautifulSoup에게 전달
5. BeautifulSoup로 필요한 데이터를 찾기
성공적으로 불러온 HTML 구문





맛집 씹고 뜯고 맛보기

1. 수집할 데이터 정하기

 웹사이트의 HTML 구문을 모두 불러왔으니, 어떤 데이터를 수집할지 결정하면 된다. 망고플레이트에서 제공하는 대표 정보는 (1) 이름, (2) 별점, (3) 주소, (4) 리뷰 개수, (5) 조회 개수가 있으므로, 이를 모두 수집하기로 한다.


2. 수집 위치 파악하기

 수집할 데이터를 정했으면, 해당 데이터가 HTML 구문에서 어디에 존재하는지 알아내야 한다. 그래야 BeautifulSoup를 사용해서 HTML 구문에서 필요한 정보를 파악 및 추출할 수 있다. BeautifulSoup의 작동 원리는 간단한데, (1) 태그 유형과 (2) 속성 정보를 입력해 HTML 구문에서 해당되는 태그를 찾는다. 예를 들어, 아래의 "info.find("h2", attrs = {"class":"title"})" 구문은 info에 저장된 HTML 구문에서 (1) "h2"라는 태그 유형 중에서도 (2) "class" 속성으로 "title"이라는 값을 지닌 태그를 찾음을 뜻한다.


 개발자 도구를 실행하면, 현재 웹사이트를 구성하는 HTML 구문을 확인할 수 있다. 참고로, 크롬은 [설정 아이콘] - [도구 더 보기] - [개발자 도구]에 있다. 확인 결과, 수집하고자 하는 정보는 "class" 속성값이 "info"인 "div" 태그에 있음을 알아냈다.


 다만, 수집하려는 (3) 주소 정보가 뭔가 미흡하다. "www.mangoplate.com/search/서울" 페이지에서 확인 가능한 주소 정보는 "가로수길"처럼, 우편 주소지가 아니다. 아마, 망고플레이트에서 자체적으로 분류한 것인 듯하다. 지역 별 데이터를 집계하기 위해 각 식당의 우편 주소지가 필요한데, 이는 검색 페이지가 아니라 세부 정보 페이지에서 확인 가능하다.

검색 페이지 : mangoplate.com/search/서울?keyword={keyword}&page={pageNum}
세부 정보 페이지 : mangoplate.com/search/restaurants/{id}


 즉, 식당의 (3) 주소 정보를 수집하기 위해서 해당 식당의 상세 페이지로 이동해야 하며, 검색 페이지(/search/서울)에서 각 식당의 세부 정보 페이지의 URL 수집이 필수적이다. 다행히 해당 정보도 "class" 속성값이 "info"인 "div" 태그에 함께 존재한다. 보면, 첫 번째 "a" 태그에 링크가 있는 걸 볼 수 있다.


 

 결과적으로, 수집할 정보와 위치는 아래와 같다.

- 수집 정보
  (1) 이름, (2) 별점, (3) 주소, (4) 리뷰 개수, (5) 조회 개수
- 수집 위치
   1. 검색 페이지에서 식당의 (1) 이름, (2) 별점, (4) 리뷰 개수, (5) 조회 개수 수집
   2. 검색 페이지에서 식당의 세부 정보 페이지 URL 수집
   3. 수집한 세부 정보 페이지로 이동
   4. 식당의 (3) 주소 정보 수집


3. 데이터 수집하기

 이제 코드를 활용해 필요한 정보를 수집하면 된다. 서울 인기 맛집 리스트를 수집하는 크롤링 코드는 다음과 같으며, selenium과 BeautifulSoup뿐 만 아니라, pandas와 time 패키지도 사용한다. pandas는 수집한 데이터를 정리하는 용도로, time은 동적 페이지가 실행된 후에 관련 정보를 모두 호출하기까지 기다리게 만드는 용도로 사용한다. 최종 크롤링한 데이터를 확인해 보면, 아래와 같다. 50개의 페이지에, 각 페이지에 20개의 식당 정보가 호출되니 총 식당 정보는 1,000개가 된다.


 참고로, 위 코드에서 "estimation" 변수는 별점을 의미하는데, "try ~ except ~" 구문이 쓰인 걸 볼 수 있다. 이는 별점 유형이 (1) 예측치 또는, (2) 실제치로 존재하기 때문이다. 망고플레이트는 유저가 남긴 리뷰 정보를 합산해서 별점을 계산한다. 다만, 리뷰 정보가 충분히 모이지 않는 경우, 예측치를 보여준다. 이렇게, 웹사이트에서 존재하는 예외적 규칙도 확인해야 크롤링을 할 때 문제가 없다.

(좌) 회색 글씨인 예측치 (우) 주황 글씨인 실제치






2022 서울 맛집 지역 배틀!

1. 위도 및 경도 데이터 알아내기

 마지막으로, 2022 맛집이 서울에 어떻게 분포되고 있는지 알아내보자. 현재 수집한 주소 정보는 "서울시 강남구 신사동 ~"과 같은데, 지도 시각화를 하려면 위도와 경도 값이 필요하다. 인터넷을 뒤져보니, 다행히 각 동별 위도와 경도를 정리해주신 고마운 분의 글을 찾았다. 행정동 별 좌표 데이터와 망고플레이트 데이터를 비교해, 위도 및 경도 값을 얻으면 된다.


 해당 원본 데이터에서 (1) 서울만 필터링하고 (2) 중복값을 제거했다. 그리고, (3) 시, 구, 동 데이터를 합쳤다. 물론 이렇게 하면, 식당의 정확한 주소가 아닌, 동 주소로 매핑이 되지만... 어쩔 수 없다.,.. 데이터를 못 찾겠는걸 ㅜㅜ


 이제 좌표 데이터와 결합하기 위해, 망고플레이트 데이터를 수정할 차례다. 우선, 원본 데이터를 해치지 않도록, 데이터를 카피해 준다. 판다스의 df 객체는 list 객체처럼 mutable 하다. 즉, 복사한 df 객체가 변하면, 원본 df 객체에도 영향을 준다. 이를 방지하기 위해서, df.copy() 메소드를 사용하면 좋다.


 앞선 좌표 데이터의 칼럼과 동일한 값을 지니도록, 망고플레이트의 주소 형식을 변환한 후, merge 함수를 이용해 두 데이터를 결합하면 된다. 위도 및 경도 데이터가 없어서, 병합이 안 된 부분은 dropna 메소드로 제거한다. 1000개의 식당 중에서 18개의 식당이 사라졌다. 아디오스...


2. 지도에 마커 표시하기

 마지막으로, 식당 별 위도 및 경도 데이터를 지도에 그리면 된다. 지도에 데이터를 보여주기 위해서 folium 패키지를 사용해야 한다. folium으로 map 객체를 생성하고 위도, 경도 등의 정보를 입력해서 각각의 마커를 그려 지도에 표시할 수 있다. 이때, 마커가 겹치는 경우에 보기 힘들므로, makercluster 객체를 사용한다. 그러면, 지도상에서 위도 및 경도를 기준으로 각 인기 식당의 위치가 나오며, 팝업으로 상세 내용을 확인할 수 있다.


3. 지도에 히트맵 그리기

 어떤 구역에 인기 맛집이 몰려있는지 더 잘 보여주기 위해 히트맵을 사용하면 좋다. 앞서 생성한 map 객체에 choropleth 메소드를 사용하면 1개의 마커가 아니라, 여러 마커를 조합해서 특정 면(지역)을 생성할 수도 있다. 마커 3개를 찍고, 모두 선을 그으면 삼격형의 면이 생기는 것처럼 말이다.


 역시 온라인은 집단 지성의 보고로, 서울의 행정 구역을 위도 및 경도를 표시한 json 데이터(링크)가 있다. 해당 데이터를 다음과 같이 불러올 수 있다. "name"이란 변수를 보면, "구" 단위로 표시된 걸 확인할 수 있다.  따라서, 2022 인기 맛집도 구 단위로 데이터를 집계했다.

(좌) 서울 행정구역 json 데이터 (2) 망고플레이트 구별 인기 맛집 갯수


 이제 map 객체에 choropleth 메소드를 사용해 구별 데이터를 전달하면 된다. 그러면 앞서 존재한 마커와 함께, 구별 데이터를 히트맵으로 확인할 수 있다.






서울 최고의 맛집 지역은?

1. 맛집이 많은 지역

 위 히트맵을 확인한 결과, 2022 인기 맛집이 가장 많이 분포된 지역은 강남구, 마포구, 용산구임을 알 수 있다. 내가 사는 서대문구.. 의문의 1패


2. 맛이 뛰어난 지역

 인기 맛집이 얼마나 많은지도 중요하지만, 각 매장이 얼마나 퀄리티가 높은 지도 중요하다! 각 구별 매장의 평가 점수를 평균내면, 오히려 성동구, 동작구, 성북구쪽이 높은 걸 볼 수 있다.


3. 많이 방문한 지역

 사람들이 자주 방문한 지역은 어디일까? 구별 매장의 평균 리뷰 개수를 확인해 보면, 오히려 마포와 관악쪽이 리뷰가 많은 걸 볼 수 있다.


4. 많이 관심 가진 지역

 사람들이 가장 관심 갖는 지역도 확인해 보자. 각 식당의 조회수를 평균내서 집계해 보면, 동작 쪽이 가장 높다.


 최종적으로, 2022 인기 맛집 결과는 아래와 같다. 동작구 식당에 사람들이 관심이 많고, 마포구에 맛집이 많은 걸 확인할 수 있다. 해당 결과는 재미로만 봐주시길!

맛집이 많은 지역 : 강남구, 용산구, 마포구

맛이 뛰어난 지역 : 성동구, 동작구, 성북구

많이 방문한 지역 : 마포구, 관악구

많이 관심 가진 지역 : 동작구


 최종 맛집 지도는 아래서 확인할 수 있습니다.

맛집 배틀은 재미로만 보자! (출처 : <거침없는 하이킥>)





레퍼런스

브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari