File size: 10,074 Bytes
2c5fbe9
60e12de
 
 
 
 
 
 
be283ee
60e12de
 
 
 
 
 
be283ee
60e12de
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2c5fbe9
 
 
 
 
 
 
 
 
 
 
 
 
 
60e12de
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2c5fbe9
 
 
60e12de
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2c5fbe9
60e12de
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2c5fbe9
 
 
60e12de
 
2c5fbe9
 
 
 
60e12de
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
from transformers import pipeline
from dataclasses import dataclass, field
from typing import List, Optional, Dict, Any
import re
from datetime import datetime
import logging
import html
from uuid import uuid4

# Настройка логирования
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

@dataclass
class Comment:
    """
    Представляет комментарий Instagram со всеми метаданными и вложенной структурой.
    Attributes:
        id: Уникальный идентификатор комментария
        username: Имя пользователя
        time: Временная метка
        content: Текст комментария
        likes: Количество лайков
        level: Уровень вложенности
        parent_id: ID родительского комментария
        replies: Список ответов
        is_verified: Верифицированный аккаунт
        mentions: Упоминания пользователей
        hashtags: Хэштеги
        is_deleted: Флаг удаленного комментария
    """
    id: str = field(default_factory=lambda: str(uuid4()))
    username: str = ""
    time: str = ""
    content: str = ""
    likes: int = 0
    level: int = 0
    parent_id: Optional[str] = None
    replies: List['Comment'] = field(default_factory=list)
    is_verified: bool = False
    mentions: List[str] = field(default_factory=list)
    hashtags: List[str] = field(default_factory=list)
    is_deleted: bool = False

    def __post_init__(self):
        """Валидация после инициализации"""
        if len(self.content) > 2200:
            logger.warning(f"Comment content exceeds 2200 characters for user {self.username}")
            self.content = self.content[:2200] + "..."

class InstagramCommentAnalyzer:
    """
    Основной класс для обработки и анализа комментариев Instagram.
    Обрабатывает парсинг комментариев, вложенную структуру и особые случаи.
    """

    # Регулярное выражение для извлечения комментариев
    COMMENT_PATTERN = r'''
        (?P<username>[\w.-]+)\s+
        (?P<time>\d+\s+нед\.)
        (?P<content>.*?)
        (?:Отметки\s*"Нравится":\s*(?P<likes>\d+))?
        (?:Ответить)?(?:Показать\sперевод)?(?:Нравится)?
    '''

    def __init__(self, max_depth: int = 10, max_comment_length: int = 2200):
        """
        Инициализация анализатора с настраиваемыми параметрами.
        Args:
            max_depth: Максимальная глубина вложенности комментариев
            max_comment_length: Максимальная длина комментария
        """
        self.max_depth = max_depth
        self.max_comment_length = max_comment_length
        self.pattern = re.compile(self.COMMENT_PATTERN, re.VERBOSE | re.DOTALL)
        self.comments: List[Comment] = []
        self.stats: Dict[str, int] = {
            'total_comments': 0,
            'deleted_comments': 0,
            'empty_comments': 0,
            'max_depth_reached': 0,
            'truncated_comments': 0,
            'processed_mentions': 0,
            'processed_hashtags': 0
        }

        # Инициализация модели Hugging Face для анализа настроений
        self.sentiment_analyzer = pipeline("sentiment-analysis")

    def analyze_sentiment(self, text: str) -> str:
        """
        Анализ настроений в комментарии с использованием модели Hugging Face.
        Args:
            text: Текст комментария
        Returns:
            Строка с меткой настроения ('POSITIVE' или 'NEGATIVE')
        """
        result = self.sentiment_analyzer(text)
        return result[0]['label']

    def normalize_text(self, text: str) -> str:
        """
        Нормализация входного текста.
        Args:
            text: Исходный текст
        Returns:
            Нормализованный текст
        """
        # Декодирование HTML-сущностей
        text = html.unescape(text)
        # Нормализация пробелов
        text = ' '.join(text.split())
        # Удаление невидимых символов
        text = re.sub(r'[\u200b\ufeff\u200c]', '', text)
        return text

    def extract_metadata(self, comment: Comment) -> None:
        """
        Извлечение метаданных из комментария.
        Args:
            comment: Объект комментария
        """
        # Извлечение @упоминаний
        comment.mentions = re.findall(r'@(\w+)', comment.content)
        self.stats['processed_mentions'] += len(comment.mentions)

        # Извлечение #хэштегов
        comment.hashtags = re.findall(r'#(\w+)', comment.content)
        self.stats['processed_hashtags'] += len(comment.hashtags)

        # Проверка верификации
        comment.is_verified = bool(re.search(r'✓|Подтвержденный', comment.username))

    def process_comment(self, text: str, parent_id: Optional[str] = None, level: int = 0) -> Optional[Comment]:
        """
        Обработка отдельного комментария.
        Args:
            text: Текст комментария
            parent_id: ID родительского комментария
            level: Уровень вложенности
        Returns:
            Обработанный объект Comment или None
        """
        if level > self.max_depth:
            logger.warning(f"Maximum depth {self.max_depth} exceeded")
            self.stats['max_depth_reached'] += 1
            return None

        if not text.strip():
            self.stats['empty_comments'] += 1
            return None

        try:
            match = self.pattern.match(text)
            if not match:
                raise ValueError(f"Could not parse comment: {text[:100]}...")

            data = match.groupdict()
            comment = Comment(
                username=data['username'],
                time=data['time'],
                content=data['content'].strip(),
                likes=int(data['likes'] or 0),
                level=level,
                parent_id=parent_id
            )

            if len(comment.content) > self.max_comment_length:
                self.stats['truncated_comments'] += 1
                comment.content = comment.content[:self.max_comment_length] + "..."

            # Добавление анализа настроений
            comment.sentiment = self.analyze_sentiment(comment.content)

            self.extract_metadata(comment)
            self.stats['total_comments'] += 1
            return comment

        except Exception as e:
            logger.error(f"Error processing comment: {str(e)}")
            comment = Comment(
                username="[damaged]",
                time="",
                content="[Поврежденные данные]",
                is_deleted=True
            )
            self.stats['deleted_comments'] += 1
            return comment

    def format_comment(self, comment: Comment, index: int) -> str:
        """
        Форматирование комментария для вывода.
        Args:
            comment: Объект комментария
            index: Номер комментария
        Returns:
            Отформатированная строка комментария
        """
        if comment.is_deleted:
            return f'{index}. "[УДАЛЕНО]" "" "" "Нравится 0"'

        return (
            f'{index}. "{comment.username}" "{comment.time}" '
            f'"{comment.content}" "Нравится {comment.likes}" "Настроение {comment.sentiment}"'
        )

    def process_comments(self, text: str) -> List[str]:
        """
        Обработка всех комментариев в тексте.
        Args:
            text: Исходный текст с комментариями
        Returns:
            Список отформатированных комментариев
        """
        # Сброс статистики
        self.stats = {key: 0 for key in self.stats}

        # Нормализация текста
        text = self.normalize_text(text)

        # Разделение на отдельные комментарии
        raw_comments = text.split('ОтветитьНравится')

        # Обработка комментариев
        formatted_comments = []
        for i, raw_comment in enumerate(raw_comments, 1):
            if not raw_comment.strip():
                continue

            comment = self.process_comment(raw_comment)
            if comment:
                formatted_comments.append(self.format_comment(comment, i))

        return formatted_comments

def main():
    """
    Пример использования анализатора.
    """
    # Пример входного текста
    example_text = """
    user1 2 нед. This is a positive comment! Отметки "Нравится": 25
    user2 3 нед. This is a negative comment! Отметки "Нравится": 5
    """

    analyzer = InstagramCommentAnalyzer()
    formatted_comments = analyzer.process_comments(example_text)
    for formatted_comment in formatted_comments:
        print(formatted_comment)

if __name__ == "__main__":
    main()