Local-first pipeline for collecting posts, replies, reposts, and discussion threads across social platforms, then analyzing narratives, stance, support, conflict, and propagation.
The project stores all outputs locally and is built around one configured source per run.
Support is intentionally tiered. The project does not claim equal coverage across every network.
facebook: supported. Meta API, public web, authenticated browser web, propagation via visible shares and share comments on a best-effort basis. Facebook public web now also preserves localized visible comment counters from detail pages even when extracted comment snapshots are shallower than the visible UI count, and strips more localized UI/control lines out of extracted comment snapshots.telegram: supported. MTProto, public web, Bot API update queue, linked discussion trees, visible forwards on a best-effort basis. Telegram web now preserves visible discussion counters from the post surface even before a separate public discussion feed is attached. Telegram MTProto now reorders nested discussion messages by parent chain and grows its fallback scan using the visible thread size when that signal is available.x: supported. Official X API v2, public web, quotes and reposts as first-class propagation objects. X web now filters embedded quoted/origin status cards out of detail-page reply extraction.threads: beta. Threads API for owned-account scenarios is wired in, but public web coverage is still unstable and can yield zero posts for a valid public profile depending on the current Threads UI.instagram: beta. Instagram Graph API is wired in for owned professional accounts; public web collection currently behaves mostly as post-level extraction with shallow or empty public comments.
- collects source posts or messages for one configured account, page, or channel
- collects comments, replies, and discussion trees where the platform exposes them
- preserves visible discussion/comment counters where the platform exposes them even if full comment text extraction is partial
- detects visible propagation instances such as shares, forwards, quotes, and repost-like copies
- stores raw snapshots under
data/raw/<run_id>/ - normalizes data into parquet files and DuckDB tables
- detects language for
ru,uk, anden - clusters posts, propagations, and comments into narrative groups
- labels stance toward configured sides or actors
- computes support metrics for:
- direct origin-post comments
- propagation instances
- aggregated
origin_plus_propagations
- exports review files for manual corrections
- renders Markdown, HTML, CSV, and XLSX reports
config/project.yaml
src/social_posts_analysis/
analysis/
collectors/
reporting/
tests/
- Python 3.12+
- recommended:
uv - Playwright plus Chromium for web collectors
- Meta token for Facebook API collection
- Telegram API credentials and an authorized session file for MTProto
- Telegram bot token for Bot API mode
- X bearer token for X API mode
- Threads access token for Threads API mode
- Instagram access token plus an owned professional account for Instagram Graph API mode
Using uv:
uv venv
uv sync --extra devUsing pip:
python -m venv .venvActivate the environment:
- Windows PowerShell:
.venv\Scripts\Activate.ps1 - macOS/Linux:
source .venv/bin/activate
Then install the package:
python -m pip install -e .[dev]If you plan to use web collectors:
python -m playwright install chromiumThe checked-in [project.yaml](C:\Coding projects\facebook_posts_analysis\config\project.yaml) is a safe public template. For real runs, create a private local file such as config/project.local.yaml and pass it explicitly with --config.
Path resolution for paths.* is intentionally simple:
- when the config file lives under a
config/directory, relative output paths are resolved from the project root - when the config file lives somewhere else, relative output paths are resolved from that config file directory
- if you generate temporary configs outside the repository, prefer absolute
paths.raw_dir,paths.processed_dir,paths.review_dir,paths.reports_dir, andpaths.database_path
The CLI now emits an explicit warning for the second case so that temp-config runs do not silently write outputs into an unexpected directory.
Important top-level settings:
source.platform:facebook,telegram,x,threads, orinstagramsource.url,source.source_id, orsource.source_namesource.telegram.discussion_chat_iddate_range.startanddate_range.endcollector.mode:api,web,hybrid,mtproto,bot_api,x_api,threads_api, orinstagram_graph_apicollector.multi_pass_runscollector.wait_between_passes_secondsnormalization.merge_recent_runsnormalization.source_run_idssidesproviders.embeddingsproviders.llm
Environment variables supported by default:
META_ACCESS_TOKENSOCIAL_BROWSER_USER_DATA_DIRSOCIAL_BROWSER_PROFILE_DIRECTORYTELEGRAM_SESSION_FILETELEGRAM_API_IDTELEGRAM_API_HASHTELEGRAM_BOT_TOKENX_BEARER_TOKENTHREADS_ACCESS_TOKENINSTAGRAM_ACCESS_TOKENEMBEDDING_BASE_URLEMBEDDING_API_KEYLLM_BASE_URLLLM_API_KEY
The codebase used to be Facebook-only.
Existing configs must be migrated from:
page.urlpage.page_idpage.page_name
to:
source.platform: facebooksource.urlsource.source_idsource.source_name
Package and CLI names were also changed:
- old package:
facebook_posts_analysis - new package:
social_posts_analysis - old CLI:
facebook-posts-analysis - new CLI:
social-posts-analysis
No backward-compatible alias is kept for the old package or CLI names.
- Copy the public template:
cp config/project.yaml config/project.local.yamlWindows PowerShell:
Copy-Item config/project.yaml config/project.local.yaml- Edit
config/project.local.yaml. - Run one of the modes below with
--config config/project.local.yaml.
source:
platform: "facebook"
url: "https://www.facebook.com/example-public-page/"
collector:
mode: "web"
meta_api:
enabled: false
public_web:
enabled: true
authenticated_browser:
enabled: falseRun:
social-posts-analysis run-many --config config/project.local.yaml --passes 3source:
platform: "facebook"
url: "https://www.facebook.com/example-public-page/"
collector:
mode: "web"
public_web:
enabled: true
authenticated_browser:
enabled: true
browser: "chrome"
profile_directory: "Default"
copy_profile: trueOptional environment variables:
$env:SOCIAL_BROWSER_USER_DATA_DIR="C:\Users\<user>\AppData\Local\Google\Chrome\User Data"
$env:SOCIAL_BROWSER_PROFILE_DIRECTORY="Default"If authenticated browser mode is enabled but Facebook still returns a login wall on detail pages, the collector now emits an explicit warning. In practice that means the selected Chrome/Edge profile launched successfully, but it does not appear to be logged in to Facebook for the target surface.
source:
platform: "facebook"
source_id: "123456789"
collector:
mode: "api"
meta_api:
enabled: true$env:META_ACCESS_TOKEN="your-token"
social-posts-analysis run-all --config config/project.local.yamlsource:
platform: "telegram"
source_name: "example_channel"
telegram:
discussion_chat_id: "example_discussion"
collector:
mode: "web"
telegram_web:
enabled: true
telegram_mtproto:
enabled: falsesource:
platform: "telegram"
source_name: "example_channel"
collector:
mode: "mtproto"
telegram_mtproto:
enabled: true
session_file: ".sessions/example_channel"
api_id: 123456
api_hash: "your-api-hash"$env:TELEGRAM_SESSION_FILE=".sessions/example_channel"
$env:TELEGRAM_API_ID="123456"
$env:TELEGRAM_API_HASH="your-api-hash"
social-posts-analysis run-all --config config/project.local.yamlsource:
platform: "telegram"
source_name: "example_channel"
telegram:
discussion_chat_id: "-1001234567890"
collector:
mode: "bot_api"
telegram_bot_api:
enabled: true
bot_token: null
consume_updates: false$env:TELEGRAM_BOT_TOKEN="123456:bot-token"
social-posts-analysis run-all --config config/project.local.yamlsource:
platform: "x"
source_name: "example_account"
collector:
mode: "web"
x_web:
enabled: true
authenticated_browser:
enabled: falsesource:
platform: "x"
source_name: "example_account"
collector:
mode: "x_api"
x_api:
enabled: true
bearer_token: null
search_scope: "recent"$env:X_BEARER_TOKEN="your-bearer-token"
social-posts-analysis run-all --config config/project.local.yamlsource:
platform: "threads"
source_name: "example_account"
collector:
mode: "threads_api"
threads_api:
enabled: true
access_token: null$env:THREADS_ACCESS_TOKEN="your-threads-token"
social-posts-analysis run-all --config config/project.local.yamlsource:
platform: "threads"
source_name: "example_account"
collector:
mode: "web"
threads_web:
enabled: trueExpected public-web shape today:
- posts: best-effort, can be
0for a valid public profile - comments/replies: often
0 - propagations: only visible quote/repost surfaces
- warning: current public Threads UI can hide feed content from the scraper
source:
platform: "instagram"
source_id: "17841400000000000"
collector:
mode: "instagram_graph_api"
instagram_graph_api:
enabled: true
access_token: null$env:INSTAGRAM_ACCESS_TOKEN="your-instagram-token"
social-posts-analysis run-all --config config/project.local.yamlsource:
platform: "instagram"
source_name: "example_account"
collector:
mode: "web"
instagram_web:
enabled: trueExpected public-web shape today:
- posts: usually available for public profiles
- comments/replies: often shallow or
0 - propagations: only directly observable public surfaces
- warning: public comment visibility depends on the current Instagram web UI
Full pipeline:
social-posts-analysis run-all --config config/project.local.yamlMulti-pass full pipeline:
social-posts-analysis run-many --config config/project.local.yaml --passes 3Step by step:
social-posts-analysis collect --config config/project.local.yaml
social-posts-analysis normalize --config config/project.local.yaml --run-id <run_id>
social-posts-analysis analyze --config config/project.local.yaml --run-id <run_id>
social-posts-analysis review-export --config config/project.local.yaml --run-id <run_id>
social-posts-analysis report --config config/project.local.yaml --run-id <run_id>
social-posts-analysis export-tables --config config/project.local.yaml --run-id <run_id>The package exposes the social-posts-analysis CLI with:
collectnormalizeanalyzereview-exportreportexport-tablesrun-allrun-many
Normalized tables:
posts.parquetpropagations.parquetpropagation_edges.parquetcomments.parquetcomment_edges.parquetauthors.parquetmedia_refs.parquetcollection_runs.parquet
collection_runs.parquet now carries both warning_count and merged warning_messages, so collector warnings from multi-run normalization survive into reporting. For per-run traceability, report export still resolves raw warnings by source_run_id whenever the underlying raw manifests are present.
Analysis tables:
detected_languages.parquetcluster_memberships.parquetnarrative_clusters.parquetstance_labels.parquetsupport_metrics.parquetanalysis_runs.parquet
Review files:
review/narrative_overrides.csvreview/stance_overrides.csv
Reports:
reports/report_<run_id>.mdreports/report_<run_id>.htmlreports/report_<run_id>.xlsxreports/report_<run_id>_tables/*.csv
Important report exports include:
reports/report_<run_id>_tables/source_run_trace.csvreports/report_<run_id>_tables/source_warnings.csvreports/report_<run_id>_tables/coverage_gaps.csvreports/report_<run_id>_tables/propagation_coverage_gaps.csv
source_run_trace.csv includes source_run_id, collector, mode, status, fallback_used, and warning_count, so merged snapshots still show which collector path actually ran for each raw source run.
source_warnings.csv now includes explicit source_run_id, warning_index, and warning columns, so merged snapshots still show which raw run produced each collector warning.
These files or directories should stay local and should not be committed:
config/project.local.yamldata/reports/review/- local browser profile paths
- Meta, Telegram, X, Threads, Instagram, and provider secrets
- virtual environments and cache directories
Run:
uv run ruff check .
uv run mypy src
uv run pytest -qThe current test suite covers:
- Facebook Meta API pagination, nested comments, and visible shares
- Facebook public-web parsing and timestamp handling
- Telegram MTProto source resolution, discussion collection, service-message filtering, and forward detection
- Telegram web and Bot API mappings
- X API replies plus quote/repost propagation
- Threads API and web payload mapping
- Instagram Graph API and web payload mapping
- normalization, propagation tables, and merged snapshots
- analysis helpers and support metrics
- review override application
GitHub Actions runs:
ruff check .mypy srcpytest -q
Validated smoke runs in this repository session on April 11, 2026:
threads_webagainsthttps://www.threads.net/@zuckcompleted successfully but returned0posts,0comments, and0propagations. The run status waspartialand reported the expected best-effort public UI warning.instagram_webagainsthttps://www.instagram.com/zuck/completed successfully and returned12posts,0comments, and0propagations. The run status waspartial; the report also detected3reels.
Validated smoke runs in this repository session on April 13, 2026:
facebook_webagainsthttps://www.facebook.com/VolodymyrBugrov/completed successfully and returned10posts,3extracted comments, and0propagations. The run status waspartial. The report preserved visible comment counters, including a reel withvisible=99andextracted=1, and repeated the explicit authenticated-browser login-wall warning.telegram_webagainsthttps://t.me/s/durovcompleted successfully and returned11posts,0comments, and0propagations. The run status waspartial. No linked discussion chat was visible, so the collector correctly stayed posts-only.x_webagainsthttps://x.com/OpenAIcompleted successfully and returned2posts,0comments, and0propagations. The run status waspartial. The report preserved visible reply counters (386and762) and emitted explicit warnings that the public detail pages exposed no reply articles.
Not live-validated in this repository session:
threads_apiinstagram_graph_api
Reason:
- the required access tokens were not configured in the local environment during this validation pass
Propagation coverage is asymmetric by design.
- strongest in this repository today:
facebook,telegram,x - beta or partial today:
threads,instagram
Platform-specific limits:
- Facebook public-web collection is best-effort. The DOM can expose different content across runs.
- Facebook public-web comment counters are now parsed from English, Ukrainian, and Russian surface text, and localized reply/control lines are filtered more aggressively out of extracted comment snapshots, but the DOM can still hide the actual comment list.
- Facebook authenticated browser mode still only sees what the logged-in account can see.
- Facebook propagation coverage is limited to shares and visible reshared surfaces the collectors can actually discover.
facebook_webnow filters localized UI/control lines such asВідповісти,1 відповідь,Ответить, andОтветыboth in Python comment cleanup and in the earlier DOM author-selection heuristic. It also preserves morepublished_hintvalues directly from visible comment blocks. This improves comment snapshot quality when Facebook actually exposes comment blocks, but it does not remove the April 13, 2026 heavy-reel login-wall limitation.- The Facebook timestamp parser also recognizes localized “yesterday” hints such as
ВчораandВчера в 14:03, so these lines are treated as timestamps during cleanup instead of leaking into comment body text. facebook_webcomment extraction now keeps bothraw_textand a cleanertextcandidate per comment block. Normalization usesraw_textfor author/timestamp fallback andtextfor the final message body, which reduces DOM noise without throwing away recovery signals.- Telegram MTProto and web collection support one source channel per run, plus its linked discussion when visible.
- Telegram web collection only works for public
t.me/s/...feeds. - Telegram propagation coverage is limited to visible forwards or quoted surfaces available to the current collector.
- Telegram MTProto now orders nested replies more defensively and scales fallback scan size from the visible thread size, but the scan is still bounded and can miss very large discussion threads.
- Telegram Bot API only sees updates currently available to the bot. It does not backfill history.
- X API reply coverage depends on the current search access window. With
search_scope: recent, older replies can be missing. - X web collection can scrape public profile posts, but public reply visibility is often shallower than the reply counter suggests unless an authenticated browser session is used. In the April 13, 2026 smoke run against
https://x.com/OpenAI, visible counters (386,762) were preserved while reply article extraction stayed empty. - Threads API works best for owned-account scenarios. Threads web coverage is best-effort and, in the April 11, 2026 public smoke run against
@zuck, it returned zero visible posts. - Instagram Graph API works for owned professional-account scenarios. Instagram web coverage is best-effort and public comments are often shallow. In the April 11, 2026 public smoke run against
@zuck, it returned 12 posts and zero extracted comments. - Instagram propagation coverage is intentionally conservative and limited to surfaces that are directly observable.
- Heuristic fallback providers keep the pipeline usable offline, but proper embeddings and LLM providers produce better analytical quality.
This project is licensed under the MIT License. See LICENSE.