enesmanan commited on
Commit
b833f77
·
verified ·
1 Parent(s): a258a12

fix gradio

Browse files
app.py CHANGED
@@ -1,156 +1,204 @@
1
- import gradio as gr
 
 
 
2
  import pandas as pd
3
- from scrape.trendyol_scraper import scrape_reviews
4
- from scripts.review_summarizer import ReviewAnalyzer
5
  import plotly.express as px
6
- import plotly.graph_objects as go
7
- import os
8
- import subprocess
9
- import logging
10
- from selenium import webdriver
11
- from selenium.webdriver.chrome.options import Options
12
- from selenium.webdriver.chrome.service import Service
13
-
14
- # Logging ayarları
15
- logging.basicConfig(level=logging.INFO)
16
- logger = logging.getLogger(__name__)
17
-
18
- def setup_chrome():
19
- """Chrome ve ChromeDriver kurulumu"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  try:
21
- # Chromium kullanarak Chrome yerine (Hugging Face'de önceden yüklü)
22
- chrome_options = webdriver.ChromeOptions()
23
- chrome_options.add_argument('--headless')
24
- chrome_options.add_argument('--no-sandbox')
25
- chrome_options.add_argument('--disable-dev-shm-usage')
26
- chrome_options.binary_location = '/usr/bin/chromium' # Chromium path
 
 
 
 
 
 
 
 
 
 
27
 
28
- # ChromeDriver'ı webdriver-manager ile yönet
29
- from webdriver_manager.chrome import ChromeDriverManager
30
- from webdriver_manager.core.utils import ChromeType
 
 
 
31
 
32
- driver_path = ChromeDriverManager(chrome_type=ChromeType.CHROMIUM).install()
33
- logger.info(f"ChromeDriver path: {driver_path}")
 
34
 
35
- # Test et
36
- service = Service(driver_path)
37
- with webdriver.Chrome(service=service, options=chrome_options) as driver:
38
- driver.get("https://www.google.com")
39
- logger.info("Chrome test başarılı!")
40
-
41
- except Exception as e:
42
- logger.error(f"Chrome kurulumunda hata: {str(e)}", exc_info=True)
43
- raise
44
-
45
- class ReviewAnalysisApp:
46
- def __init__(self):
47
- try:
48
- logger.info("Chrome kurulumu başlatılıyor...")
49
- setup_chrome() # Uygulama başlatılırken Chrome'u kur
50
-
51
- logger.info("ReviewAnalyzer başlatılıyor...")
52
- self.analyzer = ReviewAnalyzer()
53
- logger.info("ReviewAnalyzer başarıyla başlatıldı")
54
-
55
- except Exception as e:
56
- logger.error(f"ReviewAnalyzer başlatılırken hata: {str(e)}", exc_info=True) # Tam hata stack'ini göster
57
- self.analyzer = None
58
 
59
- def analyze_url(self, url):
60
- try:
61
- # Analyzer'ın başarıyla başlatılıp başlatılmadığını kontrol et
62
- if self.analyzer is None:
63
- logger.error("Analyzer başlatılamadı")
64
- return "Sistem başlatılamadı. Lütfen daha sonra tekrar deneyin.", None, None, None
65
-
66
- if not url or not url.startswith("https://www.trendyol.com/"):
67
- return "Lütfen geçerli bir Trendyol ürün yorumları URL'si girin.", None, None, None
68
-
69
- logger.info("Yorumlar çekiliyor...")
70
- df = scrape_reviews(url)
71
-
72
- if df.empty:
73
- return "Yorumlar çekilemedi. Lütfen URL'yi kontrol edin.", None, None, None
74
-
75
- logger.info("Sentiment analizi yapılıyor...")
76
- analyzed_df = self.analyzer.analyze_reviews(df)
77
-
78
- if analyzed_df.empty:
79
- return "Sentiment analizi yapılamadı.", None, None, None
80
-
81
- logger.info("Özet oluşturuluyor...")
82
- summary = self.analyzer.generate_summary(analyzed_df)
83
-
84
- logger.info("Grafikler oluşturuluyor...")
85
- fig1 = self.create_sentiment_distribution(analyzed_df)
86
- fig2 = self.create_rating_distribution(analyzed_df)
87
- fig3 = self.create_sentiment_by_rating(analyzed_df)
88
-
89
- return summary, fig1, fig2, fig3
90
-
91
- except Exception as e:
92
- error_msg = f"Analiz sırasında hata oluştu: {str(e)}"
93
- logger.error(error_msg)
94
- return error_msg, None, None, None
95
-
96
- def create_sentiment_distribution(self, df):
97
- fig = px.pie(df,
98
- names='sentiment_label',
99
- title='Duygu Analizi Dağılımı')
100
- return fig
101
 
102
- def create_rating_distribution(self, df):
103
- fig = px.bar(df['Yıldız Sayısı'].value_counts().sort_index(),
104
- title='Yıldız Dağılımı')
105
- fig.update_layout(xaxis_title='Yıldız Sayısı',
106
- yaxis_title='Yorum Sayısı')
107
- return fig
108
 
109
- def create_sentiment_by_rating(self, df):
110
- avg_sentiment = df.groupby('Yıldız Sayısı')['sentiment_score'].mean()
111
- fig = px.line(avg_sentiment,
112
- title='Yıldız Sayısına Göre Ortalama Sentiment Skoru')
113
- fig.update_layout(xaxis_title='Yıldız Sayısı',
114
- yaxis_title='Ortalama Sentiment Skoru')
115
- return fig
116
-
117
- def create_interface():
118
- app = ReviewAnalysisApp()
119
 
120
- with gr.Blocks(theme=gr.themes.Soft()) as interface:
121
- gr.Markdown("# Trendyol Yorum Analizi")
122
-
123
- with gr.Row():
124
- url_input = gr.Textbox(
125
- label="Trendyol Ürün Yorumları URL'si",
126
- placeholder="https://www.trendyol.com/..."
127
- )
128
-
129
- analyze_btn = gr.Button("Analiz Et")
130
-
131
- with gr.Row():
132
- with gr.Column(scale=1):
133
- summary_output = gr.Textbox(
134
- label="Özet",
135
- lines=10
136
- )
137
-
138
- with gr.Column(scale=2):
139
- with gr.Tab("Duygu Analizi"):
140
- sentiment_dist = gr.Plot()
141
- with gr.Tab("Yıldız Dağılımı"):
142
- rating_dist = gr.Plot()
143
- with gr.Tab("Sentiment-Yıldız İlişkisi"):
144
- sentiment_rating = gr.Plot()
145
-
146
- analyze_btn.click(
147
- fn=app.analyze_url,
148
- inputs=[url_input],
149
- outputs=[summary_output, sentiment_dist, rating_dist, sentiment_rating]
150
  )
151
 
152
- return interface
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
 
154
  if __name__ == "__main__":
155
- interface = create_interface()
156
- interface.launch()
 
1
+ import os
2
+ import time
3
+ import requests
4
+ import re
5
  import pandas as pd
 
 
6
  import plotly.express as px
7
+ import gradio as gr
8
+ from dotenv import load_dotenv
9
+ from scripts.review_summarizer import analyze_reviews
10
+
11
+ # Load environment variables
12
+ load_dotenv()
13
+ GEMINI_API_KEY = os.getenv('GEMINI_API_KEY')
14
+
15
+ if not os.path.exists("data"):
16
+ os.makedirs("data")
17
+
18
+ def create_sentiment_plot(df):
19
+ """Creates a pie chart visualization for sentiment distribution"""
20
+ sentiment_counts = df["sentiment_label"].value_counts()
21
+ fig = px.pie(
22
+ values=sentiment_counts.values,
23
+ names=sentiment_counts.index,
24
+ title="Duygu Analizi Dağılımı",
25
+ color_discrete_map={
26
+ "Pozitif": "#2ecc71",
27
+ "Nötr": "#95a5a6",
28
+ "Negatif": "#e74c3c",
29
+ },
30
+ )
31
+ return fig
32
+
33
+ def create_star_plot(df):
34
+ """Creates a bar chart visualization for star rating distribution"""
35
+ star_counts = df["Yıldız Sayısı"].value_counts().sort_index()
36
+ fig = px.bar(
37
+ x=star_counts.index,
38
+ y=star_counts.values,
39
+ title="Yıldız Dağılımı",
40
+ labels={"x": "Yıldız Sayısı", "y": "Yorum Sayısı"},
41
+ color_discrete_sequence=["#f39c12"],
42
+ )
43
+ fig.update_layout(
44
+ xaxis=dict(
45
+ tickmode="array",
46
+ ticktext=["⭐", "⭐⭐", "⭐⭐⭐", "⭐⭐⭐⭐", "⭐⭐⭐⭐⭐"],
47
+ )
48
+ )
49
+ return fig
50
+
51
+ def scrape_product_comments_v2(url):
52
+ headers = {
53
+ "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
54
+ "accept-language": "en-US,en;q=0.9",
55
+ "cache-control": "max-age=0",
56
+ "upgrade-insecure-requests": "1",
57
+ "user-agent": "Mozilla/5.0 (iPad; CPU OS 14_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/129.0 Mobile/15E148 Safari/605.1.15"
58
+ }
59
+
60
+ # Extract product_id using regex
61
+ match = re.search(r"-p-(\d+)", url)
62
+ if not match:
63
+ raise ValueError("Product ID not found in URL")
64
+
65
+ product_id = match.group(1)
66
+ api_url = f"https://apigw.trendyol.com/discovery-web-websfxsocialreviewrating-santral/product-reviews-detailed?contentId={product_id}&page=1&order=DESC&orderBy=Score&channelId=1"
67
+
68
+ def fetch_reviews(api_url, headers):
69
+ all_reviews = []
70
+ response = requests.get(api_url, headers=headers)
71
+ if response.status_code != 200:
72
+ raise ConnectionError(f"Initial request failed: {response.status_code}")
73
+
74
+ data = response.json()
75
+ total_pages = data["result"]["productReviews"]["totalPages"]
76
+ all_reviews.extend(data["result"]["productReviews"]["content"])
77
+
78
+ for page in range(2, total_pages + 1):
79
+ paginated_url = api_url.replace("page=1", f"page={page}")
80
+ response = requests.get(paginated_url, headers=headers)
81
+ if response.status_code == 200:
82
+ page_data = response.json()
83
+ all_reviews.extend(page_data["result"]["productReviews"]["content"])
84
+ else:
85
+ print(f"Failed to fetch page {page}: {response.status_code}")
86
+
87
+ return all_reviews
88
+
89
+ reviews = fetch_reviews(api_url, headers)
90
+ reviews_df = pd.DataFrame(reviews)
91
+ reviews_df = reviews_df.rename(columns={
92
+ "id": "Kullanıcı_id",
93
+ "userFullName": "Kullanıcı Adı",
94
+ "comment": "Yorum",
95
+ "lastModifiedDate": "Tarih",
96
+ "rate": "Yıldız Sayısı"
97
+ })
98
+ reviews_df = reviews_df[["Kullanıcı_id", "Kullanıcı Adı", "Yorum", "Tarih", "Yıldız Sayısı"]]
99
+ return reviews_df
100
+
101
+ def analyze_product(url, progress=gr.Progress()):
102
  try:
103
+ # Fetch reviews
104
+ progress(0.1, desc="Yorumlar çekiliyor...")
105
+ df = scrape_product_comments_v2(url)
106
+
107
+ if df is None or len(df) == 0:
108
+ return None, None, None, None, None, None, None, "Yorumlar çekilemedi. URL'yi kontrol edin."
109
+
110
+ # Save to CSV
111
+ data_path = os.path.join("data", "product_comments.csv")
112
+ df.to_csv(data_path, index=False, encoding="utf-8-sig")
113
+
114
+ # Analyze reviews
115
+ progress(0.4, desc="Yorumlar analiz ediliyor...")
116
+ summary, analyzed_df = analyze_reviews(data_path, GEMINI_API_KEY)
117
+
118
+ progress(0.7, desc="Sonuçlar hazırlanıyor...")
119
 
120
+ # Calculate metrics
121
+ total_reviews = len(df)
122
+ total_analyzed = len(analyzed_df)
123
+ avg_rating = f"{analyzed_df['Yıldız Sayısı'].mean():.1f}⭐"
124
+ positive_ratio = len(analyzed_df[analyzed_df["sentiment_label"] == "Pozitif"]) / len(analyzed_df) * 100
125
+ positive_ratio_str = f"%{positive_ratio:.1f}"
126
 
127
+ # Create plots
128
+ sentiment_plot = create_sentiment_plot(analyzed_df)
129
+ star_plot = create_star_plot(analyzed_df)
130
 
131
+ # Create info message for removed reviews
132
+ removed_reviews = total_reviews - total_analyzed
133
+ info_message = ""
134
+ if removed_reviews > 0:
135
+ info_message = f"Not: Toplam {removed_reviews} adet kargo, teslimat ve satıcı ile ilgili yorum analiz dışı bırakılmıştır."
136
+
137
+ progress(1.0, desc="Analiz tamamlandı!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
 
139
+ return (
140
+ str(total_reviews),
141
+ str(total_analyzed),
142
+ avg_rating,
143
+ positive_ratio_str,
144
+ sentiment_plot,
145
+ star_plot,
146
+ summary,
147
+ info_message
148
+ )
149
+
150
+ except Exception as e:
151
+ return None, None, None, None, None, None, None, f"Bir hata oluştu: {str(e)}"
152
+
153
+ # Create Gradio interface
154
+ with gr.Blocks(title="Trendyol Yorum Analizi") as demo:
155
+ gr.Markdown("""
156
+ # Trendyol Yorum Analizi
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
 
