File size: 6,635 Bytes
384f5e4
bfd4ab7
ebf358a
f8297ef
0a4103c
7c4d402
febef4d
717fe8c
7916a48
 
 
7c4d402
 
717fe8c
37db576
 
 
 
 
 
 
 
 
 
 
 
c87048f
f8297ef
c87048f
f8297ef
 
0a4103c
 
 
 
 
bfd4ab7
0a4103c
 
 
 
 
 
 
 
 
 
717fe8c
0a4103c
 
 
 
 
8b3a49e
0a4103c
 
 
ebf358a
0a4103c
 
ebf358a
d84d5f9
ebf358a
d10e98d
7c4d402
 
 
0f0bb0d
7c4d402
 
 
c87048f
7c4d402
ebf358a
7c4d402
d10e98d
242b668
5a80960
 
0a4103c
 
 
 
5a80960
 
 
 
 
 
 
 
0a4103c
 
 
 
 
 
 
 
 
ebf358a
8b3a49e
ebf358a
0a4103c
bfd4ab7
 
fd2d980
bfd4ab7
717fe8c
0a4103c
 
8b3a49e
ebf358a
0a4103c
 
 
 
ebf358a
 
384f5e4
ebf358a
0a4103c
 
fd2d980
 
384f5e4
bfd4ab7
0a4103c
bfd4ab7
58522ef
ebf358a
f8297ef
0a4103c
 
f8297ef
c87048f
 
 
 
 
 
 
ebf358a
95eccad
0a4103c
fd2d980
0a4103c
384f5e4
fd2d980
384f5e4
fd2d980
 
 
0a4103c
 
ebf358a
 
0a4103c
 
58522ef
fd2d980
ebf358a
0a4103c
 
fd2d980
ebf358a
fd2d980
ebf358a
 
61b8502
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
import streamlit as st
import requests
import matplotlib.pyplot as plt
import seaborn as sns
from transformers import pipeline
import openai
import os

# Streamlit ํŽ˜์ด์ง€ ์„ค์ •์„ ๊ฐ€์žฅ ๋จผ์ € ํ˜ธ์ถœ
st.set_page_config(page_title="์ •์น˜์  ๊ด€์  ๋ถ„์„", page_icon="๐Ÿ“ฐ", layout="wide")

# OpenAI API ํ‚ค ์„ค์ • (ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ๋ฒ•)
openai.api_key = os.getenv("OPENAI_API_KEY")

# ํ•œ๊ธ€ ํฐํŠธ ์„ค์ • (Streamlit์—์„œ ์ ์šฉํ•˜๊ธฐ ์œ„ํ•ด CSS ์ถ”๊ฐ€)
st.markdown(
    """
    <style>
    body {
        font-family: 'Nanum Gothic', sans-serif;
    }
    </style>
    """,
    unsafe_allow_html=True
)

# matplotlib ํ•œ๊ธ€ ํฐํŠธ ์„ค์ •
import matplotlib
matplotlib.rcParams['font.family'] = 'NanumGothic'  # ํ•œ๊ธ€ ํฐํŠธ๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
matplotlib.rcParams['axes.unicode_minus'] = False  # ๋งˆ์ด๋„ˆ์Šค ๊ธฐํ˜ธ ๊นจ์ง ๋ฐฉ์ง€

# ๋„ค์ด๋ฒ„ ๋‰ด์Šค API๋ฅผ ํ†ตํ•ด ์‹ค์ œ ๋‰ด์Šค ๊ธฐ์‚ฌ ๊ฐ€์ ธ์˜ค๊ธฐ
def fetch_naver_news(query, display=5):
    client_id = "I_8koTJh3R5l4wLurQbG"  # ๋„ค์ด๋ฒ„ ๊ฐœ๋ฐœ์ž ์„ผํ„ฐ์—์„œ ๋ฐœ๊ธ‰๋ฐ›์€ Client ID
    client_secret = "W5oWYlAgur"  # ๋„ค์ด๋ฒ„ ๊ฐœ๋ฐœ์ž ์„ผํ„ฐ์—์„œ ๋ฐœ๊ธ‰๋ฐ›์€ Client Secret

    url = "https://openapi.naver.com/v1/search/news.json"
    headers = {
        "X-Naver-Client-Id": client_id,
        "X-Naver-Client-Secret": client_secret,
    }
    params = {
        "query": query,
        "display": display,
        "start": 1,
        "sort": "date",  # ์ตœ์‹ ์ˆœ์œผ๋กœ ์ •๋ ฌ
    }

    response = requests.get(url, headers=headers, params=params)
    if response.status_code == 200:
        news_data = response.json()
        return news_data['items']  # ๋‰ด์Šค ๊ธฐ์‚ฌ ๋ฆฌ์ŠคํŠธ ๋ฐ˜ํ™˜
    else:
        st.error("๋‰ด์Šค ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.")
        return []

