Skip to content

Latest commit

Β 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

facebook-posts-details

PyPI Python License

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 int or None β€” never a string like "1.4M". The displayed form is kept in followers_text / following_text.
  • Reels only publish a rounded share count, so share_count on a reel is rounded the same way the page shows it (1000 for "1K"). Likes and comments are exact.
  • post_images entries are always {"url", "caption"}. caption is None when 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 by pip

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.

About

Scrape public Facebook posts, reels, videos, photos and profiles. Python library + CLI returning likes, comments, shares and author details. No API key.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages