[Python] selenium과 requests를 통해 크롤링 하기

어쩌다·2022년 7월 30일
0

[Python] selenium과 requests를 통해 크롤링 하기


크롤링에 필요한 모듈부터 다운로드 받자.

selenium install

pip3 install selenium

selenium official site

requests install

pip3 install requests

requests official site

bs4 install

pip3 install bs4

bs4 official site

html_table_parser install

pip3 install html_table_parser

html_table_parser official site

모듈에 대해서 알기

selenium 모듈이란

Selenium automates browsers. That's it!

셀레니움은 자동화된 브라우저 입니다. 끝!

  1. 보통 테스트 목적으로 웹 애플리케이션을 자동화하지만, 여러가지 목적으로 사용할 수 있다고 한다.
  2. 간단하게 말하자면 특정한 브라우저의 드라이버를 실행시켜 웹 애플리케이션을 자동화하게 해주는 모듈이라는 것이다.
from selenium import webdriver


driver = webdriver.Chrome()

driver.get("http://selenium.dev")

driver.quit()
  1. 공식 문서를 보면 '상호작용'이라는 단어가 있는데, 해당 브라우저를 가지고 자동화를 만들거나 임의로 애플리케이션을 제작하여 말 그대로 클라이언트와 '상호작용' 하는 것을 지원한다는 것이다.
  2. 따라서 해당 드라이버는 selenium의 서버를 통해 브라우저를 구동한다.
  3. 또한 이 웹 드라이버를 로컬에 원격으로 구동이 가능하다는 것이다. 즉, selenium 서버를 원격으로 remote 해줘야 로컬로 사용할 수 있다.(해당 관련 문서)

크롤링에 왜 selenium이 필요하나?

wait 걸기

  1. 민감한 데이터를 가지고 있는 웹사이트는 wait를 거는 곳이 많다. ex) 환율, 물류 등
  2. request를 요청하고 response를 응답하는 순간에 소켓을 wait 걸어서 크롤링을 막는 것이다.
  3. 보통 크롤링을 할 때는 request를 바로 전달하는 순간에 크롤링을 하는데, 이를 방지하는 것이다.
  4. 따라서 request를 할 때도 wait를 걸어주어야 하고, selenium에서 이것이 가능하다.

AJAX 통신

  1. 보통 JS를 사용하여 AJAX(비동기 통신)을 통해 데이터를 동적으로 가져오게 된다.
  2. 이는 CSR(Client Side Rendering)과 이어지게 되는데, 이렇게 되면 앞에서 말한 wait를 거는 것과 똑같고, HTML 소스만으로 크롤링하는 것에 한계가 있기 때문에(버튼을 눌러야만 데이터를 가져오는 등) 더더욱이 '자동화'가 필요한 셈이다.

requests 모듈이란

  1. HTTP request를 자동화하여 코드로 간단하게 컨트롤러를 만들 수 있는 모듈이다.
r = requests.get('https://api.github.com/user', auth=('user', 'pass'))
r.status_code
200
r.headers['content-type']
'application/json; charset=utf8'
r.encoding
'utf-8'
r.text
'{"type":"User"...'
r.json()
{'private_gists': 419, 'total_private_repos': 77, ...}
  1. request 객체는 pooling 패턴으로 사용하고 있어 부하가 적고, 쿼리스트링도 직접 적을 필요도 없는 편의성을 가지고 있다.

bs4 모듈이란

  1. 간단하게 HTML 또는 XML 파일을 파싱하는 모듈이다.
  2. str, list와 같은 타입으로 파싱이 가능하다.
  3. 특정 DOM 등을 querySelector를 사용하여 파싱이 가능하다.
  4. 크롤링의 목적은 해당 웹사이트의 데이터를 가져오는 것이니 필수 요소이다.

html_table_parser 모듈이란

  1. HTML 안에 있는 <table>을 파싱해주는 모듈이다.
  2. 테이블을 통해서 데이터가 이루어져있을 때, 크롤링 방지를 위해서 id 속성을 넣지 않거나 주석을 섞는 등으로 방지하는데, 이를 손쉽게 파싱해주는 모듈이다.

Example

  1. 실제로 코드를 작성하여 울산의 선석 정보를 크롤링하는 코드를 예시로 들었다.

데이터 크롤링

from selenium import webdriver
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from bs4 import BeautifulSoup
from html_table_parser import parser_functions
import no_connection_test

# 드라이버 초기화
driver = webdriver.Chrome("/opt/homebrew/bin/chromedriver")

