n_stock / app.py
Kims12's picture
Update app.py
feb317a verified
import gradio as gr
import requests
from bs4 import BeautifulSoup
import pandas as pd
import logging
import io
import tempfile
from openpyxl import load_workbook
from openpyxl.styles import Font
# 디버깅을 μœ„ν•œ λ‘œκΉ… μ„€μ •
logging.basicConfig(level=logging.DEBUG)
def format_change_web(text):
"""
전일비 및 등락λ₯  ν…μŠ€νŠΈλ₯Ό μ•„μ΄μ½˜κ³Ό μƒ‰μƒμœΌλ‘œ ν¬λ§·νŒ…ν•©λ‹ˆλ‹€.
μƒμŠΉ: 뢉은색, ν•˜λ½: νŒŒλž€μƒ‰
μ›Ή μΈν„°νŽ˜μ΄μŠ€μš© HTML ν¬λ§·νŒ…
"""
if "μƒν•œκ°€" in text:
number = ''.join(filter(lambda x: x.isdigit() or x == '-', text))
return f'<span style="color:red;">↑{number}</span>'
elif "μƒμŠΉ" in text:
number = ''.join(filter(lambda x: x.isdigit() or x == '-', text))
return f'<span style="color:red;">β–²{number}</span>'
elif "ν•˜ν•œκ°€" in text:
number = ''.join(filter(lambda x: x.isdigit() or x == '-', text))
return f'<span style="color:blue;">↓{number}</span>'
elif "ν•˜λ½" in text:
number = ''.join(filter(lambda x: x.isdigit() or x == '-', text))
return f'<span style="color:blue;">β–Ό{number}</span>'
elif text.startswith('+'):
return f'<span style="color:red;">β–²{text}</span>'
elif text.startswith('-'):
return f'<span style="color:blue;">β–Ό{text}</span>'
else:
return text
def format_change_excel(text):
"""
전일비 및 등락λ₯  ν…μŠ€νŠΈλ₯Ό μ•„μ΄μ½˜μœΌλ‘œ ν¬λ§·νŒ…ν•©λ‹ˆλ‹€.
μƒμŠΉ: β–², ↑
ν•˜λ½: β–Ό, ↓
μ—‘μ…€μš© ν…μŠ€νŠΈ ν¬λ§·νŒ… (HTML νƒœκ·Έ μ—†μŒ)
"""
if "μƒν•œκ°€" in text:
number = ''.join(filter(lambda x: x.isdigit() or x == '-', text))
return f'↑{number}'
elif "μƒμŠΉ" in text:
number = ''.join(filter(lambda x: x.isdigit() or x == '-', text))
return f'β–²{number}'
elif "ν•˜ν•œκ°€" in text:
number = ''.join(filter(lambda x: x.isdigit() or x == '-', text))
return f'↓{number}'
elif "ν•˜λ½" in text:
number = ''.join(filter(lambda x: x.isdigit() or x == '-', text))
return f'β–Ό{number}'
elif text.startswith('+'):
return f'β–²{text}'
elif text.startswith('-'):
return f'β–Ό{text}'
else:
return text
def scrape_market(market):
if market == "μ½”μŠ€λ‹₯":
url = "https://finance.naver.com/sise/sise_rise.naver?sosok=1"
elif market == "μ½”μŠ€ν”Ό":
url = "https://finance.naver.com/sise/sise_rise.naver?sosok=0"
else:
logging.error("잘λͺ»λœ μ‹œμž₯ 선택")
return "잘λͺ»λœ μ‹œμž₯을 μ„ νƒν•˜μ…¨μŠ΅λ‹ˆλ‹€.", None
logging.debug(f"μš”μ²­ν•  URL: {url}")
try:
response = requests.get(url)
response.raise_for_status()
logging.debug("μ›Ή νŽ˜μ΄μ§€ μš”μ²­ 성곡")
except requests.exceptions.RequestException as e:
logging.error(f"μ›Ή νŽ˜μ΄μ§€ μš”μ²­ μ‹€νŒ¨: {e}")
return f"데이터λ₯Ό κ°€μ Έμ˜€λŠ” 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€: {e}", None
soup = BeautifulSoup(response.text, "html.parser")
table = soup.find("table", {"class": "type_2"})
if not table:
logging.error("ν…Œμ΄λΈ”μ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.")
return "ν…Œμ΄λΈ”μ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.", None
rows = table.find_all("tr")
logging.debug(f"총 ν–‰ 수: {len(rows)}")
data_display = [] # μ›Ήμš© 데이터
data_excel = [] # μ—‘μ…€μš© 데이터
headers = ["N", "μ’…λͺ©λͺ…", "ν˜„μž¬κ°€", "전일비", "등락λ₯ ", "κ±°λž˜λŸ‰",
"λ§€μˆ˜ν˜Έκ°€", "λ§€λ„ν˜Έκ°€", "λ§€μˆ˜μ΄μž”λŸ‰", "λ§€λ„μ΄μž”λŸ‰", "PER", "ROE"]
for row in rows[2:]: # 헀더λ₯Ό κ±΄λ„ˆλ›°κΈ° μœ„ν•΄ 인덱슀 2λΆ€ν„° μ‹œμž‘
cols = row.find_all("td")
if len(cols) < 12:
logging.debug("데이터가 λΆ€μ‘±ν•œ 행을 κ±΄λ„ˆλœλ‹ˆλ‹€.")
continue
try:
n = cols[0].get_text(strip=True)
μ’…λͺ©λͺ… = cols[1].find("a").get_text(strip=True)
ν˜„μž¬κ°€ = cols[2].get_text(strip=True).replace(',', '')
전일비_raw = cols[3].get_text(strip=True)
등락λ₯ _raw = cols[4].get_text(strip=True)
κ±°λž˜λŸ‰ = cols[5].get_text(strip=True).replace(',', '')
λ§€μˆ˜ν˜Έκ°€ = cols[6].get_text(strip=True).replace(',', '')
λ§€λ„ν˜Έκ°€ = cols[7].get_text(strip=True).replace(',', '')
λ§€μˆ˜μ΄μž”λŸ‰ = cols[8].get_text(strip=True).replace(',', '')
λ§€λ„μ΄μž”λŸ‰ = cols[9].get_text(strip=True).replace(',', '')
PER = cols[10].get_text(strip=True)
ROE = cols[11].get_text(strip=True)
# 전일비와 등락λ₯  ν¬λ§·νŒ…
전일비_web = format_change_web(전일비_raw)
등락λ₯ _web = format_change_web(등락λ₯ _raw)
전일비_excel = format_change_excel(전일비_raw)
등락λ₯ _excel = format_change_excel(등락λ₯ _raw)
logging.debug(f"μΆ”μΆœ 데이터 - N: {n}, μ’…λͺ©λͺ…: {μ’…λͺ©λͺ…}, ν˜„μž¬κ°€: {ν˜„μž¬κ°€}, 전일비: {전일비_raw} -> {전일비_excel}, 등락λ₯ : {등락λ₯ _raw} -> {등락λ₯ _excel}, κ±°λž˜λŸ‰: {κ±°λž˜λŸ‰}, λ§€μˆ˜ν˜Έκ°€: {λ§€μˆ˜ν˜Έκ°€}, λ§€λ„ν˜Έκ°€: {λ§€λ„ν˜Έκ°€}, λ§€μˆ˜μ΄μž”λŸ‰: {λ§€μˆ˜μ΄μž”λŸ‰}, λ§€λ„μ΄μž”λŸ‰: {λ§€λ„μ΄μž”λŸ‰}, PER: {PER}, ROE: {ROE}")
# μ›Ήμš© 데이터에 HTML ν¬λ§·νŒ… 적용
data_display.append([n, μ’…λͺ©λͺ…, ν˜„μž¬κ°€, 전일비_web, 등락λ₯ _web, κ±°λž˜λŸ‰,
λ§€μˆ˜ν˜Έκ°€, λ§€λ„ν˜Έκ°€, λ§€μˆ˜μ΄μž”λŸ‰, λ§€λ„μ΄μž”λŸ‰, PER, ROE])
# μ—‘μ…€μš© 데이터에 μ•„μ΄μ½˜ μΆ”κ°€ (HTML νƒœκ·Έ μ—†μŒ)
data_excel.append([n, μ’…λͺ©λͺ…, ν˜„μž¬κ°€, 전일비_excel, 등락λ₯ _excel, κ±°λž˜λŸ‰,
λ§€μˆ˜ν˜Έκ°€, λ§€λ„ν˜Έκ°€, λ§€μˆ˜μ΄μž”λŸ‰, λ§€λ„μ΄μž”λŸ‰, PER, ROE])
except Exception as e:
logging.error(f"데이터 μΆ”μΆœ 쀑 였λ₯˜ λ°œμƒ: {e}")
continue
if not data_display:
logging.error("μΆ”μΆœλœ 데이터가 μ—†μŠ΅λ‹ˆλ‹€.")
return "μΆ”μΆœλœ 데이터가 μ—†μŠ΅λ‹ˆλ‹€.", None
# μ›Ήμš© λ°μ΄ν„°ν”„λ ˆμž„ 생성
df_display = pd.DataFrame(data_display, columns=headers)
logging.debug("μ›Ήμš© λ°μ΄ν„°ν”„λ ˆμž„ 생성 μ™„λ£Œ")
# μ—‘μ…€μš© λ°μ΄ν„°ν”„λ ˆμž„ 생성
df_excel = pd.DataFrame(data_excel, columns=headers)
logging.debug("μ—‘μ…€μš© λ°μ΄ν„°ν”„λ ˆμž„ 생성 μ™„λ£Œ")
# HTML ν…Œμ΄λΈ” 생성 (μ›Ήμš©)
html = """
<style>
table {
width: 100%;
border-collapse: collapse;
}
th, td {
border: 1px solid #dddddd;
text-align: center;
padding: 8px;
}
th {
background-color: #f2f2f2;
}
</style>
<table>
<tr>
<th>N</th>
<th>μ’…λͺ©λͺ…</th>
<th>ν˜„μž¬κ°€</th>
<th>전일비</th>
<th>등락λ₯ </th>
<th>κ±°λž˜λŸ‰</th>
<th>λ§€μˆ˜ν˜Έκ°€</th>
<th>λ§€λ„ν˜Έκ°€</th>
<th>λ§€μˆ˜μ΄μž”λŸ‰</th>
<th>λ§€λ„μ΄μž”λŸ‰</th>
<th>PER</th>
<th>ROE</th>
</tr>
"""
for index, row in df_display.iterrows():
html += f"""
<tr>
<td>{row['N']}</td>
<td>{row['μ’…λͺ©λͺ…']}</td>
<td>{row['ν˜„μž¬κ°€']}</td>
<td>{row['전일비']}</td>
<td>{row['등락λ₯ ']}</td>
<td>{row['κ±°λž˜λŸ‰']}</td>
<td>{row['λ§€μˆ˜ν˜Έκ°€']}</td>
<td>{row['λ§€λ„ν˜Έκ°€']}</td>
<td>{row['λ§€μˆ˜μ΄μž”λŸ‰']}</td>
<td>{row['λ§€λ„μ΄μž”λŸ‰']}</td>
<td>{row['PER']}</td>
<td>{row['ROE']}</td>
</tr>
"""
html += "</table>"
logging.debug("HTML ν…Œμ΄λΈ” 생성 μ™„λ£Œ")
# μ—‘μ…€ 파일 생성 및 μ…€ 색상 적용
try:
excel_buffer = io.BytesIO()
df_excel.to_excel(excel_buffer, index=False, engine='openpyxl')
excel_buffer.seek(0)
# μ—‘μ…€ 파일 λ‘œλ“œ
wb = load_workbook(excel_buffer)
ws = wb.active
# "전일비"λŠ” Dμ—΄, "등락λ₯ "은 Eμ—΄
for row in ws.iter_rows(min_row=2, min_col=4, max_col=5, max_row=ws.max_row):
전일비_cell, 등락λ₯ _cell = row
# 전일비 색상 적용
if 전일비_cell.value.startswith('β–²') or 전일비_cell.value.startswith('↑'):
전일비_cell.font = Font(color="FF0000") # 빨간색
elif 전일비_cell.value.startswith('β–Ό') or 전일비_cell.value.startswith('↓'):
전일비_cell.font = Font(color="0000FF") # νŒŒλž€μƒ‰
# 등락λ₯  색상 적용
if 등락λ₯ _cell.value.startswith('β–²') or 등락λ₯ _cell.value.startswith('↑'):
등락λ₯ _cell.font = Font(color="FF0000") # 빨간색
elif 등락λ₯ _cell.value.startswith('β–Ό') or 등락λ₯ _cell.value.startswith('↓'):
등락λ₯ _cell.font = Font(color="0000FF") # νŒŒλž€μƒ‰
# μˆ˜μ •λœ μ—‘μ…€ νŒŒμΌμ„ λ‹€μ‹œ BytesIO둜 μ €μž₯
final_excel = io.BytesIO()
wb.save(final_excel)
final_excel.seek(0)
# μž„μ‹œ 파일 생성
temp = tempfile.NamedTemporaryFile(delete=False, suffix=".xlsx")
temp.write(final_excel.read())
temp.close()
logging.debug("μ—‘μ…€ 파일 생성 및 μ €μž₯ μ™„λ£Œ")
except Exception as e:
logging.error(f"μ—‘μ…€ 파일 생성 쀑 였λ₯˜ λ°œμƒ: {e}")
temp = None
return html, temp.name if temp else None
with gr.Blocks() as demo:
gr.Markdown("### 넀이버 증ꢌ μ½”μŠ€λ‹₯/μ½”μŠ€ν”Ό μ’…λͺ© 정보 μŠ€ν¬λž˜ν•‘")
with gr.Row():
market_choice = gr.Radio(
choices=["μ½”μŠ€λ‹₯", "μ½”μŠ€ν”Ό"],
label="μ‹œμž₯ 선택",
value="μ½”μŠ€λ‹₯"
)
btn = gr.Button("데이터 κ°€μ Έμ˜€κΈ°")
with gr.Row():
output_html = gr.HTML()
output_file = gr.File(label="μ—‘μ…€ 파일 λ‹€μš΄λ‘œλ“œ")
btn.click(scrape_market, inputs=market_choice, outputs=[output_html, output_file])
if __name__ == "__main__":
demo.launch()