본문 바로가기

크롤링, 스크래핑

웹사이트 크롤링 AWS EC2 Django 프로젝트에 적용

결과물

 

*본 게시물은 아래의 게시물에서 이어지는 게시물이다.

https://dong5854.tistory.com/16

 

selenium과 bs4를 이용한 동적 웹사이트 크롤링

사용목적 우리가 개발중인 웹 페이지에 선박명을 입력받으면 선박의 위치를 지도에 나타내는 기능을 넣으려고 한다. 하지만 선박의 이름을 입력받아 선박의 위치를 알려주는 API서비스는 찾지

dong5854.tistory.com

AWS EC2 인스턴스에 있는 Django 프로젝트에 위의 게시물에서 작성한 크롤링 프로그램을 합치는 작업을 했다.

작업을 성공적으로 마친 후에는 다음과 같은 결과가 나타났다.

크롤링과 기존 시스템을 통해 성공적으로 작동을 한 경우
marinetraffic API에서 선박의 정보를 제공하지 않는 경우
존재하지 않는 선박을 검색했을 경우

크롤링과 기존시스템을 통해 성공적으로 작동을 한 경우는 첫 번째 사진처럼 결과가 나타나고 선박은 존재하지만 marinetraffic API에서 선박에 대한 위치정보를 제공하지 않는 경우는 두번째 사진과 같이 경고창이 정보를 찾을 수 없다는 경고창이 나타나고,  선박 자체가 존재하지 않는 선박인 경우에는 해당하는 선박을 찾을 수 없다는 경고창이 나타난다.

준비과정

윈도우 환경에서 로컬로 돌리던 크롤링 파일을 돌리기 위해 AWS ec2 우분투 인스턴스에도 크롬을 설치하고 그 버전에 맞는 크롬드라이버를 설치해야 하는데 내가 설치를 하면서 본 블로그의 링크를 남긴다.

https://dvpzeekke.tistory.com/1

 

AWS EC2 ubuntu에 selenium, chrome, chromedriver 설치하기

본 내용은 aws ec2 ubuntu 서버에서 진행됩니다. selenium 설치하기 selenium을 포함한 다른 패키지들을 설치할 때, pip을 사용합니다. $ sudo apt-get install python-pip 위 명령처럼 pip을 설치해주세요. $ sud..

dvpzeekke.tistory.com

 

코드 살펴보기

프로젝트의 코드들 중에서 이번 작업과 관련이 있는 부분들을 살펴보자

//ship_position_page.html의 script 태그

$(document).on("click", "#search_button", function () {	//html의 search_button아이디가 부여된 요소를 클릭시 실행
  var search_key = $('#shipCallCode').val()
  $.ajax({
    type: 'GET', 
    url: "{% url 'single_Vessel_position' %}",
    dataType : "json",	//서버측에서 전송받은 데이터의 형식
    data: { //서버측에 보내 줄 데이터
      'search_key': search_key 
    },
    error: function () {
      // 선박명이 없는경우 response에 실패
      alert("해당하는 선박을 찾을 수 없습니다.")
    },
    success: function (data) {	//서버에서 성공적으로 데이터를 받아옴
      $('#card_position').empty() //기존 card_position ID가 부여된 부분을 비운다.
      data_json = data
      
      let temp_mmsi = data_json['mmsi'] //서버에서 받아온 데이터를 넣어주는 부분
      let temp_latitude = data_json['latitude']
      let temp_longitude = data_json['longitude']
      temp_html = `        <div class="card text-dark bg-light mb-3"> //추가할 html 백틱(`)을 사용함에 유의
        <div class="card-header">${search_key}</div>
        <div class="card-body">
          <h5 class="card-title"></h5>
          <p class="card-text">MMSI : ${temp_mmsi}<br>경도 : ${temp_latitude}<br>위도: ${temp_longitude}</p>
        </div>
      </div>
      if (data_json['result'] == 'fail_data'){
        alert("정보를 제공하지 않는 선박입니다.")
      }
      markerMaking() //지도 마커표시에 관여하는 함수(블로그에서는 보여주지 않는 함수)
      $('#card_position').append(temp_html) //card_position ID를 가지고 있는 요소에 만들어둔 html을 append
    }
  })
})

ajax를 통해 검색창에 들어가 있는 값(선박명)을 서버측으로 전해준다. 서버에서 이 데이터(선박명)을 이용하여 IMO 번호를 알아낸 후 이를 다시 클라이언트 측으로 보내준 후 이를 이용해 html을 가공하는 방식이다.

 

#views.py
from django.shortcuts import render
from typing import Protocol
import requests
import json
from django.http import HttpResponse
from django.http import JsonResponse
import sys, os
sys.path.append(os.path.dirname(os.path.abspath(os.path.dirname(__file__)))) //해당 파일의 상위 디렉토리에 있는 모듈을 임포트 하기 위해 넣은 부분
from crawling import imo_crawling //상위 디렉토리의 crawling 폴더 안의 imo_crawling.py 임포트


