boompack commited on
Commit
6bdcb96
·
verified ·
1 Parent(s): 8a11e5e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +169 -91
app.py CHANGED
@@ -15,7 +15,8 @@ logger = logging.getLogger(__name__)
15
 
16
  def clean_text(text):
17
  """Очищает текст от лишних пробелов и переносов строк"""
18
- return ' '.join(text.split())
 
19
 
20
  def count_emojis(text):
21
  """Подсчитывает количество эмодзи в тексте"""
@@ -34,19 +35,28 @@ def analyze_sentiment(text):
34
  """Расширенный анализ тональности по эмодзи и ключевым словам"""
35
  positive_indicators = ['🔥', '❤️', '👍', '😊', '💪', '👏', '🎉', '♥️', '😍', '🙏',
36
  'круто', 'супер', 'класс', 'огонь', 'пушка', 'отлично', 'здорово',
37
- 'прекрасно', 'молодец', 'красота', 'спасибо', 'топ', 'лучший']
 
 
38
  negative_indicators = ['👎', '😢', '😞', '😠', '😡', '💔', '😕', '😑',
39
  'плохо', 'ужас', 'отстой', 'фу', 'жесть', 'ужасно',
40
- 'разочарован', 'печаль', 'грустно']
 
41
 
42
  text_lower = text.lower()
 
 
43
  positive_count = sum(1 for ind in positive_indicators if ind in text_lower)
44
  negative_count = sum(1 for ind in negative_indicators if ind in text_lower)
45
 
 
46
  exclamation_count = text.count('!')
47
- positive_count += exclamation_count * 0.5 if positive_count > negative_count else 0
48
- negative_count += exclamation_count * 0.5 if negative_count > positive_count else 0
 
 
49
 
 
50
  if positive_count > negative_count:
51
  return 'positive'
52
  elif negative_count > positive_count:
@@ -54,62 +64,122 @@ def analyze_sentiment(text):
54
  return 'neutral'
55
 
56
  def extract_comment_data(comment_text):
57
- """Извлекает данные из отдельного комментария"""
58
  try:
59
- if 'Скрыто алгоритмами Instagram' in comment_text:
60
- username_match = re.search(r"Фото профиля ([^\n]+)", comment_text)
61
- if username_match:
62
- return username_match.group(1).strip(), "", 0, 0
63
-
64
- username_match = re.search(r"Фото профиля ([^\n]+)", comment_text)
65
- if not username_match:
66
- return None, None, 0, 0
67
-
68
- username = username_match.group(1).strip()
69
-
70
- comment_pattern = fr"{re.escape(username)}\n(.*?)(?:\d+ нед\.)"
71
- comment_match = re.search(comment_pattern, comment_text, re.DOTALL)
72
- if comment_match:
73
- comment = clean_text(comment_match.group(1))
74
- comment = re.sub(fr'^{re.escape(username)}\s*', '', comment)
75
- comment = re.sub(r'^@[\w\.]+ ', '', comment)
76
- else:
77
- comment = ""
78
 
79
- week_match = re.search(r'(\d+) нед\.', comment_text)
80
- weeks = int(week_match.group(1)) if week_match else 0
 
 
 
81
 
82
- likes = 0
83
  likes_patterns = [
84
  r"(\d+) отметк[аи] \"Нравится\"",
85
  r"Нравится: (\d+)",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  ]
87
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  for pattern in likes_patterns:
89
  likes_match = re.search(pattern, comment_text)
90
  if likes_match:
91
  likes = int(likes_match.group(1))
92
  break
93
-
94
  return username, comment.strip(), likes, weeks
 
95
  except Exception as e:
96
  logger.error(f"Error extracting comment data: {e}")
97
  return None, None, 0, 0
98
 
99
- def analyze_post(content_type, link_to_post, post_likes, post_date, description, comment_count, all_comments):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  try:
101
- # Улучшенное разделение комментариев
102
- comments_blocks = re.split(r'(?=Фото профиля|Скрыто алгоритмами Instagram)', all_comments)
103
- comments_blocks = [block for block in comments_blocks if block.strip()]
 
 
 
 
104
 
