Side Projects/공모주 알리미

[공모주 알리미 개발] 2-3. 38커뮤니케이션 크롤링 : 종목별 url 추출하기(수정)

hzoooo 2021. 11. 22. 17:19

저번 글 이후, 두 달이 넘어서 다시 공모주 알리미 개발 포스팅을 하게 됐다.

개발은 열심히 해서 약 30명에게 서비스 중이다. 개발할 때 그때그때 글을 썼으면 더 좋았으련만.ㅎㅎ

글 쓰는데 시간이 오래 걸리는 탓도 있고, 게으른것도 조금 있고.. 그래서 어느 정도 구현이 된 상황에서 글을 다시 쓰려고 한다.

 

저번 글 회고와 같이, 확장성 면에서 매우 좋지 않은 코드를 짰기 때문에 다시 작성하였다.

2달 전과 비교해서 전체적으로 리팩토링을 하여서,

클래스 전체 구조를 적기엔 너무 길 것 같아 여러 메서드들을 풀어서 그냥 쓰려고 한다.

 

<공모 청약일정 url 가져오기 : 청약 마감, 청약 시작, 청약 하루 전 순서로 저장>

아래 더보기는 소스코드이다.

 

더보기

 

import requests
from enum import IntEnum
from datetime import datetime
from bs4 import BeautifulSoup

def check_bidding_status(date_diff_bidding_start, date_diff_bidding_finish):
    if date_diff_bidding_finish > 0:
        return BiddingStatus.ALREADY_FINISHED
    elif date_diff_bidding_start == -1:
        return BiddingStatus.START_TOMORROW
    elif date_diff_bidding_start == 0:
        return BiddingStatus.START_TODAY
    elif date_diff_bidding_start >= 1:
        if date_diff_bidding_finish == 0:
            return BiddingStatus.FINISH_TODAY
        else:
            return BiddingStatus.PROCEEDING
    else:
        return BiddingStatus.START_MORE_THAN_TWO_DAY_AFTER


class BiddingStatus(IntEnum):
    FINISH_TODAY = 0
    START_TODAY = 1
    START_TOMORROW = 2
    START_MORE_THAN_TWO_DAY_AFTER = 3
    PROCEEDING = 1
    ALREADY_FINISHED = 5


class BiddingUrlList:
    def __init__(self):
        self.start_tomorrow = []
        self.start_today = []
        self.finish_today = []

    def __getitem__(self, index):
        if index == BiddingStatus.START_TOMORROW:
            return self.start_tomorrow
        elif index == BiddingStatus.START_TODAY:
            return self.start_today
        elif index == BiddingStatus.PROCEEDING:  # REITs Exception(REITS bidding for 3 days)
            return self.start_today
        elif index == BiddingStatus.FINISH_TODAY:
            return self.finish_today

def parsing_html(url):
    response = requests.get(url)
    html = response.content.decode('euc-kr', 'replace')
    return BeautifulSoup(html, 'lxml')

def get_company_tr_list(data_type, soup):
    if data_type == 'bid':
        company_tr_list = soup.find('table', {'summary': '공모주 청약일정'})
    else:
        company_tr_list = soup.find('table', {'summary': '신규상장종목'})
        
    company_tr_list = company_tr_list.find('tbody').find_all('tr')
    return company_tr_list
    
def convert_bidding_td_to_datetime(td, time):
    temp_date_list = td.text.strip().replace(' ', '').split('~')
    year = temp_date_list[0][:4]
    if time == 'start':
        return datetime.strptime(temp_date_list[0], "%Y.%m.%d")
    elif time == 'finish':
        return datetime.strptime(str(year) + '.' + temp_date_list[1], "%Y.%m.%d")

