reelminner
README.md
<p align="center">
<img src="assets/icon.ico" alt="Reelminner" width="96" height="96" />
</p>
<h1 align="center">π¬ Reelminner</h1>
<p align="center"><b>Instagram Reel & Profile Scraper β Pro</b></p>
<p align="center">
<img alt="Python" src="https://img.shields.io/badge/Python-3.10%2B-3776AB?logo=python&logoColor=white" />
<img alt="Playwright" src="https://img.shields.io/badge/Engine-Playwright-2EAD33?logo=playwright&logoColor=white" />
<img alt="License" src="https://img.shields.io/badge/License-MIT-green" />
<img alt="Platform" src="https://img.shields.io/badge/Platform-Windows%20%7C%20WSL%2FLinux-lightgrey" />
</p>
<p align="center">
Extract <b>reel metadata</b>, <b>owner profiles</b>, <b>follower counts</b>, <b>music</b>,
<b>likes / comments / views</b> and more from any public Instagram Reel β through a
desktop GUI, a oneβcommand CLI, a Python API, or an <b>MCP server</b> that AI agents can drive.
</p>
> π‘ **Name note:** This project's final public name is **Reelminner**. The Python
> engine class is `Reelminner` (see `scraper.py`), the CLI/GUI and MCP server are
> branded `reelminner`, and the GitHub repository is **`reelminner`**. The earlier
> working codename *ReelSnipe* has been fully retired. Other name ideas are listed in
> [Name options](#-name-options).
---
## π Table of Contents
- [What is Reelminner](#what-is-reelminner)
- [β¨ Features](#-features)
- [π§ How it works](#-how-it-works)
- [ποΈ Project architecture](#οΈ-project-architecture)
- [π¦ Installation](#-installation)
- [π Quick Start](#-quick-start)
- [π» Usage](#-usage)
- [Desktop GUI](#1-desktop-gui)
- [Command Line (CLI)](#2-command-line-cli)
- [MCP Server (for AI agents)](#3-mcp-server-for-ai-agents)
- [Python API](#4-python-api)
- [π Output format](#-output-format)
- [βοΈ Configuration](#οΈ-configuration)
- [ποΈ Project structure](#οΈ-project-structure)
- [π§ͺ Testing & QA](#-testing--qa)
- [π¦ Building a standalone EXE](#-building-a-standalone-exe)
- [β οΈ Legal & ethical disclaimer](#οΈ-legal--ethical-disclaimer)
- [π€ Contributing](#-contributing)
- [π License](#-license)
- [π Troubleshooting & FAQ](#-troubleshooting--faq)
- [π·οΈ Name](#-name)
---
## What is Reelminner
**Reelminner** is an openβsource toolkit that pulls structured data out of Instagram Reels
and the profiles that posted them. It is built around a single, reusable engine
(`Reelminner`) that is exposed four different ways:
| Interface | File | Best for |
|-----------|------|----------|
| π₯οΈ Desktop GUI | `gui.py` | Nonβtechnical users, oneβclick scraping |
| β¨οΈ CLI | `scraper.py` | Power users, batch jobs, scripts |
| π€ MCP server | `mcp_server.py` | AI agents / LLM workflows |
| π Python API | import `scraper` | Embedding inside your own code |
Everything shares the **same parsing, session, and rateβlimit logic**, so results are
identical no matter which frontβend you use.
---
## β¨ Features
- **Multiβsource reel parsing** β Reelminner reads data from several layers (embedded JSON,
GraphQL responses, and a live DOM fallback) so it keeps working even when Instagram
changes one of them.
- **Owner profile enrichment** β for every reel it can autoβfetch the poster's
`username`, `full_name`, `bio`, `followers`, `is_verified`, and `reels_count`.
- **Follower count extraction** β pulled via Instagram's GraphQL `UserByRestrictedView`
/ `GraphQLOwnerInfo` query, with a DOM fallback and **pagination** (handles capped
follower figures like β1.2Mβ by scrolling the profile).
- **Music metadata** β reel audio `music_title`, `music_artist`, and `music_id`.
- **Engagement metrics** β `views`, `likes`, `comments`, and the direct `video_url` /
`thumbnail`.
- **Session & login management** β interactive QR/login, cookie import from
**EditThisCookie** exports, and a 24βhour session refresh so you don't reβlogin constantly.
- **Concurrent scraping** β a thread pool (`--workers`, default 3) with polite interβrequest
delays (`--delay`, default 2s) and **adaptive backβoff** when Instagram throws
`BLOCKED` / `RATE_LIMITED`.
- **Resilient status tracking** β every row carries a `status` code
(`OK`, `PARSED_PARTIAL`, `FAILED`, `NO_DATA`, `BLOCKED`, `RATE_LIMITED`) so you know
exactly what succeeded.
- **Multiple export formats** β CSV (default), JSON, and Excel (`.xlsx` via `openpyxl`).
- **MCP server** β five stable tools so an AI agent (Claude, Cursor, etc.) can scrape,
check status, import cookies, stop, and export.
- **Desktop GUI** β builtβin dark theme, pasteβURL box, live results table, rightβclick
*copy URL* / *open reel*, and oneβclick export.
- **Tested** β pytest suite + an endβtoβend QA harness that enforces dataβquality gates.
---
## π§ How it works
```
ββββββββββββββ ββββββββββββββ ββββββββββββββ ββββββββββββββ
β GUI β β CLI β β MCP srv β β Python β
β gui.py β β scraper.py β βmcp_server β β import β
βββββββ¬βββββββ βββββββ¬βββββββ βββββββ¬βββββββ βββββββ¬βββββββ
ββββββββββββββββββ΄βββββββββββββββββ΄βββββββββββββββββ
βΌ
βββββββββββββββββββββββββ
β Reelminner β β the engine (scraper.py)
β β’ session / cookies β
β β’ thread pool β
β β’ adaptive backβoff β
βββββββββββββ¬ββββββββββββ
βΌ
βββββββββββββββββββββββββ
β parsers.py β β pure extraction helpers
β parse_reel_page / jsonβ
β parse_owner / music β
β regex adapters β
βββββββββββββββββββββββββ
```
1. **Normalize** the input URL (`normalize_reel_url`) so `/reel/X/` and `/reel/s/β¦/` both work.
2. **Load session** β apply saved cookies (`sessionid`, `csrftoken`, `ds_user_id`, `ig_did`,
`mid`, `rur`) or log in.
3. **Fetch & parse** the reel page with a layered fallback:
- `parse_reel_page` β embedded `window.__additionalData` / `sharedData` HTML JSON
- `parse_reel_json` β raw GraphQL `GQL` response
- `parse_graphql_reel` β `shortcodeMedia` object
- DOM fallback β `_extract_text_raw` queries the live page for
likes / comments / plays / followers via regex adapters.
4. **Enrich owner** (unless `--no-profiles`): fetch the profile and read
`followers`, `full_name`, `bio`, `is_verified`, `reels_count`.
5. **Respect limits**: sleep `delay` between requests; if blocked, back off and retry.
6. **Write** rows to CSV / JSON / Excel with a `status` per row.
---
## ποΈ Project architecture
Reelminner is a **singleβengine, multiβinterface** design. One core engine
(`Reelminner`) does all the real work; the GUI, CLI, MCP server, and
Python API are thin frontβends that call into it. This keeps parsing, session
handling, and rateβlimiting identical across every entry point.
```
βββββββββββββββββββββββββββββββ
URL(s) in βββββββΆβ Reelminner β scraper.py
β ββ engine / orchestrator ββ β
βββββββββ¬ββββββββββββ¬βββββββββββ
run scrapes β β enrich owner
βΌ βΌ
ββββββββββββββββββ ββββββββββββββββββββ
β parsers.py β β session + graphqlβ
β pure extractors β β (followers/music)β
βββββββββ¬βββββββββ βββββββββββ¬βββββββββ
βββββββββββ¬βββββββββββββ
βΌ
ReelData row + status
βΌ
CSV / JSON / Excel writers
```
### Module responsibilities
| File | Role | Key public symbols |
|------|------|--------------------|
| `scraper.py` | **Core engine** + CLI. Owns the browser, session, thread pool, and writers. | `Reelminner`, `scrape()`, `login()`, `has_session()`, `save_cookies_from_file()`, `clear_session()`, `write_csv`, `export_json`, `export_excel`, `normalize_reel_url`, `csv_columns`, `ReelData`, `DEFAULT_STATE_FILE` |
| `parsers.py` | **Pure extraction helpers** β no browser, easy to unitβtest. | `parse_reel_page`, `parse_reel_json`, `parse_graphql_reel`, `parse_owner_username_from_html`, `parse_music`, `parse_count`, `parse_caption`, `parse_graphql_followers`, `parse_profile_card` |
| `gui.py` | **Tkinter desktop app**. Builds the window, menu, URL box, workers slider, results table, and export dialogs. | `ReelminnerGUI`, `build()`, `scrape()`, `export_*`, `copy_url()`, `open_reel()` |
| `theme.py` | **GUI styling** β applies the dark theme to `ttk` widgets. | `apply_dark_theme(root)` |
| `mcp_server.py` | **MCP server** β exposes the engine as 5 tools for AI agents over stdio. | `mcp` (FastMCP), `scrape_reels`, `get_status`, `import_cookies`, `stop_scrape`, `export_results` |
| `build_exe.py` | **Packaging** β PyInstaller oneβfile build. | `EXE(...)`, `COLLECT`/`Analysis` |
| `run_qa.py` | **QA harness** β runs the engine over a corpus and enforces dataβquality gates. | `run_qa()`, gate checks, `qa_report.json` |
### Engine internals (`Reelminner`)
- **Session layer** β `_SESSION_COOKIE_NAMES` (`sessionid`, `csrftoken`, `ds_user_id`,
`ig_did`, `mid`, `rur`); `_apply_cookies()`, `_refresh_if_needed()` (24h),
`login()` (interactive QR), `clear_session()`.
- **Concurrency** β `scrape()` spins up a `ThreadPoolExecutor(max_workers=workers)`;
each URL is handled by `_worker` β `_scrape_url`, which calls `_gather_metadata`
(reel data) and optionally `_gather_article` (owner profile). A semaphore +
`_sleep()` enforce politeness; `status_code` / `retcode` drive an adaptive
retry/backβoff loop when Instagram returns `BLOCKED` / `RATE_LIMITED`.
- **Parsing pipeline (layered fallback)** β inside `_gather_metadata` the engine tries,
in order: `parse_reel_page` (embedded HTML JSON) β `parse_reel_json` (raw GraphQL
`GQL`) β `parse_graphql_reel` (`shortcodeMedia`) β DOM fallback via the
`_extract_text_html` / `_extract_text_raw` adapters and the `_PATTERNS` regex list
(likes/comments/plays/followers).
- **Profile enrichment** β `get_follower_count()` uses Instagram's GraphQL
`UserByRestrictedView` / `GraphQLOwnerInfo` query, falling back to the DOM and
paginating followers (`_fetch_followers` with `end_cursor`) when counts are capped.
- **Output** β rows are collected as `ReelData` dicts and written by `write_csv`
(respecting `csv_columns`), `export_json`, or `export_excel` (needs `openpyxl`).
### Why this layout
- **Testability** β all parsing lives in `parsers.py` with no browser dependency, so
`tests/test_parsers.py` can assert on saved HTML/JSON fixtures.
- **One source of truth** β every interface shares the same `Reelminner`, so
a fix in the engine benefits the GUI, CLI, and MCP server simultaneously.
- **Safe packaging** β the GUI/CLI thin shells mean the PyInstaller EXE only bundles
the engine + a minimal UI, keeping the binary small.
---
## π¦ Installation
> Requirements: **Python 3.10+** and the **Playwright** browser engine.
```bash
# 1. Clone
git clone https://github.com/ilovekushgola/reelminner.git
cd reelminner
# 2. (Recommended) create a virtual environment
python -m venv .venv
.venv\Scripts\activate # Windows
# source .venv/bin/activate # macOS / Linux
# 3. Install dependencies
pip install -r requirements.txt
# 4. Install the Chromium browser for Playwright
playwright install chromium
```
> **GUI only:** the desktop app uses `tkinter`, which ships with standard Python installs.
> No extra package needed. The GUI is most polished on **Windows**.
Optional dev/test tools:
```bash
pip install -r requirements-dev.txt # pytest, coverage
```
> π‘ **Before you start:** Reelminner works best with a loggedβin Instagram session β
> some reels and *all* owner/follower data require authentication. Run
> `python scraper.py --login` once (interactive QR), or import cookies exported from the
> **EditThisCookie** browser extension with `python scraper.py --import-cookies cookies.json`.
> It only reads **public** content you're already allowed to view.
---
## π Quick Start
```bash
# Scrape a single reel from the command line
python scraper.py "https://www.instagram.com/reel/CxXYZ123/"
# β¦or many reels from a file (one URL per line)
python scraper.py -f urls.txt -o export.csv
# Launch the desktop GUI
python gui.py
```
---
## π» Usage
### 1. Desktop GUI
```bash
python gui.py
```
- Click **Login** (optional but recommended β improves success rate).
- Paste one reel URL per line into the box (or `Ctrl+A` to select all).
- Drag the **Workers** slider, then click **Scrape**.
- Watch results appear in the table.
- **Rightβclick** a row to *Copy URL* or *Open Reel*.
- **Export** to CSV / Excel / JSON, or **Open results folder**.
The last results are autoβsaved to `results/_last_results.json`.
### 2. Command Line (CLI)
```bash
python scraper.py [URL ...] [options]
```
| Flag | Default | Description |
|------|---------|-------------|
| `urls` | β | One or more reel URLs (positional). |
| `-f`, `--file` | β | Text file with one reel URL per line. |
| `--login` | off | Open a browser to log in interactively (QR). |
| `--import-cookies FILE` | β | Import an **EditThisCookie** JSON export. |
| `--clear-session` | off | Delete the saved `storage_state.json`. |
| `--headless` | off | Run the browser without a window. |
| `-w`, `--workers` | `3` | Number of concurrent scrape threads. |
| `--delay` | `2.0` | Seconds to wait between requests. |
| `--state` | `storage_state.json` | Path for the saved session. |
| `-o`, `--output` | `reels_results.csv` | Output CSV path. |
| `--no-profiles` | off | Skip autoβfetching owner follower data. |
```bash
# Headless, 5 workers, 1s delay, no profile enrichment
python scraper.py -f reels.txt -w 5 --delay 1 --headless --no-profiles -o out.csv
```
### 3. MCP Server (for AI agents)
Reelminner ships an **MCP (Model Context Protocol)** server so an AI client can drive it.
```bash
python mcp_server.py # stdio transport
```
Configure your MCP client (`.mcp.json` is included in the repo):
```json
{
"mcpServers": {
"reelminner": {
"command": "python",
"args": ["mcp_server.py"],
"cwd": ".",
"env": { "RMIN_HEADLESS": "true" }
}
}
}
```
**Tools exposed (5, stable):**
| Tool | Signature | Purpose |
|------|-----------|---------|
| `scrape_reels` | `(urls, workers, delay, headless, with_profiles)` | Run a scrape job. |
| `get_status` | `()` | Current progress / last result summary. |
| `import_cookies` | `(json_path)` | Load cookies from an EditThisCookie file. |
| `stop_scrape` | `()` | Stop the running job. |
| `export_results` | `(path, fmt)` | Export to `csv` / `json` / `xlsx`. |
**Environment overrides:** `RMIN_HEADLESS`, `RMIN_WORKERS`, `RMIN_DELAY`, `RMIN_WITH_PROFILES`.
### 4. Python API
```python
from scraper import Reelminner, write_csv
scraper = Reelminner(workers=3, delay=2.0, headless=True)
rows, report = scraper.scrape(
["https://www.instagram.com/reel/CxXYZ123/"],
with_profiles=True,
)
write_csv(rows, "out.csv")
for r in rows:
print(r["username"], r["followers"], r["likes"], r["status"])
```
Key members of `Reelminner`:
- `scrape(urls, with_profiles=True)` β `(rows, report)`
- `login()` β interactive login
- `has_session()` / `save_cookies_from_file(path)` / `clear_session()`
- `write_csv(rows, path)`, `export_json(rows, path)`, `export_excel(rows, path)`
- `normalize_reel_url(url)` β public helper
- `csv_columns` β the ordered list of output fields
- `DEFAULT_STATE_FILE` β default `storage_state.json`
---
## π Output format
Each reel becomes one row. The full CSV schema (`scraper.csv_columns`):
| Column | Description |
|--------|-------------|
| `idx` | Row index. |
| `username` | Reel owner handle (e.g. `natgeo`). |
| `followers` | Owner follower count (may be `follower_min`β`follower_max`). |
| `full_name` | Owner display name. |
| `bio` | Owner biography text. |
| `is_verified` | `True` / `False`. |
| `reels_count` | Number of reels on the owner profile. |
| `profile_url` | Link to the owner profile. |
| `reel_url` | Canonical reel URL. |
| `reel_id` | Instagram reel shortcode / ID. |
| `caption` | Reel caption text. |
| `upload_date` | Post timestamp. |
| `views` | Play / view count. |
| `likes` | Like count. |
| `comments` | Comment count. |
| `video_url` | Direct video file URL. |
| `thumbnail` | Thumbnail image URL. |
| `music_title` | Audio track title. |
| `music_artist` | Audio artist. |
| `music_id` | Audio / music ID. |
| `scrape_ts` | When this row was scraped (ISO timestamp). |
| `status` | `OK` Β· `PARSED_PARTIAL` Β· `FAILED` Β· `NO_DATA` Β· `BLOCKED` Β· `RATE_LIMITED`. |
---
## βοΈ Configuration
**Cookies / session**
- Log in with `python scraper.py --login` (saves `storage_state.json`).
- Or export cookies from your browser via the *EditThisCookie* extension and run
`python scraper.py --import-cookies cookies.json`.
**Environment variables** (used by MCP server & CLI defaults)
| Variable | Effect |
|----------|--------|
| `RMIN_HEADLESS` | `true`/`false` β run browser headless. |
| `RMIN_WORKERS` | Default worker count. |
| `RMIN_DELAY` | Default delay between requests (seconds). |
| `RMIN_WITH_PROFILES` | `true`/`false` β autoβenrich owner profiles. |
A template is provided: copy `mcp.env.example` β `mcp.env` to override MCP defaults.
---
## ποΈ Project structure
```
reelminner/
βββ scraper.py # Core engine: Reelminner + CLI
βββ gui.py # Tkinter desktop application
βββ parsers.py # Pure extraction helpers (HTML/JSON/music/regex)
βββ mcp_server.py # MCP server (5 tools for AI agents)
βββ theme.py # Darkβtheme styling for the GUI
βββ build_exe.py # PyInstaller build script
βββ Reelminner.spec # PyInstaller spec (oneβfile EXE)
βββ run_qa.py # Endβtoβend QA harness with dataβquality gates
βββ requirements.txt # Runtime dependencies
βββ requirements-dev.txt# Dev / test dependencies
βββ mcp.env.example # MCP env template
βββ .mcp.json # MCP client configuration
βββ assets/ # Icons (icon.ico)
βββ docs/ # SKILL.md, E2E test/fix plan
βββ skills/ # Agent skill definition
βββ tests/ # pytest suite + corpus.txt
βββ results/ # Scrape outputs (gitβignored)
```
---
## π§ͺ Testing & QA
```bash
# Unit / integration tests
pytest -q
# Endβtoβend dataβquality run (uses your saved session)
python run_qa.py # full run over tests/corpus.txt
python run_qa.py --quick # 1 URL, headless, fast iteration
python run_qa.py --url <reel> # custom single URL
python run_qa.py --report-only # show last qa_report.json
```
The QA harness enforces gates such as parsedβrate, verifiedβrate, nonβemptyβrate,
blockedβrate, and max runtime, and writes `results/qa/qa_report.json` +
`qa_results.csv`.
---
## π¦ Building a standalone EXE
On Windows, produce a portable `.exe` (no Python needed by end users):
```bash
pip install pyinstaller
python build_exe.py
```
Output: `dist/Reelminner.exe` (oneβfile build via `Reelminner.spec`).
---
## β οΈ Legal & ethical disclaimer
> Reelminner is provided **for educational and authorized/personal use only**.
>
> - Scraping Instagram may violate its **Terms of Service**. Use it only on
> content you own or are permitted to access.
> - Respect rate limits (`--delay`, fewer `--workers`) and **do not** use it for
> spam, harassment, or commercial bulk extraction.
> - You are responsible for how you use this tool and for complying with
> applicable laws (incl. GDPR / privacy regulations) in your jurisdiction.
> - The authors are **not affiliated with Instagram/Meta** and accept no liability.
---
## π Troubleshooting & FAQ
**`playwright` says the browser isn't installed / pages won't open**
β Make sure you ran both `pip install -r requirements.txt` **and**
`playwright install chromium`. Without the Chromium download nothing will launch.
**Most fields are empty, or I get `BLOCKED` / `RATE_LIMITED`**
β Log in (`python scraper.py --login`) or import cookies, then slow down:
`--delay 4` and fewer workers (`-w 1`). Instagram throttles anonymous/unauthenticated
traffic hardest, so an authenticated session is the single biggest success factor.
**A reel returns `NO_DATA`**
β The post may be private, deleted, or regionβlocked, or Instagram served a login wall.
Try again with a loggedβin session.
**The GUI window won't open or fonts look wrong**
β The GUI uses Python's builtβin `tkinter`. On Windows it's most polished. On Linux/macOS
install the Tk package if the window fails to launch (e.g. `sudo apt install python3-tk`).
**`ModuleNotFoundError` when I run a script**
β You're likely outside the repo or its virtual environment. `cd` into the project folder
and activate the venv (`.venv\Scripts\activate` on Windows, `source .venv/bin/activate`
on macOS/Linux) before running `python scraper.py`.
**How do I scrape lots of reels at once?**
β Put one URL per line in a text file and run
`python scraper.py -f urls.txt -o out.csv`.
**Can an AI agent use this?**
β Yes β run `python mcp_server.py` and point any MCP client (Claude Desktop, Cursor, etc.)
at the included `.mcp.json`. See [MCP Server](#3-mcp-server-for-ai-agents).
---
## π€ Contributing
1. Fork the repo and create a feature branch.
2. `pip install -r requirements-dev.txt`
3. Add/adjust tests in `tests/`; run `pytest` and `python run_qa.py --quick`.
4. Open a pull request describing the change and the QA result.
---
## π License
Released under the **MIT License** β see [LICENSE](LICENSE).
---
## π·οΈ Name
The project's final public name is **Reelminner** ("Reel miner"). Earlier internal
codenames have been retired. If you fork it you can rename it to anything you like β
just update the title in `gui.py` and this README.
<p align="center">Made with β€οΈ for the openβsource scraping community.</p>
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues