Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
|
@@ -3,7 +3,7 @@ import requests
|
|
| 3 |
import json
|
| 4 |
import os
|
| 5 |
from datetime import datetime, timedelta
|
| 6 |
-
from concurrent.futures import ThreadPoolExecutor
|
| 7 |
from functools import lru_cache
|
| 8 |
from requests.adapters import HTTPAdapter
|
| 9 |
from requests.packages.urllib3.util.retry import Retry
|
|
@@ -45,6 +45,9 @@ def convert_to_seoul_time(timestamp_str):
|
|
| 45 |
return timestamp_str
|
| 46 |
|
| 47 |
def analyze_sentiment_batch(articles, client):
|
|
|
|
|
|
|
|
|
|
| 48 |
try:
|
| 49 |
# ๋ชจ๋ ๊ธฐ์ฌ์ ์ ๋ชฉ๊ณผ ๋ด์ฉ์ ํ๋์ ํ
์คํธ๋ก ๊ฒฐํฉ
|
| 50 |
combined_text = "\n\n".join([
|
|
@@ -52,7 +55,6 @@ def analyze_sentiment_batch(articles, client):
|
|
| 52 |
for article in articles
|
| 53 |
])
|
| 54 |
|
| 55 |
-
# f""" ... """ ํํ๋ก ์ฌ๋ฌ ์ค ๋ฌธ์์ด์ ์ ํํ ์ฌ์ฉ
|
| 56 |
prompt = f"""๋ค์ ๋ด์ค ๋ชจ์์ ๋ํด ์ ๋ฐ์ ์ธ ๊ฐ์ฑ ๋ถ์์ ์ํํ์ธ์:
|
| 57 |
|
| 58 |
๋ด์ค ๋ด์ฉ:
|
|
@@ -96,6 +98,9 @@ def init_db():
|
|
| 96 |
conn.close()
|
| 97 |
|
| 98 |
def save_to_db(keyword, country, results):
|
|
|
|
|
|
|
|
|
|
| 99 |
conn = sqlite3.connect("search_results.db")
|
| 100 |
c = conn.cursor()
|
| 101 |
seoul_tz = pytz.timezone('Asia/Seoul')
|
|
@@ -110,6 +115,9 @@ def save_to_db(keyword, country, results):
|
|
| 110 |
conn.close()
|
| 111 |
|
| 112 |
def load_from_db(keyword, country):
|
|
|
|
|
|
|
|
|
|
| 113 |
conn = sqlite3.connect("search_results.db")
|
| 114 |
c = conn.cursor()
|
| 115 |
c.execute("SELECT results, timestamp FROM searches WHERE keyword=? AND country=? ORDER BY timestamp DESC LIMIT 1",
|
|
@@ -121,6 +129,9 @@ def load_from_db(keyword, country):
|
|
| 121 |
return None, None
|
| 122 |
|
| 123 |
def display_results(articles):
|
|
|
|
|
|
|
|
|
|
| 124 |
output = ""
|
| 125 |
for idx, article in enumerate(articles, 1):
|
| 126 |
output += f"### {idx}. {article['title']}\n"
|
|
@@ -131,6 +142,9 @@ def display_results(articles):
|
|
| 131 |
return output
|
| 132 |
|
| 133 |
def search_company(company):
|
|
|
|
|
|
|
|
|
|
| 134 |
error_message, articles = serphouse_search(company, "United States")
|
| 135 |
if not error_message and articles:
|
| 136 |
save_to_db(company, "United States", articles)
|
|
@@ -138,17 +152,29 @@ def search_company(company):
|
|
| 138 |
return f"{company}์ ๋ํ ๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค."
|
| 139 |
|
| 140 |
def load_company(company):
|
|
|
|
|
|
|
|
|
|
| 141 |
results, timestamp = load_from_db(company, "United States")
|
| 142 |
if results:
|
| 143 |
return f"### {company} ๊ฒ์ ๊ฒฐ๊ณผ\n์ ์ฅ ์๊ฐ: {timestamp}\n\n" + display_results(results)
|
| 144 |
return f"{company}์ ๋ํ ์ ์ฅ๋ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค."
|
| 145 |
|
| 146 |
def show_stats():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
conn = sqlite3.connect("search_results.db")
|
| 148 |
c = conn.cursor()
|
| 149 |
|
| 150 |
output = "## ํ๊ตญ ๊ธฐ์
๋ด์ค ๋ถ์ ๋ฆฌํฌํธ\n\n"
|
| 151 |
|
|
|
|
|
|
|
| 152 |
for company in KOREAN_COMPANIES:
|
| 153 |
c.execute("""
|
| 154 |
SELECT results, timestamp
|
|
@@ -158,36 +184,70 @@ def show_stats():
|
|
| 158 |
LIMIT 1
|
| 159 |
""", (company,))
|
| 160 |
|
| 161 |
-
|
| 162 |
-
if
|
| 163 |
-
results_json, timestamp =
|
| 164 |
articles = json.loads(results_json)
|
| 165 |
seoul_time = convert_to_seoul_time(timestamp)
|
| 166 |
-
|
| 167 |
-
output += f"### {company}\n"
|
| 168 |
-
output += f"- ๋ง์ง๋ง ์
๋ฐ์ดํธ: {seoul_time}\n"
|
| 169 |
-
output += f"- ์ ์ฅ๋ ๊ธฐ์ฌ ์: {len(articles)}๊ฑด\n\n"
|
| 170 |
-
|
| 171 |
-
if articles:
|
| 172 |
-
sentiment_analysis = analyze_sentiment_batch(articles, client)
|
| 173 |
-
output += "#### ๋ด์ค ๊ฐ์ฑ ๋ถ์\n"
|
| 174 |
-
output += f"{sentiment_analysis}\n\n"
|
| 175 |
-
|
| 176 |
-
output += "---\n\n"
|
| 177 |
|
| 178 |
conn.close()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
return output
|
| 180 |
|
|
|
|
|
|
|
| 181 |
def search_all_companies():
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
overall_result = "# [์ ์ฒด ๊ฒ์ ๊ฒฐ๊ณผ]\n\n"
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
return overall_result
|
| 188 |
|
| 189 |
def load_all_companies():
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
overall_result = "# [์ ์ฒด ์ถ๋ ฅ ๊ฒฐ๊ณผ]\n\n"
|
|
|
|
| 191 |
for comp in KOREAN_COMPANIES:
|
| 192 |
overall_result += f"## {comp}\n"
|
| 193 |
overall_result += load_company(comp)
|
|
@@ -195,17 +255,23 @@ def load_all_companies():
|
|
| 195 |
return overall_result
|
| 196 |
|
| 197 |
def full_summary_report():
|
| 198 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
search_result_text = search_all_companies()
|
| 200 |
-
|
|
|
|
| 201 |
load_result_text = load_all_companies()
|
| 202 |
-
|
|
|
|
| 203 |
stats_text = show_stats()
|
| 204 |
|
| 205 |
combined_report = (
|
| 206 |
"# ์ ์ฒด ๋ถ์ ๋ณด๊ณ ์์ฝ\n\n"
|
| 207 |
"์๋ ์์๋ก ์คํ๋์์ต๋๋ค:\n"
|
| 208 |
-
"1. ๋ชจ๋ ์ข
๋ชฉ ๊ฒ์ โ 2. ๋ชจ๋ ์ข
๋ชฉ DB ๊ฒฐ๊ณผ ์ถ๋ ฅ โ 3. ์ ์ฒด ๊ฐ์ฑ ๋ถ์ ํต๊ณ\n\n"
|
| 209 |
f"{search_result_text}\n\n"
|
| 210 |
f"{load_result_text}\n\n"
|
| 211 |
"## [์ ์ฒด ๊ฐ์ฑ ๋ถ์ ํต๊ณ]\n\n"
|
|
@@ -213,6 +279,47 @@ def full_summary_report():
|
|
| 213 |
)
|
| 214 |
return combined_report
|
| 215 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
ACCESS_TOKEN = os.getenv("HF_TOKEN")
|
| 217 |
if not ACCESS_TOKEN:
|
| 218 |
raise ValueError("HF_TOKEN environment variable is not set")
|
|
@@ -224,6 +331,8 @@ client = OpenAI(
|
|
| 224 |
|
| 225 |
API_KEY = os.getenv("SERPHOUSE_API_KEY")
|
| 226 |
|
|
|
|
|
|
|
| 227 |
COUNTRY_LANGUAGES = {
|
| 228 |
"United States": "en",
|
| 229 |
"KOREA": "ko",
|
|
@@ -366,8 +475,12 @@ COUNTRY_LOCATIONS = {
|
|
| 366 |
"Iceland": "Iceland"
|
| 367 |
}
|
| 368 |
|
|
|
|
| 369 |
@lru_cache(maxsize=100)
|
| 370 |
def translate_query(query, country):
|
|
|
|
|
|
|
|
|
|
| 371 |
try:
|
| 372 |
if is_english(query):
|
| 373 |
return query
|
|
@@ -404,6 +517,10 @@ def is_english(text):
|
|
| 404 |
return all(ord(char) < 128 for char in text.replace(' ', '').replace('-', '').replace('_', ''))
|
| 405 |
|
| 406 |
def search_serphouse(query, country, page=1, num_result=10):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 407 |
url = "https://api.serphouse.com/serp/live"
|
| 408 |
|
| 409 |
now = datetime.utcnow()
|
|
@@ -420,7 +537,7 @@ def search_serphouse(query, country, page=1, num_result=10):
|
|
| 420 |
"lang": COUNTRY_LANGUAGES.get(country, "en"),
|
| 421 |
"device": "desktop",
|
| 422 |
"serp_type": "news",
|
| 423 |
-
"page":
|
| 424 |
"num": "100",
|
| 425 |
"date_range": date_range,
|
| 426 |
"sort_by": "date"
|
|
@@ -474,6 +591,10 @@ def search_serphouse(query, country, page=1, num_result=10):
|
|
| 474 |
}
|
| 475 |
|
| 476 |
def format_results_from_raw(response_data):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 477 |
if "error" in response_data:
|
| 478 |
return "Error: " + response_data["error"], []
|
| 479 |
|
|
@@ -481,10 +602,12 @@ def format_results_from_raw(response_data):
|
|
| 481 |
results = response_data["results"]
|
| 482 |
translated_query = response_data["translated_query"]
|
| 483 |
|
|
|
|
| 484 |
news_results = results.get('results', {}).get('results', {}).get('news', [])
|
| 485 |
if not news_results:
|
| 486 |
return "๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค.", []
|
| 487 |
|
|
|
|
| 488 |
korean_domains = [
|
| 489 |
'.kr', 'korea', 'korean', 'yonhap', 'hankyung', 'chosun',
|
| 490 |
'donga', 'joins', 'hani', 'koreatimes', 'koreaherald'
|
|
@@ -502,9 +625,10 @@ def format_results_from_raw(response_data):
|
|
| 502 |
|
| 503 |
is_korean_content = (
|
| 504 |
any(domain in url or domain in channel for domain in korean_domains) or
|
| 505 |
-
any(keyword in title
|
| 506 |
)
|
| 507 |
|
|
|
|
| 508 |
if not is_korean_content:
|
| 509 |
filtered_articles.append({
|
| 510 |
"index": idx,
|
|
@@ -522,9 +646,14 @@ def format_results_from_raw(response_data):
|
|
| 522 |
return f"๊ฒฐ๊ณผ ์ฒ๋ฆฌ ์ค ์ค๋ฅ ๋ฐ์: {str(e)}", []
|
| 523 |
|
| 524 |
def serphouse_search(query, country):
|
|
|
|
|
|
|
|
|
|
| 525 |
response_data = search_serphouse(query, country)
|
| 526 |
return format_results_from_raw(response_data)
|
| 527 |
|
|
|
|
|
|
|
| 528 |
css = """
|
| 529 |
/* ์ ์ญ ์คํ์ผ */
|
| 530 |
footer {visibility: hidden;}
|
|
@@ -700,15 +829,44 @@ footer {visibility: hidden;}
|
|
| 700 |
}
|
| 701 |
"""
|
| 702 |
|
|
|
|
|
|
|
| 703 |
with gr.Blocks(theme="Yntec/HaleyCH_Theme_Orange", css=css, title="NewsAI ์๋น์ค") as iface:
|
| 704 |
init_db()
|
| 705 |
|
| 706 |
with gr.Tabs():
|
| 707 |
-
# ์ฒซ ๋ฒ์งธ ํญ
|
| 708 |
with gr.Tab("Earnbot"):
|
| 709 |
gr.Markdown("## EarnBot: ๊ธ๋ก๋ฒ ๋น
ํ
ํฌ ๊ธฐ์
๋ฐ ํฌ์ ์ ๋ง AI ์๋ ๋ถ์")
|
| 710 |
-
gr.Markdown(" '์ ์ฒด ๋ถ์ ๋ณด๊ณ ์์ฝ' ํด๋ฆญ์ ์ ์ฒด ์๋ ๋ณด๊ณ ์์ฑ
|
| 711 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 712 |
with gr.Row():
|
| 713 |
full_report_btn = gr.Button("์ ์ฒด ๋ถ์ ๋ณด๊ณ ์์ฝ", variant="primary")
|
| 714 |
full_report_display = gr.Markdown()
|
|
@@ -718,6 +876,7 @@ with gr.Blocks(theme="Yntec/HaleyCH_Theme_Orange", css=css, title="NewsAI ์๋น
|
|
| 718 |
outputs=full_report_display
|
| 719 |
)
|
| 720 |
|
|
|
|
| 721 |
with gr.Column():
|
| 722 |
for i in range(0, len(KOREAN_COMPANIES), 2):
|
| 723 |
with gr.Row():
|
|
|
|
| 3 |
import json
|
| 4 |
import os
|
| 5 |
from datetime import datetime, timedelta
|
| 6 |
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
| 7 |
from functools import lru_cache
|
| 8 |
from requests.adapters import HTTPAdapter
|
| 9 |
from requests.packages.urllib3.util.retry import Retry
|
|
|
|
| 45 |
return timestamp_str
|
| 46 |
|
| 47 |
def analyze_sentiment_batch(articles, client):
|
| 48 |
+
"""
|
| 49 |
+
OpenAI API๋ฅผ ํตํด ๋ด์ค ๊ธฐ์ฌ๋ค์ ์ข
ํฉ ๊ฐ์ฑ ๋ถ์์ ์ํ
|
| 50 |
+
"""
|
| 51 |
try:
|
| 52 |
# ๋ชจ๋ ๊ธฐ์ฌ์ ์ ๋ชฉ๊ณผ ๋ด์ฉ์ ํ๋์ ํ
์คํธ๋ก ๊ฒฐํฉ
|
| 53 |
combined_text = "\n\n".join([
|
|
|
|
| 55 |
for article in articles
|
| 56 |
])
|
| 57 |
|
|
|
|
| 58 |
prompt = f"""๋ค์ ๋ด์ค ๋ชจ์์ ๋ํด ์ ๋ฐ์ ์ธ ๊ฐ์ฑ ๋ถ์์ ์ํํ์ธ์:
|
| 59 |
|
| 60 |
๋ด์ค ๋ด์ฉ:
|
|
|
|
| 98 |
conn.close()
|
| 99 |
|
| 100 |
def save_to_db(keyword, country, results):
|
| 101 |
+
"""
|
| 102 |
+
ํน์ (keyword, country) ์กฐํฉ์ ๋ํ ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ DB์ ์ ์ฅ
|
| 103 |
+
"""
|
| 104 |
conn = sqlite3.connect("search_results.db")
|
| 105 |
c = conn.cursor()
|
| 106 |
seoul_tz = pytz.timezone('Asia/Seoul')
|
|
|
|
| 115 |
conn.close()
|
| 116 |
|
| 117 |
def load_from_db(keyword, country):
|
| 118 |
+
"""
|
| 119 |
+
ํน์ (keyword, country) ์กฐํฉ์ ๋ํ ๊ฐ์ฅ ์ต๊ทผ ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ DB์์ ๋ถ๋ฌ์ค๊ธฐ
|
| 120 |
+
"""
|
| 121 |
conn = sqlite3.connect("search_results.db")
|
| 122 |
c = conn.cursor()
|
| 123 |
c.execute("SELECT results, timestamp FROM searches WHERE keyword=? AND country=? ORDER BY timestamp DESC LIMIT 1",
|
|
|
|
| 129 |
return None, None
|
| 130 |
|
| 131 |
def display_results(articles):
|
| 132 |
+
"""
|
| 133 |
+
๋ด์ค ๊ธฐ์ฌ ๋ชฉ๋ก์ Markdown ๋ฌธ์์ด๋ก ๋ณํํ์ฌ ๋ฐํ
|
| 134 |
+
"""
|
| 135 |
output = ""
|
| 136 |
for idx, article in enumerate(articles, 1):
|
| 137 |
output += f"### {idx}. {article['title']}\n"
|
|
|
|
| 142 |
return output
|
| 143 |
|
| 144 |
def search_company(company):
|
| 145 |
+
"""
|
| 146 |
+
๋จ์ผ ๊ธฐ์
(๋๋ ํค์๋)์ ๋ํด ๋ฏธ๊ตญ ๋ด์ค ๊ฒ์, DB ์ ์ฅ ํ ๊ฒฐ๊ณผ Markdown ๋ฐํ
|
| 147 |
+
"""
|
| 148 |
error_message, articles = serphouse_search(company, "United States")
|
| 149 |
if not error_message and articles:
|
| 150 |
save_to_db(company, "United States", articles)
|
|
|
|
| 152 |
return f"{company}์ ๋ํ ๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค."
|
| 153 |
|
| 154 |
def load_company(company):
|
| 155 |
+
"""
|
| 156 |
+
DB์์ ๋จ์ผ ๊ธฐ์
(๋๋ ํค์๋)์ ๋ฏธ๊ตญ ๋ด์ค ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ๋ถ๋ฌ์ Markdown ๋ฐํ
|
| 157 |
+
"""
|
| 158 |
results, timestamp = load_from_db(company, "United States")
|
| 159 |
if results:
|
| 160 |
return f"### {company} ๊ฒ์ ๊ฒฐ๊ณผ\n์ ์ฅ ์๊ฐ: {timestamp}\n\n" + display_results(results)
|
| 161 |
return f"{company}์ ๋ํ ์ ์ฅ๋ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค."
|
| 162 |
|
| 163 |
def show_stats():
|
| 164 |
+
"""
|
| 165 |
+
KOREAN_COMPANIES ๋ชฉ๋ก ๋ด ๋ชจ๋ ๊ธฐ์
์ ๋ํด:
|
| 166 |
+
- ๊ฐ์ฅ ์ต๊ทผ DB ์ ์ฅ ์ผ์
|
| 167 |
+
- ๊ธฐ์ฌ ์
|
| 168 |
+
- ๊ฐ์ฑ ๋ถ์ ๊ฒฐ๊ณผ
|
| 169 |
+
๋ฅผ ์์ฐจ(๋๋ ๋ณ๋ ฌ)๋ก ์กฐํํ์ฌ ๋ณด๊ณ ์ ํํ๋ก ๋ฐํ
|
| 170 |
+
"""
|
| 171 |
conn = sqlite3.connect("search_results.db")
|
| 172 |
c = conn.cursor()
|
| 173 |
|
| 174 |
output = "## ํ๊ตญ ๊ธฐ์
๋ด์ค ๋ถ์ ๋ฆฌํฌํธ\n\n"
|
| 175 |
|
| 176 |
+
# ๋ชจ๋ ๊ธฐ์
์ ๋ํด DB์์ ์ฝ์ด์ฌ (company, timestamp, articles) ๋ชฉ๋ก ์์ง
|
| 177 |
+
data_list = []
|
| 178 |
for company in KOREAN_COMPANIES:
|
| 179 |
c.execute("""
|
| 180 |
SELECT results, timestamp
|
|
|
|
| 184 |
LIMIT 1
|
| 185 |
""", (company,))
|
| 186 |
|
| 187 |
+
row = c.fetchone()
|
| 188 |
+
if row:
|
| 189 |
+
results_json, timestamp = row
|
| 190 |
articles = json.loads(results_json)
|
| 191 |
seoul_time = convert_to_seoul_time(timestamp)
|
| 192 |
+
data_list.append((company, seoul_time, articles))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
|
| 194 |
conn.close()
|
| 195 |
+
|
| 196 |
+
# (์ต์
) ๊ฐ ๊ธฐ์
๊ฐ์ฑ ๋ถ์์ ๋ณ๋ ฌ ์ฒ๋ฆฌ
|
| 197 |
+
def analyze_data(item):
|
| 198 |
+
comp, tstamp, arts = item
|
| 199 |
+
sentiment = ""
|
| 200 |
+
if arts:
|
| 201 |
+
sentiment = analyze_sentiment_batch(arts, client)
|
| 202 |
+
return (comp, tstamp, len(arts), sentiment)
|
| 203 |
+
|
| 204 |
+
# ThreadPoolExecutor๋ก ๋ณ๋ ฌ ๊ฐ์ฑ ๋ถ์
|
| 205 |
+
results_list = []
|
| 206 |
+
with ThreadPoolExecutor(max_workers=5) as executor:
|
| 207 |
+
futures = [executor.submit(analyze_data, dl) for dl in data_list]
|
| 208 |
+
for future in as_completed(futures):
|
| 209 |
+
results_list.append(future.result())
|
| 210 |
+
|
| 211 |
+
# ๊ฒฐ๊ณผ ์ ๋ ฌ(์ํ๋ ์์๋๋ก) - ์ฌ๊ธฐ์๋ ๊ธฐ์
๋ช
๊ธฐ์ค or ๊ทธ๋ฅ ์์ ์์
|
| 212 |
+
for comp, tstamp, count, sentiment in results_list:
|
| 213 |
+
output += f"### {comp}\n"
|
| 214 |
+
output += f"- ๋ง์ง๋ง ์
๋ฐ์ดํธ: {tstamp}\n"
|
| 215 |
+
output += f"- ์ ์ฅ๋ ๊ธฐ์ฌ ์: {count}๊ฑด\n\n"
|
| 216 |
+
if sentiment:
|
| 217 |
+
output += "#### ๋ด์ค ๊ฐ์ฑ ๋ถ์\n"
|
| 218 |
+
output += f"{sentiment}\n\n"
|
| 219 |
+
output += "---\n\n"
|
| 220 |
+
|
| 221 |
return output
|
| 222 |
|
| 223 |
+
|
| 224 |
+
### (1) ์ ์ฒด ๊ฒ์: ๋ฉํฐ์ค๋ ๋ ์ ์ฉ
|
| 225 |
def search_all_companies():
|
| 226 |
+
"""
|
| 227 |
+
KOREAN_COMPANIES ๋ฆฌ์คํธ ๋ด ๋ชจ๋ ๊ธฐ์
์ ๋ํด,
|
| 228 |
+
๊ฒ์์ ๋ณ๋ ฌ(์ฐ๋ ๋)๋ก ์ํ ํ ๊ฒฐ๊ณผ๋ฅผ ํฉ์ณ Markdown ํํ๋ก ๋ฐํ
|
| 229 |
+
"""
|
| 230 |
overall_result = "# [์ ์ฒด ๊ฒ์ ๊ฒฐ๊ณผ]\n\n"
|
| 231 |
+
|
| 232 |
+
def do_search(comp):
|
| 233 |
+
return comp, search_company(comp)
|
| 234 |
+
|
| 235 |
+
with ThreadPoolExecutor(max_workers=5) as executor:
|
| 236 |
+
futures = [executor.submit(do_search, c) for c in KOREAN_COMPANIES]
|
| 237 |
+
for future in as_completed(futures):
|
| 238 |
+
comp, res_text = future.result()
|
| 239 |
+
overall_result += f"## {comp}\n"
|
| 240 |
+
overall_result += res_text + "\n\n"
|
| 241 |
+
|
| 242 |
return overall_result
|
| 243 |
|
| 244 |
def load_all_companies():
|
| 245 |
+
"""
|
| 246 |
+
KOREAN_COMPANIES ๋ฆฌ์คํธ ๋ด ๋ชจ๋ ๊ธฐ์
์ ๋ํด,
|
| 247 |
+
DB์์ ๋ถ๋ฌ์จ ๊ฒฐ๊ณผ๋ฅผ ์์ฐจ(๋๋ ๋ณ๋ ฌ)๋ก ํฉ์ณ์ Markdown ํํ๋ก ๋ฐํ
|
| 248 |
+
"""
|
| 249 |
overall_result = "# [์ ์ฒด ์ถ๋ ฅ ๊ฒฐ๊ณผ]\n\n"
|
| 250 |
+
|
| 251 |
for comp in KOREAN_COMPANIES:
|
| 252 |
overall_result += f"## {comp}\n"
|
| 253 |
overall_result += load_company(comp)
|
|
|
|
| 255 |
return overall_result
|
| 256 |
|
| 257 |
def full_summary_report():
|
| 258 |
+
"""
|
| 259 |
+
(1) ๋ชจ๋ ๊ธฐ์
๊ฒ์ -> (2) DB์์ ๋ชจ๋ ๊ธฐ์
๋ถ๋ฌ์ค๊ธฐ -> (3) ๊ฐ์ฑ ๋ถ์ ํต๊ณ
|
| 260 |
+
์์๋๋ก ์คํํ์ฌ, ์ ์ฒด ๋ฆฌํฌํธ๋ฅผ ํฉ์ณ ๋ฐํ
|
| 261 |
+
"""
|
| 262 |
+
# 1) ์ ์ฒด ๊ฒ์(๋ณ๋ ฌ)
|
| 263 |
search_result_text = search_all_companies()
|
| 264 |
+
|
| 265 |
+
# 2) ์ ์ฒด ์ถ๋ ฅ(์์ฐจ)
|
| 266 |
load_result_text = load_all_companies()
|
| 267 |
+
|
| 268 |
+
# 3) ์ ์ฒด ํต๊ณ(๊ฐ์ฑ ๋ถ์)
|
| 269 |
stats_text = show_stats()
|
| 270 |
|
| 271 |
combined_report = (
|
| 272 |
"# ์ ์ฒด ๋ถ์ ๋ณด๊ณ ์์ฝ\n\n"
|
| 273 |
"์๋ ์์๋ก ์คํ๋์์ต๋๋ค:\n"
|
| 274 |
+
"1. ๋ชจ๋ ์ข
๋ชฉ ๊ฒ์(๋ณ๋ ฌ) โ 2. ๋ชจ๋ ์ข
๋ชฉ DB ๊ฒฐ๊ณผ ์ถ๋ ฅ โ 3. ์ ์ฒด ๊ฐ์ฑ ๋ถ์ ํต๊ณ\n\n"
|
| 275 |
f"{search_result_text}\n\n"
|
| 276 |
f"{load_result_text}\n\n"
|
| 277 |
"## [์ ์ฒด ๊ฐ์ฑ ๋ถ์ ํต๊ณ]\n\n"
|
|
|
|
| 279 |
)
|
| 280 |
return combined_report
|
| 281 |
|
| 282 |
+
|
| 283 |
+
### (2) ์ฌ์ฉ์ ์์ ๊ฒ์ + ๊ตญ๊ฐ ์ ํ ๊ธฐ๋ฅ
|
| 284 |
+
def search_custom(query, country):
|
| 285 |
+
"""
|
| 286 |
+
์ฌ์ฉ์๊ฐ ์
๋ ฅํ (query, country)๋ฅผ ๋์์ผ๋ก
|
| 287 |
+
- ๊ฒ์ (API ์์ฒญ)
|
| 288 |
+
- DB ์ ์ฅ
|
| 289 |
+
- DB ๋ก๋ ํ ๊ฐ์ฑ ๋ถ์
|
| 290 |
+
- ์ต์ข
๊ฒฐ๊ณผ๋ฅผ Markdown ํํ๋ก ๋ฐํ
|
| 291 |
+
"""
|
| 292 |
+
# 1) ๊ฒ์
|
| 293 |
+
error_message, articles = serphouse_search(query, country)
|
| 294 |
+
if error_message:
|
| 295 |
+
return f"์ค๋ฅ ๋ฐ์: {error_message}"
|
| 296 |
+
if not articles:
|
| 297 |
+
return "๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค."
|
| 298 |
+
|
| 299 |
+
# 2) DB ์ ์ฅ
|
| 300 |
+
save_to_db(query, country, articles)
|
| 301 |
+
|
| 302 |
+
# 3) DB์์ ๋ค์ ๋ถ๋ฌ์ค๊ธฐ
|
| 303 |
+
results, timestamp = load_from_db(query, country)
|
| 304 |
+
if not results:
|
| 305 |
+
return f"DB ๋ก๋ ์คํจ: ์ ์ฅ๋ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค."
|
| 306 |
+
|
| 307 |
+
# 4) ๊ฐ์ฑ ๋ถ์
|
| 308 |
+
sentiment_analysis = analyze_sentiment_batch(results, client)
|
| 309 |
+
|
| 310 |
+
# 5) ์ต์ข
๋ฆฌํฌํธ(๊ธฐ์ฌ ๋ชฉ๋ก + ๊ฐ์ฑ ๋ถ์)
|
| 311 |
+
output = f"## [์ฌ์ฉ์ ์์ ๊ฒ์ ๊ฒฐ๊ณผ]\n\n"
|
| 312 |
+
output += f"**ํค์๋**: {query}\n\n"
|
| 313 |
+
output += f"**๊ตญ๊ฐ**: {country}\n\n"
|
| 314 |
+
output += f"**์ ์ฅ ์๊ฐ**: {timestamp}\n\n"
|
| 315 |
+
output += display_results(results)
|
| 316 |
+
|
| 317 |
+
output += "### ๋ด์ค ๊ฐ์ฑ ๋ถ์\n"
|
| 318 |
+
output += f"{sentiment_analysis}\n"
|
| 319 |
+
return output
|
| 320 |
+
|
| 321 |
+
|
| 322 |
+
### (ํ์) API ์ธ์ฆ
|
| 323 |
ACCESS_TOKEN = os.getenv("HF_TOKEN")
|
| 324 |
if not ACCESS_TOKEN:
|
| 325 |
raise ValueError("HF_TOKEN environment variable is not set")
|
|
|
|
| 331 |
|
| 332 |
API_KEY = os.getenv("SERPHOUSE_API_KEY")
|
| 333 |
|
| 334 |
+
|
| 335 |
+
### ๊ตญ๊ฐ๋ณ ์ค์
|
| 336 |
COUNTRY_LANGUAGES = {
|
| 337 |
"United States": "en",
|
| 338 |
"KOREA": "ko",
|
|
|
|
| 475 |
"Iceland": "Iceland"
|
| 476 |
}
|
| 477 |
|
| 478 |
+
|
| 479 |
@lru_cache(maxsize=100)
|
| 480 |
def translate_query(query, country):
|
| 481 |
+
"""
|
| 482 |
+
Google Translation API(๋น๊ณต์) ์ฌ์ฉํ์ฌ ๊ฒ์์ด๋ฅผ ํด๋น ๊ตญ๊ฐ ์ธ์ด๋ก ๋ฒ์ญ
|
| 483 |
+
"""
|
| 484 |
try:
|
| 485 |
if is_english(query):
|
| 486 |
return query
|
|
|
|
| 517 |
return all(ord(char) < 128 for char in text.replace(' ', '').replace('-', '').replace('_', ''))
|
| 518 |
|
| 519 |
def search_serphouse(query, country, page=1, num_result=10):
|
| 520 |
+
"""
|
| 521 |
+
SerpHouse API์ ์ค์๊ฐ ๊ฒ์ ์์ฒญ์ ๋ณด๋ด์ด,
|
| 522 |
+
'๋ด์ค' ํญ (sort_by=date)์์ ํด๋น query์ ๋ํ ๊ธฐ์ฌ ๋ชฉ๋ก์ ๊ฐ์ ธ์จ๋ค.
|
| 523 |
+
"""
|
| 524 |
url = "https://api.serphouse.com/serp/live"
|
| 525 |
|
| 526 |
now = datetime.utcnow()
|
|
|
|
| 537 |
"lang": COUNTRY_LANGUAGES.get(country, "en"),
|
| 538 |
"device": "desktop",
|
| 539 |
"serp_type": "news",
|
| 540 |
+
"page": str(page),
|
| 541 |
"num": "100",
|
| 542 |
"date_range": date_range,
|
| 543 |
"sort_by": "date"
|
|
|
|
| 591 |
}
|
| 592 |
|
| 593 |
def format_results_from_raw(response_data):
|
| 594 |
+
"""
|
| 595 |
+
SerpHouse API์ ์๋ต ๋ฐ์ดํฐ๋ฅผ ๊ฐ๊ณตํ์ฌ,
|
| 596 |
+
(์๋ฌ๋ฉ์์ง, ๊ธฐ์ฌ๋ฆฌ์คํธ) ํํ๋ก ๋ฐํ.
|
| 597 |
+
"""
|
| 598 |
if "error" in response_data:
|
| 599 |
return "Error: " + response_data["error"], []
|
| 600 |
|
|
|
|
| 602 |
results = response_data["results"]
|
| 603 |
translated_query = response_data["translated_query"]
|
| 604 |
|
| 605 |
+
# ์ค์ ๋ด์ค ๊ฒฐ๊ณผ
|
| 606 |
news_results = results.get('results', {}).get('results', {}).get('news', [])
|
| 607 |
if not news_results:
|
| 608 |
return "๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค.", []
|
| 609 |
|
| 610 |
+
# ํ๊ตญ ๋๋ฉ์ธ ๋ฐ ํ๊ตญ ๊ด๋ จ ํค์๋ ํฌํจ ๊ธฐ์ฌ ์ ์ธ
|
| 611 |
korean_domains = [
|
| 612 |
'.kr', 'korea', 'korean', 'yonhap', 'hankyung', 'chosun',
|
| 613 |
'donga', 'joins', 'hani', 'koreatimes', 'koreaherald'
|
|
|
|
| 625 |
|
| 626 |
is_korean_content = (
|
| 627 |
any(domain in url or domain in channel for domain in korean_domains) or
|
| 628 |
+
any(keyword in title for keyword in korean_keywords)
|
| 629 |
)
|
| 630 |
|
| 631 |
+
# ํ๊ตญ์ด ๋ด์ค(๋๋ ํ๊ตญ ๋๋ฉ์ธ) ์ ์ธ
|
| 632 |
if not is_korean_content:
|
| 633 |
filtered_articles.append({
|
| 634 |
"index": idx,
|
|
|
|
| 646 |
return f"๊ฒฐ๊ณผ ์ฒ๋ฆฌ ์ค ์ค๋ฅ ๋ฐ์: {str(e)}", []
|
| 647 |
|
| 648 |
def serphouse_search(query, country):
|
| 649 |
+
"""
|
| 650 |
+
๊ฒ์ ๋ฐ ๊ฒฐ๊ณผ ํฌ๋งคํ
๊น์ง ์ผ๊ด ์ฒ๋ฆฌ
|
| 651 |
+
"""
|
| 652 |
response_data = search_serphouse(query, country)
|
| 653 |
return format_results_from_raw(response_data)
|
| 654 |
|
| 655 |
+
|
| 656 |
+
# CSS (UI ์ปค์คํฐ๋ง์ด์ง)
|
| 657 |
css = """
|
| 658 |
/* ์ ์ญ ์คํ์ผ */
|
| 659 |
footer {visibility: hidden;}
|
|
|
|
| 829 |
}
|
| 830 |
"""
|
| 831 |
|
| 832 |
+
import gradio as gr
|
| 833 |
+
|
| 834 |
with gr.Blocks(theme="Yntec/HaleyCH_Theme_Orange", css=css, title="NewsAI ์๋น์ค") as iface:
|
| 835 |
init_db()
|
| 836 |
|
| 837 |
with gr.Tabs():
|
| 838 |
+
# ์ฒซ ๋ฒ์งธ ํญ
|
| 839 |
with gr.Tab("Earnbot"):
|
| 840 |
gr.Markdown("## EarnBot: ๊ธ๋ก๋ฒ ๋น
ํ
ํฌ ๊ธฐ์
๋ฐ ํฌ์ ์ ๋ง AI ์๋ ๋ถ์")
|
| 841 |
+
gr.Markdown(" * '์ ์ฒด ๋ถ์ ๋ณด๊ณ ์์ฝ' ํด๋ฆญ ์ ์ ์ฒด ์๋ ๋ณด๊ณ ์์ฑ.\n * ์๋ ๊ฐ๋ณ ์ข
๋ชฉ์ '๊ฒ์(DB ์๋ ์ ์ฅ)'๊ณผ '์ถ๋ ฅ(DB ์๋ ํธ์ถ)'๋ ๊ฐ๋ฅ.\n * ์ถ๊ฐ๋ก, ์ํ๋ ์์ ํค์๋ ๋ฐ ๊ตญ๊ฐ๋ก ๊ฒ์/๋ถ์ํ ์๋ ์์ต๋๋ค.")
|
| 842 |
|
| 843 |
+
# (2) ์ฌ์ฉ์ ์์ ๊ฒ์ ์น์
|
| 844 |
+
with gr.Group():
|
| 845 |
+
gr.Markdown("### ์ฌ์ฉ์ ์์ ๊ฒ์")
|
| 846 |
+
with gr.Row():
|
| 847 |
+
with gr.Column():
|
| 848 |
+
user_input = gr.Textbox(
|
| 849 |
+
label="๊ฒ์์ด ์
๋ ฅ",
|
| 850 |
+
placeholder="์) Apple, Samsung ๋ฑ ์์ ๋กญ๊ฒ"
|
| 851 |
+
)
|
| 852 |
+
with gr.Column():
|
| 853 |
+
country_selection = gr.Dropdown(
|
| 854 |
+
choices=list(COUNTRY_LOCATIONS.keys()),
|
| 855 |
+
value="United States",
|
| 856 |
+
label="๊ตญ๊ฐ ์ ํ"
|
| 857 |
+
)
|
| 858 |
+
with gr.Column():
|
| 859 |
+
custom_search_btn = gr.Button("์คํ", variant="primary")
|
| 860 |
+
|
| 861 |
+
custom_search_output = gr.Markdown()
|
| 862 |
+
|
| 863 |
+
custom_search_btn.click(
|
| 864 |
+
fn=search_custom,
|
| 865 |
+
inputs=[user_input, country_selection],
|
| 866 |
+
outputs=custom_search_output
|
| 867 |
+
)
|
| 868 |
+
|
| 869 |
+
# ์ ์ฒด ๋ถ์ ๋ณด๊ณ ์์ฝ ๋ฒํผ
|
| 870 |
with gr.Row():
|
| 871 |
full_report_btn = gr.Button("์ ์ฒด ๋ถ์ ๋ณด๊ณ ์์ฝ", variant="primary")
|
| 872 |
full_report_display = gr.Markdown()
|
|
|
|
| 876 |
outputs=full_report_display
|
| 877 |
)
|
| 878 |
|
| 879 |
+
# ๊ธฐ์กด ๊ฐ๋ณ ๊ธฐ์
๊ฒ์/์ถ๋ ฅ ์์ญ
|
| 880 |
with gr.Column():
|
| 881 |
for i in range(0, len(KOREAN_COMPANIES), 2):
|
| 882 |
with gr.Row():
|