촬리의늘솔길

3주차 개발일지~! 본문

✍~2022/WEB

3주차 개발일지~!

리촬리 2021. 7. 26. 16:27

셀레니움 배운다. 마침 해킹 점검 툴 코드 짤때 셀레니움에 대해 처음 알게되었는데

오이게 웬걸~ 때마침 배우네 개이득


이번에 배울것

브라우저 제어 - selenium


웹 스크래핑이란?

웹페이지에서 우리가 원하는 부분의 데이터를 수집해오는 것

  • 한국에서는 같은 작업을 크롤링 crawling 이라는 용어로 혼용해서 쓰는 경우가 많습니다. 원래는 크롤링은 자동화하여 주기적으로 웹 상에서 페이지들을 돌아다니며 분류/색인하고 업데이트된 부분을 찾는 등의 일을 하는 것을 뜻해요. 구글 검색을 할 때는 web scraping 으로 검색해야 우리가 배우는 페이지 추출에 대한 결과가 나올 거예요!
  • 참고 Web Scraping(wikipedia) / Web Crawler(wikipedia) Web Scraping vs Web Crawling: What’s the Difference?
 

Web Scraping vs Web Crawling: What’s the Difference? - DZone Web Dev

In this article, read an explanation of the differences between web scraping and web crawling.

dzone.com

 


멜론 차트처럼 동적인 웹페이지를 스크래핑 할때는 브라우저에 띄운 후 소스코드를 가져오는 방법을 써야함.

좋아요 숫자가 뜨는게 처음 html이 로드될때는 없는거임

서버에 다시 좋아요 개수를 요청하고 입력을 하는건데

리퀘스트가 가져오는거는 딱 날것의 html만 가져오는거라서

브라우저를 띄워서 좋아요 수를 가져오는건데 셀레니움으로만 할 수 있음.

 

1. 크롬 드라이버 다운로드하기

크롬을 셀레니움이 통제할 수 있도록 도와주는게 크롬드라이버임

버전 확인 후에 다운로드 하기.

 

멜론사이트를 스크래핑 해보자.

드라이버 기본 세팅 코드 + 멜론 사이트 스크래핑 코드

from bs4 import BeautifulSoup
from selenium import webdriver
from time import sleep

driver = webdriver.Chrome('./chromedriver')  # 드라이버를 실행합니다.


url = "https://www.melon.com/chart/day/index.htm"
# headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'}
# data = requests.get(url, headers=headers)

driver.get(url)  # 드라이버에 해당 url의 웹페이지를 띄웁니다.
sleep(5)  # 페이지가 로딩되는 동안 5초 간 기다립니다.

req = driver.page_source  # html 정보를 가져옵니다.
driver.quit()  # 정보를 가져왔으므로 드라이버는 꺼줍니다.

# soup = BeautifulSoup(data.text, 'html.parser')
soup = BeautifulSoup(req, 'html.parser')  # 가져온 정보를 beautifulsoup으로 파싱해줍니다.

songs = soup.select("#frm > div > table > tbody > tr")
print(len(songs))

for song in songs:
    title = song.select_one("td > div > div.wrap_song_info > div.rank01 > span > a").text
    artist = song.select_one("td > div > div.wrap_song_info > div.rank02 > span > a").text
    likes_tag = song.select_one("td > div > button.like > span.cnt")
    likes_tag.span.decompose()  # span 태그 없애기
    likes = likes_tag.text.strip()  # 텍스트화한 후 앞뒤로 빈 칸 지우기
    print(title, artist, likes)

request와 비슷해보이지만 

sleep과 같이 조금씩 다름


.decompose()가 뭘까?

<span>abcd</span>

있으면 결과물은 abcd로 바꿔주는거 (태그없애기)


브라우저 제어 - 스크롤, 버튼

네이버 이미지 검색창 스크래핑해보기

화면에 보이는것만 스크래핑 함.

그래서 스크롤 되었을때 이미지도 추가해서 스크래핑

sleep(10)
#기다리게 하는동안 스크롤을 내리고 결과를 보는
driver.execute_script("window.scrollTo(0, 1000)")  # 1000픽셀만큼 내리기
sleep(1)
#맨 밑까지 스크롤 내리기
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
sleep(10)
req = driver.page_source
driver.quit()

