Tutorial

Automate Social Media Screenshots

|10 min read

Capture tweets, LinkedIn posts, and other social content as clean PNG images for newsletters, blog posts, and presentations. SnapRender's screenshot API handles JavaScript rendering and element-level capture.

Why screenshot social posts?

Newsletter content

Include tweets and LinkedIn posts in email newsletters where embeds do not work.

Blog illustrations

Embed social proof and examples in blog posts as static, fast-loading images.

Presentation slides

Drop clean social media screenshots into pitch decks and keynotes.

Social proof archives

Preserve customer testimonials and mentions as permanent records, even if posts are deleted.

Capture Twitter/X posts

Use the selector parameter to capture just the tweet element, not the entire page. The hide_selectors parameter removes reply/like buttons and the sidebar for a cleaner image.

twitter.py
#E8A0BF">import requests
#E8A0BF">import os

API_KEY = #A8D4A0">"sr_live_YOUR_KEY"

#E8A0BF">def screenshot_tweet(tweet_url, output_dir=#A8D4A0">"screenshots"):
    #A8D4A0">""#A8D4A0">"Capture a clean screenshot of a tweet/X post."#A8D4A0">""
    os.makedirs(output_dir, exist_ok=#E8A0BF">True)

    resp = requests.#87CEEB">post(
        #A8D4A0">"https://api.snaprender.dev/v1/screenshot",
        headers={#A8D4A0">"x-api-key": API_KEY},
        json={
            #A8D4A0">"url": tweet_url,
            #A8D4A0">"width": 550,
            #A8D4A0">"height": 800,
            #A8D4A0">"format": #A8D4A0">"png",
            #A8D4A0">"selector": #A8D4A0">'article[data-testid=#A8D4A0">"tweet"]',
            #A8D4A0">"wait_for": #A8D4A0">'article[data-testid=#A8D4A0">"tweet"]',
            #A8D4A0">"hide_selectors": [
                #A8D4A0">'[data-testid=#A8D4A0">"reply"]',
                #A8D4A0">'[data-testid=#A8D4A0">"like"]',
                #A8D4A0">'[data-testid=#A8D4A0">"retweet"]',
                #A8D4A0">'nav',
                #A8D4A0">'[data-testid=#A8D4A0">"sidebarColumn"]',
            ],
        }
    )

    #E8A0BF">if resp.#87CEEB">status_code == 200:
        # Generate filename #E8A0BF">from tweet ID
        tweet_id = tweet_url.#87CEEB">rstrip(#A8D4A0">"/").#87CEEB">split(#A8D4A0">"/")[-1]
        path = output_dir + #A8D4A0">"/tweet_" + tweet_id + #A8D4A0">".png"
        #E8A0BF">with #E8A0BF">open(path, #A8D4A0">"wb") #E8A0BF">as f:
            f.#87CEEB">write(resp.#87CEEB">content)
        #E8A0BF">print(#A8D4A0">"Saved: " + path)
        #E8A0BF">return path
    #E8A0BF">else:
        #E8A0BF">print(#A8D4A0">"Error: " + str(resp.#87CEEB">status_code))
        #E8A0BF">return #E8A0BF">None

