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.
- instagram_posts_scraper-0.0.2/PKG-INFO +81 -0
- {instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/instagram_posts_scraper/example.py +1 -1
- {instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/instagram_posts_scraper/file_operation.py +0 -9
- instagram_posts_scraper-0.0.2/instagram_posts_scraper/instagram_posts_scraper.py +263 -0
- instagram_posts_scraper-0.0.2/instagram_posts_scraper.egg-info/PKG-INFO +81 -0
- {instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/setup.py +2 -2
- instagram_posts_scraper-0.0.1/PKG-INFO +0 -104
- instagram_posts_scraper-0.0.1/instagram_posts_scraper/instagram_posts_scraper.py +0 -160
- instagram_posts_scraper-0.0.1/instagram_posts_scraper.egg-info/PKG-INFO +0 -104
- {instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/LICENSE +0 -0
- {instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/instagram_posts_scraper/__init__.py +0 -0
- {instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/instagram_posts_scraper/parse.py +0 -0
- {instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/instagram_posts_scraper/request.py +0 -0
- {instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/instagram_posts_scraper/scraper.py +0 -0
- {instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/instagram_posts_scraper/utils/__init__.py +0 -0
- {instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/instagram_posts_scraper/utils/utils.py +0 -0
- {instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/instagram_posts_scraper/utils.py +0 -0
- {instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/instagram_posts_scraper.egg-info/SOURCES.txt +0 -0
- {instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/instagram_posts_scraper.egg-info/dependency_links.txt +0 -0
- {instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/instagram_posts_scraper.egg-info/top_level.txt +0 -0
- {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
|
+

|
|
80
|
+
|
|
81
|
+

|
{instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/instagram_posts_scraper/example.py
RENAMED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from instagram_posts_scraper.instagram_posts_scraper import InstaPeriodScraper
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
target_info = {"username": "
|
|
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
|
+

|
|
80
|
+
|
|
81
|
+

|
|
@@ -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.
|
|
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/
|
|
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
|
|
File without changes
|
{instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/instagram_posts_scraper/__init__.py
RENAMED
|
File without changes
|
{instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/instagram_posts_scraper/parse.py
RENAMED
|
File without changes
|
{instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/instagram_posts_scraper/request.py
RENAMED
|
File without changes
|
{instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/instagram_posts_scraper/scraper.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{instagram_posts_scraper-0.0.1 → instagram_posts_scraper-0.0.2}/instagram_posts_scraper/utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|