# ์ •์น˜ ์„ฑํ–ฅ ๋ถ„์„ ๋ชจ๋ธ ๋กœ๋“œ
def load_sentiment_model():
    classifier = pipeline("text-classification", model="bucketresearch/politicalBiasBERT")
    return classifier

# GPT-4๋ฅผ ์ด์šฉํ•ด ๋ฐ˜๋Œ€ ๊ด€์  ๊ธฐ์‚ฌ ์ƒ์„ฑ
def generate_article_gpt4(prompt):
    try:
        # GPT-4 ๋ชจ๋ธ์„ ์ด์šฉํ•ด ๋ฐ˜๋Œ€ ๊ด€์  ๊ธฐ์‚ฌ๋ฅผ ์ƒ์„ฑ
        response = openai.ChatCompletion.create(
            model="gpt-4",  # GPT-4 ๋ชจ๋ธ์„ ์‚ฌ์šฉ
            messages=[ 
                {"role": "system", "content": "You are a helpful assistant that generates articles."},
                {"role": "user", "content": prompt}  # ์‚ฌ์šฉ์ž๊ฐ€ ์ œ๊ณตํ•œ ํ”„๋กฌํ”„ํŠธ
            ],
            max_tokens=1024,  # ๊ธ€์ž ์ˆ˜ ์ œํ•œ ํ•ด์ œ (์ตœ๋Œ€ 1024 ํ† ํฐ)
            temperature=0.7  # ์ฐฝ์˜์„ฑ ์ •๋„
        )
        return response['choices'][0]['message']['content']  # GPT์˜ ์‘๋‹ต ํ…์ŠคํŠธ ๋ฐ˜ํ™˜
    except Exception as e:
        return f"Error generating text: {e}"

# ์ •์น˜ ์„ฑํ–ฅ ๋ถ„์„
def analyze_article_sentiment(text, classifier):
    result = classifier(text[:512])  # ๋„ˆ๋ฌด ๊ธด ํ…์ŠคํŠธ๋Š” ์ž˜๋ผ์„œ ๋ถ„์„
    label = result[0]["label"]
    score = result[0]["score"]

    # ๋ชจ๋ธ์—์„œ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ผ๋ฒจ์„ "์ง„๋ณด", "๋ณด์ˆ˜", "์ค‘๋ฆฝ"์œผ๋กœ ๋งคํ•‘
    if label == "LEFT":
        return "์ง„๋ณด", score
    elif label == "RIGHT":
        return "๋ณด์ˆ˜", score
    else:
        return "์ค‘๋ฆฝ", score

# ์ •์น˜์  ๊ด€์  ๋น„๊ต ๋ฐ ๋ฐ˜๋Œ€ ๊ด€์  ์ƒ์„ฑ
def analyze_news_political_viewpoint(query):
    # ๋‰ด์Šค ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
    news_items = fetch_naver_news(query)
    if not news_items:
        return [], {}

    classifier = load_sentiment_model()
    results = []
    sentiment_counts = {"์ง„๋ณด": 0, "๋ณด์ˆ˜": 0, "์ค‘๋ฆฝ": 0}  # ๋งคํ•‘๋œ ๋ผ๋ฒจ์— ๋งž๊ฒŒ ์ดˆ๊ธฐํ™”

    for item in news_items:
        title = item["title"]
        description = item["description"]
        link = item["link"]  # ๋‰ด์Šค ๋งํฌ ๊ฐ€์ ธ์˜ค๊ธฐ
        combined_text = f"{title}. {description}"

        # ๊ธฐ์‚ฌ ์„ฑํ–ฅ ๋ถ„์„
        sentiment, score = analyze_article_sentiment(combined_text, classifier)
        sentiment_counts[sentiment] += 1  # ๋งคํ•‘๋œ ํ‚ค๋กœ ์นด์šดํŠธ ์ฆ๊ฐ€

        # ๋ฐ˜๋Œ€ ๊ด€์  ๊ธฐ์‚ฌ ์ƒ์„ฑ
        opposite_perspective = "๋ณด์ˆ˜์ " if sentiment == "์ง„๋ณด" else "์ง„๋ณด์ "
        prompt = f"{combined_text}๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ {opposite_perspective} ๊ด€์ ์˜ ๊ธฐ์‚ฌ๋ฅผ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”."
        opposite_article = generate_article_gpt4(prompt)

        results.append({
            "์ œ๋ชฉ": title,
            "์›๋ณธ ๊ธฐ์‚ฌ": description,
            "์„ฑํ–ฅ": sentiment,
            "์„ฑํ–ฅ ์ ์ˆ˜": score,
            "๋Œ€์กฐ ๊ด€์  ๊ธฐ์‚ฌ": opposite_article,
            "๋‰ด์Šค ๋งํฌ": link  # ๋งํฌ ์ถ”๊ฐ€
        })

    return results, sentiment_counts

# ์„ฑํ–ฅ ๋ถ„ํฌ ์‹œ๊ฐํ™” (๋ง‰๋Œ€ ๊ทธ๋ž˜ํ”„)
def visualize_sentiment_distribution(sentiment_counts):
    fig, ax = plt.subplots(figsize=(8, 5))
    labels = list(sentiment_counts.keys())
    sizes = list(sentiment_counts.values())

    # ์ƒ‰์ƒ ์„ค์ • (๋ถ€๋“œ๋Ÿฌ์šด ํŒ”๋ ˆํŠธ)
    color_palette = sns.color_palette("pastel")[0:len(sizes)]
    
    ax.bar(labels, sizes, color=color_palette)
    ax.set_xlabel('์„ฑํ–ฅ', fontsize=14)
    ax.set_ylabel('๊ฑด์ˆ˜', fontsize=14)
    ax.set_title('๋‰ด์Šค ์„ฑํ–ฅ ๋ถ„ํฌ', fontsize=16)
    st.pyplot(fig)

# Streamlit ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜
st.title("๐Ÿ“ฐ ์ •์น˜์  ๊ด€์  ๋น„๊ต ๋ถ„์„ ๋„๊ตฌ")
st.markdown("๋‰ด์Šค ๊ธฐ์‚ฌ์˜ ์ •์น˜ ์„ฑํ–ฅ ๋ถ„์„๊ณผ ๋ฐ˜๋Œ€ ๊ด€์  ๊ธฐ์‚ฌ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค.")

# ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ๊ฒ€์ƒ‰์–ด ์ž…๋ ฅ ๋ฐ›๊ธฐ
query = st.text_input("๊ฒ€์ƒ‰ ํ‚ค์›Œ๋“œ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”", value="์ •์น˜")

# ๋ถ„์„ ์‹œ์ž‘ ๋ฒ„ํŠผ
if st.button("๐Ÿ” ๋ถ„์„ ์‹œ์ž‘"):
    with st.spinner("๋ถ„์„ ์ค‘..."):
        analysis_results, sentiment_counts = analyze_news_political_viewpoint(query)

        if analysis_results:
            st.success("๋‰ด์Šค ๋ถ„์„์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
            
            # ์„ฑํ–ฅ ๋ถ„ํฌ ์‹œ๊ฐํ™” (๋ง‰๋Œ€ ๊ทธ๋ž˜ํ”„)
            st.subheader("๐Ÿ“Š ์„ฑํ–ฅ ๋ถ„ํฌ ์‹œ๊ฐํ™”")
            visualize_sentiment_distribution(sentiment_counts)
            
            # ์ƒ์„ธ ๋ถ„์„ ๊ฒฐ๊ณผ ์ถœ๋ ฅ
            st.subheader("๐Ÿ“ ์ƒ์„ธ ๋ถ„์„ ๊ฒฐ๊ณผ")
            for result in analysis_results:
                st.write(f"#### {result['์ œ๋ชฉ']}")
                st.write(f"- **์›๋ณธ ๊ธฐ์‚ฌ**: {result['์›๋ณธ ๊ธฐ์‚ฌ']}")
                st.write(f"- **์„ฑํ–ฅ**: {result['์„ฑํ–ฅ']} (์ ์ˆ˜: {result['์„ฑํ–ฅ ์ ์ˆ˜']:.2f})")
                st.write(f"- **๋Œ€์กฐ ๊ด€์  ๊ธฐ์‚ฌ**: {result['๋Œ€์กฐ ๊ด€์  ๊ธฐ์‚ฌ']}")
                st.write(f"- **๋‰ด์Šค ๋งํฌ**: [๋งํฌ]({result['๋‰ด์Šค ๋งํฌ']})")  # ๋งํฌ ์ถœ๋ ฅ
                st.write("---")
        else:
            st.error("๋ถ„์„๋œ ๋‰ด์Šค ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.")