# Example
screenshot_tweet(#A8D4A0">"https://x.com/elonmusk/status/1234567890")

Capture LinkedIn posts

LinkedIn public posts can be captured with the same approach — target the post container element and hide navigation and action buttons.

linkedin.py
#E8A0BF">def screenshot_linkedin_post(post_url, output_dir=#A8D4A0">"screenshots"):
    #A8D4A0">""#A8D4A0">"Capture a LinkedIn post #E8A0BF">as a clean image."#A8D4A0">""
    os.makedirs(output_dir, exist_ok=#E8A0BF">True)

    resp = requests.#87CEEB">post(
        #A8D4A0">"https://api.snaprender.dev/v1/screenshot",
        headers={#A8D4A0">"x-api-key": API_KEY},
        json={
            #A8D4A0">"url": post_url,
            #A8D4A0">"width": 600,
            #A8D4A0">"height": 1000,
            #A8D4A0">"format": #A8D4A0">"png",
            #A8D4A0">"selector": #A8D4A0">".feed-shared-update-v2",
            #A8D4A0">"wait_for": #A8D4A0">".feed-shared-update-v2",
            #A8D4A0">"hide_selectors": [
                #A8D4A0">".feed-shared-social-actions",
                #A8D4A0">".nav",
                #A8D4A0">".global-footer",
            ],
        }
    )

    #E8A0BF">if resp.#87CEEB">status_code == 200:
        slug = post_url.#87CEEB">split(#A8D4A0">"/")[-1].#87CEEB">split(#A8D4A0">"?")[0]
        path = output_dir + #A8D4A0">"/linkedin_" + slug + #A8D4A0">".png"
        #E8A0BF">with #E8A0BF">open(path, #A8D4A0">"wb") #E8A0BF">as f:
            f.#87CEEB">write(resp.#87CEEB">content)
        #E8A0BF">print(#A8D4A0">"Saved: " + path)
        #E8A0BF">return path

    #E8A0BF">return #E8A0BF">None

Generic fallback

For platforms without known selectors, take a viewport screenshot with sensible defaults.

generic.py
#E8A0BF">def screenshot_generic(url, output_dir=#A8D4A0">"screenshots"):
    #A8D4A0">""#A8D4A0">"Generic social media screenshot #E8A0BF">with smart defaults."#A8D4A0">""
    os.makedirs(output_dir, exist_ok=#E8A0BF">True)

    resp = requests.#87CEEB">post(
        #A8D4A0">"https://api.snaprender.dev/v1/screenshot",
        headers={#A8D4A0">"x-api-key": API_KEY},
        json={
            #A8D4A0">"url": url,
            #A8D4A0">"width": 600,
            #A8D4A0">"height": 800,
            #A8D4A0">"format": #A8D4A0">"png",
            #A8D4A0">"full_page": #E8A0BF">False,
            #A8D4A0">"use_flaresolverr": #E8A0BF">True,
        }
    )

    #E8A0BF">if resp.#87CEEB">status_code == 200:
        # Clean URL #E8A0BF">for filename
        clean = url.#87CEEB">split(#A8D4A0">"//")[1].#87CEEB">replace(#A8D4A0">"/", #A8D4A0">"_")[:80]
        path = output_dir + #A8D4A0">"/" + clean + #A8D4A0">".png"
        #E8A0BF">with #E8A0BF">open(path, #A8D4A0">"wb") #E8A0BF">as f:
            f.#87CEEB">write(resp.#87CEEB">content)
        #E8A0BF">return path

    #E8A0BF">return #E8A0BF">None

Batch processing

Process multiple URLs in a loop with rate limiting.

batch.py
#E8A0BF">import time

#E8A0BF">def batch_screenshot(urls, platform=#A8D4A0">"twitter"):
    #A8D4A0">""#A8D4A0">"Screenshot multiple posts #E8A0BF">with rate limiting."#A8D4A0">""
    results = []

    #E8A0BF">for i, url #E8A0BF">in enumerate(urls):
        #E8A0BF">print(#A8D4A0">"[" + str(i+1) + #A8D4A0">"/" + str(len(urls)) + #A8D4A0">"] " + url)

        #E8A0BF">if platform == #A8D4A0">"twitter":
            path = screenshot_tweet(url)
        elif platform == #A8D4A0">"linkedin":
            path = screenshot_linkedin_post(url)
        #E8A0BF">else:
            path = screenshot_generic(url)

        results.#87CEEB">append({#A8D4A0">"url": url, #A8D4A0">"path": path})

        # Rate limit: 1 per second
        time.#87CEEB">sleep(1)

    #E8A0BF">return results

# Batch capture tweets
tweets = [
    #A8D4A0">"https://x.com/user1/status/111",
    #A8D4A0">"https://x.com/user2/status/222",
    #A8D4A0">"https://x.com/user3/status/333",
]
batch_screenshot(tweets, platform=#A8D4A0">"twitter")

Node.js version

The same tweet capture logic in Node.js.

screenshot.js
#E8A0BF">const fs = #E8A0BF">require(#A8D4A0">'fs');

#E8A0BF">async #E8A0BF">function screenshotTweet(tweetUrl) {
  #E8A0BF">const resp = #E8A0BF">await fetch(#A8D4A0">'https://api.snaprender.dev/v1/screenshot', {
    method: #A8D4A0">'POST',
    headers: {
      #A8D4A0">'x-api-key': #A8D4A0">'sr_live_YOUR_KEY',
      #A8D4A0">'Content-Type': #A8D4A0">'application/json',
    },
    body: JSON.#87CEEB">stringify({
      url: tweetUrl,
      width: 550,
      height: 800,
      format: #A8D4A0">'png',
      selector: #A8D4A0">'article[data-testid=#A8D4A0">"tweet"]',
      wait_for: #A8D4A0">'article[data-testid=#A8D4A0">"tweet"]',
    }),
  });

  #E8A0BF">if (resp.ok) {
    #E8A0BF">const buffer = Buffer.#E8A0BF">from(#E8A0BF">await resp.#87CEEB">arrayBuffer());
    #E8A0BF">const id = tweetUrl.#87CEEB">split(#A8D4A0">'/').pop();
    #E8A0BF">const path = #A8D4A0">"screenshots/tweet_" + id + #A8D4A0">".png";
    fs.#87CEEB">writeFileSync(path, buffer);
    console.#87CEEB">log(#A8D4A0">"Saved: " + path);
    #E8A0BF">return path;
  }
  #E8A0BF">return #E8A0BF">null;
}

Start capturing social content

Get your API key in 30 seconds. 100 free screenshots/month. No credit card required.

Get Your API Key

Frequently asked questions

Taking screenshots of public social media posts for personal use, journalism, or commentary is generally considered fair use. However, republishing at scale or for commercial purposes may raise copyright concerns. Always credit the original author and respect platform terms of service.

Embeds load slowly, break when posts are deleted, and look inconsistent across platforms. Screenshots are static images that load instantly, display consistently everywhere, and serve as a permanent archive of the content at the time of capture.

SnapRender captures pages as an anonymous visitor. Public posts on Twitter/X and LinkedIn are visible without login. Instagram public profiles work, but most Instagram content requires authentication. For gated content, you would need to use cookies or session-based approaches.

Social media cards look best at specific widths. Twitter/X: 550px width. LinkedIn: 600px width. Use SnapRender's width and height parameters to set the viewport. The examples in this tutorial use optimized sizes for each platform.