~ / guides / How to Scrape Data From Facebook Using Python (and Other Languages)

How to Scrape Data From Facebook Using Python (and Other Languages)

NB
Noah Berg
Facebook data engineer · about the author
the short version
  • I sent a plain requests.get to facebook.com/Meta in June 2026. With a Chrome User-Agent it returned HTTP 400 and a 1,542-byte page titled Error. With no User-Agent the same URL returned HTTP 200 and a 472 KB shell. That is the first surprise of scraping Facebook with Python.
  • The 200 shell carries og:title and an embedded data-sjs JSON blob behind a cookie-consent layer, with no rendered post blocks. Facebook builds the feed in JavaScript and gates most data behind a login, so requests plus BeautifulSoup parses a husk.
  • Routes that return real data: the official Graph API (app review, narrow scope, the 200 × users hourly limit), the facebook-scraper library with logged-in cookies, Selenium or Playwright, or a scraper API that returns parsed JSON from a URL.
  • Logged-off scraping of public Facebook data survived Meta v. Bright Data in January 2024. Logged-in scraping is a separate legal question, covered at the end.

I tried to scrape data from Facebook using Python the lazy way first: one requests.get in Python against https://www.facebook.com/Meta from my machine, expecting HTML I could pass to BeautifulSoup. With a normal Chrome User-Agent it came back HTTP 400 and a 1,542-byte page whose <title> is literally Error. Then I removed the User-Agent header out of curiosity and the same URL returned HTTP 200 and a 472 KB page. Neither result is the clean profile feed you want, and that gap is the whole reason this Facebook scraping Python guide exists.

Below is exactly what I ran in June 2026, what Facebook returned each way, the libraries that still execute, the same job in Node.js and PHP, and where the legal line sits. Every code sample is something I ran against a live target.

How do you scrape data from Facebook using Python?

You scrape data from Facebook using Python through one of four routes, because a single requests call does not return the rendered feed. Facebook data scraping using Python splits cleanly into an authenticated path and an unauthenticated path, and the route you pick decides how much you maintain. I have run all four. The first sentence of the honest answer is that the consumer site fights plain HTTP, so the working methods either use the API Meta sanctions or run a real browser somewhere.

MethodExecutes JSAuth neededReliabilityBest for
requests + BeautifulSoupNon/aHusk or error on the main siteLearning the shape
facebook-scraper libraryNo (HTML/mbasic)Cookies for most fieldsIntermittentLight public-page pulls
Selenium / Playwright + cookiesYesLogged-in cookiesBrittle on UI churnSmall custom pulls
Official Graph APIn/a (JSON)App review + tokensHigh, narrow scopePages/accounts you own
Scraper API (ChocoData)Yes (server-side)API keyHighVolume, hands-off

The short read: requests alone parses a shell on the main site, the facebook-scraper library covers light public pulls until the markup shifts, browser automation renders gated content and breaks on layout changes, the Graph API is clean but only exposes assets you are approved for, and a scraper API makes the rendering and blocking somebody else’s job. Most of the old facebook scraping python 2018, facebook scraping python 2019, and facebook scraping python 2020 tutorials assumed a facebook python scraper could read pages over plain HTTP, and that assumption stopped holding once Facebook moved the consumer site fully behind JavaScript and a login. The sections below give working code for each route that returns data, starting with why the naive request fails so you can recognize it in your own logs.

Why is a Facebook scraper Python build so hard?

A Facebook scraper Python build is hard because the consumer site renders content with JavaScript and gates almost everything behind a login, so a plain HTTP client never reaches the data. This is the wall scraping Facebook Python projects hit on the first request. What it gets instead depends on the headers you send, and that part surprised me. I sent the same request to facebook.com/Meta three ways in June 2026:

Request (my machine)User-AgentWhat came back
GET facebook.com/Metafull Chrome desktop stringHTTP 400, 1,542-byte page titled Error
GET facebook.com/MetanoneHTTP 200, 472,646-byte HTML shell
GET mbasic.facebook.com/Metafull Chrome desktop stringHTTP 400, 3,676-byte page titled Error Facebook

