This project explores protest activity in Russia since 2022 using data from VKontakte.
It powered an investigation published in Novaya Gazeta Europe: Picking Your Battles (June 2025)
Journalists analyzed nearly 40,000 protests across Russia over the last three years, based on posts on VKontakte.
The repository contains three main programs:
-
Data Collection
- Downloads posts from VKontakte groups and newsfeed using the platform API.
-
Data Vectorization & Cleaning
- Cleans posts via keyword filtering.
- Converts texts into embeddings (using OpenAI API).
- Clusters posts and removes duplicates or semantically similar entries.
-
Entity Extraction with LLM
- Performs final semantic cleaning.
- Extracts named entities (locations, organizations, protest types) using Gemini API queries.
Before running any programs, you'll need to obtain API tokens:
-
VKontakte Token(s)
- One or more VK access tokens for API requests
- Multiple tokens recommended for multithreading
- Obtain from: VK API Documentation
-
OpenAI Token
- Required for text vectorization (converting posts to embeddings)
- Obtain from: OpenAI API Keys
-
Google Gemini Token(s)
- One or more Gemini API tokens for LLM entity extraction
- Multiple tokens recommended for processing large datasets
- Obtain from: Google AI Studio
This script downloads posts from VKontakte using various scraping methods.
# Make the script executable
chmod +x src/scraping/download_posts_vk.sh
# Run the script
cd src/scraping && ./download_posts_vk.shWhat it does:
- Prompts you to enter VK API tokens (press Enter after each token, empty input to finish)
- Runs multiple Python scrapers:
vk_search_cities.py- searches city identifiers using Russian population datavk_search_groups.py- searches for groups in specific cities by keywordsvk_scraper_posts_groups.py- scrapes posts from target groupsvk_scraper_posts_newsfeed.py- scrapes from news feedsclean_posts_by_keywords.py- cleans posts by keywordsvk_scraper_groups_info.py- collects group metadatavk_scraper_users_info.py- collects user informationmerge_groups_users_info.py- merges collected metadata
Input: VK API tokens (entered interactively), Russian population data
Output: Raw post data in JSONL/CSV format
This script processes the collected data by vectorizing texts and removing duplicates.
# Make the script executable
chmod +x src/preprocessing/drop_duplicates.sh
# Run the script
cd src/preprocessing && ./drop_duplicates.shWhat it does:
- Prompts for OpenAI API token
- Converts posts to embeddings using
posts_to_embeddings.py - Clusters similar posts within 10 day periods
cluster_embeddings_by_time.py - Removes duplicate/similar content using
remove_similar_posts.py
Input: OpenAI API token, raw post data
Output: Deduplicated dataset
This script uses Gemini LLM to extract entities and perform final semantic analysis.
# Make the script executable
chmod +x src/llm/process_posts_ai.sh
# Run the script
cd src/llm && ./process_posts_ai.shWhat it does:
- Prompts for Gemini API token(s)
- Uses
query_gemini.pyto perform final semantic cleaning of the dataset - Uses
query_gemini.pyto extract:- Geographic locations
- Organization names
- Protest types and categories
- Other named entities
Input: Gemini API token(s), processed post data
Output: Final dataset with extracted entities
- Clone the repository
- Obtain all required API tokens (see Prerequisites section)
- Run the scripts in order: collection → processing → LLM analysis
- VK Tokens: Use multiple tokens to avoid rate limits. The scripts will rotate through available tokens automatically.
- Gemini Tokens: Multiple tokens significantly speed up large-scale entity extraction due to parallel processing.
- OpenAI Token: One token is typically sufficient for embeddings generation, but ensure adequate quota for your dataset size.