모두를 위한 웹 스크래핑(1/5)-사례

주식 일 데이터 가져오기

  • 상황
    • 주식 투자자 김족박 씨는 종목 발굴에 관심이 많습니다. 매일매일 전체 종목의 시가/저가/고가/종가/등락률을 확인하고 내일의 투자전략을 수립하고 싶어합니다.
    • 시장의 흐름(실제)이 본인의 투자 모델(예측)과 얼마나 벌어져 있는지 체크하고 이를 본인의 투자 모형에 반영하기 위해서 입니다.
    • 하지만 매일매일 770개 가까이 되는 KOSPI 종목의 가격을 확인하는 것은 꽤나 번거로운 일입니다.
  • 해결
    • 지인 중 국내 최고의 금융 IT 서비스 기업을 지향하는 IBK시스템 직원인 이태리 대리에게 이런게 가능할지 물어봤습니다.
    • 가능합니다! 역시 유능한 IBK시스템 직원입니다. 김족박씨와 이태리 씨는 함께 주식투자 모델을 만들어 보기로 결정했습니다.

1. 데이터를 어디서 가져오지?

  • 일단 간단하게 네x버에서 가져올 수 있을 것 같네요.
  • 일단 데이터를 주워오기 위한 방법을 찾아내기 위해서 브라우저의 개발자 도구를 활용해서 요청/응답 규칙을 찾아냅니다.
  • 이 주소를 사용할 수 있어 보입니다. http://finance.naver.com/item/sise.nhn?code=053800
  • 응답 데이터 중 아래 부분을 사용하면 될것 같습니다.
<dl class="blind">
    <dt>종목 시세 정보</dt>
    <dd>2017년 05월 04일 16시 10분 기준 장마감</dd>
    <dd>종목명 안랩</dd>
    <dd>종목코드 053800 코스닥</dd>
    <dd>현재가 59,900 전일대비 하락 300 마이너스 0.50 퍼센트</dd>
    <dd>전일가 60,200</dd>
    <dd>시가 59,400</dd>
    <dd>고가 61,800</dd>
    <dd>상한가 78,200</dd>
    <dd>저가 58,400</dd>
    <dd>하한가 42,200</dd>
    <dd>거래량 924,476</dd>
    <dd>거래대금 55,683백만</dd>
</dl>

2. 데이터를 어떻게 가져오지?

  • 웹 스크래핑에 많이 활용되는 파이썬 프로그래밍 언어를 사용해 보려고 합니다.
  • 조금 찾아보니 풍부한 라이브러리로 간단하게 개발이 가능할 것 같습니다.
  • 여러가지 레퍼런스도 아주 많아 보입니다. 좋습니다.
  • HTTP 요청은 requests를, 응답으로 들어온 HTML 파싱은 beautifulsoup4 라이브러리를 활용하면 쉽게 될것 같습니다.
  • 종목 데이터
  • 종목 데이터는 KRX에서 다운로드 받은 엑셀 파일을 읽어옵니다.
  • !주의! 아래에 프로그램 코드가 나옵니다. 놀라지 마세요.
from bs4 import BeautifulSoup
import codecs

f = codecs.open('상장법인목록.xls', encoding='euc-kr')
lines = f.readlines()

table = BeautifulSoup("".join(lines), 'lxml').find('table')
  • 확인해보니 엑셀 파일 안에는 HTML 형식의 데이터가 들어 있었습니다. 우리가 필요로하는 ‘종목코드’ 정보 부분은 ‘table’ 태그 부분에 있었습니다.
  • 엑셀 파일의 인코딩이 EUC-KR로 되어 있어서 읽어오는데 번거로웠습니다. 아 제발 UTF-8을 사용합시다.
  • 시세 데이터
  • 네x버에서 조회해옵니다.
import requests
from bs4 import beautifulsoup

def get_today_price_data(code):
sample_url = "http://finance.naver.com/item/sise.nhn?code={}".format(code)

