facebook-posts-details
Extract public data from Facebook post, reel, video, photo, story and profile URLs. No login, no API key, no browser.
Get the likes, comments, shares, views, text, images, video URL and author of any public Facebook post or reel — and the name, bio, followers, category and profile picture of any public profile or page — with one function call.
A lightweight alternative to the Facebook Graph API when all you need is public data: no app review, no access token, no rate-limit dashboard.
Install
pip install facebook-posts-details
That is all you need — pip pulls the dependencies automatically.
Quick start
import facebook_posts_details as fb
post = fb.post_data("https://www.facebook.com/reel/941309212326403")
post["likes_count"] # 104378
post["comments_count"] # 292
post["post_text"] # '😎😎…..'
post["video_url"] # 'https://video-del3-1.xx.fbcdn.net/...'
profile = fb.profile_data("https://www.facebook.com/zuck")
profile["name"] # 'Mark Zuckerberg'
profile["followers"] # 121000000
profile["bio"] # 'Bringing the world closer together.'
Let the URL pick the right one for you:
data = fb.scrape(url)
Functions
| Call | Returns |
|---|---|
fb.post_data(url) |
a post, reel, video, photo or story |
fb.profile_data(url) |
a profile or page |
fb.profile_about(url) |
every "About" tab, merged into one dict |
fb.scrape(url) |
whichever of the above fits the URL |
All of them return None if the page could not be read, so one bad URL never
stops a batch:
for url in urls:
data = fb.scrape(url)
if data is None:
continue
...
Also fetch the author's profile
url = ("https://www.facebook.com/middlestumpcric/posts/"
"pfbid02UgzMPp1nyS1gzQt9RNBG1AaiqJ1eZWAVSbt5rHkTw7jod5JLNCAD8USh1wkxVYGPl")
post = fb.post_data(url, with_profile=True)
post["user"]["name"] # 'Middle Stump Cricket'
post["user"]["followers"] # 1400000
Costs one extra request. Without the flag, post["user"] is {}.
Not every author publishes every field — many personal profiles hide their
follower count, in which case post["user"]["followers"] is None while
post["user"]["name"] still works.
What you get back
Every dict has a fixed set of keys. A field that could not be found is
None (or an empty list/dict), never missing — so post["likes_count"] never
raises a KeyError.
The examples below are illustrative and come from different posts: no single
post has every field. views_count appears on some videos and reels,
transcript only on videos that have one, video_url only where there is a
video.
post_data(url)
| Key | Example |
|---|---|
url |
'https://www.facebook.com/reel/941309212326403' |
post_id |
'941309212326403' |
post_type |
'reel' — one of post, reel, video, photo, story |
post_text |
'😎😎…..' |
publish_date |
1786517734 (unix seconds) |
publish_date_iso |
'2026-08-12T06:55:34+00:00' |
likes_count |
104378 |
comments_count |
292 |
share_count |
1000 |
views_count |
9400000 |
post_images |
[{"url": "https://...", "caption": "May be an image of ..."}] |
video_url |
'https://video-del3-1.xx.fbcdn.net/...' |
transcript |
'What does Rahul Gandhi do every...' |
name |
'Sucha Shooter' |
profile_url |
'https://www.facebook.com/sucha.shooter.965' |
profile_image |
'https://...' |
user |
{}, or the author's profile with with_profile=True |
profile_data(url)
| Key | Example |
|---|---|
url |
'https://www.facebook.com/zuck' |
profile_id |
'zuck' (username, or the numeric id) |
profile_id_number |
'4' |
name |
'Mark Zuckerberg' |
bio |
'Bringing the world closer together.' |
category_name |
'Public figure' |
gender |
'MALE' |
is_verified |
True |
followers |
121000000 |
followers_text |
'121M followers' (as displayed) |
following |
None |
following_text |
None |
profile_pic |
'https://...' |
cover_photo |
'https://...' |
about_section |
{"intro": {"website": [{"value": "x.com", "url": "https://..."}]}} |
photos |
['https://...', ...] |
Good to know:
- Counts are always
intorNone— never a string like"1.4M". The displayed form is kept infollowers_text/following_text. - Reels only publish a rounded share count, so
share_counton a reel is rounded the same way the page shows it (1000for "1K"). Likes and comments are exact. post_imagesentries are always{"url", "caption"}.captionisNonewhen Facebook has no real description for the image.
Command line
facebook-posts-details <url> # prints JSON
facebook-posts-details <url1> <url2> ... # several at once
facebook-posts-details <url> --summary # short readable output
facebook-posts-details <url> --with-profile # include the author's profile
facebook-posts-details <url> --proxy http://user:pass@host:port
facebook-posts-details <url> --kind profile # skip URL auto-detection
facebook-posts-details <url> --timeout 60 --retries 3
facebook-posts-details <url> -v # show progress on stderr
JSON goes to stdout and messages go to stderr, so this works cleanly:
facebook-posts-details <url> | jq .likes_count
Exit status is 1 if any URL returned nothing.
Proxies
No proxy is used unless you pass one. Proxies work the same way as in
requests, and are accepted by post_data, profile_data, profile_about,
scrape and the CLI (--proxy).
# one URL, used for both http and https
fb.post_data(url, proxy="http://user:pass@1.2.3.4:8080")
fb.post_data(url, proxy="socks5://1.2.3.4:1080")
# a scheme-less value is treated as http
fb.post_data(url, proxy="1.2.3.4:8080")
# or a requests-style mapping
fb.post_data(url, proxy={"http": "http://1.2.3.4:8080",
"https": "http://1.2.3.4:8080"})
Examples
Without a proxy
The normal flow — nothing to configure.
import facebook_posts_details as fb
url = "https://www.facebook.com/reel/941309212326403"
post = fb.post_data(url)
if post is None:
print("could not read that page")
else:
print(post["name"], "-", post["post_text"])
print(post["likes_count"], "likes,", post["comments_count"], "comments")
for image in post["post_images"]:
print(image["url"])
With a proxy
Pass proxy= to the same call. Everything else is identical.
import facebook_posts_details as fb
PROXY = "http://user:pass@1.2.3.4:8080"
url = "https://www.facebook.com/reel/941309212326403"
post = fb.post_data(url, proxy=PROXY)
print(post["likes_count"], post["comments_count"])
Several URLs, with a different proxy each time
Rotation is up to you — just pass a different proxy per call.
import itertools
import facebook_posts_details as fb
PROXIES = [
"http://user:pass@1.2.3.4:8080",
"http://user:pass@5.6.7.8:8080",
]
URLS = [
"https://www.facebook.com/reel/941309212326403",
"https://www.facebook.com/zuck",
]
pool = itertools.cycle(PROXIES)
for url in URLS:
data = fb.scrape(url, proxy=next(pool))
if data is None:
print("failed:", url)
continue
print(data.get("name"), data.get("likes_count") or data.get("followers"))
To run the same loop without proxies, drop the argument:
data = fb.scrape(url)
A post plus its author, saved to JSON
import json
import facebook_posts_details as fb
url = ("https://www.facebook.com/middlestumpcric/posts/"
"pfbid02UgzMPp1nyS1gzQt9RNBG1AaiqJ1eZWAVSbt5rHkTw7jod5JLNCAD8USh1wkxVYGPl")
post = fb.post_data(
url,
with_profile=True, # also fetch the author's profile
proxy=None, # or "http://user:pass@host:port"
timeout=60,
retries=3,
)
with open("post.json", "w", encoding="utf-8") as f:
json.dump(post, f, indent=2, ensure_ascii=False)
print(post["name"], "->", post["user"]["followers"], "followers")
# Middle Stump Cricket -> 1400000 followers
Command line, with and without a proxy
# without
facebook-posts-details "https://www.facebook.com/reel/941309212326403" --summary
# with
facebook-posts-details "https://www.facebook.com/reel/941309212326403" \
--proxy http://user:pass@1.2.3.4:8080 --summary
Timeouts and retries
fb.post_data(url, timeout=60, retries=3)
Or change the defaults for every call:
fb.config.DEFAULT_TIMEOUT = 60 # default 30 seconds
fb.config.DEFAULT_RETRIES = 3 # default 2 attempts
Errors
Nothing is printed to stdout. Failures are logged and the call returns None.
To see why something failed:
import logging
logging.basicConfig(level=logging.DEBUG)
Exceptions are only raised for programming mistakes, e.g. fb.InvalidURL if you
pass a kind that does not exist.
Requirements
- Python 3.8 or newer
tls-client3— installed automatically bypip
Legal
Reads only publicly accessible pages, exactly as a logged-out browser would. You are responsible for complying with Facebook's Terms of Service and any law that applies to you. Provided as-is under the MIT licence.
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 facebook_posts_details-0.1.0.tar.gz.
File metadata
- Download URL: facebook_posts_details-0.1.0.tar.gz
- Upload date:
- Size: 39.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
79218cecf7dccb2a1eeca06b7e33ad1bb041afc4a04b4d94f9e622ec055a0bee
|
|
| MD5 |
965cc4907dde379e309204a2509b4b0e
|
|
| BLAKE2b-256 |
b2fbdc7b034a48af6b836acff84e1965f834af01ad90471d5a946b22619935ea
|
File details
Details for the file facebook_posts_details-0.1.0-py3-none-any.whl.
File metadata
- Download URL: facebook_posts_details-0.1.0-py3-none-any.whl
- Upload date:
- Size: 31.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ccdeebf5e932a56168b21598bca35161b3af5bc3c327de99e505246a6c7e40d0
|
|
| MD5 |
9b8f766b37c58aa58200d18564ad7cfc
|
|
| BLAKE2b-256 |
a969cac8404d8dc21f5340e7f1ae77fcdfc109fda2d34200efeccb63effcbc59
|