[Python] 페이징이 있는 동적 페이지 크롤링하기

어쩌다·2022년 9월 15일
0

[Python] 페이징이 있는 동적 페이지 크롤링하기


크롤링을 할 사이트 분석

  • 크롤링을 할 사이트는 인천의 선석 정보가 있는 사이트이다.
  • 테이블 안에 있는 데이터만 필요한데, <table> 태그 중 class가 있으면 bs4의 selector를 통해서 요소를 찾고 파싱을 하면 된다.
  • 하지만 페이징 기능이 있고, 이를 쿼리스트링에 남기지 않는 동적 웹사이트라면 자동화가 필요해진다.

source

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 selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from bs4 import BeautifulSoup
from datetime import datetime, timedelta
from html_table_parser import parser_functions
import no_connection_test


page_tables = []
get_table_text = []
page_no = 2

# 날짜 세팅
now = datetime.now()
print(now)

after = now + timedelta(days=7)
print(after)

# req url
req_url = 'https://scon.icpa.or.kr/vescall/list.do'
query_date = '?searchStartDt={}&searchEndDt={}'.format(
    now.strftime("%Y-%m-%d"), after.strftime("%Y-%m-%d"))

print(req_url + query_date)

# 드라이버 초기화
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))

try:
    driver.get(req_url + query_date)
    element = WebDriverWait(driver, 10)  # request wait 걸기

    while True:
        # 페이지 넘버, 1씩 증가
        page_no = page_no + 1
        page_xpath = '//*[@id="con_body"]/ul/li[{}]/a'.format(page_no)
        print(page_xpath)
        try:
            xpath_test = driver.find_element(
                By.XPATH, page_xpath).click()  # 페이징 xpath를 통해 페이징 자동화

            html = driver.page_source  # page_source 얻기

            soup = BeautifulSoup(html, 'html.parser')  # get html

            table = soup.select_one(".tb_base")
            page_tables.append(table)
        except Exception as e:
            break

    for result in page_tables:
        get_tables = parser_functions.make2d(result)  # table parsing
        get_table_text.append(get_tables)

    # 전체 6, 2차원 : 21
    total = len(get_table_text)  # 6
    inlineTotal = len(get_table_text[0])  # 21

    for i in range(0, total - 2):
        print(i)
        for index, get in enumerate(get_table_text[i], 1):
            # print('{}번째 {}데이터'.format(index, get))

            if get[1] == "한진인천컨테이너터미널":
                get[1] = "HJIT"
            elif get[1] == "선광신컨테이너터미널":
                get[1] = "SNCT"
            elif get[1] == "E1컨테이너터미널":
                get[1] = "E1CT"
            elif get[1] == "인천컨테이너터미널":
                get[1] = "ICT"

            oid = get[3]  # oid
            trminl_code = get[1]  # 터미널코드
            berth_code = get[2]  # 선석코드
            trminl_voyg = get[3]  # 모선-항차
            trminl_shipnm = get[5]  # 선명
            csdhp_prarnde = get[6]  # 접안 (예정) 일시
            carry_fin_day = get[7]  # 반입 마감 시간
            tkoff_prarnde = get[8]  # 출항 (예정) 일시
            wtorcmp_code = get[9]  # 선사
            landng_qy = get[10]  # 양하
            shipng_qy = get[11]  # 적하
            shifting = get[12]  # shift

            # dict로 만들기
            result = {
                "oid": oid,
                "trminlCode": trminl_code,
                "berthCode": berth_code,
                "trminlVoyg": trminl_voyg,
                "trminlShipnm": trminl_shipnm,
                "wtorcmpCode": wtorcmp_code,
                "carryFiniDay": carry_fin_day,
                "csdhpPrarnde": csdhp_prarnde,
                "tkoffPrarnde": tkoff_prarnde,
                "landngQy": landng_qy,
                "shipngQy": shipng_qy,
                "shifting": shifting,
            }

            print(result)

finally:
    driver.quit()

쿼리스트링에서 사용할 날짜 포맷

# 날짜 세팅
now = datetime.now()
print(now)

after = now + timedelta(days=7)
print(after)

# req url
req_url = 'https://scon.icpa.or.kr/vescall/list.do'
query_date = '?searchStartDt={}&searchEndDt={}'.format(
    now.strftime("%Y-%m-%d"), after.strftime("%Y-%m-%d"))

print(req_url + query_date)
  1. python에서는 moment 모듈을 사용하는 것보다 내장되어있는 datetime과 delta를 사용하는 게 좋다.
  2. 1주일 간격을 통해서 시작 날짜와 끝 날짜에 세팅해준다.
  3. 보통 form 태그가 있는 테이블 페이지는 쿼리스트링을 남기기 때문에 개발자 도구의 Network를 살펴보면 좋다.

