본문 바로가기

혼공학습단(한빛미디어)

[혼공단14기] 혼자 만들면서 공부하는 파이썬 - 4주차


혼자 만들면서 공부하는 파이썬
  커리큘럼

 

 

 

혼자 만들면서 공부하는 파이썬 - 예스24

“파이썬을 배워서 어디에 활용할 수 있을지 궁금했나요?”나의 신용카드 내역 분석, 우리 아파트 실거래가 시각화해보기, 맛집 지도 웹 앱 만들기 등 생활 속 15가지 파이썬 프로젝트를 직접 완

www.yes24.com

https://www.yes24.com/product/goods/142258696

 

 


4주차(25.07.21 - 07.27) Chapter 07 시가총액 분석

 

 

네이버 증권 - 시가총액  

 

 

시가총액 : 네이버페이 증권

관심종목의 실시간 주가를 가장 빠르게 확인하는 곳

finance.naver.com

https://finance.naver.com/sise/sise_market_sum.naver

 

 

 

 

기본미션

▶ Ch.07(07-3) 시가 총액 데이터 시각화 하기

 

 

 

 

추가미션

 Ch.07 미니 프로젝트(p.246) ETF 데이터 시각화 하기

 

네이버 증권 - ETF

 

 

 

네이버페이 증권

국내 해외 증시 지수, 시장지표, 뉴스, 증권사 리서치 등 제공

finance.naver.com

https://finance.naver.com/sise/etf.naver

 

 

[ETF 데이터 크롤링]

 

from pathlib import Path
from playwright.sync_api import Page
from step_1_1 import OUT_DIR  # 이전에 작성한 모듈을 불러옵니다.
from step_1_2 import run_playwright
from step_1_4 import table_to_dataframe

def goto_market_etf(page: Page):
    page.goto("https://finance.naver.com")
    page.get_by_role("link", name="국내증시").click()
    page.get_by_role("link", name="ETF").click()


def parse_table_etf(page: Page) -> tuple[list, list]:
    page.wait_for_selector("table.type_1.type_etf tbody#etfItemTable tr")
    tag_table = page.locator("table", has_text="ETF 주요시세정보")

    # 헤더 추출
    tag_header_row = tag_table.locator("tr").first
    tag_thead = tag_header_row.locator("th")
    header = tag_thead.all_inner_texts()

    # 데이터 행 추출
    tag_tbody = tag_table.locator("tbody > tr")
    body = []

    for tag_tr in tag_tbody.all():
        tag_tds = tag_tr.locator("td")

        # 각 셀의 텍스트를 inner_text()로 하나씩 추출
        texts = [td.inner_text().strip() for td in tag_tds.all()]

        if not texts or all(t == "" for t in texts):
            continue

        # 전일비 이미지 alt 처리
        tag_img = tag_tr.locator("td > img")
        if tag_img.count() > 0:
            alt_text = tag_img.get_attribute("alt")
            texts[2] = f"{alt_text} {texts[2]}"

        # 누락 보정 (필요 시)
        if len(texts) < len(header):
            texts += [''] * (len(header) - len(texts))

        body.append(texts)

    # 디버깅
    if body and len(header) != len(body[0]):
        print(f"[⚠️ Warning] 헤더 개수({len(header)}) ≠ 데이터 열 개수({len(body[0])})")
        print("헤더:", header)
        print("예시 데이터 행:", body[0])

    return header, body


if __name__ == "__main__":
    play, browser, page = run_playwright(slow_mo=1000)
    goto_market_etf(page)  # ETF 페이지로 이동
    header, body = parse_table_etf(page)  # ETF 데이터 수집
    df_raw = table_to_dataframe(header, body)  # 데이터 정제 및 DataFrame 객체 생성
    df_raw.to_csv(OUT_DIR / f"{Path(__file__).stem}.csv", index=False)  # CSV로 저장

    browser.close()
    play.stop()

 

 

 

 

 

 

 

 

[ETF 상위 50% 트리맵 만들기 전체 코드]

from pathlib import Path
from playwright.sync_api import sync_playwright, Page
import pandas as pd
import plotly.express as px

