Анализ целевой аудитории сообщества VK: различия между версиями

Материал из Поле цифровой дидактики
Нет описания правки
Нет описания правки
Строка 68: Строка 68:
TOKEN = "Ваш токен"
TOKEN = "Ваш токен"


# Собирает данные о сообществе
def get_community_data(group_id):
def get_community_data(group_id):
    """Собирает данные о сообществе"""
     try:
     try:
         vk_session = vk_api.VkApi(token=TOKEN)
         vk_session = vk_api.VkApi(token=TOKEN)
Строка 175: Строка 175:
         return {'success': False, 'error': str(e)}
         return {'success': False, 'error': str(e)}


# Генерирует графики
def generate_charts(members_data, posts_data):
def generate_charts(members_data, posts_data):
    """Генерирует графики и возвращает их в base64"""
     charts = {}
     charts = {}
      
      

Версия от 22:09, 26 марта 2026

Параметр Описание
Описание Веб-приложение для анализа аудитории сообществ ВКонтакте. Инструмент собирает данные о подписчиках (пол, возраст, география) и постах (типы контента, вовлечённость), визуализирует статистику в виде графиков и формирует рекомендации по оптимизации контент-стратегии. Проект решает задачу автоматизации SMM-аналитики и помогает администраторам сообществ понимать свою целевую аудиторию.
Область знаний Веб-разработка, анализ данных, работа с API социальных сетей, визуализация данных, SMM-аналитика, Python-программирование.
Близкие понятия SMM-аналитика, парсинг VK API, дашборд для сообществ, анализ целевой аудитории (ЦА), вовлечённость (ER), демографический портрет аудитории, контент-стратегия, репрезентативная выборка, Flask-приложение.
Среда разработки Python 3.8+, Flask, vk_api, matplotlib, pandas

Цель проекта

Разработать веб-приложение для автоматического анализа аудитории сообществ ВКонтакте с целью получения демографической статистики и рекомендаций по оптимизации контент-стратегии. Приложение должно предоставлять наглядную визуализацию данных в виде графиков и формировать практические советы для администраторов сообществ.

Задачи

  1. Интеграция с VK API — реализовать сбор данных о подписчиках и постах сообщества.
  2. Обработка данных — агрегировать информацию о поле, возрасте, географии и вовлечённости.
  3. Визуализация — построить 4 графика:
    • Распределение по полу (круговая диаграмма)
    • Возрастное распределение (гистограмма)
    • Топ-5 городов (горизонтальная столбчатая диаграмма)
    • Вовлечённость по типам контента (столбчатая диаграмма)
  4. Формирование рекомендаций — на основе полученных данных выдать текстовые советы по контент-стратегии.
  5. Создание веб-интерфейса — разработать удобную форму для ввода ссылки на сообщество и отображения результатов.

Диаграмма работы приложения

Структура проекта

Структура проекта в VS Code:

Основной python-скрипт приложения. Обеспечивает интеграцию с VK API, сбор данных о подписчиках и постах, генерацию 4 графиков, формирование рекомендаций и работу веб-сервера на Flask: ``` from flask import Flask, render_template, request, jsonify import vk_api import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt from collections import Counter from datetime import datetime import os import base64 import io

app = Flask(__name__)

  1. Папка для статики

os.makedirs('static', exist_ok=True)

TOKEN = "Ваш токен"

  1. Собирает данные о сообществе

def get_community_data(group_id):

   try:
       vk_session = vk_api.VkApi(token=TOKEN)
       vk = vk_session.get_api()
       
       # Определяем ID сообщества
       if str(group_id).lstrip('-').isdigit():
           owner_id = int(group_id)
           group_id_for_api = owner_id
       else:
           group_id_for_api = group_id
       
       # Получаем информацию о сообществе
       try:
           group = vk.groups.getById(group_id=group_id_for_api, fields='members_count,description')[0]
           community_name = group['name']
           members_count = group['members_count']
       except:
           # Если не получилось, пробуем как числовой ID
           group = vk.groups.getById(group_id=str(group_id_for_api), fields='members_count,description')[0]
           community_name = group['name']
           members_count = group['members_count']
       
       # Собираем подписчиков
       members_data = []
       try:
           members = vk.groups.getMembers(group_id=group_id_for_api, fields='sex,bdate,city', count=500)
           for user in members['items']:
               user_info = {}
               
               # Пол
               if user.get('sex') == 1:
                   user_info['sex'] = 'Женщины'
               elif user.get('sex') == 2:
                   user_info['sex'] = 'Мужчины'
               else:
                   user_info['sex'] = 'Не указан'
               
               # Возраст
               if user.get('bdate'):
                   bdate = user['bdate']
                   if len(bdate.split('.')) == 3:
                       try:
                           year = int(bdate.split('.')[2])
                           age = datetime.now().year - year
                           if 0 < age < 100:
                               user_info['age'] = age
                       except:
                           pass
               
               # Город
               if user.get('city') and user['city'].get('title'):
                   user_info['city'] = user['city']['title']
               
               members_data.append(user_info)
       except Exception as e:
           print(f"Ошибка при сборе подписчиков: {e}")
       
       # Собираем посты
       posts_data = []
       try:
           wall = vk.wall.get(owner_id=owner_id if str(group_id).lstrip('-').isdigit() else group_id, count=20, filter='owner')
           for post in wall['items']:
               post_info = {
                   'text': post.get('text', )[:100],
                   'likes': post['likes']['count'],
                   'comments': post['comments']['count'],
                   'reposts': post['reposts']['count'],
                   'views': post.get('views', {}).get('count', 0)
               }
               
               # Тип контента (без эмодзи, чтобы не было проблем с шрифтами)
               if post.get('attachments'):
                   attach_type = post['attachments'][0]['type']
                   if attach_type == 'photo':
                       post_info['type'] = 'Фото'
                   elif attach_type == 'video':
                       post_info['type'] = 'Видео'
                   elif attach_type == 'link':
                       post_info['type'] = 'Ссылка'
                   else:
                       post_info['type'] = 'Текст'
               else:
                   post_info['type'] = 'Текст'
               
               # ER
               if post_info['views'] > 0:
                   post_info['er'] = (post_info['likes'] + post_info['comments'] + post_info['reposts']) / post_info['views'] * 1000
               else:
                   post_info['er'] = 0
               
               posts_data.append(post_info)
       except Exception as e:
           print(f"Ошибка при сборе постов: {e}")
       
       return {
           'success': True,
           'community_name': community_name,
           'members_count': members_count,
           'members': members_data,
           'posts': posts_data
       }
       
   except Exception as e:
       return {'success': False, 'error': str(e)}
  1. Генерирует графики

def generate_charts(members_data, posts_data):

   charts = {}
   
   # Настройка шрифтов для русских букв
   plt.rcParams['font.family'] = 'sans-serif'
   plt.rcParams['font.sans-serif'] = ['Arial', 'DejaVu Sans']
   
   # 1. Распределение по полу
   if members_data:
       fig, ax = plt.subplots(figsize=(6, 4))
       sex_counts = Counter([m.get('sex', 'Не указан') for m in members_data])
       ax.pie(sex_counts.values(), labels=sex_counts.keys(), autopct='%1.1f%%')
       
       buf = io.BytesIO()
       plt.savefig(buf, format='png', bbox_inches='tight', dpi=100)
       buf.seek(0)
       charts['sex'] = base64.b64encode(buf.getvalue()).decode()
       plt.close(fig)
   
   # 2. Возрастное распределение
   ages = [m['age'] for m in members_data if 'age' in m]
   if ages:
       fig, ax = plt.subplots(figsize=(6, 4))
       ax.hist(ages, bins=15, color='skyblue', edgecolor='black')
       ax.set_xlabel('Возраст')
       ax.set_ylabel('Количество')
       
       buf = io.BytesIO()
       plt.savefig(buf, format='png', bbox_inches='tight', dpi=100)
       buf.seek(0)
       charts['age'] = base64.b64encode(buf.getvalue()).decode()
       plt.close(fig)
   
   # 3. Топ-5 городов
   cities = [m['city'] for m in members_data if 'city' in m and m['city'] != 'Не указан']
   if cities:
       fig, ax = plt.subplots(figsize=(8, 5))
       city_counts = Counter(cities).most_common(5)
       city_names, city_values = zip(*city_counts)
   
       # Создаём горизонтальную столбчатую диаграмму
       bars = ax.barh(city_names, city_values, color='lightcoral', edgecolor='darkred')
       ax.set_title('Топ-5 городов аудитории', fontsize=14, fontweight='bold')
       ax.set_xlabel('Количество подписчиков', fontsize=11)
       ax.set_ylabel('Город', fontsize=11)
       
       plt.tight_layout()
   
       buf = io.BytesIO()
       plt.savefig(buf, format='png', bbox_inches='tight', dpi=100)
       buf.seek(0)
       charts['cities'] = base64.b64encode(buf.getvalue()).decode()
       plt.close(fig)
   
   # 4. Вовлечённость по типам контента
   if posts_data:
       fig, ax = plt.subplots(figsize=(8, 5))
       type_er = {}
       for post in posts_data:
           post_type = post['type']
           if post_type not in type_er:
               type_er[post_type] = []
           type_er[post_type].append(post['er'])
       
       avg_er = {t: sum(ers)/len(ers) for t, ers in type_er.items()}
       bars = ax.bar(avg_er.keys(), avg_er.values(), color='lightgreen', edgecolor='darkgreen')
       ax.set_ylabel('ER (на 1000 просмотров)', fontsize=11)
       plt.xticks(rotation=45)
       
       # Добавляем значения на столбцы
       for bar, val in zip(bars, avg_er.values()):
           ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.5, 
                  f'{val:.1f}', ha='center', va='bottom', fontsize=9)
       
       plt.tight_layout()
       
       buf = io.BytesIO()
       plt.savefig(buf, format='png', bbox_inches='tight', dpi=100)
       buf.seek(0)
       charts['engagement'] = base64.b64encode(buf.getvalue()).decode()
       plt.close(fig)
   
   return charts

@app.route('/') def index():

   return render_template('index.html')

@app.route('/analyze', methods=['POST']) def analyze():

   group_url = request.json.get('group_url', ).strip()
   
   if not group_url:
       return jsonify({'success': False, 'error': 'Введите ссылку на сообщество'})
   
   # Извлекаем ID сообщества из ссылки
   if 'vk.com/' in group_url:
       group_id = group_url.split('vk.com/')[-1].split('/')[0]
   else:
       group_id = group_url
   
   # Собираем данные
   data = get_community_data(group_id)
   
   if not data['success']:
       return jsonify({'success': False, 'error': data.get('error', 'Ошибка при получении данных')})
   
   # Генерируем графики
   charts = generate_charts(data['members'], data['posts'])
   
   # Считаем статистику
   stats = {}
   
   # Пол
   if data['members']:
       sex_counts = Counter([m.get('sex', 'Не указан') for m in data['members']])
       stats['sex'] = dict(sex_counts)
   
   # Возраст
   ages = [m['age'] for m in data['members'] if 'age' in m]
   if ages:
       stats['avg_age'] = sum(ages) / len(ages)
       stats['min_age'] = min(ages)
       stats['max_age'] = max(ages)
   
   # Города
   cities = [m['city'] for m in data['members'] if 'city' in m and m['city'] != 'Не указан']
   if cities:
       stats['top_cities'] = Counter(cities).most_common(5)
   
   # Посты
   if data['posts']:
       avg_er = sum(p['er'] for p in data['posts']) / len(data['posts'])
       stats['avg_er'] = avg_er
       # Лучший тип контента
       type_er = {}
       for post in data['posts']:
           if post['type'] not in type_er:
               type_er[post['type']] = []
           type_er[post['type']].append(post['er'])
       best_type = max(type_er.keys(), key=lambda t: sum(type_er[t])/len(type_er[t]))
       stats['best_post_type'] = best_type
   
   # Рекомендации
   recommendations = []
   if 'avg_age' in stats:
       if stats['avg_age'] < 20:
           recommendations.append("🎯 Аудитория молодая (до 20 лет) → используйте короткие, трендовые форматы, мемы, TikTok-подход")
       elif stats['avg_age'] < 30:
           recommendations.append("🎯 Аудитория 20-30 лет → чередуйте развлекательный и полезный контент, используйте качественные фото")
       else:
           recommendations.append("🎯 Аудитория 30+ лет → делайте акцент на полезный, экспертный контент, длинные посты")
   
   if stats.get('best_post_type'):
       recommendations.append(f"📈 Лучший формат контента: {stats['best_post_type']} → публикуйте чаще именно его")
   
   if data['posts'] and stats.get('avg_er'):
       if stats['avg_er'] < 50:
           recommendations.append("⚠️ Вовлечённость ниже среднего → попробуйте добавить опросы, конкурсы и интерактив")
       elif stats['avg_er'] > 150:
           recommendations.append("🔥 Отличная вовлечённость! Продолжайте в том же духе")
   
   # Добавляем рекомендацию по городам
   if stats.get('top_cities'):
       main_city = stats['top_cities'][0][0]
       main_city_count = stats['top_cities'][0][1]
       total_with_city = sum(count for _, count in stats['top_cities'])
       if len(stats['top_cities']) > 1:
           recommendations.append(f"🏙️ Топ-5 городов: {', '.join([f'{city} ({cnt})' for city, cnt in stats['top_cities'][:3]])}")
       recommendations.append(f"📍 Основная аудитория из {main_city} → можно проводить локальные мероприятия или делать привязку к городу")
   
   return jsonify({
       'success': True,
       'community_name': data['community_name'],
       'members_count': data['members_count'],
       'stats': stats,
       'charts': charts,
       'recommendations': recommendations
   })

if __name__ == '__main__':

   app.run(debug=True)

```

После запуска приложение доступно по адресу: http://127.0.0.1:5000

Выводы

  1. Разработано полноценное веб-приложение для анализа сообществ ВКонтакте, работающее на localhost.
  2. Реализована интеграция с VK API — приложение успешно получает данные о подписчиках (пол, возраст, город) и постах (типы контента, вовлечённость).
  3. Создана система визуализации — 4 информативных графика, отображающих ключевые метрики аудитории:
    • Распределение по полу (круговая диаграмма)
    • Возрастное распределение (гистограмма)
    • Топ-5 городов (горизонтальная столбчатая диаграмма)
    • Вовлечённость по типам контента (столбчатая диаграмма)
  4. Автоматическое формирование рекомендаций — на основе анализа данных приложение выдаёт практические советы по оптимизации контент-стратегии.
  5. Удобный пользовательский интерфейс — адаптивный дизайн, анимации загрузки, понятная навигация.