페이징 처리하기

    while True:
        # 페이지 넘버, 1씩 증가
        page_no = page_no + 1
        page_xpath = '//*[@id="con_body"]/ul/li[{}]/a'.format(page_no)
        print(page_xpath)
        try:
            xpath_test = driver.find_element(
                By.XPATH, page_xpath).click()  # 페이징 xpath를 통해 페이징 자동화

            html = driver.page_source  # page_source 얻기

            soup = BeautifulSoup(html, 'html.parser')  # get html

            table = soup.select_one(".tb_base")
            page_tables.append(table)
        except Exception as e:
            break
  1. 사이트의 하단에 보면 페이지 넘버링이 보인다.
  2. 1, 2, 3... ... 으로 이어지는 <a>위치를 찾아 클릭하는 것을 자동화 시키면 모든 페이지의 데이터를 가져올 수 있을 것이다.
  3. 이를 위해서 xpath가 필요한데, 개발자 도구에서 해당 소스를 선택하여 오른쪽 클릭을 하면 주소가 복사된다.
  4. xpath를 통해 보이는 규칙은 <li>를 알 수 있다는 점이다.
  5. 페이징의 <a><li>의 자식 태그이기 때문에 해당 수를 통해서 페이지의 수를 잡아낼 수 있다고 도출할 수 있다.
  6. 따라서 해당 자리에 1씩 증가시키면 페이지가 넘어가게 될 것이다.
  7. 페이지는 어디까지 있는 지 정해져있지 않기 때문에 while을 통해서 받아낸다.

데이터 수집하기

# 전체 6, 2차원 : 21
    total = len(get_table_text)  # 6
    inlineTotal = len(get_table_text[0])  # 21

    for i in range(0, total - 1):
        print(i)
        for index, get in enumerate(get_table_text[i], 1):
            # print('{}번째 {}데이터'.format(index, get))

            if get[1] == "한진인천컨테이너터미널":
                get[1] = "HJIT"
            elif get[1] == "선광신컨테이너터미널":
                get[1] = "SNCT"
            elif get[1] == "E1컨테이너터미널":
                get[1] = "E1CT"
            elif get[1] == "인천컨테이너터미널":
                get[1] = "ICT"

            oid = get[3]  # oid
            trminl_code = get[1]  # 터미널코드
            berth_code = get[2]  # 선석코드
            trminl_voyg = get[3]  # 모선-항차
            trminl_shipnm = get[5]  # 선명
            csdhp_prarnde = get[6]  # 접안 (예정) 일시
            carry_fin_day = get[7]  # 반입 마감 시간
            tkoff_prarnde = get[8]  # 출항 (예정) 일시
            wtorcmp_code = get[9]  # 선사
            landng_qy = get[10]  # 양하
            shipng_qy = get[11]  # 적하
            shifting = get[12]  # shift

            # dict로 만들기
            result = {
                "oid": oid,
                "trminlCode": trminl_code,
                "berthCode": berth_code,
                "trminlVoyg": trminl_voyg,
                "trminlShipnm": trminl_shipnm,
                "wtorcmpCode": wtorcmp_code,
                "carryFiniDay": carry_fin_day,
                "csdhpPrarnde": csdhp_prarnde,
                "tkoffPrarnde": tkoff_prarnde,
                "landngQy": landng_qy,
                "shipngQy": shipng_qy,
                "shifting": shifting,
            }

            print(result)
  1. 그러면 이렇게 받아온 데이터를 DB에 INSERT하는 작업을 할 것이다.
  2. 페이지의 수만큼 반복[1]하면서 해당 페이지의 데이터를 dict으로 만들고[2] DB에 INSERT[3]해야 하기 때문에 먼저 배열 안에 있는 전체 데이터 수를 구해야 한다.
  3. 이것이 즉 수집을 한 페이지의 수라고 할 수 있겠다.
  4. total - 1을 한 이유는 페이징의 넘버링이 숫자만이 아닌 '이전', '다음'과 같은 버튼까지 포함시키기 때문에 수를 줄였다.
  5. 터미널 코드를 그대로 가져오고 싶었지만, 해당 데이터는 한글로 되어있어서 if-elif를 통해 직접 데이터를 바꾸었다.
  6. 따라서 각각 데이터를 변수로 담아 dict로 만드는 것으로 마무리가 된다.
  7. 이를 node.js 등에 넘겨주면 끝이다.
  8. python은 데이터를 수집, 가공하고 넘겨주는 역할로만 사용했기 때문에 코드는 이렇게 마무리 된다.
profile
혼자 공부하는 공간

0개의 댓글