Scrape your own logged-in Facebook timeline by observing the GraphQL responses your own browser session makes. No token replay, no credential injection.
Project description
scraper-for-facebook
Scrape posts from a logged-in personal Facebook timeline by observing the GraphQL responses your own browser session makes — no token replay, no credential injection. You log in once by hand; the browser generates its own fb_dtsg/doc_id/lsd, and this tool just reads what comes back.
Read DISCLAIMER.md before using this. Automating a Facebook account violates its Terms of Service, publishing this tool exposes its maintainer, and scraping other people's posts can make you a data controller over their personal data. Use a dedicated/throwaway account, not your primary one.
This is not the first tool that does this. facebook-graphql-scraper captures GraphQL responses via Selenium + selenium-wire with credential-based login. This project's difference is incremental, not categorical: it reuses a persisted browser-login profile instead of injecting a username/password, and builds on scrapling's modern, actively-maintained fetch stack (Playwright-driven Chromium) instead of the largely-unmaintained selenium-wire.
Install
This package depends on scrapling[fetchers], which pins exact Playwright/patchright versions. Installing it into a shared environment alongside other Playwright-based tools can fail to resolve, or silently break one of them. Always install this tool in an isolated environment:
uv tool install scraper-for-facebook
# or
pipx install scraper-for-facebook
Do not pip install scraper-for-facebook into a general-purpose virtualenv you share with other projects.
After installing, provision the browser (into its own isolated cache — this never touches a browser install any other tool manages):
scrape-fb setup
Platform: macOS is the tested, first-class target (v1). Linux likely works for the fetch/parse/CLI layer but is untested against a live Facebook session. Windows is unsupported.
Quick start
# 1. One-time interactive login — opens a real browser window, you log in by hand.
scrape-fb login
# 2. Verify the session (and that the browser + capture pipeline actually work).
scrape-fb doctor
# 3. Fetch the last 30 posts from a profile you're logged in and able to view.
scrape-fb fetch https://www.facebook.com/some.profile --limit 30
Output defaults to a JSON file under this tool's own data directory (never your current directory or stdout — see --output below), because captured posts contain other people's personal data (§4 of the disclaimer) that shouldn't casually end up in a git-tracked path.
CLI reference
scrape-fb --version
scrape-fb login [--profile NAME] [--profile-dir PATH]
scrape-fb status [--profile NAME] [--profile-dir PATH] [--json]
scrape-fb setup
scrape-fb doctor [--profile NAME] [--profile-dir PATH]
scrape-fb fetch <profile_url_or_username>
--profile NAME persisted login profile (default: "default")
--limit N max posts
--since YYYY-MM-DD lower date bound (inclusive), best-effort (see "Limitations" below)
--until YYYY-MM-DD upper date bound (inclusive)
--format json|ndjson default: json
--output PATH default: a non-repo path under this tool's data directory
--scroll-pause MIN,MAX seconds between scrolls; MIN is clamped to >= 0.5 (see "Guardrails")
--max-scrolls N scroll budget (default 40)
--profile-dir PATH override where the login profile is stored
--headed show the browser (debugging)
--raw include the raw captured story node per post (debug; contains PII).
Redacted (scrubbed) by default — combine with --no-redact for the
truly raw, unscrubbed node (prints an on-screen PII warning).
--no-redact disable redaction of --raw output (only has an effect with --raw)
-v / --verbose extra diagnostics (redaction-scrubbed by default)
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success — limit met, requested date window fully reached, or feed genuinely exhausted |
| 1 | Other/unexpected error |
| 2 | Login required or session expired — run scrape-fb login |
| 3 | Account checkpoint (Meta flagged the session) — log in again in a real browser |
| 4 | Zero posts returned — possibly parser drift against a Facebook response-shape change |
| 5 | Profile unavailable (memorialized, blocked, restricted, or nonexistent) |
| 7 | Partial: --since was requested but not confirmed reached within --max-scrolls |
A one-line summary on stderr always states the post count, observed date range, and why the run stopped — so a partial --since run is never mistaken for a complete one.
Guardrails
- The scroll-pause floor (
--scroll-pause) is clamped to ≥ 0.5s and cannot be set to 0 — this is the one non-bypassable limit in this tool, and it exists both to reduce your account's checkpoint/ban risk and to keep this from being usable as a mass-scraping tool. - One target profile per invocation; no batch/multi-profile mode; no built-in scheduler or daemon loop.
- Deeper
--sinceruns scroll more, and more scrolling raises checkpoint risk. If you value the account, prefer shallow/recent fetches and--headedruns over deep history.
Limitations (v1)
- Facebook only — no Instagram, no Threads (see roadmap).
- Personal-profile timeline posts only — no groups, pages, or photo albums.
--sinceis best-effort: because this tool observes pagination rather than driving it, Facebook can stall further pagination before your requested date is reached. Exit code7and the stderr summary tell you when that happened.- Media is captured as URLs only (no file download) — and those URLs are signed, expire, and are scoped to your viewing session; treat them as sensitive.
- No guaranteed deep-history reach; no incremental
--since-laststate (yet).
Python API
from scraper_for_facebook import FacebookScraper, Post, Media, LinkAttachment
from scraper_for_facebook.errors import (
LoginRequiredError, SessionExpiredError, ChallengeError,
ProfileUnavailableError, SessionClosedError,
)
# One-time interactive login (opens a headed browser; you log in by hand).
FacebookScraper(profile="default").login()
with FacebookScraper(profile="default") as fb: # headless reuse
posts: list[Post] = fb.fetch_profile(
"https://www.facebook.com/some.profile", limit=30, since="2026-01-01",
)
for post in fb.iter_profile("https://www.facebook.com/some.profile", limit=30):
... # must be consumed inside the `with` block
FacebookScraper(profile="default").status() # -> Status.LOGGED_IN | EXPIRED | CHECKPOINT
Contributing
uv venv && source .venv/bin/activate
uv pip install -e ".[dev]"
pre-commit install
pytest
Unit tests run against synthetic, PII-free fixtures (tests/fixtures/) — never against real captures. Live integration tests (tests/live/) are opt-in (SFB_LIVE_TESTS=1) and never run in CI. See the design doc in this repo's history for the full architecture and the reasoning behind each guardrail.
License
MIT — see LICENSE. The license covers the code; it does not cover what you do with the data you collect (see DISCLAIMER.md).
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file scraper_for_facebook-0.1.0.tar.gz.
File metadata
- Download URL: scraper_for_facebook-0.1.0.tar.gz
- Upload date:
- Size: 45.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6f79663faef55eeba73ffd2b7b475bb1b5c13d8ff35dec2b4e649e676614e88b
|
|
| MD5 |
1a3a6b4612e2ed501469a8520b6fa8ca
|
|
| BLAKE2b-256 |
1cb4e5f3f0aa48dbfbca06abfda050a31d0603fe9faf46f3635cb53b86379048
|
Provenance
The following attestation bundles were made for scraper_for_facebook-0.1.0.tar.gz:
Publisher:
publish.yml on tjdwls101010/Scraper-for-Facebook
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
scraper_for_facebook-0.1.0.tar.gz -
Subject digest:
6f79663faef55eeba73ffd2b7b475bb1b5c13d8ff35dec2b4e649e676614e88b - Sigstore transparency entry: 2075294199
- Sigstore integration time:
-
Permalink:
tjdwls101010/Scraper-for-Facebook@24ad3da48725eedf68d4242c57c0c1afc1cf9909 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/tjdwls101010
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@24ad3da48725eedf68d4242c57c0c1afc1cf9909 -
Trigger Event:
push
-
Statement type:
File details
Details for the file scraper_for_facebook-0.1.0-py3-none-any.whl.
File metadata
- Download URL: scraper_for_facebook-0.1.0-py3-none-any.whl
- Upload date:
- Size: 38.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
992bfb48d69e78626a9798ffbb7cee6a377bc9ff39de2f6fe459a2c7089e1c97
|
|
| MD5 |
9bcb8902f90ac7da4e0dfbc03cc636be
|
|
| BLAKE2b-256 |
7b09178865c38de3cf6724a587b60c9b971dbbd814e5d07cb849bf9f8fae3f1b
|
Provenance
The following attestation bundles were made for scraper_for_facebook-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on tjdwls101010/Scraper-for-Facebook
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
scraper_for_facebook-0.1.0-py3-none-any.whl -
Subject digest:
992bfb48d69e78626a9798ffbb7cee6a377bc9ff39de2f6fe459a2c7089e1c97 - Sigstore transparency entry: 2075294258
- Sigstore integration time:
-
Permalink:
tjdwls101010/Scraper-for-Facebook@24ad3da48725eedf68d4242c57c0c1afc1cf9909 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/tjdwls101010
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@24ad3da48725eedf68d4242c57c0c1afc1cf9909 -
Trigger Event:
push
-
Statement type: