enesmanan commited on
Commit
99d7b92
·
verified ·
1 Parent(s): 26b9192
app.py CHANGED
@@ -1,331 +1,108 @@
1
- import gradio as gr
2
- import pandas as pd
3
- import plotly.express as px
4
- import plotly.graph_objects as go
5
- import os
6
- import shutil
7
- from scrape.trendyol_scraper import scrape_reviews
8
- import torch
9
- from transformers import AutoTokenizer, AutoModelForSequenceClassification, pipeline
10
- import re
11
- from tqdm import tqdm
12
- import nltk
13
- from nltk.corpus import stopwords
14
- from dotenv import load_dotenv
15
- import google.generativeai as genai
16
- from pathlib import Path
17
-
18
- class ReviewAnalysisApp:
19
- def __init__(self):
20
- self.setup_models()
21
- self.setup_stopwords()
22
- self.setup_gemini()
23
-
24
- def setup_stopwords(self):
25
- """Türkçe stopwords'leri yükle"""
26
- try:
27
- nltk.data.find('corpora/stopwords')
28
- except LookupError:
29
- nltk.download('stopwords')
30
-
31
- self.turkish_stopwords = set(stopwords.words('turkish'))
32
- # Ekstra stopwords ekle
33
- self.logistics_seller_words = {
34
- 'kargo', 'kargocu', 'paket', 'gönderi', 'satıcı', 'mağaza',
35
- 'sipariş', 'teslimat', 'gönderim', 'kutu', 'paketleme'
36
- }
37
- self.turkish_stopwords.update(self.logistics_seller_words)
38
-
39
- def setup_models(self):
40
- """Modelleri yükle ve hazırla"""
41
- # Sadece sentiment model
42
- self.device = "cpu"
43
- print(f"Cihaz: {self.device}")
44
-
45
- model_name = "savasy/bert-base-turkish-sentiment-cased"
46
- self.sentiment_tokenizer = AutoTokenizer.from_pretrained(model_name)
47
- self.sentiment_model = (
48
- AutoModelForSequenceClassification.from_pretrained(
49
- model_name,
50
- low_cpu_mem_usage=False
51
- )
52
- .to(self.device)
53
- .to(torch.float32)
54
- )
55
-
56
- def setup_gemini(self):
57
- """Gemini API'yi hazırla"""
58
- try:
59
- # Önce .env dosyasından API key'i al
60
- load_dotenv()
61
- api_key = os.getenv('GOOGLE_API_KEY')
62
- if not api_key:
63
- raise ValueError("API key bulunamadı!")
64
-
65
- # Gemini'yi yapılandır
66
- genai.configure(api_key=api_key)
67
-
68
- # Modeli ayarla
69
- self.gemini_model = genai.GenerativeModel('gemini-pro')
70
-
71
- except Exception as e:
72
- print(f"Gemini API yapılandırma hatası: {str(e)}")
73
- self.gemini_model = None
74
-
75
- def preprocess_text(self, text):
76
- """Metin ön işleme"""
77
- if isinstance(text, str):
78
- # Küçük harfe çevir
79
- text = text.lower()
80
- # Özel karakterleri temizle
81
- text = re.sub(r'[^\w\s]', '', text)
82
- # Sayıları temizle
83
- text = re.sub(r'\d+', '', text)
84
- # Fazla boşlukları temizle
85
- text = re.sub(r'\s+', ' ', text).strip()
86
- # Stop words'leri çıkar
87
- words = text.split()
88
- words = [word for word in words if word not in self.turkish_stopwords]
89
- return ' '.join(words)
90
- return ''
91
-
92
- def filter_product_reviews(self, df):
93
- """Ürün ile ilgili olmayan yorumları filtrele"""
94
- def is_product_review(text):
95
- if not isinstance(text, str):
96
- return False
97
- return not any(word in text.lower() for word in self.logistics_seller_words)
98
-
99
- filtered_df = df[df['Yorum'].apply(is_product_review)].copy()
100
-
101
- print(f"\nFiltreleme İstatistikleri:")
102
- print(f"Toplam yorum sayısı: {len(df)}")
103
- print(f"Ürün yorumu sayısı: {len(filtered_df)}")
104
- print(f"Filtrelenen yorum sayısı: {len(df) - len(filtered_df)}")
105
- print(f"Filtreleme oranı: {((len(df) - len(filtered_df)) / len(df) * 100):.2f}%")
106
-
107
- return filtered_df
108
-
109
- def predict_sentiment(self, text):
110
- """Tek bir yorum için sentiment analizi yap"""
111
- # Önce metni temizle
112
- text = self.preprocess_text(text)
113
-
114
- if not text:
115
- return {"label": "nötr", "score": 0.5}
116
-
117
- inputs = self.sentiment_tokenizer(text, return_tensors="pt", truncation=True, max_length=512)
118
- inputs = {k: v.to(self.device) for k, v in inputs.items()}
119
-
120
- with torch.no_grad():
121
- outputs = self.sentiment_model(**inputs)
122
- scores = torch.nn.functional.softmax(outputs.logits, dim=1)
123
-
124
- positive_score = scores[0][1].item()
125
- label = "pozitif" if positive_score > 0.5 else "negatif"
126
-
127
- return {"label": label, "score": positive_score}
128
-
129
- def analyze_reviews(self, df):
130
- """Tüm yorumları analiz et"""
131
- print("\nSentiment analizi başlatılıyor...")
132
-
133
- # Önce ürün ile ilgili olmayan yorumları filtrele
134
- df = self.filter_product_reviews(df)
135
-
136
- # Sentiment analizi
137
- results = []
138
- for text in tqdm(df['Yorum'], desc="Yorumlar analiz ediliyor"):
139
- sentiment = self.predict_sentiment(text)
140
- results.append(sentiment)
141
-
142
- df['sentiment_score'] = [r['score'] for r in results]
143
- df['sentiment_label'] = [r['label'] for r in results]
144
-
145
- return df
146
-
147
- def generate_summary(self, df):
148
- """İstatistiksel özet ve Gemini ile detaylı analiz"""
149
- # Temel istatistikler
150
- avg_rating = df['Yıldız Sayısı'].mean()
151
- total_reviews = len(df)
152
-
153
- # Sentiment bazlı gruplandırma
154
- positive_comments = df[df['sentiment_label'] == 'pozitif']['Yorum'].tolist()
155
- negative_comments = df[df['sentiment_label'] == 'negatif']['Yorum'].tolist()
156
- positive_count = len(positive_comments)
157
- negative_count = len(negative_comments)
158
-
159
- # Yıldız dağılımı
160
- star_dist = df['Yıldız Sayısı'].value_counts().sort_index()
161
- star_dist_text = "\n".join([f"{star} yıldız: {count} yorum" for star, count in star_dist.items()])
162
-
163
- # En sık kelimeler
164
- all_words = []
165
- for text in df['Yorum']:
166
- cleaned_text = self.preprocess_text(text)
167
- if cleaned_text:
168
- all_words.extend(cleaned_text.split())
169
-
170
- from collections import Counter
171
- word_freq = Counter(all_words).most_common(10)
172
- frequent_words = ", ".join([f"{word} ({count} kez)" for word, count in word_freq])
173
-
174
- # İstatistiksel özet metni
175
- stats_summary = f"""📊 ÜRÜN ANALİZ RAPORU
176
-
177
- ⭐ Ortalama Puan: {avg_rating:.1f}/5
178
- 📝 Toplam Yorum: {total_reviews}
179
- ✅ Pozitif Yorum: {positive_count}
180
- ❌ Negatif Yorum: {negative_count}
181
-
182
- 📈 YILDIZ DAĞILIMI:
183
- {star_dist_text}
184
-
185
- 🔍 EN SIK KULLANILAN KELİMELER:
186
- {frequent_words}
187
-
188
- 💬 ÖRNEK YORUMLAR:
189
- ✅ Pozitif Yorumlar:
190
- {' | '.join(positive_comments[:3])}
191
-
192
- ❌ Negatif Yorumlar:
193
- {' | '.join(negative_comments[:3])}"""
194
-
195
- # Gemini ile detaylı analiz
196
- if self.gemini_model:
197
- try:
198
- prompt = f"""Aşağıdaki ürün yorumları verilerine dayanarak detaylı bir analiz yap:
199
-
200
- 1. İstatistikler:
201
- - Toplam {total_reviews} yorum
202
- - Ortalama puan: {avg_rating:.1f}/5
203
- - {positive_count} pozitif, {negative_count} negatif yorum
204
-
205
- 2. Örnek Pozitif Yorumlar:
206
- {' | '.join(positive_comments[:3])}
207
-
208
- 3. Örnek Negatif Yorumlar:
209
- {' | '.join(negative_comments[:3])}
210
-
211
- 4. En Sık Kullanılan Kelimeler:
212
- {frequent_words}
213
-
214
- Lütfen şu başlıklar altında bir değerlendirme yap:
215
- 1. Ürünün güçlü yönleri
216
- 2. Ürünün zayıf yönleri
217
- 3. Genel kullanıcı memnuniyeti
218
- 4. Potansiyel alıcılar için öneriler
219
-
220
- Yanıtını Türkçe olarak ver ve mümkün olduğunca özlü tut."""
221
-
222
- response = self.gemini_model.generate_content(prompt)
223
- ai_analysis = response.text
224
-
225
- # İstatistiksel özet ve AI analizini birleştir
226
- return f"{stats_summary}\n\n🤖 YAPAY ZEKA ANALİZİ:\n{ai_analysis}"
227
-
228
- except Exception as e:
229
- print(f"Gemini API hatası: {str(e)}")
230
- return stats_summary
231
-
232
- return stats_summary
233
-
234
- def analyze_url(self, url):
235
- try:
236
- # Temizlik
237
- if os.path.exists("data"):
238
- shutil.rmtree("data")
239
-
240
- # Yorumları çek
241
- df = scrape_reviews(url)
242
-
243
- if df.empty:
244
- return "Yorumlar çekilemedi. Lütfen URL'yi kontrol edin.", None, None, None
245
-
246
- # Sentiment analizi yap
247
- analyzed_df = self.analyze_reviews(df)
248
-
249
- # Özet oluştur
250
- summary = self.generate_summary(analyzed_df)
251
-
252
- # Grafikleri oluştur
253
- fig1 = self.create_sentiment_distribution(analyzed_df)
254
- fig2 = self.create_rating_distribution(analyzed_df)
255
- fig3 = self.create_sentiment_by_rating(analyzed_df)
256
-
257
- return summary, fig1, fig2, fig3
258
-
259
- except Exception as e:
260
- return f"Bir hata oluştu: {str(e)}", None, None, None
261
-
262
- finally:
263
- # Temizlik
264
- if os.path.exists("data"):
265
- shutil.rmtree("data")
266
-
267
- def create_sentiment_distribution(self, df):
268
- fig = px.pie(df,
269
- names='sentiment_label',
270
- title='Duygu Analizi Dağılımı')
271
- return fig
272
-
273
- def create_rating_distribution(self, df):
274
- fig = px.bar(df['Yıldız Sayısı'].value_counts().sort_index(),
275
- title='Yıldız Dağılımı')
276
- fig.update_layout(xaxis_title='Yıldız Sayısı',
277
- yaxis_title='Yorum Sayısı')
278
- return fig
279
-
280
- def create_sentiment_by_rating(self, df):
281
- avg_sentiment = df.groupby('Yıldız Sayısı')['sentiment_score'].mean()
282
- fig = px.line(avg_sentiment,
283
- title='Yıldız Sayısına Göre Ortalama Sentiment Skoru')
284
- fig.update_layout(xaxis_title='Yıldız Sayısı',
285
- yaxis_title='Ortalama Sentiment Skoru')
286
- return fig
287
-
288
- def create_interface():
289
- app = ReviewAnalysisApp()
290
-
291
- with gr.Blocks(theme=gr.themes.Soft()) as interface:
292
- gr.Markdown("# Trendyol Yorum Analizi")
293
-
294
- with gr.Row():
295
- url_input = gr.Textbox(
296
- label="Trendyol Ürün Yorumları URL'si",
297
- placeholder="https://www.trendyol.com/..."
298
- )
299
-
300
- analyze_btn = gr.Button("Analiz Et")
301
-
302
- with gr.Row():
303
- with gr.Column(scale=1):
304
- summary_output = gr.Textbox(
305
- label="Özet",
306
- lines=10
307
- )
308
-
309
- with gr.Column(scale=2):
310
- with gr.Tab("Duygu Analizi"):
311
- sentiment_dist = gr.Plot()
312
- with gr.Tab("Yıldız Dağılımı"):
313
- rating_dist = gr.Plot()
314
- with gr.Tab("Sentiment-Yıldız İlişkisi"):
315
- sentiment_rating = gr.Plot()
316
-
317
- analyze_btn.click(
318
- fn=app.analyze_url,
319
- inputs=[url_input],
320
- outputs=[summary_output, sentiment_dist, rating_dist, sentiment_rating]
321
- )
322
-
323
- return interface
324
-
325
- if __name__ == "__main__":
326
- interface = create_interface()
327
- interface.launch(
328
- server_name="0.0.0.0", # Dış bağlantılara izin ver
329
- share=True, # Public link oluştur
330
- server_port=7860 # Space'in varsayılan portu
331
- )
 
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
+
10
+ # ChromeDriver kurulumu için fonksiyon
11
+ def setup_chrome():
12
+ # Chrome kurulumu
13
+ os.system('apt-get update && apt-get install -y wget gnupg')
14
+ os.system('wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -')
15
+ os.system('echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list')
16
+ os.system('apt-get update && apt-get install -y google-chrome-stable')
17
+
18
+ # ChromeDriver kurulumu
19
+ chrome_version = subprocess.check_output(['google-chrome', '--version']).decode().strip().split()[2].split('.')[0]
20
+ os.system(f'wget -q "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_{chrome_version}" -O chrome_version')
21
+ os.system('wget -q "https://chromedriver.storage.googleapis.com/$(cat chrome_version)/chromedriver_linux64.zip"')
22
+ os.system('unzip chromedriver_linux64.zip && mv chromedriver /usr/local/bin/ && chmod +x /usr/local/bin/chromedriver')
23
+
24
+ # Ana uygulama başlamadan önce Chrome kurulumunu yap
25
+ setup_chrome()
26
+
27
+ class ReviewAnalysisApp:
28
+ def __init__(self):
29
+ self.analyzer = ReviewAnalyzer()
30
+
31
+ def analyze_url(self, url):
32
+ # Yorumları çek
33
+ df = scrape_reviews(url)
34
+
35
+ # Sentiment analizi yap
36
+ analyzed_df = self.analyzer.analyze_reviews(df)
37
+
38
+ # Özet oluştur
39
+ summary = self.analyzer.generate_summary(analyzed_df)
40
+
41
+ # Grafikleri oluştur
42
+ fig1 = self.create_sentiment_distribution(analyzed_df)
43
+ fig2 = self.create_rating_distribution(analyzed_df)
44
+ fig3 = self.create_sentiment_by_rating(analyzed_df)
45
+
46
+ return summary, fig1, fig2, fig3
47
+
48
+ def create_sentiment_distribution(self, df):
49
+ fig = px.pie(df,
50
+ names='sentiment_label',
51
+ title='Duygu Analizi Dağılımı')
52
+ return fig
53
+
54
+ def create_rating_distribution(self, df):
55
+ fig = px.bar(df['Yıldız Sayısı'].value_counts().sort_index(),
56
+ title='Yıldız Dağılımı')
57
+ fig.update_layout(xaxis_title='Yıldız Sayısı',
58
+ yaxis_title='Yorum Sayısı')
59
+ return fig
60
+
61
+ def create_sentiment_by_rating(self, df):
62
+ avg_sentiment = df.groupby('Yıldız Sayısı')['sentiment_score'].mean()
63
+ fig = px.line(avg_sentiment,
64
+ title='Yıldız Sayısına Göre Ortalama Sentiment Skoru')
65
+ fig.update_layout(xaxis_title='Yıldız Sayısı',
66
+ yaxis_title='Ortalama Sentiment Skoru')
67
+ return fig
68
+
69
+ def create_interface():
70
+ app = ReviewAnalysisApp()
71
+
72
+ with gr.Blocks(theme=gr.themes.Soft()) as interface:
73
+ gr.Markdown("# Trendyol Yorum Analizi")
74
+
75
+ with gr.Row():
76
+ url_input = gr.Textbox(
77
+ label="Trendyol Ürün Yorumları URL'si",
78
+ placeholder="https://www.trendyol.com/..."
79
+ )
80
+
81
+ analyze_btn = gr.Button("Analiz Et")
82
+
83
+ with gr.Row():
84
+ with gr.Column(scale=1):
85
+ summary_output = gr.Textbox(
86
+ label="Özet",
87
+ lines=10
88
+ )
89
+
90
+ with gr.Column(scale=2):
91
+ with gr.Tab("Duygu Analizi"):
92
+ sentiment_dist = gr.Plot()
93
+ with gr.Tab("Yıldız Dağılımı"):
94
+ rating_dist = gr.Plot()
95
+ with gr.Tab("Sentiment-Yıldız İlişkisi"):
96
+ sentiment_rating = gr.Plot()
97
+
98
+ analyze_btn.click(
99
+ fn=app.analyze_url,
100
+ inputs=[url_input],
101
+ outputs=[summary_output, sentiment_dist, rating_dist, sentiment_rating]
102
+ )
103
+
104
+ return interface
105
+
106
+ if __name__ == "__main__":
107
+ interface = create_interface()
108
+ interface.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt CHANGED
@@ -1,15 +1,14 @@
1
- pandas
2
- numpy
3
- torch
4
- transformers
5
- nltk
6
- plotly
7
- gradio
8
- selenium
9
- webdriver_manager
10
- tqdm
11
- regex
12
- scikit-learn
13
- accelerate>=0.26.0
14
- python-dotenv
15
- google-generativeai
 
