File size: 11,261 Bytes
ee199e4
 
 
 
 
 
 
 
a41e96c
ae968de
a41e96c
 
ee199e4
a41e96c
 
dd081fe
a41e96c
 
ae968de
ee199e4
 
 
a41e96c
 
ee199e4
 
a41e96c
ee199e4
 
 
 
 
 
 
a41e96c
 
 
 
ae968de
a41e96c
ee199e4
a41e96c
 
 
 
 
 
 
 
 
 
 
 
 
 
ee199e4
a41e96c
 
 
ee199e4
a41e96c
ee199e4
a41e96c
 
 
 
 
 
 
ee199e4
a41e96c
 
dd081fe
 
a41e96c
 
ee199e4
a41e96c
 
 
 
ee199e4
a41e96c
 
ab26d2c
 
a41e96c
ab26d2c
 
a41e96c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ee199e4
a41e96c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ab26d2c
a41e96c
ab26d2c
a41e96c
ee199e4
a41e96c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ab26d2c
 
a41e96c
 
 
 
 
 
 
 
 
 
 
ee199e4
a41e96c
 
ee199e4
a41e96c
ee199e4
a41e96c
 
ee199e4
a41e96c
 
 
 
 
 
 
dd081fe
 
a41e96c
 
 
 
 
 
 
 
ee199e4
a41e96c
 
ee199e4
 
 
 
a41e96c
dd081fe
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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
import gradio as gr
import requests
import pandas as pd
from dotenv import load_dotenv
import os

load_dotenv()

# --- 環境変数と定数の設定 ---
# NewsAPI設定
NEWS_API_URL = os.getenv("NEWS_API_URL", "https://newsapi.org/v2/everything")
NEWS_API_KEY = os.getenv("API_KEY")

# Alpha Vantage API設定
ALPHA_VANTAGE_API_URL = "https://www.alphavantage.co/query"
ALPHA_VANTAGE_API_KEY = os.getenv("ALPHA_VANTAGE_API_KEY") # 環境変数から読み込む

# --- ニュース検索機能 ---
def fetch_news(query="ecolab", sort_by="publishedAt", page_size=10, language="auto"):
    """
    NewsAPIからニュース情報を取得する関数
    """
    if not NEWS_API_KEY:
        return pd.DataFrame(), "❌ NewsAPIのAPIキーが設定されていません。"
    try:
        headers = {
            "X-API-Key": NEWS_API_KEY,
            "User-Agent": "NewsApp/1.0"
        }
        params = {
            "q": query,
            "sortBy": sort_by,
            "pageSize": page_size,
        }
        # 言語設定
        if language in ["en", "jp"]:
             params["language"] = language
        elif language == "auto" and not any('\u3040' <= char <= '\u309F' or '\u30A0' <= char <= '\u30FF' or '\u4E00' <= char <= '\u9FAF' for char in query):
            params["language"] = "en"

        response = requests.get(NEWS_API_URL, headers=headers, params=params)
        response.raise_for_status() # エラーがあれば例外を発生させる

        data = response.json()
        if data["status"] == "ok" and data["totalResults"] > 0:
            articles = data["articles"]
            news_data = [{
                "タイトル": article.get("title"),
                "説明": article.get("description"),
                "ソース": article.get("source", {}).get("name"),
                "公開日": article.get("publishedAt"),
                "URL": article.get("url")
            } for article in articles]
            df = pd.DataFrame(news_data)
            return df, f"✅ {len(articles)}件のニュースを取得しました"
        else:
            return pd.DataFrame(), "❌ ニュースが見つかりませんでした"
    except requests.exceptions.RequestException as e:
        return pd.DataFrame(), f"❌ APIリクエストエラー: {e}"
    except Exception as e:
        return pd.DataFrame(), f"❌ 不明なエラーが発生しました: {e}"

def search_news(query: str, sort_by: str, page_size: int, language: str):
    if not query.strip():
        return pd.DataFrame(), "❌ 検索キーワードを入力してください"
    return fetch_news(query, sort_by, page_size, language)

