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.
#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.
#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">NoneGeneric fallback
For platforms without known selectors, take a viewport screenshot with sensible defaults.
#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">NoneBatch processing
Process multiple URLs in a loop with rate limiting.
#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.
#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 KeyFrequently 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.