[파이썬] 네이버 플레이스, 맛집 (JSON 데이터) 크롤링 (1)
- 프로그래밍/파이썬
- 2020. 6. 12. 22:21
+) +) 제가 크롤링에 사용한 URL 서비스 자체(store.naver.com)가 중단되고 새롭게 개편된 것으로 확인됩니다. 따라서 아래 코드에 사용된 URL로는 현재 크롤링이 불가하니 JSON 응답값을 주는 사이트의 크롤링 코드 작성 방법, 방식 위주로만 확인 부탁드립니다!
빅데이터 관련 수업을 들은 적이 있는데 뭔가 데이터 처리를 하기 전에 일단 데이터를 크롤링해와야 했던 과제가 있었다. 네이버 플레이스에서 맛집을 크롤링해보기로 해서 약 12만 건? 정도 크롤링했었다. 이번 포스팅에서는 네이버 플레이스에서 서울시 맛집을 구/동별로 크롤링하는 코드를 소개해보려 한다.
서울시 구/동 정보, 파일에 저장해놓기
서울시의 구/동 정보를 먼저 탐색하는 이유는 두가지이다. 첫 번째는 크롤링할 때, URL 매개변수로 구/동을 전달하기 위함인데 구/동 기준으로 크롤링하기 위함이라고 보면 된다. 두번째는 나중에 크롤링된 데이터를 활용할 때, 구/동으로 검색이 가능하도록 GUI를 구성하도록 하기 위함이다.
-
크롤링할 때, URL 매개변수로 구/동을 전달하기 위해
-
나중에 구/동으로 검색 시, 크롤링된 데이터에서 결과를 찾아주는 시스템으로 만들기 위해 (GUI)
어쨌든 그러기 위해선 정확한 서울시의 행정구역 정보가 필요한데 서울 공공포털에서 다운받은 공공데이터를 활용했다. 아래 링크에 나와있는 서울 열린데이터광장의 데이터셋에서 동별을 검색하면 서울시 동별 관련 통계자료를 여러 개 볼 수 있다.
그중 아무거나 다운로드하여서 아래 사진과 같이 구/동 정보만 따로 txt에 저장해놓았다. 나중에 엑셀파일(seoul_code.txt)에서 구/동정보를 긁어서 파이썬 딕셔너리에 저장할 것이다.
서울시 구/동 정보 얻어오기
seoul_code.txt 파일을 읽어 구/동 정보를 seoul_dict 딕셔너리에 저장한다. 행 단위로 읽으면서 ","를 기준으로 구와 동을 나누고, 동은 dong_list에 추가시키고 딕셔너리의 key는 구로 설정한다. 그리고 '\n'을 만나면 하나의 구가 끝났다는 것이므로 추가해온 dong_list를 딕셔너리에 추가해준다.
딕셔너리에 넣든, 이중 리스트에 넣든, 어찌 됐든 구와 동을 편한 대로 자료구조에 저장하면 된다. 본격적인 크롤링 시, 각 구/동을 기준으로 루프를 돌게 된다. 참고로, 이 다음부터의 CRAWL 클래스 내부의 함수는 탭이 너무 많아져서 함수만 따로 분리해 작성한다. 탭간격 조정이 되면 좋을 텐데.. 탭 간격이 너무 크다ㅠㅠ
class CRAWL:
def __init__(self):
self.seoul_dict = {}
# seoul_code.txt -> dictionary
def get_seoul_code(self):
dong_list = []
with open("seoul_code.txt","rt",encoding='UTF8') as f:
for line in f:
if line == '\n':
self.seoul_dict[key] = dong_list
dong_list = []
continue
tmp = line.split(",")
gu = tmp[0]
dong = tmp[1].replace("\n","")
dong_list.append(dong)
key = gu
크롤링 대상 페이지 선정 : 네이버 플레이스
맛집 정보를 어디서 크롤링할까 하다가 네이버 플레이스를 활용하기로 했다. 네이버 플레이스는 그냥 검색창에 검색을 해보니 '네이버 MY플레이스', '네이버 스마트플레이스' 이것밖에 안 나오던데... 이 2개는 다른 페이지이다. 네이버 검색창에 예를 들어 '신림동 맛집'을 검색하면 아래와 같이 검색 결과가 뜨고, 검색 결과 중 하나를 클릭하면, 네이버 플레이스 페이지가 나온다.
크롤링 URL 매개변수 구성 및 요청
URL을 보면, "store.naver.com/restaurants/detail?id=XXXXX" 이런 식인데 이 URL 형식으로는 구/동별로 크롤링하기가 당연히 힘들기 때문에 아래 URL을 사용했다. 아래 URL은 네이버 플레이스 크롤링해봤던 연구실 친구가 크롤링하기 편하다고 해서 알려줬는데 진짜 편하다.
"store.naver.com/sogum/api/businesses?start=1&display=100&query=관악구+신림동+맛집&sortingOrder=reviewCount"
매개변수가 3개인데, start는 페이지 수를 넘기는 부분이고 display는 페이지 당 보여지는 수이고 query는 검색 쿼리를 입력하면 된다. display는 100으로 고정해놓을 것이고, start는 일단 코드가 전체적으로 잘 돌아가는 것을 확인하는 것이 먼저이므로 10까지 할 것인데 10까지 검색이 안 되는 경우를 따로 처리를 해주어야 한다.
query는 "[구]+[동]+맛집"으로 구성할 것이다. 여기서 구/동은 위에서 저장해놓은 seoul_dict을 이용하면 된다. sortingOrder는 reviewCount로 리뷰 수가 많은 순대로 정렬되도록 했는데 단순히 '리뷰 수가 많은 음식점은 맛집으로 생각하자'라고 전제했기 때문이다. 사실, 각 구/동 별 크롤링하는 음식점 수가 많기 때문에 상위 몇 개의 음식점을 제외하곤 맛집이라고 생각하기 곤란할 수 있다. 맛집을 구체적으로 선별하기 위해선 이다음 포스팅에서 파싱하는 데이터 중 리뷰수를 파싱해서 직접 절대적인 리뷰수를 조건으로 설정해놓던가, 아니면 다른 방식을 생각해서 적용하던가 해야 할 듯하다.
아래 코드에서 볼 수 있듯이, 딕셔너리/동리스트/start변수 해서 총 3중 for문을 돌게 되는데 이 방법 말고 현재로써는 딱히 떠오르는 게 없어서 의식의 흐름대로 작성했다. 혹시 이것보다 좀 더 시간효율적인 방법이 있다면 댓글로 남겨주시면 감사하겠습니다. 또한, 혹시 몰라 request.get(url) 앞에 sleep을 주었다. 이다음 포스팅에서 언급하는데, 처음에는 seep을 1 정도로 주었었는데 중간에 크롤링이 막히는 문제가 있어 5 정도로 주니 해결이 되었다.
# start crawling
def crawling(self):
print('[*] Start Crawling...')
f = open('test.csv','w',encoding='utf-8-sig', newline='')
f.close()
# loop for gu/dong
for gu,dong_list in self.seoul_dict.items():
print('-'+gu)
for dong in dong_list:
print(' -'+dong)
gu_dong = gu+'+'+dong+'+'+u'맛집'
display = 100
#send query(start:1,display:100->start:101,display:100 ...)
for start in range(1,11):
url = 'https://store.naver.com/sogum/api/businesses?start='+str(start) \
+'&display='+str(display) \
+'&query='+gu_dong \
+'&sortingOrder=reviewCount' \
time.sleep(5)
data = requests.get(url)
위 URL을 직접 확인해보면, 아래와 같이 json 데이터임을 확인할 수 있다. json 데이터이기 때문에 파이썬의 json 모듈을 사용해서 파싱해줄 것이다.
위 데이터는 query 부분이고 아래부터 각 음식점 별 정보가 저장되어 있는 items가 리스트 형태로 쭉 나열되어 있는 구조이다.
참고로, json 데이터는 그냥 보면 가독성이 매우 떨어지기 때문에 크롬일 경우 JSONView와 같은 확장 프로그램을 설치해놓는 것이 좋다. 관련 내용은 아래 포스팅을 확인하길 바란다.
[크롬 확장프로그램] JSONView로 json 데이터 가독성있게 보기
보통 페이지가 1000까지 가는 경우는 당연히 없을 것이기 때문에 상태 코드가 어떻게 나오는지 좀 확인을 해보면, 상태코드가 500으로 찍힌다.
따라서 상태 코드를 체크해주어 500일 경우 반복문을 끝내는 구문을 추가해야 한다.
# start crawling
def crawling(self):
# 코드...
# get data and check status code
if data.status_code == 500: break
네이버 플레이스, 크롤링 전체 코드 (일부)
여기까지의 전체 코드는 아래와 같다. 코드를 보면 알겠지만, 결국 각 구의 각 동별로 위에서 봤던 쿼리를 날리는 방식이다.
#-*-coding:utf-8-*-
import os
import sys
import json
import time
import requests
import csv
class CRAWL:
def __init__(self):
self.seoul_dict = {}
self.tmp_list = []
self.data = []
self.write = []
# seoul_code.xlsx -> dictionary
def get_seoul_code(self):
dong_list = []
with open("seoul_code.txt","rt",encoding='UTF8') as f:
for line in f:
if line == '\n':
self.seoul_dict[key] = dong_list
dong_list = []
continue
tmp = line.split(",")
gu = tmp[0]
dong = tmp[1].replace("\n","")
dong_list.append(dong)
key = gu
# parsing json data using items
def get_json_value(self,key,i):
try:
if key in self.data["items"][i]:
value = self.data["items"][i][key]
#self.tmp += value
self.write.append(value)
else:
#self.tmp += "N"
self.write.append("None")
except:
self.stop = 1
# start crawling
def crawling(self):
print('[*] Start Crawling...')
f = open('test.csv','w',encoding='utf-8-sig', newline='')
f.close()
#loop for gu/dong
for gu,dong_list in self.seoul_dict.items():
print('-'+gu)
for dong in dong_list:
print(' -'+dong)
gu_dong = gu+'+'+dong+'+'+u'맛집'
display = 100
self.stop = 0
# send query
for start in range(1,6):
url = 'https://store.naver.com/sogum/api/businesses?start='+str(start) \
+'&display='+str(display) \
+'&query='+gu_dong \
+'&sortingOrder=reviewCount' \
# request url
time.sleep(5)
data = requests.get(url)
#get data and check status code
if data.status_code == 500: break
# 코드...
if __name__ == "__main__":
# gu = sys.argv[1]
# dong = sys.argv[2]
cr = CRAWL()
cr.get_seoul_code()
생각보다 포스팅이 길어져서 크롤링한 데이터를 파이썬 json 모듈을 사용해 파싱하고 저장하는 내용과 코드는 아래 다음포스팅에서 이어서 작성하도록 하겠다.
다음포스팅
[파이썬] 네이버 플레이스, 맛집 (JSON 데이터) 크롤링 (2)
'프로그래밍 > 파이썬' 카테고리의 다른 글
[파이썬] pandas로 csv에서 특정 값을 가진 행 찾기 (0) | 2020.06.14 |
---|---|
[파이썬] 네이버 플레이스, 맛집 (JSON 데이터) 크롤링 (2) (19) | 2020.06.13 |
파이썬 wordcloud를 사용한 한글 명사 시각화 (8) | 2020.02.15 |
파이썬 KoNLPy를 사용한 한글 명사 추출 및 빈도 계산 (28) | 2020.02.14 |
KoNLPy (파이썬 한글 형태소 분석기 ) 윈도우 설치 방법 (9) | 2020.02.14 |