def single_Vessel_position(request):
    if request.method == 'GET':
        api_key ='1f86e6bc78c4a862fc418932b8649f7d44469e32'
        timespan = '20'
        Protocol_type = 'jsono'
        search_key = request.GET['search_key']
        
        IMO = imo_crawling.get_IMO(search_key) //IMO번호를 받아오는 크롤링을 하는 함수에 선박명을 넣어준다.

        pre_single_vessel = 'https://services.marinetraffic.com/api/exportvessel/v:5/'
        queryParams = {'service_key': api_key, 'timespan': timespan, 'Protocol_type' : Protocol_type, 'IMO' : IMO}

        api_url = pre_single_vessel + queryParams['service_key'] + '/timespan:' + queryParams['timespan'] + '/protocol:' + queryParams['Protocol_type'] + '/imo:' + queryParams['IMO']

        print(api_url)

        r = requests.get(api_url)
        r = r.json()
        #정보가 없는 예외처리
        if not r:
            return JsonResponse({'result' : 'fail_data'})
     

        res_mmsi = r[0]['MMSI']
        res_latitude = r[0]['LAT']
        res_longitude = r[0]['LON']
        res_speed = r[0]['SPEED']
        res_heading = r[0]['HEADING']
        res_course = r[0]['COURSE']
        res_status = r[0]['STATUS']
        res_timestamp = r[0]['TIMESTAMP']
        res_dsrc = r[0]['DSRC']

        context = {
            'result' : 'success',
            'mmsi' : res_mmsi,
            'latitude' : res_latitude,
            'longitude' : res_longitude,
            'speed' : res_speed,
            'heading' : res_heading,
            'course' : res_course,
            'status' : res_status,
            'timestamp' : res_timestamp,
            'dsrc' : res_dsrc
        }
        print(context)
        print('mmsi:' + res_mmsi)
        print('latutude:' + res_latitude)
        print('longitude:' + res_longitude)
        print('speed:' + res_speed)
        print('heading:' + res_heading)
        print('course:' + res_course)
        print('status:' + res_status)
        print('timestamp:' + res_timestamp)
        print('dsrc:' + res_dsrc)
        
        return JsonResponse(context)

    else:
        return HttpResponse("GET이아님")

html의 ajax를 통해서 보낸 데이터를 이용하여 imo 번호를 구하고 이 imo 번호를 다시 API에 활용해 선박의 위치정보를 받아와 리턴해서 다시 클라이언트 측으로 보내주는 함수이다. 이 코드를 작성하며 조금 시간을 잡아먹은 부분은

        #정보가 없는 예외처리

        if not r:

            return JsonResponse({'result' : 'fail_data'})

 

이부분인데 정보가 없을 때 이 r이 빈 리스트로 값이 들어와 if r == []: 로 조건문을 주었는데 제대로 if문으로 들어가지 않아 구글링으로 방법을 찾아보니 빈 Sequence(String / Tuple / List)는 False 값을 가지기 때문에 is not r: 로 if문을 처리할 수 있다는 것을 알았다.

 

#imo_crawling.py

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


def get_IMO(search_key):

    options = webdriver.ChromeOptions()
    options.add_experimental_option("excludeSwitches", ["enable-logging"])

    # 크롬 드라이버 경로 설정, 및 옵션 설정(옵션은 DevToolsActivePort를 찾을 수 없다는 에러 해결을 위해)
    chrome_options = webdriver.ChromeOptions()
    chrome_options.add_argument('--headless')
    chrome_options.add_argument('--no-sandbox')
    chrome_options.add_argument("--single-process")
    chrome_options.add_argument("--disable-dev-shm-usage")
    path = '/home/ubuntu/portwebsite/crawling/chromedriver'
    driver = webdriver.Chrome(path, options=chrome_options)
    #--------------------------------------------------------------------------------------------------------

    url = "https://new.portmis.go.kr/portmis/websquare/popup.html?w2xPath=/portmis/w2/sample/popup/pop/UC_PM_CM_002_07_02.xml&menuCd=M0182&popupID=mf_tacMain_contents_M0182_body_popupSearchVsslInnb&idx=idx10_16280424157712233.0042600478396&w2xHome=/portmis/w2/main/&w2xDocumentRoot="

    driver.get(url)

    search_key = search_key.lower()


    # search_box = driver.find_element_by_css_selector("input#mf_ipt1")
    search_box = driver.find_element_by_xpath('//*[@id="mf_ipt1"]')
    search_box.send_keys(search_key)

    driver.implicitly_wait(1)

    search_btn = driver.find_element_by_css_selector("div#mf_udcSearch_btnSearch")

    search_btn.click()

    # driver.implicitly_wait(10)
    sleep(0.5)

    req = driver.page_source

    soup = BeautifulSoup(req, 'html.parser')  # 가져온 정보를 beautifulsoup로 파싱

    ship_name = None
    IMO_num =None

    for i in range(0, 9):
        try:
            ship_name_temp = soup.select_one(f"#mf_grdCallList_cell_{i}_0 > nobr").text
            IMO_num_temp = soup.select_one(f"#mf_grdCallList_cell_{i}_3 > nobr").text
            if search_key == ship_name_temp.lower():
                ship_name = ship_name_temp
                IMO_num = IMO_num_temp

        except AttributeError:
            break

    print(ship_name)
    
    driver.quit()
    if ship_name is not None:
        return IMO_num
    else:
        return False

IMO 번호를 구하기 위한 크롤링 파일인데 대부분은 전의 게시물에 올린 것과 같은 모습이고, 리턴값과 처음의 설정에서 서 차이가 있다. 또한 크롬 드라이브에서 DevToolsActivePort를 찾을 수 없다는 에러 해결을 위한 설정에 차이가 있다.

    options = webdriver.ChromeOptions()
    options.add_experimental_option("excludeSwitches", ["enable-logging"])

    # 크롬 드라이버 경로 설정, 및 옵션 설정(옵션은 DevToolsActivePort를 찾을 수 없다는 에러 해결을 위해)
    chrome_options = webdriver.ChromeOptions()
    chrome_options.add_argument('--headless')
    chrome_options.add_argument('--no-sandbox')
    chrome_options.add_argument("--single-process")
    chrome_options.add_argument("--disable-dev-shm-usage")
    path = '/home/ubuntu/portwebsite/crawling/chromedriver'
    driver = webdriver.Chrome(path, options=chrome_options)