맛집 지도 만들기(Web 심화 3)

호호빵·2022년 5월 2일
0

Web

목록 보기
9/12

API 설계

1. scraping.py

  • 사이트에서 셀레니움으로 스크래핑 해오기

2. 메인페이지 (index.html)

  • 지도에 마커로 맛집 보여주기
  • 마커를 클릭하면 목록에서 해당 맛집 찾아주기
  • 목록에서 맛집클릭하면 마커로 찾아주기
  • 각 맛집 별 마커, 정보창, 카드 만들고 서로 연결하기
  • 즐겨찾기 표시는 숙제!

3. 스크래핑 해온 정보를 나타내기 (prac_map.html)

  • 지도 만들기
  • 마커 만들기
  • 정보창 띄우기

프로젝트 setting

뼈대 준비

  • app.py
main, matjip(GET)
  • templates
# index - get_matjips, make_card, make_marker, add_info, click2center

# prac_map - map, marker, infowindow
  • scraping.py
  • static - 배너, favicon 이미지

스크래핑 (scraping.py)

1. 맛집 정보 스크래핑

  • 셀레니움으로 스크래핑하기
  • 각 식당에 해당하는 카드선택하고 식당 이름, 주소, 카테고리, 출연 프로그램과 회차 정보를 출력하기

# 셀레니움으로 스크래핑
driver.get(url)
time.sleep(5)

req = driver.page_source
driver.quit()

soup = BeautifulSoup(req, 'html.parser')


# 카드 선택하고, 정보 출력
places = soup.select("ul.restaurant_list > div > div > li > div > a")
print(len(places))  # 가져온 카드 개수, 처음에 보인 것만 가져옴

for place in places:
    title = place.select_one("strong.box_module_title").text
    address = place.select_one("div.box_module_cont > div > div > div.mil_inner_spot > span.il_text").text
    category = place.select_one("div.box_module_cont > div > div > div.mil_inner_kind > span.il_text").text
    show, episode = place.select_one("div.box_module_cont > div > div > div.mil_inner_tv > span.il_text").text.rsplit(" ", 1)
    print(title, address, category, show, episode)

2. 맛집 정보를 좌표로 변환

  • 요청 파라미터, geocode (status,addresses[].x 나와있음)
  • 네이버에서 제공하는 geocoing을 통해 맛집의 경도와 위도 표시하기
  • 주소에 오류가 있어 결과를 하나도 받지 못하는 경우가 있으므로 결과가 있을 때만 값을 출력
# geocoding 연결
headers = {
    "X-NCP-APIGW-API-KEY-ID": "[내 클라이언트 아이디]",
    "X-NCP-APIGW-API-KEY": "[내 클라이언트 시크릿 키]"
}
r = requests.get(f"https://naveropenapi.apigw.ntruss.com/map-geocode/v2/geocode?query={address}", headers=headers)
response = r.json()


# 결과가 있을 때 맛집의 경도, 위도 표시
if response["status"] == "OK":
	  if len(response["addresses"])>0:
	      x = float(response["addresses"][0]["x"])
	      y = float(response["addresses"][0]["y"])
	      print(title, address, category, show, episode, x, y)
	  else:
	      print(title, "좌표를 찾지 못했습니다")

# float : 소수로 만들어줌. ("12.2" -> 12.2)

3. 맛집 정보 DB에 저장하기

  • 더보기 버튼을 10번 눌러서 정보 스크래핑
  • DB에 저장하기 (doc = {} \ db.matjips.insert_one(doc))
# 셀레니움 드라이버로 css 선택자 요소를 찾아서 btn_more 변수로 지정
btn_more = driver.find_element_by_css_selector("#foodstar-front-location-curation-more-self > div > button")
btn_more.click()
time.sleep(5)

# 더보기버튼 10번 클릭 하기 
for i in range(10):
    try:
        btn_more = driver.find_element_by_css_selector("#foodstar-front-location-curation-more-self > div > button")
        btn_more.click()
        time.sleep(5)
    except NoSuchElementException:  # 에러가 나면
        break

# DB에 저장
doc = {'title' : title, ...}
db.matjips.insert_one(doc)

메인페이지(index.html)

