본문 바로가기
코딩 공부/python

json 파일을 이용해 업데이트 페이지 수 수정하기

by Camel_coding_food 2022. 2. 23.
반응형

저는 C언어 계열을 배우고 파이썬을 배워 좀 수월한 거 같습니다.

이제 곧 이번 장 마지막입니다.

 

처음 실행되면 종목별로 시세 정보를 100페이지,

그 이후에는 1페이지씩 가져오도록 프로그램 해줍니다.

 

이번 코드를 실행하면 프로젝트 파일 내에 config.json이라는 파일이 생길텐데

이 파일으리 pages_to_fetch를 바꿔주면 처음 업데이트 이후의 가져오는 페이지 수를 바꿔줄 수 있습니다.

 

이번에도 코드별 설명은 코드 아래에 첨부하겠습니다.

 

def execute_daily(self):
        self.update_comp_info()
        #전에 만든 매소드를 이용해 기업 목록을 DB에 올린다.

        try:
            with open('config.json','r') as in_file:
            #읽기 모드로 프로젝트 파일 내에 config.json파일을 연다.

                config = json.load(in_file)
                pages_to_fetch = config['pages_to_fetch']
        except FileNotFoundError:
            #아래 코드는 처음 실행하여 config.json파일이 존재하지 않을때 실행된다

            with open ('config.json','w') as out_file:
                pages_to_fetch = 100
                config = {'pages_to_fetch':1}
                json.dump(config, out_file)
        self.update_daily_price(pages_to_fetch)

		#이 아래는 오후 5시마다 업데이트하도록 설정한겁니다.
        tmnow = datetime.now()
        lastday = calendar.monthrange(tmnow.year, tmnow.month)[1]
        #calendar 모듈의 monthrange 매서드를 이용하여 올해와 이번달을 입력하여 마지막일을 구한다.
        
        if tmnow.month == 12 and tmnow.day == lastday:
            tmnext = tmnow.replace(year=tmnow.year+1, month=1, day=1,
                hour=17, minute=0, second=0)
        elif tmnow.day == lastday:
            tmnext = tmnow.replace(month=tmnow.month+1, day=1, hour=17,
                minute=0, second=0)
        else:
            tmnext = tmnow.replace(day=tmnow.day+1, hour=17, minute=0,
                second=0)   
        tmdiff = tmnext - tmnow
        secs = tmdiff.seconds
        t = Timer(secs, self.execute_daily)
        print("Waiting for next update ({}) ... ".format(tmnext.strftime
            ('%Y-%m-%d %H:%M')))
        t.start()

 

여기까지 하셨다면 데이버페이스 업데이트 class는 완성한겁니다.

 

마지막에 결과값을 출력해주는 코드를 포함한 전체 코드를 첨부하겠습니다.

 

import pandas as pd
from bs4 import BeautifulSoup
import pymysql, calendar, time, json
import requests
from datetime import datetime
from threading import Timer
from urllib.request import urlopen


