Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 156 additions & 0 deletions apps/backend/Gemini_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import os
import warnings
from typing import List, Dict, Tuple

import google.generativeai as genai
import pandas as pd
from dotenv import load_dotenv
from sentence_transformers import SentenceTransformer, util
from tqdm import tqdm
from tqdm.auto import tqdm as auto_tqdm # pandas.apply ์ง„ํ–‰๋ฅ  ํ‘œ์‹œ๋ฅผ ์œ„ํ•จ

# --- 1. ์„ค์ • (Configuration) ---
INPUT_FILENAME = "news_test.csv"
OUTPUT_FILENAME = "news_classified_results_representatives2.csv"
EMBEDDING_MODEL_NAME = 'distiluse-base-multilingual-cased-v1'
GEMINI_MODEL_NAME = 'gemini-2.5-flash' # ๋˜๋Š” 'gemini-1.0-pro' ๋“ฑ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ชจ๋ธ
TOPIC_CATEGORIES = [
"์‹ ์‚ฌ์—…/M&A", "๊ฒฝ์˜์ „๋žต/๋ฆฌ๋”์‹ญ", "ํ•ด์™ธ์ง„์ถœ/๊ธ€๋กœ๋ฒŒ ๋™ํ–ฅ", "ํˆฌ์ž์œ ์น˜/์žฌ๋ฌด", "์‹ ์ œํ’ˆ/์„œ๋น„์Šค ์ถœ์‹œ",
"๊ธฐ์ˆ ๊ฐœ๋ฐœ/R&D", "์ƒ์‚ฐ/๊ณต๊ธ‰๋ง ๊ด€๋ฆฌ", "ํŠนํ—ˆ/๊ธฐ์ˆ ์ธ์ฆ", "์‹œ์žฅ๋™ํ–ฅ/ํŠธ๋ Œ๋“œ ๋ถ„์„", "๊ฒฝ์Ÿ์‚ฌ ๋™ํ–ฅ",
"์ •๋ถ€๊ทœ์ œ/์ •์ฑ…", "์ธ์žฌ์ฑ„์šฉ/์ธ์žฌ์ƒ", "์กฐ์ง๋ฌธํ™”/์ธ์‚ฌ์ œ๋„", "์ž„์ง์› ๋™์ •/์ธ์‚ฌ", "๋…ธ์‚ฌ๊ด€๊ณ„/๊ณ ์šฉ์ด์Šˆ",
"ESG/์ง€์†๊ฐ€๋Šฅ๊ฒฝ์˜", "์‚ฌํšŒ๊ณตํ—Œ/CSR", "์†Œ๋น„์ž๋ณดํ˜ธ/๋ถ„์Ÿ", "ํŒŒํŠธ๋„ˆ์‹ญ/ํ˜‘๋ ฅ", "๋Œ€์™ธํ™œ๋™/ํ™๋ณด", "๋ฆฌ์Šคํฌ/์œ„๊ธฐ๊ด€๋ฆฌ"
]

