| import streamlit as st |
| import requests |
| from requests.adapters import HTTPAdapter |
| from urllib3.util.retry import Retry |
| from datetime import datetime, timedelta |
| import pandas as pd |
| import traceback |
| import sys |
| import logging |
| import os |
| import importlib.util |
| from bs4 import BeautifulSoup |
| import random |
| import time |
| import concurrent.futures |
| import re |
| import json |
| from dateutil import parser |
| import ssl |
| from gnews import GNews |
| from typing import Any, Sequence, cast |
|
|
| |
| def google_search(query, num_results=10, lang='tr'): |
| """ |
| Google araması yapmak için basit bir fonksiyon. |
| Eğer google-search-python çalışmazsa, bu basit aramaları kullanacağız. |
| """ |
| try: |
| |
| |
| logger.warning( |
| "google_search() devre dışı: gerçek arama sağlayıcısı yapılandırılmadı. " |
| "Dummy sonuç dönülmüyor." |
| ) |
| return [] |
| except Exception as e: |
| logging.error(f"Google arama hatası: {str(e)}") |
| return [] |
|
|
|
|
| def _get_newsapi_key(): |
| """NewsAPI anahtarını ortam değişkenlerinden veya Streamlit secrets'tan okur.""" |
| key = os.environ.get("NEWS_API_KEY") or os.environ.get("NEWSAPI_KEY") |
| if key: |
| return key |
| try: |
| return st.secrets.get("NEWS_API_KEY") or st.secrets.get("NEWSAPI_KEY") |
| except Exception: |
| return "" |
|
|
| |
| def fetch_news_content(url, log_container=None): |
| """ |
| Haber içeriğini çeken fonksiyon. Bu fonksiyon ui/improved_news_tab.py içerisindeki |
| aynı isimli fonksiyonu çağırır ve farklı modüllerden erişim sağlar. |
| |
| Args: |
| url (str): Haber URL'si |
| log_container: Loglama için container (opsiyonel) |
| |
| Returns: |
| dict: İçerik bilgilerini içeren sözlük ya da None |
| """ |
| try: |
| |
| import os |
| import sys |
| import importlib.util |
| |
| |
| current_dir = os.path.dirname(os.path.abspath(__file__)) |
| parent_dir = os.path.dirname(current_dir) |
| |
| |
| module_path = os.path.join(parent_dir, 'ui', 'improved_news_tab.py') |
| |
| |
| logger.info(f"improved_news_tab.py modülü import ediliyor: {module_path}") |
| |
| |
| if not os.path.exists(module_path): |
| logger.error(f"Modül dosyası bulunamadı: {module_path}") |
| return None |
| |
| |
| spec = importlib.util.spec_from_file_location("improved_news_tab", module_path) |
| if spec is None or spec.loader is None: |
| logger.error(f"Modül spec/loader oluşturulamadı: {module_path}") |
| return None |
|
|
| news_tab = importlib.util.module_from_spec(spec) |
| |
| if parent_dir not in sys.path: |
| sys.path.append(parent_dir) |
| |
| spec.loader.exec_module(news_tab) |
| |
| |
| if hasattr(news_tab, 'fetch_news_content'): |
| logger.info(f"fetch_news_content fonksiyonu başarıyla import edildi ve çağrılıyor: {url}") |
| return news_tab.fetch_news_content(url, log_container) |
| else: |
| logger.error("fetch_news_content fonksiyonu improved_news_tab modülünde bulunamadı!") |
| return None |
| |
| except Exception as e: |
| logger.error(f"fetch_news_content fonksiyonu çağrılırken hata: {e}") |
| logger.error(traceback.format_exc()) |
| return None |
|
|
| |
| import pytz |
|
|
| |
| try: |
| from newspaper import Article, Config, ArticleException |
| NEWSPAPER_AVAILABLE = True |
| except ImportError: |
| NEWSPAPER_AVAILABLE = False |
| ArticleException = Exception |
| |
| class Config: |
| def __init__(self, *args: Any, **kwargs: Any): |
| raise ImportError("newspaper3k not available") |
|
|
| class Article: |
| def __init__(self, *args: Any, **kwargs: Any): |
| raise ImportError("newspaper3k not available") |
|
|
| logger = logging.getLogger(__name__) |
| logger.warning("newspaper3k kütüphanesi bulunamadı. Haber içeriği çekme özelliği kısıtlı olacak.") |
|
|
| |
| logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') |
| logger = logging.getLogger(__name__) |
|
|
| |
| try: |
| current_dir = os.path.dirname(os.path.abspath(__file__)) |
| parent_dir = os.path.dirname(current_dir) |
| utils_dir = os.path.join(parent_dir, 'utils') |
| if parent_dir not in sys.path: |
| sys.path.append(parent_dir) |
| logger.info(f"Proje kök dizini sys.path'e eklendi: {parent_dir}") |
| except Exception as path_e: |
| logger.error(f"sys.path ayarlanırken hata: {path_e}") |
| st.warning(f"sys.path ayarlanırken hata: {path_e}. Modül importları başarısız olabilir.") |
|
|
| |
| def log_info(message, visible=False): |
| """Bilgi mesajı göster, visible=False durumunda sadece debug modunda göster""" |
| if visible: |
| st.info(message) |
| else: |
| st.write(f"<div style='display:none'>{message}</div>", unsafe_allow_html=True) |
| logger.info(message) |
|
|
| def log_success(message, visible=False): |
| """Başarı mesajı göster""" |
| if visible: |
| st.success(message) |
| logger.info(f"BAŞARILI: {message}") |
|
|
| def log_warning(message, visible=True): |
| """Uyarı mesajı göster""" |
| if visible: |
| st.warning(message) |
| logger.warning(message) |
|
|
| def log_error(message, exception=None, visible=False): |
| """Hata mesajı göster""" |
| if visible: |
| st.error(message) |
| logger.error(message) |
| if exception: |
| logger.error(f"HATA DETAY: {str(exception)}") |
| logger.error(traceback.format_exc()) |
|
|
| def log_progress(message, is_warning=False, is_error=False, icon=None, visible=False, progress_container=None): |
| """Logging fonksiyonu - container'a veya doğrudan UI'ye gönderilebilir""" |
| logger.info(message) |
| |
| if not visible: |
| return None |
| |
| if progress_container is not None: |
| |
| if is_error: |
| progress_container.error(message, icon=icon) |
| elif is_warning: |
| progress_container.warning(message, icon=icon) |
| else: |
| progress_container.info(message) |
| else: |
| |
| progress_placeholder = st.empty() |
| if is_error: |
| progress_placeholder.error(message, icon=icon) |
| elif is_warning: |
| progress_placeholder.warning(message, icon=icon) |
| else: |
| progress_placeholder.info(message) |
| return progress_placeholder |
|
|
| |
| def check_required_libraries(): |
| """Gerekli kütüphanelerin yüklü olup olmadığını kontrol eder""" |
| required_packages = { |
| 'newspaper3k': 'newspaper', |
| 'lxml_html_clean': 'lxml_html_clean', |
| 'bs4': 'bs4', |
| 'requests': 'requests' |
| } |
| |
| missing_packages = [] |
| |
| for package, module_name in required_packages.items(): |
| try: |
| importlib.import_module(module_name) |
| except ImportError: |
| missing_packages.append(package) |
| |
| return missing_packages |
|
|
| |
| def get_news_from_newsapi(search_term, max_results=10, api_key=None): |
| """NewsAPI kullanarak haber arama""" |
| try: |
| api_key = (api_key or _get_newsapi_key() or "").strip() |
| if not api_key: |
| logger.warning("NewsAPI anahtarı tanımlı değil; NewsAPI adımı atlandı.") |
| return [] |
| |
| try: |
| from newsapi import NewsApiClient |
| newsapi = NewsApiClient(api_key=api_key) |
| except ImportError: |
| logger.warning("newsapi-python kütüphanesi yüklü değil, direkt API isteği kullanılacak") |
| return fetch_news_direct_api(search_term, max_results, api_key) |
| |
| |
| articles = newsapi.get_everything( |
| q=f"{search_term}", |
| sort_by="publishedAt", |
| page_size=max_results |
| ) |
| |
| if not articles or "articles" not in articles: |
| logger.warning(f"NewsAPI'den haber getirilemedi: {articles.get('message', 'Bilinmeyen hata')}") |
| return [] |
| |
| |
| news_list = [] |
| for article in articles["articles"]: |
| |
| sentiment_data = {"sentiment": "Nötr", "score": 0.5} |
| if article.get("description"): |
| sentiment_data = analyze_sentiment(article["description"]) |
| |
| news_list.append({ |
| "title": article.get("title", "Başlık Yok"), |
| "source": article.get("source", {}).get("name", "Bilinmeyen Kaynak"), |
| "summary": article.get("description", "Özet alınamadı."), |
| "url": article.get("url", "#"), |
| "link": article.get("url", "#"), |
| "pub_date": article.get("publishedAt", datetime.now().isoformat()), |
| "published_date": article.get("publishedAt", datetime.now().isoformat()), |
| "image_url": article.get("urlToImage", ""), |
| "sentiment": sentiment_data["score"], |
| "provider": "NewsAPI" |
| }) |
| |
| return news_list |
| |
| except Exception as e: |
| logger.error(f"NewsAPI haberleri alınırken hata: {str(e)}") |
| return [] |
|
|
| |
| def fetch_news_direct_api(search_term, max_results=10, api_key=None): |
| """NewsAPI kütüphanesi olmadan direkt API çağrısı yapar""" |
| try: |
| api_key = (api_key or _get_newsapi_key() or "").strip() |
| if not api_key: |
| logger.warning("NewsAPI anahtarı tanımlı değil; NewsAPI adımı atlandı.") |
| return [] |
| url = "https://newsapi.org/v2/everything" |
| params = { |
| "q": search_term, |
| "sortBy": "publishedAt", |
| "pageSize": max_results, |
| "apiKey": api_key |
| } |
| |
| response = requests.get(url, params=params) |
| if response.status_code != 200: |
| logger.warning(f"NewsAPI HTTP hatası: {response.status_code}") |
| return [] |
| |
| try: |
| data = response.json() |
| except ValueError: |
| logger.error("NewsAPI'den geçersiz JSON yanıtı alındı") |
| return [] |
| |
| if not data or "articles" not in data: |
| logger.warning(f"NewsAPI'den geçersiz yanıt: {data.get('message', 'Bilinmeyen hata')}") |
| return [] |
| |
| |
| news_list = [] |
| for article in data["articles"]: |
| |
| sentiment_data = {"sentiment": "Nötr", "score": 0.5} |
| if article.get("description"): |
| sentiment_data = analyze_sentiment(article["description"]) |
| |
| news_list.append({ |
| "title": article.get("title", "Başlık Yok"), |
| "source": article.get("source", {}).get("name", "Bilinmeyen Kaynak"), |
| "summary": article.get("description", "Özet alınamadı."), |
| "url": article.get("url", "#"), |
| "link": article.get("url", "#"), |
| "pub_date": article.get("publishedAt", datetime.now().isoformat()), |
| "published_date": article.get("publishedAt", datetime.now().isoformat()), |
| "image_url": article.get("urlToImage", ""), |
| "sentiment": sentiment_data["score"], |
| "provider": "NewsAPI" |
| }) |
| |
| return news_list |
| |
| except Exception as e: |
| logger.error(f"Doğrudan NewsAPI isteği sırasında hata: {str(e)}") |
| return [] |
|
|
| |
| def analyze_sentiment(text): |
| """Metin içeriğine göre duyarlılık analizi yapar""" |
| if not text or len(text) < 20: |
| return {"sentiment": "Nötr", "score": 0.5} |
| |
| try: |
| |
| negative_indicators = [ |
| "düşüş", "düştü", "kayb", "geriledi", "düşerek", "azald", "eksiye", |
| "eksi", "değer kaybetti", "düşüşle", "kapatmıştı", "gerileme" |
| ] |
| |
| |
| positive_indicators = [ |
| "yükseliş", "yükseldi", "artış", "arttı", "kazanç", "rekor", "başarı", |
| "yükselerek", "değer kazandı", "olumlu" |
| ] |
| |
| text_lower = text.lower() |
| |
| |
| neg_matches = sum(1 for word in negative_indicators if word in text_lower) |
| pos_matches = sum(1 for word in positive_indicators if word in text_lower) |
| |
| |
| borsa_negative_patterns = [ |
| "borsa düş", "borsa geril", "endeks düş", "endeks geril", |
| "borsa eksi", "endeks eksi", "bist düş", "bist geril" |
| ] |
| |
| for pattern in borsa_negative_patterns: |
| if pattern in text_lower: |
| |
| return {"sentiment": "Olumsuz", "score": 0.2} |
| |
| |
| from ai.sentiment_analysis import SentimentAnalyzer |
| import re |
| |
| analyzer: Any = SentimentAnalyzer() |
|
|
| model = getattr(analyzer, 'model', None) |
| if not model: |
| raise ValueError("Duyarlılık analizi modeli yüklenemedi") |
| |
| |
| max_words = 200 |
| words = text.split() |
| if len(words) > max_words: |
| text = " ".join(words[:max_words]) |
| |
| |
| def clean_text(txt): |
| |
| txt = txt.lower() |
| |
| txt = re.sub(r'[^\w\s.,!?%]', ' ', txt) |
| |
| txt = re.sub(r'\s+', ' ', txt).strip() |
| return txt |
| |
| |
| cleaned_text = clean_text(text) |
| |
| |
| predict_fn = getattr(analyzer, 'predict', None) |
| proba_fn = getattr(analyzer, 'predict_proba', None) |
| if not callable(predict_fn) or not callable(proba_fn): |
| raise ImportError("SentimentAnalyzer arayüzü beklenen metotları sağlamıyor") |
|
|
| prediction_out = cast(Sequence[Any], predict_fn([cleaned_text])) |
| score_out = cast(Sequence[Any], proba_fn([cleaned_text])) |
| prediction = prediction_out[0] |
| score = score_out[0] |
| |
| |
| sentiment = "Olumlu" if prediction == 1 else "Olumsuz" |
| |
| |
| if sentiment == "Olumlu" and neg_matches > pos_matches and any(p in text_lower for p in ["borsa", "endeks", "bist"]): |
| |
| sentiment = "Olumsuz" |
| score = -abs(score) |
| |
| return {"sentiment": sentiment, "score": score} |
| |
| except ImportError: |
| |
| pass |
| |
| |
| import re |
| |
| |
| positive_words = { |
| 'artış', 'yükseliş', 'kazanç', 'kâr', 'rekor', 'başarı', 'pozitif', 'olumlu', 'güçlü', 'büyüme', |
| 'iyileşme', 'yükseldi', 'arttı', 'çıktı', 'güven', 'istikrar', 'avantaj', 'fırsat', 'yatırım', |
| 'imzalandı', 'anlaşma', 'destek', 'teşvik', 'ivme', 'fayda', 'artırdı', 'kazandı', 'genişleme', |
| 'ihracat', 'ciro', 'teşvik', 'ödül', 'toparlanma', 'umut', 'iyi', 'memnuniyet', 'ralli', |
| 'yüksek', 'çözüm', 'artacak', 'başarılı', 'kazanım', 'gelişme', 'ilerleme', 'potansiyel' |
| } |
| |
| negative_words = { |
| 'düşüş', 'kayıp', 'zarar', 'risk', 'gerileme', 'olumsuz', 'negatif', 'zayıf', 'belirsizlik', |
| 'endişe', 'azaldı', 'düştü', 'kaybetti', 'gecikme', 'borç', 'iflas', 'kriz', 'tehdit', 'sorun', |
| 'başarısız', 'yaptırım', 'ceza', 'iptal', 'durgunluk', 'darbe', 'kötü', 'daralma', 'kesinti', |
| 'baskı', 'paniği', 'çöküş', 'alarm', 'tedirgin', 'zor', 'şok', 'dava', 'soruşturma', 'satış', |
| 'düşük', 'ağır', 'kötüleşme', 'panik', 'küçülme', 'yavaşlama', 'kapatma', 'haciz', 'çöktü' |
| } |
| |
| |
| text = text.lower() |
| words = re.findall(r'\b\w+\b', text) |
| |
| |
| positive_count = sum(1 for word in words if word in positive_words) |
| negative_count = sum(1 for word in words if word in negative_words) |
| |
| total_count = positive_count + negative_count |
| if total_count == 0: |
| |
| return {"sentiment": "Nötr", "score": 0.5} |
| |
| |
| sentiment_score = positive_count / (positive_count + negative_count) |
| |
| |
| if sentiment_score > 0.65: |
| return {"sentiment": "Olumlu", "score": sentiment_score} |
| elif sentiment_score < 0.35: |
| return {"sentiment": "Olumsuz", "score": sentiment_score} |
| else: |
| return {"sentiment": "Nötr", "score": sentiment_score} |
|
|
| |
| def analyze_news_with_gemini(url, log_container=None): |
| """ |
| Gemini API kullanarak haber URL'sini analiz eder. |
| |
| Args: |
| url (str): Haber URL'si |
| log_container: Log mesajlarını göstermek için Streamlit container |
| |
| Returns: |
| dict: Analiz sonuçları |
| """ |
| |
| def log_info(message): |
| if log_container is not None: |
| log_container.info(message) |
| else: |
| logger.info(message) |
| |
| def log_error(message): |
| if log_container is not None: |
| log_container.error(message) |
| else: |
| logger.error(message) |
| |
| try: |
| log_info(f"Haber analizi başlatılıyor: {url}") |
| |
| |
| try: |
| import newspaper |
| from newspaper import Article |
| |
| log_info("Haber içeriği alınıyor...") |
| article = Article(url) |
| article.download() |
| article.parse() |
| |
| |
| title = article.title |
| authors = ", ".join(article.authors) if article.authors else "Belirtilmemiş" |
| publish_date = article.publish_date.strftime("%d.%m.%Y") if article.publish_date else "Belirtilmemiş" |
| content = article.text |
| |
| if not content or len(content) < 100: |
| log_info("Haberde yeterli içerik bulunamadı...") |
| return { |
| "success": False, |
| "error": "Haber içeriği alınamadı veya çok kısa." |
| } |
| |
| log_info(f"Haber başlığı: {title}") |
| log_info(f"İçerik uzunluğu: {len(content)} karakter") |
| |
| except Exception as e: |
| log_error(f"Haber içeriği alınırken hata: {str(e)}") |
| return { |
| "success": False, |
| "error": f"Haber içeriği alınamadı: {str(e)}" |
| } |
| |
| |
| log_info("Duyarlılık analizi yapılıyor...") |
| sentiment_result = analyze_sentiment(content) |
| |
| |
| from ai.api import initialize_gemini_api |
| |
| gemini_pro = initialize_gemini_api() |
| if gemini_pro is None: |
| log_error("Gemini API bağlantısı kurulamadı! API anahtarı kontrol edin.") |
| return { |
| "success": True, |
| "title": title, |
| "authors": authors, |
| "publish_date": publish_date, |
| "content": content, |
| "sentiment": sentiment_result["sentiment"], |
| "sentiment_score": sentiment_result["score"], |
| "ai_summary": "Yapay zeka hizmeti şu anda kullanılamıyor.", |
| "ai_analysis": { |
| "etki": "nötr", |
| "etki_sebebi": "Yapay zeka analizi yapılamadı. API bağlantısını kontrol edin.", |
| "önemli_noktalar": ["Analiz için API bağlantısı gerekiyor."] |
| } |
| } |
| |
| |
| log_info("Yapay zeka analizi yapılıyor...") |
| prompt = f""" |
| Aşağıdaki finans/ekonomi haberi metnini dikkatlice analiz et: |
| |
| BAŞLIK: {title} |
| |
| İÇERİK: |
| {content[:4000]} # En fazla 4000 karakter kullan (API limiti için) |
| |
| Bir finans uzmanı olarak, lütfen bu haberi analiz et ve aşağıdaki formatla yanıt ver: |
| |
| 1. ÖZET: Haberin 2-3 cümlelik kısa bir özeti. Finans açısından en önemli bilgileri içer. |
| |
| 2. ANALİZ: |
| - etki: "olumlu", "olumsuz" veya "nötr" olarak haberin piyasa etkisi |
| - etki_sebebi: Haberin neden bu etkiye sahip olduğuna dair 1-2 cümlelik açıklama |
| - önemli_noktalar: Haberdeki finansal açıdan önemli 2-4 noktayı madde işaretleriyle liste halinde belirt |
| |
| JSON formatında yanıt ver. |
| """ |
| |
| try: |
| response = gemini_pro.generate_content(prompt) |
| result_text = response.text |
| |
| |
| import json |
| import re |
| |
| |
| try: |
| |
| json_match = re.search(r'```json\s*([\s\S]*?)\s*```', result_text) |
| if json_match: |
| json_str = json_match.group(1) |
| else: |
| json_str = result_text |
| |
| ai_result = json.loads(json_str) |
| |
| |
| if isinstance(ai_result, dict): |
| ai_summary = ai_result.get("ÖZET", ai_result.get("özet", "")) |
| |
| |
| ai_analysis = ai_result.get("ANALİZ", ai_result.get("analiz", {})) |
| if not isinstance(ai_analysis, dict): |
| ai_analysis = { |
| "etki": "nötr", |
| "etki_sebebi": "Analiz yapılamadı.", |
| "önemli_noktalar": ["Yapısal analiz yapılamadı."] |
| } |
| else: |
| ai_summary = "Analiz yapılamadı." |
| ai_analysis = { |
| "etki": "nötr", |
| "etki_sebebi": "Analiz yapılamadı.", |
| "önemli_noktalar": ["Yapısal analiz yapılamadı."] |
| } |
| |
| except json.JSONDecodeError: |
| |
| log_info("JSON parsing hatası, manuel analiz yapılıyor...") |
| |
| |
| summary_match = re.search(r'(?:ÖZET|özet):\s*(.*?)(?=\n\n|\n\d\.|\Z)', result_text, re.DOTALL) |
| ai_summary = summary_match.group(1).strip() if summary_match else "Özet yapılamadı." |
| |
| |
| etki_match = re.search(r'(?:etki|ETKİ):\s*(.*?)(?=\n|\Z)', result_text, re.DOTALL) |
| etki = etki_match.group(1).strip().lower() if etki_match else "nötr" |
| |
| |
| etki_sebebi_match = re.search(r'(?:etki_sebebi|ETKİ SEBEBİ):\s*(.*?)(?=\n|\Z)', result_text, re.DOTALL) |
| etki_sebebi = etki_sebebi_match.group(1).strip() if etki_sebebi_match else "Belirtilmemiş" |
| |
| |
| önemli_noktalar_match = re.search(r'(?:önemli_noktalar|ÖNEMLİ NOKTALAR):\s*(.*?)(?=\n\n|\Z)', result_text, re.DOTALL) |
| if önemli_noktalar_match: |
| önemli_noktalar_text = önemli_noktalar_match.group(1) |
| önemli_noktalar = re.findall(r'[-*]\s*(.*?)(?=\n[-*]|\Z)', önemli_noktalar_text, re.DOTALL) |
| önemli_noktalar = [point.strip() for point in önemli_noktalar if point.strip()] |
| else: |
| önemli_noktalar = ["Önemli nokta belirtilmemiş."] |
| |
| ai_analysis = { |
| "etki": etki, |
| "etki_sebebi": etki_sebebi, |
| "önemli_noktalar": önemli_noktalar |
| } |
| |
| log_info("Yapay zeka analizi tamamlandı.") |
| |
| return { |
| "success": True, |
| "title": title, |
| "authors": authors, |
| "publish_date": publish_date, |
| "content": content, |
| "sentiment": sentiment_result["sentiment"], |
| "sentiment_score": sentiment_result["score"], |
| "ai_summary": ai_summary, |
| "ai_analysis": ai_analysis |
| } |
| |
| except Exception as api_error: |
| log_error(f"Gemini API analiz hatası: {str(api_error)}") |
| return { |
| "success": True, |
| "title": title, |
| "authors": authors, |
| "publish_date": publish_date, |
| "content": content, |
| "sentiment": sentiment_result["sentiment"], |
| "sentiment_score": sentiment_result["score"], |
| "ai_summary": "Yapay zeka analizi sırasında hata oluştu.", |
| "ai_analysis": { |
| "etki": "nötr", |
| "etki_sebebi": f"Analiz hatası: {str(api_error)}", |
| "önemli_noktalar": ["Analiz tamamlanamadı."] |
| } |
| } |
| |
| except Exception as e: |
| log_error(f"Haber analizi sırasında beklenmeyen hata: {str(e)}") |
| return { |
| "success": False, |
| "error": str(e) |
| } |
|
|
| |
| DEFAULT_TIMEOUT = 20 |
| REQUEST_HEADERS = { |
| 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', |
| 'Accept-Language': 'tr-TR,tr;q=0.9,en-US;q=0.8,en;q=0.7', |
| 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8', |
| 'Accept-Encoding': 'gzip, deflate, br', |
| 'DNT': '1', |
| 'Connection': 'keep-alive', |
| 'Upgrade-Insecure-Requests': '1' |
| } |
|
|
| |
| BING_NEWS_API_KEY = os.environ.get("BING_NEWS_API_KEY", "") |
| NEWS_API_KEY = os.environ.get("NEWS_API_KEY", "") |
|
|
| |
| def requests_retry_session(retries=3, backoff_factor=0.3, status_forcelist=(500, 502, 504), session=None): |
| session = session or requests.Session() |
| retry = Retry( |
| total=retries, |
| read=retries, |
| connect=retries, |
| backoff_factor=backoff_factor, |
| status_forcelist=status_forcelist, |
| ) |
| adapter = HTTPAdapter(max_retries=retry) |
| session.mount('http://', adapter) |
| session.mount('https://', adapter) |
| return session |
|
|
| |
| class NewsSource: |
| """Haber kaynağı için temel sınıf""" |
| def __init__(self, name): |
| self.name = name |
| |
| self.session = requests_retry_session() |
| self.session.headers.update(REQUEST_HEADERS) |
| |
| self.update_random_user_agent() |
| |
| def update_random_user_agent(self): |
| """Her istek öncesi rastgele User-Agent ata""" |
| user_agents = [ |
| 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', |
| 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', |
| 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', |
| 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', |
| 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:124.0) Gecko/20100101 Firefox/124.0' |
| ] |
| self.session.headers['User-Agent'] = random.choice(user_agents) |
|
|
| def is_relevant(self, title, link, stock_code): |
| """Haber başlığı ve linkini ilgili hisse senedi kodu ile karşılaştırır""" |
| if not title or not stock_code: |
| return False |
| |
| title_upper = title.upper() |
| stock_code_upper = stock_code.upper().replace('.IS', '') |
| |
| |
| if stock_code_upper in title_upper: |
| return True |
| |
| |
| common_names = { |
| 'THYAO': ['TÜRK HAVA YOLLARI', 'THY', 'TURKISH AIRLINES'], |
| 'ASELS': ['ASELSAN', 'ASELSAN A.Ş.'], |
| 'GARAN': ['GARANTİ', 'GARANTİ BANKASI', 'GARANTI BBVA', 'GARANTI BANKASI'], |
| 'SASA': ['SASA POLYESTER', 'SASA A.Ş.'], |
| 'KCHOL': ['KOÇ HOLDİNG', 'KOÇ GRUBU', 'KOC HOLDING', 'KOC GRUP'], |
| 'AKBNK': ['AKBANK', 'AKBANK T.A.Ş.'], |
| 'ISCTR': ['İŞ BANKASI', 'IS BANKASI', 'İŞBANK', 'ISBANK'], |
| 'TCELL': ['TURKCELL', 'TURKCELL İLETİŞİM'], |
| 'TUPRS': ['TÜPRAŞ', 'TUPRAS', 'TÜRKİYE PETROL RAFİNERİLERİ'], |
| 'PETKM': ['PETKİM', 'PETKIM PETROKIMYA', 'PETKİM PETROKİMYA'], |
| 'EREGL': ['EREĞLİ DEMİR', 'EREGLI DEMIR', 'ERDEMİR', 'ERDEMIR', 'EREĞLİ DEMİR ÇELİK'], |
| 'BIMAS': ['BİM', 'BİM', 'BİM MAĞAZALAR', 'BIM MAGAZALAR', 'BİRLEŞİK MAĞAZALAR'], |
| 'TOASO': ['TOFAŞ', 'TOFAS', 'TOFAŞ OTOMOBİL', 'TOFAS OTOMOBIL', 'FCA'], |
| 'FROTO': ['FORD OTOSAN', 'FORD OTOMOTİV', 'FORD OTOMOTIV', 'FORD MOTOR'], |
| 'HEKTS': ['HEKTAŞ', 'HEKTAS', 'HEKTAŞ TARIM'], |
| 'VESTL': ['VESTEL', 'VESTEL ELEKTRONİK', 'VESTEL ELEKTRONIK'], |
| 'PGSUS': ['PEGASUS', 'PEGASUS HAVA YOLLARI', 'PEGASUS AIRLINES'], |
| 'SAHOL': ['SABANCI', 'SABANCI HOLDİNG', 'SABANCI HOLDING'], |
| 'AKSEN': ['AKSA ENERJİ', 'AKSA ENERJI', 'AKSA'], |
| 'KRDMD': ['KARDEMİR', 'KARDEMIR', 'KARDEMİR D', 'KARDEMIR D'], |
| 'SISE': ['ŞİŞECAM', 'SISECAM', 'ŞİŞE CAM', 'SISE CAM', 'CAM İŞ', 'CAM IS'], |
| 'ALARK': ['ALARKO HOLDİNG', 'ALARKO HOLDING', 'ALARKO'], |
| 'ARCLK': ['ARÇELİK', 'ARCELIK', 'ARÇELİK A.Ş.', 'KOÇ ARÇELİK'] |
| } |
| |
| |
| if stock_code_upper in common_names: |
| for name in common_names[stock_code_upper]: |
| |
| if re.search(r'\b' + re.escape(name) + r'\b', title_upper): |
| return True |
| |
| |
| |
| if f"/{stock_code_upper.lower()}" in link.lower() or f"-{stock_code_upper.lower()}" in link.lower(): |
| return True |
| |
| |
| return False |
| |
| def extract_summary(self, article_url): |
| """URL'den haber içerik özetini çıkarır ve sentiment analizi yapar""" |
| if not NEWSPAPER_AVAILABLE: |
| return {"summary": "Özet alınamadı.", "sentiment_score": 0.5} |
| |
| if not article_url or article_url == "#": |
| return {"summary": "Özet alınamadı.", "sentiment_score": 0.5} |
| |
| try: |
| |
| news_config: Any = Config() |
| news_config.request_timeout = 10 |
| news_config.browser_user_agent = self.session.headers['User-Agent'] |
| |
| article: Any = Article(article_url, config=news_config, language='tr') |
| article.download() |
| article.parse() |
| |
| |
| image_url = article.top_image if hasattr(article, 'top_image') else "" |
| |
| if not article.text or len(article.text.strip()) < 50: |
| if article.meta_description: |
| summary = article.meta_description |
| else: |
| return {"summary": "Özet alınamadı.", "sentiment_score": 0.5, "image_url": image_url} |
| else: |
| |
| summary = article.text.strip()[:500] + "..." if len(article.text) > 500 else article.text.strip() |
| |
| |
| sentiment_result = analyze_sentiment(summary) |
| |
| return { |
| "summary": summary, |
| "sentiment_score": sentiment_result.get("score", 0.5), |
| "image_url": image_url |
| } |
| |
| except ArticleException as article_ex: |
| return {"summary": "Özet alınamadı.", "sentiment_score": 0.5, "image_url": ""} |
| except Exception as e: |
| return {"summary": "Özet alınamadı.", "sentiment_score": 0.5, "image_url": ""} |
| |
| def format_date(self, date_obj, now): |
| """Tarih nesnesini okunabilir formata çevirir""" |
| if not date_obj: |
| return "Tarih Yok" |
| |
| if date_obj.date() == now.date(): |
| return f"Bugün {date_obj.strftime('%H:%M')}" |
| |
| |
| yesterday = now.date() - timedelta(days=1) |
| if date_obj.date() == yesterday: |
| return f"Dün {date_obj.strftime('%H:%M')}" |
| |
| return date_obj.strftime("%d.%m.%Y %H:%M") |
|
|
| class GoogleNewsSource(NewsSource): |
| """Google News için haber kaynağı""" |
| |
| def __init__(self): |
| super().__init__("Google News") |
| self.update_random_user_agent() |
| |
| def fetch_news(self, search_term, cutoff_date, common_names_dict=None, max_items=10): |
| """Google News'ten haberleri getirir""" |
| |
| |
| log_info(f"Google News'te aranıyor: {search_term}") |
| |
| news_items = [] |
| |
| |
| |
| if common_names_dict is None: |
| common_names_dict = {} |
| |
| |
| if cutoff_date.tzinfo is not None: |
| cutoff_date = cutoff_date.replace(tzinfo=None) |
| |
| |
| encoded_term = search_term.replace(" ", "+") |
| |
| |
| urls = [ |
| f"https://news.google.com/search?q={encoded_term}&hl=tr&gl=TR&ceid=TR:tr", |
| f"https://news.google.com/rss/search?q={encoded_term}&hl=tr&gl=TR&ceid=TR:tr" |
| ] |
| |
| for url in urls: |
| try: |
| |
| is_rss = "rss" in url |
| |
| session = requests_retry_session() |
| response = session.get(url, headers=self.session.headers, timeout=30) |
| |
| if response.status_code != 200: |
| log_warning(f"Google News yanıt kodu: {response.status_code}") |
| continue |
| |
| |
| if is_rss: |
| import xml.etree.ElementTree as ET |
| |
| |
| root = ET.fromstring(response.content) |
| |
| |
| items = root.findall('.//item') |
| |
| for item in items[:max_items]: |
| title_elem = item.find('title') |
| link_elem = item.find('link') |
| |
| if title_elem is None or link_elem is None: |
| continue |
| |
| title = title_elem.text |
| link = link_elem.text |
| |
| |
| source = "Google News" |
| if title and " - " in title: |
| title_parts = title.split(" - ") |
| title = " - ".join(title_parts[:-1]) |
| source = title_parts[-1] |
| |
| |
| pub_date_elem = item.find('pubDate') |
| pub_date = datetime.now() |
| |
| if pub_date_elem is not None and pub_date_elem.text: |
| try: |
| |
| from email.utils import parsedate_to_datetime |
| pub_date = parsedate_to_datetime(pub_date_elem.text) |
| |
| if pub_date.tzinfo is not None: |
| pub_date = pub_date.replace(tzinfo=None) |
| except Exception as date_err: |
| log_warning(f"RSS tarih çözümleme hatası: {date_err}") |
| |
| |
| if pub_date < cutoff_date: |
| continue |
| |
| |
| description_elem = item.find('description') |
| summary = description_elem.text if description_elem is not None else "" |
| |
| if not summary: |
| summary = "Özet bulunamadı." |
| |
| |
| content_info = self.extract_summary(link) |
| if content_info.get("summary", "") != "Özet alınamadı." and len(content_info.get("summary", "")) > len(summary): |
| summary = content_info.get("summary", summary) |
| |
| |
| image_url = content_info.get("image_url", "") |
| |
| |
| news_items.append({ |
| 'title': title, |
| 'link': link, |
| 'source': source, |
| 'summary': summary, |
| 'published_datetime': pub_date, |
| 'provider': self.name, |
| 'sentiment': content_info.get("sentiment_score", 0.5), |
| 'image_url': image_url |
| }) |
| |
| else: |
| |
| soup = BeautifulSoup(response.text, 'html.parser') |
| |
| |
| articles = soup.select('div[class*="NiLAwe"]') |
| |
| if not articles: |
| |
| articles = soup.select('article, div.xrnccd, main article') |
| |
| |
| if not articles: |
| articles = soup.select('div[role="article"], div.S8PBwe, main > div > div > div') |
| |
| |
| if articles: |
| log_info(f"Google News'den {len(articles)} sonuç alındı") |
| else: |
| log_warning("Google News'den sonuç alınamadı, HTML yapısı değişmiş olabilir", visible=False) |
| continue |
| |
| for article in articles[:max_items]: |
| |
| title_elem = article.select_one('h3 a, h4 a, a[aria-label], a.DY5T1d') |
| |
| if not title_elem: |
| |
| title_elem = article.select_one('a, span[class*="title"]') |
| |
| if not title_elem: |
| continue |
| |
| |
| title = title_elem.get_text(strip=True) |
| |
| |
| link = str(title_elem.get('href', '#')) |
| |
| |
| if link.startswith('./'): |
| link = "https://news.google.com" + link[1:] |
| elif link.startswith('/'): |
| link = "https://news.google.com" + link |
| |
| |
| source_elem = article.select_one('a[data-n-tid], span.SVJrMe, div.vr1PYe') |
| source = source_elem.get_text(strip=True) if source_elem else "Google News" |
| |
| |
| time_elem = article.select_one('time, span[class*="time"], div.OSrXXb') |
| time_text = time_elem.get_text(strip=True) if time_elem else "" |
| |
| |
| published_date = datetime.now() |
| |
| if time_text: |
| try: |
| |
| if 'dakika önce' in time_text or 'dk. önce' in time_text: |
| minutes = int(''.join(filter(str.isdigit, time_text))) |
| published_date = datetime.now() - timedelta(minutes=minutes) |
| elif 'saat önce' in time_text or 'sa. önce' in time_text: |
| hours = int(''.join(filter(str.isdigit, time_text))) |
| published_date = datetime.now() - timedelta(hours=hours) |
| elif 'gün önce' in time_text: |
| days = int(''.join(filter(str.isdigit, time_text))) |
| published_date = datetime.now() - timedelta(days=days) |
| elif 'ay önce' in time_text: |
| months = int(''.join(filter(str.isdigit, time_text))) |
| published_date = datetime.now() - timedelta(days=months*30) |
| |
| else: |
| try: |
| |
| formats = [ |
| '%d %b', |
| '%d.%m.%Y', |
| '%d %B %Y', |
| '%d %b %Y' |
| ] |
| |
| for date_format in formats: |
| try: |
| published_date = datetime.strptime(time_text, date_format) |
| |
| if '%Y' not in date_format: |
| published_date = published_date.replace(year=datetime.now().year) |
| break |
| except ValueError: |
| continue |
| except Exception as e: |
| log_warning(f"Tarih ayrıştırma hatası: {str(e)}") |
| except Exception as time_err: |
| log_warning(f"Zaman ayrıştırma hatası: {str(time_err)}") |
| |
| |
| if published_date.tzinfo is not None: |
| published_date = published_date.replace(tzinfo=None) |
| |
| |
| summary_elem = article.select_one('span[class*="xBbh9"], div.GI74Re') |
| summary = summary_elem.get_text(strip=True) if summary_elem else "Özet bulunamadı." |
| |
| |
| if published_date < cutoff_date: |
| continue |
| |
| |
| image_url = "" |
| img_elem = article.select_one('img[src*="https"]') |
| if img_elem and img_elem.get('src'): |
| image_url = img_elem.get('src') |
| |
| |
| content_info = self.extract_summary(link) |
| if content_info.get("summary", "") != "Özet alınamadı." and len(content_info.get("summary", "")) > len(summary): |
| summary = content_info.get("summary", summary) |
| |
| |
| if not image_url and content_info.get("image_url"): |
| image_url = content_info.get("image_url") |
| |
| |
| news_items.append({ |
| 'title': title, |
| 'link': link, |
| 'source': source, |
| 'summary': summary, |
| 'published_datetime': published_date, |
| 'provider': self.name, |
| 'sentiment': content_info.get("sentiment_score", 0.5), |
| 'image_url': image_url |
| }) |
| |
| |
| if len(news_items) >= max_items: |
| break |
| |
| except Exception as e: |
| log_warning(f"Google News veri çekme hatası ({url}): {str(e)}") |
| continue |
| |
| return news_items |
|
|
| class YahooNewsSource(NewsSource): |
| """Yahoo Finance için haber kaynağı""" |
| |
| def __init__(self): |
| super().__init__("Yahoo Finance") |
| self.update_random_user_agent() |
| |
| def fetch_news(self, search_term, cutoff_date, common_names_dict=None, max_items=10): |
| """Yahoo Finance'ten haberleri getirir""" |
| |
| |
| log_info(f"Yahoo Finance'te aranıyor: {search_term}") |
| |
| news_items = [] |
| |
| try: |
| |
| encoded_term = search_term.replace(" ", "+") |
| url = f"https://search.yahoo.com/search?p={encoded_term}+finance+news&fr=finance" |
| |
| session = requests_retry_session() |
| response = session.get(url, headers=self.session.headers, timeout=30) |
| |
| if response.status_code == 200: |
| soup = BeautifulSoup(response.content, 'html.parser') |
| |
| |
| results = soup.select('div.algo') |
| |
| for result in results[:max_items]: |
| |
| title_elem = result.select_one('h3') |
| if not title_elem: |
| continue |
| |
| link_elem = title_elem.select_one('a') |
| if not link_elem: |
| continue |
| |
| title = title_elem.get_text(strip=True) |
| link = str(link_elem.get('href', '')) |
| |
| |
| summary_elem = result.select_one('p, div.compText, span.fz-ms') |
| summary = summary_elem.get_text(strip=True) if summary_elem else "Özet alınamadı." |
| |
| |
| source_domain = link.split('//')[-1].split('/')[0] |
| |
| |
| if 'tr.investing.com' in source_domain: |
| source = 'Investing.com' |
| elif 'finance.yahoo.com' in source_domain or 'tr.finance.yahoo.com' in source_domain: |
| source = 'Yahoo Finance' |
| elif 'bloomberght.com' in source_domain: |
| source = 'BloombergHT' |
| elif 'finans.mynet.com' in source_domain: |
| source = 'Mynet Finans' |
| elif 'bigpara.hurriyet.com.tr' in source_domain: |
| source = 'BigPara' |
| elif 'finansgundem.com' in source_domain: |
| source = 'Finans Gündem' |
| elif 'businessht.com.tr' in source_domain: |
| source = 'Business HT' |
| elif 'ekonomi.haber7.com' in source_domain: |
| source = 'Haber7 Ekonomi' |
| elif 'paraanaliz.com' in source_domain: |
| source = 'Para Analiz' |
| else: |
| |
| parts = source_domain.split('.') |
| if len(parts) >= 2: |
| source = parts[-2].capitalize() |
| else: |
| source = source_domain.capitalize() |
| |
| |
| published_date = datetime.now() |
| |
| |
| content_info = self.extract_summary(link) |
| if content_info.get("summary", "") != "Özet alınamadı." and len(content_info.get("summary", "")) > len(summary): |
| summary = content_info.get("summary", summary) |
| |
| |
| news_items.append({ |
| 'title': title, |
| 'link': link, |
| 'source': source, |
| 'summary': summary, |
| 'published_datetime': published_date, |
| 'provider': self.name, |
| 'sentiment': content_info.get("sentiment_score", 0.5) |
| }) |
| |
| |
| if len(news_items) >= max_items: |
| break |
| |
| |
| log_info(f"[{self.name}] {len(news_items)} haber döndürüldü.") |
| |
| except Exception as e: |
| log_warning(f"[{self.name}] Haber alınırken hata: {str(e)}") |
| |
| return news_items |
|
|
| |
|
|
| def get_stock_news(stock_symbol, max_results=10, news_period="1m", progress_container=None, providers=None): |
| """ |
| Belirtilen hisse senedi kodu için haberleri çeker. |
| |
| Parametreler: |
| - stock_symbol: Hisse senedi kodu |
| - max_results: Maksimum sonuç sayısı |
| - news_period: Haber dönemi (1d, 1w, 1m, 3m, 1y) |
| - progress_container: İlerleme göstergeleri için container |
| - providers: Kullanılacak haber kaynakları listesi |
| |
| Dönüş: |
| - Haber bilgilerini içeren liste |
| """ |
| if not stock_symbol: |
| log_error("Hisse senedi kodu belirtilmedi!") |
| return None |
| |
| |
| def log_progress(message, is_warning=False, is_error=False, icon=None): |
| if progress_container is not None: |
| if is_error: |
| progress_container.error(message, icon=icon) |
| elif is_warning: |
| progress_container.warning(message, icon=icon) |
| else: |
| progress_container.info(message) |
| logger.info(message) |
| |
| try: |
| |
| log_progress(f"{stock_symbol} için haberler aranıyor...") |
| |
| |
| if not providers: |
| providers = ["Google News", "Yahoo Finance"] |
| |
| |
| now = datetime.now() |
| |
| if news_period == "1d": |
| cutoff_date = now - timedelta(days=1) |
| elif news_period == "3d": |
| cutoff_date = now - timedelta(days=3) |
| elif news_period == "1w": |
| cutoff_date = now - timedelta(days=7) |
| elif news_period == "1m": |
| cutoff_date = now - timedelta(days=30) |
| elif news_period == "3m": |
| cutoff_date = now - timedelta(days=90) |
| elif news_period == "1y": |
| cutoff_date = now - timedelta(days=365) |
| else: |
| |
| cutoff_date = now - timedelta(days=30) |
| |
| |
| search_term = f"{stock_symbol}" |
| |
| log_progress(f"Tarih aralığı: {cutoff_date.strftime('%Y-%m-%d')} - {now.strftime('%Y-%m-%d')}") |
| |
| all_news = [] |
| |
| |
| if "NewsAPI" in providers: |
| log_progress("NewsAPI üzerinden haberler getiriliyor...") |
| api_key = _get_newsapi_key() |
| if not api_key: |
| log_progress("NewsAPI anahtarı tanımlı değil; NewsAPI adımı atlandı.", is_warning=True) |
| else: |
| newsapi_results = get_news_from_newsapi(search_term, max_results, api_key) |
| if newsapi_results: |
| log_progress(f"NewsAPI'den {len(newsapi_results)} haber bulundu.") |
| all_news.extend(newsapi_results) |
| else: |
| log_progress("NewsAPI'den sonuç alınamadı.", is_warning=True) |
| |
| |
| if "Google News" in providers and len(all_news) < max_results: |
| log_progress("Google News üzerinden haberler getiriliyor...") |
| google_news = GoogleNewsSource() |
| try: |
| google_results = google_news.fetch_news(search_term, cutoff_date, None, max_items=max_results) |
| if google_results: |
| log_progress(f"Google News'ten {len(google_results)} haber bulundu.") |
| all_news.extend(google_results) |
| else: |
| log_progress("Google News'ten sonuç alınamadı.", is_warning=True) |
| except Exception as gnews_err: |
| log_progress(f"Google News hatası: {str(gnews_err)}", is_error=True) |
| |
| |
| if "Yahoo Finance" in providers and len(all_news) < max_results: |
| log_progress("Yahoo Finance üzerinden haberler getiriliyor...") |
| yahoo_news = YahooNewsSource() |
| try: |
| yahoo_results = yahoo_news.fetch_news(search_term, cutoff_date, None, max_items=max_results) |
| if yahoo_results: |
| log_progress(f"Yahoo Finance'den {len(yahoo_results)} haber bulundu.") |
| all_news.extend(yahoo_results) |
| else: |
| log_progress("Yahoo Finance'den sonuç alınamadı.", is_warning=True) |
| except Exception as yahoo_err: |
| log_progress(f"Yahoo Finance hatası: {str(yahoo_err)}", is_error=True) |
| |
| |
| unique_news = [] |
| titles = set() |
| |
| for news in all_news: |
| title = news.get("title", "") |
| if title and title not in titles: |
| titles.add(title) |
| unique_news.append(news) |
| |
| log_progress(f"Toplam {len(unique_news)} benzersiz haber bulundu.") |
| |
| |
| if len(unique_news) > max_results: |
| unique_news = unique_news[:max_results] |
| log_progress(f"Sonuçlar {max_results} ile sınırlandırıldı.") |
| |
| return unique_news |
| |
| except Exception as e: |
| log_error(f"Haber arama sırasında hata: {str(e)}", e) |
| return None |
|
|
| def get_general_market_news(max_results=5, news_period="1w", progress_container=None): |
| """ |
| Genel piyasa ve ekonomi haberlerini farklı finans kaynaklarından getirir |
| |
| Args: |
| max_results (int): Maksimum sonuç sayısı |
| news_period (str): Zaman dilimi ('1d', '1w', '1m', '3m') |
| progress_container: Log mesajları için konteyner |
| |
| Returns: |
| list: Haberler listesi |
| """ |
| |
| def log_progress(message, is_warning=False, is_error=False, icon=None): |
| """Logging fonksiyonu - container'a veya doğrudan UI'ye gönderilebilir""" |
| if progress_container is not None: |
| |
| if is_error: |
| progress_container.error(message, icon=icon) |
| elif is_warning: |
| progress_container.warning(message, icon=icon) |
| else: |
| progress_container.info(message) |
| else: |
| |
| progress_placeholder = st.empty() |
| if is_error: |
| progress_placeholder.error(message, icon=icon) |
| elif is_warning: |
| progress_placeholder.warning(message, icon=icon) |
| else: |
| progress_placeholder.info(message) |
| return progress_placeholder |
| |
| |
| progress_placeholder = log_progress("Genel piyasa haberleri alınıyor...") |
| |
| news_items = [] |
| processed_links = set() |
| |
| |
| period_map = { |
| "1d": timedelta(days=1), |
| "3d": timedelta(days=3), |
| "1w": timedelta(days=7), |
| "2w": timedelta(days=14), |
| "1m": timedelta(days=30), |
| "3m": timedelta(days=90) |
| } |
| |
| now = datetime.now() |
| cutoff_date = now - period_map.get(news_period, timedelta(days=7)) |
| |
| |
| search_queries = [ |
| |
| "Borsa İstanbul", |
| "Türkiye Ekonomi", |
| "Küresel Ekonomi", |
| "Jeopolitik Riskler", |
| |
| "Faiz Kararı", |
| "Enflasyon Verisi", |
| "ABD Enflasyon", |
| "Fed Faiz", |
| "Avrupa Merkez Bankası", |
| "Petrol Fiyatları", |
| |
| "hisse senedi haberleri site:tr.investing.com", |
| "ekonomi haberleri site:tr.investing.com", |
| "piyasa analizi site:tr.investing.com", |
| "ABD Çin ticaret site:tr.investing.com", |
| |
| "borsa haberleri site:finance.yahoo.com", |
| "Turkey stock market site:finance.yahoo.com", |
| "BIST analysis site:finance.yahoo.com", |
| "Turkish economy site:finance.yahoo.com", |
| "piyasalar site:yahoo.com" |
| ] |
| |
| |
| news_sources = [ |
| GoogleNewsSource() |
| ] |
| |
| |
| for i, source in enumerate(news_sources): |
| if len(news_items) >= max_results: |
| break |
| |
| |
| source_max_items = 5 |
| |
| log_progress(f"Genel piyasa haberleri alınıyor ({i+1}/{len(news_sources)})...") |
| |
| |
| source_queries = search_queries.copy() |
| |
| for query in source_queries: |
| if len(news_items) >= max_results: |
| break |
| |
| try: |
| |
| if source.name == "Google News": |
| source_news = source.fetch_news(query, cutoff_date, common_names_dict={}, max_items=source_max_items) |
| else: |
| source_news = source.fetch_news(query, cutoff_date, max_items=source_max_items) |
| |
| if source_news: |
| for news in source_news: |
| |
| if news['link'] in processed_links: |
| continue |
| |
| if len(news_items) >= max_results: |
| break |
| |
| |
| summary = news.get('summary', "Özet alınamadı.") |
| if (summary == "Özet alınamadı." or summary == "Özet alınıyor...") and news.get('link'): |
| try: |
| from newspaper import Article, Config |
| |
| |
| news_config = Config() |
| news_config.request_timeout = 10 |
| headers = getattr(source, 'headers', None) |
| news_config.browser_user_agent = headers.get('User-Agent') if isinstance(headers, dict) else None |
| |
| article = Article(news['link'], config=news_config, language='tr') |
| article.download() |
| time.sleep(0.5) |
| article.parse() |
| |
| if article.text and len(article.text.strip()) > 20: |
| summary = article.text.strip()[:200] + "..." |
| elif article.meta_description: |
| summary = article.meta_description.strip()[:200] + "..." |
| except Exception as article_ex: |
| log_progress(f"Haber özeti alınamadı: {article_ex}", is_warning=True, icon="⚠️") |
| |
| |
| news_items.append({ |
| 'title': news.get('title', 'Başlık Yok'), |
| 'source': news.get('source', 'Kaynak Yok'), |
| 'link': news.get('link'), |
| 'summary': summary, |
| 'published_datetime': news.get('published_datetime') |
| }) |
| processed_links.add(news['link']) |
| |
| except Exception as e: |
| log_progress(f"{source.name} üzerinde '{query}' araması sırasında hata: {str(e)}", is_warning=True, icon="⚠️") |
| continue |
| |
| |
| try: |
| news_items.sort(key=lambda x: x.get('published_datetime') or datetime.min, reverse=True) |
| except Exception as sort_e: |
| log_progress(f"Haberler sıralanırken hata: {str(sort_e)}", is_warning=True, icon="⚠️") |
| |
| return news_items[:max_results] |