--- title: "Scraping Social Media Data with Python: X (Twitter), Reddit & Instagram (2026)" author: "ZyVOP" published: "2026-07-21T06:09:20.030Z" updated: "2026-08-29T04:15:00.130Z" url: "https://zyvop.com/scraping-social-media-data-with-python-x-twitter-reddit-instagram-2026-k7og3" canonical: "https://zyvop.com/scraping-social-media-data-with-python-x-twitter-reddit-instagram-2026-k7og3" tags: ["instagram data scraping python","scrape reddit python tutorial","social media sentiment analysis python scraping","python social media scraping tutorial 2026","scrape twitter x python 2026"] --- # Scraping Social Media Data with Python: X (Twitter), Reddit & Instagram (2026) Learn how to scrape X (Twitter), Reddit, and Instagram with Python in 2026. Working code for tweets, subreddit posts, profiles, and sentiment analysis — with anti-bot handling. ![Cover image]() Over 500 million posts are published on X (formerly Twitter) every single day. Reddit hosts more than 100,000 active communities discussing everything from machine learning to local weather. Instagram's 2 billion users generate a continuous stream of product opinions, lifestyle signals, and trend indicators. Social media data is the closest thing the internet has to real-time public opinion. It powers: - **Sentiment analysis** — What do people think about your product, brand, or competitor right now? - **Trend detection** — What topics are accelerating before they go mainstream? - **Academic research** — Studying misinformation, political discourse, crisis communication - **Market intelligence** — Tracking competitor mentions, industry conversations - **AI training datasets** — Social text is gold for fine-tuning conversational models The challenge in 2026: platforms have made scraping harder than ever. The official X API now starts at $100/month and the free tier is almost useless for research. Instagram aggressively blocks headless browsers. Reddit has rate-limited its API severely after the 2023 controversy. This guide gives you working approaches for all three platforms — from DIY Python scrapers to managed tools — with honest assessments of what each method can and cannot do. * * * ## The 2026 Reality Check: What's Still Scrapable Before writing a single line of code, here's the honest state of social media scraping in 2026: | Platform | Public Posts | Profile Data | Followers | DMs | Difficulty | | --- | --- | --- | --- | --- | --- | | **X / Twitter** | ✅ (with effort) | ✅ | ✅ | ❌ | Hard | | **Reddit** | ✅ (via API/PRAW) | ✅ | ❌ | ❌ | Easy–Medium | | **Instagram** | ⚠️ Public only | ⚠️ Public only | ❌ | ❌ | Very Hard | | **TikTok** | ✅ (via mobile API) | ✅ | ❌ | ❌ | Hard | | **LinkedIn** | ⚠️ (see Blog 02) | ⚠️ (see Blog 02) | ❌ | ❌ | Hard | Key principles: - **Never scrape private data, DMs, or anything behind a login you don't own** - **Always respect robots.txt and Terms of Service for your use case** - **For academic or commercial research, official APIs or licensed providers are the safer path** * * * ## Part 1: Scraping X (Twitter) in 2026 ### The Landscape X's public guest API was effectively removed in 2023. Almost every endpoint now requires a logged-in session and a CSRF token. The internal GraphQL API that underpins X's own web interface is the main DIY scraping target — but its endpoint identifiers change regularly. Three working approaches exist in 2026: | Method | Cost | Volume | Reliability | Best for | | --- | --- | --- | --- | --- | | X API v2 (official) | $100+/month | Limited | High | Authorised developers | | Playwright + session | Free | Low-medium | Medium | Research, personal use | | ScrapeGraphAI | Paid per req | High | High | Production | ### Method 1: X API v2 (Official) For authorised projects, the official API is always the right choice. The free tier gives 500k tweets/month read access: ```bash pip install tweepy ``` ```python import tweepy import pandas as pd from datetime import datetime, timezone, timedelta # Get credentials at developer.twitter.com client = tweepy.Client(bearer_token="YOUR_BEARER_TOKEN") def search_recent_tweets( query: str, max_results: int = 100, days_back: int = 7 ) -> pd.DataFrame: """ Search tweets from the last 7 days using X API v2. Args: query: Search query. Supports operators like: - AND/OR: "python scraping OR web crawling" - Exact: '"machine learning" tutorial' - Exclude: "python -is:retweet" - Language: "python lang:en" - Has media: "python has:images" """ start_time = datetime.now(timezone.utc) - timedelta(days=days_back) # Append filters to reduce noise full_query = f"{query} -is:retweet lang:en" tweets_data = [] paginator = tweepy.Paginator( client.search_recent_tweets, query=full_query, tweet_fields=[ "created_at", "public_metrics", "author_id", "lang", "context_annotations", "entities" ], user_fields=["name", "username", "public_metrics", "verified"], expansions=["author_id"], start_time=start_time, max_results=min(100, max_results), ) users_map = {} for response in paginator: if not response.data: break # Build user lookup from includes if response.includes and "users" in response.includes: for user in response.includes["users"]: users_map[user.id] = user for tweet in response.data: user = users_map.get(tweet.author_id) metrics = tweet.public_metrics or {} tweets_data.append({ "tweet_id": str(tweet.id), "text": tweet.text, "created_at": tweet.created_at, "author_id": str(tweet.author_id), "username": user.username if user else None, "name": user.name if user else None, "followers": user.public_metrics.get("followers_count") if user else None, "retweets": metrics.get("retweet_count", 0), "likes": metrics.get("like_count", 0), "replies": metrics.get("reply_count", 0), "quotes": metrics.get("quote_count", 0), "impressions": metrics.get("impression_count", 0), "url": f"https://x.com/{user.username if user else 'i'}/status/{tweet.id}", }) if len(tweets_data) >= max_results: break df = pd.DataFrame(tweets_data) print(f"Collected {len(df)} tweets for query: '{query}'") return df # Example: track brand mentions df = search_recent_tweets( query="python web scraping 2026", max_results=200, days_back=7 ) df.to_csv("tweets.csv", index=False) print(df[["username", "text", "likes", "retweets"]].head(10)) ``` ### Method 2: Playwright-Based X Scraper (No API Key) For personal research without API access, Playwright can scrape public search results and profiles using a saved session — exactly as described for LinkedIn in Blog 02: ```python import asyncio import random import json from playwright.async_api import async_playwright from playwright_stealth import stealth_async async def save_x_session(): """Log in to X manually once and save cookies. Run this first.""" async with async_playwright() as p: browser = await p.chromium.launch(headless=False) context = await browser.new_context( user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120" ) page = await context.new_page() await page.goto("https://x.com/login") print("Log in manually, then press Enter...") input() cookies = await context.cookies() with open("x_cookies.json", "w") as f: json.dump(cookies, f) print(f"Saved {len(cookies)} cookies.") await browser.close() async def scrape_x_search(query: str, scroll_times: int = 10) -> list[dict]: """ Scrape X search results using a saved session. Returns a list of tweet dicts. """ async with async_playwright() as p: browser = await p.chromium.launch( headless=True, args=["--disable-blink-features=AutomationControlled"] ) context = await browser.new_context( user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120", viewport={"width": 1280, "height": 900} ) # Load saved session with open("x_cookies.json") as f: await context.add_cookies(json.load(f)) page = await context.new_page() await stealth_async(page) # Navigate to search encoded = query.replace(" ", "%20") await page.goto( f"https://x.com/search?q={encoded}&src=typed_query&f=top", wait_until="domcontentloaded" ) await asyncio.sleep(random.uniform(2, 4)) tweets = [] seen_ids = set() for scroll in range(scroll_times): # Extract tweet data from the current viewport tweet_articles = await page.query_selector_all("article[data-testid='tweet']") for article in tweet_articles: try: # Get tweet text text_el = await article.query_selector("[data-testid='tweetText']") text = await text_el.inner_text() if text_el else None # Username user_el = await article.query_selector("[data-testid='User-Name'] a") href = await user_el.get_attribute("href") if user_el else "" username = href.strip("/").split("/")[-1] if href else None # Display name name_els = await article.query_selector_all( "[data-testid='User-Name'] span" ) display_name = None for el in name_els: txt = await el.inner_text() if txt and not txt.startswith("@"): display_name = txt break # Engagement stats async def get_stat(testid): el = await article.query_selector(f"[data-testid='{testid}']") if el: txt = await el.inner_text() return txt.strip() or "0" return "0" likes = await get_stat("like") replies = await get_stat("reply") retweets = await get_stat("retweet") # Time time_el = await article.query_selector("time") posted_at = await time_el.get_attribute("datetime") if time_el else None # Unique ID to deduplicate tweet_id = f"{username}_{posted_at}" if tweet_id in seen_ids or not text: continue seen_ids.add(tweet_id) tweets.append({ "username": username, "display_name": display_name, "text": text, "likes": likes, "replies": replies, "retweets": retweets, "posted_at": posted_at, }) except Exception: continue # Scroll down for more tweets await page.evaluate("window.scrollBy(0, window.innerHeight * 1.5)") await asyncio.sleep(random.uniform(1.5, 3.0)) print(f" Scroll {scroll+1}/{scroll_times} — {len(tweets)} tweets collected") await browser.close() print(f"\nTotal unique tweets: {len(tweets)}") return tweets # Run tweets = asyncio.run(scrape_x_search("python scraping 2026", scroll_times=8)) df = pd.DataFrame(tweets) df.to_csv("x_search_results.csv", index=False) ``` * * * ## Part 2: Reddit Scraping with PRAW Reddit is the most developer-friendly major social platform for data collection. Its official Python wrapper PRAW (Python Reddit API Wrapper) is free, well-documented, and gives access to posts, comments, user profiles, and subreddit data at no cost. ```bash pip install praw pandas ``` Register a Reddit app at [reddit.com/prefs/apps]() (takes 2 minutes, free) to get your client ID and secret. ### Scraping subreddit posts ```python import praw import pandas as pd from datetime import datetime, timezone reddit = praw.Reddit( client_id="YOUR_CLIENT_ID", client_secret="YOUR_CLIENT_SECRET", user_agent="PythonResearchBot/1.0 by u/your_username", ) def scrape_subreddit( subreddit_name: str, sort: str = "hot", # "hot", "new", "top", "rising" time_filter: str = "week", # "hour", "day", "week", "month", "year", "all" limit: int = 500 ) -> pd.DataFrame: """ Scrape posts from a subreddit. Best for: topic research, trend monitoring, content analysis. """ sub = reddit.subreddit(subreddit_name) # Choose sort method if sort == "hot": posts_gen = sub.hot(limit=limit) elif sort == "new": posts_gen = sub.new(limit=limit) elif sort == "top": posts_gen = sub.top(time_filter=time_filter, limit=limit) elif sort == "rising": posts_gen = sub.rising(limit=limit) else: posts_gen = sub.hot(limit=limit) records = [] for post in posts_gen: records.append({ "post_id": post.id, "title": post.title, "text": post.selftext[:500] if post.selftext else None, "author": str(post.author) if post.author else "[deleted]", "score": post.score, "upvote_ratio": post.upvote_ratio, "num_comments": post.num_comments, "url": post.url, "permalink": f"https://reddit.com{post.permalink}", "flair": post.link_flair_text, "is_self": post.is_self, "created_utc": datetime.fromtimestamp(post.created_utc, tz=timezone.utc).isoformat(), "subreddit": subreddit_name, }) df = pd.DataFrame(records) print(f"Scraped {len(df)} posts from r/{subreddit_name}") return df # Example: research Python discussions df = scrape_subreddit("learnpython", sort="top", time_filter="month", limit=500) df.to_csv("reddit_learnpython.csv", index=False) print(df[["title", "score", "num_comments"]].head(10)) ``` ### Scraping post comments (deep dive) ```python def scrape_post_comments( post_url: str, max_comments: int = 200, include_replies: bool = False ) -> pd.DataFrame: """ Scrape all comments from a Reddit post. Useful for sentiment analysis, topic deep-dives, building datasets. """ submission = reddit.submission(url=post_url) # Replace "MoreComments" objects to get all comments submission.comments.replace_more(limit=0) all_comments = submission.comments.list() if include_replies else submission.comments records = [] for comment in list(all_comments)[:max_comments]: if not hasattr(comment, "body"): continue records.append({ "comment_id": comment.id, "author": str(comment.author) if comment.author else "[deleted]", "body": comment.body, "score": comment.score, "depth": comment.depth, "created_utc": datetime.fromtimestamp( comment.created_utc, tz=timezone.utc ).isoformat(), "is_op": comment.is_submitter, "awards": comment.total_awards_received, }) return pd.DataFrame(records) # Example: deep dive on a specific post comments_df = scrape_post_comments( "https://www.reddit.com/r/MachineLearning/comments/example/", max_comments=300 ) print(f"Top comment: {comments_df.sort_values('score', ascending=False).iloc[0]['body'][:200]}") ``` ### Multi-subreddit keyword monitoring ```python def monitor_keyword_across_subreddits( keyword: str, subreddits: list[str], limit_per_sub: int = 100 ) -> pd.DataFrame: """ Search for a keyword across multiple subreddits. Great for competitive intelligence and brand monitoring. """ all_posts = [] for sub_name in subreddits: print(f"Searching r/{sub_name} for '{keyword}'...") try: sub = reddit.subreddit(sub_name) for post in sub.search(keyword, sort="new", limit=limit_per_sub): all_posts.append({ "subreddit": sub_name, "title": post.title, "text": post.selftext[:300] if post.selftext else None, "author": str(post.author) if post.author else "[deleted]", "score": post.score, "comments": post.num_comments, "permalink": f"https://reddit.com{post.permalink}", "created_utc": datetime.fromtimestamp( post.created_utc, tz=timezone.utc ).isoformat(), }) except Exception as e: print(f" Error on r/{sub_name}: {e}") df = pd.DataFrame(all_posts) # Summary by subreddit summary = df.groupby("subreddit").agg( posts=("title", "count"), avg_score=("score", "mean"), total_comments=("comments", "sum") ).sort_values("posts", ascending=False) print(f"\n── Results for '{keyword}' ──") print(summary.to_string()) return df # Track "Python scraping" across tech subreddits df = monitor_keyword_across_subreddits( keyword="python scraping", subreddits=["Python", "learnpython", "webdev", "datascience", "MachineLearning"], limit_per_sub=50 ) df.to_csv("reddit_keyword_monitor.csv", index=False) ``` * * * ## Part 3: Instagram Scraping (Public Profiles Only) Instagram is the most aggressively protected major platform in 2026. The public guest API was removed years ago. Almost all data requires a logged-in session. The GraphQL API underlying Instagram's web interface changes identifiers constantly. This section covers only public profile and hashtag data — the bare minimum available without login — and the Playwright approach for logged-in access to your own account's data. ### Scraping public profile metadata ```python import httpx import json import asyncio async def get_instagram_profile(username: str) -> dict | None: """ Fetch public profile data for an Instagram username. Uses the semi-public shared data endpoint. Only works for public accounts. """ url = f"https://www.instagram.com/{username}/?__a=1&__d=dis" headers = { "User-Agent": ( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 Chrome/120 Safari/537.36" ), "Accept": "*/*", "Referer": "https://www.instagram.com/", "X-IG-App-ID": "936619743392459", # Instagram Web app ID } async with httpx.AsyncClient(headers=headers, follow_redirects=True) as client: try: r = await client.get(url, timeout=15) if r.status_code == 200: data = r.json() user = data.get("graphql", {}).get("user", {}) return { "username": user.get("username"), "full_name": user.get("full_name"), "biography": user.get("biography"), "followers": user.get("edge_followed_by", {}).get("count"), "following": user.get("edge_follow", {}).get("count"), "posts": user.get("edge_owner_to_timeline_media", {}).get("count"), "is_verified": user.get("is_verified"), "is_business": user.get("is_business_account"), "profile_url": f"https://www.instagram.com/{username}/", } except Exception as e: print(f"Error fetching @{username}: {e}") return None # Batch scrape public profiles async def batch_profile_scrape(usernames: list[str]) -> pd.DataFrame: results = [] for username in usernames: print(f"Fetching @{username}...") profile = await get_instagram_profile(username) if profile: results.append(profile) await asyncio.sleep(random.uniform(2, 5)) return pd.DataFrame(results) profiles_df = asyncio.run(batch_profile_scrape([ "natgeo", "nasa", "python.learning" ])) print(profiles_df[["username", "followers", "posts", "is_verified"]]) ``` ### Playwright-based Instagram scraper (logged-in session) For your own account's data or influencer research on public accounts: ```python import asyncio, json, random from playwright.async_api import async_playwright from playwright_stealth import stealth_async async def save_instagram_session(): """Log in manually and save session cookies. Run once.""" async with async_playwright() as p: browser = await p.chromium.launch(headless=False) context = await browser.new_context( user_agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) " "AppleWebKit/605.1.15 Mobile/15E148 Safari/604.1", viewport={"width": 390, "height": 844}, is_mobile=True, ) page = await context.new_page() await page.goto("https://www.instagram.com/accounts/login/") print("Log in manually, then press Enter...") input() cookies = await context.cookies() with open("ig_cookies.json", "w") as f: json.dump(cookies, f) print("Session saved.") await browser.close() async def scrape_instagram_posts(username: str, max_posts: int = 30) -> list[dict]: """ Scrape recent posts from a public Instagram profile. Requires a saved session from save_instagram_session(). """ async with async_playwright() as p: browser = await p.chromium.launch( headless=True, args=["--no-sandbox", "--disable-blink-features=AutomationControlled"] ) context = await browser.new_context( user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120", viewport={"width": 1280, "height": 900} ) with open("ig_cookies.json") as f: await context.add_cookies(json.load(f)) page = await context.new_page() await stealth_async(page) await page.goto( f"https://www.instagram.com/{username}/", wait_until="domcontentloaded" ) await asyncio.sleep(random.uniform(2, 4)) posts = [] # Scroll to load posts for _ in range(max_posts // 12 + 1): post_links = await page.query_selector_all("article a[href*='/p/']") for link in post_links: href = await link.get_attribute("href") if href and href not in [p.get("href") for p in posts]: posts.append({"href": href, "username": username}) await page.evaluate("window.scrollBy(0, window.innerHeight)") await asyncio.sleep(random.uniform(1, 2.5)) # Deduplicate and limit seen = set() unique_posts = [] for p in posts: if p["href"] not in seen: seen.add(p["href"]) unique_posts.append(p) posts = unique_posts[:max_posts] await browser.close() print(f"Found {len(posts)} posts for @{username}") return posts ``` * * * ## Part 4: Sentiment Analysis on Collected Data Once you've collected social media data, analysing sentiment turns raw text into actionable signals. The `transformers` library from HuggingFace provides excellent pre-trained sentiment models: ```bash pip install transformers torch ``` ```python from transformers import pipeline import pandas as pd # Load a pre-trained sentiment model (downloads ~67MB on first run) # "cardiffnlp/twitter-roberta-base-sentiment-latest" is trained on tweets sentiment_pipeline = pipeline( "sentiment-analysis", model="cardiffnlp/twitter-roberta-base-sentiment-latest", tokenizer="cardiffnlp/twitter-roberta-base-sentiment-latest", max_length=512, truncation=True, ) LABEL_MAP = { "LABEL_0": "negative", "LABEL_1": "neutral", "LABEL_2": "positive", } def analyse_sentiment_batch(texts: list[str], batch_size: int = 32) -> list[dict]: """ Run sentiment analysis on a list of social media texts. Returns list of {label, score} dicts. """ results = [] for i in range(0, len(texts), batch_size): batch = texts[i:i + batch_size] # Clean texts — remove URLs and excessive whitespace cleaned = [ " ".join(word for word in t.split() if not word.startswith("http"))[:512] for t in batch ] preds = sentiment_pipeline(cleaned) for pred in preds: results.append({ "sentiment": LABEL_MAP.get(pred["label"], pred["label"]), "confidence": round(pred["score"], 3), }) print(f" Processed {min(i + batch_size, len(texts))}/{len(texts)}") return results def sentiment_report(df: pd.DataFrame, text_col: str = "text") -> pd.DataFrame: """ Enrich a DataFrame with sentiment scores and produce a summary report. """ texts = df[text_col].fillna("").tolist() print(f"Analysing sentiment for {len(texts)} posts...") sentiments = analyse_sentiment_batch(texts) df["sentiment"] = [s["sentiment"] for s in sentiments] df["confidence"] = [s["confidence"] for s in sentiments] # Summary summary = df["sentiment"].value_counts(normalize=True).mul(100).round(1) print("\n── Sentiment Distribution ──") for label, pct in summary.items(): bar = "█" * int(pct / 2) print(f" {label:10s}: {bar} {pct}%") return df # Apply to Reddit data reddit_df = pd.read_csv("reddit_learnpython.csv") reddit_df = sentiment_report(reddit_df, text_col="title") reddit_df.to_csv("reddit_with_sentiment.csv", index=False) # Apply to X data tweets_df = pd.read_csv("tweets.csv") tweets_df = sentiment_report(tweets_df, text_col="text") tweets_df.to_csv("tweets_with_sentiment.csv", index=False) ``` * * * ## Part 5: Building a Brand Monitoring Dashboard Combine all sources into a unified brand monitoring pipeline: ```python import asyncio import pandas as pd from datetime import datetime, timezone async def run_brand_monitor(brand_name: str, competitor: str = None) -> dict: """ Collect brand mentions from Reddit and X, analyse sentiment, and produce a unified brand health report. """ report = { "brand": brand_name, "run_at": datetime.now(timezone.utc).isoformat(), "reddit": {}, "twitter": {}, "sentiment": {}, } # ── Reddit ──────────────────────────────────────────────── print(f"\n[1/3] Scraping Reddit for '{brand_name}'...") reddit_df = monitor_keyword_across_subreddits( keyword=brand_name, subreddits=["technology", "Python", "webdev", "datascience", "programming"], limit_per_sub=50 ) reddit_df = sentiment_report(reddit_df, text_col="title") report["reddit"] = { "total_posts": len(reddit_df), "avg_score": round(reddit_df["score"].mean(), 1), "total_comments": int(reddit_df["comments"].sum()), "top_post": reddit_df.sort_values("score", ascending=False).iloc[0]["title"], "top_subreddit": reddit_df["subreddit"].value_counts().index[0], } # ── Sentiment ───────────────────────────────────────────── all_sentiments = pd.concat([ reddit_df[["sentiment", "confidence"]], ]) sentiment_counts = all_sentiments["sentiment"].value_counts(normalize=True).mul(100) report["sentiment"] = { "positive_pct": round(sentiment_counts.get("positive", 0), 1), "neutral_pct": round(sentiment_counts.get("neutral", 0), 1), "negative_pct": round(sentiment_counts.get("negative", 0), 1), "overall": sentiment_counts.idxmax(), } # ── Print Report ────────────────────────────────────────── print(f"\n{'═'*50}") print(f" BRAND MONITOR: {brand_name.upper()}") print(f" Run at: {report['run_at']}") print(f"{'═'*50}") print(f"\n Reddit mentions: {report['reddit']['total_posts']}") print(f" Avg post score: {report['reddit']['avg_score']}") print(f" Most active sub: r/{report['reddit']['top_subreddit']}") print(f"\n Sentiment:") print(f" ✅ Positive: {report['sentiment']['positive_pct']}%") print(f" 😐 Neutral: {report['sentiment']['neutral_pct']}%") print(f" ❌ Negative: {report['sentiment']['negative_pct']}%") print(f" Overall: {report['sentiment']['overall'].upper()}") print(f"\n Top post: \"{report['reddit']['top_post'][:80]}...\"") return report # Run report = asyncio.run(run_brand_monitor("python scraping")) ``` * * * ## Rate Limits and Platform Rules Reference | Platform | Daily free limit | Rate limit reset | Key restriction | | --- | --- | --- | --- | | X API v2 (Free) | 500k tweets/month read | 15 min windows | No historical data | | X API v2 (Basic, $100/mo) | 10M tweets/month | 15 min windows | 30-day history | | Reddit PRAW | 1,000 req/10 min | Rolling | Public posts only | | Instagram (no auth) | ~200 req/hour | Hourly | Public profiles only | | Instagram (with session) | 200–500 actions/day | Daily | Avoid aggressive scraping | * * * ## FAQ **Q: snscrape is broken in 2026 — what should I use instead?** snscrape's X scraper broke in 2023 when X removed the guest API and has not been reliably fixed. Use Tweepy with X API v2 for authorised projects. For DIY scraping, use the Playwright approach above. **Q: Can I scrape private Instagram posts?** No — and you shouldn't. Scraping private account data without consent violates Instagram's Terms of Service, GDPR, and potentially criminal hacking laws in many jurisdictions. Only collect publicly available data. **Q: What's the best model for social media sentiment analysis in 2026?**`cardiffnlp/twitter-roberta-base-sentiment-latest` is trained specifically on tweets and outperforms generic models. For Reddit, which is longer-form, `distilbert-base-uncased-finetuned-sst-2-english` works well. For multilingual content, use `nlptown/bert-base-multilingual-uncased-sentiment`. **Q: Reddit changed its API — is PRAW still free?** Yes. PRAW's free tier (500 requests per 10 minutes) was not affected by the 2023 API changes. Only third-party apps accessing certain endpoints at very high volume were impacted. Standard research use via PRAW remains free. **Q: How do I handle deleted posts in my Reddit dataset?** Posts with `[deleted]` author or `[removed]` body are common. Filter them before analysis: `df = df[df['author'] != '[deleted]']` and `df = df[df['text'] != '[removed]']`. * * * ## Summary | Platform | Best Method | Key Library | What You Can Get | | --- | --- | --- | --- | | X / Twitter | Tweepy (API v2) | `tweepy` | Tweets, metrics, author data | | X / Twitter | Playwright + session | `playwright` | Search results, profiles | | Reddit | PRAW | `praw` | Posts, comments, subreddits | | Instagram | httpx + cookies | `httpx` | Public profiles, post counts | | All platforms | HuggingFace | `transformers` | Sentiment, emotion, topics |