def setup_gemini() -> genai.GenerativeModel:
"""API ํ‚ค๋ฅผ ์„ค์ •ํ•˜๊ณ  Gemini ๋ชจ๋ธ์„ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค."""
load_dotenv()
warnings.filterwarnings("ignore")
auto_tqdm.pandas(desc="๊ฐœ๋ณ„ ๊ธฐ์‚ฌ ๋ถ„์„(์š”์•ฝ+๋ถ„๋ฅ˜) ์ค‘") # tqdm.pandas() ํ™œ์„ฑํ™”
try:
api_key = os.environ.get("GOOGLE_API_KEY")
if not api_key:
raise ValueError("'.env' ํŒŒ์ผ์—์„œ GOOGLE_API_KEY๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
genai.configure(api_key=api_key)
print("โœ… Gemini API ํ‚ค๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์„ค์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
return genai.GenerativeModel(GEMINI_MODEL_NAME)
except Exception as e:
print(f"โŒ API ํ‚ค ๋˜๋Š” ๋ชจ๋ธ ์„ค์ • ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}")
exit()

def load_and_preprocess_data(filepath: str) -> pd.DataFrame:
"""CSV ํŒŒ์ผ์„ ๋กœ๋“œํ•˜๊ณ  ๋ถ„์„์— ๋งž๊ฒŒ ์ „์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค."""
print(f"\n1๋‹จ๊ณ„: '{filepath}' ํŒŒ์ผ ๋กœ๋”ฉ ๋ฐ ์ „์ฒ˜๋ฆฌ...")
try:
df = pd.read_csv(filepath, encoding='utf-8-sig')
except FileNotFoundError:
print(f"โŒ ์˜ค๋ฅ˜: '{filepath}' ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
exit()

df.columns = df.columns.str.strip()
if '๋ณธ๋ฌธ' in df.columns: df.rename(columns={'๋ณธ๋ฌธ': 'content'}, inplace=True)
if '์ œ๋ชฉ' in df.columns: df.rename(columns={'์ œ๋ชฉ': 'title'}, inplace=True)
df.dropna(subset=['title', 'content'], inplace=True)
df.drop_duplicates(subset=['title'], keep='first', inplace=True)
df.reset_index(drop=True, inplace=True)
print(f"โœ… ์ด {len(df)}๊ฐœ์˜ ๊ณ ์œ ํ•œ ๋‰ด์Šค ๊ธฐ์‚ฌ๋ฅผ ์ค€๋น„ํ–ˆ์Šต๋‹ˆ๋‹ค.")
return df

def cluster_articles(df: pd.DataFrame) -> pd.DataFrame:
"""SentenceTransformer๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ '๊ฑฐ์˜ ๋™์ผํ•œ' ๊ธฐ์‚ฌ๋“ค์„ ๊ทธ๋ฃนํ™”ํ•ฉ๋‹ˆ๋‹ค."""
print(f"\n2๋‹จ๊ณ„: '{EMBEDDING_MODEL_NAME}' ๋ชจ๋ธ๋กœ ์ค‘๋ณต ๊ธฐ์‚ฌ ๊ทธ๋ฃนํ™”...")
model = SentenceTransformer(EMBEDDING_MODEL_NAME)
embeddings = model.encode(df['content'].tolist(), show_progress_bar=True)

# [ํ•ต์‹ฌ] '๊ฑฐ์˜ ๋™์ผํ•œ ์ด๋ฒคํŠธ'๋ฅผ ๋ฌถ๋„๋ก ๊ธฐ์ค€์„ ์ƒํ–ฅ (threshold=0.8)
clusters = util.community_detection(embeddings, min_community_size=2, threshold=0.8)

doc_id_to_cluster_id = {doc_id: i for i, cluster in enumerate(clusters) for doc_id in cluster}

df['cluster_id'] = df.index.map(lambda x: doc_id_to_cluster_id.get(x, -1))
print(f"โœ… {len(clusters)}๊ฐœ์˜ ๊ณ ์œ ํ•œ ์ด๋ฒคํŠธ(์ค‘๋ณต ๊ทธ๋ฃน)๋ฅผ ๋ฐœ๊ฒฌํ–ˆ์Šต๋‹ˆ๋‹ค.")
return df

# [์‹ ๊ทœ] 3๋‹จ๊ณ„: ๊ฐœ๋ณ„ ๊ธฐ์‚ฌ ๋ถ„์„ (์š”์•ฝ + ๋ถ„๋ฅ˜)
def get_summary_and_topic(content: str, model: genai.GenerativeModel, categories: List[str]) -> Tuple[str, str]:
"""
๋‹จ์ผ ๊ธฐ์‚ฌ ๋ณธ๋ฌธ์„ ๋ฐ›์•„, [์š”์•ฝ]๊ณผ [ํ† ํ”ฝ]์„ ํ•œ ๋ฒˆ์˜ API ํ˜ธ์ถœ๋กœ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
"""
category_list_str = f"[{', '.join(categories)}]"

# [ํ”„๋กฌํ”„ํŠธ ๊ณ ๋„ํ™”] ์š”์•ฝ๊ณผ ๋ถ„๋ฅ˜๋ฅผ ํ•œ ๋ฒˆ์— ์š”์ฒญ
prompt = (
f"๋‹น์‹ ์€ ์ทจ์—… ์ค€๋น„์ƒ์˜ ๋ฉด์ ‘ ์ค€๋น„๋ฅผ ๋•๋Š” ์ „๋ฌธ ์ปค๋ฆฌ์–ด ์• ๋„๋ฆฌ์ŠคํŠธ์ž…๋‹ˆ๋‹ค.\n"
f"๋‹ค์Œ ๋‰ด์Šค ๊ธฐ์‚ฌ ๋ณธ๋ฌธ์„ ์ฝ๊ณ , 2๊ฐ€์ง€ ์ž„๋ฌด๋ฅผ ์ˆ˜ํ–‰ํ•ด์ฃผ์„ธ์š”.\n\n"
f"1. **[์˜๋ฏธ ์š”์•ฝ]**: ์ด ๋‰ด์Šค๊ฐ€ ์ง€์›์ž์—๊ฒŒ ์–ด๋–ค ์˜๋ฏธ๊ฐ€ ์žˆ๋Š”์ง€(์„ฑ์žฅ ๋™๋ ฅ, ์œ„๊ธฐ, ์ธ์žฌ์ƒ ๋“ฑ)์— ์ดˆ์ ์„ ๋งž์ถฐ 2-3๋ฌธ์žฅ์œผ๋กœ ์š”์•ฝํ•ฉ๋‹ˆ๋‹ค.\n"
f"2. **[ํ† ํ”ฝ ๋ถ„๋ฅ˜]**: ์ฃผ์–ด์ง„ 'ํ† ํ”ฝ ๋ชฉ๋ก'์—์„œ ์ด ๊ธฐ์‚ฌ์˜ ํ•ต์‹ฌ ์ฃผ์ œ์™€ ๊ฐ€์žฅ ์ ํ•ฉํ•œ ์นดํ…Œ๋ฆฌ ํ•˜๋‚˜๋งŒ์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.\n\n"
f"--- ํ† ํ”ฝ ๋ชฉ๋ก ---\n{category_list_str}\n\n"
f"--- ๊ธฐ์‚ฌ ๋ณธ๋ฌธ ---\n{content}\n\n"
f"--- [์ค‘์š”] ์‘๋‹ต ํ˜•์‹ ---\n"
f"๋ฐ˜๋“œ์‹œ ์•„๋ž˜์™€ ๊ฐ™์€ ํ˜•์‹์œผ๋กœ๋งŒ ์‘๋‹ตํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. (๋‹ค๋ฅธ ์„ค๋ช… ์—†์ด):\n"
f"์š”์•ฝ: [์—ฌ๊ธฐ์— 1๋ฒˆ ์ž„๋ฌด์˜ ์š”์•ฝ ๋‚ด์šฉ์„ ์ž‘์„ฑ]\n"
f"ํ† ํ”ฝ: [์—ฌ๊ธฐ์— 2๋ฒˆ ์ž„๋ฌด์˜ ํ† ํ”ฝ์„ ์ž‘์„ฑ]"
)

try:
response = model.generate_content(prompt)
text = response.text.strip()

# [์‘๋‹ต ํŒŒ์‹ฑ] "์š”์•ฝ:", "ํ† ํ”ฝ:"์„ ๊ธฐ์ค€์œผ๋กœ ํ…์ŠคํŠธ ๋ถ„๋ฆฌ
summary_part = "์š”์•ฝ ์‹คํŒจ"
topic_part = "๋ถ„๋ฅ˜ ์‹คํŒจ"

if "์š”์•ฝ:" in text and "ํ† ํ”ฝ:" in text:
summary_raw = text.split("์š”์•ฝ:")[1].split("ํ† ํ”ฝ:")[0].strip()
topic_raw = text.split("ํ† ํ”ฝ:")[1].strip()

summary_part = summary_raw
# ํ† ํ”ฝ ๋ชฉ๋ก์— ์žˆ๋Š” ์œ ํšจํ•œ ์นดํ…Œ๊ณ ๋ฆฌ์ธ์ง€ ํ•œ๋ฒˆ ๋” ํ™•์ธ
topic_part = next((cat for cat in categories if cat in topic_raw), "๋ถ„๋ฅ˜ ์‹คํŒจ")
else:
# ์˜ˆ์™ธ: ํ˜•์‹์„ ์ง€ํ‚ค์ง€ ์•Š์€ ์‘๋‹ต
summary_part = text[:150] + "..." # ์‘๋‹ต์˜ ์ผ๋ถ€๋ผ๋„ ์ €์žฅ

return summary_part, topic_part

except Exception as e:
return f"์š”์•ฝ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}", f"๋ถ„๋ฅ˜ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}"


def main():
"""๋ฉ”์ธ ์‹คํ–‰ ํ•จ์ˆ˜"""
gemini_model = setup_gemini()
df = load_and_preprocess_data(INPUT_FILENAME)
df_clustered = cluster_articles(df)

# --- ์ค‘๋ณต ๊ธฐ์‚ฌ ์ œ๊ฑฐ ๋ฐ ๋Œ€ํ‘œ ๊ธฐ์‚ฌ ์„ ์ • ---
print("\n์ค‘๊ฐ„ ๋‹จ๊ณ„: ๊ฐ ๊ทธ๋ฃน๋ณ„ ๋Œ€ํ‘œ ๊ธฐ์‚ฌ 1๊ฐœ์”ฉ ์„ ์ •...")
df_representatives = df_clustered[df_clustered['cluster_id'] != -1].drop_duplicates(subset=['cluster_id'], keep='first')
df_others = df_clustered[df_clustered['cluster_id'] == -1]
df_filtered = pd.concat([df_representatives, df_others]).sort_index()

print(f"โœ… '๊ธฐํƒ€'(๊ณ ์œ ) ๊ธฐ์‚ฌ {len(df_others)}๊ฐœ์™€ '๋Œ€ํ‘œ' ๊ธฐ์‚ฌ {len(df_representatives)}๊ฐœ๋ฅผ ํฌํ•จ, ์ด {len(df_filtered)}๊ฐœ๋กœ ์••์ถ•๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
# --- ๋กœ์ง ๋ ---


# --- [์‹ ๊ทœ] 3๋‹จ๊ณ„: ์••์ถ•๋œ ๋ฆฌ์ŠคํŠธ์— ๋Œ€ํ•ด ๊ฐœ๋ณ„ ๋ถ„์„ (์š”์•ฝ + ๋ถ„๋ฅ˜) ์‹คํ–‰ ---
print(f"\n3๋‹จ๊ณ„: Gemini API๋กœ {len(df_filtered)}๊ฐœ์˜ ๊ณ ์œ /๋Œ€ํ‘œ ๊ธฐ์‚ฌ ๋ถ„์„ ์‹œ์ž‘...")

# .progress_apply()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ง„ํ–‰๋ฅ  ํ‘œ์‹œ์ค„๊ณผ ํ•จ๊ป˜ ๋ชจ๋“  ํ–‰์— ํ•จ์ˆ˜ ์ ์šฉ
# ์ด ํ•จ์ˆ˜๋Š” (summary, topic) ํŠœํ”Œ์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
results = df_filtered['content'].progress_apply(
lambda x: get_summary_and_topic(x, gemini_model, TOPIC_CATEGORIES)
)

# ํŠœํ”Œ๋กœ ๋ฐ˜ํ™˜๋œ ๊ฒฐ๊ณผ๋ฅผ ๋‘ ๊ฐœ์˜ ์ƒˆ๋กœ์šด ์—ด('summary', 'topic')๋กœ ๋ถ„๋ฆฌ
df_final = df_filtered.copy()
df_final['summary'] = results.apply(lambda x: x[0])
df_final['topic'] = results.apply(lambda x: x[1])

df_final.to_csv(OUTPUT_FILENAME, index=False, encoding='utf-8-sig')

print(f"\n๐ŸŽ‰ ๋ชจ๋“  ๋ถ„์„์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค! ๊ฒฐ๊ณผ๊ฐ€ '{OUTPUT_FILENAME}' ํŒŒ์ผ์— ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
print("\n--- ์ตœ์ข… ํ† ํ”ฝ ๋ถ„๋ฅ˜ ์š”์•ฝ ---")
print(df_final['topic'].value_counts())

if __name__ == "__main__":
main()
109 changes: 109 additions & 0 deletions apps/backend/analysis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import pandas as pd
from bertopic import BERTopic
from sentence_transformers import SentenceTransformer
from keybert import KeyBERT
from kiwipiepy import Kiwi
import warnings

# --- 0. ์ดˆ๊ธฐ ์„ค์ • ---
# ๋ถˆํ•„์š”ํ•œ ๊ฒฝ๊ณ  ๋ฉ”์‹œ์ง€๋ฅผ ์ˆจ๊ธฐ๊ธฐ
warnings.filterwarnings("ignore")

# --- 1. ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ๋ฐ ์ „์ฒ˜๋ฆฌ ---
print("CSV ํŒŒ์ผ์„ ๋กœ๋”ฉํ•ฉ๋‹ˆ๋‹ค...")
try:
df = pd.read_csv("news.csv", encoding='utf-8-sig')
except FileNotFoundError:
print("์˜ค๋ฅ˜: 'news.csv' ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
print("์Šคํฌ๋ฆฝํŠธ์™€ ๊ฐ™์€ ํด๋”์— ํŒŒ์ผ์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•ด์ฃผ์„ธ์š”.")
exit()

print(f"ํŒŒ์ผ์—์„œ ์ฝ์–ด์˜จ ์›๋ณธ ์—ด ์ด๋ฆ„: {df.columns.tolist()}")

# CSV ํŒŒ์ผ์˜ ์—ด ์ด๋ฆ„์— ์žˆ์„ ์ˆ˜ ์žˆ๋Š” ์ขŒ์šฐ ๊ณต๋ฐฑ์„ ์ œ๊ฑฐ
df.columns = df.columns.str.strip()

# '๋ณธ๋ฌธ' ์—ด์˜ ์ด๋ฆ„์„ 'content'๋กœ ํ†ต์ผ
if '๋ณธ๋ฌธ' in df.columns and 'content' not in df.columns:
df.rename(columns={'๋ณธ๋ฌธ': 'content'}, inplace=True)

# '์ œ๋ชฉ' ์—ด์˜ ์ด๋ฆ„์„ 'title'๋กœ ํ†ต์ผ
if '์ œ๋ชฉ' in df.columns and 'title' not in df.columns:
df.rename(columns={'์ œ๋ชฉ': 'title'}, inplace=True)

# --- ๊ฐ•ํ™”๋œ ์˜ค๋ฅ˜ ์ง„๋‹จ ๋กœ์ง ---
# ๋ถ„์„์— ํ•„์š”ํ•œ 'content'์™€ 'title' ์—ด์ด ์žˆ๋Š”์ง€ ์ตœ์ข… ํ™•์ธ
required_cols = ['title', 'content']
missing_cols = [col for col in required_cols if col not in df.columns]

if missing_cols:
print("-" * 50)
print(f"์˜ค๋ฅ˜: ํ•„์ˆ˜ ์—ด์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค: {', '.join(missing_cols)}")
print("CSV ํŒŒ์ผ์— '์ œ๋ชฉ'๊ณผ '๋ณธ๋ฌธ' ์—ด์ด ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ํฌํ•จ๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธํ•ด์ฃผ์„ธ์š”.")
print(f"์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์ธ์‹ํ•œ ํ˜„์žฌ ์—ด ๋ชฉ๋ก: {df.columns.tolist()}")
print("-" * 50)
exit()


# 'content' ๋˜๋Š” 'title' ์—ด์— ๋น„์–ด์žˆ๋Š” ๋ฐ์ดํ„ฐ ์ œ๊ฑฐ
df.dropna(subset=['content', 'title'], inplace=True)

# --- ์ค‘๋ณต ๋‰ด์Šค ๊ธฐ์‚ฌ ์ œ๊ฑฐ (์ œ๋ชฉ ๊ธฐ์ค€) ---
initial_count = len(df)
df.drop_duplicates(subset=['title'], keep='first', inplace=True)
final_count = len(df)
print(f"์ค‘๋ณต ๋‰ด์Šค {initial_count - final_count}๊ฐœ๋ฅผ ์ œ๊ฑฐํ–ˆ์Šต๋‹ˆ๋‹ค.")

documents = df['content'].astype(str).tolist()
print(f"์ด {len(documents)}๊ฐœ์˜ ๊ณ ์œ ํ•œ ๋‰ด์Šค ๊ธฐ์‚ฌ๋ฅผ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค.")


# --- 2. BERTopic์œผ๋กœ ์ „์ฒด ๋‰ด์Šค ํ† ํ”ฝ ๋ถ„์„ ---
print("\nBERTopic ๋ถ„์„์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค... (์‹œ๊ฐ„์ด ์†Œ์š”๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค)")
# ํ•œ๊ตญ์–ด ๋ฌธ์žฅ ์ž„๋ฒ ๋”ฉ์— ํŠนํ™”๋œ ๋ชจ๋ธ ๋กœ๋“œ
embedding_model = SentenceTransformer('jhgan/ko-sbert-nli')

# BERTopic ๋ชจ๋ธ ์ดˆ๊ธฐํ™” (min_topic_size๋Š” ํ† ํ”ฝ์œผ๋กœ ์ธ์ •ํ•  ์ตœ์†Œ ๋ฌธ์„œ ์ˆ˜)
topic_model = BERTopic(embedding_model=embedding_model,
min_topic_size=5,
verbose=True)

# ๋ชจ๋ธ ํ•™์Šต ๋ฐ ํ† ํ”ฝ ํ• ๋‹น
topics, _ = topic_model.fit_transform(documents)

# <<ํ•ต์‹ฌ ๋กœ์ง>>: ๋ถ„์„๋œ ํ† ํ”ฝ ๊ฒฐ๊ณผ๋ฅผ 'topic_num'์ด๋ผ๋Š” ์ƒˆ ์—ด์— ์ถ”๊ฐ€
df['topic_num'] = topics
print("BERTopic ๋ถ„์„ ๋ฐ ํ† ํ”ฝ ์—ด ์ถ”๊ฐ€ ์™„๋ฃŒ!")


# --- 3. ๊ฐœ๋ณ„ ๋‰ด์Šค ํ‚ค์›Œ๋“œ ์ถ”์ถœ (KeyBERT ์‚ฌ์šฉ) ---
print("\n๊ฐœ๋ณ„ ๋‰ด์Šค์— ๋Œ€ํ•œ ํ‚ค์›Œ๋“œ ์ถ”์ถœ์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค...")
kw_model = KeyBERT(embedding_model)
kiwi = Kiwi()

def extract_keywords_keybert(text, num_keywords=5):
"""Kiwi๋กœ ๋ช…์‚ฌ๋ฅผ ์ถ”์ถœํ•˜๊ณ  KeyBERT๋กœ ํ•ต์‹ฌ ํ‚ค์›Œ๋“œ๋ฅผ ์ฐพ๋Š” ํ•จ์ˆ˜"""
try:
nouns = " ".join([token.form for token in kiwi.analyze(text)[0][0] if token.tag in ['NNG', 'NNP']])
if not nouns:
return ""
keywords = kw_model.extract_keywords(nouns, keyphrase_ngram_range=(1, 1), stop_words=None, top_n=num_keywords)
return ", ".join([f"#{kw[0]}" for kw in keywords])
except Exception as e:
print(f"ํ‚ค์›Œ๋“œ ์ถ”์ถœ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}")
return ""

# 'keywords'๋ผ๋Š” ์ƒˆ ์—ด์— ์ถ”์ถœ๋œ ํ‚ค์›Œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
df['keywords'] = df['content'].apply(extract_keywords_keybert)
print("ํ‚ค์›Œ๋“œ ์ถ”์ถœ ๋ฐ ์—ด ์ถ”๊ฐ€ ์™„๋ฃŒ!")


# --- 4. ์ตœ์ข… ๊ฒฐ๊ณผ ์ €์žฅ ๋ฐ ์š”์•ฝ ์ถœ๋ ฅ ---
output_filename = "news_analysis_results.csv"
df.to_csv(output_filename, index=False, encoding='utf-8-sig')

print(f"\nโœ… ๋ชจ๋“  ๋ถ„์„์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๊ฒฐ๊ณผ๊ฐ€ '{output_filename}' ํŒŒ์ผ์— ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
print("\n--- BERTopic ์ฃผ์š” ํ† ํ”ฝ ์š”์•ฝ ---")
# ํ† ํ”ฝ์˜ ๋Œ€ํ‘œ ๋‹จ์–ด๋“ค๊ณผ ํ•จ๊ป˜ ์ •๋ณด ์ถœ๋ ฅ
topic_infos = topic_model.get_topic_info()
print(topic_infos.head(11))
Loading