def get_bidding_url_list(target_date: datetime):
    base_bidding_url = 'http://www.38.co.kr/html/fund/index.htm?o=k'
    bidding_url_list = BiddingUrlList()
    soup = parsing_html(base_bidding_url)
    company_tr_list = get_company_tr_list('bid', soup)

    for idx in range(-1, -len(company_tr_list) - 1, -1):
        company_td_list = company_tr_list[idx].find_all('td')
        company_a_tag = company_td_list[0].select('a')
        company_href = company_a_tag[0].get('href')

        bidding_start = convert_bidding_td_to_datetime(company_td_list[1], 'start')
        bidding_finish = convert_bidding_td_to_datetime(company_td_list[1], 'finish')

        date_diff_bidding_start = (target_date - bidding_start).days
        date_diff_bidding_finish = (target_date - bidding_finish).days

        result_url = 'http://www.38.co.kr' + company_href
        bidding_status = check_bidding_status(date_diff_bidding_start, date_diff_bidding_finish)

        if bidding_status == BiddingStatus.ALREADY_FINISHED:
            continue
        elif bidding_status == BiddingStatus.START_MORE_THAN_TWO_DAY_AFTER:
            break
        else:
            bidding_url_list[bidding_status].append(result_url)

    return bidding_url_list

공모 청약일정은, http://www.38.co.kr/html/fund/index.htm?o=k에서 가져올 수 있다.

 

공모주일정 - 공모청약일정, 공모주청약, 공모주분석, 기업공개, 상장, 공모일정, 공모주청약일

  전체종목   청구종목   승인종목   기업IR일정   수요예측일정   수요예측결과   공모청약일정   신규상장   종목명 공모주일정 확정공모가 희망공모가 청약경쟁률 주간사 분석  하나금융

www.38.co.kr

 

가져올 정보는 청약 마감, 청약 시작, 청약 하루 전 종목들이며, target_date를 기준으로 정한다.

 

check_bidding_status 메서드에서,

  -  청약 시작일 까지와의 차(date_diff_bidding_start)

  -  청약 마감일 까지와의 차(date_diff_bidding_finish)

두 가지 parameter를 통해 얻고자 하는 데이터를 선별해낼 수 있다.

(리츠 종목의 경우 청약일이 3일이기 때문에, 이틀 차도 청약 시작 종목에 포함시켰다.)

이미 청약 마감된 종목(ALREADY_FINISHED), 아직 청약 시작까지 이틀 이상 남은 종목(START_MORE_THAN_TWO_DAY_AFTER)들도 혹시 언젠가 사용할 수 있을 것 같아서, 일단은 정의해두었다.

 

사소하지만 페이지 내에서 내가 원하는 조건 내 데이터는 적고, 이미 청약 마감했거나 청약까지 이틀 이상 남은 종목들이 더 많기 때문에 조건문 상단에 위치시켰다.

 

페이지 가장 아래에서부터 크롤링하기 때문에,

  -  이미 청약이 마감된 종목을 만났을 시 넘기며

  -  아직 청약 시작까지 이틀 이상 남은 종목을 만나면, 반복문을 종료한다.

  -  이 두 조건문 사이(else)에 있는 종목들은 청약 마감, 청약 시작, 청약 하루 전 종목들이기 때문에 해당되는 index에 맞게 저장해준다.

 


<신규상장일정 url 가져오기 : 상장 당일, 상장 하루전 순서로 저장>

아래 더보기는 소스코드이다.

 

더보기
import requests
from enum import IntEnum
from datetime import datetime
from bs4 import BeautifulSoup


def check_ipo_status(date_diff_ipo_start):
    if date_diff_ipo_start > 0:
        return IpoStatus.ALREADY_FINISHED
    elif date_diff_ipo_start == 0:
        return IpoStatus.START_TODAY
    elif date_diff_ipo_start == -1:
        return IpoStatus.START_TOMORROW
    else:
        return IpoStatus.START_MORE_THAN_TWO_DAY_AFTER


class IpoStatus(IntEnum):
    START_TODAY = 0
    START_TOMORROW = 1
    ALREADY_FINISHED = 2
    START_MORE_THAN_TWO_DAY_AFTER = 3


