instagram-videos · git:20260709.ec45941 · 2026-07-09 · sha256 e868209cc27a7dfa
instagram-videos git:20260709.ec45941A
Immutable. This exact content is served forever at /api/v1/blob/e868209cc27a7dfa.
---
name: instagram-videos
description: Download all public videos/reels from an Instagram profile to a local folder, named with upload date-time (YYYY-MM-DD_HH-MM-SS.mp4). Uses Picnob via browser automation plus a Python download script. Use when the user asks to download Instagram videos, reels, or video content from a public profile, save IG videos locally, or batch-download profile reels with datetime filenames.
---
# Instagram Videos Downloader
Download every public video/reel from an Instagram profile into a user-specified folder, named by upload datetime.
**Do not use** `instaloader`, `yt-dlp`, or `gallery-dl` on profile URLs first — they return 401 without a session cookie. Use Picnob.
## Inputs
| Parameter | Required | Example |
|---|---|---|
| Instagram handle or profile URL | yes | `arnoldo_schaffner_bofill` |
| Output directory | yes | `lolo/videos` |
| Timezone for filenames | no | `America/Santiago` (default) |
Extract handle from URLs: `instagram.com/{handle}/` → `{handle}`.
## Workflow checklist
```
- [ ] Step 1: Verify profile is public
- [ ] Step 2: Scrape Picnob profile feed (browser)
- [ ] Step 3: Save posts JSON
- [ ] Step 4: Run download script
- [ ] Step 5: Report results
```
---
## Step 1: Verify profile is public
Open `https://www.instagram.com/{handle}/` in the browser. If it shows a login wall or "This Account is Private", **stop** — this skill is public profiles only.
## Step 2: Scrape Picnob via browser MCP
Navigate to `https://www.picnob.com/profile/{handle}/`.
Run via `browser_cdp` → `Runtime.evaluate` with `awaitPromise: true`:
```javascript
(async () => {
let lastCount = 0, stable = 0;
for (let i = 0; i < 60; i++) {
window.scrollTo(0, document.body.scrollHeight);
await new Promise(r => setTimeout(r, 1500));
const c = document.querySelectorAll('.post_box').length;
if (c === lastCount) { if (++stable >= 3) break; } else { stable = 0; }
lastCount = c;
}
const posts = [];
document.querySelectorAll('.post_box').forEach((box, idx) => {
const a = box.querySelector('a[href*="/post/"]');
const m = a?.href?.match(/\/post\/([^/?#]+)/);
const counts = Array.from(box.querySelectorAll('.num')).map(e => e.textContent.trim());
posts.push({
idx, pId: m?.[1],
caption: box.querySelector('.sum')?.textContent?.trim() || '',
time: box.querySelector('.time')?.textContent?.trim() || '',
likes: counts[0], comments: counts[1],
downloadUrl: box.querySelector('.downbtn')?.href,
isVideo: !!box.querySelector('.icon_video, [class*=video]'),
isSidecar: !!box.querySelector('.icon_album, [class*=sidecar], [class*=album]'),
picnobHref: a?.href,
});
});
return posts;
})()
```
## Step 3: Save posts JSON
Save the result to `{project}/posts-raw/meta/picnob_{handle}.json`. Create `posts-raw/` if needed; add to `.gitignore` if not already there.
## Step 4: Download and rename
```bash
python3 ~/.cursor/skills/instagram-videos/scripts/download_profile_videos.py \
--posts-json posts-raw/meta/picnob_{handle}.json \
--output-dir {output_folder} \
--timezone America/Santiago
```
The script:
1. Filters videos (`.mp4` in `downloadUrl` or `isVideo: true`)
2. Downloads each file from Picnob CDN URLs
3. Fetches exact `uploadDate` from each post's JSON-LD schema on Picnob
4. Renames to `YYYY-MM-DD_HH-MM-SS.mp4` in the chosen timezone
5. Writes `{output_folder}/videos_manifest.json`
**Video detection note:** Picnob often omits the video icon on reels. Always treat posts with `.mp4` in `downloadUrl` as videos — the script handles this.
**Collisions:** Two posts at the same second get `_2`, `_3` suffixes.
## Step 5: Report to user
Summarize: handle, output path, count downloaded, any errors, and list filenames sorted newest-first.
---
## Anti-patterns
1. **Do not** scrape faster than ~1 req/sec on Picnob post pages (date lookup).
2. **Do not** commit `posts-raw/` — scraping artifacts belong in `.gitignore`.
3. **Do not** use Picnob `pId` as Instagram shortcode — it's an internal ID.
4. **Do not** scrape private profiles.
## Utility scripts
| Script | Purpose |
|---|---|
| `scripts/download_profile_videos.py` | Download + datetime rename (main entry point) |
| `scripts/scrape_picnob.js` | Reference for browser-side scrape logic |
## Additional resources
- Real run example: [examples.md](examples.md)
- Full post/caption scraping (images, sidecars): use the `instagram-scraper` skill