import mysql.connector import requests import json import os import time import random from datetime import datetime from mysql.connector import errorcode from dotenv import load_dotenv from ensure_db import ensure_database_structure # Загрузка переменных окружения из .env файла load_dotenv() # Получение конфигурации из переменных окружения VK_GROUP_ID = int(os.getenv('VK_GROUP_ID', -3342146)) VK_ACCESS_TOKEN = os.getenv('VK_ACCESS_TOKEN', '') VK_API_VERSION = os.getenv('VK_API_VERSION', '5.131') IS_EVENT = os.getenv('IS_EVENT', 'false').lower() in ('true', '1', 'yes', 'on') AUTO_PUBLISH = os.getenv('AUTO_PUBLISH', 'false').lower() in ('true', '1', 'yes', 'on') VK_NO_REPOST_TAG = os.getenv('VK_NO_REPOST_TAG', '') # Тег для отключения автопубликации LOG_FILE = os.getenv('LOG_FILE', 'vk_loader.log') # Путь к лог-файлу LOG_PREFIX = "VK_loader" # Уникальный префикс для идентификации скрипта # Конфигурация MariaDB из .env DB_CONFIG = { 'host': os.getenv('MDB_HOST', 'localhost'), 'user': os.getenv('MDB_USER', 'root'), 'password': os.getenv('MDB_PW', ''), 'database': os.getenv('MDBASE', 'vk_posts') } def log_message(message, max_retries=5, retry_delay=0.1): """ Записывает сообщение в лог-файл с обработкой блокировок и идентификатором скрипта """ if not LOG_FILE: return timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') # Добавляем идентификатор скрипта к сообщению log_line = f"[{timestamp}] [{LOG_PREFIX}] {message}\n" for attempt in range(max_retries): try: with open(LOG_FILE, 'a', encoding='utf-8') as log: log.write(log_line) return True except (IOError, OSError) as e: if "locked" in str(e).lower() and attempt < max_retries - 1: # Случайная задержка для уменьшения коллизий sleep_time = retry_delay * (1 + random.random() * 0.5) time.sleep(sleep_time) else: # Если не удалось записать после всех попыток print(f"Ошибка записи в лог: {e}") print(f"Сообщение для лога: {log_line.strip()}") return False def get_vk_posts(): """Получает последние 10 постов из VK группы""" url = 'https://api.vk.ru/method/wall.get' params = { 'owner_id': VK_GROUP_ID, 'count': 10, 'access_token': VK_ACCESS_TOKEN, 'v': VK_API_VERSION, 'extended': 1 } try: response = requests.get(url, params=params, timeout=10) response.raise_for_status() data = response.json() if 'error' in data: error_msg = f"VK API Error: {data['error']['error_msg']}" log_message(error_msg) raise Exception(error_msg) return data['response']['items'] except requests.exceptions.RequestException as e: error_msg = f"Ошибка подключения к VK API: {e}" log_message(error_msg) raise Exception(error_msg) def process_post(post): """Извлекает необходимые данные из поста VK""" post_id = post['id'] vk_post_url = f"https://vk.ru/wall{VK_GROUP_ID}_{post_id}" published_at = datetime.utcfromtimestamp(post['date']).strftime('%Y-%m-%d %H:%M:%S') # Инициализация полей опроса is_poll = 0 poll_question = None poll_options = None poll_multiple = False poll_end_date = None # Инициализация поля изображения image_url = None # Обработка вложений for attachment in post.get('attachments', []): if attachment['type'] == 'poll': is_poll = 1 poll = attachment['poll'] poll_question = poll['question'] poll_options = json.dumps([answer['text'] for answer in poll['answers']]) poll_multiple = bool(poll.get('multiple', False)) if poll.get('end_date', 0) > 0: poll_end_date = datetime.utcfromtimestamp(poll['end_date']).strftime('%Y-%m-%d %H:%M:%S') elif attachment['type'] == 'photo' and not image_url: # Берем только первое изображение photo = attachment['photo'] sizes = photo.get('sizes', []) if sizes: # Выбираем изображение максимального качества max_size = max(sizes, key=lambda s: s.get('width', 0) * s.get('height', 0)) image_url = max_size['url'] return { 'vk_post_id': post_id, 'text': post['text'], 'image_url': image_url, 'vk_post_url': vk_post_url, 'published_at': published_at, 'is_poll': is_poll, 'poll_question': poll_question, 'poll_options': poll_options, 'poll_multiple': poll_multiple, 'poll_end_date': poll_end_date } def save_to_database(posts): """Сохраняет новые посты в MariaDB с учетом настроек публикации""" try: conn = mysql.connector.connect(**DB_CONFIG) cursor = conn.cursor() new_posts_count = 0 for post in posts: try: # Проверка наличия тега VK_NO_REPOST_TAG в тексте поста post_text = post.get('text', '') should_auto_publish = AUTO_PUBLISH if VK_NO_REPOST_TAG and VK_NO_REPOST_TAG in post_text: # Если тег найден, отключаем автопубликацию независимо от AUTO_PUBLISH should_auto_publish = False # Добавляем поля is_event, marked_for_publication, shortname и action_number cursor.execute(''' INSERT INTO posts ( vk_post_id, text, image_url, vk_post_url, published_at, is_poll, poll_question, poll_options, poll_multiple, poll_end_date, is_event, marked_for_publication, shortname, action_number ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) ON DUPLICATE KEY UPDATE vk_post_id = VALUES(vk_post_id) ''', ( post['vk_post_id'], post['text'], post['image_url'], post['vk_post_url'], post['published_at'], post['is_poll'], post['poll_question'], post['poll_options'], int(post['poll_multiple']), post['poll_end_date'], IS_EVENT, int(should_auto_publish), # Значение с учетом проверки тега None, # shortname - пока не используется, устанавливаем NULL None # action_number - пока не используется, устанавливаем NULL )) if cursor.rowcount > 0: new_posts_count += 1 # Логируем добавление нового поста с указанием причины отключения автопубликации auto_publish_reason = 'Да' if not should_auto_publish: if VK_NO_REPOST_TAG and VK_NO_REPOST_TAG in post_text: auto_publish_reason = 'Нет (найден тег ' + VK_NO_REPOST_TAG + ')' else: auto_publish_reason = 'Нет' log_message(f"Добавлен новый пост: ID {post['vk_post_id']}, Дата: {post['published_at']}, Автопубликация: {auto_publish_reason}") except mysql.connector.Error as err: error_msg = f"Ошибка при вставке поста {post['vk_post_id']}: {err}" log_message(error_msg) continue conn.commit() return new_posts_count except mysql.connector.Error as err: error_msg = f"Ошибка подключения к базе данных: {err}" log_message(error_msg) return 0 finally: if 'conn' in locals() and conn.is_connected(): cursor.close() conn.close() def vk_load_10(): """ Экспортируемая функция для загрузки 10 последних постов из VK Возвращает количество добавленных постов """ # Стартовая информация start_msg = f"Старт скрипта | База: {DB_CONFIG['database']}@{DB_CONFIG['host']} | IS_EVENT: {IS_EVENT} | AUTO_PUBLISH: {AUTO_PUBLISH}" print(start_msg) log_message(start_msg) # Обеспечиваем структуру БД try: ensure_database_structure() except Exception as e: error_msg = f"Не удалось создать структуру базы данных: {e}" print(error_msg) log_message(error_msg) return 0 # Получаем посты из VK try: vk_posts = get_vk_posts() status_msg = f"Получено {len(vk_posts)} постов из VK" print(status_msg) log_message(status_msg) except Exception as e: error_msg = f"Ошибка при получении данных из VK: {e}" print(error_msg) log_message(error_msg) return 0 # Обрабатываем и сохраняем посты processed_posts = [process_post(post) for post in vk_posts] new_count = save_to_database(processed_posts) # Фиксируем результат if new_count > 0: result_msg = f"Добавлено {new_count} новых постов в базу данных" else: result_msg = "Новые посты не обнаружены" print(result_msg) log_message(result_msg) # Завершение работы end_msg = "Работа скрипта завершена" print(end_msg) log_message(end_msg) return new_count def main(): """Основная функция для запуска скрипта из командной строки""" vk_load_10() if __name__ == "__main__": main()