# 출력 경로
OUT_DIR = Path(__file__).parent / "output"
OUT_DIR.mkdir(exist_ok=True)

CSV_PATH = OUT_DIR / "etf_data.csv"
TREEMAP_DATA_PATH = OUT_DIR / "etf_top_50pct.csv"
TREEMAP_IMG_PATH = OUT_DIR / "etf_treemap_50pct.png"


def run_playwright(slow_mo: int = 1000):
    play = sync_playwright().start()
    browser = play.chromium.launch(headless=False, slow_mo=slow_mo)
    page = browser.new_page()
    return play, browser, page


def goto_market_etf(page: Page):
    page.goto("https://finance.naver.com")
    page.get_by_role("link", name="국내증시").click()
    page.get_by_role("link", name="ETF").click()


def parse_table_etf(page: Page) -> tuple[list[str], list[list[str]]]:
    page.goto("https://finance.naver.com/sise/etf.naver")
    page.wait_for_selector("table.type_1.type_etf tbody#etfItemTable tr")
    tag_table = page.locator("table", has_text="ETF 주요시세정보")

    tag_header_row = tag_table.locator("tr").first
    header = tag_header_row.locator("th").all_inner_texts()

    tag_tbody = tag_table.locator("tbody > tr")
    body = []

    for tag_tr in tag_tbody.all():
        tag_tds = tag_tr.locator("td")
        if tag_tds.count() == 0:
            continue

        texts = [td.inner_text().strip() for td in tag_tds.all()]
        if not texts or all(t == "" for t in texts):
            continue

        tag_img = tag_tr.locator("td > img")
        if tag_img.count() > 0:
            alt_text = tag_img.get_attribute("alt")
            texts[2] = f"{alt_text} {texts[2]}"

        if len(texts) < len(header):
            texts += [''] * (len(header) - len(texts))

        body.append(texts)

    return header, body


def table_to_dataframe(header: list[str], body: list[list[str]]) -> pd.DataFrame:
    df = pd.DataFrame(body, columns=header)
    return df


def top_ETF_company(df_raw: pd.DataFrame, prop: float) -> pd.DataFrame:
    df_raw = df_raw.copy()
    df_raw["시가총액"] = df_raw["시가총액(억)"].str.replace(",", "").replace("", "0").astype(int)
    df_raw["조단위"] = df_raw["시가총액"] / 10_000  # 억 → 조
    df_raw = df_raw.sort_values("시가총액", ascending=False)
    df_raw["누적비율"] = df_raw["시가총액"].cumsum() / df_raw["시가총액"].sum()
    df_sliced = df_raw[df_raw["누적비율"] <= prop]
    return df_sliced[["종목명", "시가총액", "조단위", "누적비율"]]


def draw_treemap(df_top: pd.DataFrame):
    fig = px.treemap(
        df_top,
        path=["종목명"],
        values="조단위",
    )
    fig.update_traces(
        marker=dict(
            cornerradius=5,
            colorscale="Plasma",
            pad=dict(t=10, r=10, b=10, l=10),
        ),
        texttemplate="<b>%{label}</b><br>%{value:,.2f}조원",
        textfont_size=30,
    )
    fig.update_layout(margin=dict(t=0, r=0, b=0, l=0))
    fig.write_image(str(TREEMAP_IMG_PATH), width=1600, height=900, scale=2)
    print(f"✅ 트리맵 이미지 저장 완료: {TREEMAP_IMG_PATH}")


if __name__ == "__main__":
    play, browser, page = run_playwright(slow_mo=500)
    goto_market_etf(page)
    header, body = parse_table_etf(page)
    df_raw = table_to_dataframe(header, body)
    df_raw.to_csv(CSV_PATH, index=False, encoding="utf-8-sig")
    print(f"✅ CSV 저장 완료: {CSV_PATH}")

    # 트리맵 데이터 준비 및 시각화
    df_top = top_ETF_company(df_raw, 0.5)
    df_top.to_csv(TREEMAP_DATA_PATH, index=False, encoding="utf-8-sig")
    print(f"✅ 상위 50% ETF CSV 저장 완료: {TREEMAP_DATA_PATH}")

    draw_treemap(df_top)

    browser.close()
    play.stop()