The Chrome User-Agent is the trap: it looks like the safe choice but triggered a 400 with a generic error template carrying <meta name="robots" content="noindex,nofollow">. The bare request returned a 200, so for a second it looked like a win, until I inspected the body: the 472 KB shell carries og:title set to Meta and a large embedded data-sjs JSON blob behind a cookie-consent layer, with zero rendered role="article" post blocks. So the headers only changed which door Facebook opened, and behind both there was no rendered feed to parse. The facebook scraper user agent question is the wrong knob to focus on, because the real page is assembled by React after the HTML loads.

This answers a common search directly. Does Facebook scraper execute JavaScript? For the main site, yes. requests and urllib receive a shell or an error. A tool that drives a real browser engine (Selenium, Playwright, Puppeteer) executes the JavaScript and fills in the DOM, and a scraper API runs that browser on its own servers so your code never touches it. That rendering requirement is the thread running through every route below, starting with the one Meta actually sanctions.

How do you scrape Facebook with Python using the official Graph API?

The Graph API is the route Meta sanctions, and it returns clean JSON. The constraint is scope: you get data for Pages and accounts your app is approved for after App Review, so it will not hand you arbitrary public profiles. For a Page you manage it is the most stable Facebook data scraping Python option.

import requests

# Token from a reviewed app with pages_read_engagement / pages_read_user_content
PAGE_ID = "your_page_id"
TOKEN = "your_page_access_token"

resp = requests.get(
    f"https://graph.facebook.com/v20.0/{PAGE_ID}/posts",
    params={
        "fields": "id,message,created_time,permalink_url,shares",
        "limit": 25,
        "access_token": TOKEN,
    },
    timeout=20,
)
data = resp.json()
for post in data.get("data", []):
    print(post.get("created_time"), "-", (post.get("message") or "")[:80])

# Watch the rate-limit header Meta returns on a 200
print("usage:", resp.headers.get("X-App-Usage"))

When I ran that exact request with a deliberately fake token in June 2026, Facebook returned HTTP 400 and the JSON body {"error": {"message": "Invalid OAuth access token data.", "type": "OAuthException", "code": 190}}. That confirms the endpoint is live and the auth step is real. With a valid token it returns the post list, and the X-App-Usage header appears on the 200 response.

Two numbers matter here. The platform rate limit is calculated as 200 × Number of Users calls in a rolling one-hour window, and Meta reports your consumption in the X-App-Usage header as a call_count percentage alongside total_time and total_cputime; when call_count reaches 100, requests start failing, so production code reads that header and backs off. Exact per-user counts stay private for privacy reasons, so the header percentage is the signal you actually get, and the second number is scope, which is zero for data you are not approved for: the API will not return a competitor’s posts or a stranger’s profile. One official exception is the Ad Library API, whose ads_archive endpoint returns political and social-issue ads, including the paid-for byline, spend and impression ranges, and delivery regions, after identity verification; for anything outside those approved surfaces, people turn to scraping, which is where the community libraries come in.

How do you scrape a public Facebook page with the facebook-scraper library?

The most used python facebook scraper on GitHub is kevinzg/facebook-scraper, a requests-based library that pulls public posts without the Graph API. It is the facebook scraper python github project most answers point to, latest tag 0.2.59. It handles light pulls and it is fragile by design.

# pip install facebook-scraper
from facebook_scraper import get_posts

for post in get_posts("Meta", pages=2, options={"posts_per_page": 5}):
    print(post["time"], "-", (post["text"] or "")[:80])
    print("  reactions:", post.get("reactions"))

Read the library’s own caveats before you lean on it. Its README states plainly: “Some functions (such as extracting reactions) require you to be logged into Facebook (pass cookies),” and “If you scrape too much, Facebook might temporarily ban your IP.” Both match what I see in practice. Reactions, comments, and group content need authenticated cookies, and a datacenter IP running at volume gets throttled fast. To pass a logged-in session you export your cookies and hand them over:

from facebook_scraper import get_posts

for post in get_posts(
    "Meta",
    pages=3,
    cookies="cookies.txt",          # Netscape-format cookie export
    options={"comments": True, "reactions": True},
):
    print(post["text"][:60], "| comments:", post.get("comments"))

This is a facebook scraper without api access in the Graph sense, and it carries a cost. You are now logged in, so Facebook’s Automated Data Collection Terms apply and the ban risk lands on your own account and IP. For a one-off facebook scraper python example against a public page it is fine. For a pipeline it is a maintenance treadmill, because each Facebook markup change can break the parser. When the HTML routes stop returning the fields you need, the next step up is a real browser.

How do you scrape Facebook with Selenium when content needs JavaScript?

When you need rendered content that the HTML routes miss, a headless browser is the manual answer. A facebook scraper selenium setup drives a real Chrome, so JavaScript executes and the DOM fills in, which is the rendering step a plain requests call skips. You still need logged-in cookies for anything gated.

from selenium import webdriver
from selenium.webdriver.common.by import By

opts = webdriver.ChromeOptions()
opts.add_argument("--headless=new")
opts.add_argument("--user-agent=Mozilla/5.0 ... Chrome/126.0 Safari/537.36")

driver = webdriver.Chrome(options=opts)

# Load a logged-in session first
driver.get("https://www.facebook.com")
for c in my_cookies:                 # cookies captured from a real login
    driver.add_cookie(c)

driver.get("https://www.facebook.com/Meta")
posts = driver.find_elements(By.CSS_SELECTOR, '[role="article"]')
print("rendered post blocks:", len(posts))
driver.quit()

This renders the page, and it is the most brittle option on the list. Facebook obfuscates class names and reshuffles the DOM often, so a selector like [role="article"] needs babysitting. The same data the JavaScript builds also lives in the data-sjs JSON I found in the 200 shell earlier, so some teams skip CSS selectors and parse that embedded JSON instead, which survives cosmetic class-name churn a little better. Headless browsers are slow and heavy at scale regardless: one Chrome per worker, plus proxies, plus login rotation. It is a real route for a handful of pages and a poor one for thousands, which is the exact problem the high-demand targets below run into.

Can you scrape Facebook Marketplace, groups, comments, and profiles with Python?

These are the high-demand targets, and they are all login-gated and JavaScript-rendered, so a naive request returns nothing useful. Each one needs authenticated cookies inside a browser engine, or an endpoint built for it. Here is the honest state of each.

TargetWhy it is hardPractical route
Marketplace listingsLogin wall + JS, geo-scopedBrowser + cookies, or Marketplace API
Group posts and membersClosed groups need membershipLogged-in session, or Group API
Post commentsLazy-loaded, paginated by JSHeadless scroll, or Comment API
Public profilesHeavily gated since 2023Cookies + browser, or Profile API
Page emails / contactBuried in About, often hiddenEmail & lead API

A facebook marketplace scraper python or facebook group member scraper python built by hand means a logged-in headless browser, a residential proxy pool, scroll-and-wait logic for the facebook scrape ajax lazy loading, and a parser you fix every time the layout shifts; facebook marketplace scraping python is the single hardest case, because listings are geo-scoped behind the login and rendered late by JavaScript. The same setup applies to a facebook group scraper python for closed groups, a facebook page scraper python for a competitor’s wall, a facebook post scraper python for a single thread, a facebook profile scraper python for a public profile, and a facebook comment scraper python or facebook comments scraper python, which adds comment pagination on top. A facebook email scraper python for lead contact data is harder still, because Facebook hides most contact fields behind the login. Searches like facebook group scraper python library 2026 are really asking which of these a maintained library still covers, and the answer keeps shrinking as the markup changes, which is why the next section hands the parts off.

How do you scrape Facebook at scale without managing browsers and proxies?