105
- # Подсчет скрытых комментариев
106
- hidden_comments = len(re.findall(r'Скрыто алгоритмами Instagram', all_comments))
 
107
 
 
108
  usernames = []
109
  comments = []
110
  likes = []
111
  weeks = []
112
-
113
  total_emojis = 0
114
  mentions = []
115
  sentiments = []
@@ -124,12 +194,13 @@ def analyze_post(content_type, link_to_post, post_likes, post_date, description,
124
  continue
125
 
126
  username, comment, like_count, week_number = extract_comment_data(block)
127
- if username and (comment is not None):
128
  usernames.append(username)
129
  comments.append(comment)
130
  likes.append(str(like_count))
131
  weeks.append(week_number)
132
 
 
133
  total_emojis += count_emojis(comment)
134
  mentions.extend(extract_mentions(comment))
135
  sentiment = analyze_sentiment(comment)
@@ -140,6 +211,7 @@ def analyze_post(content_type, link_to_post, post_likes, post_date, description,
140
  words_per_comment.append(len(words))
141
  all_words.extend(words)
142
 
 
143
  if username not in user_engagement:
144
  user_engagement[username] = {
145
  'comments': 0,
@@ -147,8 +219,9 @@ def analyze_post(content_type, link_to_post, post_likes, post_date, description,
147
  'emoji_usage': 0,
148
  'avg_length': 0,
149
  'sentiments': [],
150
- 'weeks': [] # Добавлено для анализа временной активности
151
  }
 
152
  user_stats = user_engagement[username]
153
  user_stats['comments'] += 1
154
  user_stats['total_likes'] += like_count
@@ -157,10 +230,10 @@ def analyze_post(content_type, link_to_post, post_likes, post_date, description,
157
  user_stats['sentiments'].append(sentiment)
158
  user_stats['weeks'].append(week_number)
159
 
160
- # Проверка количества комментариев
161
  total_comments = len(comments)
162
- if total_comments != comment_count:
163
- logger.warning(f"Found {total_comments} comments, but expected {comment_count}")
164
 
165
  # Обновление статистики пользователей
166
  for username in user_engagement:
@@ -170,47 +243,49 @@ def analyze_post(content_type, link_to_post, post_likes, post_date, description,
170
  stats['sentiment_ratio'] = sum(1 for s in stats['sentiments'] if s == 'positive') / len(stats['sentiments'])
171
  stats['activity_period'] = max(stats['weeks']) - min(stats['weeks']) if stats['weeks'] else 0
172
 
173
- # Расчет базовой статистики
174
  avg_comment_length = sum(comment_lengths) / total_comments
175
  sentiment_distribution = Counter(sentiments)
176
  most_active_users = Counter(usernames).most_common(5)
177
  most_mentioned = Counter(mentions).most_common(5)
178
  avg_likes = sum(map(int, likes)) / len(likes) if likes else 0
179
- earliest_week = max(weeks) if weeks else 0
180
- latest_week = min(weeks) if weeks else 0
181
-
182
- # Расширенная статистика
183
- median_comment_length = statistics.median(comment_lengths)
184
- avg_words_per_comment = sum(words_per_comment) / total_comments
185
- common_words = Counter(all_words).most_common(10)
186
 
187
- # Экспериментальная аналитика
188
- engagement_periods = {
189
- 'early': [],
190
- 'middle': [],
191
- 'late': []
192
- }
193
- week_range = max(weeks) - min(weeks) if weeks else 0
194
- period_length = week_range / 3 if week_range > 0 else 1
195
-
196
- for i, week in enumerate(weeks):
197
- if week >= max(weeks) - period_length:
198
- engagement_periods['early'].append(i)
199
- elif week >= max(weeks) - 2 * period_length:
200
- engagement_periods['middle'].append(i)
201
- else:
202
- engagement_periods['late'].append(i)
203
-
204
- period_stats = {
205
- period: {
206
- 'comments': len(indices),
207
- 'avg_likes': sum(int(likes[i]) for i in indices) / len(indices) if indices else 0,
208
- 'sentiment_ratio': sum(1 for i in indices if sentiments[i] == 'positive') / len(indices) if indices else 0
209
  }
210
- for period, indices in engagement_periods.items()
211
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
 
213
- # Подготовка данных для CSV
214
  csv_data = {
215
  'metadata': {
216
  'content_type': content_type,
@@ -218,27 +293,22 @@ def analyze_post(content_type, link_to_post, post_likes, post_date, description,
218
  'post_likes': post_likes,
219
  'post_date': post_date,
220
  'total_comments': total_comments,
221
- 'expected_comments': comment_count,
222
- 'hidden_comments': hidden_comments
223
  },
224
  'basic_stats': {
225
- 'avg_comment_length': avg_comment_length,
226
- 'median_comment_length': median_comment_length,
227
- 'avg_words': avg_words_per_comment,
228
  'total_emojis': total_emojis,
229
- 'avg_likes': avg_likes
230
- },
231
- 'sentiment_stats': {
232
- 'positive': sentiment_distribution['positive'],
233
- 'neutral': sentiment_distribution['neutral'],
234
- 'negative': sentiment_distribution['negative']
235
  },
 
236
  'period_analysis': period_stats,
237
  'top_users': dict(most_active_users),
238
  'top_mentioned': dict(most_mentioned)
239
  }
240
 
241
- # Создаем CSV строку
242
  output = StringIO()
243
  writer = csv.writer(output)
244
  for section, data in csv_data.items():
@@ -248,7 +318,7 @@ def analyze_post(content_type, link_to_post, post_likes, post_date, description,
248
  writer.writerow([])
249
  csv_output = output.getvalue()
250
 
251
- # Формируем текстовый отчет
252
  analytics_summary = (
253
  f"CSV DATA:\n{csv_output}\n\n"
254
  f"ДЕТАЛЬНЫЙ АНАЛИЗ:\n"
@@ -256,10 +326,18 @@ def analyze_post(content_type, link_to_post, post_likes, post_date, description,
256
  f"Ссылка: {link_to_post}\n\n"
257
  f"СТАТИСТИКА:\n"
258
  f"- Всего комментариев: {total_comments} (ожидалось: {comment_count})\n"
259
- f"- Скрытых комментариев: {hidden_comments}\n"
260
- f"- Всего лайков: {sum(map(int, likes))}\n"
261
- f"- Среднее лайков: {avg_likes:.1f}\n"
262
- f"- Период: {earliest_week}-{latest_week} недель\n\n"
 
 
 
 
 
 
 
 
263
  f"АНАЛИЗ КОНТЕНТА:\n"
264
  f"- Средняя длина: {avg_comment_length:.1f} символов\n"
265
  f"- Медиана длины: {median_comment_length} символов\n"
 
15
 
16
  def clean_text(text):
17
  """Очищает текст от лишних пробелов и переносов строк"""
18
+ text = re.sub(r'\s+', ' ', text)
19
+ return text.strip()
20
 
21
  def count_emojis(text):
22
  """Подсчитывает количество эмодзи в тексте"""
 
35
  """Расширенный анализ тональности по эмодзи и ключевым словам"""
36
  positive_indicators = ['🔥', '❤️', '👍', '😊', '💪', '👏', '🎉', '♥️', '😍', '🙏',
37
  'круто', 'супер', 'класс', 'огонь', 'пушка', 'отлично', 'здорово',
38
+ 'прекрасно', 'молодец', 'красота', 'спасибо', 'топ', 'лучший',
39
+ 'amazing', 'wonderful', 'great', 'perfect', 'love', 'beautiful']
40
+
41
  negative_indicators = ['👎', '😢', '😞', '😠', '😡', '💔', '😕', '😑',
42
  'плохо', 'ужас', 'отстой', 'фу', 'жесть', 'ужасно',
43
+ 'разочарован', 'печаль', 'грустно', 'bad', 'worst',
44
+ 'terrible', 'awful', 'sad', 'disappointed']
45
 
46
  text_lower = text.lower()
47
+
48
+ # Подсчет индикаторов настроения
49
  positive_count = sum(1 for ind in positive_indicators if ind in text_lower)
50
  negative_count = sum(1 for ind in negative_indicators if ind in text_lower)
51
 
52
+ # Учет восклицательных знаков
53
  exclamation_count = text.count('!')
54
+ if positive_count > negative_count:
55
+ positive_count += exclamation_count * 0.5
56
+ elif negative_count > positive_count:
57
+ negative_count += exclamation_count * 0.5
58
 
59
+ # Определение итогового настроения
60
  if positive_count > negative_count:
61
  return 'positive'
62
  elif negative_count > positive_count:
 
64
  return 'neutral'
65
 
66
  def extract_comment_data(comment_text):
67
+ """Извлекает данные из отдельного комментария с поддержкой различных форматов"""
68
  try:
69
+ # Паттерны для извлечения данных
70
+ username_patterns = [
71
+ r"Фото профиля ([^\n]+)",
72
+ r"^([^\s]+)\s+",
73
+ r"@([^\s]+)\s+",
74
+ ]
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
+ time_patterns = [
77
+ r"(\d+)\s*(?:ч|нед)\.",
78
+ r"(\d+)\s*(?:h|w)",
79
+ r"(\d+)\s*(?:час|hour|week)",
80
+ ]
81
 
 
82
  likes_patterns = [
83
  r"(\d+) отметк[аи] \"Нравится\"",
84
  r"Нравится: (\d+)",
85
+ r"(\d+) отметка \"Нравится\"",
86
+ r"\"Нравится\": (\d+)",
87
+ r"likes?: (\d+)",
88
+ ]
89
+
90
+ # Поиск имени пользователя
91
+ username = None
92
+ for pattern in username_patterns:
93
+ username_match = re.search(pattern, comment_text)
94
+ if username_match:
95
+ username = username_match.group(1).strip()
96
+ break
97
+
98
+ if not username:
99
+ return None, None, 0, 0
100
+
101
+ # Извлечение комментария
102
+ comment = comment_text
103
+
104
+ # Удаление метаданных
105
+ metadata_patterns = [
106
+ r"Фото профиля [^\n]+\n",
107
+ r"\d+\s*(?:ч|нед|h|w|час|hour|week)\.",
108
+ r"Нравится:?\s*\d+",
109
+ r"\d+ отметк[аи] \"Нравится\"",
110
+ r"Ответить",
111
+ r"Показать перевод",
112
+ r"Скрыть все ответы",
113
+ r"Смотреть все ответы \(\d+\)",
114
+ username
115
  ]
116
 
117
+ for pattern in metadata_patterns:
118
+ comment = re.sub(pattern, '', comment)
119
+
120
+ comment = clean_text(comment)
121
+
122
+ # Определение времени публикации
123
+ weeks = 0
124
+ for pattern in time_patterns:
125
+ time_match = re.search(pattern, comment_text)
126
+ if time_match:
127
+ time_value = int(time_match.group(1))
128
+ if any(unit in comment_text.lower() for unit in ['нед', 'w', 'week']):
129
+ weeks = time_value
130
+ else:
131
+ weeks = time_value / (24 * 7) # конвертация часов в недели
132
+ break
133
+
134
+ # Подсчет лайков
135
+ likes = 0
136
  for pattern in likes_patterns:
137
  likes_match = re.search(pattern, comment_text)
138
  if likes_match:
139
  likes = int(likes_match.group(1))
140
  break
141
+
142
  return username, comment.strip(), likes, weeks
143
+
144
  except Exception as e:
145
  logger.error(f"Error extracting comment data: {e}")
146
  return None, None, 0, 0
147
 
148
+ def analyze_post(content_type: str, link_to_post: str, post_likes: int, post_date: str,
149
+ description: str, comment_count: int, all_comments: str) -> Tuple[str, str, str, str, str]:
150
+ """
151
+ Анализирует пост Instagram и его комментарии
152
+
153
+ Args:
154
+ content_type: Тип контента (фото/видео)
155
+ link_to_post: Ссылка на пост
156
+ post_likes: Количество лайков поста
157
+ post_date: Дата публикации
158
+ description: Описание поста
159
+ comment_count: Ожидаемое количество комментариев
160
+ all_comments: Текст всех комментариев
161
+
162
+ Returns:
163
+ Tuple[str, str, str, str, str]: Кортеж с результатами анализа
164
+ """
165
  try:
166
+ # Разделение на блоки комментариев
167
+ comment_patterns = [
168
+ r"(?=Фото профиля)",
169
+ r"(?=\n\s*[a-zA-Z0-9._]+\s+[^\n]+\n)",
170
+ r"(?=^[a-zA-Z0-9._]+\s+[^\n]+\n)",
171
+ r"(?=@[a-zA-Z0-9._]+\s+[^\n]+\n)"
172
+ ]
173
 
174
+ split_pattern = '|'.join(comment_patterns)
175
+ comments_blocks = re.split(split_pattern, all_comments)
176
+ comments_blocks = [block.strip() for block in comments_blocks if block and block.strip()]
177
 
178
+ # Инициализация переменных для анализа
179
  usernames = []
180
  comments = []
181
  likes = []
182
  weeks = []
 
183
  total_emojis = 0
184
  mentions = []
185
  sentiments = []
 
194
  continue
195
 
196
  username, comment, like_count, week_number = extract_comment_data(block)
197
+ if username and comment:
198
  usernames.append(username)
199
  comments.append(comment)
200
  likes.append(str(like_count))
201
  weeks.append(week_number)
202
 
203
+ # Сбор статистики
204
  total_emojis += count_emojis(comment)
205
  mentions.extend(extract_mentions(comment))
206
  sentiment = analyze_sentiment(comment)
 
211
  words_per_comment.append(len(words))
212
  all_words.extend(words)
213
 
214
+ # Обновление статистики пользователя
215
  if username not in user_engagement:
216
  user_engagement[username] = {
217
  'comments': 0,
 
219
  'emoji_usage': 0,
220
  'avg_length': 0,
221
  'sentiments': [],
222
+ 'weeks': []
223
  }
224
+
225
  user_stats = user_engagement[username]
226
  user_stats['comments'] += 1
227
  user_stats['total_likes'] += like_count
 
230
  user_stats['sentiments'].append(sentiment)
231
  user_stats['weeks'].append(week_number)
232
 
233
+ # Расчет статистики
234
  total_comments = len(comments)
235
+ if total_comments == 0:
236
+ return "No comments found", "", "", "", "0"
237
 
238
  # Обновление статистики пользователей
239
  for username in user_engagement:
 
243
  stats['sentiment_ratio'] = sum(1 for s in stats['sentiments'] if s == 'positive') / len(stats['sentiments'])
244
  stats['activity_period'] = max(stats['weeks']) - min(stats['weeks']) if stats['weeks'] else 0
245
 
246
+ # Базовая статистика
247
  avg_comment_length = sum(comment_lengths) / total_comments
248
  sentiment_distribution = Counter(sentiments)
249
  most_active_users = Counter(usernames).most_common(5)
250
  most_mentioned = Counter(mentions).most_common(5)
251
  avg_likes = sum(map(int, likes)) / len(likes) if likes else 0
 
 
 
 
 
 
 
252
 
253
+ # Временной анализ
254
+ if weeks:
255
+ earliest_week = max(weeks)
256
+ latest_week = min(weeks)
257
+ week_range = earliest_week - latest_week
258
+
259
+ # Разделение на периоды
260
+ period_length = week_range / 3 if week_range > 0 else 1
261
+ engagement_periods = {
262
+ 'early': [],
263
+ 'middle': [],
264
+ 'late': []
 
 
 
 
 
 
 
 
 
 
265
  }
266
+
267
+ for i, week in enumerate(weeks):
268
+ if week >= earliest_week - period_length:
269
+ engagement_periods['early'].append(i)
270
+ elif week >= earliest_week - 2 * period_length:
271
+ engagement_periods['middle'].append(i)
272
+ else:
273
+ engagement_periods['late'].append(i)
274
+
275
+ period_stats = {
276
+ period: {
277
+ 'comments': len(indices),
278
+ 'avg_likes': sum(int(likes[i]) for i in indices) / len(indices) if indices else 0,
279
+ 'sentiment_ratio': sum(1 for i in indices if sentiments[i] == 'positive') / len(indices) if indices else 0
280
+ }
281
+ for period, indices in engagement_periods.items()
282
+ }
283
+ else:
284
+ period_stats = {}
285
+ earliest_week = 0
286
+ latest_week = 0
287
 
288
+ # Подготовка CSV
289
  csv_data = {
290
  'metadata': {
291
  'content_type': content_type,
 
293
  'post_likes': post_likes,
294
  'post_date': post_date,
295
  'total_comments': total_comments,
296
+ 'expected_comments': comment_count
 
297
  },
298
  'basic_stats': {
299
+ 'avg_comment_length': round(avg_comment_length, 2),
300
+ 'median_comment_length': statistics.median(comment_lengths),
301
+ 'avg_words': round(sum(words_per_comment) / total_comments, 2),
302
  'total_emojis': total_emojis,
303
+ 'avg_likes': round(avg_likes, 2)
 
 
 
 
 
304
  },
305
+ 'sentiment_stats': dict(Counter(sentiments)),
306
  'period_analysis': period_stats,
307
  'top_users': dict(most_active_users),
308
  'top_mentioned': dict(most_mentioned)
309
  }
310
 
311
+ # Создание CSV строки
312
  output = StringIO()
313
  writer = csv.writer(output)
314
  for section, data in csv_data.items():
 
318
  writer.writerow([])
319
  csv_output = output.getvalue()
320
 
321
+ # Формирование отчета
322
  analytics_summary = (
323
  f"CSV DATA:\n{csv_output}\n\n"
324
  f"ДЕТАЛЬНЫЙ АНАЛИЗ:\n"
 
326
  f"Ссылка: {link_to_post}\n\n"
327
  f"СТАТИСТИКА:\n"
328
  f"- Всего комментариев: {total_comments} (ожидалось: {comment_count})\n"
329
+ f"- Всего лайков на комментариях: {sum(map(int, likes))}\n"
330
+ f"- Среднее лайков на комментарий: {avg_likes:.1f}\n"
331
+ f"- Период активности: {earliest_week}-{latest_week} недель\n\n"
332
+ f"АНАЛИЗ КОНТЕНТА:\n"
333
+ f"- Средняя длина комментария: {avg_comment_length:.1f} символов\n"
334
+ f"- Медиана длины: {statistics.median(comment_lengths)} символов\n"
335
+ f"- Среднее количество слов: {sum(words_per_comment) / total_comments:.1f}\n"
336
+ f"- Всего эмодзи: {total_emojis}\n"
337
+ f"- Тональность:\n"
338
+ f" * Позитивных: {sentiment_distribution['positive']}\n"
339
+ f" * Нейтральных: {sentiment_distribution['neutral']}\n"
340
+ f" * Негативных: {sentiment_distribution['negative']}\n\n"
341
  f"АНАЛИЗ КОНТЕНТА:\n"
342
  f"- Средняя длина: {avg_comment_length:.1f} символов\n"
343
  f"- Медиана длины: {median_comment_length} символов\n"