[개요]
3학년 1학기 텀 프로젝트 주제로 식당 테이블 예약 앱을 만들기로 했다. Demo앱처럼 지도 api 넣어서 지도위에서 마커 누르고 하는 것 보다, list형식으로 쭉 띄워주는게 훨씬 보기 좋고, 그게 맞는 방향이라 생각 해서 그 방식으로 진행하려 했다.
단순히 geolocator로 현재 위치를 뽑아오고, 그 위치를 google map api로 넘겨서 주변 식당 list를 쭉 뽑아온 뒤 작업을 하려 했는데 여러 가지 문제가 발생했다.
[문제]
1. google map api에서 fetch해 올 수 있는 주변 식당 list는 최대 60개다.
최대 60개라고는 하지만, test해보니 40개 밖에 안 되는듯 하다. 사실상 40개면 이 api를 이용해서 앱 개발은 불가능 하다고 봐야한다.
naver나 kakao map api를 쓰면 사용하기에 더 복잡하고, 게다가 가져올 수 있는 식당 list가 더 적다
2. 주변 식당 list를 모두 가져올 수 있다고 해도, 그걸 처리하는 건 별개의 문제다.
결국은 식당 정보를 받아오게 되면,
1)그 식당들이 db에 있는지 확인하고
2)있으면 남은 table정보나 메뉴, 리뷰 정보를 return한다
3)없으면 insert해주고 그 정보를 return한다.
새로운 장소에서 사용자가 접속할 경우, api로 식당 list들을 전부 받아와서, 그 각각의 식당을 db에 검색하고, db에 insert해서 return해주어야 하는데.. api로 긁어오는 것조차 느린데 그걸 전부 read하고 insert하는게 오래 걸릴 것이다.
물론 lazy하게 show해주면 해결 되기는 한다.
3. 그럼 애초에 db에 전부 넣어두면 어떨까?
첫 번째 문제인 google map api로는 40개 밖에 못 긁어 오기 때문에 다른 대책을 세워야한다. 당연히 생각한 것은 '공공데이터'
하지만, 지역구 별로 데이터가 있는 것도 있고 없는 것도 있고 지역구 마음대로다. 즉, 전국의 모든 data를 제공하지 않는다.
우리 학교 주변의 data만 긁어오고 싶어도 우리 지역구의 data는 존재하지도 않는다.
그래서, 아예 전국 식당목록 db를 사려고 가격을 알아보니 열람기간 24시간에 99만원이나 한다... 한 학기 텀 프로젝트로 소비할 데이터의 가격치고는 너무 비싸다.
[해결책]
그래서 최종 해결책은? 전부 크롤링해오기로 했다.
먼저, 전국 식당 list를 얻기 위해 naver, kakao, google map을 모두 테스트해 본 결과,
크롤링이 가능하고 의미있는 데이터를 얻기위해서는 kakao가 제격이라고 판단된다.
naver는 크롤링을 막기위해서 인지 좌표가 거의 random으로 변하는 수준이고, google을 데이터가 부족하다.
아, 이제 전국 단위로 '식당'을 검색해서 나오는 모든 정보를 긁어오자 싶어서 kakao map에 '식당'을 검색하니 90만개의 결과가 나왔고, 하나하나 페이지 넘겨가며 크롤링하려 했는데...
한번에 보여주는 데이터가 최대 520개다.
즉, 다시 말해 지도를 잘게 잘라 나누어서 520개 이하로 결과가 나오게 '식당'을 검색해야 전국 식당 data를 모두 뽑을 수 있다.
여러가지 테스트 결과, 최대 zoom level인 13의 직전인 12일때 520개 초과로 결과가 나오는 경우도 있어서, 최대 level인 13으로 해야 최대한 많은 데이터를 긁어 올 수 있다.
그냥 naive하게 원하는 크롤링 범위(직사각형)을 zoom level 13일때의 지도 크기로 고정하고, 하나하나 완전탐색하듯이 for loop 2개 돌려서 돌리니 '부산'이 들어가는 최소 직사각형 범위의 식당만 크롤링하는데 대략 2일 정도 걸린다.
전국을 다 크롤링하는데에는 길게는 6개월, 짧게는 2개월정도의 시간이 걸릴 것이라 계산되었다.
한 학기의 텀 프로젝트를 하기에는 불가능한 데이터 처리 시간일 뿐만아니라, 중간에 크롤링돌리는 서버가 다운되거나 kakaomap에서 reject라도 날리는 날에는...
물론, 실험담당 조교님이 부산만 처리해도 충분할 듯 하다고 얘기하셔서 크게 문제가 되지는 않지만, 2년동안 ps 공부한 내가 그냥 넘어가면 안 되지.
[DFS, 백트래킹]
답은 'DFS, 백트래킹'이다. 사실 아이디어는 너무 간단하다.
전국을 모두 포함하는 직사각형의 LT, RB을 구하고, 그 LT, RB직사각형 범위의 식당 데이터가 520개가 넘어가면 직사각형을 4등분하여 더 zoom하여 다시 식당 데이터를 검색한다. 520개 이하가 될 때 까지 zoom해서 검색하고, 520개 이하가 되면 거기서 크롤링한 뒤 return한다.
자, 얼마나 빨라졌을까?
식당 데이터의 distribute가 사실상 randomly하다고 봐도 무관하기때문에, 얼마나 시간이 줄어드는가는 계산이 힘들지만, 산이 많은 우리나라의 특성상, DFS의 depth가 깊어지기 전에 pruning되는 경우가 매우 많다.
radomly distribute한 data기 때문에 무의미한 비교라고도 볼 수 있지만,
현재 18시간 for loop돌린 크롤러는 고작 3.8mb의 데이터를 쌓았고, 전체 크롤링해야하는 면적의 거의 절반정도의 진행률을 갖고 있다. 즉, 부산전체를 cover하는 직사각형의 크기의 절반정도의 면적을 크롤링 했고, 남은 시간은 대략 18시간 이다.
반면, 백트래킹으로 pruning해가는 크롤러는 3시간 돌린 현재, 대략 14.6mb의 데이터를 쌓았고, 현재 진행상황은 파악이 불가능하다..
근데... DFS을 할 때 한 가지 단점이 있다.
map을 zoom 할 때 마다 정확히 2배씩, 즉 면적은 4배씩 증가하는데 browser의 크기에 따라 변하는 비율이 일정하지 않은 것으로 보이며, 가끔 이상한 data를 읽어올 때도 있다.
가령, 현재 충청남도의 식당 data를 크롤링 중이고, data들을 보면 대부분 LT에 가까운 지방의 데이터를 긁어왔는데, 갑자기 뜬금없이 경남 거제시의 데이터를 크롤링해온 기록이 남아있다.
DFS의 logic은 간단해서 알고리즘의 문제는 아니라 판단되는데 또 경기도 화성에서 해운대로 가네...
일단 data를 모두 쌓아놓은 뒤, 뭐가 문제였는지 파악해봐야 겠다.
--추가
글을 개시하기 직전에 다시 봤는데.. DFS 로직을 잘 못 짯다. 어제 새벽에 급하게 짜느라... 에휴.. 어쩐지 직사각형 내에 북한도 포함 되기 때문에 북한으로 location이 옮겨가야하는데 안 옮겨가더라... 다시 데이터 쌓아야한다.
--추가 21.04.01
왜 인지는 모르겠으나, 노트북으로 돌릴 때에는 죽는 경우가 없는데 서버에서 돌릴때는 카카오맵에서 reject를 계속 날린다. for loop으로 부산만 크롤링 하던 크롤러가 진작 끝났어야하는데 이 때문에 거의 진행이 안 되어서 reject날리면 접속 될 때 까지 새로고침 하도록 수정해주었다.
그리고, browser크기에 따라 zoom 비율이 이상하게 달라져서 같은 data를 여러번 읽어오는 문제도 최대한 수정해주었다. 그냥 browser크기를 1390 x 1000으로 고정하고 돌려주고 있다(kakao map의 좌측 탭이 390을 차지한다)
지금까지 크롤링한 데이터를 보니 같은 식당을 5번 6번씩 가져오고 있었고, 그 말인 즉슨 최대 5~6배 느리다는 소리이므로, 얼른 수정해서 다시 돌리고있다.
물론, 이렇게 해준다고 해서 중복되는 데이터가 안 들어오지는 않기 때문에 나중에 중복 데이터를 제거해줘야한다. 80만개의 data가 약 120mb이니 총 90만개의 식당, 중복포함 100만개로 잡으면 넉넉히 150mb로 볼 수 있고, 굳이 해싱을 안 해주더라도 정렬후 unique만 써도 될듯하다.
--추가 21.04.02 00시
고작 12시간 돌렸는데, 말도 안 되게 압도적으로 빠르다. 크롤링 window를 정사각형으로 바꾸어 제대로 긁어오게 수정하니 data는 거의 distinct하게 들어오고, 그에 따라 거의 배의 속도를 보여주는 듯하다.
기존에 직사각형으로 돌리던 크롤러는 이제 겨우 경기도~충북 쪽을 크롤링하고 있는 반면, 완벽하게 수정된 크롤러는 전북까지 내려가서 크롤링중이다. 돌린시간은 거의 3배 차이가 나니까 예상했던대로 6배 정도 차이가 난다.
끝까지 돌려서 얼마나 차이나는지 확인해보고 싶으나, 노트북이 제발 한번만 꺼달라고 부탁하므로, 기존의 크롤러를 종료하겠다
--추가 21.04.02 14시
약 24시간만에 전부 크롤링되었다. 중복포함 약 156mb로 예상과 같이 들어왔고, 중복제거시 133mb정도 이다.
이제 남은건 주소를 위도, 경도로 바꾸는 작업인데 오픈api 로 변환가능하고, 전부 변환하는데 약 3~4일 정도 소요된다.
그리고 변환이 완료 되면 detail페이지 크롤링과 변환되지 않은 주소 재변환이 필요하다.
위 오픈 api로 변환 시 규칙에 맞는 정확한 주소만 변환이 가능해서, 정확하지 않는 주소인 약 1%는 변환되지 않는다.
따라서, google gecoder api를 사용해서 변환하면 무조건 변환되게 되므로 변환해주고, 주소 재확인 warning attribute를 줘서 client단에서 주의 표시를 띄워주도록 처리해주고, client가 수동으로 업데이트 신청을 하는 형식으로 처리해주면 될 듯하다.
detail page는 puppeteer를 쓸 필요 없이 axios로 바로 http 긁어오면 되니 하루면 될 듯 하다.