1
+ pandas
2
+ numpy
3
+ torch
4
+ transformers
5
+ nltk
6
+ plotly
7
+ gradio
8
+ selenium
9
+ webdriver-manager
10
+ tqdm
11
+ regex
12
+ scikit-learn
13
+ google-generativeai
14
+ python-dotenv
 
scrape/trendyol_scraper.py CHANGED
@@ -3,136 +3,108 @@ from selenium.webdriver.chrome.service import Service
3
  from selenium.webdriver.common.by import By
4
  from selenium.webdriver.support.ui import WebDriverWait
5
  from selenium.webdriver.support import expected_conditions as EC
6
- from selenium.webdriver.chrome.options import Options
7
  import time
8
  import pandas as pd
9
  import os
10
- import random
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
  def scrape_reviews(url):
13
- print("Scraping başlatılıyor...")
14
-
15
  data_directory = "data"
16
  if not os.path.exists(data_directory):
17
  os.makedirs(data_directory)
18
 
19
- def comprehensive_scroll(driver):
20
- print("Sayfa kaydırma başlıyor...")
21
- last_height = driver.execute_script("return document.body.scrollHeight")
22
- scroll_count = 0
23
- while True:
24
- driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
25
- time.sleep(3)
26
- new_height = driver.execute_script("return document.body.scrollHeight")
27
- scroll_count += 1
28
- print(f"Scroll {scroll_count}: {new_height}")
29
- if new_height == last_height:
30
- break
31
- last_height = new_height
32
- print("Sayfa kaydırma tamamlandı")
33
-
34
- chrome_options = Options()
35
  chrome_options.add_argument('--headless')
 