r = requests.post(sample_url)
code = r.text
soup = BeautifulSoup(code, 'lxml')
dls = soup.findAll("dl", { "class" : "blind" })

dds = dls[0].find_all('dd')

print(dds[1] # 종목명
, dds[3] # 현재가
, dds[4] # 전일종가
, dds[5] # 시가
, dds[6] # 고가
, dds[8] # 저가
, dds[10] # 거래량
, dds[11])
return dds[1].text, dds[3].text, dds[4].text, dds[5].text, dds[6].text, dds[8].text, dds[10].text, dds[11].text

 

  • 시험해보기
price_data = get_today_price_data('053800')
print(price_data)
  • 결과
    (‘종목명 안랩’, ‘현재가 59,900 전일대비 하락 300 마이너스 0.50 퍼센트’, ‘전일가 60,200’, ‘시가 59,400’, ‘고가 61,800’, ‘저가 58,400’, ‘거래량 924,476’, ‘거래대금 55,683백만’)
  • 데이터가 그리 깔끔하지 않습니다. 제목과 내용이 함께 들어가 있어서 조금 더 처리가 필요해 보입니다.
  • Web API 방식으로 필요로하는 데이터만 가져올 수 있다면 가장 편하겠지만..필요한건 돈을 주고 사거나 스스로 처리해야겠죠.
  • 제목과 내용이 붙어있는 데이터를 나눠서 읽을 수 있도록 함수를 하나 만들어 봅니다.
def parse_price_data(price_data_raw):
prices = list(map(lambda x: x.split(' ')[1].replace(',', '').replace('백만', '000'), price_data))
return {'price': prices[1],
'yesterday_price': prices[2],
'start_price': prices[3],
'high_price': prices[4],
'low_price': prices[5],
'volume': prices[6],
'turnover': prices[7]}

 

  • 이제 부품들이 모두 준비된 것 같습니다.

3. 데이터를 어디에 저장할까?

  • 일단 종목 마스터(종목코드, 종목명 등) 간단한 데이터는 경량 데이터베이스인 SQLite 에 저장합니다. 상대적으로 변경이 자주 발생하지 않기 때문입니다.
    • 테이블을 만듭니다.
    import sqlite3
    conn = sqlite3.connect('stock_codes.db')
    
    c = conn.cursor()
    
    # Drop table
    c.execute('''DROP TABLE stocks''')
    
    # Create table
    c.execute('''CREATE TABLE stocks
                 (name text, codes text, ipo_date date)''')
  • 종목 데이터 읽은 것을 저장합니다.
    rows = table.find_all('tr')
    for row in rows:
        cols = row.find_all('td')
        if len(cols) > 4:
            c.execute("INSERT INTO stocks VALUES (?, ?, ?)", [cols[0].text, cols[1].text, cols[4].text])
    
    conn.commit()
    conn.close()
  • 시세 데이터는 RDB에 저장합니다.
    • MySQL DB에 접속하고 테이블을 만듭니다.
    import mysql.connector
    from sqlalchemy import create_engine
    
    pwd = getpass("DB Password: ")
    
    cnx_str = 'mysql+mysqlconnector://samsee:'+pwd+'@xxx.xxx.xxx.xxx/scrap4'
    engine = create_engine(cnx_str, echo=False)
    con = engine.connect()
    sql = '''
    CREATE TABLE `scrap4`.`STOCK_PRICE` (
      `DATE` DATE NOT NULL,
      `STOCK_CD` VARCHAR(6) NOT NULL,
      `PRICE` DECIMAL(9) NULL,
      `YESTERDAY_PRICE` DECIMAL(9) NULL,
      `START_PRICE` DECIMAL(9) NULL,
      `HIGH_PRICE` DECIMAL(9) NULL,
      `LOW_PRICE` DECIMAL(9) NULL,
      `VOLUME` DECIMAL(9) NULL,
      `TURNOVER` DECIMAL(12) NULL,
      PRIMARY KEY (`DATE`, `STOCK_CD`))
    '''
    con.execute(sql)
  • 수집된 데이터 전체를 insert 합니다.
    from datetime import datetime
    
    today = datetime.today()
    ins_sql = '''
    insert into STOCK_PRICE (`date`, `stock_cd`, `price`, `yesterday_price`, `start_price`, `high_price`, `low_price`, `volume`, `turnover`) values (%s,%s,%s,%s,%s,%s,%s,%s,%s)
    '''
    
    values = (today.strftime('%Y-%m-%d'),
              code, 
              price_data['price'], 
              price_data['yesterday_price'], 
              price_data['start_price'], 
              price_data['high_price'], 
              price_data['low_price'], 
              price_data['volume'], 
              price_data['turnover'])
    
    con.execute(ins_sql, values)
  • 이제 모두 모아서 돌려봅니다.
  conn = sqlite3.connect('stock_codes.db')
  cnx_str = 'mysql+mysqlconnector://samsee:'+pwd+'@xxx.xxx.xxx.xxx/scrap4'
  engine = create_engine(cnx_str, echo=False)
  con = engine.connect()
  
  read_sql = "SELECT * FROM stocks"
  df_stocks = pd.read_sql(read_sql, conn)
  today = '2017-05-08'
  for index, row in df_stocks.iterrows():
  #     print(row['codes'])
      price_in_raw = get_today_price_data(row['codes'])
      price_data = parse_price_data(price_in_raw)
      
      ins_price(row['codes'], today, price_data)
  
  con.close()
  conn.close()

