fix: add mobile user-agent and random delays to bypass Instagram anti-bot

- iPhone Safari user-agent + X-IG-App-ID header
- 2-6s random delay between profile checks
- 1-3s delay between media downloads
- RateController for built-in throttling
- Try/except per followee to not stop on errors
This commit is contained in:
2026-06-17 11:58:02 +07:00
parent 0faebcd41b
commit c119b28e4d

View File

@@ -1,7 +1,9 @@
import http.cookiejar
import json
import os
import random
import shutil
import time
from datetime import datetime
from pathlib import Path
from typing import AsyncIterator
@@ -22,8 +24,16 @@ class InstagramMonitor:
compress_json=False,
post_metadata_txt_pattern="",
max_connection_attempts=3,
request_timeout=30,
request_timeout=60,
rate_controller=instaloader.RateController(),
)
self.loader.context._session.headers.update({
"User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1",
"X-IG-App-ID": "936619743392459",
"X-IG-WWW-Claim": "0",
"X-Requested-With": "XMLHttpRequest",
"Accept-Language": "en-US,en;q=0.9",
})
self._session_path = Path(config.session_file)
self._download_dir = Path(config.download_dir)
self._state_file = self._download_dir / "state.json"
@@ -153,6 +163,11 @@ class InstagramMonitor:
"""Clear session cookies to start fresh."""
self.loader.context._session.cookies.clear()
def _random_delay(self, min_sec: float = 2.0, max_sec: float = 5.0) -> None:
"""Random delay to avoid detection."""
delay = random.uniform(min_sec, max_sec)
time.sleep(delay)
def _load_cookies_from_json(self, path: Path) -> None:
with open(path) as f:
cookies = json.load(f)
@@ -228,6 +243,8 @@ class InstagramMonitor:
logger.error("Cannot check updates without login")
return
self._random_delay(3, 7)
try:
# First verify who we are logged in as
logger.info(f"Attempting to fetch profile: {config.instagram_username}")
@@ -253,32 +270,44 @@ class InstagramMonitor:
for followee in followees:
username = followee.username
for story in self._get_profile_stories(username):
media_path = self._download_media(story)
if media_path:
self._state[str(story.mediaid)] = datetime.utcnow().isoformat()
self._save_state()
yield {
"type": "story",
"username": username,
"media_path": media_path,
"caption": "",
"shortcode": str(story.mediaid),
}
self._random_delay(3, 6)
for post in self._get_profile_posts(username):
caption = post.caption or ""
media_path = self._download_media(post)
if media_path:
self._state[str(post.mediaid)] = datetime.utcnow().isoformat()
self._save_state()
yield {
"type": "post",
"username": username,
"media_path": media_path,
"caption": caption[:500],
"shortcode": post.shortcode,
}
try:
for story in self._get_profile_stories(username):
media_path = self._download_media(story)
if media_path:
self._state[str(story.mediaid)] = datetime.utcnow().isoformat()
self._save_state()
yield {
"type": "story",
"username": username,
"media_path": media_path,
"caption": "",
"shortcode": str(story.mediaid),
}
self._random_delay(1, 3)
except Exception as e:
logger.error(f"Error processing stories for {username}: {e}")
self._random_delay(2, 4)
try:
for post in self._get_profile_posts(username):
caption = post.caption or ""
media_path = self._download_media(post)
if media_path:
self._state[str(post.mediaid)] = datetime.utcnow().isoformat()
self._save_state()
yield {
"type": "post",
"username": username,
"media_path": media_path,
"caption": caption[:500],
"shortcode": post.shortcode,
}
self._random_delay(2, 5)
except Exception as e:
logger.error(f"Error processing posts for {username}: {e}")
def cleanup_downloads(self, max_age_hours: int = 24) -> None:
now = datetime.utcnow()