36
  chrome_options.add_argument('--no-sandbox')
37
  chrome_options.add_argument('--disable-dev-shm-usage')
38
- chrome_options.add_argument('--disable-gpu')
39
- chrome_options.add_argument('--lang=tr')
40
- chrome_options.add_argument('--disable-notifications')
41
  chrome_options.add_argument("--window-size=1920,1080")
42
-
43
  try:
44
- print("Chrome başlatılıyor...")
45
- service = Service()
46
  driver = webdriver.Chrome(service=service, options=chrome_options)
47
 
48
- print(f"URL'ye gidiliyor: {url}")
49
  driver.get(url)
50
- time.sleep(5) # Sayfa yüklenme süresini artırdık
51
-
52
- # Sayfa kaynağını kontrol et
53
- page_source = driver.page_source
54
- print(f"Sayfa uzunluğu: {len(page_source)}")
55
 
56
- try:
57
- print("Çerez popup'ı aranıyor...")
58
- cookie_button = WebDriverWait(driver, 10).until(
59
- EC.presence_of_element_located((By.ID, "onetrust-accept-btn-handler"))
60
- )
61
- cookie_button.click()
62
- print("Çerez popup'ı kapatıldı")
63
- except Exception as e:
64
- print(f"Çerez popup'ı işlemi: {str(e)}")
65
 
