first commit

This commit is contained in:
TutorialsGHG
2026-05-10 21:03:57 +02:00
commit f2e7545f9a
56 changed files with 835 additions and 0 deletions

133
README.md Normal file
View File

@@ -0,0 +1,133 @@
# Social Media Collection Downloader
Automatically downloads TikTok collections and Instagram saved posts, then unsaves them.
## Requirements
- Python 3.10+
- Chrome, Firefox, or Edge (for cookie extraction or headless automation)
## Quick Start
```bash
# 1. Install dependencies
pip install -r requirements.txt
playwright install chromium
# 2. Run the interactive setup wizard (recommended)
python setup.py
# 3. Or run directly if config already exists
python downloader.py
```
---
## Getting Your Cookies (Required for Private Content)
TikTok and Instagram require you to be logged in. The app needs your session cookies.
### Option A — Export from browser (recommended)
1. Install the **"Get cookies.txt LOCALLY"** extension:
- [Chrome](https://chrome.google.com/webstore/detail/get-cookiestxt-locally/cclelndahbckbenkjhflpdbgdldlbecc)
- [Firefox](https://addons.mozilla.org/en-US/firefox/addon/cookies-txt/)
2. Log into tiktok.com and/or instagram.com
3. Click the extension icon → choose **JSON** format → Export
4. Save the files somewhere safe (e.g. `config/tiktok_cookies.json`)
5. Enter the paths during `setup.py`
### Option B — Auto-read from browser
During setup, choose option `2` (browser auto-read) and enter your browser name (`chrome`, `firefox`, `edge`).
> ⚠ Your browser must be **closed** (or using a separate profile) when this runs.
---
## Finding Your Collection URLs
### TikTok
1. Go to your profile → tap **Collections** (bookmark icon)
2. Open the collection you want to download
3. Copy the URL from your browser — it looks like:
`https://www.tiktok.com/@yourhandle/collection/MyVideos-1234567890`
### Instagram
1. Go to your profile → tap the **bookmark icon** (Saved)
2. Open the collection you want to download
3. Copy the URL — it looks like:
`https://www.instagram.com/yourhandle/saved/my-collection/1234567890/`
---
## Configuration
Edit `config/config.json` directly, or use `setup.py` to regenerate it.
See `config/config.example.json` for all available options.
Key options:
| Option | Description |
|---|---|
| `enabled` | Enable/disable a platform |
| `cookies_file` | Path to exported cookies JSON |
| `cookies_from_browser` | Auto-read from `chrome`/`firefox`/`edge` |
| `collections` | List of collection URLs to process |
| `unsave_after_download` | Remove posts after downloading |
| `headless` | Hide browser window during unsaving |
| `delay_between_downloads` | Seconds between each download (avoid rate limits) |
| `delay_between_unsaves` | Seconds between each unsave click |
---
## Downloaded Files
Files are saved to:
```
downloads/
tiktok/
Username - Video Title [video_id].mp4
Username - Video Title [video_id].info.json
instagram/
Username - Post Title [post_id].mp4
Username - Post Title [post_id].jpg
```
---
## Scheduling (run automatically)
### Windows Task Scheduler
Create a task that runs:
```
python C:\path\to\social-dl\downloader.py
```
### macOS / Linux (cron)
```bash
# Run every day at 9am
0 9 * * * cd /path/to/social-dl && python downloader.py
```
---
## Troubleshooting
**yt-dlp says "login required"**
→ Your cookies are missing or expired. Re-export them from your browser.
**Unsave doesn't work / button not found**
→ TikTok/Instagram may have updated their UI. Set `"headless": false` to watch the browser and inspect what's happening. Open an issue with a screenshot.
**Rate limited / banned temporarily**
→ Increase `delay_between_downloads` to `5` or more.
**Instagram downloads fail**
→ Instagram heavily restricts scraping. Use fresh cookies from a recently-logged-in session. Try `cookies_from_browser` with your browser open to Instagram.
---
## Notes
- Downloads are skipped if the file already exists (`--no-overwrites`)
- Logs are saved to `logs/session_YYYYMMDD_HHMMSS.log`
- The unsave step opens a browser window (unless `headless: true`) — this is normal
- This tool uses your own account cookies; it does not share or store credentials anywhere

View File

@@ -0,0 +1,26 @@
{
"tiktok": {
"enabled": true,
"cookies_file": "/path/to/tiktok_cookies.json",
"cookies_from_browser": null,
"collections": [
"https://www.tiktok.com/@yourhandle/collection/MyCollection-1234567890"
],
"unsave_after_download": true,
"headless": false,
"delay_between_downloads": 2,
"delay_between_unsaves": 2
},
"instagram": {
"enabled": true,
"cookies_file": "/path/to/instagram_cookies.json",
"cookies_from_browser": null,
"collections": [
"https://www.instagram.com/yourhandle/saved/collection-name/1234567890/"
],
"unsave_after_download": true,
"headless": false,
"delay_between_downloads": 3,
"delay_between_unsaves": 3
}
}

17
config/config.json Normal file
View File

@@ -0,0 +1,17 @@
{
"tiktok": {
"enabled": true,
"cookies_from_browser": "firefox",
"cookies_file": null,
"collections": [
"https://www.tiktok.com/@tuxxxxax787/collection/Download-7626779378428545824"
],
"unsave_after_download": true,
"headless": false,
"delay_between_downloads": 5,
"delay_between_unsaves": 3
},
"instagram": {
"enabled": false
}
}

350
downloader.py Normal file
View File

@@ -0,0 +1,350 @@
"""
Social Media Collection Downloader
Downloads TikTok collections and Instagram saved posts, then removes them.
Requires: yt-dlp, playwright, browser cookies exported via browser extension.
"""
import os
import json
import time
import logging
import subprocess
import sys
from pathlib import Path
from datetime import datetime
from typing import Optional
# ── Logging ──────────────────────────────────────────────────────────────────
LOG_DIR = Path(__file__).parent / "logs"
LOG_DIR.mkdir(exist_ok=True)
log_file = LOG_DIR / f"session_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
import io
_stream_handler = logging.StreamHandler(io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace"))
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
handlers=[
logging.FileHandler(log_file, encoding="utf-8"),
_stream_handler,
],
)
log = logging.getLogger(__name__)
# ── Config ────────────────────────────────────────────────────────────────────
CONFIG_PATH = Path(__file__).parent / "config" / "config.json"
DOWNLOADS_DIR = Path(__file__).parent / "downloads"
def load_config() -> dict:
if not CONFIG_PATH.exists():
log.error(f"Config not found at {CONFIG_PATH}. Run: python setup.py")
sys.exit(1)
with open(CONFIG_PATH) as f:
return json.load(f)
# ── yt-dlp helpers ───────────────────────────────────────────────────────────
def build_ytdlp_cmd(url: str, output_dir: Path, cookies_file: Optional[str] = None, cookies_from_browser: Optional[str] = None) -> list:
cmd = [
"yt-dlp",
"--no-warnings",
"--quiet",
"--progress",
"-o", str(output_dir / "%(uploader)s - %(title).80s [%(id)s].%(ext)s"),
"--write-info-json",
"--no-overwrites",
"--retries", "3",
"--fragment-retries", "3",
"--concurrent-fragments", "4",
]
if cookies_file and Path(cookies_file).exists():
cmd += ["--cookies", cookies_file]
elif cookies_from_browser:
cmd += ["--cookies-from-browser", cookies_from_browser]
cmd.append(url)
return cmd
def download_url(url: str, output_dir: Path, cookies_file: Optional[str] = None, cookies_from_browser: Optional[str] = None) -> bool:
cmd = build_ytdlp_cmd(url, output_dir, cookies_file, cookies_from_browser)
log.info(f"Downloading: {url}")
result = subprocess.run(cmd, capture_output=False, text=True)
if result.returncode == 0:
log.info(f"[OK] Downloaded: {url}")
return True
else:
log.error(f"[FAIL] Failed: {url}")
return False
# ── TikTok ───────────────────────────────────────────────────────────────────
def get_tiktok_collection_urls(collection_url: str, cookies_file: Optional[str], cookies_from_browser: Optional[str]) -> list[str]:
"""Use yt-dlp to extract all video URLs from a TikTok collection/playlist."""
cmd = [
"yt-dlp",
"--flat-playlist",
"--print", "url",
"--no-warnings",
"--quiet",
]
if cookies_file and Path(cookies_file).exists():
cmd += ["--cookies", cookies_file]
elif cookies_from_browser:
cmd += ["--cookies-from-browser", cookies_from_browser]
cmd.append(collection_url)
log.info(f"Fetching TikTok collection URLs from: {collection_url}")
result = subprocess.run(cmd, capture_output=True, text=True)
urls = [line.strip() for line in result.stdout.splitlines() if line.strip().startswith("http")]
log.info(f"Found {len(urls)} videos in collection")
return urls
def download_tiktok_collection(config: dict) -> list[str]:
"""Download all videos from configured TikTok collections. Returns list of downloaded URLs."""
tk_cfg = config.get("tiktok", {})
if not tk_cfg.get("enabled", False):
log.info("TikTok disabled in config, skipping.")
return []
collections = tk_cfg.get("collections", [])
if not collections:
log.warning("No TikTok collections configured.")
return []
cookies_file = tk_cfg.get("cookies_file")
cookies_from_browser = tk_cfg.get("cookies_from_browser") # e.g. "chrome", "firefox"
output_dir = DOWNLOADS_DIR / "tiktok"
output_dir.mkdir(parents=True, exist_ok=True)
downloaded_urls = []
for collection_url in collections:
urls = get_tiktok_collection_urls(collection_url, cookies_file, cookies_from_browser)
for url in urls:
success = download_url(url, output_dir, cookies_file, cookies_from_browser)
if success:
downloaded_urls.append(url)
time.sleep(tk_cfg.get("delay_between_downloads", 2))
return downloaded_urls
# ── Instagram ─────────────────────────────────────────────────────────────────
def get_instagram_saved_urls(collection_url: str, cookies_file: Optional[str], cookies_from_browser: Optional[str]) -> list[str]:
"""Use yt-dlp to extract all post URLs from an Instagram saved collection."""
cmd = [
"yt-dlp",
"--flat-playlist",
"--print", "url",
"--no-warnings",
"--quiet",
]
if cookies_file and Path(cookies_file).exists():
cmd += ["--cookies", cookies_file]
elif cookies_from_browser:
cmd += ["--cookies-from-browser", cookies_from_browser]
cmd.append(collection_url)
log.info(f"Fetching Instagram saved URLs from: {collection_url}")
result = subprocess.run(cmd, capture_output=True, text=True)
urls = [line.strip() for line in result.stdout.splitlines() if line.strip().startswith("http")]
log.info(f"Found {len(urls)} posts in saved collection")
return urls
def download_instagram_collection(config: dict) -> list[str]:
"""Download all posts from configured Instagram saved collections."""
ig_cfg = config.get("instagram", {})
if not ig_cfg.get("enabled", False):
log.info("Instagram disabled in config, skipping.")
return []
collections = ig_cfg.get("collections", [])
if not collections:
log.warning("No Instagram collections configured.")
return []
cookies_file = ig_cfg.get("cookies_file")
cookies_from_browser = ig_cfg.get("cookies_from_browser")
output_dir = DOWNLOADS_DIR / "instagram"
output_dir.mkdir(parents=True, exist_ok=True)
downloaded_urls = []
for collection_url in collections:
urls = get_instagram_saved_urls(collection_url, cookies_file, cookies_from_browser)
for url in urls:
success = download_url(url, output_dir, cookies_file, cookies_from_browser)
if success:
downloaded_urls.append(url)
time.sleep(ig_cfg.get("delay_between_downloads", 3))
return downloaded_urls
# ── Unsave / Remove ───────────────────────────────────────────────────────────
def unsave_tiktok_videos(urls: list[str], config: dict):
"""Use Playwright to unsave/unlike downloaded TikTok videos."""
if not urls:
return
try:
from playwright.sync_api import sync_playwright
except ImportError:
log.error("Playwright not installed. Run: pip install playwright && playwright install chromium")
return
tk_cfg = config.get("tiktok", {})
cookies_file = tk_cfg.get("cookies_file")
log.info(f"Unsaving {len(urls)} TikTok videos...")
with sync_playwright() as p:
browser = p.chromium.launch(headless=tk_cfg.get("headless", False))
context = browser.new_context()
if cookies_file and Path(cookies_file).exists():
with open(cookies_file) as f:
raw = json.load(f)
pw_cookies = []
for c in raw:
if "tiktok.com" in c.get("domain", ""):
pw_cookies.append({
"name": c["name"],
"value": c["value"],
"domain": c["domain"],
"path": c.get("path", "/"),
"httpOnly": c.get("httpOnly", False),
"secure": c.get("secure", False),
})
context.add_cookies(pw_cookies)
page = context.new_page()
for url in urls:
try:
log.info(f"Unsaving: {url}")
page.goto(url, wait_until="networkidle", timeout=30000)
time.sleep(2)
# Try clicking bookmark/save button (TikTok uses aria-label)
bookmark = page.query_selector('[data-e2e="bookmark-icon"], [aria-label*="Add to Favorites"], [aria-label*="Save"]')
if bookmark:
bookmark.click()
time.sleep(1)
log.info(f"[OK] Unsaved: {url}")
else:
log.warning(f"[WARN] Could not find bookmark button for: {url}")
time.sleep(tk_cfg.get("delay_between_unsaves", 2))
except Exception as e:
log.error(f"Error unsaving {url}: {e}")
browser.close()
def unsave_instagram_posts(urls: list[str], config: dict):
"""Use Playwright to unsave downloaded Instagram posts."""
if not urls:
return
try:
from playwright.sync_api import sync_playwright
except ImportError:
log.error("Playwright not installed. Run: pip install playwright && playwright install chromium")
return
ig_cfg = config.get("instagram", {})
cookies_file = ig_cfg.get("cookies_file")
log.info(f"Unsaving {len(urls)} Instagram posts...")
with sync_playwright() as p:
browser = p.chromium.launch(headless=ig_cfg.get("headless", False))
context = browser.new_context(
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
)
if cookies_file and Path(cookies_file).exists():
with open(cookies_file) as f:
raw = json.load(f)
pw_cookies = []
for c in raw:
if "instagram.com" in c.get("domain", ""):
pw_cookies.append({
"name": c["name"],
"value": c["value"],
"domain": c["domain"],
"path": c.get("path", "/"),
"httpOnly": c.get("httpOnly", False),
"secure": c.get("secure", False),
})
context.add_cookies(pw_cookies)
page = context.new_page()
for url in urls:
try:
log.info(f"Unsaving: {url}")
page.goto(url, wait_until="networkidle", timeout=30000)
time.sleep(2)
# Instagram save button - look for bookmark SVG button
save_btn = page.query_selector('svg[aria-label="Remove"]')
if not save_btn:
save_btn = page.query_selector('[aria-label="Unsave"]')
if not save_btn:
# Try finding bookmark icon that's currently "saved" (filled state)
save_btn = page.query_selector('button svg[aria-label*="Save"]')
if save_btn:
save_btn.click()
time.sleep(1)
log.info(f"[OK] Unsaved: {url}")
else:
log.warning(f"[WARN] Could not find save button for: {url}")
time.sleep(ig_cfg.get("delay_between_unsaves", 3))
except Exception as e:
log.error(f"Error unsaving {url}: {e}")
browser.close()
# ── Main ──────────────────────────────────────────────────────────────────────
def main():
log.info("=" * 60)
log.info("Social Media Collection Downloader — Starting")
log.info("=" * 60)
config = load_config()
# Download TikTok
tiktok_downloaded = download_tiktok_collection(config)
log.info(f"TikTok: downloaded {len(tiktok_downloaded)} videos")
# Download Instagram
instagram_downloaded = download_instagram_collection(config)
log.info(f"Instagram: downloaded {len(instagram_downloaded)} posts")
# Unsave TikTok videos
if config.get("tiktok", {}).get("unsave_after_download", True):
unsave_tiktok_videos(tiktok_downloaded, config)
# Unsave Instagram posts
if config.get("instagram", {}).get("unsave_after_download", True):
unsave_instagram_posts(instagram_downloaded, config)
log.info("=" * 60)
log.info(f"Done. TikTok: {len(tiktok_downloaded)} | Instagram: {len(instagram_downloaded)}")
log.info(f"Log saved to: {log_file}")
log.info("=" * 60)
if __name__ == "__main__":
main()

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,7 @@
2026-05-09 19:59:01,573 [INFO] ============================================================
2026-05-09 19:59:01,573 [INFO] Social Media Collection Downloader — Starting
2026-05-09 19:59:01,573 [INFO] ============================================================
2026-05-09 19:59:01,578 [INFO] Fetching TikTok collection URLs from: https://www.tiktok.com/@tuxxxxax787/collection/Download-7626779378428545824
2026-05-09 19:59:04,174 [INFO] Found 24 videos in collection
2026-05-09 19:59:04,174 [INFO] Downloading: https://www.tiktok.com/@alsnr_09/video/7637595662003014944
2026-05-09 19:59:12,895 [INFO] Downloading: https://www.tiktok.com/@alsnr_09/video/7636827375724006689

View File

@@ -0,0 +1,112 @@
2026-05-09 20:00:29,709 [INFO] ============================================================
2026-05-09 20:00:29,709 [INFO] Social Media Collection Downloader — Starting
2026-05-09 20:00:29,709 [INFO] ============================================================
2026-05-09 20:00:29,710 [INFO] Fetching TikTok collection URLs from: https://www.tiktok.com/@tuxxxxax787/collection/Download-7626779378428545824
2026-05-09 20:00:32,465 [INFO] Found 24 videos in collection
2026-05-09 20:00:32,465 [INFO] Downloading: https://www.tiktok.com/@alsnr_09/video/7637595662003014944
2026-05-09 20:00:35,099 [INFO] [OK] Downloaded: https://www.tiktok.com/@alsnr_09/video/7637595662003014944
2026-05-09 20:00:40,100 [INFO] Downloading: https://www.tiktok.com/@alsnr_09/video/7636827375724006689
2026-05-09 20:00:42,958 [INFO] [OK] Downloaded: https://www.tiktok.com/@alsnr_09/video/7636827375724006689
2026-05-09 20:00:47,959 [INFO] Downloading: https://www.tiktok.com/@anniieerose/video/7634839871621926146
2026-05-09 20:00:51,768 [INFO] [OK] Downloaded: https://www.tiktok.com/@anniieerose/video/7634839871621926146
2026-05-09 20:00:56,769 [INFO] Downloading: https://www.tiktok.com/@anniieerose/video/7628766610232331542
2026-05-09 20:01:00,315 [INFO] [OK] Downloaded: https://www.tiktok.com/@anniieerose/video/7628766610232331542
2026-05-09 20:01:05,316 [INFO] Downloading: https://www.tiktok.com/@hannahxgda/video/7633775858339138848
2026-05-09 20:01:08,993 [INFO] [OK] Downloaded: https://www.tiktok.com/@hannahxgda/video/7633775858339138848
2026-05-09 20:01:13,994 [INFO] Downloading: https://www.tiktok.com/@anniieerose/video/7632954468690169110
2026-05-09 20:01:18,790 [INFO] [OK] Downloaded: https://www.tiktok.com/@anniieerose/video/7632954468690169110
2026-05-09 20:01:23,795 [INFO] Downloading: https://www.tiktok.com/@wbr.marina/video/7630954438395563296
2026-05-09 20:01:29,037 [INFO] [OK] Downloaded: https://www.tiktok.com/@wbr.marina/video/7630954438395563296
2026-05-09 20:01:34,038 [INFO] Downloading: https://www.tiktok.com/@anniieerose/video/7632319995040697623
2026-05-09 20:01:38,456 [INFO] [OK] Downloaded: https://www.tiktok.com/@anniieerose/video/7632319995040697623
2026-05-09 20:01:43,457 [INFO] Downloading: https://www.tiktok.com/@wbr.marina/video/7631687184290024736
2026-05-09 20:01:47,424 [INFO] [OK] Downloaded: https://www.tiktok.com/@wbr.marina/video/7631687184290024736
2026-05-09 20:01:52,425 [INFO] Downloading: https://www.tiktok.com/@wbr.marina/video/7632413865589869857
2026-05-09 20:01:55,467 [INFO] [OK] Downloaded: https://www.tiktok.com/@wbr.marina/video/7632413865589869857
2026-05-09 20:02:00,468 [INFO] Downloading: https://www.tiktok.com/@wbr.marina/video/7631687398547590432
2026-05-09 20:02:03,161 [INFO] [OK] Downloaded: https://www.tiktok.com/@wbr.marina/video/7631687398547590432
2026-05-09 20:02:08,162 [INFO] Downloading: https://www.tiktok.com/@anniieerose/video/7631369508929735958
2026-05-09 20:02:11,298 [INFO] [OK] Downloaded: https://www.tiktok.com/@anniieerose/video/7631369508929735958
2026-05-09 20:02:16,299 [INFO] Downloading: https://www.tiktok.com/@anniieerose/video/7631369724319878422
2026-05-09 20:02:19,751 [INFO] [OK] Downloaded: https://www.tiktok.com/@anniieerose/video/7631369724319878422
2026-05-09 20:02:24,752 [INFO] Downloading: https://www.tiktok.com/@anniieerose/video/7631086293740555542
2026-05-09 20:02:28,535 [INFO] [OK] Downloaded: https://www.tiktok.com/@anniieerose/video/7631086293740555542
2026-05-09 20:02:33,538 [INFO] Downloading: https://www.tiktok.com/@anniieerose/video/7631088690676256022
2026-05-09 20:02:36,885 [INFO] [OK] Downloaded: https://www.tiktok.com/@anniieerose/video/7631088690676256022
2026-05-09 20:02:41,886 [INFO] Downloading: https://www.tiktok.com/@anniieerose/video/7630387450480708886
2026-05-09 20:02:45,551 [INFO] [OK] Downloaded: https://www.tiktok.com/@anniieerose/video/7630387450480708886
2026-05-09 20:02:50,552 [INFO] Downloading: https://www.tiktok.com/@hannahxgda/video/7628933958373215521
2026-05-09 20:02:53,297 [INFO] [OK] Downloaded: https://www.tiktok.com/@hannahxgda/video/7628933958373215521
2026-05-09 20:02:58,297 [INFO] Downloading: https://www.tiktok.com/@anniieerose/video/7627690853955489027
2026-05-09 20:03:01,688 [INFO] [OK] Downloaded: https://www.tiktok.com/@anniieerose/video/7627690853955489027
2026-05-09 20:03:06,689 [INFO] Downloading: https://www.tiktok.com/@anniieerose/video/7625324579522710806
2026-05-09 20:03:09,988 [INFO] [OK] Downloaded: https://www.tiktok.com/@anniieerose/video/7625324579522710806
2026-05-09 20:03:14,989 [INFO] Downloading: https://www.tiktok.com/@romy_ronm/video/7612331993858116886
2026-05-09 20:03:17,678 [INFO] [OK] Downloaded: https://www.tiktok.com/@romy_ronm/video/7612331993858116886
2026-05-09 20:03:22,679 [INFO] Downloading: https://www.tiktok.com/@romy_ronm/video/7626392072790035745
2026-05-09 20:03:26,337 [INFO] [OK] Downloaded: https://www.tiktok.com/@romy_ronm/video/7626392072790035745
2026-05-09 20:03:31,337 [INFO] Downloading: https://www.tiktok.com/@latenightfinds603/video/7626247102334586143
2026-05-09 20:03:34,215 [INFO] [OK] Downloaded: https://www.tiktok.com/@latenightfinds603/video/7626247102334586143
2026-05-09 20:03:39,216 [INFO] Downloading: https://www.tiktok.com/@anniieerose/video/7626716959744331030
2026-05-09 20:03:42,528 [INFO] [OK] Downloaded: https://www.tiktok.com/@anniieerose/video/7626716959744331030
2026-05-09 20:03:47,529 [INFO] Downloading: https://www.tiktok.com/@twinsonice_official/video/7625674301684894998
2026-05-09 20:03:51,154 [INFO] [OK] Downloaded: https://www.tiktok.com/@twinsonice_official/video/7625674301684894998
2026-05-09 20:03:56,155 [INFO] TikTok: downloaded 24 videos
2026-05-09 20:03:56,155 [INFO] Instagram disabled in config, skipping.
2026-05-09 20:03:56,155 [INFO] Instagram: downloaded 0 posts
2026-05-09 20:03:56,479 [INFO] Unsaving 24 TikTok videos...
2026-05-09 20:03:58,160 [INFO] Unsaving: https://www.tiktok.com/@alsnr_09/video/7637595662003014944
2026-05-09 20:04:07,384 [WARNING] [WARN] Could not find bookmark button for: https://www.tiktok.com/@alsnr_09/video/7637595662003014944
2026-05-09 20:04:10,386 [INFO] Unsaving: https://www.tiktok.com/@alsnr_09/video/7636827375724006689
2026-05-09 20:04:15,427 [WARNING] [WARN] Could not find bookmark button for: https://www.tiktok.com/@alsnr_09/video/7636827375724006689
2026-05-09 20:04:18,428 [INFO] Unsaving: https://www.tiktok.com/@anniieerose/video/7634839871621926146
2026-05-09 20:04:23,982 [WARNING] [WARN] Could not find bookmark button for: https://www.tiktok.com/@anniieerose/video/7634839871621926146
2026-05-09 20:04:27,077 [INFO] Unsaving: https://www.tiktok.com/@anniieerose/video/7628766610232331542
2026-05-09 20:04:34,892 [WARNING] [WARN] Could not find bookmark button for: https://www.tiktok.com/@anniieerose/video/7628766610232331542
2026-05-09 20:04:37,892 [INFO] Unsaving: https://www.tiktok.com/@hannahxgda/video/7633775858339138848
2026-05-09 20:04:42,765 [WARNING] [WARN] Could not find bookmark button for: https://www.tiktok.com/@hannahxgda/video/7633775858339138848
2026-05-09 20:04:45,765 [INFO] Unsaving: https://www.tiktok.com/@anniieerose/video/7632954468690169110
2026-05-09 20:04:51,523 [WARNING] [WARN] Could not find bookmark button for: https://www.tiktok.com/@anniieerose/video/7632954468690169110
2026-05-09 20:04:54,524 [INFO] Unsaving: https://www.tiktok.com/@wbr.marina/video/7630954438395563296
2026-05-09 20:05:02,892 [WARNING] [WARN] Could not find bookmark button for: https://www.tiktok.com/@wbr.marina/video/7630954438395563296
2026-05-09 20:05:05,892 [INFO] Unsaving: https://www.tiktok.com/@anniieerose/video/7632319995040697623
2026-05-09 20:05:12,095 [ERROR] Error unsaving https://www.tiktok.com/@anniieerose/video/7632319995040697623: Page.goto: Target page, context or browser has been closed
Call log:
- navigating to "https://www.tiktok.com/@anniieerose/video/7632319995040697623", waiting until "networkidle"
2026-05-09 20:05:12,096 [INFO] Unsaving: https://www.tiktok.com/@wbr.marina/video/7631687184290024736
2026-05-09 20:05:12,101 [ERROR] Error unsaving https://www.tiktok.com/@wbr.marina/video/7631687184290024736: Page.goto: Target page, context or browser has been closed
2026-05-09 20:05:12,105 [INFO] Unsaving: https://www.tiktok.com/@wbr.marina/video/7632413865589869857
2026-05-09 20:05:12,126 [ERROR] Error unsaving https://www.tiktok.com/@wbr.marina/video/7632413865589869857: Page.goto: Target page, context or browser has been closed
2026-05-09 20:05:12,129 [INFO] Unsaving: https://www.tiktok.com/@wbr.marina/video/7631687398547590432
2026-05-09 20:05:12,133 [ERROR] Error unsaving https://www.tiktok.com/@wbr.marina/video/7631687398547590432: Page.goto: Target page, context or browser has been closed
2026-05-09 20:05:12,138 [INFO] Unsaving: https://www.tiktok.com/@anniieerose/video/7631369508929735958
2026-05-09 20:05:12,143 [ERROR] Error unsaving https://www.tiktok.com/@anniieerose/video/7631369508929735958: Page.goto: Target page, context or browser has been closed
2026-05-09 20:05:12,144 [INFO] Unsaving: https://www.tiktok.com/@anniieerose/video/7631369724319878422
2026-05-09 20:05:12,160 [ERROR] Error unsaving https://www.tiktok.com/@anniieerose/video/7631369724319878422: Page.goto: Target page, context or browser has been closed
2026-05-09 20:05:12,160 [INFO] Unsaving: https://www.tiktok.com/@anniieerose/video/7631086293740555542
2026-05-09 20:05:12,163 [ERROR] Error unsaving https://www.tiktok.com/@anniieerose/video/7631086293740555542: Page.goto: Target page, context or browser has been closed
2026-05-09 20:05:12,164 [INFO] Unsaving: https://www.tiktok.com/@anniieerose/video/7631088690676256022
2026-05-09 20:05:12,200 [ERROR] Error unsaving https://www.tiktok.com/@anniieerose/video/7631088690676256022: Page.goto: Target page, context or browser has been closed
2026-05-09 20:05:12,206 [INFO] Unsaving: https://www.tiktok.com/@anniieerose/video/7630387450480708886
2026-05-09 20:05:12,211 [ERROR] Error unsaving https://www.tiktok.com/@anniieerose/video/7630387450480708886: Page.goto: Target page, context or browser has been closed
2026-05-09 20:05:12,211 [INFO] Unsaving: https://www.tiktok.com/@hannahxgda/video/7628933958373215521
2026-05-09 20:05:12,219 [ERROR] Error unsaving https://www.tiktok.com/@hannahxgda/video/7628933958373215521: Page.goto: Target page, context or browser has been closed
2026-05-09 20:05:12,220 [INFO] Unsaving: https://www.tiktok.com/@anniieerose/video/7627690853955489027
2026-05-09 20:05:12,224 [ERROR] Error unsaving https://www.tiktok.com/@anniieerose/video/7627690853955489027: Page.goto: Target page, context or browser has been closed
2026-05-09 20:05:12,224 [INFO] Unsaving: https://www.tiktok.com/@anniieerose/video/7625324579522710806
2026-05-09 20:05:12,229 [ERROR] Error unsaving https://www.tiktok.com/@anniieerose/video/7625324579522710806: Page.goto: Target page, context or browser has been closed
2026-05-09 20:05:12,230 [INFO] Unsaving: https://www.tiktok.com/@romy_ronm/video/7612331993858116886
2026-05-09 20:05:12,239 [ERROR] Error unsaving https://www.tiktok.com/@romy_ronm/video/7612331993858116886: Page.goto: Target page, context or browser has been closed
2026-05-09 20:05:12,241 [INFO] Unsaving: https://www.tiktok.com/@romy_ronm/video/7626392072790035745
2026-05-09 20:05:12,244 [ERROR] Error unsaving https://www.tiktok.com/@romy_ronm/video/7626392072790035745: Page.goto: Target page, context or browser has been closed
2026-05-09 20:05:12,247 [INFO] Unsaving: https://www.tiktok.com/@latenightfinds603/video/7626247102334586143
2026-05-09 20:05:12,264 [ERROR] Error unsaving https://www.tiktok.com/@latenightfinds603/video/7626247102334586143: Page.goto: Target page, context or browser has been closed
2026-05-09 20:05:12,269 [INFO] Unsaving: https://www.tiktok.com/@anniieerose/video/7626716959744331030
2026-05-09 20:05:12,277 [ERROR] Error unsaving https://www.tiktok.com/@anniieerose/video/7626716959744331030: Page.goto: Target page, context or browser has been closed
2026-05-09 20:05:12,277 [INFO] Unsaving: https://www.tiktok.com/@twinsonice_official/video/7625674301684894998
2026-05-09 20:05:12,281 [ERROR] Error unsaving https://www.tiktok.com/@twinsonice_official/video/7625674301684894998: Page.goto: Target page, context or browser has been closed
2026-05-09 20:05:13,047 [INFO] ============================================================
2026-05-09 20:05:13,047 [INFO] Done. TikTok: 24 | Instagram: 0
2026-05-09 20:05:13,051 [INFO] Log saved to: C:\Users\timoh\Documents\New project\logs\session_20260509_200029.log
2026-05-09 20:05:13,051 [INFO] ============================================================

2
requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
yt-dlp>=2024.1.1
playwright>=1.40.0

164
setup.py Normal file
View File

@@ -0,0 +1,164 @@
"""
Setup wizard for Social Media Collection Downloader.
Run this first to configure your collections, cookies, and options.
"""
import json
import sys
import os
import subprocess
from pathlib import Path
CONFIG_DIR = Path(__file__).parent / "config"
CONFIG_PATH = CONFIG_DIR / "config.json"
CONFIG_DIR.mkdir(exist_ok=True)
def check_dependency(cmd: str, name: str, install_hint: str) -> bool:
try:
subprocess.run([cmd, "--version"], capture_output=True, check=True)
print(f"{name} found")
return True
except (FileNotFoundError, subprocess.CalledProcessError):
print(f"{name} not found — {install_hint}")
return False
def check_dependencies():
print("\n── Checking dependencies ──────────────────────────────────")
ok = True
ok &= check_dependency("yt-dlp", "yt-dlp", "pip install yt-dlp")
try:
from playwright.sync_api import sync_playwright
print(" ✓ Playwright found")
except ImportError:
print(" ✗ Playwright not found — pip install playwright && playwright install chromium")
ok = False
return ok
def prompt(question: str, default: str = "") -> str:
if default:
val = input(f" {question} [{default}]: ").strip()
return val if val else default
else:
return input(f" {question}: ").strip()
def prompt_bool(question: str, default: bool = True) -> bool:
d = "Y/n" if default else "y/N"
val = input(f" {question} [{d}]: ").strip().lower()
if not val:
return default
return val in ("y", "yes", "1", "true")
def setup_platform(platform: str) -> dict:
print(f"\n── {platform} Setup ─────────────────────────────────────────")
cfg: dict = {}
enabled = prompt_bool(f"Enable {platform}?", True)
cfg["enabled"] = enabled
if not enabled:
return cfg
print(f"""
How to get your cookies:
1. Install "Get cookies.txt LOCALLY" browser extension
2. Log into {platform.lower()}.com
3. Click the extension and export cookies as JSON (or Netscape .txt)
4. Save the file anywhere and enter the path below
OR: let yt-dlp read cookies directly from your browser (Chrome/Firefox)
(your browser must be closed or the profile must be accessible)
""")
method = prompt("Cookie method — (1) cookies file (2) browser auto-read", "1")
if method == "2":
browser = prompt("Browser name (chrome / firefox / edge / safari)", "chrome")
cfg["cookies_from_browser"] = browser
cfg["cookies_file"] = None
else:
cookies_path = prompt("Path to cookies file (JSON or Netscape txt)", "")
cfg["cookies_file"] = cookies_path if cookies_path else None
cfg["cookies_from_browser"] = None
print(f"\n Enter your {platform} collection/saved URLs.")
print(" TikTok: Go to your profile → Collections → open a collection → copy URL")
print(" Instagram: Go to profile → Saved → open a collection → copy URL")
print(" (Enter one per line, blank line when done)\n")
collections = []
while True:
url = input(" Collection URL (or press Enter to finish): ").strip()
if not url:
break
if url.startswith("http"):
collections.append(url)
print(f" Added: {url}")
else:
print(" ⚠ That doesn't look like a URL, skipping.")
cfg["collections"] = collections
cfg["unsave_after_download"] = prompt_bool("Unsave/remove posts after downloading?", True)
cfg["headless"] = prompt_bool("Run browser in headless mode (invisible) for unsaving?", False)
delay_dl = prompt("Seconds to wait between downloads (avoid rate limits)", "2")
cfg["delay_between_downloads"] = int(delay_dl) if delay_dl.isdigit() else 2
delay_un = prompt("Seconds to wait between unsaves", "3")
cfg["delay_between_unsaves"] = int(delay_un) if delay_un.isdigit() else 3
return cfg
def save_config(config: dict):
with open(CONFIG_PATH, "w") as f:
json.dump(config, f, indent=2)
print(f"\n ✓ Config saved to {CONFIG_PATH}")
def print_banner():
print("""
╔══════════════════════════════════════════════════════════╗
║ Social Media Collection Downloader — Setup ║
║ TikTok & Instagram → Download → Auto-unsave ║
╚══════════════════════════════════════════════════════════╝
""")
def main():
print_banner()
deps_ok = check_dependencies()
if not deps_ok:
print("\n Please install missing dependencies first, then re-run setup.py")
print("\n Quick install:")
print(" pip install yt-dlp playwright")
print(" playwright install chromium\n")
sys.exit(1)
config = {}
config["tiktok"] = setup_platform("TikTok")
config["instagram"] = setup_platform("Instagram")
print("\n── Summary ─────────────────────────────────────────────────")
for platform in ["tiktok", "instagram"]:
p = config[platform]
status = "✓ Enabled" if p.get("enabled") else "✗ Disabled"
n = len(p.get("collections", []))
print(f" {platform.capitalize()}: {status} | {n} collection(s)")
if prompt_bool("\n Save this config and run now?", True):
save_config(config)
print("\n Running downloader...\n")
subprocess.run([sys.executable, str(Path(__file__).parent / "downloader.py")])
else:
save_config(config)
print("\n Config saved. Run later with:")
print(" python downloader.py\n")
if __name__ == "__main__":
main()