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()