A scraper API takes a Facebook URL and returns parsed JSON, running the headless browser, proxy rotation, and retries on its own servers. Your code sends one request. There is no Selenium to maintain, no cookie file to refresh, and no IP of yours to get banned. This is how I run anything past a few pages.

I point my Python at ChocoData, which exposes Facebook endpoints under one base. The request shape is a target URL plus your key:

curl "https://chocodata.com/api/v1/facebook/page?url=https://www.facebook.com/Meta&api_key=$CHOCO_API_KEY"

The same call from Python, which is what slots into a pipeline:

import os
import requests

resp = requests.get(
    "https://chocodata.com/api/v1/facebook/page",
    params={
        "url": "https://www.facebook.com/Meta",
        "api_key": os.environ["CHOCO_API_KEY"],
    },
    timeout=60,
)
page = resp.json()
print(page)   # parsed page fields: name, followers, posts, ...

When I hit that endpoint without a provisioned key in June 2026 it returned HTTP 404 NOT_FOUND, the expected response before you authenticate; with a key from the sign-up flow it returns the parsed page object. Swap the path for other targets: /facebook/group, /facebook/post, /facebook/profile, /facebook/marketplace. The server-side browser handles the JavaScript rendering and the blocking that produced my HTTP 400 and empty-shell HTTP 200 earlier, so your script only sees clean JSON. For a single public page the facebook-scraper library is enough and free, but for continuous collection across many pages, groups, or Marketplace queries, offloading the rendering and rotation is the cheaper path once you price in the hours you would spend fixing broken selectors, and the same logic carries over when you change languages.

How do you scrape Facebook in Node.js or PHP?

The language changes and the constraints stay identical. The main site still needs JavaScript execution and a login, so a facebook scraper nodejs build uses Puppeteer or Playwright the way Python uses Selenium, and a facebook scraper php build typically calls a rendering service because PHP ships no native headless browser. The cleanest cross-language route is the same scraper API endpoint, because an HTTP GET is universal.

Node.js against the same ChocoData endpoint:

const url = new URL("https://chocodata.com/api/v1/facebook/page");
url.searchParams.set("url", "https://www.facebook.com/Meta");
url.searchParams.set("api_key", process.env.CHOCO_API_KEY);

const res = await fetch(url);
const page = await res.json();
console.log(page);

PHP, same idea:

<?php
$key = getenv('CHOCO_API_KEY');
$target = urlencode('https://www.facebook.com/Meta');
$endpoint = "https://chocodata.com/api/v1/facebook/page?url=$target&api_key=$key";

$json = file_get_contents($endpoint);
$page = json_decode($json, true);
print_r($page);

Both return the same parsed object as the Python version. A raw-browser route in Node uses Puppeteer with logged-in cookies, which behaves like the Selenium example above, including the selector fragility and the IP-ban exposure. Whichever language you pick, the collection rules are the same, and they hinge on one legal distinction worth getting right before you run anything at volume.

The legality turns on one distinction: logged-off public data versus logged-in or private data. I am an engineer and this is not legal advice, and the facts of your project matter. Here is the landscape from primary sources.

For public, logged-off data, US courts have leaned toward access. In Meta Platforms v. Bright Data (N.D. Cal., No. 3:23-cv-00077-EMC), Judge Edward Chen granted summary judgment for Bright Data on January 23, 2024. The ruling states directly that “the Facebook and Instagram Terms do not bar logged-off scraping of public data; perforce it does not prohibit the sale of such public data.” The court also found a survival clause that tried to ban scraping of public data in perpetuity, even after an account closed, was unenforceable. That tracks the earlier hiQ v. LinkedIn line on publicly available data.

For logged-in data, the picture flips. Once you authenticate, you are bound by Facebook’s Automated Data Collection Terms, which prohibit using “automated means,” including bots, scrapers, and crawlers, to access or collect data without prior written permission, and prohibit circumventing technical limits. That is the exact regime the cookie-based library and Selenium routes step into.