class DBUpdater:
    def __init__(self):
        """생성자: MariaDB 연결 및 종목코드 딕셔너리 생성"""

    
        self.conn = pymysql.connect(host='127.0.0.1', user='root',password='cho2002!', db='investar', charset='utf8')
        with self.conn.cursor() as curs:
            sql ="""
            CREATE TABLE IF NOT EXISTS company_info (
                code VARCHAR(20),
                company VARCHAR(40),
                last_update DATE,
                PRIMARY KEY (code))
            """
            curs.execute(sql)
            sql="""
            CREATE TABLE IF NOT EXISTS daily_price (
                code VARCHAR(20),
                date DATE,
                open BIGINT(20),
                high BIGINT(20),
                low BIGINT(20),
                close BIGINT(20),
                diff BIGINT(20),
                volume BIGINT(20),
                PRIMARY KEY (code, date))
            """
            #BIGINT 는 큰 정수를 범위로 지정해준다. 대신 저장공간 차지 많이한다.
            #NOT NULL은 공백을 허용하지 않는다는 뜻이다.
            #PRIMARY KEY는 괄호 안의 칼럼 내용들이 중복되면 안된다는걸 지정해준다.
            curs.execute(sql)
        self.conn.commit()
        self.codes =dict()
        self.update_comp_info()

    def __del__(self):
        """소멸자: MariaDB 연결 해제"""
        self.conn.close()


    def read_krx_code(self):
        url = 'https://kind.krx.co.kr/corpgeneral/corpList.do?method='\
        'download&searchTpye=13'

        krx = pd.read_html(url, header=0)[0]    #html 파일을 읽어준다.
        krx = krx[['종목코드', '회사명']]   #데이터프레임을 이용하여 원하는 칼럼만 뽑는다.
        krx = krx.rename(columns={'종목코드':'code','회사명':'company'})    #영어 칼럼을 한글 칼럼으로 바꿔준다. 꼭 필요하지는 않은 코드이다.
        krx.code = krx.code.map('{:06d}'.format)    #종목코드형식을 조정해준다.
        return krx


    def update_comp_info(self):
        sql = "SELECT * FROM company_info"      #SQL문법을 이용해 sql 변수에 company_info 테이블을 저장해준다.
        df = pd.read_sql(sql, self.conn)        #판다스 모듈의 read_sql 함수를 이용하여 company_info 테이블을 읽어준다.
        for idx in range(len(df)):
            self.codes[df['code'].values[idx]]=df['company'].values[idx]    #읽어온 테이블을 이용해 codes 딕셔너리를 만든다.
        with self.conn.cursor() as curs:        #with문을 이용해 conn.cursor()를 curs로 객체화 시킨다.
            sql ="SELECT max(last_update) FROM company_info"    #SQL문법을 이용해 last_update 테이블의 가장 최근 업데이트 날짜를 읽도록 할당한다.
            curs.execute(sql)                   #curs로 객체화된 cursor()을 이용하여 sql에 저장된 문장을 excute() 메소드를 이용해 DB에 쿼리문을 보낸다.            
            rs = curs.fetchone()                #DB에서 가장 최근 업데이트 날짜를 가져와 rs변수에 저장한다.
            today = datetime.today().strftime('%Y-%m-%d')   #오늘 날짜를 today 변수에 저장해준다.

        
            if rs[0] == None or rs[0].strftime('%Y-%m-%d') < today: #위에서 추출한 rs가 없거나, 오늘보다 옛날일때 업데이트를 해준다.
                krx = self.read_krx_code()      #krx 상장기업을 읽어와 krx 프레임에 저장한다.
                for idx in range(len(krx)):     
                    code = krx.code.values[idx]
                    company = krx.company.values[idx]
                    sql = f"REPLACE INTO company_info (code, company, last_update) VALUES ('{code}','{company}','{today}')"
                    curs.execute(sql)            #curs로 객체화된 cursor()을 이용하여 sql에 저장된 문장을 excute() 메소드를 이용해 DB에 쿼리문을 보낸다. (업데이트)
                    self.codes[code] = company
                    tmnow = datetime.now().strftime('%Y-%m-%d %H:%M')
                    print(f"[{tmnow}] {idx:04d} REPLACE INTO company_info VALUES ({code},{company},{today})")
                        
                self.conn.commit()
                print('')


    def read_naver(self, code, company, pages_to_fetch):
        """네이버에서 주식 시세를 읽어서 데이터프레임으로 반환"""
        try:
            url = f"http://finance.naver.com/item/sise_day.nhn?code={code}"
            with urlopen(url) as doc: #url 속 데이터를 doc 변수에 저장해준다.
                if doc is None:       #아무것도 url 속에 없다면 none 반환.
                    return None
            html = BeautifulSoup(doc, "lxml")
            pgrr = html.find("td", class_="pgRR")   #html이라고 별명을 지어준 페이지 파싱 뷰티풀 수프를 이용해 페이지의 마지막 수를 구한다.
            if pgrr is None:
                return None
            s = str(pgrr.a["href"]).split('=')
            lastpage = s[-1]        #네이버 금융 시세 마지막 페이지를 구한다.
            df = pd.DataFrame()     
            pages = min(int(lastpage), pages_to_fetch)      #설정 파일 페이지 수와 네이버 금융 페이지를 비교하여 작은걸 선택한다.
            for page in range(1, pages + 1):
                pg_url = '{}&page={}'.format(url, page)
                df = df.append(pd.read_html(pg_url, header=0)[0])   #일별 시세를 데이터프레임에 추가한다.
                tmnow = datetime.now().strftime('%Y-%m-%d %H:%M')   
                print('[{}] {} ({}) : {:04d}/{:04d} pages are downloading...'.
                    format(tmnow, company, code, page, pages), end="\r")
            df = df.rename(columns={'날짜':'date','종가':'close','전일비':'diff'
                ,'시가':'open','고가':'high','저가':'low','거래량':'volume'})   #칼럼명을 영문으로 바꾼다.
            df['date'] = df['date'].replace('.', '-')
            df = df.dropna()
            df[['close', 'diff', 'open', 'high', 'low', 'volume']] = df[['close',
                'diff', 'open', 'high', 'low', 'volume']].astype(int)
            df = df[['date', 'open', 'high', 'low', 'close', 'diff', 'volume']] #데이터프레임을 만든다.
        except Exception as e:
            print('Exception occured :', str(e))
            return None
        return df


    def replace_into_db(self, df, num, code, company):
        """네이버에서 읽어온 주식 시세를 DB에 REPLACE"""
        with self.conn.cursor() as curs:
            for r in df.itertuples():
                sql = f"REPLACE INTO daily_price VALUES ('{code}', "\
                    f"'{r.date}', {r.open}, {r.high}, {r.low}, {r.close}, "\
                    f"{r.diff}, {r.volume})"
                curs.execute(sql)
            self.conn.commit()
            print('[{}] #{:04d} {} ({}) : {} rows > REPLACE INTO daily_'\
                'price [OK]'.format(datetime.now().strftime('%Y-%m-%d'\
                ' %H:%M'), num+1, company, code, len(df)))

    def update_daily_price(self, pages_to_fetch):
        """KRX 상장법인의 주식 시세를 네이버로부터 읽어서 DB에 업데이트"""  
        for idx, code in enumerate(self.codes):
            df = self.read_naver(code, self.codes[code], pages_to_fetch)
            if df is None:
                continue
            self.replace_into_db(df, idx, code, self.codes[code])
            #  

    def execute_daily(self):
        self.update_comp_info()
        #전에 만든 매소드를 이용해 기업 목록을 DB에 올린다.

        try:
            with open('config.json','r') as in_file:
            #읽기 모드로 프로젝트 파일 내에 config.json파일을 연다.

                config = json.load(in_file)
                pages_to_fetch = config['pages_to_fetch']
        except FileNotFoundError:
            #아래 코드는 처음 실행하여 config.json파일이 존재하지 않을때 실행된다

            with open ('config.json','w') as out_file:
                pages_to_fetch = 100
                config = {'pages_to_fetch':1}
                json.dump(config, out_file)
        self.update_daily_price(pages_to_fetch)

        tmnow = datetime.now()
        lastday = calendar.monthrange(tmnow.year, tmnow.month)[1]
        if tmnow.month == 12 and tmnow.day == lastday:
            tmnext = tmnow.replace(year=tmnow.year+1, month=1, day=1,
                hour=17, minute=0, second=0)
        elif tmnow.day == lastday:
            tmnext = tmnow.replace(month=tmnow.month+1, day=1, hour=17,
                minute=0, second=0)
        else:
            tmnext = tmnow.replace(day=tmnow.day+1, hour=17, minute=0,
                second=0)   
        tmdiff = tmnext - tmnow
        secs = tmdiff.seconds
        t = Timer(secs, self.execute_daily)
        print("Waiting for next update ({}) ... ".format(tmnext.strftime
            ('%Y-%m-%d %H:%M')))
        t.start()

if __name__ == '__main__':
    dbu = DBUpdater()
    dbu.execute_daily()

그냥 "데이터베이스에 필요한 정보를 끌어다준다."라고 이해하고

Ctrl C + V해서 사용하셔도 무방합니다.

 

 

반응형

댓글