There are two fundamentally different ways to extract a color palette from a website in code: quantize the pixels of a screenshot, or read the colors out of the CSS. They give different answers, and one of them is usually wrong for what you want. The short version: pixel methods find what a page looks like; CSS methods find what the brand is. If you want brand colors, read the CSS.
Here are both, working, then the comparison.
Method 1: quantize a screenshot (fast, misleading)
The classic approach - screenshot the page, run color quantization:
pip install playwright colorthief
playwright install chromium
from io import BytesIO
from colorthief import ColorThief
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page(viewport={"width": 1440, "height": 900})
page.goto("https://stripe.com", wait_until="networkidle")
shot = page.screenshot()
browser.close()
palette = ColorThief(BytesIO(shot)).get_palette(color_count=6)
print(['#%02x%02x%02x' % c for c in palette])
Works in ten lines. The problem is that a screenshot has no idea what is UI and what is content. A hero photo dominates the pixel math, anti-aliased text contributes hundreds of blend colors that exist nowhere in the stylesheet, and the accent color that defines the brand might be forty pixels of button. Use this method for artwork and photos; do not use it for brand palettes.
Method 2: read the CSS (the right answer)
Ask the browser what colors the stylesheet actually applies:
from collections import Counter
from playwright.sync_api import sync_playwright
COLLECT = """
() => [...document.querySelectorAll('body *')].slice(0, 5000).flatMap(el => {
const s = getComputedStyle(el);
return [s.color, s.backgroundColor, s.borderTopColor];
})
"""
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://stripe.com", wait_until="networkidle")
values = page.evaluate(COLLECT)
browser.close()
skip = {"rgba(0, 0, 0, 0)", "rgb(0, 0, 0)", "rgb(255, 255, 255)"}
for color, count in Counter(v for v in values if v not in skip).most_common(8):
print(f"{count:>5} {color}")
Frequency ranking does the rest: the most-used saturated color is almost always the brand primary. For the full version with fonts, spacing, and the cleanup steps, see the design tokens guide.
Checking one site by hand? Use the console
Paste this in DevTools on any page:
Object.entries([...document.querySelectorAll('body *')].reduce((acc, el) => {
const c = getComputedStyle(el).backgroundColor;
if (c !== 'rgba(0, 0, 0, 0)') acc[c] = (acc[c] || 0) + 1;
return acc;
}, {})).sort((a, b) => b[1] - a[1]).slice(0, 8)
Fastest palette check that exists. It just does not scale past the tab you are in.
Screenshot vs CSS vs API
| Screenshot quantization | CSS computed styles | MiroMiro API | |
|---|---|---|---|
| Finds brand accent colors | Poorly (pixel-count bias) | Yes | Yes |
| Affected by photos in the page | Badly | No | No |
| Invents colors (anti-aliasing) | Yes | No | No |
| Gradients | Sort of (as pixels) | Needs backgroundImage too | Yes, as gradient strings |
| Output format | RGB tuples | rgb() strings | Hex, ranked, plus the rest of the token set |
| Runs without your own browser infra | No | No | Yes |
Or use one API call
curl "https://miromiro.app/api/v1/extract?url=stripe.com&fields=colors,gradients" \
-H "Authorization: Bearer $MIROMIRO_API_KEY"
Ranked palette with gradients included, normalized, 10 credits a call. There is also a free color palette extractor that runs the same engine in the browser if you want to eyeball a site before writing any code. Free key here - 100 credits a month, no card.
Summary
Read the CSS, not the pixels. Screenshot quantization answers "what does this image look like", which is the wrong question for a brand palette, and it will confidently hand you the colors of a stock photo. The console snippet for one site, Playwright for a script, the API when it needs to run inside something.
Frequently asked questions
How do I get a website's color palette in Python?
Playwright plus getComputedStyle counting (code above). Skip ColorThief-on-screenshot for brand work - it measures content, not identity.
Why does screenshot extraction give wrong brand colors?
Pixel counts reward photos and anti-aliasing blends, not the forty pixels of button that carry the actual accent color.
Can I do this in the browser console?
Yes - the snippet above ranks background colors on any page you have open.
How do I get hex instead of rgb() strings?
Three parseInt calls per channel, or use an API that normalizes for you.
Skip the maintenance - one API call
Design tokens, brand data, fonts, SVGs, images, and section code from any URL. 100 free credits a month, no card.
Soraia · Founder, MiroMiro
Building MiroMiro: a browser extension and API that extract design tokens, assets, and production-ready code from any live website. The gotchas in these posts come from the extraction engine's own commit history.
Follow on X