66
  comprehensive_scroll(driver)
67
-
68
- print("Yorum elementleri aranıyor...")
69
- # Önce yorum container'ını bul
70
- review_container = driver.find_element(By.CLASS_NAME, "pr-rnr-com-w")
71
- print("Yorum container'ı bulundu")
72
 
73
- # Yorum elementlerini bul
74
- comment_elements = review_container.find_elements(By.CLASS_NAME, "comment-cards-item")
75
  total_comments = len(comment_elements)
76
-
77
- if total_comments == 0:
78
- print("Alternatif yorum elementi aranıyor...")
79
- comment_elements = driver.find_elements(By.CSS_SELECTOR, "div.comment-cards-item")
80
- total_comments = len(comment_elements)
81
-
82
- print(f"Bulunan yorum sayısı: {total_comments}")
83
-
84
- if total_comments == 0:
85
- print("Hiç yorum bulunamadı!")
86
- return pd.DataFrame()
87
-
88
  data = []
89
- for i, element in enumerate(comment_elements, 1):
 
90
  try:
91
- username = element.find_element(By.CLASS_NAME, "user-name").text
 
92
  except:
93
  username = "N/A"
94
- print(f"Kullanıcı adı alınamadı: {i}")
95
 
96
  try:
97
- comment = element.find_element(By.CLASS_NAME, "comment-text").text
 
