Facebook GraphQL Scraper
Scrape Facebook GraphQL data without an API key — profiles, posts, comments, reactions, groups, marketplace, search_engine.
Inspired by facebook-graphql-scraper.
I couldn't find a free Facebook scraper that was still working, so I decided to build one :)
The scraper is designed to be fast, lightweight, and reliable.
- GraphQL-based: Fetches data directly through Facebook's internal GraphQL endpoints.
- No HTML parsing or CSS selectors: Avoids brittle UI-dependent scraping that can break when Facebook changes its layout.
- No headless browser or browser automation: Reduces resource usage by avoiding the overhead of running a full browser.
When Facebook updates its GraphQL operation Doc IDs, the scraper automatically rediscovers them using a lightweight JavaScript runtime powered by V8 PyMiniRacer.
Facebook Gql Scraper
│
▼
┌───────────┐
│ Doc ID │◄──────────────┐
└─────┬─────┘ │
│ │
▼ │
GraphQL Request │
│ │
┌─────┴─────────┐ │
│ │ │
[Doc Id Valid] [Doc Id Invalid] │
│ │ │
▼ ▼ │
Scrape Mini JavaScript │
V8 │
│ │
▼ │
New Doc ID ────┘
Installation
pip install facebook-gql-scraper
Quick start
Export your Facebook session cookies as JSON and save them as
facebook_cookies.json in your working directory.
- Firefox: Cookie-Editor
- Windows: EditThisCookie (V3)
Profile
import facebook_gql_scraper as fgs
ctx = fgs.FacebookContext(cookies_file="facebook_cookies.json")
user = fgs.Profile.from_id(ctx, id="100084254631043")
# Or from username: user = fgs.Profile.from_username(ctx, username="Ishowspeed")
print(user.name)
Pass
quiet=TruetoFacebookContextto hide logs.
Profile properties
## Identity/ Social
user.id # "123"
user.name # "John Doe"
user.alternate_name # "john donut"
user.gender # "MALE"
user.url # "https://www.facebook.com/johndoe"
user.status_text # "La peace"
user.profile_picture_url # "https://scontent...jpg"
user.is_verified # False
user.followers_count # '123'
user.following_count # '6.9k'
user.friends_count # '4'
## About / bio
user.biography # "Thank God👏🏽🙏🏽"
user.category # "Gaming video creator"
user.description # "desc"
user.current_location # "La Peace"
user.hometown # "Cincinnati, Ohio, USA"
user.birthday # "21 January 2005"
user.relationship # {"type": "Married", "date": "12 June 2019", "desc": "with Jane Doe"}
user.family # [
# {"name": "Jane Doe", "type": "Wife"},
# {"name": "John Dad", "type": "Father"},
# ]
user.language # ["English", "Spanish"]
user.links # [{"link": "speed.store", "desc": "speed.store"}]
user.works # [{"company": "Minecraft", "desc": "Senior | 2020 - present"}]
user.colleges # [
# {
# "name": "Streamer University",
# "date": "2025 - 2026",
# "subject": "Content Creation 101",
# "activities": None,
# "dorm": None,
# "classes": None,
# "desc": None,
# },
# ]
user.high_schools # [{"name": "Streamer High School", "date": "2025 - 2026"}]
user.hobbies # [{"name": "Gaming", "desc": ""}]
user.interests # {
# "musics": [{"name": "Radiohead", "desc": None}],
# "tv_shows": [...],
# "movies": [...],
# "games": [...],
# "sports": [...],
# }
user.travels # [{"name": "Tokyo", "date": "March 2023"}]
user.contacts # [{
# "socials": [{"type": "YouTube", "name": "@johndoe"}],
# "phones": [{"type": "Mobile", "name": "+123456789"}],
# "emails": [{"type": "Personal", "name": "johndoe@example.com"}],
# "messenger": [],
# "whatsapp_number": [],
# "media_kit": [],
# }]
user.name_pronunciation # [{"name": "john-Dawg"}]
user.nicknames # [{"type": "Short", "name": "john"}]
## business
user.business_rating # "4.6 (100 reviews)"
user.business_address # "123 Main St, Ohio"
user.business_hours # "Mon-Fri: 6am-7pm"
Profile methods
# User's posts → Post
for post in user.get_posts():
print(post.id, post.text)
# User's photos → Photo
for photo in user.get_photos():
print(photo.id, photo.image_url)
# User's videos/reels → Video
for video in user.get_videos():
print(video.id, video.url, video.play_count)
# Users following this user → Profile
for follower in user.get_followers():
print(follower.id, follower.name)
# Users this user follows → Profile
for followee in user.get_followees():
print(followee.id, followee.name)
# Friends who follow this user → Profile
for friend in user.get_friends_followers():
print(friend.id, friend.name)
# Friends this user follows → Profile
for friend in user.get_friends_followees():
print(friend.id, friend.name)
Post
post = fgs.Post.from_id(ctx, id="253053527513133")
# Or from PFBID: post = fgs.Post.from_pfbid(ctx, pfbid="pfbid0qHZRZohg13A1DmZpkFvHr3yuv7y1heFNnNoijtvi5onn1ctx2sjM5rRgTMSh6EGql")
print(post.text)
Post properties
## Identity
post.id # "253053527513133"
post.creation_time # 1690761224
post.url # "https://web.facebook.com/Ishowspeed/posts/pfbid..."
post.title # None
post.text # "Thank you for all the prayers <3"
## Engagement
post.reaction_count # "47619"
post.share_count # 2380
post.comments_count # 1676
post.reactors_visible # True
post.top_reaction_breakdown # [
# {"id": "1635855486666999", "name": "Haha", "count": 21197},
# {"id": "1678524932434102", "name": "Like", "count": 17562},
# {"id": "867209233497706", "name": "Love", "count": 6761},
# ...
# ]
## Media
post.attachments # [Photo(id="123", image_url="https://...", url="https://...")]
## Related models
post.owner # Profile(id="100044296486382", name="Cristiano Ronaldo", ...)
post.group # Group (the group the post belongs to, if any)
Post methods
# All comments on the post → Comment
for comment in post.get_comments():
print(comment.id, comment.text)
# Users who reacted to the post → Reactor
for reactor in post.get_reactors():
print(reactor.reaction_type_id, reactor.profile.name)
Comment
A Comment may be a top-level comment on a post, or a reply to another comment.
Comment properties
comment.id # "1234567898767876"
comment.text # "Nice!!"
comment.created_time # 1790291800
comment.reaction_count # 12
comment.reply_count # 3
comment.attachments # [Photo(id="...", image_url="https://...")]
comment.owner # Profile(id="...", name="John Doe", ...)
comment.post # Post(id="1086837787662153", ...)
Comment methods
# Replies to the comment → Comment
for comment_reply in comment.get_replies():
print(comment_reply.id, comment_reply.text)
# Sub-replies to the reply (Facebook's max reply depth) → Comment
for comment_subreply in comment_reply.get_replies():
print(comment_subreply.id, comment_subreply.text)
# Users who reacted to the comment → Reactor
for reactor in comment.get_reactors():
print(reactor.reaction_type_id, reactor.profile.name)
Group
group = fgs.Group.from_id(ctx, id="2781022932139267")
# Or from groupname: group = fgs.Group.from_id(ctx, groupname="workingmemes")
print(group.name)
Group properties
## Identity
group.id # "2781022932139267"
group.name # "Working Memes"
group.url # "https://www.facebook.com/groups/workingmemes"
group.privacy # "PUBLIC"
group.member_count # "1.2M members"
group.profile_picture_url # "https://scontent...jpg"
group.created_time # 1456789012
group.description # "Memes for the working class."
group.privacy_info # "Public group"
group.discoverability_info # "Anyone can find this group"
Group methods
# Group's posts → Post
for post in group.get_posts():
print(post.id, post.text)
# Group's members → GroupMember
for member in group.get_members():
print(member.join_status, member.contribution_score, member.profile.name)
MarketplaceListing
listing = fgs.MarketplaceListing.from_id(ctx, id="123456789876789")
print(listing.product_details_type)
A listing is one of two types, indicated by product_details_type:
"FOR_SALE_ITEM"— regular Marketplace listing sold by a Facebook user."PARTNER_LISTING"— partner listing (e.g. eBay).
MarketplaceListing Properties
## Identity
listing.id # "123456789876789"
listing.title # "USB Microphone"
listing.description # "USB microphone with spit guard..."
listing.url # "https://web.facebook.com/marketplace/item/123456789876789/"
listing.post_id # "12345123451234512"
listing.creation_time # 1790291735 (FOR_SALE_ITEM only)
## Pricing
listing.currency # "USD"
listing.price # 25
listing.strikethrough_price # 30 (FOR_SALE_ITEM only)
## Location
listing.location_text # "Johnstown, PA"
listing.latitude # 40.322570800781 (FOR_SALE_ITEM only)
listing.longitude # -78.931274414062 (FOR_SALE_ITEM only)
## Status
listing.is_live # True (FOR_SALE_ITEM only)
listing.is_pending # False (FOR_SALE_ITEM only)
listing.is_sold # False (FOR_SALE_ITEM only)
listing.is_hidden # False (FOR_SALE_ITEM only)
## Product details
listing.product_details_type # "FOR_SALE_ITEM" or "PARTNER_LISTING"
listing.shipping_cost_text # None (PARTNER_LISTING only)
listing.has_shipping_cost # None (PARTNER_LISTING only)
listing.delivery_window_text # None (PARTNER_LISTING only)
listing.product_details_page_uri # None (PARTNER_LISTING only)
## Category / commerce
listing.category_id # "1234554321123451"
listing.attributes # [{"name": "Condition", "value": "used_good"}, ...]
listing.delivery_types # ["IN_PERSON", "PUBLIC_MEETUP"] (FOR_SALE_ITEM only)
listing.is_shipping_offered # False
listing.is_purchase_protected # False (FOR_SALE_ITEM only)
listing.inventory_count # None (FOR_SALE_ITEM only)
listing.messaging_enabled # True (FOR_SALE_ITEM only)
## Media
listing.listing_photos # [
# Photo(id="3477221012465782", image_url="https://...jpg", url=None),
# Photo(id="2159451611596036", image_url="https://...jpg", url=None),
# ]
listing.videos # [Video(id="...", thumbnail_image_url="https://...", video_url="https://...")]
## Seller (FOR_SALE_ITEM only)
seller = listing.seller
seller.profile.id # "123451234512345"
seller.profile.name # "OneEvolutionarydawn"
seller.profile.profile_picture_url # "https://...jpg"
seller.profile.joined_time # 1615703350
seller.is_verified # False
seller.seller_ratings_are_private # False
seller.seller_ratings_average # 5.0
seller.seller_ratings_count # 10
seller.combined_ratings_are_private # False
seller.combined_ratings_average # 4.9
seller.combined_ratings_count # 24
## PartnerSeller (PARTNER_LISTING only)
partner = listing.partner_seller
partner.profile.id # "1033819751641974"
partner.profile.name # "RICKS HOLLYWOOD"
partner.profile.profile_picture_url # None
partner.partner_brand_name # "eBay"
partner.seller_review_count # 12903
partner.seller_positive_ratings_pct # 0.994
partner.seller_member_since # 883251803
partner.number_of_sold_listings # None
partner.average_ship_time # None
SearchEngine
Facebook search service — profiles, posts, groups, and Marketplace listings.
engine = fgs.SearchEngine(ctx)
Search
Search Facebook for profiles, posts, or groups.
it = engine.search("Ishowspeed", filter_type="profile", exact_match=True)
for item in it:
print(item.id, item.name)
# filter_type: "all" | "profile" | "post" | "group"
# "all" → yields Profile, Post, Group
# "profile" → yields Profile
# "post" → yields Post
# "group" → yields Group
Search marketplace
Search Facebook Marketplace listings.
it = engine.search_marketplace(
query="iphone",
is_stock_available=True,
filter_location_latitude=40.7128,
filter_location_longitude=-74.0060,
filter_price_lower_bound=0,
filter_price_upper_bound=1000,
filter_radius_km=100,
item_condition="used_good", # new | used_like_new | used_good | used_fair
last_n_days=7,
sort_by="CREATION_TIME_DESCEND", # PRICE_DESCEND | PRICE_ASCEND | DISTANCE_ASCEND | CREATION_TIME_DESCEND
)
for listing in it: # yields MarketplaceListing
print(listing.id, listing.title, listing.price)
Events (TODO)
- Add support for scraping Facebook Events
Pagination Iterator
Every iterator-returning method returns a PaginatedIterator. Each item
returned by the iterator is a full model object, with all of its properties
and methods available.
For example, get_followers() returns full Profile objects:
for follower in user.get_followers():
# Properties: follower.name, follower.gender, follower.is_verified, etc.
# Methods: follower.get_posts(), follower.get_photos(), etc.
Pagination — freeze / thaw
The iterator state can be serialized and resumed later:
it = post.get_comments()
# Process the first 10 comments
for i, comment in enumerate(it):
print(comment.text)
if i == 9:
break
# Save state at any point
frozen = it.freeze()
with open("resume.json", "w") as f:
json.dump(frozen._asdict(), f)
# Later, in a new process, continue where it left off
with open("resume.json") as f:
frozen = fgs.FrozenPaginatedIterator(**json.load(f))
it2 = post.get_comments()
it2.thaw(frozen)
for comment in it2:
print(comment.text)
Contributing
🤝 Contributions are welcome! Feel free to open an issue or submit a pull request.
Metadata
Release files for facebook-gql-scraper 0.5.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| facebook_gql_scraper-0.5.2.tar.gz | 61.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| facebook_gql_scraper-0.5.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 120.6 kB
Release files / facebook_gql_scraper-0.5.2.tar.gz
| Download URL | facebook_gql_scraper-0.5.2.tar.gz |
|---|---|
| Size | 61.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1c3d162c8ed19279977fd7adca9953e1131b196d8138284a0ad8510d118fc082
|
|
BLAKE2b-256 checksum How to use checksums |
a4e2cd2a1951034db8efa548884c96124b26d29a05973baf212179a2098b49b1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.6
|
Release files / facebook_gql_scraper-0.5.2-py3-none-any.whl
| Download URL | facebook_gql_scraper-0.5.2-py3-none-any.whl |
|---|---|
| Size | 58.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ae12ea29ff2f8b2ebcc92d1bed43ee2794cfb9169d06b0e341546daeeb965d7a
|
|
BLAKE2b-256 checksum How to use checksums |
5cab202f9f18bf693febe2f828c2566137ee0e20d3b31d5c531d336145898639
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.6
|