# --- 暗号資産情報取得機能 ---
def fetch_crypto_data(function, symbol, market):
    """
    Alpha Vantage APIから暗号資産の情報を取得する関数
    """
    if not ALPHA_VANTAGE_API_KEY:
        return pd.DataFrame(), "❌ Alpha VantageのAPIキーが設定されていません。"
    if not symbol.strip() or not market.strip():
        return pd.DataFrame(), "❌ 通貨シンボルと市場を両方入力してください。"

    params = {
        "apikey": ALPHA_VANTAGE_API_KEY,
        "function": function
    }

    # functionに応じてパラメータを動的に設定
    if function == "CURRENCY_EXCHANGE_RATE":
        params["from_currency"] = symbol.strip()
        params["to_currency"] = market.strip()
    else:
        params["symbol"] = symbol.strip()
        params["market"] = market.strip()

    try:
        response = requests.get(ALPHA_VANTAGE_API_URL, params=params)
        response.raise_for_status()
        data = response.json()

        # APIからのエラーメッセージをチェック
        if "Error Message" in data:
            return pd.DataFrame(), f"❌ APIエラー: {data['Error Message']}"
        if "Information" in data:
             return pd.DataFrame(), f"ℹ️ API情報: {data['Information']}"
        if not data:
            return pd.DataFrame(), "❌ APIから有効なデータが返されませんでした。"


        # 取得したデータに応じてDataFrameに変換
        if function == "CURRENCY_EXCHANGE_RATE":
            key = "Realtime Currency Exchange Rate"
            if key in data:
                # データを整形してDataFrameに
                rate_data = {
                    "項目": list(data[key].keys()),
                    "値": list(data[key].values())
                }
                df = pd.DataFrame(rate_data)
                return df, f"✅ {symbol}/{market}のリアルタイム価格を取得しました。"
            else:
                return pd.DataFrame(), "❌ リアルタイム価格のデータを取得できませんでした。"

        else: # 時系列データ (DAILY, WEEKLY, MONTHLY)
            # "Time Series (...)" というキーを探す
            ts_key = next((k for k in data.keys() if k.startswith("Time Series")), None)
            if ts_key and data[ts_key]:
                df = pd.DataFrame.from_dict(data[ts_key], orient='index')
                df.index.name = "日付"
                # カラム名を分かりやすく変更
                df.columns = [col.split('. ')[1] for col in df.columns]
                return df, f"✅ {symbol}/{market}{function.replace('DIGITAL_CURRENCY_', '')}データを取得しました。"
            else:
                return pd.DataFrame(), "❌ 時系列データを取得できませんでした。シンボルや市場が正しいか確認してください。"

    except requests.exceptions.RequestException as e:
        return pd.DataFrame(), f"❌ APIリクエストエラー: {e}"
    except Exception as e:
        return pd.DataFrame(), f"❌ 不明なエラーが発生しました: {e}"