98
  except:
99
  comment = "N/A"
100
- print(f"Yorum metni alınamadı: {i}")
101
 
102
  try:
103
- date = element.find_element(By.CLASS_NAME, "comment-date").text
 
104
  except:
105
  date = "N/A"
106
- print(f"Tarih alınamadı: {i}")
107
 
 
108
  try:
109
- stars = len(element.find_elements(By.CSS_SELECTOR, "div.full[style='width: 100%; max-width: 100%;']"))
 
110
  except:
111
- stars = 0
112
- print(f"Yıldız sayısı alınamadı: {i}")
113
 
114
  data.append({
115
- "Kullanıcı_id": i,
116
  "Kullanıcı Adı": username,
117
  "Yorum": comment,
118
  "Tarih": date,
119
- "Yıldız Sayısı": stars
120
  })
121
 
122
- if i % 5 == 0:
123
- print(f"{i} yorum işlendi")
124
-
125
- print("Veri toplama tamamlandı")
126
- return pd.DataFrame(data)
 
127
 
128
  except Exception as e:
129
- print(f"Kritik hata: {str(e)}")
130
- if 'driver' in locals():
131
- print("Son sayfa kaynağı:")
132
- print(driver.page_source[:500]) # İlk 500 karakteri göster
133
- return pd.DataFrame()
134
-
135
  finally:
136
- if 'driver' in locals():
137
- driver.quit()
138
- print("Chrome kapatıldı")
 
 
3
  from selenium.webdriver.common.by import By
4
  from selenium.webdriver.support.ui import WebDriverWait
5
  from selenium.webdriver.support import expected_conditions as EC
 
6
  import time
7
  import pandas as pd
8
  import os
9
+
10
+ def comprehensive_scroll(driver):
11
+ # Scroll until no more new content is loaded
12
+ last_height = driver.execute_script("return document.body.scrollHeight")
13
+ while True:
14
+ # Scroll to bottom
15
+ driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
16
+ time.sleep(3) # Wait for potential content loading
17
+
18
+ # Calculate new scroll height
19
+ new_height = driver.execute_script("return document.body.scrollHeight")
20
+
21
+ # Check if bottom has been reached
22
+ if new_height == last_height:
23
+ break
24
+
25
+ last_height = new_height
26
 
27
  def scrape_reviews(url):
28
+ """URL'den yorumları çeken fonksiyon"""
29
+ # Data directory oluştur
30
  data_directory = "data"
31
  if not os.path.exists(data_directory):
32
  os.makedirs(data_directory)
33
 
34
+ # Chrome options ayarları
35
+ chrome_options = webdriver.ChromeOptions()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  chrome_options.add_argument('--headless')
37
+ chrome_options.add_argument('--disable-gpu')
38
  chrome_options.add_argument('--no-sandbox')
39
  chrome_options.add_argument('--disable-dev-shm-usage')
 
 
 
40
  chrome_options.add_argument("--window-size=1920,1080")