excute_script는 쌍따옴표 안에 있는거를 크롬브라우저가 실행을 해줘라는 것임

window.scrollTo -> window 창에서 높이를 1000픽셀만큼 내려서 스크롤을 해줘~


맛집지도 만들기 프로젝트~시작!

API 기술문서

https://navermaps.github.io/maps.js.ncp/docs/

 

NAVER Maps API v3

NAVER Maps API v3로 여러분의 지도를 만들어 보세요. 유용한 기술문서와 다양한 예제 코드를 제공합니다.

navermaps.github.io

 

사용신청하기

1. 회원가입

https://www.ncloud.com/

 

NAVER CLOUD PLATFORM

cloud computing services for corporations, IaaS, PaaS, SaaS, with Global region and Security Technology Certification

www.ncloud.com

 

2. 여기 들어가보면 api신청가능

https://www.ncloud.com/product/applicationService/maps

좌표값 변환하게 해주는거

선택 신청

 

인증정보의 clientsecret과 id를 활용해야함.

 

prac_map.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport"
              content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
        <title>간단한 지도 표시하기</title>
        <script type="text/javascript"
                src="https://openapi.map.naver.com/openapi/v3/maps.js?ncpClientId=YOUR_CLIENT_ID"></script>
        <script src=" https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>

        <style>
            #map {
                width: 100%;
                height: 400px;
            }
        </style>

        <script>
            $(document).ready(function () {
                let map = new naver.maps.Map('map', {
                    center: new naver.maps.LatLng(37.4981125, 127.0379399),
                    zoom: 10
                });
            })
        </script>
    </head>
    <body>
        <div id="map"></div>
    </body>
</html>

 

클라이언트 아이디 나의것으로 바꿔줌

app.py에 html로드할 수 있도록 코드 추가해주고 실행하면!@

 

잘 뜨는거 확인 가능

 

 


지도에 마커 + info window 추가해보기

 

확대/축소 버튼 넣기

let map = new naver.maps.Map('map', {
    center: new naver.maps.LatLng(37.4981125, 127.0379399),
    zoom: 10,
    zoomControl: true,
    zoomControlOptions: {
        style: naver.maps.ZoomControlStyle.SMALL,
        position: naver.maps.Position.TOP_RIGHT
    }
});

마커띄우기

let marker = new naver.maps.Marker({
    position: new naver.maps.LatLng(37.4981125, 127.0379399),
    map: map
});

마커 이미지 바꾸기

let marker = new naver.maps.Marker({
    position: new naver.maps.LatLng(37.4981125, 127.0379399),
    map: map,
    icon: "{{ url_for('static', filename='rtan_heart.png') }}"
});

infowindow만들고 열기

let infowindow = new naver.maps.InfoWindow({
    content: `<div style="width: 50px;height: 20px;text-align: center"><h5>안녕!</h5></div>`,
});
infowindow.open(map, marker);

infowindow닫기

infowindow.close();

강력새로고침 = ctrl +shift+R

 

열고닫는

naver.maps.Event.addListener(marker, "click", function () {
    console.log(infowindow.getMap()); // 정보창이 열려있을 때는 연결된 지도를 반환하고 닫혀있을 때는 null을 반환
    if (infowindow.getMap()) {
        infowindow.close();
    } else {
        infowindow.open(map, marker);
    }
});

event.addListener : 어떤 행동이 발생했음을 알리는거를 등록하겠다.

marker라는 녀석을 기준으로 클릭이 일어났을때 function을 하겠다.

만약 열려있다면 

닫아주고

닫혀있다면 

열어줘라 라는것.


맛집 지도 구성 본격적으로 시작해보기!

구성

 

맛집 정보 스크래핑

지도 보여주기

각 맛집 별 마커, 정보창(infoWindow), 카드만들고 서로 연결하기


맛집정보

http://matstar.sbs.co.kr/location.html

 

SBS TV맛집

 

matstar.sbs.co.kr

카드 하나당 맛집 하나

제목과 주소 있음

스크롤 내리다보면 더보기 버튼 있음 (추가 맛집 정보 로드됨)


맛집 정보 주소 좌표로 변환해주는 api를 네이버에서 가져옴