158
+ Bu uygulama, Trendyol ürün sayfasındaki yorumları analiz eder ve özetler.
 
 
 
 
 
159
 
160
+ Kullanım:
161
+ 1. Trendyol ürün yorumlar sayfasının URL'sini girin
162
+ 2. 'Analiz Et' butonuna tıklayın
163
+ """)
 
 
 
 
 
 
164
 
165
+ with gr.Row():
166
+ url_input = gr.Textbox(
167
+ label="Trendyol Ürün Yorumları URL",
168
+ placeholder="ürünün linki"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  )
170
 
171
+ analyze_btn = gr.Button("Analiz Et")
172
+
173
+ with gr.Row():
174
+ total_reviews = gr.Textbox(label="Toplam Yorum")
175
+ total_analyzed = gr.Textbox(label="Ürün Değerlendirme Sayısı")
176
+ avg_rating = gr.Textbox(label="Ortalama Puan")
177
+ positive_ratio = gr.Textbox(label="Olumlu Yorum Oranı")
178
+
179
+ info_message = gr.Markdown()
180
+
181
+ with gr.Row():
182
+ sentiment_plot = gr.Plot()
183
+ star_plot = gr.Plot()
184
+
185
+ summary = gr.Markdown(label="📝 Genel Değerlendirme")
186
+ error_message = gr.Markdown()
187
+
188
+ analyze_btn.click(
189
+ analyze_product,
190
+ inputs=[url_input],
191
+ outputs=[
192
+ total_reviews,
193
+ total_analyzed,
194
+ avg_rating,
195
+ positive_ratio,
196
+ sentiment_plot,
197
+ star_plot,
198
+ summary,
199
+ error_message
200
+ ]
201
+ )
202
 
203
  if __name__ == "__main__":
204
+ demo.launch()
 
requirements.txt CHANGED
@@ -1,19 +1,13 @@
1
- pandas
2
- numpy==1.24.3
3
- seaborn
4
- matplotlib
5
- torch==2.1.2
6
- transformers==4.36.2
7
- nltk
8
- plotly
9
- gradio
10
- selenium
11
- webdriver-manager
12
- tqdm
13
- regex
14
- scikit-learn
15
- google-generativeai
16
- python-dotenv
17
- requests
18
- sentencepiece
19
- protobuf
 
1
+ pandas==2.2.3
2
+ numpy==1.26.4
3
+ torch==2.5.1
4
+ transformers==4.47.0
5
+ nltk==3.8.1
6
+ requests==2.32.3
7
+ google-generativeai==0.8.3
8
+ selenium==4.27.1
9
+ streamlit==1.36.0
10
+ plotly==5.18.0
11
+ python-dotenv==1.0.1
12
+ tqdm==4.67.1
13
+ regex
 
 
 
 
 
 
scripts/data_prp_eda.py CHANGED
@@ -1,357 +1,491 @@
1
- import pandas as pd
2
- import numpy as np
3
- import matplotlib.pyplot as plt
4
- import seaborn as sns
5
- from wordcloud import WordCloud
6
- import re
7
- from collections import Counter
8
- from datetime import datetime
9
- import warnings
10
- from textblob import TextBlob
11
- import nltk
12
- from nltk.corpus import stopwords
13
- from nltk.tokenize import word_tokenize
14
- from nltk.util import ngrams
15
- import requests
16
- import os
17
- warnings.filterwarnings('ignore')
18
- plt.style.use('seaborn')
19
-
20
- nltk.download('stopwords')
21
- nltk.download('punkt')
22
-
23
- class ReviewAnalyzer:
24
- def __init__(self, file_path):
25
- self.df = pd.read_csv(file_path)
26
- self.turkish_stopwords = self.get_turkish_stopwords()
27
-
28
- # Lojistik ve satıcı ile ilgili kelimeleri genişletilmiş liste ile tanımla
29
- self.logistics_seller_words = {
30
- # Kargo ve teslimat ile ilgili
31
- 'kargo', 'kargocu', 'paket', 'paketleme', 'teslimat', 'teslim',
32
- 'gönderi', 'gönderim', 'ulaştı', 'ulaşım', 'geldi', 'kurye',
33
- 'dağıtım', 'hasarlı', 'hasar', 'kutu', 'ambalaj', 'zamanında',
34
- 'geç', 'hızlı', 'yavaş', 'günde', 'saatte',
35
-
36
- # Satıcı ve mağaza ile ilgili
37
- 'satıcı', 'mağaza', 'sipariş', 'trendyol', 'tedarik', 'stok',
38
- 'garanti', 'fatura', 'iade', 'geri', 'müşteri', 'hizmet',
39
- 'destek', 'iletişim', 'şikayet', 'sorun', 'çözüm', 'hediye',
40
-
41
- # Fiyat ve ödeme ile ilgili
42
- 'fiyat', 'ücret', 'para', 'bedava', 'ücretsiz', 'indirim',
43
- 'kampanya', 'taksit', 'ödeme', 'bütçe', 'hesap', 'kur',
44
-
45
- # Zaman ile ilgili teslimat kelimeleri
46
- 'bugün', 'yarın', 'dün', 'hafta', 'gün', 'saat', 'süre',
47
- 'bekleme', 'gecikme', 'erken', 'geç'
48
- }
49
-
50
- # Sentiment analizi için kelimeler
51
- self.positive_words = {
52
- 'güzel', 'harika', 'mükemmel', 'süper', 'iyi', 'muhteşem',
53
- 'teşekkür', 'memnun', 'başarılı', 'kaliteli', 'kusursuz',
54
- 'özgün', 'şahane', 'enfes', 'ideal'
55
- }
56
-
57
- self.negative_words = {
58
- 'kötü', 'berbat', 'rezalet', 'yetersiz', 'başarısız', 'vasat',
59
- 'korkunç', 'düşük', 'zayıf', 'çöp', 'pişman', 'kırık', 'bozuk'
60
- }
61
-
62
- # Türkçe-İngilizce ay çevirisi
63
- self.month_map = {
64
- 'Ocak': 'January', 'Şubat': 'February', 'Mart': 'March',
65
- 'Nisan': 'April', 'Mayıs': 'May', 'Haziran': 'June',
66
- 'Temmuz': 'July', 'Ağustos': 'August', 'Eylül': 'September',
67
- 'Ekim': 'October', 'Kasım': 'November', 'Aralık': 'December'
68
- }
69
-
70
- def get_turkish_stopwords(self):
71
- """Türkçe stop words listesini oluştur"""
72
- turkish_stops = set(stopwords.words('turkish'))
73
-
74
- github_url = "https://raw.githubusercontent.com/sgsinclair/trombone/master/src/main/resources/org/voyanttools/trombone/keywords/stop.tr.turkish-lucene.txt"
75
- try:
76
- response = requests.get(github_url)
77
- if response.status_code == 200:
78
- github_stops = set(word.strip() for word in response.text.split('\n') if word.strip())
79
- turkish_stops.update(github_stops)
80
- except Exception as e:
81
- print(f"GitHub'dan stop words çekilirken hata oluştu: {e}")
82
-
83
- custom_stops = {'bir', 've', 'çok', 'bu', 'de', 'da', 'için', 'ile', 'ben',
84
- 'sen', 'o', 'biz', 'siz', 'onlar', 'bu', 'şu', 'ama', 'fakat',
85
- 'ancak', 'lakin', 'ki', 'dahi', 'mi', 'mı', 'mu', 'mü'}
86
- turkish_stops.update(custom_stops)
87
-
88
- return turkish_stops
89
-
90
- def filter_product_reviews(self):
91
- """Salt ürün yorumlarını filtrele"""
92
- def is_pure_product_review(text):
93
- if not isinstance(text, str):
94
- return False
95
-
96
- text_lower = text.lower()
97
- return not any(word in text_lower for word in self.logistics_seller_words)
98
-
99
- # Filtrelenmiş DataFrame
100
- original_count = len(self.df)
101
- self.df = self.df[self.df['Yorum'].apply(is_pure_product_review)]
102
- filtered_count = len(self.df)
103
-
104
- print(f"\nFiltreleme İstatistikleri:")
105
- print(f"Orijinal yorum sayısı: {original_count}")
106
- print(f"Salt ürün yorumu sayısı: {filtered_count}")
107
- print(f"Çıkarılan yorum sayısı: {original_count - filtered_count}")
108
- print(f"Filtreleme oranı: {((original_count - filtered_count) / original_count * 100):.2f}%")
109
-
110
- print("\nÖrnek Salt Ürün Yorumları:")
111
- sample_reviews = self.df['Yorum'].sample(min(3, len(self.df)))
112
- for idx, review in enumerate(sample_reviews, 1):
113
- print(f"{idx}. {review[:100]}...")
114
-
115
- def convert_turkish_date(self, date_str):
116
- """Türkçe tarihleri İngilizce'ye çevir"""
117
- try:
118
- day, month, year = date_str.split()
119
- english_month = self.month_map[month]
120
- return f"{day} {english_month} {year}"
121
- except:
122
- return None
123
-
124
- def preprocess_text(self, text):
125
- """Metin ön işleme"""
126
- if isinstance(text, str):
127
- text = text.lower()
128
- text = re.sub(r'[^\w\s]', '', text)
129
- text = re.sub(r'\d+', '', text)
130
- text = re.sub(r'\s+', ' ', text).strip()
131
- return text
132
- return ''
133
-
134
-
135
- def analyze_timestamps(self):
136
- """Zaman bazlı analizler"""
137
- # Tarihleri dönüştür
138
- self.df['Tarih'] = self.df['Tarih'].apply(self.convert_turkish_date)
139
- self.df['Tarih'] = pd.to_datetime(self.df['Tarih'], format='%d %B %Y')
140
-
141
- # Günlük dağılım
142
- plt.figure(figsize=(12, 6))
143
- plt.hist(self.df['Tarih'], bins=20, edgecolor='black')
144
- plt.title('Yorumların Zaman İçindeki Dağılımı')
145
- plt.xlabel('Tarih')
146
- plt.ylabel('Yorum Sayısı')
147
- plt.xticks(rotation=45)
148
- plt.tight_layout()
149
- plt.savefig('images/yorum_zaman_dagilimi.png')
150
- plt.close()
151
-
152
- # Aylık dağılım
153
- monthly_reviews = self.df.groupby(self.df['Tarih'].dt.to_period('M')).size()
154
- plt.figure(figsize=(12, 6))
155
- monthly_reviews.plot(kind='bar')
156
- plt.title('Aylık Yorum Dağılımı')
157
- plt.xlabel('Ay')
158
- plt.ylabel('Yorum Sayısı')
159
- plt.xticks(rotation=45)
160
- plt.tight_layout()
161
- plt.savefig('images/aylik_yorum_dagilimi.png')
162
- plt.close()
163
-
164
- # Mevsimsel analiz
165
- self.df['Mevsim'] = self.df['Tarih'].dt.month.map({
166
- 12: 'Kış', 1: 'Kış', 2: 'Kış',
167
- 3: 'İlkbahar', 4: 'İlkbahar', 5: 'İlkbahar',
168
- 6: 'Yaz', 7: 'Yaz', 8: 'Yaz',
169
- 9: 'Sonbahar', 10: 'Sonbahar', 11: 'Sonbahar'
170
- })
171
- seasonal_reviews = self.df.groupby('Mevsim').size()
172
- plt.figure(figsize=(10, 6))
173
- seasonal_reviews.plot(kind='bar')
174
- plt.title('Mevsimsel Yorum Dağılımı')
175
- plt.xlabel('Mevsim')
176
- plt.ylabel('Yorum Sayısı')
177
- plt.tight_layout()
178
- plt.savefig('images/mevsimsel_dagilim.png')
179
- plt.close()
180
-
181
- def analyze_ratings(self):
182
- """Yıldız bazlı analizler"""
183
- plt.figure(figsize=(10, 6))
184
- sns.countplot(data=self.df, x='Yıldız Sayısı')
185
- plt.title('Yıldız Dağılımı')
186
- plt.xlabel('Yıldız Sayısı')
187
- plt.ylabel('Yorum Sayısı')
188
- plt.savefig('images/yildiz_dagilimi.png')
189
- plt.close()
190
-
191
- return {
192
- 'Ortalama Yıldız': self.df['Yıldız Sayısı'].mean(),
193
- 'Medyan Yıldız': self.df['Yıldız Sayısı'].median(),
194
- 'Mod Yıldız': self.df['Yıldız Sayısı'].mode()[0],
195
- 'Standart Sapma': self.df['Yıldız Sayısı'].std()
196
- }
197
-
198
- def create_wordcloud(self):
199
- """Kelime bulutu oluştur"""
200
- all_comments = ' '.join([self.preprocess_text(str(comment))
201
- for comment in self.df['Yorum']])
202
-
203
- words = word_tokenize(all_comments)
204
- filtered_words = [word for word in words
205
- if word not in self.turkish_stopwords]
206
- clean_text = ' '.join(filtered_words)
207
-
208
- wordcloud = WordCloud(
209
- width=800, height=400,
210
- background_color='white',
211
- max_words=100,
212
- font_path='C:/Windows/Fonts/arial.ttf' # Windows varsayılan font
213
- ).generate(clean_text)
214
-
215
- plt.figure(figsize=(15,8))
216
- plt.imshow(wordcloud, interpolation='bilinear')
217
- plt.axis('off')
218
- plt.savefig('images/wordcloud.png')
219
- plt.close()
220
-
221
- def analyze_ngrams(self, max_n=3, top_n=10):
222
- """N-gram analizi"""
223
- all_texts = []
224
- for comment in self.df['Yorum']:
225
- if isinstance(comment, str):
226
- words = self.preprocess_text(comment).split()
227
- filtered_words = [word for word in words
228
- if word not in self.turkish_stopwords]
229
- all_texts.extend(filtered_words)
230
-
231
- for n in range(1, max_n + 1):
232
- print(f"\n{n}-gram Analizi:")
233
-
234
- if n == 1:
235
- ngrams_list = all_texts
236
- else:
237
- ngrams_list = list(ngrams(all_texts, n))
238
-
239
- ngram_freq = Counter(ngrams_list).most_common(top_n)
240
-
241
- if n == 1:
242
- labels = [item[0] for item in ngram_freq]
243
- else:
244
- labels = [' '.join(item[0]) for item in ngram_freq]
245
-
246
- values = [item[1] for item in ngram_freq]
247
-
248
- plt.figure(figsize=(12, 6))
249
- bars = plt.barh(range(len(values)), values)
250
- plt.yticks(range(len(labels)), labels)
251
- plt.title(f'En Sık Kullanılan {n}-gramlar')
252
- plt.xlabel('Frekans')
253
-
254
- for i, bar in enumerate(bars):
255
- width = bar.get_width()
256
- plt.text(width, bar.get_y() + bar.get_height()/2,
257
- f'{int(width)}',
258
- ha='left', va='center', fontweight='bold')
259
-
260
- plt.tight_layout()
261
- plt.savefig(f'images/{n}gram_analizi.png')
262
- plt.close()
263
-
264
- print(f"\nEn sık kullanılan {n}-gramlar:")
265
- for ngram, freq in ngram_freq:
266
- if n == 1:
267
- print(f"{ngram}: {freq}")
268
- else:
269
- print(f"{' '.join(ngram)}: {freq}")
270
-
271
- def analyze_sentiment(self):
272
- """Duygu analizi"""
273
- def count_sentiment_words(text):
274
- if not isinstance(text, str):
275
- return 0, 0
276
-
277
- text_lower = text.lower()
278
- words = text_lower.split()
279
- positive_count = sum(1 for word in words if word in self.positive_words)
280
- negative_count = sum(1 for word in words if word in self.negative_words)
281
- return positive_count, negative_count
282
-
283
- sentiment_counts = self.df['Yorum'].apply(count_sentiment_words)
284
- self.df['Pozitif_Kelime_Sayisi'] = [count[0] for count in sentiment_counts]
285
- self.df['Negatif_Kelime_Sayisi'] = [count[1] for count in sentiment_counts]
286
- self.df['Sentiment_Skor'] = self.df['Pozitif_Kelime_Sayisi'] - self.df['Negatif_Kelime_Sayisi']
287
-
288
- plt.figure(figsize=(10, 6))
289
- sns.boxplot(data=self.df, x='Yıldız Sayısı', y='Sentiment_Skor')
290
- plt.title('Yıldız Sayısı ve Sentiment Skoru İlişkisi')
291
- plt.savefig('images/sentiment_yildiz_iliskisi.png')
292
- plt.close()
293
-
294
- plt.figure(figsize=(10, 6))
295
- plt.hist(self.df['Sentiment_Skor'], bins=20)
296
- plt.title('Sentiment Skor Dağılımı')
297
- plt.xlabel('Sentiment Skoru')
298
- plt.ylabel('Yorum Sayısı')
299
- plt.savefig('images/sentiment_dagilimi.png')
300
- plt.close()
301
-
302
- def analyze_comment_lengths(self):
303
- """Yorum uzunluğu analizi"""
304
- self.df['Yorum_Uzunlugu'] = self.df['Yorum'].str.len()
305
-
306
- plt.figure(figsize=(10, 6))
307
- plt.hist(self.df['Yorum_Uzunlugu'].dropna(), bins=30)
308
- plt.title('Yorum Uzunluğu Dağılımı')
309
- plt.xlabel('Karakter Sayısı')
310
- plt.ylabel('Yorum Sayısı')
311
- plt.savefig('images/yorum_uzunluk_dagilimi.png')
312
- plt.close()
313
-
314
- plt.figure(figsize=(10, 6))
315
- sns.boxplot(data=self.df, x='Yıldız Sayısı', y='Yorum_Uzunlugu')
316
- plt.title('Yıldız Sayısı ve Yorum Uzunluğu İlişkisi')
317
- plt.xlabel('Yıldız')
318
- plt.ylabel('Yorum Uzunluğu (Karakter)')
319
- plt.savefig('images/yildiz_uzunluk_iliskisi.png')
320
- plt.close()
321
-
322
- def run_analysis(self):
323
- """Ana analiz fonksiyonu"""
324
- print("Analiz başlatılıyor...")
325
-
326
- if not os.path.exists('images'):
327
- os.makedirs('images')
328
-
329
- print("\nÜrün odaklı yorum filtresi uygulanıyor...")
330
- self.filter_product_reviews()
331
-
332
- print("\n1. Yorum Uzunluğu Analizi")
333
- self.analyze_comment_lengths()
334
-
335
- print("\n2. Zaman Analizi")
336
- self.analyze_timestamps()
337
-
338
- print("\n3. Yıldız Analizi")
339
- rating_stats = self.analyze_ratings()
340
- print("\nYıldız İstatistikleri:")
341
- for key, value in rating_stats.items():
342
- print(f"{key}: {value:.2f}")
343
-
344
- print("\n4. Kelime Bulutu Oluşturuluyor")
345
- self.create_wordcloud()
346
-
347
- print("\n5. N-gram Analizleri")
348
- self.analyze_ngrams(max_n=3, top_n=10)
349
-
350
- print("\n6. Duygu Analizi")
351
- self.analyze_sentiment()
352
-
353
- print("\nAnaliz tamamlandı! Tüm görseller 'images' klasörüne kaydedildi.")
354
-
355
- if __name__ == "__main__":
356
- analyzer = ReviewAnalyzer('data/macbook_product_comments_with_ratings.csv')
357
- analyzer.run_analysis()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import warnings
4
+ from collections import Counter
5
+ from datetime import datetime
6
+
7
+ import matplotlib.pyplot as plt
8
+ import nltk
9
+ import numpy as np
10
+ import pandas as pd
11
+ import requests
12
+ import seaborn as sns
13
+ from nltk.corpus import stopwords
14
+ from nltk.tokenize import word_tokenize
15
+ from nltk.util import ngrams
16
+ from textblob import TextBlob
17
+ from wordcloud import WordCloud
18
+
19
+ warnings.filterwarnings("ignore")
20
+ plt.style.use("seaborn")
21
+
22
+ nltk.download("stopwords")
23
+ nltk.download("punkt")
24
+
25
+
26
+ class ReviewAnalyzer:
27
+ def __init__(self, file_path):
28
+ self.df = pd.read_csv(file_path)
29
+ self.turkish_stopwords = self.get_turkish_stopwords()
30
+
31
+ # Lojistik ve satıcı ile ilgili kelimeleri genişletilmiş liste ile tanımla
32
+ self.logistics_seller_words = {
33
+ # Kargo ve teslimat ile ilgili
34
+ "kargo",
35
+ "kargocu",
36
+ "paket",
37
+ "paketleme",
38
+ "teslimat",
39
+ "teslim",
40
+ "gönderi",
41
+ "gönderim",
42
+ "ulaştı",
43
+ "ulaşım",
44
+ "geldi",
45
+ "kurye",
46
+ "dağıtım",
47
+ "hasarlı",
48
+ "hasar",
49
+ "kutu",
50
+ "ambalaj",
51
+ "zamanında",
52
+ "geç",
53
+ "hızlı",
54
+ "yavaş",
55
+ "günde",
56
+ "saatte",
57
+ # Satıcı ve mağaza ile ilgili
58
+ "satıcı",
59
+ "mağaza",
60
+ "sipariş",
61
+ "trendyol",
62
+ "tedarik",
63
+ "stok",
64
+ "garanti",
65
+ "fatura",
66
+ "iade",
67
+ "geri",
68
+ "müşteri",
69
+ "hizmet",
70
+ "destek",
71
+ "iletişim",
72
+ "şikayet",
73
+ "sorun",
74
+ "çözüm",
75
+ "hediye",
76
+ # Fiyat ve ödeme ile ilgili
77
+ "fiyat",
78
+ "ücret",
79
+ "para",
80
+ "bedava",
81
+ "ücretsiz",
82
+ "indirim",
83
+ "kampanya",
84
+ "taksit",
85
+ "ödeme",
86
+ "bütçe",
87
+ "hesap",
88
+ "kur",
89
+ # Zaman ile ilgili teslimat kelimeleri
90
+ "bugün",
91
+ "yarın",
92
+ "dün",
93
+ "hafta",
94
+ "gün",
95
+ "saat",
96
+ "süre",
97
+ "bekleme",
98
+ "gecikme",
99
+ "erken",
100
+ "geç",
101
+ }
102
+
103
+ # Sentiment analizi için kelimeler
104
+ self.positive_words = {
105
+ "güzel",
106
+ "harika",
107
+ "mükemmel",
108
+ "süper",
109
+ "iyi",
110
+ "muhteşem",
111
+ "teşekkür",
112
+ "memnun",
113
+ "başarılı",
114
+ "kaliteli",
115
+ "kusursuz",
116
+ "özgün",
117
+ "şahane",
118
+ "enfes",
119
+ "ideal",
120
+ }
121
+
122
+ self.negative_words = {
123
+ "kötü",
124
+ "berbat",
125
+ "rezalet",
126
+ "yetersiz",
127
+ "başarısız",
128
+ "vasat",
129
+ "korkunç",
130
+ "düşük",
131
+ "zayıf",
132
+ "çöp",
133
+ "pişman",
134
+ "kırık",
135
+ "bozuk",
136
+ }
137
+
138
+ # Türkçe-İngilizce ay çevirisi
139
+ self.month_map = {
140
+ "Ocak": "January",
141
+ "Şubat": "February",
142
+ "Mart": "March",
143
+ "Nisan": "April",
144
+ "Mayıs": "May",
145
+ "Haziran": "June",
146
+ "Temmuz": "July",
147
+ "Ağustos": "August",
148
+ "Eylül": "September",
149
+ "Ekim": "October",
150
+ "Kasım": "November",
151
+ "Aralık": "December",
152
+ }
153
+
154
+ def get_turkish_stopwords(self):
155
+ """Türkçe stop words listesini oluştur"""
156
+ turkish_stops = set(stopwords.words("turkish"))
157
+
158
+ github_url = "https://raw.githubusercontent.com/sgsinclair/trombone/master/src/main/resources/org/voyanttools/trombone/keywords/stop.tr.turkish-lucene.txt"
159
+ try:
160
+ response = requests.get(github_url)
161
+ if response.status_code == 200:
162
+ github_stops = set(
163
+ word.strip() for word in response.text.split("\n") if word.strip()
164
+ )
165
+ turkish_stops.update(github_stops)
166
+ except Exception as e:
167
+ print(f"GitHub'dan stop words çekilirken hata oluştu: {e}")
168
+
169
+ custom_stops = {
170
+ "bir",
171
+ "ve",
172
+ "çok",
173
+ "bu",
174
+ "de",
175
+ "da",
176
+ "için",
177
+ "ile",
178
+ "ben",
179
+ "sen",
180
+ "o",
181
+ "biz",
182
+ "siz",
183
+ "onlar",
184
+ "bu",
185
+ "şu",
186
+ "ama",
187
+ "fakat",
188
+ "ancak",
189
+ "lakin",
190
+ "ki",
191
+ "dahi",
192
+ "mi",
193
+ "mı",
194
+ "mu",
195
+ "mü",
196
+ }
197
+ turkish_stops.update(custom_stops)
198
+
199
+ return turkish_stops
200
+
201
+ def filter_product_reviews(self):
202
+ """Salt ürün yorumlarını filtrele"""
203
+
204
+ def is_pure_product_review(text):
205
+ if not isinstance(text, str):
206
+ return False
207
+
208
+ text_lower = text.lower()
209
+ return not any(word in text_lower for word in self.logistics_seller_words)
210
+
211
+ # Filtrelenmiş DataFrame
212
+ original_count = len(self.df)
213
+ self.df = self.df[self.df["Yorum"].apply(is_pure_product_review)]
214
+ filtered_count = len(self.df)
215
+
216
+ print(f"\nFiltreleme İstatistikleri:")
217
+ print(f"Orijinal yorum sayısı: {original_count}")
218
+ print(f"Salt ürün yorumu sayısı: {filtered_count}")
219
+ print(f"Çıkarılan yorum sayısı: {original_count - filtered_count}")
220
+ print(
221
+ f"Filtreleme oranı: {((original_count - filtered_count) / original_count * 100):.2f}%"
222
+ )
223
+
224
+ print("\nÖrnek Salt Ürün Yorumları:")
225
+ sample_reviews = self.df["Yorum"].sample(min(3, len(self.df)))
226
+ for idx, review in enumerate(sample_reviews, 1):
227
+ print(f"{idx}. {review[:100]}...")
228
+
229
+ def convert_turkish_date(self, date_str):
230
+ """Türkçe tarihleri İngilizce'ye çevir"""
231
+ try:
232
+ day, month, year = date_str.split()
233
+ english_month = self.month_map[month]
234
+ return f"{day} {english_month} {year}"
235
+ except:
236
+ return None
237
+
238
+ def preprocess_text(self, text):
239
+ """Metin ön işleme"""
240
+ if isinstance(text, str):
241
+ text = text.lower()
242
+ text = re.sub(r"[^\w\s]", "", text)
243
+ text = re.sub(r"\d+", "", text)
244
+ text = re.sub(r"\s+", " ", text).strip()
245
+ return text
246
+ return ""
247
+
248
+ def analyze_timestamps(self):
249
+ """Zaman bazlı analizler"""
250
+ # Tarihleri dönüştür
251
+ self.df["Tarih"] = self.df["Tarih"].apply(self.convert_turkish_date)
252
+ self.df["Tarih"] = pd.to_datetime(self.df["Tarih"], format="%d %B %Y")
253
+
254
+ # Günlük dağılım
255
+ plt.figure(figsize=(12, 6))
256
+ plt.hist(self.df["Tarih"], bins=20, edgecolor="black")
257
+ plt.title("Yorumların Zaman İçindeki Dağılımı")
258
+ plt.xlabel("Tarih")
259
+ plt.ylabel("Yorum Sayısı")
260
+ plt.xticks(rotation=45)
261
+ plt.tight_layout()
262
+ plt.savefig("images/yorum_zaman_dagilimi.png")
263
+ plt.close()
264
+
265
+ # Aylık dağılım
266
+ monthly_reviews = self.df.groupby(self.df["Tarih"].dt.to_period("M")).size()
267
+ plt.figure(figsize=(12, 6))
268
+ monthly_reviews.plot(kind="bar")
269
+ plt.title("Aylık Yorum Dağılımı")
270
+ plt.xlabel("Ay")
271
+ plt.ylabel("Yorum Sayısı")
272
+ plt.xticks(rotation=45)
273
+ plt.tight_layout()
274
+ plt.savefig("images/aylik_yorum_dagilimi.png")
275
+ plt.close()
276
+
277
+ # Mevsimsel analiz
278
+ self.df["Mevsim"] = self.df["Tarih"].dt.month.map(
279
+ {
280
+ 12: "Kış",
281
+ 1: "Kış",
282
+ 2: "Kış",
283
+ 3: "İlkbahar",
284
+ 4: "İlkbahar",
285
+ 5: "İlkbahar",
286
+ 6: "Yaz",
287
+ 7: "Yaz",
288
+ 8: "Yaz",
289
+ 9: "Sonbahar",
290
+ 10: "Sonbahar",
291
+ 11: "Sonbahar",
292
+ }
293
+ )
294
+ seasonal_reviews = self.df.groupby("Mevsim").size()
295
+ plt.figure(figsize=(10, 6))
296
+ seasonal_reviews.plot(kind="bar")
297
+ plt.title("Mevsimsel Yorum Dağılımı")
298
+ plt.xlabel("Mevsim")
299
+ plt.ylabel("Yorum Sayısı")
300
+ plt.tight_layout()
301
+ plt.savefig("images/mevsimsel_dagilim.png")
302
+ plt.close()
303
+
304
+ def analyze_ratings(self):
305
+ """Yıldız bazlı analizler"""
306
+ plt.figure(figsize=(10, 6))
307
+ sns.countplot(data=self.df, x="Yıldız Sayısı")
308
+ plt.title("Yıldız Dağılımı")
309
+ plt.xlabel("Yıldız Sayısı")
310
+ plt.ylabel("Yorum Sayısı")
311
+ plt.savefig("images/yildiz_dagilimi.png")
312
+ plt.close()
313
+
314
+ return {
315
+ "Ortalama Yıldız": self.df["Yıldız Sayısı"].mean(),
316
+ "Medyan Yıldız": self.df["Yıldız Sayısı"].median(),
317
+ "Mod Yıldız": self.df["Yıldız Sayısı"].mode()[0],
318
+ "Standart Sapma": self.df["Yıldız Sayısı"].std(),
319
+ }
320
+
321
+ def create_wordcloud(self):
322
+ """Kelime bulutu oluştur"""
323
+ all_comments = " ".join(
324
+ [self.preprocess_text(str(comment)) for comment in self.df["Yorum"]]
325
+ )
326
+
327
+ words = word_tokenize(all_comments)
328
+ filtered_words = [word for word in words if word not in self.turkish_stopwords]
329
+ clean_text = " ".join(filtered_words)
330
+
331
+ wordcloud = WordCloud(
332
+ width=800,
333
+ height=400,
334
+ background_color="white",
335
+ max_words=100,
336
+ font_path="C:/Windows/Fonts/arial.ttf", # Windows varsayılan font
337
+ ).generate(clean_text)
338
+
339
+ plt.figure(figsize=(15, 8))
340
+ plt.imshow(wordcloud, interpolation="bilinear")
341
+ plt.axis("off")
342
+ plt.savefig("images/wordcloud.png")
343
+ plt.close()
344
+
345
+ def analyze_ngrams(self, max_n=3, top_n=10):
346
+ """N-gram analizi"""
347
+ all_texts = []
348
+ for comment in self.df["Yorum"]:
349
+ if isinstance(comment, str):
350
+ words = self.preprocess_text(comment).split()
351
+ filtered_words = [
352
+ word for word in words if word not in self.turkish_stopwords
353
+ ]
354
+ all_texts.extend(filtered_words)
355
+
356
+ for n in range(1, max_n + 1):
357
+ print(f"\n{n}-gram Analizi:")
358
+
359
+ if n == 1:
360
+ ngrams_list = all_texts
361
+ else:
362
+ ngrams_list = list(ngrams(all_texts, n))
363
+
364
+ ngram_freq = Counter(ngrams_list).most_common(top_n)
365
+
366
+ if n == 1:
367
+ labels = [item[0] for item in ngram_freq]
368
+ else:
369
+ labels = [" ".join(item[0]) for item in ngram_freq]
370
+
371
+ values = [item[1] for item in ngram_freq]
372
+
373
+ plt.figure(figsize=(12, 6))
374
+ bars = plt.barh(range(len(values)), values)
375
+ plt.yticks(range(len(labels)), labels)
376
+ plt.title(f"En Sık Kullanılan {n}-gramlar")
377
+ plt.xlabel("Frekans")
378
+
379
+ for i, bar in enumerate(bars):
380
+ width = bar.get_width()
381
+ plt.text(
382
+ width,
383
+ bar.get_y() + bar.get_height() / 2,
384
+ f"{int(width)}",
385
+ ha="left",
386
+ va="center",
387
+ fontweight="bold",
388
+ )
389
+
390
+ plt.tight_layout()
391
+ plt.savefig(f"images/{n}gram_analizi.png")
392
+ plt.close()
393
+
394
+ print(f"\nEn sık kullanılan {n}-gramlar:")
395
+ for ngram, freq in ngram_freq:
396
+ if n == 1:
397
+ print(f"{ngram}: {freq}")
398
+ else:
399
+ print(f"{' '.join(ngram)}: {freq}")
400
+
401
+ def analyze_sentiment(self):
402
+ """Duygu analizi"""
403
+
404
+ def count_sentiment_words(text):
405
+ if not isinstance(text, str):
406
+ return 0, 0
407
+
408
+ text_lower = text.lower()
409
+ words = text_lower.split()
410
+ positive_count = sum(1 for word in words if word in self.positive_words)
411
+ negative_count = sum(1 for word in words if word in self.negative_words)
412
+ return positive_count, negative_count
413
+
414
+ sentiment_counts = self.df["Yorum"].apply(count_sentiment_words)
415
+ self.df["Pozitif_Kelime_Sayisi"] = [count[0] for count in sentiment_counts]
416
+ self.df["Negatif_Kelime_Sayisi"] = [count[1] for count in sentiment_counts]
417
+ self.df["Sentiment_Skor"] = (
418
+ self.df["Pozitif_Kelime_Sayisi"] - self.df["Negatif_Kelime_Sayisi"]
419
+ )
420
+
421
+ plt.figure(figsize=(10, 6))
422
+ sns.boxplot(data=self.df, x="Yıldız Sayısı", y="Sentiment_Skor")
423
+ plt.title("Yıldız Sayısı ve Sentiment Skoru İlişkisi")
424
+ plt.savefig("images/sentiment_yildiz_iliskisi.png")
425
+ plt.close()
426
+
427
+ plt.figure(figsize=(10, 6))
428
+ plt.hist(self.df["Sentiment_Skor"], bins=20)
429
+ plt.title("Sentiment Skor Dağılımı")
430
+ plt.xlabel("Sentiment Skoru")
431
+ plt.ylabel("Yorum Sayısı")
432
+ plt.savefig("images/sentiment_dagilimi.png")
433
+ plt.close()
434
+
435
+ def analyze_comment_lengths(self):
436
+ """Yorum uzunluğu analizi"""
437
+ self.df["Yorum_Uzunlugu"] = self.df["Yorum"].str.len()
438
+
439
+ plt.figure(figsize=(10, 6))
440
+ plt.hist(self.df["Yorum_Uzunlugu"].dropna(), bins=30)
441
+ plt.title("Yorum Uzunluğu Dağılımı")
442
+ plt.xlabel("Karakter Sayısı")
443
+ plt.ylabel("Yorum Sayısı")
444
+ plt.savefig("images/yorum_uzunluk_dagilimi.png")
445
+ plt.close()
446
+
447
+ plt.figure(figsize=(10, 6))
448
+ sns.boxplot(data=self.df, x="Yıldız Sayısı", y="Yorum_Uzunlugu")
449
+ plt.title("Yıldız Sayısı ve Yorum Uzunluğu İlişkisi")
450
+ plt.xlabel("Yıldız")
451
+ plt.ylabel("Yorum Uzunluğu (Karakter)")
452
+ plt.savefig("images/yildiz_uzunluk_iliskisi.png")
453
+ plt.close()
454
+
455
+ def run_analysis(self):
456
+ """Ana analiz fonksiyonu"""
457
+ print("Analiz başlatılıyor...")
458
+
459
+ if not os.path.exists("images"):
460
+ os.makedirs("images")
461
+
462
+ print("\nÜrün odaklı yorum filtresi uygulanıyor...")
463
+ self.filter_product_reviews()
464
+
465
+ print("\n1. Yorum Uzunluğu Analizi")
466
+ self.analyze_comment_lengths()
467
+
468
+ print("\n2. Zaman Analizi")
469
+ self.analyze_timestamps()
470
+
471
+ print("\n3. Yıldız Analizi")
472
+ rating_stats = self.analyze_ratings()
473
+ print("\nYıldız İstatistikleri:")
474
+ for key, value in rating_stats.items():
475
+ print(f"{key}: {value:.2f}")
476
+
477
+ print("\n4. Kelime Bulutu Oluşturuluyor")
478
+ self.create_wordcloud()
479
+
480
+ print("\n5. N-gram Analizleri")
481
+ self.analyze_ngrams(max_n=3, top_n=10)
482
+
483
+ print("\n6. Duygu Analizi")
484
+ self.analyze_sentiment()
485
+
486
+ print("\nAnaliz tamamlandı! Tüm görseller 'images' klasörüne kaydedildi.")
487
+
488
+
489
+ if __name__ == "__main__":
490
+ analyzer = ReviewAnalyzer("data/macbook_product_comments_with_ratings.csv")
491
+ analyzer.run_analysis()
scripts/review_summarizer.py CHANGED
@@ -1,291 +1,323 @@
1
- import pandas as pd
2
- import numpy as np
3
- from transformers import (
4
- AutoTokenizer,
5
- AutoModelForSequenceClassification
6
- )
7
- import torch
8
- import os
9
- import requests
10
- from collections import Counter
11
- import warnings
12
- from nltk.tokenize import word_tokenize
13
- import nltk
14
- import re
15
- import google.generativeai as genai
16
- from dotenv import load_dotenv
17
- import logging
18
-
19
- warnings.filterwarnings('ignore')
20
-
21
- # NLTK indirmelerini try-except bloğuna alalım
22
- try:
23
- nltk.download('stopwords', quiet=True)
24
- nltk.download('punkt', quiet=True)
25
- except:
26
- print("NLTK dosyaları indirilemedi, devam ediliyor...")
27
-
28
- logger = logging.getLogger(__name__)
29
-
30
- class ReviewAnalyzer:
31
- def __init__(self):
32
- try:
33
- # Load environment variables
34
- load_dotenv()
35
-
36
- # Configure Gemini API
37
- api_key = os.getenv('GOOGLE_API_KEY')
38
- if not api_key:
39
- raise ValueError("GOOGLE_API_KEY bulunamadı")
40
-
41
- genai.configure(api_key=api_key)
42
- self.model = genai.GenerativeModel('gemini-pro')
43
-
44
- # Sentiment model kurulumu
45
- self.setup_sentiment_model()
46
-
47
- # Stop words yükleme
48
- self.turkish_stopwords = self.get_turkish_stopwords()
49
-
50
- logger.info("ReviewAnalyzer başarıyla başlatıldı")
51
-
52
- except Exception as e:
53
- logger.error(f"ReviewAnalyzer başlatılırken hata: {str(e)}")
54
- raise
55
-
56
- # Lojistik ve satıcı ile ilgili kelimeleri tanımla
57
- self.logistics_seller_words = {
58
- # Kargo ve teslimat ile ilgili
59
- 'kargo', 'kargocu', 'paket', 'paketleme', 'teslimat', 'teslim',
60
- 'gönderi', 'gönderim', 'ulaştı', 'ulaşım', 'geldi', 'kurye',
61
- 'dağıtım', 'hasarlı', 'hasar', 'kutu', 'ambalaj', 'zamanında',
62
- 'geç', 'hızlı', 'yavaş', 'günde', 'saatte',
63
-
64
- # Satıcı ve mağaza ile ilgili
65
- 'satıcı', 'mağaza', 'sipariş', 'trendyol', 'tedarik', 'stok',
66
- 'garanti', 'fatura', 'iade', 'geri', 'müşteri', 'hizmet',
67
- 'destek', 'iletişim', 'şikayet', 'sorun', 'çözüm', 'hediye',
68
-
69
- # Fiyat ve ödeme ile ilgili
70
- 'fiyat', 'ücret', 'para', 'bedava', 'ücretsiz', 'indirim',
71
- 'kampanya', 'taksit', 'ödeme', 'bütçe', 'hesap', 'kur',
72
-
73
- # Zaman ile ilgili teslimat kelimeleri
74
- 'bugün', 'yarın', 'dün', 'hafta', 'gün', 'saat', 'süre',
75
- 'bekleme', 'gecikme', 'erken', 'geç'
76
- }
77
-
78
- def get_turkish_stopwords(self):
79
- """Genişletilmiş stop words listesini hazırla"""
80
- github_url = "https://raw.githubusercontent.com/sgsinclair/trombone/master/src/main/resources/org/voyanttools/trombone/keywords/stop.tr.turkish-lucene.txt"
81
- stop_words = set()
82
-
83
- try:
84
- response = requests.get(github_url)
85
- if response.status_code == 200:
86
- github_stops = set(word.strip() for word in response.text.split('\n') if word.strip())
87
- stop_words.update(github_stops)
88
- except Exception as e:
89
- print(f"GitHub'dan stop words çekilirken hata oluştu: {e}")
90
-
91
- stop_words.update(set(nltk.corpus.stopwords.words('turkish')))
92
-
93
- additional_stops = {'bir', 've', 'çok', 'bu', 'de', 'da', 'için', 'ile', 'ben', 'sen',
94
- 'o', 'biz', 'siz', 'onlar', 'bu', 'şu', 'ama', 'fakat', 'ancak',
95
- 'lakin', 'ki', 'dahi', 'mi', 'mı', 'mu', 'mü', 'var', 'yok',
96
- 'olan', 'içinde', 'üzerinde', 'bana', 'sana', 'ona', 'bize',
97
- 'size', 'onlara', 'evet', 'hayır', 'tamam', 'oldu', 'olmuş',
98
- 'olacak', 'etmek', 'yapmak', 'kez', 'kere', 'defa', 'adet'}
99
- stop_words.update(additional_stops)
100
-
101
- print(f"Toplam {len(stop_words)} adet stop words yüklendi.")
102
- return stop_words
103
-
104
- def preprocess_text(self, text):
105
- """Metin ön işleme"""
106
- if isinstance(text, str):
107
- # Küçük harfe çevir
108
- text = text.lower()
109
- # Özel karakterleri temizle
110
- text = re.sub(r'[^\w\s]', '', text)
111
- # Sayıları temizle
112
- text = re.sub(r'\d+', '', text)
113
- # Fazla boşlukları temizle
114
- text = re.sub(r'\s+', ' ', text).strip()
115
- # Stop words'leri çıkar
116
- words = text.split()
117
- words = [word for word in words if word not in self.turkish_stopwords]
118
- return ' '.join(words)
119
- return ''
120
-
121
- def setup_sentiment_model(self):
122
- """Sentiment analiz modelini hazırla"""
123
- try:
124
- self.device = "cuda" if torch.cuda.is_available() else "cpu"
125
- logger.info(f"Using device for sentiment: {self.device}")
126
-
127
- model_name = "savasy/bert-base-turkish-sentiment-cased"
128
-
129
- logger.info(f"Tokenizer yükleniyor: {model_name}")
130
- self.sentiment_tokenizer = AutoTokenizer.from_pretrained(model_name)
131
-
132
- logger.info(f"Model yükleniyor: {model_name}")
133
- self.sentiment_model = (
134
- AutoModelForSequenceClassification.from_pretrained(model_name)
135
- .to(self.device)
136
- .to(torch.float32)
137
- )
138
- logger.info("Sentiment model başarıyla yüklendi")
139
-
140
- except Exception as e:
141
- logger.error(f"Sentiment model kurulumunda hata: {str(e)}", exc_info=True)
142
- raise
143
-
144
- def filter_reviews(self, df):
145
- """Ürün ile ilgili olmayan yorumları filtrele"""
146
- def is_product_review(text):
147
- if not isinstance(text, str):
148
- return False
149
- return not any(word in text.lower() for word in self.logistics_seller_words)
150
-
151
- filtered_df = df[df['Yorum'].apply(is_product_review)].copy()
152
-
153
- print(f"\nFiltreleme İstatistikleri:")
154
- print(f"Toplam yorum sayısı: {len(df)}")
155
- print(f"Ürün yorumu sayısı: {len(filtered_df)}")
156
- print(f"Filtrelenen yorum sayısı: {len(df) - len(filtered_df)}")
157
- print(f"Filtreleme oranı: {((len(df) - len(filtered_df)) / len(df) * 100):.2f}%")
158
-
159
- return filtered_df
160
-
161
- def analyze_sentiment(self, df):
162
- """Sentiment analizi yap"""
163
- def predict_sentiment(text):
164
- if not isinstance(text, str) or len(text.strip()) == 0:
165
- return {"label": "Nötr", "score": 0.5}
166
-
167
- try:
168
- cleaned_text = self.preprocess_text(text)
169
-
170
- inputs = self.sentiment_tokenizer(
171
- cleaned_text,
172
- return_tensors="pt",
173
- truncation=True,
174
- max_length=512,
175
- padding=True
176
- ).to(self.device)
177
-
178
- with torch.no_grad():
179
- outputs = self.sentiment_model(**inputs)
180
- probs = torch.nn.functional.softmax(outputs.logits, dim=1)
181
- prediction = probs.cpu().numpy()[0]
182
-
183
- score = float(prediction[1])
184
-
185
- if score > 0.75:
186
- label = "Pozitif"
187
- elif score < 0.25:
188
- label = "Negatif"
189
- elif score > 0.55:
190
- label = "Pozitif"
191
- elif score < 0.45:
192
- label = "Negatif"
193
- else:
194
- label = "Nötr"
195
-
196
- return {"label": label, "score": score}
197
-
198
- except Exception as e:
199
- print(f"Error in sentiment prediction: {e}")
200
- return {"label": "Nötr", "score": 0.5}
201
-
202
- print("\nSentiment analizi yapılıyor...")
203
- results = [predict_sentiment(text) for text in df['Yorum']]
204
-
205
- df['sentiment_score'] = [r['score'] for r in results]
206
- df['sentiment_label'] = [r['label'] for r in results]
207
- df['cleaned_text'] = df['Yorum'].apply(self.preprocess_text)
208
-
209
- return df
210
-
211
- def get_key_phrases(self, text_series):
212
- """En önemli anahtar kelimeleri bul"""
213
- text = ' '.join(text_series.astype(str))
214
- words = self.preprocess_text(text).split()
215
- word_freq = Counter(words)
216
- # En az 3 kez geçen kelimeleri al
217
- return {word: count for word, count in word_freq.items()
218
- if count >= 3 and len(word) > 2}
219
-
220
- def generate_summary(self, df):
221
- """Yorumları özetle"""
222
- # Yorumları ve yıldızları birleştir
223
- reviews_with_ratings = [
224
- f"Yıldız: {row['Yıldız Sayısı']}, Yorum: {row['Yorum']}"
225
- for _, row in df.iterrows()
226
- ]
227
-
228
- # Prompt hazırla
229
- prompt = f"""
230
- Aşağıdaki ürün yorumlarını analiz edip özet çıkar:
231
-
232
- {reviews_with_ratings[:50]} # İlk 50 yorumu al (API limiti için)
233
-
234
- Lütfen şu başlıklar altında özetle:
235
- 1. Genel Değerlendirme
236
- 2. Olumlu Yönler
237
- 3. Olumsuz Yönler
238
- 4. Öneriler
239
-
240
- Önemli: Yanıtını Türkçe olarak ver ve madde madde listele.
241
- """
242
-
243
- try:
244
- response = self.model.generate_content(prompt)
245
- summary = response.text
246
- except Exception as e:
247
- summary = f"Özet oluşturulurken hata oluştu: {str(e)}"
248
-
249
- return summary
250
-
251
- def analyze_reviews(self, df):
252
- """Tüm yorumları analiz et"""
253
- try:
254
- # Yorumları filtrele
255
- filtered_df = self.filter_reviews(df)
256
-
257
- # Sentiment analizi yap
258
- analyzed_df = self.analyze_sentiment(filtered_df)
259
-
260
- return analyzed_df
261
-
262
- except Exception as e:
263
- print(f"Analiz sırasında hata oluştu: {str(e)}")
264
- return pd.DataFrame()
265
-
266
- def analyze_reviews(file_path):
267
- df = pd.read_csv(file_path)
268
-
269
- analyzer = ReviewAnalyzer()
270
-
271
- filtered_df = analyzer.filter_reviews(df)
272
-
273
- print("Sentiment analizi başlatılıyor...")
274
- analyzed_df = analyzer.analyze_sentiment(filtered_df)
275
-
276
- analyzed_df.to_csv('sentiment_analyzed_reviews.csv', index=False, encoding='utf-8-sig')
277
- print("Sentiment analizi tamamlandı ve kaydedildi.")
278
-
279
- print("\nÜrün özeti oluşturuluyor...")
280
- summary = analyzer.generate_summary(analyzed_df)
281
-
282
- with open('urun_ozeti.txt', 'w', encoding='utf-8') as f:
283
- f.write(summary)
284
-
285
- print("\nÜrün Özeti:")
286
- print("-" * 50)
287
- print(summary)
288
- print("\nÖzet 'urun_ozeti.txt' dosyasına kaydedildi.")
289
-
290
- if __name__ == "__main__":
291
- analyze_reviews('data/macbook_product_comments_with_ratings.csv')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import warnings
4
+ from collections import Counter
5
+
6
+ import google.generativeai as genai
7
+ import nltk
8
+ import numpy as np
9
+ import pandas as pd
10
+ import requests
11
+ import torch
12
+ from nltk.tokenize import word_tokenize
13
+ from transformers import AutoModelForSequenceClassification, AutoTokenizer
14
+
15
+ warnings.filterwarnings("ignore")
16
+
17
+ nltk.download("stopwords", quiet=True)
18
+ nltk.download("punkt", quiet=True)
19
+
20
+
21
+ class ReviewAnalyzer:
22
+ def __init__(self, gemini_api_key):
23
+ self.turkish_stopwords = self.get_turkish_stopwords()
24
+ self.setup_sentiment_model()
25
+ self.setup_gemini_model(gemini_api_key)
26
+
27
+ self.logistics_seller_words = {
28
+ "kargo",
29
+ "kargocu",
30
+ "paket",
31
+ "paketleme",
32
+ "teslimat",
33
+ "teslim",
34
+ "gönderi",
35
+ "gönderim",
36
+ "ulaştı",
37
+ "ulaşım",
38
+ "geldi",
39
+ "kurye",
40
+ "dağıtım",
41
+ "hasarlı",
42
+ "hasar",
43
+ "kutu",
44
+ "ambalaj",
45
+ "zamanında",
46
+ "geç",
47
+ "hızlı",
48
+ "yavaş",
49
+ "günde",
50
+ "saatte",
51
+ "satıcı",
52
+ "mağaza",
53
+ "sipariş",
54
+ "trendyol",
55
+ "tedarik",
56
+ "stok",
57
+ "garanti",
58
+ "fatura",
59
+ "iade",
60
+ "geri",
61
+ "müşteri",
62
+ "hizmet",
63
+ "destek",
64
+ "iletişim",
65
+ "şikayet",
66
+ "sorun",
67
+ "çözüm",
68
+ "hediye",
69
+ "fiyat",
70
+ "ücret",
71
+ "para",
72
+ "bedava",
73
+ "ücretsiz",
74
+ "indirim",
75
+ "kampanya",
76
+ "taksit",
77
+ "ödeme",
78
+ "bütçe",
79
+ "hesap",
80
+ "kur",
81
+ "bugün",
82
+ "yarın",
83
+ "dün",
84
+ "hafta",
85
+ "gün",
86
+ "saat",
87
+ "süre",
88
+ "bekleme",
89
+ "gecikme",
90
+ "erken",
91
+ "geç",
92
+ }
93
+
94
+ def get_turkish_stopwords(self):
95
+ """Türkçe stop words listesi oluştur"""
96
+ github_url = "https://raw.githubusercontent.com/sgsinclair/trombone/master/src/main/resources/org/voyanttools/trombone/keywords/stop.tr.turkish-lucene.txt"
97
+ stop_words = set()
98
+
99
+ try:
100
+ response = requests.get(github_url)
101
+ if response.status_code == 200:
102
+ github_stops = set(
103
+ word.strip() for word in response.text.split("\n") if word.strip()
104
+ )
105
+ stop_words.update(github_stops)
106
+ except Exception as e:
107
+ print(f"GitHub'dan stop words çekilirken hata oluştu: {e}")
108
+
109
+ stop_words.update(set(nltk.corpus.stopwords.words("turkish")))
110
+
111
+ additional_stops = {
112
+ "bir",
113
+ "ve",
114
+ "çok",
115
+ "bu",
116
+ "de",
117
+ "da",
118
+ "için",
119
+ "ile",
120
+ "ben",
121
+ "sen",
122
+ "o",
123
+ "biz",
124
+ "siz",
125
+ "onlar",
126
+ "bu",
127
+ "şu",
128
+ "ama",
129
+ "fakat",
130
+ "ancak",
131
+ "lakin",
132
+ "ki",
133
+ "dahi",
134
+ "mi",
135
+ "mı",
136
+ "mu",
137
+ "mü",
138
+ "var",
139
+ "yok",
140
+ "olan",
141
+ "içinde",
142
+ "üzerinde",
143
+ "bana",
144
+ "sana",
145
+ "ona",
146
+ "bize",
147
+ "size",
148
+ "onlara",
149
+ "evet",
150
+ "hayır",
151
+ "tamam",
152
+ "oldu",
153
+ "olmuş",
154
+ "olacak",
155
+ "etmek",
156
+ "yapmak",
157
+ "kez",
158
+ "kere",
159
+ "defa",
160
+ "adet",
161
+ }
162
+ stop_words.update(additional_stops)
163
+
164
+ print(f"Toplam {len(stop_words)} adet stop words yüklendi.")
165
+ return stop_words
166
+
167
+ def preprocess_text(self, text):
168
+ if isinstance(text, str):
169
+ text = text.lower()
170
+ text = re.sub(r"[^\w\s]", "", text)
171
+ text = re.sub(r"\d+", "", text)
172
+ text = re.sub(r"\s+", " ", text).strip()
173
+ words = text.split()
174
+ words = [word for word in words if word not in self.turkish_stopwords]
175
+ return " ".join(words)
176
+ return ""
177
+
178
+ def setup_sentiment_model(self):
179
+ self.device = "cuda" if torch.cuda.is_available() else "cpu"
180
+ print(f"Using device for sentiment: {self.device}")
181
+
182
+ model_name = "savasy/bert-base-turkish-sentiment-cased"
183
+ self.sentiment_tokenizer = AutoTokenizer.from_pretrained(model_name)
184
+ self.sentiment_model = (
185
+ AutoModelForSequenceClassification.from_pretrained(model_name)
186
+ .to(self.device)
187
+ .to(torch.float32)
188
+ )
189
+
190
+ def setup_gemini_model(self, api_key):
191
+ genai.configure(api_key=api_key)
192
+ self.gemini_model = genai.GenerativeModel("gemini-pro")
193
+
194
+ def filter_reviews(self, df):
195
+ def is_product_review(text):
196
+ if not isinstance(text, str):
197
+ return False
198
+ return not any(word in text.lower() for word in self.logistics_seller_words)
199
+
200
+ filtered_df = df[df["Yorum"].apply(is_product_review)].copy()
201
+
202
+ print(f"\nFiltreleme İstatistikleri:")
203
+ print(f"Toplam yorum sayısı: {len(df)}")
204
+ print(f"Ürün yorumu sayısı: {len(filtered_df)}")
205
+ print(f"Filtrelenen yorum sayısı: {len(df) - len(filtered_df)}")
206
+ print(
207
+ f"Filtreleme oranı: {((len(df) - len(filtered_df)) / len(df) * 100):.2f}%"
208
+ )
209
+
210
+ return filtered_df
211
+
212
+ def analyze_sentiment(self, df):
213
+ def predict_sentiment(text):
214
+ if not isinstance(text, str) or len(text.strip()) == 0:
215
+ return {"label": "Nötr", "score": 0.5}
216
+
217
+ try:
218
+ cleaned_text = self.preprocess_text(text)
219
+ inputs = self.sentiment_tokenizer(
220
+ cleaned_text,
221
+ return_tensors="pt",
222
+ truncation=True,
223
+ max_length=512,
224
+ padding=True,
225
+ ).to(self.device)
226
+
227
+ with torch.no_grad():
228
+ outputs = self.sentiment_model(**inputs)
229
+ probs = torch.nn.functional.softmax(outputs.logits, dim=1)
230
+ prediction = probs.cpu().numpy()[0]
231
+
232
+ score = float(prediction[1])
233
+
234
+ if score > 0.75:
235
+ label = "Pozitif"
236
+ elif score < 0.25:
237
+ label = "Negatif"
238
+ elif score > 0.55:
239
+ label = "Pozitif"
240
+ elif score < 0.45:
241
+ label = "Negatif"
242
+ else:
243
+ label = "Nötr"
244
+
245
+ return {"label": label, "score": score}
246
+
247
+ except Exception as e:
248
+ print(f"Error in sentiment prediction: {e}")
249
+ return {"label": "Nötr", "score": 0.5}
250
+
251
+ print("\nSentiment analizi yapılıyor...")
252
+ results = [predict_sentiment(text) for text in df["Yorum"]]
253
+
254
+ df["sentiment_score"] = [r["score"] for r in results]
255
+ df["sentiment_label"] = [r["label"] for r in results]
256
+ df["cleaned_text"] = df["Yorum"].apply(self.preprocess_text)
257
+
258
+ return df
259
+
260
+ def get_key_phrases(self, text_series):
261
+ text = " ".join(text_series.astype(str))
262
+ words = self.preprocess_text(text).split()
263
+ word_freq = Counter(words)
264
+ return {
265
+ word: count
266
+ for word, count in word_freq.items()
267
+ if count >= 3 and len(word) > 2
268
+ }
269
+
270
+ def generate_summary(self, df):
271
+ # en onemli yorumları sec
272
+ high_rated = df[df["Yıldız Sayısı"] >= 4]
273
+ low_rated = df[df["Yıldız Sayısı"] <= 2]
274
+
275
+ # onemli kelimleri ve yorumlari al
276
+ positive_features = self.get_key_phrases(high_rated["cleaned_text"])
277
+ negative_features = self.get_key_phrases(low_rated["cleaned_text"])
278
+
279
+ top_positive = (
280
+ high_rated.sort_values("sentiment_score", ascending=False)["Yorum"]
281
+ .head(3)
282
+ .tolist()
283
+ )
284
+ top_negative = (
285
+ low_rated.sort_values("sentiment_score")["Yorum"].head(2).tolist()
286
+ )
287
+
288
+ summary_prompt = f"""Bu ürünün genel değerlendirmesini doğal bir dille özetleyeceksin.
289
+
290
+ Veriler:
291
+ - Toplam {len(df)} değerlendirme var
292
+ - Ortalama puan: {df['Yıldız Sayısı'].mean():.1f}/5
293
+ - Pozitif yorum oranı: {(len(df[df['sentiment_label'] == 'Pozitif']) / len(df) * 100):.1f}%
294
+
295
+ En çok tekrar eden olumlu ifadeler: {', '.join(list(positive_features.keys())[:5])}
296
+ En çok tekrar eden olumsuz ifadeler: {', '.join(list(negative_features.keys())[:5])}
297
+
298
+ Örnek olumlu yorumlar:
299
+ {' '.join(top_positive)}
300
+
301
+ Örnek olumsuz yorumlar:
302
+ {' '.join(top_negative)}
303
+
304
+ Lütfen bu bilgileri kullanarak, ürünle ilgili kullanıcı deneyimlerini tek bir paragrafta, sohbet eder gibi doğal bir dille özetle.
305
+ İstatistikleri direkt verme, onları cümlelerin içine yerleştir. Olumlu ve olumsuz yönleri dengeli bir şekilde aktar."""
306
+
307
+ response = self.gemini_model.generate_content(summary_prompt)
308
+ return response.text
309
+
310
+
311
+ def analyze_reviews(file_path, api_key):
312
+ print("Analiz başlatılıyor...")
313
+ df = pd.read_csv(file_path)
314
+
315
+ analyzer = ReviewAnalyzer(api_key)
316
+
317
+ filtered_df = analyzer.filter_reviews(df)
318
+
319
+ analyzed_df = analyzer.analyze_sentiment(filtered_df)
320
+
321
+ summary = analyzer.generate_summary(analyzed_df)
322
+
323
+ return summary, analyzed_df
scripts/review_summarizer_trendyol_llama.py ADDED
@@ -0,0 +1,411 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import warnings
4
+ from collections import Counter
5
+
6
+ import matplotlib.pyplot as plt
7
+ import nltk
8
+ import numpy as np
9
+ import pandas as pd
10
+ import requests
11
+ import seaborn as sns
12
+ import torch
13
+ from nltk.tokenize import word_tokenize
14
+ from nltk.util import ngrams
15
+ from transformers import AutoModelForSequenceClassification, AutoTokenizer, pipeline
16
+ from wordcloud import WordCloud
17
+
18
+ warnings.filterwarnings("ignore")
19
+
20
+ nltk.download("stopwords")
21
+ nltk.download("punkt")
22
+
23
+
24
+ class ReviewAnalyzer:
25
+ def __init__(self):
26
+ self.turkish_stopwords = self.get_turkish_stopwords()
27
+ self.setup_sentiment_model()
28
+ self.setup_summary_model()
29
+
30
+ # Lojistik ve satıcı ile ilgili kelimeleri tanımla
31
+ self.logistics_seller_words = {
32
+ # Kargo ve teslimat ile ilgili
33
+ "kargo",
34
+ "kargocu",
35
+ "paket",
36
+ "paketleme",
37
+ "teslimat",
38
+ "teslim",
39
+ "gönderi",
40
+ "gönderim",
41
+ "ulaştı",
42
+ "ulaşım",
43
+ "geldi",
44
+ "kurye",
45
+ "dağıtım",
46
+ "hasarlı",
47
+ "hasar",
48
+ "kutu",
49
+ "ambalaj",
50
+ "zamanında",
51
+ "geç",
52
+ "hızlı",
53
+ "yavaş",
54
+ "günde",
55
+ "saatte",
56
+ # Satıcı ve mağaza ile ilgili
57
+ "satıcı",
58
+ "mağaza",
59
+ "sipariş",
60
+ "trendyol",
61
+ "tedarik",
62
+ "stok",
63
+ "garanti",
64
+ "fatura",
65
+ "iade",
66
+ "geri",
67
+ "müşteri",
68
+ "hizmet",
69
+ "destek",
70
+ "iletişim",
71
+ "şikayet",
72
+ "sorun",
73
+ "çözüm",
74
+ "hediye",
75
+ # Fiyat ve ödeme ile ilgili
76
+ "fiyat",
77
+ "ücret",
78
+ "para",
79
+ "bedava",
80
+ "ücretsiz",
81
+ "indirim",
82
+ "kampanya",
83
+ "taksit",
84
+ "ödeme",
85
+ "bütçe",
86
+ "hesap",
87
+ "kur",
88
+ # Zaman ile ilgili teslimat kelimeleri
89
+ "bugün",
90
+ "yarın",
91
+ "dün",
92
+ "hafta",
93
+ "gün",
94
+ "saat",
95
+ "süre",
96
+ "bekleme",
97
+ "gecikme",
98
+ "erken",
99
+ "geç",
100
+ }
101
+
102
+ def get_turkish_stopwords(self):
103
+ """Genişletilmiş stop words listesini hazırla"""
104
+ github_url = "https://raw.githubusercontent.com/sgsinclair/trombone/master/src/main/resources/org/voyanttools/trombone/keywords/stop.tr.turkish-lucene.txt"
105
+ stop_words = set()
106
+
107
+ try:
108
+ response = requests.get(github_url)
109
+ if response.status_code == 200:
110
+ github_stops = set(
111
+ word.strip() for word in response.text.split("\n") if word.strip()
112
+ )
113
+ stop_words.update(github_stops)
114
+ except Exception as e:
115
+ print(f"GitHub'dan stop words çekilirken hata oluştu: {e}")
116
+
117
+ stop_words.update(set(nltk.corpus.stopwords.words("turkish")))
118
+
119
+ additional_stops = {
120
+ "bir",
121
+ "ve",
122
+ "çok",
123
+ "bu",
124
+ "de",
125
+ "da",
126
+ "için",
127
+ "ile",
128
+ "ben",
129
+ "sen",
130
+ "o",
131
+ "biz",
132
+ "siz",
133
+ "onlar",
134
+ "bu",
135
+ "şu",
136
+ "ama",
137
+ "fakat",
138
+ "ancak",
139
+ "lakin",
140
+ "ki",
141
+ "dahi",
142
+ "mi",
143
+ "mı",
144
+ "mu",
145
+ "mü",
146
+ "var",
147
+ "yok",
148
+ "olan",
149
+ "içinde",
150
+ "üzerinde",
151
+ "bana",
152
+ "sana",
153
+ "ona",
154
+ "bize",
155
+ "size",
156
+ "onlara",
157
+ "evet",
158
+ "hayır",
159
+ "tamam",
160
+ "oldu",
161
+ "olmuş",
162
+ "olacak",
163
+ "etmek",
164
+ "yapmak",
165
+ "kez",
166
+ "kere",
167
+ "defa",
168
+ "adet",
169
+ }
170
+ stop_words.update(additional_stops)
171
+
172
+ print(f"Toplam {len(stop_words)} adet stop words yüklendi.")
173
+ return stop_words
174
+
175
+ def preprocess_text(self, text):
176
+ """Metin ön işleme"""
177
+ if isinstance(text, str):
178
+ # Küçük harfe çevir
179
+ text = text.lower()
180
+ # Özel karakterleri temizle
181
+ text = re.sub(r"[^\w\s]", "", text)
182
+ # Sayıları temizle
183
+ text = re.sub(r"\d+", "", text)
184
+ # Fazla boşlukları temizle
185
+ text = re.sub(r"\s+", " ", text).strip()
186
+ # Stop words'leri çıkar
187
+ words = text.split()
188
+ words = [word for word in words if word not in self.turkish_stopwords]
189
+ return " ".join(words)
190
+ return ""
191
+
192
+ def setup_sentiment_model(self):
193
+ """Sentiment analiz modelini hazırla"""
194
+ self.device = "cuda" if torch.cuda.is_available() else "cpu"
195
+ print(f"Using device for sentiment: {self.device}")
196
+
197
+ model_name = "savasy/bert-base-turkish-sentiment-cased"
198
+ self.sentiment_tokenizer = AutoTokenizer.from_pretrained(model_name)
199
+ self.sentiment_model = (
200
+ AutoModelForSequenceClassification.from_pretrained(model_name)
201
+ .to(self.device)
202
+ .to(torch.float32)
203
+ )
204
+
205
+ def setup_summary_model(self):
206
+ """Özet modelini hazırla"""
207
+ print("Loading Trendyol-LLM model...")
208
+ model_id = "Trendyol/Trendyol-LLM-8b-chat-v2.0"
209
+
210
+ self.summary_pipe = pipeline(
211
+ "text-generation",
212
+ model=model_id,
213
+ torch_dtype="auto",
214
+ device_map="auto",
215
+ )
216
+
217
+ self.terminators = [
218
+ self.summary_pipe.tokenizer.eos_token_id,
219
+ self.summary_pipe.tokenizer.convert_tokens_to_ids("<|eot_id|>"),
220
+ ]
221
+
222
+ self.sampling_params = {
223
+ "do_sample": True,
224
+ "temperature": 0.3,
225
+ "top_k": 50,
226
+ "top_p": 0.9,
227
+ "repetition_penalty": 1.1,
228
+ }
229
+
230
+ def filter_reviews(self, df):
231
+ """Ürün ile ilgili olmayan yorumları filtrele"""
232
+
233
+ def is_product_review(text):
234
+ if not isinstance(text, str):
235
+ return False
236
+ return not any(word in text.lower() for word in self.logistics_seller_words)
237
+
238
+ filtered_df = df[df["Yorum"].apply(is_product_review)].copy()
239
+
240
+ print(f"\nFiltreleme İstatistikleri:")
241
+ print(f"Toplam yorum sayısı: {len(df)}")
242
+ print(f"Ürün yorumu sayısı: {len(filtered_df)}")
243
+ print(f"Filtrelenen yorum sayısı: {len(df) - len(filtered_df)}")
244
+ print(
245
+ f"Filtreleme oranı: {((len(df) - len(filtered_df)) / len(df) * 100):.2f}%"
246
+ )
247
+
248
+ return filtered_df
249
+
250
+ def analyze_sentiment(self, df):
251
+ """Sentiment analizi yap"""
252
+
253
+ def predict_sentiment(text):
254
+ if not isinstance(text, str) or len(text.strip()) == 0:
255
+ return {"label": "Nötr", "score": 0.5}
256
+
257
+ try:
258
+ cleaned_text = self.preprocess_text(text)
259
+
260
+ inputs = self.sentiment_tokenizer(
261
+ cleaned_text,
262
+ return_tensors="pt",
263
+ truncation=True,
264
+ max_length=512,
265
+ padding=True,
266
+ ).to(self.device)
267
+
268
+ with torch.no_grad():
269
+ outputs = self.sentiment_model(**inputs)
270
+ probs = torch.nn.functional.softmax(outputs.logits, dim=1)
271
+ prediction = probs.cpu().numpy()[0]
272
+
273
+ score = float(prediction[1])
274
+
275
+ if score > 0.75:
276
+ label = "Pozitif"
277
+ elif score < 0.25:
278
+ label = "Negatif"
279
+ elif score > 0.55:
280
+ label = "Pozitif"
281
+ elif score < 0.45:
282
+ label = "Negatif"
283
+ else:
284
+ label = "Nötr"
285
+
286
+ return {"label": label, "score": score}
287
+
288
+ except Exception as e:
289
+ print(f"Error in sentiment prediction: {e}")
290
+ return {"label": "Nötr", "score": 0.5}
291
+
292
+ print("\nSentiment analizi yapılıyor...")
293
+ results = [predict_sentiment(text) for text in df["Yorum"]]
294
+
295
+ df["sentiment_score"] = [r["score"] for r in results]
296
+ df["sentiment_label"] = [r["label"] for r in results]
297
+ df["cleaned_text"] = df["Yorum"].apply(self.preprocess_text)
298
+
299
+ return df
300
+
301
+ def get_key_phrases(self, text_series):
302
+ """En önemli anahtar kelimeleri bul"""
303
+ text = " ".join(text_series.astype(str))
304
+ words = self.preprocess_text(text).split()
305
+ word_freq = Counter(words)
306
+ # En az 3 kez geçen kelimeleri al
307
+ return {
308
+ word: count
309
+ for word, count in word_freq.items()
310
+ if count >= 3 and len(word) > 2
311
+ }
312
+
313
+ def generate_summary(self, df):
314
+ """Yorumların genel özetini oluştur"""
315
+ # En önemli yorumları seç
316
+ high_rated = df[df["Yıldız Sayısı"] >= 4]
317
+ low_rated = df[df["Yıldız Sayısı"] <= 2]
318
+
319
+ # Önemli kelimeleri bul
320
+ positive_phrases = self.get_key_phrases(high_rated["cleaned_text"])
321
+ negative_phrases = self.get_key_phrases(low_rated["cleaned_text"])
322
+
323
+ # En anlamlı yorumları seç
324
+ top_positive = (
325
+ high_rated.sort_values("sentiment_score", ascending=False)["Yorum"]
326
+ .head(3)
327
+ .tolist()
328
+ )
329
+ top_negative = (
330
+ low_rated.sort_values("sentiment_score")["Yorum"].head(2).tolist()
331
+ )
332
+
333
+ # En sık kullanılan kelimeler
334
+ pos_features = ", ".join(
335
+ [f"{word} ({count})" for word, count in list(positive_phrases.items())[:5]]
336
+ )
337
+ neg_features = ", ".join(
338
+ [f"{word} ({count})" for word, count in list(negative_phrases.items())[:5]]
339
+ )
340
+
341
+ summary_prompt = f"""
342
+ MacBook Air Kullanıcı Yorumları Analizi:
343
+
344
+ İSTATİSTİKLER:
345
+ - Toplam Yorum: {len(df)}
346
+ - Ortalama Puan: {df['Yıldız Sayısı'].mean():.1f}/5
347
+ - Pozitif Yorum Oranı: {(len(df[df['sentiment_label'] == 'Pozitif']) / len(df) * 100):.1f}%
348
+
349
+ SIKÇA KULLANILAN KELİMELER:
350
+ Olumlu: {pos_features}
351
+ Olumsuz: {neg_features}
352
+
353
+ ÖRNEK OLUMLU YORUMLAR:
354
+ {' '.join([f"• {yorum[:200]}..." for yorum in top_positive])}
355
+
356
+ ÖRNEK OLUMSUZ YORUMLAR:
357
+ {' '.join([f"• {yorum[:200]}..." for yorum in top_negative])}
358
+
359
+ Lütfen bu veriler ışığında bu ürün için kısa ve öz bir değerlendirme yap.
360
+ Özellikle kullanıcıların en çok beğendiği özellikler ve en sık dile getirilen sorunlara odaklan.
361
+ Değerlendirmeyi 3 paragrafla sınırla ve somut örnekler kullan.
362
+ """
363
+
364
+ messages = [
365
+ {
366
+ "role": "system",
367
+ "content": "Sen bir ürün yorumları analiz uzmanısın. Yorumları özetlerken nesnel ve açık ol.",
368
+ },
369
+ {"role": "user", "content": summary_prompt},
370
+ ]
371
+
372
+ outputs = self.summary_pipe(
373
+ messages,
374
+ max_new_tokens=512,
375
+ eos_token_id=self.terminators,
376
+ return_full_text=False,
377
+ **self.sampling_params,
378
+ )
379
+
380
+ return outputs[0]["generated_text"]
381
+
382
+
383
+ def analyze_reviews(file_path):
384
+ df = pd.read_csv(file_path)
385
+
386
+ analyzer = ReviewAnalyzer()
387
+
388
+ filtered_df = analyzer.filter_reviews(df)
389
+
390
+ print("Sentiment analizi başlatılıyor...")
391
+ analyzed_df = analyzer.analyze_sentiment(filtered_df)
392
+
393
+ analyzed_df.to_csv(
394
+ "sentiment_analyzed_reviews.csv", index=False, encoding="utf-8-sig"
395
+ )
396
+ print("Sentiment analizi tamamlandı ve kaydedildi.")
397
+
398
+ print("\nÜrün özeti oluşturuluyor...")
399
+ summary = analyzer.generate_summary(analyzed_df)
400
+
401
+ with open("urun_ozeti.txt", "w", encoding="utf-8") as f:
402
+ f.write(summary)
403
+
404
+ print("\nÜrün Özeti:")
405
+ print("-" * 50)
406
+ print(summary)
407
+ print("\nÖzet 'urun_ozeti.txt' dosyasına kaydedildi.")
408
+
409
+
410
+ if __name__ == "__main__":
411
+ analyze_reviews("data/macbook_product_comments_with_ratings.csv")
scripts/sentiment_bert_model.py CHANGED
@@ -1,166 +1,203 @@
1
- import pandas as pd
2
- import numpy as np
3
- import matplotlib.pyplot as plt
4
- import seaborn as sns
5
- from transformers import AutoTokenizer, AutoModelForSequenceClassification
6
- import torch
7
- import os
8
- import warnings
9
- warnings.filterwarnings('ignore')
10
-
11
- class TurkishSentimentAnalyzer:
12
- def __init__(self):
13
- self.device = "cuda" if torch.cuda.is_available() else "cpu"
14
- print(f"Using device: {self.device}")
15
-
16
- # sentiment model
17
- model_name = "savasy/bert-base-turkish-sentiment-cased"
18
- self.tokenizer = AutoTokenizer.from_pretrained(model_name)
19
- self.model = AutoModelForSequenceClassification.from_pretrained(model_name).to(self.device)
20
-
21
- # Lojistik ve satıcı kelimeleri
22
- self.logistics_seller_words = {
23
- 'kargo', 'kargocu', 'paket', 'paketleme', 'teslimat', 'teslim',
24
- 'gönderi', 'gönderim', 'ulaştı', 'ulaşım', 'geldi', 'kurye',
25
- 'satıcı', 'mağaza', 'sipariş', 'trendyol', 'tedarik', 'stok',
26
- 'fiyat', 'ücret', 'para', 'bedava', 'indirim', 'kampanya',
27
- 'havale', 'ödeme', 'garanti', 'fatura'
28
- }
29
-
30
- def predict_sentiment(self, text):
31
- """Tek bir metin için sentiment tahmini yap"""
32
- if not isinstance(text, str) or len(text.strip()) == 0:
33
- return {"label": "Nötr", "score": 0.5}
34
-
35
- try:
36
- inputs = self.tokenizer(text, return_tensors="pt", truncation=True,
37
- max_length=512, padding=True).to(self.device)
38
-
39
- with torch.no_grad():
40
- outputs = self.model(**inputs)
41
- probs = torch.nn.functional.softmax(outputs.logits, dim=1)
42
- prediction = probs.cpu().numpy()[0]
43
-
44
- # İki sınıflı model için (positive/negative)
45
- score = float(prediction[1]) # Pozitif sınıfın olasılığı
46
-
47
- # Daha hassas skor eşikleri
48
- if score > 0.75: # Yüksek güvenle pozitif
49
- label = "Pozitif"
50
- elif score < 0.25: # Yüksek güvenle negatif
51
- label = "Negatif"
52
- elif score > 0.55: # Hafif pozitif eğilim
53
- label = "Pozitif"
54
- elif score < 0.45: # Hafif negatif eğilim
55
- label = "Negatif"
56
- else:
57
- label = "Nötr"
58
-
59
- return {"label": label, "score": score}
60
-
61
- except Exception as e:
62
- print(f"Error in sentiment prediction: {e}")
63
- return {"label": "Nötr", "score": 0.5}
64
-
65
- def filter_product_reviews(self, df):
66
- """Ürün ile ilgili olmayan yorumları filtrele"""
67
- def is_product_review(text):
68
- if not isinstance(text, str):
69
- return False
70
- return not any(word in text.lower() for word in self.logistics_seller_words)
71
-
72
- filtered_df = df[df['Yorum'].apply(is_product_review)].copy()
73
-
74
- print(f"\nFiltreleme İstatistikleri:")
75
- print(f"Toplam yorum sayısı: {len(df)}")
76
- print(f"Ürün yorumu sayısı: {len(filtered_df)}")
77
- print(f"Filtrelenen yorum sayısı: {len(df) - len(filtered_df)}")
78
- print(f"Filtreleme oranı: {((len(df) - len(filtered_df)) / len(df) * 100):.2f}%")
79
-
80
- return filtered_df
81
-
82
- def analyze_reviews(self, df):
83
- """Tüm yorumları analiz et"""
84
- print("\nSentiment analizi başlatılıyor...")
85
-
86
- filtered_df = self.filter_product_reviews(df)
87
-
88
- # Sentiment analizi
89
- results = []
90
- for text in filtered_df['Yorum']:
91
- sentiment = self.predict_sentiment(text)
92
- results.append(sentiment)
93
-
94
- filtered_df['sentiment_score'] = [r['score'] for r in results]
95
- filtered_df['sentiment_label'] = [r['label'] for r in results]
96
-
97
- return filtered_df
98
-
99
- def create_visualizations(self, df):
100
- """Analiz sonuçlarını görselleştir"""
101
- if not os.path.exists('images'):
102
- os.makedirs('images')
103
-
104
- # 1. Sentiment Dağılımı
105
- plt.figure(figsize=(12, 6))
106
- sns.countplot(data=df, x='sentiment_label',
107
- order=['Pozitif', 'Nötr', 'Negatif'])
108
- plt.title('Sentiment Dağılımı')
109
- plt.tight_layout()
110
- plt.savefig('images/sentiment_distribution.png', bbox_inches='tight', dpi=300)
111
- plt.close()
112
-
113
- # 2. Yıldız-Sentiment İlişkisi
114
- plt.figure(figsize=(12, 6))
115
- df_mean = df.groupby('Yıldız Sayısı')['sentiment_score'].mean().reset_index()
116
- sns.barplot(data=df_mean, x='Yıldız Sayısı', y='sentiment_score')
117
- plt.title('Yıldız Sayısına Göre Ortalama Sentiment Skoru')
118
- plt.tight_layout()
119
- plt.savefig('images/star_sentiment_relation.png', bbox_inches='tight', dpi=300)
120
- plt.close()
121
-
122
- # 3. Sentiment Score Dağılımı
123
- plt.figure(figsize=(12, 6))
124
- sns.histplot(data=df, x='sentiment_score', bins=30)
125
- plt.title('Sentiment Score Dağılımı')
126
- plt.tight_layout()
127
- plt.savefig('images/sentiment_score_distribution.png', bbox_inches='tight', dpi=300)
128
- plt.close()
129
-
130
- def print_statistics(self, df):
131
- """Analiz istatistiklerini yazdır"""
132
- print("\nSentiment Analizi Sonuçları:")
133
- print("-" * 50)
134
-
135
- sentiment_counts = df['sentiment_label'].value_counts()
136
- total_reviews = len(df)
137
-
138
- for label, count in sentiment_counts.items():
139
- percentage = (count / total_reviews) * 100
140
- print(f"{label}: {count} yorum ({percentage:.2f}%)")
141
-
142
- print("\nYıldız Bazlı Sentiment Skorları:")
143
- print("-" * 50)
144
- star_means = df.groupby('Yıldız Sayısı')['sentiment_score'].mean()
145
- for star, score in star_means.items():
146
- print(f"{star} Yıldız ortalama sentiment skoru: {score:.3f}")
147
-
148
- def main():
149
- df = pd.read_csv('data/macbook_product_comments_with_ratings.csv')
150
-
151
- analyzer = TurkishSentimentAnalyzer()
152
-
153
- print("Analiz başlatılıyor...")
154
- analyzed_df = analyzer.analyze_reviews(df)
155
-
156
- print("\nGörselleştirmeler oluşturuluyor...")
157
- analyzer.create_visualizations(analyzed_df)
158
-
159
- analyzer.print_statistics(analyzed_df)
160
-
161
- output_file = 'sentiment_analyzed_reviews.csv'
162
- analyzed_df.to_csv(output_file, index=False, encoding='utf-8-sig')
163
- print(f"\nSonuçlar '{output_file}' dosyasına kaydedildi.")
164
-
165
- if __name__ == "__main__":
166
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import warnings
3
+
4
+ import matplotlib.pyplot as plt
5
+ import numpy as np
6
+ import pandas as pd
7
+ import seaborn as sns
8
+ import torch
9
+ from transformers import AutoModelForSequenceClassification, AutoTokenizer
10
+
11
+ warnings.filterwarnings("ignore")
12
+
13
+
14
+ class TurkishSentimentAnalyzer:
15
+ def __init__(self):
16
+ self.device = "cuda" if torch.cuda.is_available() else "cpu"
17
+ print(f"Using device: {self.device}")
18
+
19
+ # sentiment model
20
+ model_name = "savasy/bert-base-turkish-sentiment-cased"
21
+ self.tokenizer = AutoTokenizer.from_pretrained(model_name)
22
+ self.model = AutoModelForSequenceClassification.from_pretrained(model_name).to(
23
+ self.device
24
+ )
25
+
26
+ # Lojistik ve satıcı kelimeleri
27
+ self.logistics_seller_words = {
28
+ "kargo",
29
+ "kargocu",
30
+ "paket",
31
+ "paketleme",
32
+ "teslimat",
33
+ "teslim",
34
+ "gönderi",
35
+ "gönderim",
36
+ "ulaştı",
37
+ "ulaşım",
38
+ "geldi",
39
+ "kurye",
40
+ "satıcı",
41
+ "mağaza",
42
+ "sipariş",
43
+ "trendyol",
44
+ "tedarik",
45
+ "stok",
46
+ "fiyat",
47
+ "ücret",
48
+ "para",
49
+ "bedava",
50
+ "indirim",
51
+ "kampanya",
52
+ "havale",
53
+ "ödeme",
54
+ "garanti",
55
+ "fatura",
56
+ }
57
+
58
+ def predict_sentiment(self, text):
59
+ """Tek bir metin için sentiment tahmini yap"""
60
+ if not isinstance(text, str) or len(text.strip()) == 0:
61
+ return {"label": "Nötr", "score": 0.5}
62
+
63
+ try:
64
+ inputs = self.tokenizer(
65
+ text, return_tensors="pt", truncation=True, max_length=512, padding=True
66
+ ).to(self.device)
67
+
68
+ with torch.no_grad():
69
+ outputs = self.model(**inputs)
70
+ probs = torch.nn.functional.softmax(outputs.logits, dim=1)
71
+ prediction = probs.cpu().numpy()[0]
72
+
73
+ # İki sınıflı model için (positive/negative)
74
+ score = float(prediction[1]) # Pozitif sınıfın olasılığı
75
+
76
+ # Daha hassas skor eşikleri
77
+ if score > 0.75: # Yüksek güvenle pozitif
78
+ label = "Pozitif"
79
+ elif score < 0.25: # Yüksek güvenle negatif
80
+ label = "Negatif"
81
+ elif score > 0.55: # Hafif pozitif eğilim
82
+ label = "Pozitif"
83
+ elif score < 0.45: # Hafif negatif eğilim
84
+ label = "Negatif"
85
+ else:
86
+ label = "Nötr"
87
+
88
+ return {"label": label, "score": score}
89
+
90
+ except Exception as e:
91
+ print(f"Error in sentiment prediction: {e}")
92
+ return {"label": "Nötr", "score": 0.5}
93
+
94
+ def filter_product_reviews(self, df):
95
+ """Ürün ile ilgili olmayan yorumları filtrele"""
96
+
97
+ def is_product_review(text):
98
+ if not isinstance(text, str):
99
+ return False
100
+ return not any(word in text.lower() for word in self.logistics_seller_words)
101
+
102
+ filtered_df = df[df["Yorum"].apply(is_product_review)].copy()
103
+
104
+ print(f"\nFiltreleme İstatistikleri:")
105
+ print(f"Toplam yorum sayısı: {len(df)}")
106
+ print(f"Ürün yorumu sayısı: {len(filtered_df)}")
107
+ print(f"Filtrelenen yorum sayısı: {len(df) - len(filtered_df)}")
108
+ print(
109
+ f"Filtreleme oranı: {((len(df) - len(filtered_df)) / len(df) * 100):.2f}%"
110
+ )
111
+
112
+ return filtered_df
113
+
114
+ def analyze_reviews(self, df):
115
+ """Tüm yorumları analiz et"""
116
+ print("\nSentiment analizi başlatılıyor...")
117
+
118
+ filtered_df = self.filter_product_reviews(df)
119
+
120
+ # Sentiment analizi
121
+ results = []
122
+ for text in filtered_df["Yorum"]:
123
+ sentiment = self.predict_sentiment(text)
124
+ results.append(sentiment)
125
+
126
+ filtered_df["sentiment_score"] = [r["score"] for r in results]
127
+ filtered_df["sentiment_label"] = [r["label"] for r in results]
128
+
129
+ return filtered_df
130
+
131
+ def create_visualizations(self, df):
132
+ """Analiz sonuçlarını görselleştir"""
133
+ if not os.path.exists("images"):
134
+ os.makedirs("images")
135
+
136
+ # 1. Sentiment Dağılımı
137
+ plt.figure(figsize=(12, 6))
138
+ sns.countplot(
139
+ data=df, x="sentiment_label", order=["Pozitif", "Nötr", "Negatif"]
140
+ )
141
+ plt.title("Sentiment Dağılımı")
142
+ plt.tight_layout()
143
+ plt.savefig("images/sentiment_distribution.png", bbox_inches="tight", dpi=300)
144
+ plt.close()
145
+
146
+ # 2. Yıldız-Sentiment İlişkisi
147
+ plt.figure(figsize=(12, 6))
148
+ df_mean = df.groupby("Yıldız Sayısı")["sentiment_score"].mean().reset_index()
149
+ sns.barplot(data=df_mean, x="Yıldız Sayısı", y="sentiment_score")
150
+ plt.title("Yıldız Sayısına Göre Ortalama Sentiment Skoru")
151
+ plt.tight_layout()
152
+ plt.savefig("images/star_sentiment_relation.png", bbox_inches="tight", dpi=300)
153
+ plt.close()
154
+
155
+ # 3. Sentiment Score Dağılımı
156
+ plt.figure(figsize=(12, 6))
157
+ sns.histplot(data=df, x="sentiment_score", bins=30)
158
+ plt.title("Sentiment Score Dağılımı")
159
+ plt.tight_layout()
160
+ plt.savefig(
161
+ "images/sentiment_score_distribution.png", bbox_inches="tight", dpi=300
162
+ )
163
+ plt.close()
164
+
165
+ def print_statistics(self, df):
166
+ """Analiz istatistiklerini yazdır"""
167
+ print("\nSentiment Analizi Sonuçları:")
168
+ print("-" * 50)
169
+
170
+ sentiment_counts = df["sentiment_label"].value_counts()
171
+ total_reviews = len(df)
172
+
173
+ for label, count in sentiment_counts.items():
174
+ percentage = (count / total_reviews) * 100
175
+ print(f"{label}: {count} yorum ({percentage:.2f}%)")
176
+
177
+ print("\nYıldız Bazlı Sentiment Skorları:")
178
+ print("-" * 50)
179
+ star_means = df.groupby("Yıldız Sayısı")["sentiment_score"].mean()
180
+ for star, score in star_means.items():
181
+ print(f"{star} Yıldız ortalama sentiment skoru: {score:.3f}")
182
+
183
+
184
+ def main():
185
+ df = pd.read_csv("data/macbook_product_comments_with_ratings.csv")
186
+
187
+ analyzer = TurkishSentimentAnalyzer()
188
+
189
+ print("Analiz başlatılıyor...")
190
+ analyzed_df = analyzer.analyze_reviews(df)
191
+
192
+ print("\nGörselleştirmeler oluşturuluyor...")
193
+ analyzer.create_visualizations(analyzed_df)
194
+
195
+ analyzer.print_statistics(analyzed_df)
196
+
197
+ output_file = "sentiment_analyzed_reviews.csv"
198
+ analyzed_df.to_csv(output_file, index=False, encoding="utf-8-sig")
199
+ print(f"\nSonuçlar '{output_file}' dosyasına kaydedildi.")
200
+
201
+
202
+ if __name__ == "__main__":
203
+ main()