41
+
42
  try:
43
+ # Linux için ChromeDriver ayarı
44
+ service = Service('chromedriver') # Linux'ta path belirtmeye gerek yok
45
  driver = webdriver.Chrome(service=service, options=chrome_options)
46
 
 
47
  driver.get(url)
 
 
 
 
 
48
 
49
+ # Çerez popup'ını kabul et
50
+ WebDriverWait(driver, 10).until(
51
+ EC.element_to_be_clickable((By.ID, 'onetrust-accept-btn-handler'))
52
+ ).click()
 
 
 
 
 
53
 
54
  comprehensive_scroll(driver)
 
 
 
 
 
55
 
56
+ comment_elements = driver.find_elements(By.XPATH, '/html/body/div[1]/div[4]/div/div/div/div/div[3]/div/div/div[3]/div[2]/div')
 
57
  total_comments = len(comment_elements)
58
+
 
 
 
 
 
 
 
 
 
 
 
59
  data = []
60
+ for i in range(1, total_comments + 1):
61
+ kullanıcı_id = i
62
  try:
63
+ username_xpath = f'/html/body/div[1]/div[4]/div/div/div/div/div[3]/div/div/div[3]/div[2]/div[{i}]/div[1]/div[2]/div[1]'
64
+ username = driver.find_element(By.XPATH, username_xpath).text
65
  except:
66
  username = "N/A"
 
67
 
68
  try:
69
+ comment_xpath = f'/html/body/div[1]/div[4]/div/div/div/div/div[3]/div/div/div[3]/div[2]/div[{i}]/div[2]/p'
70
+ comment = driver.find_element(By.XPATH, comment_xpath).text
71
  except:
72
  comment = "N/A"
 
73
 
74
  try:
75
+ date_xpath = f'/html/body/div[1]/div[4]/div/div/div/div/div[3]/div/div/div[3]/div[2]/div[{i}]/div[1]/div[2]/div[2]'
76
+ date = driver.find_element(By.XPATH, date_xpath).text
77
  except:
78
  date = "N/A"
 
79
 
80
+ star_xpath_base = f'/html/body/div[1]/div[4]/div/div/div/div/div[3]/div/div/div[3]/div[2]/div[{i}]/div[1]/div[1]/div'
81
  try:
82
+ full_stars = driver.find_elements(By.XPATH, f"{star_xpath_base}/div[@class='star-w']/div[@class='full'][@style='width: 100%; max-width: 100%;']")
83
+ star_count = len(full_stars)
84
  except:
85
+ star_count = 0
 
86
 
87
  data.append({
88
+ "Kullanıcı_id": kullanıcı_id,
89
  "Kullanıcı Adı": username,
90
  "Yorum": comment,
91
  "Tarih": date,
92
+ "Yıldız Sayısı": star_count
93
  })
94
 
95
+ # Geçici dosya olarak kaydet
96
+ temp_file = os.path.join(data_directory, 'temp_comments.csv')
97
+ df = pd.DataFrame(data)
98
+ df.to_csv(temp_file, index=False, encoding='utf-8-sig')
99
+
100
+ return df
101
 
102
  except Exception as e:
103
+ print(f"Hata oluştu: {str(e)}")
104
+ return pd.DataFrame() # Boş DataFrame döndür
105
+
 
 
 
106
  finally:
