|
import gradio as gr |
|
import re |
|
from collections import Counter |
|
from datetime import datetime |
|
import emoji |
|
import logging |
|
from typing import Tuple, List, Optional |
|
import statistics |
|
import csv |
|
from textblob import TextBlob |
|
import numpy as np |
|
|
|
|
|
logging.basicConfig(level=logging.INFO) |
|
logger = logging.getLogger(__name__) |
|
|
|
def clean_text(text): |
|
"""Очищает текст от лишних пробелов и переносов строк""" |
|
return ' '.join(text.split()) |
|
|
|
def count_emojis(text): |
|
"""Подсчитывает количество эмодзи в тексте""" |
|
return len([c for c in text if c in emoji.EMOJI_DATA]) |
|
|
|
def extract_mentions(text): |
|
"""Извлекает упоминания пользователей из текста""" |
|
return re.findall(r'@[\w\.]+', text) |
|
|
|
def get_comment_words(text): |
|
"""Получает список слов из комментария для анализа""" |
|
words = re.findall(r'\w+', text.lower()) |
|
return [w for w in words if len(w) > 2] |
|
|
|
def analyze_sentiment(text): |
|
"""Расширенный анализ тональности по эмодзи и ключевым словам""" |
|
positive_indicators = ['🔥', '❤️', '👍', '😊', '💪', '👏', '🎉', '♥️', '😍', '🙏', |
|
'круто', 'супер', 'класс', 'огонь', 'пушка', 'отлично', 'здорово', |
|
'прекрасно', 'молодец', 'красота', 'спасибо', 'топ'] |
|
negative_indicators = ['👎', '😢', '😞', '😠', '😡', '💔', '😕', '😑', |
|
'плохо', 'ужас', 'отстой', 'фу', 'жесть', 'ужасно', |
|
'разочарован', 'печаль', 'грустно'] |
|
|
|
text_lower = text.lower() |
|
positive_count = sum(1 for ind in positive_indicators if ind in text_lower) |
|
negative_count = sum(1 for ind in negative_indicators if ind in text_lower) |
|
|
|
exclamation_count = text.count('!') |
|
positive_count += exclamation_count * 0.5 if positive_count > negative_count else 0 |
|
negative_count += exclamation_count * 0.5 if negative_count > positive_count else 0 |
|
|
|
|
|
blob = TextBlob(text) |
|
sentiment_score = blob.sentiment.polarity |
|
|
|
|
|
final_score = (positive_count - negative_count) + sentiment_score |
|
|
|
if final_score > 0: |
|
return 'positive' |
|
elif final_score < 0: |
|
return 'negative' |
|
return 'neutral' |
|
|
|
def extract_comment_data(comment_text): |
|
""" |
|
Извлекает данные из отдельного комментария |
|
""" |
|
try: |
|
|
|
if 'Скрыто алгоритмами Instagram' in comment_text: |
|
username_match = re.search(r"Фото профиля ([^\n]+)", comment_text) |
|
if username_match: |
|
return username_match.group(1).strip(), "", 0, 0 |
|
|
|
|
|
username_match = re.search(r"Фото профиля ([^\n]+)", comment_text) |
|
if not username_match: |
|
return None, None, 0, 0 |
|
|
|
username = username_match.group(1).strip() |
|
|
|
|
|
comment_pattern = fr"{re.escape(username)}\n(.*?)(?:\d+ нед\.)" |
|
comment_match = re.search(comment_pattern, comment_text, re.DOTALL) |
|
if comment_match: |
|
comment = clean_text(comment_match.group(1)) |
|
comment = re.sub(fr'^{re.escape(username)}\s*', '', comment) |
|
comment = re.sub(r'^@[\w\.]+ ', '', comment) |
|
else: |
|
comment = "" |
|
|
|
|
|
week_match = re.search(r'(\d+) нед\.', comment_text) |
|
weeks = int(week_match.group(1)) if week_match else 0 |
|
|
|
|
|
likes = 0 |
|
likes_patterns = [ |
|
r"(\d+) отметк[аи] \"Нравится\"", |
|
r"Нравится: (\d+)", |
|
r"\"Нравится\": (\d+)", |
|
] |
|
|
|
for pattern in likes_patterns: |
|
likes_match = re.search(pattern, comment_text) |
|
if likes_match: |
|
likes = int(likes_match.group(1)) |
|
break |
|
|
|
return username, comment.strip(), likes, weeks |
|
except Exception as e: |
|
logger.error(f"Error extracting comment data: {e}") |
|
return None, None, 0, 0 |
|
|
|
def analyze_post(content_type, link_to_post, post_likes, post_date, description, comment_count, all_comments): |
|
try: |
|
|
|
comments_blocks = re.split(r'(?=Фото профиля|Скрыто алгоритмами Instagram)', all_comments) |
|
comments_blocks = [block for block in comments_blocks if block.strip()] |
|
|
|
|
|
usernames = [] |
|
comments = [] |
|
likes = [] |
|
weeks = [] |
|
|
|
|
|
total_emojis = 0 |
|
mentions = [] |
|
sentiments = [] |
|
comment_lengths = [] |
|
words_per_comment = [] |
|
all_words = [] |
|
user_engagement = {} |
|
reply_chains = [] |
|
current_chain = [] |
|
|
|
|
|
for block in comments_blocks: |
|
username, comment, like_count, week_number = extract_comment_data(block) |
|
if username and (comment is not None): |
|
usernames.append(username) |
|
comments.append(comment) |
|
likes.append(str(like_count)) |
|
weeks.append(week_number) |
|
|
|
|
|
total_emojis += count_emojis(comment) |
|
comment_mentions = extract_mentions(comment) |
|
mentions.extend(comment_mentions) |
|
sentiment = analyze_sentiment(comment) |
|
sentiments.append(sentiment) |
|
comment_lengths.append(len(comment)) |
|
|
|
|
|
if comment_mentions: |
|
current_chain.append((username, comment_mentions[0])) |
|
else: |
|
if current_chain: |
|
reply_chains.append(current_chain) |
|
current_chain = [] |
|
|
|
|
|
words = get_comment_words(comment) |
|
words_per_comment.append(len(words)) |
|
all_words.extend(words) |
|
|
|
|
|
if username not in user_engagement: |
|
user_engagement[username] = { |
|
'comments': 0, |
|
'total_likes': 0, |
|
'emoji_usage': 0, |
|
'avg_length': 0, |
|
'sentiments': [], |
|
'mentions_received': 0, |
|
'mentions_made': len(comment_mentions), |
|
'response_time': [] |
|
} |
|
user_stats = user_engagement[username] |
|
user_stats['comments'] += 1 |
|
user_stats['total_likes'] += like_count |
|
user_stats['emoji_usage'] += count_emojis(comment) |
|
user_stats['avg_length'] += len(comment) |
|
user_stats['sentiments'].append(sentiment) |
|
|
|
|
|
if current_chain: |
|
reply_chains.append(current_chain) |
|
|
|
|
|
for username in user_engagement: |
|
stats = user_engagement[username] |
|
stats['avg_length'] /= stats['comments'] |
|
stats['engagement_rate'] = stats['total_likes'] / stats['comments'] |
|
stats['sentiment_ratio'] = sum(1 for s in stats['sentiments'] if s == 'positive') / len(stats['sentiments']) |
|
stats['mentions_received'] = sum(1 for m in mentions if m == f'@{username}') |
|
|
|
|
|
experimental_metrics = { |
|
'conversation_depth': len(max(reply_chains, key=len)) if reply_chains else 0, |
|
'avg_response_time': np.mean([c['avg_length'] for c in user_engagement.values()]), |
|
'engagement_consistency': np.std([c['comments'] for c in user_engagement.values()]), |
|
'user_interaction_score': len([c for c in comments if any(mention in c for mention in mentions)]) / len(comments), |
|
'sentiment_volatility': np.std([1 if s == 'positive' else -1 if s == 'negative' else 0 for s in sentiments]), |
|
} |
|
|
|
|
|
csv_data = { |
|
'post_url': link_to_post, |
|
'total_comments': len(comments), |
|
'total_likes': sum(map(int, likes)), |
|
'avg_likes_per_comment': sum(map(int, likes)) / len(comments), |
|
'unique_users': len(set(usernames)), |
|
'emoji_rate': total_emojis / len(comments), |
|
'avg_comment_length': sum(comment_lengths) / len(comments), |
|
'positive_sentiment_ratio': sum(1 for s in sentiments if s == 'positive') / len(sentiments), |
|
'mention_rate': len(mentions) / len(comments), |
|
'conversation_depth': experimental_metrics['conversation_depth'], |
|
'user_interaction_score': experimental_metrics['user_interaction_score'], |
|
'sentiment_volatility': experimental_metrics['sentiment_volatility'], |
|
} |
|
|
|
|
|
csv_output = ",".join([f"{k}:{v}" for k, v in csv_data.items()]) |
|
|
|
|
|
analytics_summary = ( |
|
f"CSV_DATA\n{csv_output}\n\n" |
|
f"DETAILED_ANALYTICS\n" |
|
f"Content Type: {content_type}\n" |
|
f"Link to Post: {link_to_post}\n\n" |
|
f"BASIC_STATS\n" |
|
f"Total Comments: {len(comments)}\n" |
|
f"Total Likes: {sum(map(int, likes))}\n" |
|
f"Unique Users: {len(set(usernames))}\n" |
|
f"Activity Period: {max(weeks)}-{min(weeks)} weeks\n\n" |
|
f"CONTENT_ANALYSIS\n" |
|
f"Avg Comment Length: {sum(comment_lengths) / len(comments):.1f}\n" |
|
f"Total Emojis: {total_emojis}\n" |
|
f"Sentiment Distribution: {Counter(sentiments)}\n\n" |
|
f"EXPERIMENTAL_METRICS\n" |
|
f"Conversation Depth: {experimental_metrics['conversation_depth']}\n" |
|
f"User Interaction Score: {experimental_metrics['user_interaction_score']:.2f}\n" |
|
f"Sentiment Volatility: {experimental_metrics['sentiment_volatility']:.2f}\n" |
|
f"Engagement Consistency: {experimental_metrics['engagement_consistency']:.2f}\n" |
|
) |
|
|
|
return analytics_summary, usernames_output, comments_output, likes_chronology_output, str(sum(map(int, likes))) |
|
|
|
except Exception as e: |
|
logger.error(f"Error in analyze_post: {e}", exc_info=True) |
|
return str(e), "", "", "", "0" |
|
|
|
|
|
iface = gr.Interface( |
|
fn=analyze_post, |
|
inputs=[ |
|
gr.Radio(choices=["Photo", "Video"], label="Content Type", value="Photo"), |
|
gr.Textbox(label="Link to Post"), |
|
gr.Number(label="Likes", value=0), |
|
gr.Textbox(label="Post Date"), |
|
gr.Textbox(label="Description", lines=3), |
|
gr.Number(label="Total Comment Count", value=0), |
|
gr.Textbox(label="All Comments", lines=10) |
|
], |
|
outputs=[ |
|
gr.Textbox(label="Analytics Summary", lines=20), |
|
gr.Textbox(label="Usernames"), |
|
gr.Textbox(label="Comments"), |
|
gr.Textbox(label="Likes Chronology"), |
|
gr.Textbox(label="Total Likes on Comments") |
|
], |
|
title="Enhanced Instagram Comment Analyzer", |
|
description="Анализатор комментариев Instagram с расширенной аналитикой и CSV-форматированием" |
|
) |
|
|
|
if __name__ == "__main__": |
|
iface.launch() |