4. 데이터 분석하기

  • 이제 모든 데이터가 준비되었습니다. 몇 가지 방식으로 분석을 해봅시다.
  • 데이터 분석에는 pandas 라이브러리와 matplotlib 시각화가 도움이 됩니다.
  • 데이터 읽어오기
  import pandas as pd
  import matplotlib
  
  cnx_str = 'mysql+mysqlconnector://samsee:'+pwd+'@xxx.xxx.xxx.xxx/scrap4'
  engine = create_engine(cnx_str, echo=False)
  con = engine.connect()
  sql = 'SELECT * FROM scrap4.STOCK_PRICE WHERE DATE = "2017-05-08"'
  df = pd.read_sql(sql, con)
  • 상승률/하락률 분포
  df['change'] = (df['PRICE'] - df['START_PRICE']) / df['START_PRICE'] * 100
  df.replace([np.inf, -np.inf], 0)['change'].hist(bins=100)

> 결과

정리

  • 웹 스크래핑을 통해 데이터를 가져오고 처리한 뒤 분석하는 전체 과정을 살펴보았습니다.
  • 파이썬 언어와 각종 라이브러리를 활용하여 적은 코딩으로 원하는 프로그램을 만들 수 있었습니다.
  • 다음으로는 웹 스크래핑을 위한 파이썬 개발환경 설정을 알아보겠습니다.

Tips

  • 크롤링(Crawling)과 스크래핑(Scraping)의 차이
    • 양쪽 모두 웹 사이트의 정보를 자동으로 가져오는 방법입니다.
    • 스크래핑은 특정 목적의 정보를 가져오기 위한 방법이라고 볼 수 있습니다. 보통 하나의 URL에 대해서 반복적인 요청을 하고 거기서 나온 데이터를 정규화된 데이터베이스에 저장하는 형태로 이루어집니다.
    • 반면 크롤링은 사이트에 어떤 정보가 있는지 찾고 인덱싱을 하는 것을 목적으로 합니다. 구글이나 기타 다른 검색 엔진에서 크롤러라고 불리우는 소프트웨어 로봇이 인터넷을 돌아다니며 해당 페이지에 어떤 정보가 있는지 확인하고 저장합니다.
    • 보통 우리가 하는 것은 크롤링 보다는 스크래핑에 가깝다고 볼 수 있을 것입니다.

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다