107
+ driver.quit()
108
+ # Geçici dosyayı sil
109
+ if os.path.exists(os.path.join(data_directory, 'temp_comments.csv')):
110
+ os.remove(os.path.join(data_directory, 'temp_comments.csv'))
scripts/data_prp_eda.py ADDED
@@ -0,0 +1,357 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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()
scripts/review_summarizer.py ADDED
@@ -0,0 +1,256 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 (
6
+ AutoTokenizer,
7
+ AutoModelForSequenceClassification,
8
+ pipeline
9
+ )
10
+ import torch
11
+ import os
12
+ import requests
13
+ from collections import Counter
14
+ import warnings
15
+ from nltk.tokenize import word_tokenize
16
+ from nltk.util import ngrams
17
+ import nltk
18
+ from wordcloud import WordCloud
19
+ import re
20
+ import google.generativeai as genai
21
+ from dotenv import load_dotenv
22
+ warnings.filterwarnings('ignore')
23
+
24
+ nltk.download('stopwords')
25
+ nltk.download('punkt')
26
+
27
+ class ReviewAnalyzer:
28
+ def __init__(self):
29
+ # Load environment variables
30
+ load_dotenv()
31
+
32
+ # Configure Gemini API
33
+ genai.configure(api_key=os.getenv('GOOGLE_API_KEY'))
34
+ self.model = genai.GenerativeModel('gemini-pro')
35
+
36
+ # Diğer model kurulumları (sentiment analizi için)
37
+ self.setup_sentiment_model()
38
+
39
+ self.turkish_stopwords = self.get_turkish_stopwords()
40
+
41
+ # Lojistik ve satıcı ile ilgili kelimeleri tanımla
42
+ self.logistics_seller_words = {
43
+ # Kargo ve teslimat ile ilgili
44
+ 'kargo', 'kargocu', 'paket', 'paketleme', 'teslimat', 'teslim',
45
+ 'gönderi', 'gönderim', 'ulaştı', 'ulaşım', 'geldi', 'kurye',
46
+ 'dağıtım', 'hasarlı', 'hasar', 'kutu', 'ambalaj', 'zamanında',
47
+ 'geç', 'hızlı', 'yavaş', 'günde', 'saatte',
48
+
49
+ # Satıcı ve mağaza ile ilgili
50
+ 'satıcı', 'mağaza', 'sipariş', 'trendyol', 'tedarik', 'stok',
51
+ 'garanti', 'fatura', 'iade', 'geri', 'müşteri', 'hizmet',
52
+ 'destek', 'iletişim', 'şikayet', 'sorun', 'çözüm', 'hediye',
53
+
54
+ # Fiyat ve ödeme ile ilgili
55
+ 'fiyat', 'ücret', 'para', 'bedava', 'ücretsiz', 'indirim',
56
+ 'kampanya', 'taksit', 'ödeme', 'bütçe', 'hesap', 'kur',
57
+
58
+ # Zaman ile ilgili teslimat kelimeleri
59
+ 'bugün', 'yarın', 'dün', 'hafta', 'gün', 'saat', 'süre',
60
+ 'bekleme', 'gecikme', 'erken', 'geç'
61
+ }
62
+
63
+ def get_turkish_stopwords(self):
64
+ """Genişletilmiş stop words listesini hazırla"""
65
+ github_url = "https://raw.githubusercontent.com/sgsinclair/trombone/master/src/main/resources/org/voyanttools/trombone/keywords/stop.tr.turkish-lucene.txt"
66
+ stop_words = set()
67
+
68
+ try:
69
+ response = requests.get(github_url)
70
+ if response.status_code == 200:
71
+ github_stops = set(word.strip() for word in response.text.split('\n') if word.strip())
72
+ stop_words.update(github_stops)
73
+ except Exception as e:
74
+ print(f"GitHub'dan stop words çekilirken hata oluştu: {e}")
75
+
76
+ stop_words.update(set(nltk.corpus.stopwords.words('turkish')))
77
+
78
+ additional_stops = {'bir', 've', 'çok', 'bu', 'de', 'da', 'için', 'ile', 'ben', 'sen',
79
+ 'o', 'biz', 'siz', 'onlar', 'bu', 'şu', 'ama', 'fakat', 'ancak',
80
+ 'lakin', 'ki', 'dahi', 'mi', 'mı', 'mu', 'mü', 'var', 'yok',
81
+ 'olan', 'içinde', 'üzerinde', 'bana', 'sana', 'ona', 'bize',
82
+ 'size', 'onlara', 'evet', 'hayır', 'tamam', 'oldu', 'olmuş',
83
+ 'olacak', 'etmek', 'yapmak', 'kez', 'kere', 'defa', 'adet'}
84
+ stop_words.update(additional_stops)
85
+
86
+ print(f"Toplam {len(stop_words)} adet stop words yüklendi.")
87
+ return stop_words
88
+
89
+ def preprocess_text(self, text):
90
+ """Metin ön işleme"""
91
+ if isinstance(text, str):
92
+ # Küçük harfe çevir
93
+ text = text.lower()
94
+ # Özel karakterleri temizle
95
+ text = re.sub(r'[^\w\s]', '', text)
96
+ # Sayıları temizle
97
+ text = re.sub(r'\d+', '', text)
98
+ # Fazla boşlukları temizle
99
+ text = re.sub(r'\s+', ' ', text).strip()
100
+ # Stop words'leri çıkar
101
+ words = text.split()
102
+ words = [word for word in words if word not in self.turkish_stopwords]
103
+ return ' '.join(words)
104
+ return ''
105
+
106
+ def setup_sentiment_model(self):
107
+ """Sentiment analiz modelini hazırla"""
108
+ self.device = "cuda" if torch.cuda.is_available() else "cpu"
109
+ print(f"Using device for sentiment: {self.device}")
110
+
111
+ model_name = "savasy/bert-base-turkish-sentiment-cased"
112
+ self.sentiment_tokenizer = AutoTokenizer.from_pretrained(model_name)
113
+ self.sentiment_model = (
114
+ AutoModelForSequenceClassification.from_pretrained(model_name)
115
+ .to(self.device)
116
+ .to(torch.float32)
117
+ )
118
+
119
+ def filter_reviews(self, df):
120
+ """Ürün ile ilgili olmayan yorumları filtrele"""
121
+ def is_product_review(text):
122
+ if not isinstance(text, str):
123
+ return False
124
+ return not any(word in text.lower() for word in self.logistics_seller_words)
125
+
126
+ filtered_df = df[df['Yorum'].apply(is_product_review)].copy()
127
+
128
+ print(f"\nFiltreleme İstatistikleri:")
129
+ print(f"Toplam yorum sayısı: {len(df)}")
130
+ print(f"Ürün yorumu sayısı: {len(filtered_df)}")
131
+ print(f"Filtrelenen yorum sayısı: {len(df) - len(filtered_df)}")
132
+ print(f"Filtreleme oranı: {((len(df) - len(filtered_df)) / len(df) * 100):.2f}%")
133
+
134
+ return filtered_df
135
+
136
+ def analyze_sentiment(self, df):
137
+ """Sentiment analizi yap"""
138
+ def predict_sentiment(text):
139
+ if not isinstance(text, str) or len(text.strip()) == 0:
140
+ return {"label": "Nötr", "score": 0.5}
141
+
142
+ try:
143
+ cleaned_text = self.preprocess_text(text)
144
+
145
+ inputs = self.sentiment_tokenizer(
146
+ cleaned_text,
147
+ return_tensors="pt",
148
+ truncation=True,
149
+ max_length=512,
150
+ padding=True
151
+ ).to(self.device)
152
+
153
+ with torch.no_grad():
154
+ outputs = self.sentiment_model(**inputs)
155
+ probs = torch.nn.functional.softmax(outputs.logits, dim=1)
156
+ prediction = probs.cpu().numpy()[0]
157
+
158
+ score = float(prediction[1])
159
+
160
+ if score > 0.75:
161
+ label = "Pozitif"
162
+ elif score < 0.25:
163
+ label = "Negatif"
164
+ elif score > 0.55:
165
+ label = "Pozitif"
166
+ elif score < 0.45:
167
+ label = "Negatif"
168
+ else:
169
+ label = "Nötr"
170
+
171
+ return {"label": label, "score": score}
172
+
173
+ except Exception as e:
174
+ print(f"Error in sentiment prediction: {e}")
175
+ return {"label": "Nötr", "score": 0.5}
176
+
177
+ print("\nSentiment analizi yapılıyor...")
178
+ results = [predict_sentiment(text) for text in df['Yorum']]
179
+
180
+ df['sentiment_score'] = [r['score'] for r in results]
181
+ df['sentiment_label'] = [r['label'] for r in results]
182
+ df['cleaned_text'] = df['Yorum'].apply(self.preprocess_text)
183
+
184
+ return df
185
+
186
+ def get_key_phrases(self, text_series):
187
+ """En önemli anahtar kelimeleri bul"""
188
+ text = ' '.join(text_series.astype(str))
189
+ words = self.preprocess_text(text).split()
190
+ word_freq = Counter(words)
191
+ # En az 3 kez geçen kelimeleri al
192
+ return {word: count for word, count in word_freq.items()
193
+ if count >= 3 and len(word) > 2}
194
+
195
+ def generate_summary(self, df):
196
+ """Yorumları özetle"""
197
+ # Yorumları ve yıldızları birleştir
198
+ reviews_with_ratings = [
199
+ f"Yıldız: {row['Yıldız Sayısı']}, Yorum: {row['Yorum']}"
200
+ for _, row in df.iterrows()
201
+ ]
202
+
203
+ # Prompt hazırla
204
+ prompt = f"""
205
+ Aşağıdaki ürün yorumlarını analiz edip özet çıkar:
206
+
207
+ {reviews_with_ratings[:50]} # İlk 50 yorumu al (API limiti için)
208
+
209
+ Lütfen şu başlıklar altında özetle:
210
+ 1. Genel Değerlendirme
211
+ 2. Olumlu Yönler
212
+ 3. Olumsuz Yönler
213
+ 4. Öneriler
214
+
215
+ Önemli: Yanıtını Türkçe olarak ver ve madde madde listele.
216
+ """
217
+
218
+ try:
219
+ response = self.model.generate_content(prompt)
220
+ summary = response.text
221
+ except Exception as e:
222
+ summary = f"Özet oluşturulurken hata oluştu: {str(e)}"
223
+
224
+ return summary
225
+
226
+ def analyze_reviews(self, df):
227
+ """Tüm yorumları analiz et"""
228
+ # Mevcut analiz fonksiyonu aynen kalabilir
229
+ pass
230
+
231
+ def analyze_reviews(file_path):
232
+ df = pd.read_csv(file_path)
233
+
234
+ analyzer = ReviewAnalyzer()
235
+
236
+ filtered_df = analyzer.filter_reviews(df)
237
+
238
+ print("Sentiment analizi başlatılıyor...")
239
+ analyzed_df = analyzer.analyze_sentiment(filtered_df)
240
+
241
+ analyzed_df.to_csv('sentiment_analyzed_reviews.csv', index=False, encoding='utf-8-sig')
242
+ print("Sentiment analizi tamamlandı ve kaydedildi.")
243
+
244
+ print("\nÜrün özeti oluşturuluyor...")
245
+ summary = analyzer.generate_summary(analyzed_df)
246
+
247
+ with open('urun_ozeti.txt', 'w', encoding='utf-8') as f:
248
+ f.write(summary)
249
+
250
+ print("\nÜrün Özeti:")
251
+ print("-" * 50)
252
+ print(summary)
253
+ print("\nÖzet 'urun_ozeti.txt' dosyasına kaydedildi.")
254
+
255
+ if __name__ == "__main__":
256
+ analyze_reviews('data/macbook_product_comments_with_ratings.csv')
scripts/sentiment_bert_model.py ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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()