instagram-posts-scraper 0.0.1__tar.gz → 0.0.2__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.
Files changed (21) hide show
  1. instagram_posts_scraper-0.0.2/PKG-INFO +81 -0
  2. {instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/instagram_posts_scraper/example.py +1 -1
  3. {instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/instagram_posts_scraper/file_operation.py +0 -9
  4. instagram_posts_scraper-0.0.2/instagram_posts_scraper/instagram_posts_scraper.py +263 -0
  5. instagram_posts_scraper-0.0.2/instagram_posts_scraper.egg-info/PKG-INFO +81 -0
  6. {instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/setup.py +2 -2
  7. instagram_posts_scraper-0.0.1/PKG-INFO +0 -104
  8. instagram_posts_scraper-0.0.1/instagram_posts_scraper/instagram_posts_scraper.py +0 -160
  9. instagram_posts_scraper-0.0.1/instagram_posts_scraper.egg-info/PKG-INFO +0 -104
  10. {instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/LICENSE +0 -0
  11. {instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/instagram_posts_scraper/__init__.py +0 -0
  12. {instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/instagram_posts_scraper/parse.py +0 -0
  13. {instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/instagram_posts_scraper/request.py +0 -0
  14. {instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/instagram_posts_scraper/scraper.py +0 -0
  15. {instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/instagram_posts_scraper/utils/__init__.py +0 -0
  16. {instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/instagram_posts_scraper/utils/utils.py +0 -0
  17. {instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/instagram_posts_scraper/utils.py +0 -0
  18. {instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/instagram_posts_scraper.egg-info/SOURCES.txt +0 -0
  19. {instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/instagram_posts_scraper.egg-info/dependency_links.txt +0 -0
  20. {instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/instagram_posts_scraper.egg-info/top_level.txt +0 -0
  21. {instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/setup.cfg +0 -0
@@ -0,0 +1,81 @@
1
+ Metadata-Version: 2.2
2
+ Name: instagram-posts-scraper
3
+ Version: 0.0.2
4
+ Summary: Implement Instagram Posts Scraper for post data retrieval
5
+ Home-page: https://github.com/FaustRen/instagram-posts-scraper
6
+ Author: FaustRen
7
+ Author-email: faustren1z@gmail.com
8
+ License: MIT
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.11
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Dynamic: author
16
+ Dynamic: author-email
17
+ Dynamic: classifier
18
+ Dynamic: description
19
+ Dynamic: description-content-type
20
+ Dynamic: home-page
21
+ Dynamic: license
22
+ Dynamic: requires-python
23
+ Dynamic: summary
24
+
25
+ # Instagram Posts Scraper
26
+
27
+ InstagramPostsScraper is a Python library for collect instagram users' data.
28
+
29
+ The data obtained by web crawlers is not real-time data, but rather data from a specific point in time on the same day.
30
+
31
+ I’d really appreciate your support! You can star ⭐ or fork this repository to help me keep sharing more interesting web scrapers.
32
+
33
+ # Support Me
34
+
35
+ If you enjoy this project and would like to support me, please consider donating 🙌
36
+ Your support will help me continue developing this project and working on other exciting ideas!
37
+
38
+ ## 💖 Ways to Support:
39
+
40
+ - **PayPal**: [https://www.paypal.me/faustren1z](https://www.paypal.me/faustren1z)
41
+ - **Buy Me a Coffee**: [https://buymeacoffee.com/faustren1z](https://buymeacoffee.com/faustren1z)
42
+
43
+ Thank you for your support!! 🎉
44
+
45
+
46
+ ## Requirements
47
+ ```bash
48
+ beautifulsoup4==4.13.3
49
+ cloudscraper==1.2.71
50
+ pandas==2.2.3
51
+ pytz==2025.1
52
+ ```
53
+
54
+ ## Installation
55
+
56
+ To install the latest release from PyPI:
57
+
58
+ ```sh
59
+ pip install instagram-posts-scraper
60
+ ```
61
+
62
+ ## Usage - Sample
63
+
64
+ ```python
65
+ from instagram_posts_scraper.instagram_posts_scraper import InstaPeriodScraper
66
+
67
+
68
+ target_info = {"username": "kaicenat", "days_limit":60}
69
+ ig_posts_scraper = InstaPeriodScraper()
70
+ res = ig_posts_scraper.get_posts(target_info=target_info)
71
+ ```
72
+
73
+ ### Optional parameters
74
+
75
+ - **username**: target instagram user
76
+ - **days_limit**: Number of days within which to scrape posts..
77
+
78
+ ## Sample(Usage & Result) - KaiCenat
79
+ ![image](https://github.com/FaustRen/instagram-posts-scraper/blob/main/usage.png)
80
+
81
+ ![image](https://github.com/FaustRen/instagram-posts-scraper/blob/main/scraped_posts.png)
@@ -3,7 +3,7 @@
3
3
  from instagram_posts_scraper.instagram_posts_scraper import InstaPeriodScraper
4
4
 
5
5
 
6
- target_info = {"username": "joemanweng", "days_limit":5}
6
+ target_info = {"username": "kaicenat", "days_limit":5}
7
7
  ig_posts_scraper = InstaPeriodScraper()
8
8
  res = ig_posts_scraper.get_posts(target_info=target_info)
9
9
  res
@@ -17,12 +17,3 @@ class JsonOperation:
17
17
  with open(current_directory, 'r') as file:
18
18
  result = json.load(file)
19
19
  return result
20
-
21
-
22
-
23
- class CsvOperation:
24
- @staticmethod
25
- def read_kol_csv():
26
- current_directory = os.path.dirname(__file__) + "/resources/new_input_scraper.csv"
27
- result = pd.read_csv(current_directory)
28
- return result
@@ -0,0 +1,263 @@
1
+ # -*- coding: utf-8 -*-
2
+ from instagram_posts_scraper.request import *
3
+ from instagram_posts_scraper.parse import *
4
+ from instagram_posts_scraper.utils import *
5
+ from instagram_posts_scraper.scraper import *
6
+ from instagram_posts_scraper.utils.utils import *
7
+ from instagram_posts_scraper.file_operation import *
8
+ from datetime import datetime
9
+
10
+
11
+ class ScrapedDataManager(object):
12
+ def __init__(self):
13
+ pass
14
+
15
+
16
+ class InstaPeriodScraper(object):
17
+ def __init__(self) -> None:
18
+ self.pixwox_request = PixwoxRequest()
19
+ self.parser=Parser()
20
+ self.api_parser=ApiParser()
21
+ self.scraper=Scraper(
22
+ pixwox_request=self.pixwox_request,
23
+ parser=self.parser,
24
+ api_parser=self.api_parser
25
+ )
26
+
27
+ def check_account_is_public(self):
28
+ init_response = self.pixwox_request.send_requests(url=self.scraper.init_url)
29
+ self.profile_soup = self.parser.get_soup(response=init_response)
30
+ self.userid = self.parser.get_userid(profile_soup=self.profile_soup)
31
+ self.account_status = get_account_status(userid=self.userid, profile_soup=self.profile_soup)
32
+ return self.account_status == "public"
33
+
34
+ def get_profile(self):
35
+ self.followings = self.parser.get_followings(self.profile_soup)
36
+ self.followers = self.parser.get_followers(self.profile_soup)
37
+ self.counts_of_posts = self.parser.get_counts_of_posts(self.profile_soup)
38
+ try:
39
+ self.introduction = self.parser.get_introduction(self.profile_soup)
40
+ except:
41
+ self.introduction = None
42
+
43
+ self.profile_info = {
44
+ "introduction": self.introduction,
45
+ "counts_of_posts": self.counts_of_posts,
46
+ "followers": self.followers,
47
+ "followings": self.followings,
48
+ }
49
+
50
+ def get_init_api_data(self):
51
+ init_api_data = self.scraper.get_init_api_data(userid=self.userid)
52
+ return init_api_data
53
+
54
+ def get_next_api_data(self, next_maxid:str, next_:str,username:str):
55
+ next_api_data = self.scraper.get_next_api_data(userid=self.userid, next_maxid=next_maxid, next_=next_, username=username)
56
+ return next_api_data
57
+
58
+ def get_private_account_res(self):
59
+ res = {
60
+ "profile":{
61
+ "userid":self.userid,
62
+ "username":self.target_info["username"],
63
+ "followers":self.followers,
64
+ "followings":self.followings,
65
+ "counts_of_posts":self.counts_of_posts,
66
+ "introduction":self.introduction
67
+ },
68
+ "account_status":self.account_status,
69
+ "updated_at": get_current_time(timezone="Asia/Taipei"),
70
+ "data":[]
71
+ }
72
+ return res
73
+
74
+ def get_missing_account_res(self):
75
+ res = {
76
+ "profile":{
77
+ "userid":None,
78
+ "username":self.target_info["username"],
79
+ "followers":None,
80
+ "followings":None,
81
+ "counts_of_posts":None,
82
+ "introduction":None
83
+ },
84
+ "account_status":self.account_status,
85
+ "updated_at": get_current_time(timezone="Asia/Taipei"),
86
+ "data":[]
87
+ }
88
+ return res
89
+
90
+ def get_public_account_res(self, scraped_posts, init_api_data):
91
+ res = {
92
+ "profile": self.profile_info,
93
+ "account_status":self.account_status,
94
+ "updated_at": get_current_time(timezone="Asia/Taipei"),
95
+ "data":scraped_posts}
96
+ return res
97
+
98
+ # @timeout(300)
99
+ def get_period_data(self, days_limit: int, init_maxid: str, init_api_data: dict, username: str) -> list:
100
+ """
101
+ Fetches social media posts within specified days with retry mechanism and deduplication.
102
+
103
+ Implements pagination handling with error resilience and detailed logging for Apify platform.
104
+
105
+ Args:
106
+ days_limit: Maximum number of pagination rounds to attempt
107
+ init_maxid: Initial pagination identifier
108
+ init_api_data: Initial API response data
109
+ username: Target account username
110
+
111
+ Returns:
112
+ list: Deduplicated list of post items
113
+
114
+ Raises:
115
+ ValueError: If initial API data structure is invalid
116
+ """
117
+ # Validate initial API structure
118
+ if 'posts' not in init_api_data or 'items' not in init_api_data['posts']:
119
+ raise ValueError("Invalid initial API data structure")
120
+
121
+ # Initialize data containers
122
+ scraped_posts_res = init_api_data.get('posts', {}).get('items', [])[:] # Create list copy
123
+ next_metadata = {
124
+ 'maxid': init_api_data.get('posts', {}).get('maxid', init_maxid),
125
+ 'next': init_api_data.get('posts', {}).get('next', 0),
126
+ 'has_next': init_api_data.get('posts', {}).get('has_next', False)
127
+ }
128
+
129
+ # Deduplication setup
130
+ processed_shortcodes = {item['shortcode'] for item in scraped_posts_res if 'shortcode' in item}
131
+
132
+ rounds = 0
133
+ MAX_RETRIES = 3
134
+ RETRY_DELAY = 3
135
+
136
+ while rounds < days_limit:
137
+ retry_count = 0
138
+ success = False
139
+
140
+ while retry_count < MAX_RETRIES and not success:
141
+ try:
142
+ # Fetch next page data
143
+ next_api_data = self.get_next_api_data(
144
+ next_maxid=next_metadata['maxid'],
145
+ next_=next_metadata['next'],
146
+ username=username
147
+ )
148
+
149
+ # Process API response
150
+ posts_data = next_api_data.get('posts', {})
151
+ new_items = posts_data.get('items', [])
152
+
153
+ # Validate data format
154
+ if not isinstance(new_items, list):
155
+ new_items = []
156
+ current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
157
+ print(f"{current_time} | WARNING | {self.__class__.__name__}(get_period_data) - "
158
+ f"Invalid data format at round {rounds}, resetting to empty list")
159
+
160
+ # Deduplication processing
161
+ new_items_deduplicated = [
162
+ item for item in new_items
163
+ if 'shortcode' in item and item['shortcode'] not in processed_shortcodes
164
+ ]
165
+ scraped_posts_res.extend(new_items_deduplicated)
166
+ processed_shortcodes.update(item['shortcode'] for item in new_items_deduplicated)
167
+
168
+ # Termination conditions
169
+ termination_conditions = [
170
+ has_all_data_been_collected(scraped_posts_res, self.counts_of_posts),
171
+ is_date_exceed_half_year(new_items_deduplicated, days_limit),
172
+ len(new_items_deduplicated) == 0 # No new data
173
+ ]
174
+ if any(termination_conditions):
175
+ return scraped_posts_res
176
+
177
+ # Update pagination parameters
178
+ next_metadata = {
179
+ 'maxid': posts_data.get('maxid', ''),
180
+ 'next': posts_data.get('next', 0),
181
+ 'has_next': posts_data.get('has_next', False)
182
+ }
183
+
184
+ if not next_metadata['has_next']:
185
+ return scraped_posts_res
186
+
187
+ success = True
188
+ rounds += 1
189
+
190
+ except Exception as e:
191
+ retry_count += 1
192
+ current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
193
+
194
+ ## display the entire failed details
195
+ # error_type = type(e).__name__
196
+ # # Structured error logging
197
+ # print(f"\n{current_time} | ERROR | {self.__class__.__name__}(get_period_data) - "
198
+ # f"Round {rounds} Attempt {retry_count}/{MAX_RETRIES} failed\n"
199
+ # f"Error Type: {error_type}\n"
200
+ # f"Message: {str(e)}\n"
201
+ # "Traceback Details:")
202
+
203
+ # # Detailed traceback printing
204
+ # traceback_str = traceback.format_exc()
205
+ # print(f"{'-'*100}\n{traceback_str}\n{'-'*100}\n")
206
+
207
+ ## display failed resone
208
+ # print(f"[第 {rounds} 輪] 第 {retry_count} 次重試失敗,原因: {str(e)}")
209
+
210
+ ## display retry rounds
211
+ print(f"Instagram-posts-scraper Round {retry_count} retry.")
212
+
213
+ time.sleep(RETRY_DELAY * retry_count) # Progressive backoff
214
+
215
+ if not success:
216
+ current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
217
+ # print(f"\n{current_time} | CRITICAL | {self.__class__.__name__}(get_period_data) - "
218
+ # f"Round {rounds} failed after {MAX_RETRIES} retries\n"
219
+ # f"{'='*100}\n")
220
+ rounds += 1
221
+
222
+ return scraped_posts_res
223
+
224
+ def get_posts(self, target_info:dict):
225
+ self.target_info = target_info
226
+ self.username = self.target_info["username"]
227
+ username = self.target_info["username"]
228
+ self.scraper.set_username(username)
229
+ days_limit = target_info["days_limit"]
230
+ if not self.check_account_is_public():
231
+ print("This is private account")
232
+ if self.account_status == "private":
233
+ self.get_profile()
234
+ res = self.get_private_account_res()
235
+ return res
236
+ elif self.account_status == "missing":
237
+ res = self.get_missing_account_res()
238
+ return res
239
+
240
+ if self.check_account_is_public():
241
+ init_api_data = self.get_init_api_data() # 帳號資訊 & 上方頁面內容
242
+ self.get_profile()
243
+ print(f"This is public account")
244
+ # can scrape next round's posts
245
+ if init_api_data["posts"]["has_next"] != False:
246
+ maxid = init_api_data["posts"]["maxid"]
247
+ period_posts = self.get_period_data(
248
+ init_maxid=maxid,
249
+ days_limit=days_limit,
250
+ init_api_data=init_api_data,
251
+ username=username
252
+ )
253
+
254
+ # return period_posts
255
+ res = self.get_public_account_res(
256
+ scraped_posts=period_posts,
257
+ init_api_data=init_api_data
258
+ )
259
+ return res
260
+ # # no more posts
261
+ elif init_api_data["posts"]["has_next"] == False: # (表示該帳號貼文數<=12, 無法繼續往下找)
262
+ res = self.get_public_account_res(scraped_posts=init_api_data["posts"]["items"], init_api_data=init_api_data)
263
+ return res
@@ -0,0 +1,81 @@
1
+ Metadata-Version: 2.2
2
+ Name: instagram-posts-scraper
3
+ Version: 0.0.2
4
+ Summary: Implement Instagram Posts Scraper for post data retrieval
5
+ Home-page: https://github.com/FaustRen/instagram-posts-scraper
6
+ Author: FaustRen
7
+ Author-email: faustren1z@gmail.com
8
+ License: MIT
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.11
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Dynamic: author
16
+ Dynamic: author-email
17
+ Dynamic: classifier
18
+ Dynamic: description
19
+ Dynamic: description-content-type
20
+ Dynamic: home-page
21
+ Dynamic: license
22
+ Dynamic: requires-python
23
+ Dynamic: summary
24
+
25
+ # Instagram Posts Scraper
26
+
27
+ InstagramPostsScraper is a Python library for collect instagram users' data.
28
+
29
+ The data obtained by web crawlers is not real-time data, but rather data from a specific point in time on the same day.
30
+
31
+ I’d really appreciate your support! You can star ⭐ or fork this repository to help me keep sharing more interesting web scrapers.
32
+
33
+ # Support Me
34
+
35
+ If you enjoy this project and would like to support me, please consider donating 🙌
36
+ Your support will help me continue developing this project and working on other exciting ideas!
37
+
38
+ ## 💖 Ways to Support:
39
+
40
+ - **PayPal**: [https://www.paypal.me/faustren1z](https://www.paypal.me/faustren1z)
41
+ - **Buy Me a Coffee**: [https://buymeacoffee.com/faustren1z](https://buymeacoffee.com/faustren1z)
42
+
43
+ Thank you for your support!! 🎉
44
+
45
+
46
+ ## Requirements
47
+ ```bash
48
+ beautifulsoup4==4.13.3
49
+ cloudscraper==1.2.71
50
+ pandas==2.2.3
51
+ pytz==2025.1
52
+ ```
53
+
54
+ ## Installation
55
+
56
+ To install the latest release from PyPI:
57
+
58
+ ```sh
59
+ pip install instagram-posts-scraper
60
+ ```
61
+
62
+ ## Usage - Sample
63
+
64
+ ```python
65
+ from instagram_posts_scraper.instagram_posts_scraper import InstaPeriodScraper
66
+
67
+
68
+ target_info = {"username": "kaicenat", "days_limit":60}
69
+ ig_posts_scraper = InstaPeriodScraper()
70
+ res = ig_posts_scraper.get_posts(target_info=target_info)
71
+ ```
72
+
73
+ ### Optional parameters
74
+
75
+ - **username**: target instagram user
76
+ - **days_limit**: Number of days within which to scrape posts..
77
+
78
+ ## Sample(Usage & Result) - KaiCenat
79
+ ![image](https://github.com/FaustRen/instagram-posts-scraper/blob/main/usage.png)
80
+
81
+ ![image](https://github.com/FaustRen/instagram-posts-scraper/blob/main/scraped_posts.png)
@@ -4,7 +4,7 @@ from setuptools import setup, find_packages
4
4
 
5
5
  setup(
6
6
  name='instagram-posts-scraper',
7
- version='0.0.1',
7
+ version='0.0.2',
8
8
  packages=[
9
9
  "instagram_posts_scraper",
10
10
  "instagram_posts_scraper.utils"
@@ -15,7 +15,7 @@ setup(
15
15
  long_description_content_type='text/markdown',
16
16
  author='FaustRen',
17
17
  author_email='faustren1z@gmail.com',
18
- url='https://github.com/FaustRen/instagram_posts_scraper',
18
+ url='https://github.com/FaustRen/instagram-posts-scraper',
19
19
  classifiers=[
20
20
  "Programming Language :: Python :: 3.11",
21
21
  "License :: OSI Approved :: MIT License",
@@ -1,104 +0,0 @@
1
- Metadata-Version: 2.2
2
- Name: instagram-posts-scraper
3
- Version: 0.0.1
4
- Summary: Implement Instagram Posts Scraper for post data retrieval
5
- Home-page: https://github.com/FaustRen/instagram_posts_scraper
6
- Author: FaustRen
7
- Author-email: faustren1z@gmail.com
8
- License: MIT
9
- Classifier: Programming Language :: Python :: 3.11
10
- Classifier: License :: OSI Approved :: MIT License
11
- Classifier: Operating System :: OS Independent
12
- Requires-Python: >=3.11
13
- Description-Content-Type: text/markdown
14
- License-File: LICENSE
15
- Dynamic: author
16
- Dynamic: author-email
17
- Dynamic: classifier
18
- Dynamic: description
19
- Dynamic: description-content-type
20
- Dynamic: home-page
21
- Dynamic: license
22
- Dynamic: requires-python
23
- Dynamic: summary
24
-
25
- # Instagram Posts Scraper
26
-
27
- InstagramPostsScraper is a Python library for collect instagram users' data.
28
-
29
- The data obtained by web crawlers is not real-time data, but rather data from a specific point in time on the same day.
30
-
31
- ## Installation
32
-
33
- Use the package manager [pip](https://pip.pypa.io/en/stable/) to install instagram_posts_scraper.
34
-
35
- ```bash
36
- beautifulsoup4==4.13.3
37
- cloudscraper==1.2.71
38
- pandas==2.2.3
39
- pytz==2025.1
40
- ```
41
-
42
-
43
- ## Usage - Sample
44
-
45
- ```python
46
- from instagram_posts_scraper import InstaPeriodScraper
47
-
48
-
49
- target_info = {"username": "joemanweng", "days_limit":5}
50
- ig_posts_scraper = InstaPeriodScraper()
51
- res = ig_posts_scraper.get_posts(target_info=target_info)
52
-
53
- ```
54
-
55
- ## Result Sample
56
-
57
- ```
58
- {
59
- 'profile': {
60
- 'introduction': ['台灣Youtuber\n⬇️專屬團購連結⬇️'],
61
- 'counts_of_posts': '1392',
62
- 'followers': '581198',
63
- 'followings': '859'
64
- },
65
- 'account_status': 'public',
66
- 'updated_at': datetime.datetime(2025, 2, 9, 1, 28, 8, 793770, tzinfo=<DstTzInfo 'Asia/Taipei' CST+8:00:00 STD>),
67
- 'data': [
68
- {
69
- 'type': 'igtv',
70
- 'sum': '《靈能的挑戰》參賽者後台花絮露出👀\n老師們比賽中較勁,但私下娛樂竟是互相算命!\n不知道有沒有先算出冠軍了(吃瓜)\n預祝大家新年快樂~~\n*節目中個人言論不代表本節目立場*\n﹏﹏﹏﹏﹏﹏﹏﹏﹏﹏﹏﹏\n#靈能的挑戰\xa0#Joeman\xa0#東森超視33頻道\n全台第一檔靈能競賽節目\n📺️ 全季觀看平台|\n🎥 Joeman YT 頻道觀看全季\n🎥 東森超視 33 頻道已播出完畢\n﹋﹋﹋﹋﹋﹋﹋﹋﹋﹋﹋﹋\n#玄學 #易經 #塔羅 #通靈 #占星',
71
- 'sum_pure': '《靈能的挑戰》參賽者後台花絮露出 老師們比賽中較勁,但私下娛樂竟是互相算命!不知道有沒有先算出冠軍了(吃瓜)預祝大家新年快樂~~*節目中個人言論不代表本節目立場*﹏﹏﹏﹏﹏﹏﹏﹏﹏﹏﹏﹏#靈能的挑戰\xa0#Joeman\xa0#東森超視33頻道全台第一檔靈能競賽節目 ️ 全季觀看平台| Joeman YT 頻道觀看全季 東森超視 33 頻道已播出完畢﹋﹋﹋﹋﹋﹋﹋﹋﹋﹋﹋﹋#玄學 #易經 #塔羅 #通靈 #占星',
72
- 'shortcode': '6741582137415658464423',
73
- 'time': 1737885600,
74
- 'ftime': '13 days ago',
75
- 'count_like': 2442,
76
- 'count_comment': 6,
77
- 'count_like_pure': '2,442',
78
- 'count_comment_pure': '6',
79
- 'thum': 'https://scontent-fra3-1.cdninstagram.com/v/t51.2885-15/474907887_1129543381888335_8766424988966118915_n.jpg?stp=c0.248.640.640a_dst-jpg_e15_tt6&_nc_ht=scontent-fra3-1.cdninstagram.com&_nc_cat=103&_nc_ohc=YPiycXP_teMQ7kNvgF-DLaF&_nc_gid=3e3699c22b4246b98fb450d120d9527f&edm=APU89FABAAAA&ccb=7-5&oh=00_AYAb75YGceozAPWzbq4CQQGd_q_XyfKdjdwcKYak3J-pHA&oe=67AD07FF&_nc_sid=bc0c2c',
80
- 'pic': 'https://scontent-fra3-1.cdninstagram.com/v/t51.2885-15/474907887_1129543381888335_8766424988966118915_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-fra3-1.cdninstagram.com&_nc_cat=103&_nc_ohc=YPiycXP_teMQ7kNvgF-DLaF&_nc_gid=3e3699c22b4246b98fb450d120d9527f&edm=APU89FABAAAA&ccb=7-5&oh=00_AYA0qczIKFhJEbukWZRRSUvJJ28iqBuhIXZqRB3EEA450Q&oe=67AD07FF&_nc_sid=bc0c2c',
81
- 'pic_p': 'https://sp1.piokok.com/p/pt_6741582137415658464423_0_233bd378e8b9a4f088f6c850099951c7.jpg?u=https%3A%2F%2Fscontent-fra3-1.cdninstagram.com%2Fv%2Ft51.2885-15%2F474907887_1129543381888335_8766424988966118915_n.jpg%3Fstp%3Dc0.248.640.640a_dst-jpg_e15_tt6%26_nc_ht%3Dscontent-fra3-1.cdninstagram.com%26_nc_cat%3D103%26_nc_ohc%3DYPiycXP_teMQ7kNvgF-DLaF%26_nc_gid%3D3e3699c22b4246b98fb450d120d9527f%26edm%3DAPU89FABAAAA%26ccb%3D7-5%26oh%3D00_AYAb75YGceozAPWzbq4CQQGd_q_XyfKdjdwcKYak3J-pHA%26oe%3D67AD07FF%26_nc_sid%3Dbc0c2c',
82
- 'down_pic': 'https://scontent-fra3-1.cdninstagram.com/v/t51.2885-15/474907887_1129543381888335_8766424988966118915_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-fra3-1.cdninstagram.com&_nc_cat=103&_nc_ohc=YPiycXP_teMQ7kNvgF-DLaF&_nc_gid=3e3699c22b4246b98fb450d120d9527f&edm=APU89FABAAAA&ccb=7-5&oh=00_AYA0qczIKFhJEbukWZRRSUvJJ28iqBuhIXZqRB3EEA450Q&oe=67AD07FF&_nc_sid=bc0c2c&dl=1',
83
- 'is_video': True,
84
- 'video': 'https://scontent-fra3-1.cdninstagram.com/o1/v/t16/f2/m86/AQNbkAdWefxU8OJ015A7RX0oUxLJS-03KKFEbd2ueAbuWXS5jla4AsgRPhGeuuD9HsvVUz0mG5uKwOTWpYhvCVbbIPtxarY3vAIwG7A.mp4?stp=dst-mp4&efg=eyJxZV9ncm91cHMiOiJbXCJpZ193ZWJfZGVsaXZlcnlfdnRzX290ZlwiXSIsInZlbmNvZGVfdGFnIjoidnRzX3ZvZF91cmxnZW4uY2xpcHMuYzIuNzIwLmJhc2VsaW5lIn0&_nc_cat=103&vs=427033163733715_1223826287&_nc_vs=HBksFQIYUmlnX3hwdl9yZWVsc19wZXJtYW5lbnRfc3JfcHJvZC9ERDQ1NERCQUU1RkU4RDA5MzA5NzA2Q0IzODgxRUVBM192aWRlb19kYXNoaW5pdC5tcDQVAALIAQAVAhg6cGFzc3Rocm91Z2hfZXZlcnN0b3JlL0dPcGZUeHgyY1EtV3l3c0VBQjRRdldDeTRZdHFicV9FQUFBRhUCAsgBACgAGAAbABUAACamtPfZ0u%2BTQBUCKAJDMywXQF2QEGJN0vIYEmRhc2hfYmFzZWxpbmVfMV92MREAdf4HAA%3D%3D&_nc_rid=3e36937551&ccb=9-4&oh=00_AYANPnQ7Zlk5Z_6wS0OF1Hn3ttS8vsk-0K6OyM9y8xzM6A&oe=67A925DC&_nc_sid=bc0c2c',
85
- 'down_video': 'same_as_above'
86
- }
87
- ]
88
- }
89
-
90
-
91
-
92
-
93
- ## Contributing - Sample
94
-
95
- comming soon..
96
-
97
- ## License - Sample
98
-
99
- comming soon..
100
-
101
- ## Tests - cd to tests folder
102
- coverage run test_crawler.py
103
-
104
- coverage html
@@ -1,160 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- from instagram_posts_scraper.request import *
3
- from instagram_posts_scraper.parse import *
4
- from instagram_posts_scraper.utils import *
5
- from instagram_posts_scraper.scraper import *
6
- from instagram_posts_scraper.utils.utils import *
7
- from instagram_posts_scraper.file_operation import *
8
-
9
-
10
- class ScrapedDataManager(object):
11
- def __init__(self):
12
- pass
13
-
14
-
15
- class InstaPeriodScraper(object):
16
- def __init__(self) -> None:
17
- self.pixwox_request = PixwoxRequest()
18
- self.parser=Parser()
19
- self.api_parser=ApiParser()
20
- self.scraper=Scraper(
21
- pixwox_request=self.pixwox_request,
22
- parser=self.parser,
23
- api_parser=self.api_parser
24
- )
25
-
26
- def check_account_is_public(self):
27
- init_response = self.pixwox_request.send_requests(url=self.scraper.init_url)
28
- self.profile_soup = self.parser.get_soup(response=init_response)
29
- self.userid = self.parser.get_userid(profile_soup=self.profile_soup)
30
- self.account_status = get_account_status(userid=self.userid, profile_soup=self.profile_soup)
31
- return self.account_status == "public"
32
-
33
- def get_profile(self):
34
- self.followings = self.parser.get_followings(self.profile_soup)
35
- self.followers = self.parser.get_followers(self.profile_soup)
36
- self.counts_of_posts = self.parser.get_counts_of_posts(self.profile_soup)
37
- try:
38
- self.introduction = self.parser.get_introduction(self.profile_soup)
39
- except:
40
- self.introduction = None
41
-
42
- self.profile_info = {
43
- "introduction": self.introduction,
44
- "counts_of_posts": self.counts_of_posts,
45
- "followers": self.followers,
46
- "followings": self.followings,
47
- }
48
-
49
- def get_init_api_data(self):
50
- init_api_data = self.scraper.get_init_api_data(userid=self.userid)
51
- return init_api_data
52
-
53
- def get_next_api_data(self, next_maxid:str, next_:str,username:str):
54
- next_api_data = self.scraper.get_next_api_data(userid=self.userid, next_maxid=next_maxid, next_=next_, username=username)
55
- return next_api_data
56
-
57
- def get_private_account_res(self):
58
- res = {
59
- "profile":{
60
- "userid":self.userid,
61
- "username":self.target_info["username"],
62
- "followers":self.followers,
63
- "followings":self.followings,
64
- "counts_of_posts":self.counts_of_posts,
65
- "introduction":self.introduction
66
- },
67
- "account_status":self.account_status,
68
- "updated_at": get_current_time(timezone="Asia/Taipei"),
69
- "data":[]
70
- }
71
- return res
72
-
73
- def get_missing_account_res(self):
74
- res = {
75
- "profile":{
76
- "userid":None,
77
- "username":self.target_info["username"],
78
- "followers":None,
79
- "followings":None,
80
- "counts_of_posts":None,
81
- "introduction":None
82
- },
83
- "account_status":self.account_status,
84
- "updated_at": get_current_time(timezone="Asia/Taipei"),
85
- "data":[]
86
- }
87
- return res
88
-
89
- def get_public_account_res(self, scraped_posts, init_api_data):
90
- res = {
91
- "profile": self.profile_info,
92
- "account_status":self.account_status,
93
- "updated_at": get_current_time(timezone="Asia/Taipei"),
94
- "data":scraped_posts}
95
- return res
96
-
97
- # @timeout(300)
98
- def get_period_data(self, days_limit:int, init_maxid:str, init_api_data, username):
99
- scraped_posts_res = init_api_data["posts"]["items"]
100
- next_ = init_api_data["posts"]["next"]
101
- next_maxid = init_maxid
102
- for rounds in range(days_limit):
103
- # Scraped next rounds data
104
- next_api_data = self.get_next_api_data(next_maxid=next_maxid, next_=next_, username=username)
105
- scraped_posts = next_api_data["posts"]
106
- scraped_items = scraped_posts["items"]
107
- scraped_posts_res += scraped_items
108
-
109
- # if get all posts or get target period posts
110
- if has_all_data_been_collected(scraped_items=scraped_posts_res, counts_of_posts=self.counts_of_posts) or is_date_exceed_half_year(scraped_items=scraped_items,days_limit=days_limit):
111
- return scraped_posts_res
112
-
113
- elif next_api_data["posts"]["has_next"]: # if there are posts can scrape
114
- next_maxid = scraped_posts["maxid"]
115
- next_ = scraped_posts["next"]
116
- continue
117
- else:
118
- return scraped_posts_res
119
- return scraped_posts_res
120
-
121
- def get_posts(self, target_info:dict):
122
- self.target_info = target_info
123
- self.username = self.target_info["username"]
124
- username = self.target_info["username"]
125
- self.scraper.set_username(username)
126
- days_limit = target_info["days_limit"]
127
- if not self.check_account_is_public():
128
- print("This is private account")
129
- if self.account_status == "private":
130
- self.get_profile()
131
- res = self.get_private_account_res()
132
- return res
133
- elif self.account_status == "missing":
134
- res = self.get_missing_account_res()
135
- return res
136
-
137
- if self.check_account_is_public():
138
- init_api_data = self.get_init_api_data() # 帳號資訊 & 上方頁面內容
139
- self.get_profile()
140
- print(f"This is public account")
141
- # can scrape next round's posts
142
- if init_api_data["posts"]["has_next"] != False:
143
- maxid = init_api_data["posts"]["maxid"]
144
- period_posts = self.get_period_data(
145
- init_maxid=maxid,
146
- days_limit=days_limit,
147
- init_api_data=init_api_data,
148
- username=username
149
- )
150
-
151
- # return period_posts
152
- res = self.get_public_account_res(
153
- scraped_posts=period_posts,
154
- init_api_data=init_api_data
155
- )
156
- return res
157
- # # no more posts
158
- elif init_api_data["posts"]["has_next"] == False: # (表示該帳號貼文數<=12, 無法繼續往下找)
159
- res = self.get_public_account_res(scraped_posts=init_api_data["posts"]["items"], init_api_data=init_api_data)
160
- return res
@@ -1,104 +0,0 @@
1
- Metadata-Version: 2.2
2
- Name: instagram-posts-scraper
3
- Version: 0.0.1
4
- Summary: Implement Instagram Posts Scraper for post data retrieval
5
- Home-page: https://github.com/FaustRen/instagram_posts_scraper
6
- Author: FaustRen
7
- Author-email: faustren1z@gmail.com
8
- License: MIT
9
- Classifier: Programming Language :: Python :: 3.11
10
- Classifier: License :: OSI Approved :: MIT License
11
- Classifier: Operating System :: OS Independent
12
- Requires-Python: >=3.11
13
- Description-Content-Type: text/markdown
14
- License-File: LICENSE
15
- Dynamic: author
16
- Dynamic: author-email
17
- Dynamic: classifier
18
- Dynamic: description
19
- Dynamic: description-content-type
20
- Dynamic: home-page
21
- Dynamic: license
22
- Dynamic: requires-python
23
- Dynamic: summary
24
-
25
- # Instagram Posts Scraper
26
-
27
- InstagramPostsScraper is a Python library for collect instagram users' data.
28
-
29
- The data obtained by web crawlers is not real-time data, but rather data from a specific point in time on the same day.
30
-
31
- ## Installation
32
-
33
- Use the package manager [pip](https://pip.pypa.io/en/stable/) to install instagram_posts_scraper.
34
-
35
- ```bash
36
- beautifulsoup4==4.13.3
37
- cloudscraper==1.2.71
38
- pandas==2.2.3
39
- pytz==2025.1
40
- ```
41
-
42
-
43
- ## Usage - Sample
44
-
45
- ```python
46
- from instagram_posts_scraper import InstaPeriodScraper
47
-
48
-
49
- target_info = {"username": "joemanweng", "days_limit":5}
50
- ig_posts_scraper = InstaPeriodScraper()
51
- res = ig_posts_scraper.get_posts(target_info=target_info)
52
-
53
- ```
54
-
55
- ## Result Sample
56
-
57
- ```
58
- {
59
- 'profile': {
60
- 'introduction': ['台灣Youtuber\n⬇️專屬團購連結⬇️'],
61
- 'counts_of_posts': '1392',
62
- 'followers': '581198',
63
- 'followings': '859'
64
- },
65
- 'account_status': 'public',
66
- 'updated_at': datetime.datetime(2025, 2, 9, 1, 28, 8, 793770, tzinfo=<DstTzInfo 'Asia/Taipei' CST+8:00:00 STD>),
67
- 'data': [
68
- {
69
- 'type': 'igtv',
70
- 'sum': '《靈能的挑戰》參賽者後台花絮露出👀\n老師們比賽中較勁,但私下娛樂竟是互相算命!\n不知道有沒有先算出冠軍了(吃瓜)\n預祝大家新年快樂~~\n*節目中個人言論不代表本節目立場*\n﹏﹏﹏﹏﹏﹏﹏﹏﹏﹏﹏﹏\n#靈能的挑戰\xa0#Joeman\xa0#東森超視33頻道\n全台第一檔靈能競賽節目\n📺️ 全季觀看平台|\n🎥 Joeman YT 頻道觀看全季\n🎥 東森超視 33 頻道已播出完畢\n﹋﹋﹋﹋﹋﹋﹋﹋﹋﹋﹋﹋\n#玄學 #易經 #塔羅 #通靈 #占星',
71
- 'sum_pure': '《靈能的挑戰》參賽者後台花絮露出 老師們比賽中較勁,但私下娛樂竟是互相算命!不知道有沒有先算出冠軍了(吃瓜)預祝大家新年快樂~~*節目中個人言論不代表本節目立場*﹏﹏﹏﹏﹏﹏﹏﹏﹏﹏﹏﹏#靈能的挑戰\xa0#Joeman\xa0#東森超視33頻道全台第一檔靈能競賽節目 ️ 全季觀看平台| Joeman YT 頻道觀看全季 東森超視 33 頻道已播出完畢﹋﹋﹋﹋﹋﹋﹋﹋﹋﹋﹋﹋#玄學 #易經 #塔羅 #通靈 #占星',
72
- 'shortcode': '6741582137415658464423',
73
- 'time': 1737885600,
74
- 'ftime': '13 days ago',
75
- 'count_like': 2442,
76
- 'count_comment': 6,
77
- 'count_like_pure': '2,442',
78
- 'count_comment_pure': '6',
79
- 'thum': 'https://scontent-fra3-1.cdninstagram.com/v/t51.2885-15/474907887_1129543381888335_8766424988966118915_n.jpg?stp=c0.248.640.640a_dst-jpg_e15_tt6&_nc_ht=scontent-fra3-1.cdninstagram.com&_nc_cat=103&_nc_ohc=YPiycXP_teMQ7kNvgF-DLaF&_nc_gid=3e3699c22b4246b98fb450d120d9527f&edm=APU89FABAAAA&ccb=7-5&oh=00_AYAb75YGceozAPWzbq4CQQGd_q_XyfKdjdwcKYak3J-pHA&oe=67AD07FF&_nc_sid=bc0c2c',
80
- 'pic': 'https://scontent-fra3-1.cdninstagram.com/v/t51.2885-15/474907887_1129543381888335_8766424988966118915_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-fra3-1.cdninstagram.com&_nc_cat=103&_nc_ohc=YPiycXP_teMQ7kNvgF-DLaF&_nc_gid=3e3699c22b4246b98fb450d120d9527f&edm=APU89FABAAAA&ccb=7-5&oh=00_AYA0qczIKFhJEbukWZRRSUvJJ28iqBuhIXZqRB3EEA450Q&oe=67AD07FF&_nc_sid=bc0c2c',
81
- 'pic_p': 'https://sp1.piokok.com/p/pt_6741582137415658464423_0_233bd378e8b9a4f088f6c850099951c7.jpg?u=https%3A%2F%2Fscontent-fra3-1.cdninstagram.com%2Fv%2Ft51.2885-15%2F474907887_1129543381888335_8766424988966118915_n.jpg%3Fstp%3Dc0.248.640.640a_dst-jpg_e15_tt6%26_nc_ht%3Dscontent-fra3-1.cdninstagram.com%26_nc_cat%3D103%26_nc_ohc%3DYPiycXP_teMQ7kNvgF-DLaF%26_nc_gid%3D3e3699c22b4246b98fb450d120d9527f%26edm%3DAPU89FABAAAA%26ccb%3D7-5%26oh%3D00_AYAb75YGceozAPWzbq4CQQGd_q_XyfKdjdwcKYak3J-pHA%26oe%3D67AD07FF%26_nc_sid%3Dbc0c2c',
82
- 'down_pic': 'https://scontent-fra3-1.cdninstagram.com/v/t51.2885-15/474907887_1129543381888335_8766424988966118915_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-fra3-1.cdninstagram.com&_nc_cat=103&_nc_ohc=YPiycXP_teMQ7kNvgF-DLaF&_nc_gid=3e3699c22b4246b98fb450d120d9527f&edm=APU89FABAAAA&ccb=7-5&oh=00_AYA0qczIKFhJEbukWZRRSUvJJ28iqBuhIXZqRB3EEA450Q&oe=67AD07FF&_nc_sid=bc0c2c&dl=1',
83
- 'is_video': True,
84
- 'video': 'https://scontent-fra3-1.cdninstagram.com/o1/v/t16/f2/m86/AQNbkAdWefxU8OJ015A7RX0oUxLJS-03KKFEbd2ueAbuWXS5jla4AsgRPhGeuuD9HsvVUz0mG5uKwOTWpYhvCVbbIPtxarY3vAIwG7A.mp4?stp=dst-mp4&efg=eyJxZV9ncm91cHMiOiJbXCJpZ193ZWJfZGVsaXZlcnlfdnRzX290ZlwiXSIsInZlbmNvZGVfdGFnIjoidnRzX3ZvZF91cmxnZW4uY2xpcHMuYzIuNzIwLmJhc2VsaW5lIn0&_nc_cat=103&vs=427033163733715_1223826287&_nc_vs=HBksFQIYUmlnX3hwdl9yZWVsc19wZXJtYW5lbnRfc3JfcHJvZC9ERDQ1NERCQUU1RkU4RDA5MzA5NzA2Q0IzODgxRUVBM192aWRlb19kYXNoaW5pdC5tcDQVAALIAQAVAhg6cGFzc3Rocm91Z2hfZXZlcnN0b3JlL0dPcGZUeHgyY1EtV3l3c0VBQjRRdldDeTRZdHFicV9FQUFBRhUCAsgBACgAGAAbABUAACamtPfZ0u%2BTQBUCKAJDMywXQF2QEGJN0vIYEmRhc2hfYmFzZWxpbmVfMV92MREAdf4HAA%3D%3D&_nc_rid=3e36937551&ccb=9-4&oh=00_AYANPnQ7Zlk5Z_6wS0OF1Hn3ttS8vsk-0K6OyM9y8xzM6A&oe=67A925DC&_nc_sid=bc0c2c',
85
- 'down_video': 'same_as_above'
86
- }
87
- ]
88
- }
89
-
90
-
91
-
92
-
93
- ## Contributing - Sample
94
-
95
- comming soon..
96
-
97
- ## License - Sample
98
-
99
- comming soon..
100
-
101
- ## Tests - cd to tests folder
102
- coverage run test_crawler.py
103
-
104
- coverage html