geocoding 사용법

https://api.ncloud-docs.com/docs/ai-naver-mapsgeocoding-geocode

 

geocode - Geocoding

쿠키 제공 동의 당사는 고객님의 브라우징 기반 정보를 바탕으로 관련 정보 및 광고 제공을 위하여 지식 기반 쿠키를 사용합니다.

api.ncloud-docs.com

curl -G "https://naveropenapi.apigw.ntruss.com/map-geocode/v2/geocode" \
    --data-urlencode "query={주소}" \
    --data-urlencode "coordinate={검색_중심_좌표}" \
    -H "X-NCP-APIGW-API-KEY-ID: {애플리케이션 등록 시 발급받은 client id값}" \
    -H "X-NCP-APIGW-API-KEY: {애플리케이션 등록 시 발급받은 client secret값}" -v

-H는 헤더라는건데, 내가 이 api를 허락 받고 쓰는건지, 아닌건지 네이버가 확인하고 싶다는것.


여러페이지 스크래핑 하기

 

더보기버튼 선택자로 버튼 클릭하기

btn_more = driver.find_element_by_css_selector("#foodstar-front-location-curation-more-self > div > button")
btn_more.click()
time.sleep(5)

숙제가 항상..어렵다

서버와 클라이언트 구문을 제대로 연결하는게 어려움.

index.html

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport"
          content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
    <title>스파르타코딩클럽 | 맛집 검색</title>

    <meta property="og:title" content=" 맛집 지도"/>
    <meta property="og:description" content="mini project for Web Plus"/>
    <meta property="og:image" content="{{ url_for('static', filename='og_image.jpg') }}"/>

    <link rel="preconnect" href="https://fonts.gstatic.com">
    <link href="https://fonts.googleapis.com/css2?family=Jua&display=swap" rel="stylesheet">
    <script type="text/javascript"
            src="https://openapi.map.naver.com/openapi/v3/maps.js?ncpClientId=ID입력h&submodules=geocoder"></script>

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
          integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
          crossorigin="anonymous">
    <link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
    <link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
    <link href="//maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"
            integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
            crossorigin="anonymous"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"
            integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
            crossorigin="anonymous"></script>

    <style>


        .wrap {
            width: 90%;
            max-width: 750px;
            margin: 0 auto;
        }

        .banner {
            width: 100%;
            height: 20vh;
            background-image: url('/static/banner.jpg');
            background-position: center;
            background-size: contain;
            background-repeat: repeat;

        }


        h1.title {
            font-family: 'Jua', sans-serif;

            color: white;
            font-size: 3rem;
        }

        h5 {
            font-family: 'Jua', sans-serif;
        }

        .matjip-list {
            overflow: scroll;
            width: 100%;
            height: calc(20vh - 30px);
            position: relative;
        }

        .card-title, .card-subtitle {
            display: inline;
        }

        #map {
            width: 100%;
            height: 50vh;
            margin: 20px auto 20px auto;
        }

        .iw-inner {
            padding: 10px;
            font-size: smaller;
        }

    </style>
    <script>
        //전역변수
        let y_cen = 37.4981125   // lat
        let x_cen = 127.0379399  // long
        let map;
        let markers = []; //네이버지도 api를 쓰고싶으면 지켜줘야하는 전역변수선언
        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();
        })


        function make_card(i, matjip) {
                let html_temp = ``;
                if ("liked" in matjip) {
                    html_temp = `<div class="card" id="card-${i}">
                                    <div class="card-body">
                                        <h5 class="card-title"><a href="javascript:click2center(${i})" class="matjip-title">${matjip['title']}</a></h5>
                                        <h6 class="card-subtitle mb-2 text-muted">${matjip['category']}</h6>
                                        <i class="fa fa-bookmark" onclick="bookmark('${matjip['title']}', '${matjip['address']}', 'unlike')"></i>
                                        <p class="card-text">${matjip['address']}</p>
                                        <p class="card-text" style="color:blue;">${matjip['show']}</p>
                                    </div>
                                 </div>`;
                } else {
                    html_temp = `<div class="card" id="card-${i}">
                                    <div class="card-body">
                                        <h5 class="card-title"><a href="javascript:click2center(${i})" class="matjip-title">${matjip['title']}</a></h5>
                                        <h6 class="card-subtitle mb-2 text-muted">${matjip['category']}</h6>
                                        <i class="fa fa-bookmark-o" onclick="bookmark('${matjip['title']}', '${matjip['address']}', 'like')"></i>
                                        <p class="card-text">${matjip['address']}</p>
                                        <p class="card-text" style="color:blue;">${matjip['show']}</p>
                                    </div>
                                 </div>`;
                }


                $('#matjip-box').append(html_temp);
            }



        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);
                        let marker = make_marker(matjip);
                        add_info(i, marker, matjip)
                    }
                }
            });
        }

        //마커 만들기
         function make_marker(matjip) {
                let marker_img = '';
                if ("liked" in matjip) {
                    marker_img = '{{ url_for("static", filename="marker-liked.png") }}'
                } else {
                    marker_img = '{{ url_for("static", filename="marker-default.png") }}'
                }
                let marker = new naver.maps.Marker({
                    position: new naver.maps.LatLng(matjip["mapy"], matjip["mapx"]),
                    map: map,
                    icon: marker_img
                });
                markers.push(marker);
                return marker
            }

        //맛집정보
        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.InfoWindow({
                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.close();
                } else {
                    infowindow.open(map, marker);
                    map.setCenter(infowindow.position)

                    //버튼눌렀을때 스크롤 움직이기
                    $("#matjip-box").animate({
                        scrollTop: $("#matjip-box").get(0).scrollTop + $(`#card-${i}`).position().top
                    }, 2000);
                }
            });
        }


        function click2center(i) {
            let marker = markers[i]
            let infowindow = infowindows[i]
            if (infowindow.getMap()) {
                infowindow.close();
            } else {
                infowindow.open(map, marker);
                map.setCenter(infowindow.position)
            }
        }

         function bookmark(title, address, action) {
                $.ajax({
                    type: "POST",
                    url: "/like_matjip",
                    data: {
                        title_give: title,
                        address_give: address,
                        action_give: action
                    },
                    success: function (response) {
                        if (response["result"] == "success") {
                            get_matjips()
                        }
                    }
                })
            }

    </script>

