이번 포스팅은 이전 포스팅에서 공모주 알리미로 뿌려줄(청약 전일, 당일, 마감일 / 상장 전일, 당일) 정보들이 담긴 url들을 이용하여,
공모주 투자에 도움이 되는 정보들을 가져오면 된다.
[공모주 알리미 개발] 2-3. 38커뮤니케이션 크롤링 : 종목별 url 추출하기(수정)
저번 글 이후, 두 달이 넘어서 다시 공모주 알리미 개발 포스팅을 하게 됐다. 개발은 열심히 해서 약 30명에게 서비스 중이다. 개발할 때 그때그때 글을 썼으면 더 좋았으련만.ㅎㅎ 글 쓰는데 시
hzoo.tistory.com
ipostock에서 1차적으로 정보를 가져오지만 ipostock에서 가끔 늦게 업데이트되는 이유로 38 커뮤니케이션을 크롤링하기 때문에,
전체 정보가 아닌, 필요한 정보만 가져오면 된다.
청약 일정(특히 상장일), 확정 공모가, 기업 수요예측결과(기관 경쟁률, 의무보유확약 비율)가 늦게 나오기 때문에,
double check를 위한,
- 종목명
- 일정 관련 정보(공모 청약 시작, 마감, 환불일, 상장일)
- 금액 관련 정보(공모가 밴드 하단, 상단, 확정 공모가, 공모 규모)
- 신주 모집 주식수 및 비율
- 수요예측 결과 정보(기관 경쟁률, 의무보유확약 비율)
를 크롤링 하려고 한다.
오늘을 예로 들면, 대신밸런스스팩11호, 미래에셋글로벌리츠, 신한서부티엔디리츠 세개의 url을 청약 정보로 가져온다.
위에 표기해둔 정보는 한 url에 모두 담겨있으므로(ipostock과 다르게), 먼저 html 코드를 가져온다.
def parsing_html(url):
response = requests.get(url)
html = response.content.decode('euc-kr', 'replace')
return BeautifulSoup(html, 'lxml')
이후, 차례대로 정보들을 가져오면 된다.
<1. 종목명 가져오기>

def crawl_company_name(url):
soup = parsing_html(url)
table = soup.find('table', {'summary': '기업개요'})
tr = table.find_all('td')[1]
company_name = tr.text.strip()
return company_name
<2. 일정 관련 정보 가져오기>

def crawl_ipo_date(url):
ipo_date = IpoDate()
soup = parsing_html(url)
table = soup.find('table', {'summary': '공모청약일정'})
tr = table.find_all('tr')[1:6]
del date_tr_list[1:3]
date_td_list = [tr.find_all('td')[1].text.replace('\xa0', '').replace(' ', '') for tr in date_tr_list]
ipo_date.bidding_start, ipo_date.bidding_finish = date_td_list[0].split('~')
ipo_date.refund = date_td_list[1]
ipo_date.go_public = date_td_list[2]
return ipo_date
<3. 금액 관련 정보 가져오기>