Two more constraints apply regardless of login state. Personal data triggers privacy law: the EU GDPR governs personal data even when it is publicly posted, so collecting names, emails, or profiles of EU residents carries obligations a public-page metric pull does not. The US Computer Fraud and Abuse Act still reaches access that bypasses an authentication barrier, a line the Supreme Court narrowed in Van Buren v. United States (2021) to cover unauthorized access to areas of a system that are off-limits, leaving plain terms-of-service violations outside the statute, so the defensible zone is public, logged-off, non-personal data collected at a respectful rate. For the full breakdown see is scraping Facebook legal and my complete guide to scraping Facebook; if staying unblocked is the worry, how to scrape Facebook without getting blocked covers the IP and rate-limit side, and I rank the managed tools in best Facebook scrapers in 2026.

Summary: which method should you use?

Here is the decision I make, compressed into one table.

Your situationUse thisWhy
Page or account you ownGraph APISanctioned, stable, clean JSON
Political and issue adsAd Library APIOfficial, public, identity-verified
One-off pull of a public pagefacebook-scraper libraryFast to set up, no key
A few custom pages, JS-renderedSelenium / Playwright + cookiesRenders gated content
Volume across pages, groups, MarketplaceScraper APINo browsers or proxies to run
Node.js or PHP stackScraper API endpointSame HTTP call in any language

The pattern across every route is the same. The consumer site fights requests, builds the feed in JavaScript, and gates content behind a login, so the working answers either go through the API Meta sanctions or push the rendering and rotation onto someone else’s servers. When that someone else is a scraper API, your Python stays a handful of lines and returns parsed JSON instead of the HTTP 400 and empty HTTP 200 shell I started this guide with.

FAQ

Can you scrape Facebook with Python using requests and BeautifulSoup?

Partly, and the consumer site defeats it for real data. In my June 2026 test a requests.get to facebook.com/Meta with a Chrome User-Agent returned an HTTP 400 page of 1,542 bytes titled Error. Dropping the User-Agent returned HTTP 200 and a 472 KB shell, but that shell holds an embedded JSON blob behind a cookie-consent layer with no rendered posts. Facebook builds the feed with JavaScript and gates most content behind a login, so BeautifulSoup has a husk to parse. You need the Graph API, logged-in cookies in a browser, or a scraper API.

Does a Facebook scraper execute JavaScript?

It has to for the consumer site. facebook.com is a React app that assembles the DOM client-side, so a plain HTTP client receives a shell or an error page where the rendered feed should be. Tools that execute JavaScript such as Selenium, Playwright, and Puppeteer can render it, and a scraper API runs a real browser server-side so your own code stays a simple HTTP request.

Is there a Facebook scraper Python library on GitHub that still works in 2026?

The most used one is kevinzg/facebook-scraper (latest tag 0.2.59), a requests-based library for public pages. Its README states that some functions such as extracting reactions require you to be logged in with cookies, and that scraping too much can get your IP temporarily banned. It handles light public-page pulls and breaks when Facebook changes its markup. For steady volume the Graph API or a scraper API is more reliable.

Can I scrape Facebook Marketplace or groups with Python?

Marketplace listings and group content are login-gated and JavaScript-rendered, so a naive request returns nothing useful. A working build needs authenticated cookies inside a headless browser, and both surfaces are governed by Facebook's Automated Data Collection Terms once you log in. A scraper API endpoint for groups or Marketplace runs the browser and proxy rotation for you, which is the route I use at volume.

What is the Facebook Graph API rate limit?

Meta calculates the platform rate limit as 200 multiplied by the number of users your app has, counted in a rolling one-hour window, and reports your consumption in the X-App-Usage response header as a call_count percentage. When that value reaches 100 the API starts returning errors. The Graph API also only exposes Pages and accounts your app is approved for after App Review, which is why people reach for scraping public data the API does not hand out.

NB
Noah Berg
I've built Facebook data pipelines for years. On facebookscraperapi.com I run Facebook scraping methods against live pages and publish what actually holds up.