# --- Gradioインターフェース ---
with gr.Blocks(title="📰 総合情報検索アプリ", theme=gr.themes.Soft()) as demo:
    gr.Markdown("# 📰 総合情報検索アプリ")
    gr.Markdown("ニュース検索と暗号資産の価格情報を取得できます。")

    with gr.Tabs():
        # --- ニュース検索タブ ---
        with gr.TabItem("📰 ニュース検索"):
            gr.Markdown(
                """
                **使い方:**
                1. 検索キーワードを入力(日本語・英語両方対応)
                2. 言語とソート方法を選択
                3. 取得件数を選択
                4. 「ニュース検索」ボタンをクリック
                """
            )
            with gr.Row():
                with gr.Column(scale=1):
                    news_query_input = gr.Textbox(
                        label="🔍 検索キーワード",
                        placeholder="例: 東京, トヨタ, AI, technology...",
                        value="Ecolab"
                    )
                    language_dropdown = gr.Dropdown(
                        choices=[("自動検出", "auto"), ("日本語", "jp"), ("英語", "en"), ("すべての言語", "all")],
                        label="🌐 言語", value="auto"
                    )
                    sort_by_dropdown = gr.Dropdown(
                        choices=["publishedAt", "relevancy", "popularity"],
                        label="📊 ソート方法", value="publishedAt"
                    )
                    page_size_slider = gr.Slider(
                        minimum=5, maximum=50, value=10, step=5, label="📄 取得件数"
                    )
                    news_search_btn = gr.Button("🔍 ニュース検索", variant="primary")

                with gr.Column(scale=3):
                    news_status_output = gr.Textbox(label="📋 ステータス", interactive=False)
                    news_results_df = gr.DataFrame(label="📰 ニュース結果", wrap=True, interactive=False)

        # --- 暗号資産検索タブ ---
        with gr.TabItem("🪙 暗号資産検索"):
            gr.Markdown(
                """
                **使い方:**
                1. 取得したいデータの種類を選択
                2. 暗号資産のシンボルを入力 (例: `BTC`, `ETH`)
                3. 取引市場の通貨を入力 (例: `USD`, `EUR`)
                4. 「データ取得」ボタンをクリック
                **注:** `JPY`などの一部市場は、時系列データ(日足・週足・月足)でエラーになる場合があります。その際は`USD`など主要な市場をお試しください。
                """
            )
            with gr.Row():
                with gr.Column(scale=1):
                    crypto_function_dropdown = gr.Dropdown(
                        label="📊 データ種別",
                        choices=[
                            ("日足データ", "DIGITAL_CURRENCY_DAILY"),
                            ("週足データ", "DIGITAL_CURRENCY_WEEKLY"),
                            ("月足データ", "DIGITAL_CURRENCY_MONTHLY"),
                            ("リアルタイム価格", "CURRENCY_EXCHANGE_RATE")
                        ],
                        value="DIGITAL_CURRENCY_DAILY"
                    )
                    crypto_symbol_input = gr.Textbox(
                        label="🪙 通貨シンボル",
                        placeholder="例: BTC",
                        value="BTC"
                    )
                    crypto_market_input = gr.Textbox(
                        label="💴 取引市場",
                        placeholder="例: USD",
                        value="USD"
                    )
                    crypto_search_btn = gr.Button("🔍 データ取得", variant="primary")

                with gr.Column(scale=3):
                    crypto_status_output = gr.Textbox(label="📋 ステータス", interactive=False)
                    crypto_results_df = gr.DataFrame(label="📈 暗号資産データ", wrap=True, interactive=False)


    # --- イベントハンドラー ---
    # ニュース検索
    news_search_btn.click(
        fn=search_news,
        inputs=[news_query_input, sort_by_dropdown, page_size_slider, language_dropdown],
        outputs=[news_results_df, news_status_output]
    )
    news_query_input.submit(
        fn=search_news,
        inputs=[news_query_input, sort_by_dropdown, page_size_slider, language_dropdown],
        outputs=[news_results_df, news_status_output]
    )

    # 暗号資産検索
    crypto_search_btn.click(
        fn=fetch_crypto_data,
        inputs=[crypto_function_dropdown, crypto_symbol_input, crypto_market_input],
        outputs=[crypto_results_df, crypto_status_output]
    )
    # Enterキーでの実行(Textboxのみ)
    for inp in [crypto_symbol_input, crypto_market_input]:
        inp.submit(
            fn=fetch_crypto_data,
            inputs=[crypto_function_dropdown, crypto_symbol_input, crypto_market_input],
            outputs=[crypto_results_df, crypto_status_output]
        )


    # 初期ロード時のデータ表示
    demo.load(
        fn=lambda: search_news("Ecolab", "publishedAt", 10, "auto"),
        outputs=[news_results_df, news_status_output]
    )

# アプリ起動
if __name__ == "__main__":
    demo.launch()