def crawl_ipo_price(url):
ipo_price = IpoPrice()
soup = parsing_html(url)
table = soup.find('table', {'summary': '공모정보'})
price_tr_list = table.find_all('tr')[2:4]
band_price_range_td = price_tr_list[0].find_all('td')[1].text
band_price_range = band_price_range_td.replace('\xa0', '').replace(' ', '').replace('원', '').replace(',', '')
band_price_low, band_price_high = band_price_range.split('~')
offering_price_td = price_tr_list[1].find_all('td')[1].text.replace('-', '0')
offering_price = offering_price_td.replace('\xa0', '').replace(' ', '').replace('원', '').replace(',', '')
offering_amount_td = price_tr_list[1].find_all('td')[3].text
offering_amount = offering_amount_td.replace('\xa0', '').replace(' ', '').replace('(백만원)', '').replace(',', '')
ipo_price.band_price_low = band_price_low
ipo_price.band_price_high = band_price_high
ipo_price.offering_price = offering_price
ipo_price.offering_amount = str(int(offering_amount) // 100)
return ipo_price
<4. 신주 모집 주식수 및 비율 가져오기>

def crawl_ipo_new_stocks_info(url):
ipo_new_stocks_info = IpoNewStocksInfo()
soup = parsing_html(url)
table = soup.find('table', {'summary': '공모정보'})
new_stock_info_tr_list = table.find_all('tr')[:2]
total_num_of_new_stocks_td = new_stock_info_tr_list[0].find_all('td')[1].text
total_num_of_new_stocks = total_num_of_new_stocks_td.replace('주', '').replace(' ', '').replace(',', '').strip()
temp_td = new_stock_info_tr_list[1].find_all('td')[1].text.split('/')[0]
ratio_of_new_stocks = temp_td.split('(')[1]
ratio_of_new_stocks = ratio_of_new_stocks.replace(' ', '').replace('%)', '').strip()
ipo_new_stocks_info.total_num_of_new_stocks = total_num_of_new_stocks
ipo_new_stocks_info.ratio_of_new_stocks = ratio_of_new_stocks
return ipo_new_stocks_info
<5. 수요예측 결과 정보 가져오기>

def crawl_ipo_demand_forecast(url):
ipo_demand_forecast = IpoDemandForecast()
soup = parsing_html(url)
table = soup.find('table', {'summary': '공모청약일정'})
demand_forecast_rows_tr = table.select('table > tr')[19].find_all('td')[1::2]
competition_ratio = demand_forecast_rows_tr[0].text.replace(':1', '').strip()
commitment_ratio = demand_forecast_rows_tr[1].text.replace('0.00%', '').replace('%', '').strip()
ipo_demand_forecast.competition_ratio = competition_ratio if any(competition_ratio) else 0
ipo_demand_forecast.commitment_ratio = commitment_ratio if any(commitment_ratio) else 0
return ipo_demand_forecast
위 소스 코드들에 여러 클래스들을 정의해뒀는데, 클래스 소스코드는 바로 아래줄 더보기에 첨부하였다.
class IpoDate:
def __init__(self):
self.bidding_start = None
self.bidding_finish = None
self.refund = None
self.go_public = None
class IpoPrice:
def __init__(self):
self.band_price_low = None
self.band_price_high = None
self.offering_price = None
self.offering_amount = None
class IpoNewStocksInfo:
def __init__(self):
self.total_num_of_new_stocks = None
self.ratio_of_new_stocks = None
class IpoStockConditions:
def __init__(self):
self.total_num_of_stock_after_ipo = None
self.num_of_stock_lockup = None
self.num_of_stock_sale_available = None
self.ratio_of_lockup = None
self.ratio_of_sale_available = None
class IpoUnderwriter:
def __init__(self):
self.underwriter = None
self.num_of_stock_allocated = None
class IpoDemandForecast:
def __init__(self):
self.competition_ratio = None
self.commitment_ratio = None
class IpoData(IpoDate, IpoPrice, IpoNewStocksInfo, IpoStockConditions, IpoUnderwriter, IpoDemandForecast):
def __init__(self, company_name):
self.company_name = company_name
self.public_offering_page_url = None
self.stock_holder_page_url = None
self.demand_forecast_page_url = None
self.ref_url_ipo_stock = None
self.ref_url_38com = None
self.is_from_KONEX = False
IpoDate.__init__(self)
IpoPrice.__init__(self)
IpoNewStocksInfo.__init__(self)
IpoStockConditions.__init__(self)
IpoUnderwriter.__init__(self)
IpoDemandForecast.__init__(self)
def set_public_offering_page_url(self, url):
self.public_offering_page_url = url
self.stock_holder_page_url = self.public_offering_page_url.replace('_04', f'_02')
self.demand_forecast_page_url = self.public_offering_page_url.replace('_04', f'_05')
def set_ipo_date(self, ipo_date: IpoDate):
self.bidding_start = ipo_date.bidding_start
self.bidding_finish = ipo_date.bidding_finish
self.refund = ipo_date.refund
self.go_public = ipo_date.go_public
def set_ipo_price(self, ipo_price: IpoPrice):
self.band_price_low = ipo_price.band_price_low
self.band_price_high = ipo_price.band_price_high
self.offering_price = ipo_price.offering_price
self.offering_amount = ipo_price.offering_amount
def set_ipo_new_stocks_info(self, ipo_new_stocks_info: IpoNewStocksInfo):
self.total_num_of_new_stocks = ipo_new_stocks_info.total_num_of_new_stocks
self.ratio_of_new_stocks = ipo_new_stocks_info.ratio_of_new_stocks
def set_ipo_stock_conditions(self, ipo_stock_conditions: IpoStockConditions):
self.total_num_of_stock_after_ipo = ipo_stock_conditions.total_num_of_stock_after_ipo
self.num_of_stock_lockup = ipo_stock_conditions.num_of_stock_lockup
self.num_of_stock_sale_available = ipo_stock_conditions.num_of_stock_sale_available
self.ratio_of_lockup = ipo_stock_conditions.ratio_of_lockup
self.ratio_of_sale_available = ipo_stock_conditions.ratio_of_sale_available
def set_ipo_underwriter(self, ipo_underwriter: IpoUnderwriter):
self.underwriter = ipo_underwriter.underwriter
self.num_of_stock_allocated = ipo_underwriter.num_of_stock_allocated
def set_ipo_demand_forecast(self, ipo_demand_forecast: IpoDemandForecast):
self.competition_ratio = ipo_demand_forecast.competition_ratio
self.commitment_ratio = ipo_demand_forecast.commitment_ratio
def get_ipo_price(self):
ipo_price = IpoPrice()
ipo_price.band_price_low = self.band_price_low
ipo_price.band_price_high = self.band_price_high
ipo_price.offering_price = self.offering_price
ipo_price.offering_amount = self.offering_amount
return ipo_price
def get_ipo_date(self):
ipo_date = IpoDate()
ipo_date.bidding_start = self.bidding_start
ipo_date.bidding_finish = self.bidding_finish
ipo_date.refund = self.refund
ipo_date.go_public = self.go_public
return ipo_date
def get_ipo_new_stocks_info(self):
ipo_new_stocks_info = IpoNewStocksInfo()
ipo_new_stocks_info.total_num_of_new_stocks = self.total_num_of_new_stocks
ipo_new_stocks_info.ratio_of_new_stocks = self.ratio_of_new_stocks
return ipo_new_stocks_info
def get_ipo_stock_conditions(self):
ipo_stock_conditions = IpoStockConditions()
ipo_stock_conditions.total_num_of_stock_after_ipo = self.total_num_of_stock_after_ipo
ipo_stock_conditions.num_of_stock_lockup = self.num_of_stock_lockup
ipo_stock_conditions.num_of_stock_sale_available = self.num_of_stock_sale_available
ipo_stock_conditions.ratio_of_lockup = self.ratio_of_lockup
ipo_stock_conditions.ratio_of_sale_available = self.ratio_of_sale_available
return ipo_stock_conditions
def get_ipo_underwriter(self):
ipo_underwriter = IpoUnderwriter()
ipo_underwriter.underwriter = self.underwriter
ipo_underwriter.num_of_stock_allocated = self.num_of_stock_allocated
return ipo_underwriter
def get_ipo_demand_forecast(self):
ipo_demand_forecast = IpoDemandForecast()
ipo_demand_forecast.competition_ratio = self.competition_ratio
ipo_demand_forecast.commitment_ratio = self.commitment_ratio
return ipo_demand_forecast
이렇게 실제로 코드를 다중상속해서 써본적이 없어서, 사실 맞는 코드인가 싶기도 하다..
IpoData는 IpoDate, IpoNewStocksInfo, IpoPrice, IpoStockConditions, IpoUnderwriter, IpoDemandForecast를 포함하고 있으며,
아래 블로그를 참고하여 내 IpoData 클래스는 집합 관계>집약 관계임을 알게 되었다.
https://gmlwjd9405.github.io/2018/07/04/class-diagram.html
[UML] 클래스 다이어그램 작성법 - Heee's Development Blog
Step by step goes a long way.
gmlwjd9405.github.io
굳이 private으로 짜야될 메서드가 없을 것 같아서, 편리함을 위해 public으로 모두 구현했다.
현업으로 가면 절대 안 그렇겠지만..
'Side Projects > 공모주 알리미' 카테고리의 다른 글
| [공모주 알리미 개발] 2-3. 38커뮤니케이션 크롤링 : 종목별 url 추출하기(수정) (0) | 2021.11.22 |
|---|---|
| [공모주 알리미 개발] 2-3. 38커뮤니케이션 크롤링 : 종목별 url 추출하기 (0) | 2021.09.09 |
| [공모주 알리미 개발] 2-2. ipostock 크롤링 : 주주구성, 공모정보, 수요예측 정보 가져오기 (0) | 2021.09.06 |
| [공모주 알리미 개발] 2-1. ipostock 크롤링 : 종목별 url 추출하기 (0) | 2021.08.30 |
| [공모주 알리미 개발] 1. 개요 (3) | 2021.08.26 |