facebook-gql-scraper 0.5.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,513 @@
1
+ Metadata-Version: 2.4
2
+ Name: facebook-gql-scraper
3
+ Version: 0.5.1
4
+ Summary: Scrape Facebook GraphQL data without an API key — profiles, posts, comments, reactions, groups, marketplace, search_engine.
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: iso4217parse>=0.6.2
9
+ Requires-Dist: mini-racer>=0.14.1
10
+ Requires-Dist: price-parser>=0.5.1
11
+ Requires-Dist: requests>=2.31.0
12
+
13
+ # Facebook GraphQL Scraper
14
+
15
+ Scrape Facebook GraphQL data without an API key — profiles, posts, comments, reactions, groups, marketplace, search_engine.
16
+
17
+ Inspired by [facebook-graphql-scraper](https://github.com/FaustRen/facebook-graphql-scraper).
18
+ > I couldn't find a free Facebook scraper that was still working, so I decided to build one :)
19
+
20
+ ---
21
+
22
+ The scraper is designed to be fast, lightweight, and reliable.
23
+
24
+ - **GraphQL-based:** Fetches data directly through Facebook's internal GraphQL endpoints.
25
+ - **No HTML parsing or CSS selectors:** Avoids brittle UI-dependent scraping that can break when Facebook changes its layout.
26
+ - **No headless browser or browser automation:** Reduces resource usage by avoiding the overhead of running a full browser.
27
+
28
+ When Facebook updates its GraphQL operation Doc IDs, the scraper automatically rediscovers them using a lightweight JavaScript runtime powered by V8 [PyMiniRacer](https://github.com/bpcreech/PyMiniRacer).
29
+
30
+ ```
31
+
32
+ Facebook Gql Scraper
33
+ │
34
+ ▼
35
+ ┌───────────┐
36
+ │ Doc ID │◄──────────────┐
37
+ └─────┬─────┘ │
38
+ │ │
39
+ ▼ │
40
+ GraphQL Request │
41
+ │ │
42
+ ┌─────┴─────────┐ │
43
+ │ │ │
44
+ [Doc Id Valid] [Doc Id Invalid] │
45
+ │ │ │
46
+ ▼ ▼ │
47
+ Scrape Mini JavaScript │
48
+ V8 │
49
+ │ │
50
+ ▼ │
51
+ New Doc ID ────┘
52
+ ```
53
+
54
+ ---
55
+
56
+ ## Installation
57
+
58
+ ```bash
59
+ pip install facebook-gql-scraper
60
+ ```
61
+ ---
62
+
63
+ ## Quick start
64
+
65
+ Export your Facebook session cookies as JSON and save them as
66
+ `facebook_cookies.json` in your working directory.
67
+
68
+ - Firefox: [Cookie-Editor](https://addons.mozilla.org/en-US/firefox/addon/cookie-editor/)
69
+ - Windows: [EditThisCookie (V3)](https://chromewebstore.google.com/detail/ojfebgpkimhlhcblbalbfjblapadhbol?utm_source=item-share-cb)
70
+
71
+ ## Profile
72
+
73
+ ```python
74
+ import facebook_gql_scraper as fgs
75
+
76
+ ctx = fgs.FacebookContext(cookies_file="facebook_cookies.json")
77
+ user = fgs.Profile.from_id(ctx, id="100084254631043")
78
+ # Or from username: user = fgs.Profile.from_username(ctx, username="Ishowspeed")
79
+ print(user.name)
80
+ ```
81
+ > Pass `quiet=True` to `FacebookContext` to hide logs.
82
+ ### Profile properties
83
+
84
+ ```python
85
+ ## Identity/ Social
86
+ user.id # "123"
87
+ user.name # "John Doe"
88
+ user.alternate_name # "john donut"
89
+ user.gender # "MALE"
90
+ user.url # "https://www.facebook.com/johndoe"
91
+ user.status_text # "La peace"
92
+ user.profile_picture_url # "https://scontent...jpg"
93
+ user.is_verified # False
94
+
95
+ user.followers_count # '123'
96
+ user.following_count # '6.9k'
97
+ user.friends_count # '4'
98
+
99
+ ## About / bio
100
+ user.biography # "Thank God👏🏽🙏🏽"
101
+ user.category # "Gaming video creator"
102
+ user.description # "desc"
103
+ user.current_location # "La Peace"
104
+ user.hometown # "Cincinnati, Ohio, USA"
105
+ user.birthday # "21 January 2005"
106
+
107
+ user.relationship # {"type": "Married", "date": "12 June 2019", "desc": "with Jane Doe"}
108
+
109
+ user.family # [
110
+ # {"name": "Jane Doe", "type": "Wife"},
111
+ # {"name": "John Dad", "type": "Father"},
112
+ # ]
113
+
114
+ user.language # ["English", "Spanish"]
115
+ user.links # [{"link": "speed.store", "desc": "speed.store"}]
116
+ user.works # [{"company": "Minecraft", "desc": "Senior | 2020 - present"}]
117
+
118
+ user.colleges # [
119
+ # {
120
+ # "name": "Streamer University",
121
+ # "date": "2025 - 2026",
122
+ # "subject": "Content Creation 101",
123
+ # "activities": None,
124
+ # "dorm": None,
125
+ # "classes": None,
126
+ # "desc": None,
127
+ # },
128
+ # ]
129
+
130
+ user.high_schools # [{"name": "Streamer High School", "date": "2025 - 2026"}]
131
+ user.hobbies # [{"name": "Gaming", "desc": ""}]
132
+
133
+ user.interests # {
134
+ # "musics": [{"name": "Radiohead", "desc": None}],
135
+ # "tv_shows": [...],
136
+ # "movies": [...],
137
+ # "games": [...],
138
+ # "sports": [...],
139
+ # }
140
+
141
+ user.travels # [{"name": "Tokyo", "date": "March 2023"}]
142
+
143
+ user.contacts # [{
144
+ # "socials": [{"type": "YouTube", "name": "@johndoe"}],
145
+ # "phones": [{"type": "Mobile", "name": "+123456789"}],
146
+ # "emails": [{"type": "Personal", "name": "johndoe@example.com"}],
147
+ # "messenger": [],
148
+ # "whatsapp_number": [],
149
+ # "media_kit": [],
150
+ # }]
151
+
152
+ user.name_pronunciation # [{"name": "john-Dawg"}]
153
+ user.nicknames # [{"type": "Short", "name": "john"}]
154
+
155
+ ## business
156
+ user.business_rating # "4.6 (100 reviews)"
157
+ user.business_address # "123 Main St, Ohio"
158
+ user.business_hours # "Mon-Fri: 6am-7pm"
159
+ ```
160
+
161
+ ### Profile methods
162
+
163
+ ```python
164
+ # User's posts → Post
165
+ for post in user.get_posts():
166
+ print(post.id, post.text)
167
+
168
+ # User's photos → Photo
169
+ for photo in user.get_photos():
170
+ print(photo.id, photo.image_url)
171
+
172
+ # User's videos/reels → Video
173
+ for video in user.get_videos():
174
+ print(video.id, video.url, video.play_count)
175
+
176
+ # Users following this user → Profile
177
+ for follower in user.get_followers():
178
+ print(follower.id, follower.name)
179
+
180
+ # Users this user follows → Profile
181
+ for followee in user.get_followees():
182
+ print(followee.id, followee.name)
183
+
184
+ # Friends who follow this user → Profile
185
+ for friend in user.get_friends_followers():
186
+ print(friend.id, friend.name)
187
+
188
+ # Friends this user follows → Profile
189
+ for friend in user.get_friends_followees():
190
+ print(friend.id, friend.name)
191
+ ```
192
+ ---
193
+
194
+ ## Post
195
+
196
+ ```python
197
+ post = fgs.Post.from_id(ctx, id="253053527513133")
198
+ # Or from PFBID: post = fgs.Post.from_pfbid(ctx, pfbid="pfbid0qHZRZohg13A1DmZpkFvHr3yuv7y1heFNnNoijtvi5onn1ctx2sjM5rRgTMSh6EGql")
199
+ print(post.text)
200
+ ```
201
+
202
+ ### Post properties
203
+
204
+ ```python
205
+ ## Identity
206
+ post.id # "253053527513133"
207
+ post.creation_time # 1690761224
208
+ post.url # "https://web.facebook.com/Ishowspeed/posts/pfbid..."
209
+ post.title # None
210
+ post.text # "Thank you for all the prayers <3"
211
+
212
+ ## Engagement
213
+ post.reaction_count # "47619"
214
+ post.share_count # 2380
215
+ post.comments_count # 1676
216
+
217
+ post.reactors_visible # True
218
+ post.top_reaction_breakdown # [
219
+ # {"id": "1635855486666999", "name": "Haha", "count": 21197},
220
+ # {"id": "1678524932434102", "name": "Like", "count": 17562},
221
+ # {"id": "867209233497706", "name": "Love", "count": 6761},
222
+ # ...
223
+ # ]
224
+
225
+ ## Media
226
+ post.attachments # [Photo(id="123", image_url="https://...", url="https://...")]
227
+
228
+ ## Related models
229
+ post.owner # Profile(id="100044296486382", name="Cristiano Ronaldo", ...)
230
+ post.group # Group (the group the post belongs to, if any)
231
+ ```
232
+
233
+ ### Post methods
234
+
235
+ ```python
236
+ # All comments on the post → Comment
237
+ for comment in post.get_comments():
238
+ print(comment.id, comment.text)
239
+
240
+ # Users who reacted to the post → Reactor
241
+ for reactor in post.get_reactors():
242
+ print(reactor.reaction_type_id, reactor.profile.name)
243
+ ```
244
+
245
+ ---
246
+
247
+ ## Comment
248
+
249
+ A `Comment` may be a top-level comment on a post, or a reply to another comment.
250
+
251
+ ### Comment properties
252
+
253
+ ```python
254
+ comment.id # "1234567898767876"
255
+ comment.text # "Nice!!"
256
+ comment.created_time # 1790291800
257
+ comment.reaction_count # 12
258
+ comment.reply_count # 3
259
+
260
+ comment.attachments # [Photo(id="...", image_url="https://...")]
261
+ comment.owner # Profile(id="...", name="John Doe", ...)
262
+ comment.post # Post(id="1086837787662153", ...)
263
+ ```
264
+
265
+ ### Comment methods
266
+
267
+ ```python
268
+ # Replies to the comment → Comment
269
+ for comment_reply in comment.get_replies():
270
+ print(comment_reply.id, comment_reply.text)
271
+
272
+ # Sub-replies to the reply (Facebook's max reply depth) → Comment
273
+ for comment_subreply in comment_reply.get_replies():
274
+ print(comment_subreply.id, comment_subreply.text)
275
+
276
+ # Users who reacted to the comment → Reactor
277
+ for reactor in comment.get_reactors():
278
+ print(reactor.reaction_type_id, reactor.profile.name)
279
+ ```
280
+ ---
281
+
282
+ ## Group
283
+
284
+ ```python
285
+ group = fgs.Group.from_id(ctx, id="2781022932139267")
286
+ # Or from groupname: group = fgs.Group.from_id(ctx, groupname="workingmemes")
287
+ print(group.name)
288
+ ```
289
+
290
+ ### Group properties
291
+
292
+ ```python
293
+ ## Identity
294
+ group.id # "2781022932139267"
295
+ group.name # "Working Memes"
296
+ group.url # "https://www.facebook.com/groups/workingmemes"
297
+ group.privacy # "PUBLIC"
298
+ group.member_count # "1.2M members"
299
+ group.profile_picture_url # "https://scontent...jpg"
300
+ group.created_time # 1456789012
301
+ group.description # "Memes for the working class."
302
+ group.privacy_info # "Public group"
303
+ group.discoverability_info # "Anyone can find this group"
304
+ ```
305
+
306
+ ### Group methods
307
+
308
+ ```python
309
+ # Group's posts → Post
310
+ for post in group.get_posts():
311
+ print(post.id, post.text)
312
+
313
+ # Group's members → GroupMember
314
+ for member in group.get_members():
315
+ print(member.join_status, member.contribution_score, member.profile.name)
316
+ ```
317
+ ---
318
+
319
+ ## MarketplaceListing
320
+
321
+ ```python
322
+ listing = fgs.MarketplaceListing.from_id(ctx, id="123456789876789")
323
+ print(listing.product_details_type)
324
+ ```
325
+ A listing is one of two types, indicated by `product_details_type`:
326
+
327
+ - **`"FOR_SALE_ITEM"`** — regular Marketplace listing sold by a Facebook user.
328
+ - **`"PARTNER_LISTING"`** — partner listing (e.g. eBay).
329
+
330
+ ### MarketplaceListing Properties
331
+
332
+ ```python
333
+ ## Identity
334
+ listing.id # "123456789876789"
335
+ listing.title # "USB Microphone"
336
+ listing.description # "USB microphone with spit guard..."
337
+ listing.url # "https://web.facebook.com/marketplace/item/123456789876789/"
338
+ listing.post_id # "12345123451234512"
339
+ listing.creation_time # 1790291735 (FOR_SALE_ITEM only)
340
+
341
+ ## Pricing
342
+ listing.currency # "USD"
343
+ listing.price # 25
344
+ listing.strikethrough_price # 30 (FOR_SALE_ITEM only)
345
+
346
+ ## Location
347
+ listing.location_text # "Johnstown, PA"
348
+ listing.latitude # 40.322570800781 (FOR_SALE_ITEM only)
349
+ listing.longitude # -78.931274414062 (FOR_SALE_ITEM only)
350
+
351
+ ## Status
352
+ listing.is_live # True (FOR_SALE_ITEM only)
353
+ listing.is_pending # False (FOR_SALE_ITEM only)
354
+ listing.is_sold # False (FOR_SALE_ITEM only)
355
+ listing.is_hidden # False (FOR_SALE_ITEM only)
356
+
357
+ ## Product details
358
+ listing.product_details_type # "FOR_SALE_ITEM" or "PARTNER_LISTING"
359
+ listing.shipping_cost_text # None (PARTNER_LISTING only)
360
+ listing.has_shipping_cost # None (PARTNER_LISTING only)
361
+ listing.delivery_window_text # None (PARTNER_LISTING only)
362
+ listing.product_details_page_uri # None (PARTNER_LISTING only)
363
+
364
+ ## Category / commerce
365
+ listing.category_id # "1234554321123451"
366
+ listing.attributes # [{"name": "Condition", "value": "used_good"}, ...]
367
+ listing.delivery_types # ["IN_PERSON", "PUBLIC_MEETUP"] (FOR_SALE_ITEM only)
368
+ listing.is_shipping_offered # False
369
+ listing.is_purchase_protected # False (FOR_SALE_ITEM only)
370
+ listing.inventory_count # None (FOR_SALE_ITEM only)
371
+ listing.messaging_enabled # True (FOR_SALE_ITEM only)
372
+
373
+ ## Media
374
+ listing.listing_photos # [
375
+ # Photo(id="3477221012465782", image_url="https://...jpg", url=None),
376
+ # Photo(id="2159451611596036", image_url="https://...jpg", url=None),
377
+ # ]
378
+
379
+ listing.videos # [Video(id="...", thumbnail_image_url="https://...", video_url="https://...")]
380
+
381
+ ## Seller (FOR_SALE_ITEM only)
382
+ seller = listing.seller
383
+
384
+ seller.profile.id # "123451234512345"
385
+ seller.profile.name # "OneEvolutionarydawn"
386
+ seller.profile.profile_picture_url # "https://...jpg"
387
+ seller.profile.joined_time # 1615703350
388
+ seller.is_verified # False
389
+ seller.seller_ratings_are_private # False
390
+ seller.seller_ratings_average # 5.0
391
+ seller.seller_ratings_count # 10
392
+ seller.combined_ratings_are_private # False
393
+ seller.combined_ratings_average # 4.9
394
+ seller.combined_ratings_count # 24
395
+
396
+ ## PartnerSeller (PARTNER_LISTING only)
397
+ partner = listing.partner_seller
398
+
399
+ partner.profile.id # "1033819751641974"
400
+ partner.profile.name # "RICKS HOLLYWOOD"
401
+ partner.profile.profile_picture_url # None
402
+ partner.partner_brand_name # "eBay"
403
+ partner.seller_review_count # 12903
404
+ partner.seller_positive_ratings_pct # 0.994
405
+ partner.seller_member_since # 883251803
406
+ partner.number_of_sold_listings # None
407
+ partner.average_ship_time # None
408
+ ```
409
+
410
+ ---
411
+ ## SearchEngine
412
+
413
+ Facebook search service — profiles, posts, groups, and Marketplace listings.
414
+
415
+ ```python
416
+ engine = fgs.SearchEngine(ctx)
417
+ ```
418
+
419
+ ### Search
420
+
421
+ Search Facebook for profiles, posts, or groups.
422
+
423
+ ```python
424
+ it = engine.search("Ishowspeed", filter_type="profile", exact_match=True)
425
+ for item in it:
426
+ print(item.id, item.name)
427
+
428
+ # filter_type: "all" | "profile" | "post" | "group"
429
+ # "all" → yields Profile, Post, Group
430
+ # "profile" → yields Profile
431
+ # "post" → yields Post
432
+ # "group" → yields Group
433
+ ```
434
+
435
+ ### Search marketplace
436
+
437
+ Search Facebook Marketplace listings.
438
+
439
+ ```python
440
+ it = engine.search_marketplace(
441
+ query="iphone",
442
+ is_stock_available=True,
443
+ filter_location_latitude=40.7128,
444
+ filter_location_longitude=-74.0060,
445
+ filter_price_lower_bound=0,
446
+ filter_price_upper_bound=1000,
447
+ filter_radius_km=100,
448
+ item_condition="used_good", # new | used_like_new | used_good | used_fair
449
+ last_n_days=7,
450
+ sort_by="CREATION_TIME_DESCEND", # PRICE_DESCEND | PRICE_ASCEND | DISTANCE_ASCEND | CREATION_TIME_DESCEND
451
+ )
452
+
453
+ for listing in it: # yields MarketplaceListing
454
+ print(listing.id, listing.title, listing.price)
455
+ ```
456
+
457
+ ---
458
+
459
+ ## Events (TODO)
460
+
461
+ - [ ] Add support for scraping Facebook Events
462
+
463
+ ---
464
+
465
+ ## Pagination Iterator
466
+
467
+ Every iterator-returning method returns a `PaginatedIterator`. Each item
468
+ returned by the iterator is a full model object, with all of its properties
469
+ and methods available.
470
+
471
+ For example, `get_followers()` returns full `Profile` objects:
472
+
473
+ ```python
474
+ for follower in user.get_followers():
475
+ # Properties: follower.name, follower.gender, follower.is_verified, etc.
476
+ # Methods: follower.get_posts(), follower.get_photos(), etc.
477
+
478
+ ```
479
+ ### Pagination — freeze / thaw
480
+
481
+ The iterator state can be serialized and resumed later:
482
+
483
+ ```python
484
+ it = post.get_comments()
485
+
486
+ # Process the first 10 comments
487
+ for i, comment in enumerate(it):
488
+ print(comment.text)
489
+ if i == 9:
490
+ break
491
+
492
+ # Save state at any point
493
+ frozen = it.freeze()
494
+ with open("resume.json", "w") as f:
495
+ json.dump(frozen._asdict(), f)
496
+
497
+ # Later, in a new process, continue where it left off
498
+ with open("resume.json") as f:
499
+ frozen = fgs.FrozenPaginatedIterator(**json.load(f))
500
+
501
+ it2 = post.get_comments()
502
+ it2.thaw(frozen)
503
+
504
+ for comment in it2:
505
+ print(comment.text)
506
+ ```
507
+
508
+ ---
509
+
510
+ ## Contributing
511
+
512
+ 🤝 Contributions are welcome! Feel free to open an issue or submit a pull request.
513
+