try:
    driver.get("http://www.unct.co.kr/eservice/#/ES010_100")
    element = WebDriverWait(driver, 10) # request wait 걸기
    html = driver.page_source # page_source 얻기
    soup = BeautifulSoup(html, 'html.parser') # get html

    table = soup.select_one('.k-grid-table') # table select

    get_table_text = parser_functions.make2d(table) # table parsing
    print(get_table_text)

    for index, get in enumerate(get_table_text, 1):
        print('{}번째 {}데이터'.format(index, get))

        berth_code = ''.join(get[:1]) # 선석코드
        trminl_voyg = ''.join(get[1:2]) # 모선-항차
        trminl_shipnm = ''.join(get[2:3]) # 선명
        wtorcmp_code = ''.join(get[3:4]) # 선사
        # sunsuck = ''.join(get[:5]) # 입항 항차
        # sunsuck = ''.join(get[:6]) # 출항 항차
        csdhpDrc = ''.join(get[6:7]) # 접안 위치
        trminlShipnm = ''.join(get[7:8]) # 선대명
        tkin_closde = ''.join(get[8:9]) # 반입 마감 시간
        csdhp_prarnde = ''.join(get[9:10]) # 접안 (예정) 일시
        tkoff_prarnde = ''.join(get[10:11]) # 출항 (예정) 일시
        landng_qy = ''.join(get[11:12]) # 양하
        shipng_qy = ''.join(get[12:13]) # 적하
        reshmt_qy = ''.join(get[13:14]) # 이적
        # sunsuck = ''.join(get[:15]) # 상태
        
        # dict로 만들기
        result = {
            "berthCode":berth_code,
            "trminlVoyg":trminl_voyg,
            "trminlShipnm":trminl_shipnm,
            "wtorcmpCode":wtorcmp_code,
            "csdhpDrc":csdhpDrc,
            "trminlShipnm":trminlShipnm,
            "tkinClosde":tkin_closde,
            "csdhpPrarnde":csdhp_prarnde,
            "tkoffPrarnde":tkoff_prarnde,
            "landngQy":landng_qy,
            "shipngQy":shipng_qy,
            "reshmtQy":reshmt_qy,
        }

        no_connection_test.post(result)

finally:
    driver.quit()
  1. Python 모듈 중에 있는 pymysql을 사용하여 INSERT를 할까 했지만, node.js의 시퀄라이즈를 사용하여 create를 하는 게 효율적이라 판단했다.
  2. Python으로 API를 만드는 것은 그닥... 개인적으로 선호하지 않는다. 간단한 것을 가지고 복잡하게 욱여넣는 느낌인데 각 장단점이 있는 건 맞다.
  3. for문에서 enumerate 함수를 통해서 Index와 원소를 얻을 수 있어 수월했다.
  4. Python의 dict 자체가 JSON을 통한 통신이 효과적이기 때문에 더욱 mysql과 node.js와 연결하기 좋은 것 같다.
  5. (사실 원래 이렇게 더럽게 코드 짜면 안 되는데...)

node.js에 데이터를 POST하기

Python

import requests

# requests 기본 데이터
post_url = 'http://localhost:'
headers = {
    'Content-Type': 'application/json; charset=utf-8'
}


# post
def post(result):
    respone = requests.post(post_url, json=result, headers=headers)
    respone.close()

JS

class BerthStatPyController {
  create = async (req, res, next) => {
    try {

      const data = req.body;
      console.log(data);

      const result = berthStat.create(data);

      res.send(result);
    } catch (error) {
      next(error);
    } finally {
    }
  };
}

Example

AJAX 소스를 직접 찾아서 크롤링하기

import no_connection_test
import moment
import requests

# 울산

try:
    # 요청 url
    # 1달 주기로 검색
    req_url = 'http://www.unct.co.kr/json/comm/commonSelect.do?sqlId=es010_100Qry.selectBerthScheduleList&from={}&to={}'.format(
        moment.date('1 month ago').format('YYYYMMDD'), moment.now().format('YYYYMMDD'))
    # print(req_url)

    response = requests.get(req_url)
    # print(response)

    # JSON으로 파싱
    result = response.json()

    # body 데이터
    body = result['queryResult']
    # print(result['queryResult'][0])

    # 데이터 확인
    for index, result in enumerate(body, 1):
        print('{}번째 데이터 : {}'.format(index, result))

        data = {
            'trminlCode': 'UNCT',  # 터미널코드
            'berthCode': result['position'],  # 선석
            'trminlVoyg': result['aa'],  # 모선항차
            'trminlShipnm': result['cdvVslName'],  # 선명
            'wtorcmpCode': result['cdvVslOperator'],  # 선사
            'csdhpDrc': result['vsbVoyStartpos'],  # 접안위치
            'trminlShipnm': result['vsbVoyOutservice'],  # 선대명
            'tkinClosde': result['cct'],  # 반입 마감 시간
            'csdhpPrarnde': result['etb'],  # 접안 예정 일시
            'tkoffPrarnde': result['etd'],  # 출항 예정 일시
            'landngQy': result['vsbVoyDisvan'],  # 양하
            'shipngQy': result['vsbVoyLoadvan'],  # 적하
            'reshmtQy': result['vsbVoyTsvan'],  # 이적
        }

        print(data)

        no_connection_test.post(data)


finally:
    response.close()
    # driver.quit()
  1. 사실 이렇게 되는 게 가장 이상적이다.
  2. 개발자 도구를 통해서 Network를 통해 AJAX를 한 JSON 데이터를 찾을 수 있다.
  3. 쿼리스트링으로 할 수 있었던 것은 form 태그가 있었기 때문인데, 대부분은 찾아보면 form 태그로 되어있으니 이렇게 할 수도 있다.
profile
혼자 공부하는 공간

0개의 댓글