class IpoUrlList:
    def __init__(self):
        self.start_tomorrow = []
        self.start_today = []

    def __getitem__(self, index):
        if index == IpoStatus.START_TOMORROW:
            return self.start_tomorrow
        elif index == IpoStatus.START_TODAY:
            return self.start_today

def parsing_html(url):
    response = requests.get(url)
    html = response.content.decode('euc-kr', 'replace')
    return BeautifulSoup(html, 'lxml')

def get_company_tr_list(data_type, soup):
    if data_type == 'bid':
        company_tr_list = soup.find('table', {'summary': '공모주 청약일정'})
    else:
        company_tr_list = soup.find('table', {'summary': '신규상장종목'})
        
    company_tr_list = company_tr_list.find('tbody').find_all('tr')
    return company_tr_list

def convert_ipo_td_to_datetime(td):
    temp_datetime = td.text.strip().replace(' ', '')
    return datetime.strptime(temp_datetime, "%Y/%m/%d")

def get_ipo_url_list(target_date: datetime):
    base_ipo_url = 'http://www.38.co.kr/html/fund/index.htm?o=nw'
    ipo_url_list = IpoUrlList()
    soup = parsing_html(base_ipo_url)
    company_tr_list = get_company_tr_list('ipo', soup)

    for idx in range(-1, -len(company_tr_list) - 1, -1):
        company_td_list = company_tr_list[idx].find_all('td')
        company_a_tag = company_td_list[0].select('a')
        company_href = company_a_tag[0].get('href')

        ipo_start_date = convert_ipo_td_to_datetime(company_td_list[1])

        date_diff_ipo_start = (target_date - ipo_start_date).days

        result_url = 'http://www.38.co.kr' + '/html/fund' + company_href[1:]
        ipo_status = check_ipo_status(date_diff_ipo_start)

        if ipo_status == IpoStatus.ALREADY_FINISHED:
            continue
        elif ipo_status == IpoStatus.START_MORE_THAN_TWO_DAY_AFTER:
            break
        else:
            ipo_url_list[ipo_status].append(result_url)

신규상장일정은, http://www.38.co.kr/html/fund/index.htm?o=nw 에서 가져올 수 있다.

 

공모주일정 - 신규상장, 공모주청약, 공모주분석, 기업공개, 상장, 공모일정, 공모주청약일정,

  전체종목   청구종목   승인종목   기업IR일정   수요예측일정   수요예측결과   공모청약일정   신규상장   기업명 신규상장일 현재가(원) 전일비(%) 공모가(원) 공모가대비등락률(%) 시초

www.38.co.kr

 

가져올 정보는 상장 당일, 상장 하루 전 종목들이며, target_date를 기준으로 정한다.

 

check_bidding_status 메서드에서,

  -  상장일 까지와의 차(date_diff_bidding_start) parameter를 통해 얻고자 하는 데이터를 선별해낼 수 있다.

이미 상장된 종목(ALREADY_FINISHED), 아직 상장까지 이틀 이상 남은 종목(START_MORE_THAN_TWO_DAY_AFTER)들도 혹시 언젠가 사용할 수 있을 것 같아서, 일단은 정의해두었다.

 

마찬가지로,

사소하지만 페이지 내에서 내가 원하는 조건 내 데이터는 적고, 이미 상장했거나 상장까지 이틀 이상 남은 종목들이 더 많기 때문에 조건문 상단에 위치시켰다.

 

페이지 가장 아래에서부터 크롤링하기 때문에,

  -  이미 상장된 종목을 만났을 시 넘기며

  -  아직 상장 시작까지 이틀 이상 남은 종목을 만나면, 반복문을 종료한다.

  -  이 두 조건문 사이(else)에 있는 종목들은 상장 당일, 상장 하루 전 종목들이기 때문에 해당되는 index에 맞게 저장해준다.