1. 전체 모습 만들기

  • 배너와 지도, 카드 만들기(지도는 prac_map.html 에서)
  • DB에서 맛집 정보 찾아오기(get_matjips())
  • 카드 만들기 (make_card())
# 서버
@app.route('/matjip', methods=["GET"])
def get_matjip():
    # 맛집 목록을 반환하는 API
    matjip_list = list(db.matjips.find({}, {'_id': False}))
    # matjip_list 라는 키 값에 맛집 목록을 담아 클라이언트에게 반환합니다.
    return jsonify({'result': 'success', 'matjip_list': matjip_list})
    
    
    
# 클라이언트
<head>
  <script type="text/javascript"
            src="https://openapi.map.naver.com/openapi/v3/maps.js?ncpClientId={발급ID}&submodules=geocoder"></script>
  <script>
    let y_cen = 37.4981125  # lat 			# 전역변수 지정
    let x_cen = 127.0379399 # long
    let map;
    let markers = [];
    let infowindows = [];

    $(document).ready(function () {			
    	map = new naver.maps.Map('map', {
          center: new naver.maps.LatLng(y_cen, x_cen),
          zoom: 12,
          zoomControl: true,
          zoomControlOptions: {
          style: naver.maps.ZoomControlStyle.SMALL,
          position: naver.maps.Position.TOP_RIGHT
          }
    	});
    	get_matjips()
    })
  </script>
</head>


# 맛집 정보 불러오기
function get_matjips() {
    $('#matjip-box').empty();
    $.ajax({
        type: "GET",
        url: '/matjip',
        data: {},
        success: function (response) {
            let matjips = response["matjip_list"]
            for (let i = 0; i < matjips.length; i++) {
                let matjip = matjips[i]
                console.log(matjip)
                make_card(i, matjip);	# matjips 리스트를 각 함수에 매개변수로 사용
        		let marker = make_marker(matjip);
        		add_info(i, marker, matjip)
            }
        }
    });
}

2. 정보 추가하기

  • 마커 띄우기(make_marker(), prac_map.html에 저장되어 있음)
  • 정보창 띄우기(add_info(), prac_map.html에 저장되어 있음)

# index.html
# 마커 띄우기
function make_marker(matjip){
	let marker = new naver.map.Marker({
      position : new naver.maps.Lating(matjip["mapy"], matjip["mapx"]),
      map : map	//어느 맵에 표시될 것인가! (맨 위에 설정했던 map 불러온거임)
	});
  	markers.push(marker)		
# 위의 전역변수에 지정한 let markers = []에 불러온 마커 정보들이 입력되어 들어감
  	return marker
}

# get_matjips의 for문 내에서 불려지고 있기 때문에, for문이 도는 횟수만큼 marker도 return 됨


# 정보 띄우기, 마커를 누르면
function add_info(i, marker, matjip){ 
  let html_temp = `<div class="iw-inner">
                            <h5>${matjip['title']}</h5>
                            <p>${matjip['address']}
                         </div>`;
  let infowindow = new naver.maps.InfoWindows({
    	content: html_temp,
   		maxWidth: 200,
        backgroundColor: "#fff",
        borderColor: "#888",
        borderWidth: 2,
        anchorSize: new naver.maps.Size(15, 15),
        anchorSkew: true,
        anchorColor: "#fff",
        pixelOffset: new naver.maps.Point(10, -10)
    });
  infowindows.push(infowindow)	# 리스트로 추가 저장
  naver.maps.Event.addListener(marker, "click", function(e) {	# 마커 클릭 시
  	if(infowindow.getMap()){	# infowindow가 맵에 떠있으면
    	infowindow.close();		# 끄고
    }else{						# 그게아니면	
    	infowindow.open(map,marker); # 켜라
      	map.setCenter(infowindow.position)
      	$("#matjip-box").animate({
          scrollTop: 				# 정보창이 닫혀있으면 열고 스크롤해서 위로 보이게
   		$("#matjip-box").get(0).scrollTop + $(`#card-${i}`).position().top
        }, 2000);
    }
  });
}	

3. 추가기능

  • 목록에서 카드가 보이게 스크롤
  • 카드 클릭했을 때 목록창에 맛집 보이기
  • og 태그, favicon 삽입
profile
하루에 한 개념씩

0개의 댓글