</head>

<body>
<div class="wrap">
    <div class="banner">
        <div class="d-flex flex-column align-items-center"
             style="background-color: rgba(0,0,0,0.5);width: 100%;height: 100%;">
            <h1 class="title mt-5 mb-2">스파르타 맛집 지도</h1>
            <button type="button" onclick="get_matjips()" class="btn btn-sparta">
                        새로고침하고 더 많은 맛집 보기
                    </button>
        </div>
    </div>
    <div id="map"></div>

    <div class="matjip-list" id="matjip-box">
    </div>
</div>

</body>

</html>

app.py

from flask import Flask, render_template, request, jsonify, redirect, url_for
from pymongo import MongoClient


app = Flask(__name__)

client = MongoClient('3.34.182.231', 27017, username="test", password="test")
db = client.dbsparta_plus_week3


@app.route('/')
def main():
    return render_template("index.html")

@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})

@app.route('/like_matjip', methods=["POST"])
def like_matjip():
    title_receive = request.form["title_give"]
    address_receive = request.form["address_give"]
    action_receive = request.form["action_give"]
    print(title_receive, address_receive, action_receive)

    if action_receive == "like":
        db.matjips.update_one({"title": title_receive, "address": address_receive}, {"$set": {"liked": True}})
    else:
        #like라는 필드를 없애버려라
        db.matjips.update_one({"title": title_receive, "address": address_receive}, {"$unset": {"liked": False}})
    return jsonify({'result': 'success'})

if __name__ == '__main__':
    app.run('0.0.0.0', port=5000, debug=True)

에횽횽 어렵지만 재밌당~

 

결과 사이트 ㅎㅎ

http://3.34.182.231:5000/

 

 

728x90

'✍~2022 > WEB' 카테고리의 다른 글

드디어 ! . 완주  (0) 2021.08.01
웹개발+4주차  (0) 2021.08.01
웹개발+/2주차 필기,개발일지  (0) 2021.07.18
WEB 개발+ / 1주차 필기, 개발일지  (0) 2021.07.12
5주차 필기/개발일지  (0) 2021.07.01