아미(아름다운미소)

재무재표개선 본문

랭귀지/python

재무재표개선

유키공 2025. 8. 6. 22:15
import pandas as pd
import requests
from typing import Optional, Dict, Any
import warnings
warnings.filterwarnings('ignore')

class FinancialAnalyzer:
    """네이버 금융 재무제표 분석기"""
    
    def __init__(self):
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
        }
    
    def get_financial_statement(self, ticker: str) -> Optional[pd.DataFrame]:
        """
        네이버 금융에서 손익계산서 데이터를 가져오는 함수
        
        Args:
            ticker (str): 종목 코드 (예: '005930')
            
        Returns:
            pd.DataFrame or None: 재무제표 데이터
        """
        url = f'https://finance.naver.com/item/main.naver?code={ticker}'
        
        try:
            # requests로 먼저 페이지 확인
            response = requests.get(url, headers=self.headers, timeout=10)
            if response.status_code != 200:
                print(f"⚠️ 페이지 접근 실패: HTTP {response.status_code}")
                return None
                
            # 한글 인코딩 처리 개선
            tables = pd.read_html(url, encoding='euc-kr', header=0)
            
        except requests.exceptions.RequestException as e:
            print(f"⚠️ 네트워크 오류: {e}")
            return None
        except Exception as e:
            print(f"⚠️ 데이터 파싱 오류: {e}")
            return None

        # 손익계산서 테이블 찾기
        for i, table in enumerate(tables):
            if table.shape[1] >= 3 and len(table) > 5:
                # 매출액이 포함된 테이블 찾기
                first_col = table.iloc[:, 0].astype(str).str.strip()
                if any('매출' in cell for cell in first_col):
                    print(f"✅ 재무제표 발견 (테이블 #{i+1})")
                    return table
                    
        print("⚠️ 손익계산서 테이블을 찾을 수 없습니다")
        return None

    def clean_financial_df(self, df: pd.DataFrame) -> pd.DataFrame:
        """
        데이터프레임 전처리: 인덱싱, 숫자 변환, 결측치 처리 등
        
        Args:
            df (pd.DataFrame): 원본 재무제표 데이터
            
        Returns:
            pd.DataFrame: 정제된 데이터
        """
        df_copy = df.copy()
        
        # 첫 번째 컬럼을 인덱스로 설정
        df_copy.set_index(df_copy.columns[0], inplace=True)
        
        # 결측치 및 특수 문자 처리
        df_copy = df_copy.replace(['-', '/', 'N/A', '', ' '], '0')
        
        # 숫자 변환 함수
        def convert_to_number(x):
            if pd.isna(x) or x == '':
                return 0
            try:
                # 문자열인 경우 콤마 제거 후 숫자 변환
                if isinstance(x, str):
                    cleaned = x.replace(',', '').replace('(', '-').replace(')', '').strip()
                    return float(cleaned) if cleaned else 0
                return float(x)
            except (ValueError, TypeError):
                return 0
        
        # 모든 컬럼에 숫자 변환 적용
        for col in df_copy.columns:
            df_copy[col] = df_copy[col].apply(convert_to_number)
            
        return df_copy

    def find_row_name(self, df: pd.DataFrame, candidates: list) -> str:
        """
        유연한 행 이름 매칭
        
        Args:
            df (pd.DataFrame): 데이터프레임
            candidates (list): 후보 행 이름들
            
        Returns:
            str: 매칭된 행 이름
        """
        index_str = df.index.astype(str).str.strip()
        
        for candidate in candidates:
            # 정확히 일치하는 경우
            if candidate in index_str.values:
                return candidate
            
            # 부분 매칭 (포함 관계)
            matches = index_str[index_str.str.contains(candidate, na=False)]
            if len(matches) > 0:
                return matches.iloc[0]
        
        raise KeyError(f"다음 항목을 찾을 수 없습니다: {candidates}")

    def calculate_growth_rate(self, current: float, previous: float) -> str:
        """
        성장률 계산
        
        Args:
            current (float): 현재 값
            previous (float): 이전 값
            
        Returns:
            str: 성장률 (%)
        """
        try:
            if previous == 0:
                return "N/A (이전값 0)"
            
            growth_rate = ((current - previous) / abs(previous)) * 100
            return f"{growth_rate:.2f}%"
            
        except (ZeroDivisionError, TypeError):
            return "N/A"

    def analyze_financials(self, df: pd.DataFrame) -> Dict[str, Any]:
        """
        주요 지표 분석 및 해석 제공
        
        Args:
            df (pd.DataFrame): 재무제표 데이터
            
        Returns:
            dict: 분석 결과
        """
        analysis = {}
        
        try:
            df_cleaned = self.clean_financial_df(df)
            
            # 최신 두 기간 선택
            if len(df_cleaned.columns) < 2:
                raise ValueError("비교할 데이터가 충분하지 않습니다 (최소 2개 기간 필요)")
            
            latest = df_cleaned.columns[-1]
            prev = df_cleaned.columns[-2]
            
            print(f"📊 비교 기간: {prev} vs {latest}")
            
            # 주요 항목 찾기
            try:
                row_revenue = self.find_row_name(df_cleaned, ['매출액', '수익(매출액)', '총매출액'])
                row_operating = self.find_row_name(df_cleaned, ['영업이익', '영업이익(손실)', '영업손익'])
                row_net = self.find_row_name(df_cleaned, ['당기순이익', '당기순이익(손실)', '순이익', '당기순손익'])
            except KeyError as e:
                analysis['오류'] = str(e)
                return analysis
            
            # 데이터 추출 (백만원 단위)
            revenue_current = df_cleaned.loc[row_revenue, latest]
            revenue_previous = df_cleaned.loc[row_revenue, prev]
            
            operating_current = df_cleaned.loc[row_operating, latest]
            operating_previous = df_cleaned.loc[row_operating, prev]
            
            net_current = df_cleaned.loc[row_net, latest]
            net_previous = df_cleaned.loc[row_net, prev]
            
            # 기본 정보
            analysis['📈 매출액 현재'] = f"{revenue_current:,.0f}백만원"
            analysis['📈 매출액 이전'] = f"{revenue_previous:,.0f}백만원"
            analysis['📈 매출액 변화'] = '증가' if revenue_current > revenue_previous else '감소'
            analysis['📈 매출액 증감률'] = self.calculate_growth_rate(revenue_current, revenue_previous)
            
            analysis['💼 영업이익 현재'] = f"{operating_current:,.0f}백만원"
            analysis['💼 영업이익 이전'] = f"{operating_previous:,.0f}백만원"
            analysis['💼 영업이익 변화'] = '증가' if operating_current > operating_previous else '감소'
            analysis['💼 영업이익 증감률'] = self.calculate_growth_rate(operating_current, operating_previous)
            
            analysis['💰 순이익 현재'] = f"{net_current:,.0f}백만원"
            analysis['💰 순이익 이전'] = f"{net_previous:,.0f}백만원"
            analysis['💰 순이익 변화'] = '증가' if net_current > net_previous else '감소'
            analysis['💰 순이익 증감률'] = self.calculate_growth_rate(net_current, net_previous)
            
            # 수익성 지표 계산
            if revenue_current != 0:
                operating_margin = (operating_current / revenue_current) * 100
                net_margin = (net_current / revenue_current) * 100
                
                analysis['📊 영업이익률'] = f"{operating_margin:.2f}%"
                analysis['📊 순이익률'] = f"{net_margin:.2f}%"
                
                # 영업이익률 평가
                if operating_margin >= 15:
                    analysis['⭐ 영업이익률 평가'] = '매우 우수'
                elif operating_margin >= 10:
                    analysis['⭐ 영업이익률 평가'] = '우수'
                elif operating_margin >= 5:
                    analysis['⭐ 영업이익률 평가'] = '보통'
                elif operating_margin >= 0:
                    analysis['⭐ 영업이익률 평가'] = '낮음'
                else:
                    analysis['⭐ 영업이익률 평가'] = '적자'
            
            # 종합 평가
            positive_signals = 0
            if revenue_current > revenue_previous:
                positive_signals += 1
            if operating_current > operating_previous:
                positive_signals += 1
            if net_current > net_previous:
                positive_signals += 1
                
            if positive_signals == 3:
                analysis['🎯 종합평가'] = '매우 긍정적 (3/3 지표 개선)'
            elif positive_signals == 2:
                analysis['🎯 종합평가'] = '긍정적 (2/3 지표 개선)'
            elif positive_signals == 1:
                analysis['🎯 종합평가'] = '혼조 (1/3 지표 개선)'
            else:
                analysis['🎯 종합평가'] = '부정적 (모든 지표 악화)'
            
            # 데이터 유형 추정
            column_names = ' '.join(df_cleaned.columns.astype(str))
            if any(keyword in column_names for keyword in ['년', 'Year', '연간']):
                analysis['📅 데이터 유형'] = '연간 실적'
            else:
                analysis['📅 데이터 유형'] = '분기별 실적'
                
        except Exception as e:
            analysis['❌ 분석 오류'] = str(e)
            
        return analysis

    def print_analysis(self, ticker: str):
        """
        재무 분석 결과 출력
        
        Args:
            ticker (str): 종목 코드
        """
        print(f"\n{'='*50}")
        print(f"📋 [{ticker}] 재무제표 분석 보고서")
        print(f"{'='*50}")
        
        df = self.get_financial_statement(ticker)
        
        if df is None:
            print("❌ 재무제표 데이터를 가져올 수 없습니다")
            print("   - 종목 코드가 올바른지 확인해주세요")
            print("   - 네트워크 연결 상태를 확인해주세요")
            return
        
        print(f"✅ 데이터 로드 완료 (행: {len(df)}, 열: {len(df.columns)})")
        
        analysis_result = self.analyze_financials(df)
        
        print(f"\n📊 분석 결과:")
        print("-" * 40)
        
        for key, value in analysis_result.items():
            print(f"{key}: {value}")
        
        print(f"\n{'='*50}")


# 사용 예시
def main():
    analyzer = FinancialAnalyzer()
    
    # 여러 종목 분석 예시
    tickers = [
        "005930",  # 삼성전자
        "000660",  # SK하이닉스  
        "035420",  # NAVER
        "005380",  # 현대차
    ]
    
    for ticker in tickers:
        try:
            analyzer.print_analysis(ticker)
            print("\n" + "="*60 + "\n")
        except KeyboardInterrupt:
            print("\n사용자에 의해 중단되었습니다.")
            break
        except Exception as e:
            print(f"❌ {ticker} 분석 중 오류: {e}")
            continue


if __name__ == "__main__":
    # 단일 종목 분석
    analyzer = FinancialAnalyzer()
    analyzer.print_analysis("005930")  # 삼성전자
    
    # 또는 여러 종목 분석
    # main()

'랭귀지 > python' 카테고리의 다른 글

재무재표심화  (1) 2025.08.06
재무제표 분석  (0) 2025.08.06
안전하게 삭제하는 코드 예시  (0) 2025.06.26
git merge  (0) 2025.06.26
파일에 최대 5줄까지만 유지하면서 새로운 내용을 추가  (0) 2025.02.19
Comments