fb_scraper_request 0.2.0__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,19 @@
1
+ Copyright (c) <2024> <FaustRen>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1,292 @@
1
+ Metadata-Version: 2.4
2
+ Name: fb_scraper_request
3
+ Version: 0.2.0
4
+ Summary: Facebook GraphQL Scraper - No login required, simple API to scrape public Facebook posts
5
+ Home-page: https://github.com/FaustRen/FB_graphql_scraper
6
+ Author: FaustRen
7
+ Author-email: Quang Nguyen <your.email@example.com>
8
+ License: MIT
9
+ Project-URL: Homepage, https://github.com/quangnguyen/facebook-graphql-scraper
10
+ Project-URL: Repository, https://github.com/quangnguyen/facebook-graphql-scraper
11
+ Project-URL: Issues, https://github.com/quangnguyen/facebook-graphql-scraper/issues
12
+ Keywords: facebook,scraper,graphql,social-media,crawler
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Internet :: WWW/HTTP
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Requires-Python: >=3.11
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: pandas>=2.0.0
27
+ Requires-Dist: requests>=2.28.0
28
+ Requires-Dist: pytz
29
+ Provides-Extra: dev
30
+ Requires-Dist: build>=0.8.0; extra == "dev"
31
+ Requires-Dist: twine>=4.0.0; extra == "dev"
32
+ Dynamic: author
33
+ Dynamic: home-page
34
+ Dynamic: license-file
35
+ Dynamic: requires-python
36
+
37
+ # Facebook GraphQL Scraper
38
+
39
+ Scrape public Facebook posts without login.
40
+
41
+ ## Install
42
+
43
+ ```bash
44
+ pip install fb-graphql-scraper-simple
45
+ ```
46
+
47
+ ## Usage
48
+
49
+ ```python
50
+ from facebook_graphql_scraper import FacebookGraphqlScraper
51
+
52
+ fb = FacebookGraphqlScraper()
53
+ result = fb.get_user_posts("vietgiaitri", days_limit=3)
54
+
55
+ for post in result["data"]:
56
+ print(post["context"])
57
+ print(f"Likes: {post['reaction_count.count']}")
58
+ ```
59
+
60
+ ## API
61
+
62
+ - `FacebookGraphqlScraper()` - Create scraper (no params)
63
+ - `get_user_posts(username_or_id, days_limit=61)` - Scrape posts
64
+
65
+ Returns dict with `data` (posts list), `profile` (name), `fb_username_or_userid`.
66
+
67
+ ## Requirements
68
+
69
+ Python 3.9+, pandas>=2.0.0, requests>=2.28.0
70
+
71
+ # Support Me
72
+
73
+ If you enjoy this project and would like to support me, please consider donating 🙌
74
+ Your support will help me continue developing this project and working on other exciting ideas!
75
+
76
+ ## 💖 Ways to Support:
77
+
78
+ - **PayPal**: [https://www.paypal.me/faustren1z](https://www.paypal.me/faustren1z)
79
+ - **Buy Me a Coffee**: [https://buymeacoffee.com/faustren1z](https://buymeacoffee.com/faustren1z)
80
+
81
+ Thank you for your support!! 🎉
82
+
83
+ ### Usage
84
+
85
+ You can choose between two methods to collect user posts data.
86
+ - **Pleas setup driver path at first**
87
+ - **Log in with your account credentials**: login facebook account
88
+ - **Without logging in**: Without logging in, click the X icon to
89
+ - **Difference**: The difference between these two methods is that for some personal accounts, you cannot browse the user's posts without logging into a Facebook account.
90
+
91
+ ```python
92
+ # -*- coding: utf-8 -*-
93
+ from fb_graphql_scraper.facebook_graphql_scraper import FacebookGraphqlScraper as fb_graphql_scraper
94
+
95
+
96
+ ## Example.1 - without logging in
97
+ if __name__ == "__main__":
98
+ facebook_user_name = "love.yuweishao"
99
+ facebook_user_id = "100044253168423"
100
+ days_limit = 100 # Number of days within which to scrape posts
101
+ driver_path = "/Users/hongshangren/Downloads/chromedriver-mac-arm64_136/chromedriver"
102
+ fb_spider = fb_graphql_scraper(driver_path=driver_path, open_browser=False)
103
+ res = fb_spider.get_user_posts(fb_username_or_userid=facebook_user_id, days_limit=days_limit,display_progress=True)
104
+ # print(res)
105
+
106
+
107
+ ## Example.2 - login in your facebook account to collect data
108
+ # if __name__ == "__main__":
109
+ # facebook_user_name = "love.yuweishao"
110
+ # facebook_user_id = "100044253168423"
111
+ # fb_account = "facebook_account"
112
+ # fb_pwd = "facebook_paswword"
113
+ # days_limit = 30 # Number of days within which to scrape posts
114
+ # driver_path = "/Users/hongshangren/Downloads/chromedriver-mac-arm64_136/chromedriver"
115
+ # fb_spider = fb_graphql_scraper(fb_account=fb_account,fb_pwd=fb_pwd, driver_path=driver_path, open_browser=False)
116
+ # res = fb_spider.get_user_posts(fb_username_or_userid=facebook_user_name, days_limit=days_limit,display_progress=True)
117
+ # print(res)
118
+
119
+
120
+
121
+ ```
122
+
123
+ ### Optional parameters
124
+
125
+ - **display_progress**:
126
+ A boolean value (`True` or `False`).
127
+ If set to `True`, the scraper will display how many days of posts remain to be collected based on your `days_limit`.
128
+ For example, if `days_limit=180`, it will scrape posts from today back to 180 days ago.
129
+ During the process, the remaining days will be printed and decrease gradually until reaching 0 or below, at which point scraping stops.
130
+ Example output:
131
+ `439 more days of posts to collect.`
132
+
133
+ - **open_browser**:
134
+ If set to `True`, the scraper will launch a browser window.
135
+ This allows login-based scraping (if `fb_account` and `fb_pwd` are provided), which may access more content.
136
+ However, this mode consumes more memory and **does not guarantee that your Facebook account will avoid being blocked**.
137
+ It is also useful for debugging if scraping fails or unexpected behavior occurs.
138
+
139
+ - **fb_username_or_userid**:
140
+ The Facebook Group ID, Fan Page ID, User ID, or User Name to scrape posts from.
141
+
142
+ - **days_limit**:
143
+ The number of days of posts to retrieve, counting backwards from today.
144
+
145
+ - **fb_account**:
146
+ Your Facebook account (Login-based scraping is still under maintenance.)
147
+
148
+ - **fb_pwd**:
149
+ Your Facebook account password (Login-based scraping is still under maintenance.)
150
+
151
+
152
+
153
+ ## Result example
154
+
155
+ ```python
156
+ {'fb_username_or_userid': '100044253168423',
157
+ 'profile': ['任何工作事宜請洽 高先生',
158
+ '聯絡信箱:hawa00328@gmail.com',
159
+ '聯絡電話:0975-386-266',
160
+ 'Page',
161
+ ' · 演員',
162
+ 'hawa00328@gmail.com',
163
+ 'Not yet rated (0 Reviews)',
164
+ '\ufeff',
165
+ '1,484,829 followers'],
166
+ 'data': [{'post_id': '1245565493595211',
167
+ 'post_url': 'https://www.facebook.com/1245565493595211',
168
+ 'username_or_userid': '100044253168423',
169
+ 'owing_profile': {'__typename': 'User',
170
+ 'name': '邵雨薇',
171
+ 'short_name': '邵雨薇',
172
+ 'id': '100044253168423'},
173
+ 'published_date': Timestamp('2025-05-09 09:14:42'),
174
+ 'published_date2': '2025-05-09',
175
+ 'time': 1746782082,
176
+ 'reaction_count.count': 3566,
177
+ 'comment_rendering_instance.comments.total_count': 55,
178
+ 'share_count.count': 13,
179
+ 'sub_reactions': {'讚': 3273, '大心': 283, '加油': 6, '哈': 2, '哇': 2},
180
+ 'context': '溫柔的大貓咪\n緬因貓~~~~~~\n好喜歡❤️❤️❤️',
181
+ 'video_view_count': None},
182
+ {'post_id': '1243688160449611',
183
+ 'post_url': 'https://www.facebook.com/1243688160449611',
184
+ 'username_or_userid': '100044253168423',
185
+ 'owing_profile': {'__typename': 'User',
186
+ 'name': '邵雨薇',
187
+ 'short_name': '邵雨薇',
188
+ 'id': '100044253168423'},
189
+ 'published_date': Timestamp('2025-05-06 12:38:46'),
190
+ 'published_date2': '2025-05-06',
191
+ 'time': 1746535126,
192
+ 'reaction_count.count': 3270,
193
+ 'comment_rendering_instance.comments.total_count': 59,
194
+ 'share_count.count': 22,
195
+ 'sub_reactions': {'讚': 2978, '大心': 282, '加油': 8, '哈': 2},
196
+ 'context': '💛',
197
+ 'video_view_count': None},
198
+ {'post_id': '1242879413863819',
199
+ 'post_url': 'https://www.facebook.com/1242879413863819',
200
+ 'username_or_userid': '100044253168423',
201
+ 'owing_profile': {'__typename': 'User',
202
+ 'name': '邵雨薇',
203
+ 'short_name': '邵雨薇',
204
+ 'id': '100044253168423'},
205
+ 'published_date': Timestamp('2025-05-05 10:02:32'),
206
+ 'published_date2': '2025-05-05',
207
+ 'time': 1746439352,
208
+ 'reaction_count.count': 3868,
209
+ 'comment_rendering_instance.comments.total_count': 55,
210
+ 'share_count.count': 28,
211
+ 'sub_reactions': {'讚': 3493, '大心': 362, '加油': 9, '哈': 3, '哇': 1},
212
+ 'context': '愛的表達方式有很多,\n真誠言語直接的愛、\n以行動表達溫度的愛,\n又或是充滿美麗魔法的愛! \n\n母親節就給媽媽一份加滿心意以及滿滿美麗的禮物吧!\n#潤姬桃子 的愛的魔法\n祝媽媽母親節快樂💗\n\n@uruhime.momoko.official',
213
+ 'video_view_count': None},
214
+ {'post_id': '1239140660904361',
215
+ 'post_url': 'https://www.facebook.com/1239140660904361',
216
+ 'username_or_userid': '100044253168423',
217
+ 'owing_profile': {'__typename': 'User',
218
+ 'name': '邵雨薇',
219
+ 'short_name': '邵雨薇',
220
+ 'id': '100044253168423'},
221
+ 'published_date': Timestamp('2025-04-30 09:01:18'),
222
+ 'published_date2': '2025-04-30',
223
+ 'time': 1746003678,
224
+ 'reaction_count.count': 3455,
225
+ 'comment_rendering_instance.comments.total_count': 42,
226
+ 'share_count.count': 12,
227
+ 'sub_reactions': {'讚': 3249, '大心': 199, '哈': 4, '加油': 2, '哇': 1},
228
+ 'context': '紐約碎片。\n\n沒注意到主人在,\n拍完往後轉抖了一大下。\n點點頭🙂\u200d↕️對了主人比個大拇指(意義不明?)',
229
+ 'video_view_count': None},
230
+ {'post_id': '1237090651109362',
231
+ 'post_url': 'https://www.facebook.com/1237090651109362',
232
+ 'username_or_userid': '100044253168423',
233
+ 'owing_profile': {'__typename': 'User',
234
+ 'name': '邵雨薇',
235
+ 'short_name': '邵雨薇',
236
+ 'id': '100044253168423'},
237
+ 'published_date': Timestamp('2025-04-27 12:56:19'),
238
+ 'published_date2': '2025-04-27',
239
+ 'time': 1745758579,
240
+ 'reaction_count.count': 4682,
241
+ 'comment_rendering_instance.comments.total_count': 25,
242
+ 'share_count.count': 12,
243
+ 'sub_reactions': {'讚': 4354, '大心': 311, '加油': 11, '哈': 5, '哇': 1},
244
+ 'context': '回家抱老迪 (請自動忽略阿爸)\n迪底撿回來也11年了,希望你也健康幸福。\n希望家人們都平安健康快樂。\n\n#迪底是阿筆的第一個兄弟',
245
+ 'video_view_count': None},
246
+ {'post_id': '1236471601171267',
247
+ 'post_url': 'https://www.facebook.com/1236471601171267',
248
+ 'username_or_userid': '100044253168423',
249
+ 'owing_profile': {'__typename': 'User',
250
+ 'name': '邵雨薇',
251
+ 'short_name': '邵雨薇',
252
+ 'id': '100044253168423'},
253
+ 'published_date': Timestamp('2025-04-26 16:23:29'),
254
+ 'published_date2': '2025-04-26',
255
+ 'time': 1745684609,
256
+ 'reaction_count.count': 3004,
257
+ 'comment_rendering_instance.comments.total_count': 41,
258
+ 'share_count.count': 13,
259
+ 'sub_reactions': {'讚': 2789, '大心': 210, '哈': 3, '加油': 2},
260
+ 'context': '剛在坐高鐵時,覺得時間實在是過得太快了。\n還來不及消化感受些什麼,轉頭又得先離開。\n一天當三天用確實感覺很精彩,\n但是不是錯過太多細節了呢? 晚安',
261
+ 'video_view_count': None},
262
+ {'post_id': '1235381784613582',
263
+ 'post_url': 'https://www.facebook.com/1235381784613582',
264
+ 'username_or_userid': '100044253168423',
265
+ 'owing_profile': {'__typename': 'User',
266
+ 'name': '邵雨薇',
267
+ 'short_name': '邵雨薇',
268
+ 'id': '100044253168423'},
269
+ 'published_date': Timestamp('2025-04-25 05:49:56'),
270
+ 'published_date2': '2025-04-25',
271
+ 'time': 1745560196,
272
+ 'reaction_count.count': 6846,
273
+ 'comment_rendering_instance.comments.total_count': 101,
274
+ 'share_count.count': 40,
275
+ 'sub_reactions': {'讚': 6405, '大心': 408, '加油': 19, '哈': 14},
276
+ 'context': '偶爾需要遇見一道彩虹,\n雨後剛轉天晴時,就像一個新希望。',
277
+ 'video_view_count': None}
278
+ ]
279
+ }
280
+ ```
281
+
282
+ ### Notes
283
+ - If you choose to collect data by logging into your account, you may face the risk of your account being blocked, even if this program only scrolls through Facebook web pages.
284
+ - Reaction Categories (EN): [`like`, `haha`, `angry`, `love`, `care`, `wow`, `sad`]
285
+ - Reaction Categories (TW): [`讚`, `哈`, `怒`, `大心`, `加油`, `哇`, `嗚`]
286
+
287
+
288
+ ```python
289
+
290
+ ## To-Do
291
+
292
+ - Login-based scraping
@@ -0,0 +1,256 @@
1
+ # Facebook GraphQL Scraper
2
+
3
+ Scrape public Facebook posts without login.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install fb-graphql-scraper-simple
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```python
14
+ from facebook_graphql_scraper import FacebookGraphqlScraper
15
+
16
+ fb = FacebookGraphqlScraper()
17
+ result = fb.get_user_posts("vietgiaitri", days_limit=3)
18
+
19
+ for post in result["data"]:
20
+ print(post["context"])
21
+ print(f"Likes: {post['reaction_count.count']}")
22
+ ```
23
+
24
+ ## API
25
+
26
+ - `FacebookGraphqlScraper()` - Create scraper (no params)
27
+ - `get_user_posts(username_or_id, days_limit=61)` - Scrape posts
28
+
29
+ Returns dict with `data` (posts list), `profile` (name), `fb_username_or_userid`.
30
+
31
+ ## Requirements
32
+
33
+ Python 3.9+, pandas>=2.0.0, requests>=2.28.0
34
+
35
+ # Support Me
36
+
37
+ If you enjoy this project and would like to support me, please consider donating 🙌
38
+ Your support will help me continue developing this project and working on other exciting ideas!
39
+
40
+ ## 💖 Ways to Support:
41
+
42
+ - **PayPal**: [https://www.paypal.me/faustren1z](https://www.paypal.me/faustren1z)
43
+ - **Buy Me a Coffee**: [https://buymeacoffee.com/faustren1z](https://buymeacoffee.com/faustren1z)
44
+
45
+ Thank you for your support!! 🎉
46
+
47
+ ### Usage
48
+
49
+ You can choose between two methods to collect user posts data.
50
+ - **Pleas setup driver path at first**
51
+ - **Log in with your account credentials**: login facebook account
52
+ - **Without logging in**: Without logging in, click the X icon to
53
+ - **Difference**: The difference between these two methods is that for some personal accounts, you cannot browse the user's posts without logging into a Facebook account.
54
+
55
+ ```python
56
+ # -*- coding: utf-8 -*-
57
+ from fb_graphql_scraper.facebook_graphql_scraper import FacebookGraphqlScraper as fb_graphql_scraper
58
+
59
+
60
+ ## Example.1 - without logging in
61
+ if __name__ == "__main__":
62
+ facebook_user_name = "love.yuweishao"
63
+ facebook_user_id = "100044253168423"
64
+ days_limit = 100 # Number of days within which to scrape posts
65
+ driver_path = "/Users/hongshangren/Downloads/chromedriver-mac-arm64_136/chromedriver"
66
+ fb_spider = fb_graphql_scraper(driver_path=driver_path, open_browser=False)
67
+ res = fb_spider.get_user_posts(fb_username_or_userid=facebook_user_id, days_limit=days_limit,display_progress=True)
68
+ # print(res)
69
+
70
+
71
+ ## Example.2 - login in your facebook account to collect data
72
+ # if __name__ == "__main__":
73
+ # facebook_user_name = "love.yuweishao"
74
+ # facebook_user_id = "100044253168423"
75
+ # fb_account = "facebook_account"
76
+ # fb_pwd = "facebook_paswword"
77
+ # days_limit = 30 # Number of days within which to scrape posts
78
+ # driver_path = "/Users/hongshangren/Downloads/chromedriver-mac-arm64_136/chromedriver"
79
+ # fb_spider = fb_graphql_scraper(fb_account=fb_account,fb_pwd=fb_pwd, driver_path=driver_path, open_browser=False)
80
+ # res = fb_spider.get_user_posts(fb_username_or_userid=facebook_user_name, days_limit=days_limit,display_progress=True)
81
+ # print(res)
82
+
83
+
84
+
85
+ ```
86
+
87
+ ### Optional parameters
88
+
89
+ - **display_progress**:
90
+ A boolean value (`True` or `False`).
91
+ If set to `True`, the scraper will display how many days of posts remain to be collected based on your `days_limit`.
92
+ For example, if `days_limit=180`, it will scrape posts from today back to 180 days ago.
93
+ During the process, the remaining days will be printed and decrease gradually until reaching 0 or below, at which point scraping stops.
94
+ Example output:
95
+ `439 more days of posts to collect.`
96
+
97
+ - **open_browser**:
98
+ If set to `True`, the scraper will launch a browser window.
99
+ This allows login-based scraping (if `fb_account` and `fb_pwd` are provided), which may access more content.
100
+ However, this mode consumes more memory and **does not guarantee that your Facebook account will avoid being blocked**.
101
+ It is also useful for debugging if scraping fails or unexpected behavior occurs.
102
+
103
+ - **fb_username_or_userid**:
104
+ The Facebook Group ID, Fan Page ID, User ID, or User Name to scrape posts from.
105
+
106
+ - **days_limit**:
107
+ The number of days of posts to retrieve, counting backwards from today.
108
+
109
+ - **fb_account**:
110
+ Your Facebook account (Login-based scraping is still under maintenance.)
111
+
112
+ - **fb_pwd**:
113
+ Your Facebook account password (Login-based scraping is still under maintenance.)
114
+
115
+
116
+
117
+ ## Result example
118
+
119
+ ```python
120
+ {'fb_username_or_userid': '100044253168423',
121
+ 'profile': ['任何工作事宜請洽 高先生',
122
+ '聯絡信箱:hawa00328@gmail.com',
123
+ '聯絡電話:0975-386-266',
124
+ 'Page',
125
+ ' · 演員',
126
+ 'hawa00328@gmail.com',
127
+ 'Not yet rated (0 Reviews)',
128
+ '\ufeff',
129
+ '1,484,829 followers'],
130
+ 'data': [{'post_id': '1245565493595211',
131
+ 'post_url': 'https://www.facebook.com/1245565493595211',
132
+ 'username_or_userid': '100044253168423',
133
+ 'owing_profile': {'__typename': 'User',
134
+ 'name': '邵雨薇',
135
+ 'short_name': '邵雨薇',
136
+ 'id': '100044253168423'},
137
+ 'published_date': Timestamp('2025-05-09 09:14:42'),
138
+ 'published_date2': '2025-05-09',
139
+ 'time': 1746782082,
140
+ 'reaction_count.count': 3566,
141
+ 'comment_rendering_instance.comments.total_count': 55,
142
+ 'share_count.count': 13,
143
+ 'sub_reactions': {'讚': 3273, '大心': 283, '加油': 6, '哈': 2, '哇': 2},
144
+ 'context': '溫柔的大貓咪\n緬因貓~~~~~~\n好喜歡❤️❤️❤️',
145
+ 'video_view_count': None},
146
+ {'post_id': '1243688160449611',
147
+ 'post_url': 'https://www.facebook.com/1243688160449611',
148
+ 'username_or_userid': '100044253168423',
149
+ 'owing_profile': {'__typename': 'User',
150
+ 'name': '邵雨薇',
151
+ 'short_name': '邵雨薇',
152
+ 'id': '100044253168423'},
153
+ 'published_date': Timestamp('2025-05-06 12:38:46'),
154
+ 'published_date2': '2025-05-06',
155
+ 'time': 1746535126,
156
+ 'reaction_count.count': 3270,
157
+ 'comment_rendering_instance.comments.total_count': 59,
158
+ 'share_count.count': 22,
159
+ 'sub_reactions': {'讚': 2978, '大心': 282, '加油': 8, '哈': 2},
160
+ 'context': '💛',
161
+ 'video_view_count': None},
162
+ {'post_id': '1242879413863819',
163
+ 'post_url': 'https://www.facebook.com/1242879413863819',
164
+ 'username_or_userid': '100044253168423',
165
+ 'owing_profile': {'__typename': 'User',
166
+ 'name': '邵雨薇',
167
+ 'short_name': '邵雨薇',
168
+ 'id': '100044253168423'},
169
+ 'published_date': Timestamp('2025-05-05 10:02:32'),
170
+ 'published_date2': '2025-05-05',
171
+ 'time': 1746439352,
172
+ 'reaction_count.count': 3868,
173
+ 'comment_rendering_instance.comments.total_count': 55,
174
+ 'share_count.count': 28,
175
+ 'sub_reactions': {'讚': 3493, '大心': 362, '加油': 9, '哈': 3, '哇': 1},
176
+ 'context': '愛的表達方式有很多,\n真誠言語直接的愛、\n以行動表達溫度的愛,\n又或是充滿美麗魔法的愛! \n\n母親節就給媽媽一份加滿心意以及滿滿美麗的禮物吧!\n#潤姬桃子 的愛的魔法\n祝媽媽母親節快樂💗\n\n@uruhime.momoko.official',
177
+ 'video_view_count': None},
178
+ {'post_id': '1239140660904361',
179
+ 'post_url': 'https://www.facebook.com/1239140660904361',
180
+ 'username_or_userid': '100044253168423',
181
+ 'owing_profile': {'__typename': 'User',
182
+ 'name': '邵雨薇',
183
+ 'short_name': '邵雨薇',
184
+ 'id': '100044253168423'},
185
+ 'published_date': Timestamp('2025-04-30 09:01:18'),
186
+ 'published_date2': '2025-04-30',
187
+ 'time': 1746003678,
188
+ 'reaction_count.count': 3455,
189
+ 'comment_rendering_instance.comments.total_count': 42,
190
+ 'share_count.count': 12,
191
+ 'sub_reactions': {'讚': 3249, '大心': 199, '哈': 4, '加油': 2, '哇': 1},
192
+ 'context': '紐約碎片。\n\n沒注意到主人在,\n拍完往後轉抖了一大下。\n點點頭🙂\u200d↕️對了主人比個大拇指(意義不明?)',
193
+ 'video_view_count': None},
194
+ {'post_id': '1237090651109362',
195
+ 'post_url': 'https://www.facebook.com/1237090651109362',
196
+ 'username_or_userid': '100044253168423',
197
+ 'owing_profile': {'__typename': 'User',
198
+ 'name': '邵雨薇',
199
+ 'short_name': '邵雨薇',
200
+ 'id': '100044253168423'},
201
+ 'published_date': Timestamp('2025-04-27 12:56:19'),
202
+ 'published_date2': '2025-04-27',
203
+ 'time': 1745758579,
204
+ 'reaction_count.count': 4682,
205
+ 'comment_rendering_instance.comments.total_count': 25,
206
+ 'share_count.count': 12,
207
+ 'sub_reactions': {'讚': 4354, '大心': 311, '加油': 11, '哈': 5, '哇': 1},
208
+ 'context': '回家抱老迪 (請自動忽略阿爸)\n迪底撿回來也11年了,希望你也健康幸福。\n希望家人們都平安健康快樂。\n\n#迪底是阿筆的第一個兄弟',
209
+ 'video_view_count': None},
210
+ {'post_id': '1236471601171267',
211
+ 'post_url': 'https://www.facebook.com/1236471601171267',
212
+ 'username_or_userid': '100044253168423',
213
+ 'owing_profile': {'__typename': 'User',
214
+ 'name': '邵雨薇',
215
+ 'short_name': '邵雨薇',
216
+ 'id': '100044253168423'},
217
+ 'published_date': Timestamp('2025-04-26 16:23:29'),
218
+ 'published_date2': '2025-04-26',
219
+ 'time': 1745684609,
220
+ 'reaction_count.count': 3004,
221
+ 'comment_rendering_instance.comments.total_count': 41,
222
+ 'share_count.count': 13,
223
+ 'sub_reactions': {'讚': 2789, '大心': 210, '哈': 3, '加油': 2},
224
+ 'context': '剛在坐高鐵時,覺得時間實在是過得太快了。\n還來不及消化感受些什麼,轉頭又得先離開。\n一天當三天用確實感覺很精彩,\n但是不是錯過太多細節了呢? 晚安',
225
+ 'video_view_count': None},
226
+ {'post_id': '1235381784613582',
227
+ 'post_url': 'https://www.facebook.com/1235381784613582',
228
+ 'username_or_userid': '100044253168423',
229
+ 'owing_profile': {'__typename': 'User',
230
+ 'name': '邵雨薇',
231
+ 'short_name': '邵雨薇',
232
+ 'id': '100044253168423'},
233
+ 'published_date': Timestamp('2025-04-25 05:49:56'),
234
+ 'published_date2': '2025-04-25',
235
+ 'time': 1745560196,
236
+ 'reaction_count.count': 6846,
237
+ 'comment_rendering_instance.comments.total_count': 101,
238
+ 'share_count.count': 40,
239
+ 'sub_reactions': {'讚': 6405, '大心': 408, '加油': 19, '哈': 14},
240
+ 'context': '偶爾需要遇見一道彩虹,\n雨後剛轉天晴時,就像一個新希望。',
241
+ 'video_view_count': None}
242
+ ]
243
+ }
244
+ ```
245
+
246
+ ### Notes
247
+ - If you choose to collect data by logging into your account, you may face the risk of your account being blocked, even if this program only scrolls through Facebook web pages.
248
+ - Reaction Categories (EN): [`like`, `haha`, `angry`, `love`, `care`, `wow`, `sad`]
249
+ - Reaction Categories (TW): [`讚`, `哈`, `怒`, `大心`, `加油`, `哇`, `嗚`]
250
+
251
+
252
+ ```python
253
+
254
+ ## To-Do
255
+
256
+ - Login-based scraping
@@ -0,0 +1,14 @@
1
+ """Facebook GraphQL Scraper - Simple API to scrape public Facebook posts without login.
2
+
3
+ Example:
4
+ from facebook_graphql_scraper import FacebookGraphqlScraper
5
+
6
+ fb = FacebookGraphqlScraper()
7
+ result = fb.get_user_posts("vietgiaitri", days_limit=3)
8
+ print(result["data"])
9
+ """
10
+
11
+ from .facebook_graphql_scraper import FacebookGraphqlScraper
12
+
13
+ __version__ = "0.2.0"
14
+ __all__ = ["FacebookGraphqlScraper"]
@@ -0,0 +1,90 @@
1
+ # -*- coding:utf-8 -*-
2
+ from selenium.webdriver.chrome.options import Options as ChromeOptions
3
+ from selenium.webdriver.chrome.service import Service
4
+
5
+
6
+ class BasePage:
7
+ def __init__(self, driver_path: str, open_browser: bool = False):
8
+ chrome_options = self._build_options(open_browser)
9
+ normalized_driver_path = self._normalize_path(driver_path)
10
+
11
+ if self._looks_like_chrome_binary(normalized_driver_path):
12
+ chrome_options.binary_location = normalized_driver_path
13
+ service = Service()
14
+ elif normalized_driver_path:
15
+ service = Service(normalized_driver_path)
16
+ else:
17
+ service = Service()
18
+
19
+ self.driver = self._build_driver(service=service, chrome_options=chrome_options)
20
+ self.driver.maximize_window()
21
+
22
+ @staticmethod
23
+ def _build_options(open_browser: bool) -> ChromeOptions:
24
+ options = ChromeOptions()
25
+ options.add_argument("--disable-blink-features")
26
+ options.add_argument("--disable-notifications")
27
+ options.add_argument("--disable-blink-features=AutomationControlled")
28
+ if not open_browser:
29
+ options.add_argument("--headless=new")
30
+ options.add_argument("--blink-settings=imagesEnabled=false")
31
+ return options
32
+
33
+ @staticmethod
34
+ def _normalize_path(path: str | None) -> str | None:
35
+ if not path:
36
+ return None
37
+ # In notebook strings users often escape spaces (e.g. "Google\ Chrome").
38
+ return path.replace("\\ ", " ").strip()
39
+
40
+ @staticmethod
41
+ def _looks_like_chrome_binary(path: str | None) -> bool:
42
+ if not path:
43
+ return False
44
+ normalized = path.lower()
45
+ return normalized.endswith("/google chrome") or normalized.endswith("/chrome")
46
+
47
+ @staticmethod
48
+ def _build_driver(service: Service, chrome_options: ChromeOptions):
49
+ from seleniumwire import webdriver
50
+
51
+ try:
52
+ return webdriver.Chrome(service=service, options=chrome_options)
53
+ except AttributeError as exc:
54
+ if "VERSION_CHOICES" not in str(exc):
55
+ raise
56
+ # Some selenium-wire/pyOpenSSL combinations expose this at runtime.
57
+ BasePage._patch_seleniumwire_tls_version_choices()
58
+ return webdriver.Chrome(service=service, options=chrome_options)
59
+
60
+ @staticmethod
61
+ def _patch_seleniumwire_tls_version_choices() -> None:
62
+ from OpenSSL import SSL
63
+ from seleniumwire.thirdparty.mitmproxy.net import tls
64
+
65
+ if hasattr(tls, "VERSION_CHOICES"):
66
+ return
67
+
68
+ basic_options = SSL.OP_CIPHER_SERVER_PREFERENCE
69
+ if hasattr(SSL, "OP_NO_COMPRESSION"):
70
+ basic_options |= SSL.OP_NO_COMPRESSION
71
+
72
+ default_method = getattr(SSL, "SSLv23_METHOD", getattr(SSL, "TLS_METHOD", None))
73
+ default_options = basic_options
74
+ if hasattr(SSL, "OP_NO_SSLv2"):
75
+ default_options |= SSL.OP_NO_SSLv2
76
+ if hasattr(SSL, "OP_NO_SSLv3"):
77
+ default_options |= SSL.OP_NO_SSLv3
78
+
79
+ version_choices = {
80
+ "all": (default_method, basic_options),
81
+ "secure": (default_method, default_options),
82
+ }
83
+
84
+ for name in ("TLSv1", "TLSv1_1", "TLSv1_2"):
85
+ method_name = f"{name}_METHOD"
86
+ method = getattr(SSL, method_name, None)
87
+ if method is not None:
88
+ version_choices[name] = (method, basic_options)
89
+
90
+ tls.VERSION_CHOICES = version_choices