Merge pull request #4 from ksanjeev284/feature/observability-enhancements

fix: --limit flag now works correctly + video download with audio
This commit is contained in:
Sanjeev Kumar 2025-12-14 07:10:00 +05:30 committed by GitHub
commit 89991bcc4a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 766 additions and 6 deletions

View file

@ -38,6 +38,22 @@ python main.py --dashboard
# Opens at http://localhost:8501 # Opens at http://localhost:8501
``` ```
### 📋 Requirements
- **Python 3.8+**
- **ffmpeg** (optional, for video with audio)
```bash
# Windows (via chocolatey)
choco install ffmpeg
# macOS
brew install ffmpeg
# Ubuntu/Debian
sudo apt install ffmpeg
```
--- ---
## 📖 All Commands ## 📖 All Commands

536
docs/BLOG.md Normal file
View file

@ -0,0 +1,536 @@
# Building the Ultimate Reddit Scraper: A Full-Featured, API-Free Data Collection Suite
![Reddit Scraper](https://img.shields.io/badge/Reddit-Scraper-FF4500?style=for-the-badge&logo=reddit&logoColor=white)
![Python](https://img.shields.io/badge/Python-3.10+-3776AB?style=for-the-badge&logo=python&logoColor=white)
![Docker](https://img.shields.io/badge/Docker-Ready-2496ED?style=for-the-badge&logo=docker&logoColor=white)
**December 2024** | By Sanjeev Kumar
---
## TL;DR
I built a **complete Reddit scraper suite** that requires **zero API keys**. It comes with a beautiful Streamlit dashboard, REST API for integration with tools like Grafana and Metabase, plugin system for post-processing, scheduled scraping, notifications, and much more. Best of all—it's completely open source.
🔗 **GitHub**: [reddit-universal-scraper](https://github.com/ksanjeev284/reddit-universal-scraper)
---
## The Problem
If you've ever tried to scrape Reddit data for analysis, research, or just personal projects, you know the pain:
1. **Reddit's API is heavily rate-limited** (especially after the 2023 API changes)
2. **API keys require approval** and are increasingly restricted
3. **Existing scrapers are often single-purpose** - scrape posts OR comments, not both
4. **No easy way to visualize or analyze the data** after scraping
5. **Running scrapes manually is tedious** - you want automation
I decided to solve all of these problems at once.
---
## The Solution: Universal Reddit Scraper Suite
After weeks of development, I created a full-featured scraper that:
| Feature | What It Does |
|---------|--------------|
| 📊 **Full Scraping** | Posts, comments, images, videos, galleries—everything |
| 🚫 **No API Keys** | Uses Reddit's public JSON endpoints and mirrors |
| 📈 **Web Dashboard** | Beautiful 7-tab Streamlit UI for analysis |
| 🚀 **REST API** | Connect Metabase, Grafana, DuckDB, and more |
| 🔌 **Plugin System** | Extensible post-processing (sentiment analysis, deduplication, keywords) |
| 📅 **Scheduled Scraping** | Cron-style automation |
| 📧 **Notifications** | Discord & Telegram alerts when scrapes complete |
| 🐳 **Docker Ready** | One command to deploy anywhere |
---
## Architecture Deep Dive
### How It Works Without API Keys
The secret sauce is in the approach. Instead of using Reddit's official (and restricted) API, I leverage:
1. **Reddit's public JSON endpoints**: Every Reddit page has a `.json` suffix that returns structured data
2. **Multiple mirror fallbacks**: When one source is rate-limited, the scraper automatically rotates through alternatives like Redlib instances
3. **Smart rate limiting**: Built-in delays and cool-down periods to stay under the radar
```python
MIRRORS = [
"https://old.reddit.com",
"https://redlib.catsarch.com",
"https://redlib.vsls.cz",
"https://r.nf",
"https://libreddit.northboot.xyz",
"https://redlib.tux.pizza"
]
```
When one source fails, it automatically tries the next. No manual intervention needed.
### The Core Scraping Engine
The scraper operates in three modes:
**1. Full Mode** - The complete package
```bash
python main.py python --mode full --limit 100
```
This scrapes posts, downloads all media (images, videos, galleries), and fetches comments with their full thread hierarchy.
**2. History Mode** - Fast metadata-only
```bash
python main.py python --mode history --limit 500
```
Perfect for quickly building a dataset of post metadata without the overhead of media downloads.
**3. Monitor Mode** - Live watching
```bash
python main.py python --mode monitor
```
Continuously checks for new posts every 5 minutes. Ideal for tracking breaking news or trending discussions.
---
## The Dashboard Experience
One of the standout features is the **7-tab Streamlit dashboard** that makes data exploration a joy:
### 📊 Overview Tab
At a glance, see:
- Total posts and comments
- Cumulative score across all posts
- Media post breakdown
- Posts-over-time chart
- Top 10 posts by score
### 📈 Analytics Tab
This is where it gets interesting:
- **Sentiment Analysis**: Run VADER-based sentiment scoring on your entire dataset
- **Keyword Cloud**: See the most frequently used terms
- **Best Posting Times**: Data-driven insights on when posts get the most engagement
### 🔍 Search Tab
Full-text search across all scraped data with filters for:
- Minimum score
- Post type (text, image, video, gallery, link)
- Author
- Custom sorting
### 💬 Comments Analysis
- View top-scoring comments
- See who the most active commenters are
- Track comment patterns over time
### ⚙️ Scraper Controls
Start new scrapes right from the dashboard! Configure:
- Target subreddit/user
- Post limits
- Mode (full/history)
- Media and comment toggles
### 📋 Job History
Full observability into every scrape job:
- Status tracking (running, completed, failed)
- Duration metrics
- Post/comment/media counts
- Error logging
### 🔌 Integrations
Pre-configured instructions for connecting:
- Metabase
- Grafana
- DreamFactory
- DuckDB
---
## The Plugin Architecture
I designed a plugin system to allow extensible post-processing. The architecture is simple but powerful:
```python
class Plugin:
"""Base class for all plugins."""
name = "base"
description = "Base plugin"
enabled = True
def process_posts(self, posts):
return posts
def process_comments(self, comments):
return comments
```
### Built-in Plugins
**1. Sentiment Tagger**
Analyzes the emotional tone of every post and comment using VADER sentiment analysis:
```python
class SentimentTagger(Plugin):
name = "sentiment_tagger"
description = "Adds sentiment scores and labels to posts"
def process_posts(self, posts):
for post in posts:
text = f"{post.get('title', '')} {post.get('selftext', '')}"
score, label = analyze_sentiment(text)
post['sentiment_score'] = score
post['sentiment_label'] = label
return posts
```
**2. Deduplicator**
Removes duplicate posts that may appear across multiple scraping sessions.
**3. Keyword Extractor**
Pulls out the most significant terms from your scraped content for trend analysis.
### Creating Your Own Plugin
Drop a new Python file in the `plugins/` directory:
```python
from plugins import Plugin
class MyCustomPlugin(Plugin):
name = "my_plugin"
description = "Does something cool"
enabled = True
def process_posts(self, posts):
# Your logic here
return posts
```
Enable plugins during scraping:
```bash
python main.py python --mode full --plugins
```
---
## REST API for External Integrations
The REST API opens up the scraper to a whole ecosystem of tools:
```bash
python main.py --api
# API at http://localhost:8000
# Docs at http://localhost:8000/docs
```
### Key Endpoints
| Endpoint | Description |
|----------|-------------|
| `GET /posts` | List posts with filters (subreddit, limit, offset) |
| `GET /comments` | List comments |
| `GET /subreddits` | All scraped subreddits |
| `GET /jobs` | Job history |
| `GET /query?sql=...` | Raw SQL queries for power users |
| `GET /grafana/query` | Grafana-compatible time-series data |
### Real-World Integration: Grafana Dashboard
1. Install the "JSON API" or "Infinity" plugin in Grafana
2. Add datasource pointing to `http://localhost:8000`
3. Use the `/grafana/query` endpoint for time-series panels
```sql
SELECT date(created_utc) as time, COUNT(*) as posts
FROM posts GROUP BY date(created_utc)
```
Now you have a real-time dashboard tracking Reddit activity!
---
## Scheduled Scraping & Notifications
### Automation Made Easy
Set up recurring scrapes with cron-style scheduling:
```bash
# Scrape every 60 minutes
python main.py --schedule delhi --every 60
# With custom options
python main.py --schedule delhi --every 30 --mode full --limit 50
```
### Get Notified
Configure Discord or Telegram alerts when scrapes complete:
```bash
# Environment variables
export DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/..."
export TELEGRAM_BOT_TOKEN="123456:ABC..."
export TELEGRAM_CHAT_ID="987654321"
```
Now you get notified with scrape summaries directly in your preferred platform.
---
## Dry Run Mode: Test Before You Commit
One of my favorite features is **dry run mode**. It simulates the entire scrape without saving any data:
```bash
python main.py python --mode full --limit 50 --dry-run
```
Output:
```
🧪 DRY RUN MODE - No data will be saved
🧪 DRY RUN COMPLETE!
📊 Would scrape: 100 posts
💬 Would scrape: 245 comments
```
Perfect for:
- Testing your scrape configuration
- Estimating data volume before committing
- Debugging without cluttering your dataset
---
## Docker Deployment
### Quick Start
```bash
# Build
docker build -t reddit-scraper .
# Run a scrape
docker run -v ./data:/app/data reddit-scraper python --limit 100
# Run with plugins
docker run -v ./data:/app/data reddit-scraper python --plugins
```
### Full Stack with Docker Compose
```bash
docker-compose up -d
```
This spins up:
- Dashboard at `http://localhost:8501`
- REST API at `http://localhost:8000`
### Deploy to Any VPS
```bash
ssh user@your-server-ip
git clone https://github.com/ksanjeev284/reddit-universal-scraper.git
cd reddit-universal-scraper
docker-compose up -d
```
Open the firewall:
```bash
sudo ufw allow 8000
sudo ufw allow 8501
```
You now have a production-ready Reddit scraping platform!
---
## Data Export Options
### CSV (Default)
All scraped data is saved as CSV files:
- `data/r_<subreddit>/posts.csv`
- `data/r_<subreddit>/comments.csv`
### Parquet (Analytics-Optimized)
Export to columnar format for analytics tools:
```bash
python main.py --export-parquet python
```
Query directly with DuckDB:
```python
import duckdb
duckdb.query("SELECT * FROM 'data/parquet/*.parquet'").df()
```
### Database Maintenance
```bash
# Backup
python main.py --backup
# Optimize/vacuum
python main.py --vacuum
# View job history
python main.py --job-history
```
---
## Data Schema
### Posts Table
| Column | Description |
|--------|-------------|
| `id` | Reddit post ID |
| `title` | Post title |
| `author` | Username |
| `score` | Net upvotes |
| `num_comments` | Comment count |
| `post_type` | text/image/video/gallery/link |
| `selftext` | Post body (for text posts) |
| `created_utc` | Timestamp |
| `permalink` | Reddit URL |
| `is_nsfw` | NSFW flag |
| `flair` | Post flair |
| `sentiment_score` | -1.0 to 1.0 (with plugins) |
### Comments Table
| Column | Description |
|--------|-------------|
| `comment_id` | Comment ID |
| `post_permalink` | Parent post URL |
| `author` | Username |
| `body` | Comment text |
| `score` | Upvotes |
| `depth` | Nesting level |
| `is_submitter` | Whether author is OP |
---
## Use Cases
### 1. Academic Research
- Analyze subreddit community dynamics
- Track sentiment over time during events
- Study user engagement patterns
### 2. Market Research
- Monitor brand mentions
- Track product feedback
- Identify emerging trends
### 3. Content Creation
- Find popular topics in your niche
- Analyze what makes posts go viral
- Discover optimal posting times
### 4. Data Journalism
- Archive discussions around breaking news
- Analyze public sentiment during events
- Track narrative evolution
### 5. Personal Projects
- Build a dataset for ML training
- Create Reddit-based recommendation systems
- Archive communities you care about
---
## Performance Considerations
### Respect Reddit's Servers
The scraper includes built-in delays:
- **3 second cooldown** between API requests
- **30 second wait** if all mirrors fail
- **Automatic mirror rotation** to distribute load
### Optimize Your Scrapes
- Use `--mode history` for faster metadata-only scrapes
- Use `--no-media` if you don't need images/videos
- Use `--no-comments` for post-only data
### Handle Large Datasets
- Parquet export for analytics queries
- SQLite database for structured storage
- Automatic deduplication to avoid bloat
---
## What's Next? Roadmap
I'm actively developing new features:
- [ ] **Async scraping** for even faster data collection
- [ ] **Multi-subreddit monitoring** in a single command
- [ ] **Email notifications** in addition to Discord/Telegram
- [ ] **Cloud deployment templates** (AWS, GCP, Azure)
- [ ] **Web-based scraper configuration** (no CLI needed)
---
## Getting Started
### Prerequisites
- Python 3.10+
- pip
### Installation
```bash
# Clone the repo
git clone https://github.com/ksanjeev284/reddit-universal-scraper.git
cd reddit-universal-scraper
# Install dependencies
pip install -r requirements.txt
# Your first scrape
python main.py python --mode full --limit 50
# Launch the dashboard
python main.py --dashboard
```
That's it! You're now scraping Reddit like a pro.
---
## Contributing
This is an open-source project and contributions are welcome! Whether it's:
- Bug fixes
- New plugins
- Documentation improvements
- Feature suggestions
Open an issue or submit a PR on [GitHub](https://github.com/ksanjeev284/reddit-universal-scraper).
---
## Conclusion
The Universal Reddit Scraper Suite represents months of work solving a problem that many data enthusiasts face. By combining a robust scraping engine with analytics capabilities, a beautiful dashboard, and extensive integration options—all without requiring API keys—I hope this tool empowers you to unlock insights from Reddit's vast treasure trove of community discussions.
**Happy scraping!** 🤖
---
*If you found this useful, consider giving the project a ⭐ on [GitHub](https://github.com/ksanjeev284/reddit-universal-scraper)!*
---
## Connect
- **GitHub**: [@ksanjeev284](https://github.com/ksanjeev284)
- **Project**: [reddit-universal-scraper](https://github.com/ksanjeev284/reddit-universal-scraper)
---
*Tags: Reddit, Web Scraping, Python, Data Analysis, Streamlit, REST API, Docker, Open Source*

103
main.py
View file

@ -12,6 +12,8 @@ import argparse
import random import random
import sys import sys
import json import json
import subprocess
import tempfile
from urllib.parse import urlparse from urllib.parse import urlparse
from pathlib import Path from pathlib import Path
@ -166,6 +168,97 @@ def download_media(url, save_path, media_type="image"):
pass pass
return False return False
def download_reddit_video_with_audio(video_url, save_path):
"""
Downloads Reddit video with audio by fetching both streams and merging.
Reddit stores video and audio separately - this combines them.
"""
try:
if os.path.exists(save_path):
return True
# Try to find the audio URL by replacing video quality with audio
# Reddit videos have audio at URLs like .../DASH_audio.mp4 or .../DASH_AUDIO_128.mp4
base_url = video_url.rsplit('/', 1)[0]
# Common audio URL patterns
audio_urls = [
f"{base_url}/DASH_audio.mp4",
f"{base_url}/DASH_AUDIO_128.mp4",
f"{base_url}/DASH_AUDIO_64.mp4",
f"{base_url}/audio.mp4",
f"{base_url}/audio"
]
# Download video to temp file first
with tempfile.NamedTemporaryFile(suffix='_video.mp4', delete=False) as video_temp:
video_temp_path = video_temp.name
response = SESSION.get(video_url, timeout=60, stream=True)
if response.status_code != 200:
return False
for chunk in response.iter_content(chunk_size=8192):
video_temp.write(chunk)
# Try to download audio
audio_temp_path = None
for audio_url in audio_urls:
try:
response = SESSION.get(audio_url, timeout=30, stream=True)
if response.status_code == 200:
with tempfile.NamedTemporaryFile(suffix='_audio.mp4', delete=False) as audio_temp:
audio_temp_path = audio_temp.name
for chunk in response.iter_content(chunk_size=8192):
audio_temp.write(chunk)
break
except:
continue
if audio_temp_path:
# Merge video and audio using ffmpeg
try:
cmd = [
'ffmpeg', '-y', '-hide_banner', '-loglevel', 'error',
'-i', video_temp_path,
'-i', audio_temp_path,
'-c:v', 'copy', '-c:a', 'aac',
'-shortest', save_path
]
result = subprocess.run(cmd, capture_output=True, timeout=120)
if result.returncode == 0:
# Cleanup temp files
os.unlink(video_temp_path)
os.unlink(audio_temp_path)
return True
else:
# ffmpeg failed, fall back to video only
print(f" ⚠️ ffmpeg merge failed, saving video without audio")
os.rename(video_temp_path, save_path)
os.unlink(audio_temp_path)
return True
except FileNotFoundError:
# ffmpeg not installed, save video only
print(f" ⚠️ ffmpeg not found, saving video without audio")
os.rename(video_temp_path, save_path)
if audio_temp_path:
os.unlink(audio_temp_path)
return True
except Exception as e:
# Other error, save video only
os.rename(video_temp_path, save_path)
if audio_temp_path and os.path.exists(audio_temp_path):
os.unlink(audio_temp_path)
return True
else:
# No audio found, just use video
os.rename(video_temp_path, save_path)
return True
except Exception as e:
# Cleanup any temp files on error
pass
return False
def download_post_media(post_data, dirs, post_id): def download_post_media(post_data, dirs, post_id):
"""Downloads all media from a post.""" """Downloads all media from a post."""
media = get_media_urls(post_data) media = get_media_urls(post_data)
@ -187,7 +280,11 @@ def download_post_media(post_data, dirs, post_id):
if 'youtube' not in vid_url: if 'youtube' not in vid_url:
ext = '.mp4' ext = '.mp4'
save_path = os.path.join(dirs["videos"], f"{post_id}_{i}{ext}") save_path = os.path.join(dirs["videos"], f"{post_id}_{i}{ext}")
if download_media(vid_url, save_path, "video"): # Use enhanced download for Reddit videos (includes audio)
if 'v.redd.it' in vid_url or 'reddit.com' in vid_url:
if download_reddit_video_with_audio(vid_url, save_path):
downloaded["videos"] += 1
elif download_media(vid_url, save_path, "video"):
downloaded["videos"] += 1 downloaded["videos"] += 1
return downloaded return downloaded
@ -354,7 +451,9 @@ def run_full_history(target, limit, is_user=False, download_media_flag=True,
else: else:
path = f"/r/{target}/new.json" path = f"/r/{target}/new.json"
target_url = f"{base_url}{path}?limit=100&raw_json=1" # Use proper batch size - min of remaining posts needed or 100 (Reddit's max per request)
batch_size = min(100, limit - total_posts)
target_url = f"{base_url}{path}?limit={batch_size}&raw_json=1"
if after: if after:
target_url += f"&after={after}" target_url += f"&after={after}"

View file

@ -15,6 +15,8 @@ import sys
sys.path.insert(0, str(Path(__file__).parent.parent)) sys.path.insert(0, str(Path(__file__).parent.parent))
from config import USER_AGENT, MIRRORS, ASYNC_MAX_CONCURRENT, ASYNC_BATCH_SIZE from config import USER_AGENT, MIRRORS, ASYNC_MAX_CONCURRENT, ASYNC_BATCH_SIZE
import subprocess
import tempfile
# Semaphore to limit concurrent requests # Semaphore to limit concurrent requests
semaphore = None semaphore = None
@ -33,14 +35,14 @@ async def fetch_json(session, url, retries=3):
await asyncio.sleep(2) await asyncio.sleep(2)
return None return None
async def fetch_posts_page(session, base_url, target, after=None, is_user=False): async def fetch_posts_page(session, base_url, target, after=None, is_user=False, batch_size=100):
"""Fetch a single page of posts.""" """Fetch a single page of posts."""
if is_user: if is_user:
path = f"/user/{target}/submitted.json" path = f"/user/{target}/submitted.json"
else: else:
path = f"/r/{target}/new.json" path = f"/r/{target}/new.json"
url = f"{base_url}{path}?limit=100&raw_json=1" url = f"{base_url}{path}?limit={batch_size}&raw_json=1"
if after: if after:
url += f"&after={after}" url += f"&after={after}"
@ -65,6 +67,107 @@ async def download_media_async(session, url, save_path):
pass pass
return False return False
async def download_reddit_video_with_audio_async(session, video_url, save_path):
"""
Downloads Reddit video with audio asynchronously.
Reddit stores video and audio separately - this combines them using ffmpeg.
"""
global semaphore
if os.path.exists(save_path):
return True
async with semaphore:
try:
# Find audio URL by replacing video quality with audio
base_url = video_url.rsplit('/', 1)[0]
audio_urls = [
f"{base_url}/DASH_audio.mp4",
f"{base_url}/DASH_AUDIO_128.mp4",
f"{base_url}/DASH_AUDIO_64.mp4",
f"{base_url}/audio.mp4",
f"{base_url}/audio"
]
# Download video to temp file
video_temp = tempfile.NamedTemporaryFile(suffix='_video.mp4', delete=False)
video_temp_path = video_temp.name
video_temp.close()
try:
async with session.get(video_url, timeout=aiohttp.ClientTimeout(total=60)) as response:
if response.status != 200:
return False
async with aiofiles.open(video_temp_path, 'wb') as f:
async for chunk in response.content.iter_chunked(8192):
await f.write(chunk)
except:
if os.path.exists(video_temp_path):
os.unlink(video_temp_path)
return False
# Try to download audio
audio_temp_path = None
for audio_url in audio_urls:
try:
async with session.get(audio_url, timeout=aiohttp.ClientTimeout(total=30)) as response:
if response.status == 200:
audio_temp = tempfile.NamedTemporaryFile(suffix='_audio.mp4', delete=False)
audio_temp_path = audio_temp.name
audio_temp.close()
async with aiofiles.open(audio_temp_path, 'wb') as f:
async for chunk in response.content.iter_chunked(8192):
await f.write(chunk)
break
except:
continue
if audio_temp_path:
# Merge video and audio using ffmpeg
try:
cmd = [
'ffmpeg', '-y', '-hide_banner', '-loglevel', 'error',
'-i', video_temp_path,
'-i', audio_temp_path,
'-c:v', 'copy', '-c:a', 'aac',
'-shortest', save_path
]
proc = await asyncio.create_subprocess_exec(
*cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
await asyncio.wait_for(proc.wait(), timeout=120)
if proc.returncode == 0:
os.unlink(video_temp_path)
os.unlink(audio_temp_path)
return True
else:
# ffmpeg failed, use video only
os.rename(video_temp_path, save_path)
os.unlink(audio_temp_path)
return True
except FileNotFoundError:
# ffmpeg not installed
os.rename(video_temp_path, save_path)
if audio_temp_path and os.path.exists(audio_temp_path):
os.unlink(audio_temp_path)
return True
except Exception:
os.rename(video_temp_path, save_path)
if audio_temp_path and os.path.exists(audio_temp_path):
os.unlink(audio_temp_path)
return True
else:
# No audio found, just use video
os.rename(video_temp_path, save_path)
return True
except Exception:
pass
return False
async def fetch_comments_async(session, permalink): async def fetch_comments_async(session, permalink):
"""Fetch comments asynchronously.""" """Fetch comments asynchronously."""
global semaphore global semaphore
@ -242,7 +345,9 @@ async def scrape_async(target, limit=100, is_user=False, download_media=True, sc
data = None data = None
for mirror in mirrors: for mirror in mirrors:
data = await fetch_posts_page(session, mirror, target, after, is_user) # Use proper batch size
batch_size = min(100, limit - total_fetched)
data = await fetch_posts_page(session, mirror, target, after, is_user, batch_size)
if data: if data:
print(f"✅ Fetched from {mirror}") print(f"✅ Fetched from {mirror}")
break break
@ -288,7 +393,11 @@ async def scrape_async(target, limit=100, is_user=False, download_media=True, sc
for i, vid_url in enumerate(media['videos'][:2]): for i, vid_url in enumerate(media['videos'][:2]):
if 'youtube' not in vid_url: if 'youtube' not in vid_url:
save_path = f"{videos_dir}/{post['id']}_{i}.mp4" save_path = f"{videos_dir}/{post['id']}_{i}.mp4"
media_tasks.append(download_media_async(session, vid_url, save_path)) # Use enhanced download for Reddit videos (includes audio)
if 'v.redd.it' in vid_url or 'reddit.com' in vid_url:
media_tasks.append(download_reddit_video_with_audio_async(session, vid_url, save_path))
else:
media_tasks.append(download_media_async(session, vid_url, save_path))
# Queue comment fetching # Queue comment fetching
if scrape_comments and post['num_comments'] > 0: if scrape_comments and post['num_comments'] > 0: