Files
Zilant2025/vk_load.py
T
gitadmin 825428bca3
ci/woodpecker/push/woodpecker Pipeline failed
[ВОЛК] starting prod...
2025-12-26 00:28:13 +03:00

300 lines
13 KiB
Python

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': 20,
'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_repost = 'copy_history' in post and len(post.get('copy_history', [])) > 0
# Если это репост, используем данные из оригинального поста
if is_repost:
original_post = post['copy_history'][0] # Берем первый элемент (обычно он один)
# Используем данные из оригинального поста
source_post = original_post
# Сохраняем информацию о том, что это репост, в тексте
repost_prefix = f"[Репост] "
original_owner_id = original_post.get('owner_id', 0)
original_post_id = original_post.get('id', 0)
original_url = f"https://vk.ru/wall{original_owner_id}_{original_post_id}"
repost_info = f"\n\n[Оригинальный пост: {original_url}]"
else:
source_post = post
repost_prefix = ""
repost_info = ""
# Инициализация полей опроса
is_poll = 0
poll_question = None
poll_options = None
poll_multiple = False
poll_end_date = None
# Инициализация поля изображения
image_url = None
# Обработка вложений из оригинального поста (если это репост) или текущего поста
for attachment in source_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']
# Формируем текст поста: если это репост, добавляем префикс и информацию об оригинале
post_text = source_post.get('text', '')
if is_repost:
# Если у репоста есть свой текст, добавляем его перед текстом оригинала
if post.get('text', '').strip():
post_text = f"{post['text']}\n\n---\n\n{post_text}"
post_text = repost_prefix + post_text + repost_info
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,
'is_repost': is_repost
}
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 = 'Нет'
# Добавляем информацию о репосте в лог
repost_info = ""
if post.get('is_repost', False):
repost_info = ", Репост: Да"
else:
repost_info = ", Репост: Нет"
log_message(f"Добавлен новый пост: ID {post['vk_post_id']}, Дата: {post['published_at']}, Автопубликация: {auto_publish_reason}{repost_info}")
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()