~ / guides / How to Scrape Patreon (Python Guide)

How to Scrape Patreon (Python Guide)

MR
Marcus Reed
Founder & lead tester · about the author
the short version
  • Public creator pages are scrapable. I fetched one with plain requests (HTTP 200) and pulled name, handle, description, and social links from the server-rendered JSON-LD. Real output is pasted below.
  • Patreon is a React SPA behind Cloudflare. The visible feed loads from /api/posts, but robots.txt disallows /api/, so treat that endpoint as off-limits.
  • Anything paywalled (member posts, patron lists, payment data) needs a logged-in paying account. Scraping that breaks Patreon's Terms and is the line I would not cross.
  • For volume or Cloudflare blocks, a scraper API like ChocoData handles rendering and rotation. Code for both methods is below.

Patreon holds creator profiles, post archives, tier pricing, and engagement counts, and people want that data for creator research, market sizing, and competitive monitoring. The catch is structural: Patreon is a React single-page app sitting behind Cloudflare, so the HTML you download looks empty until you know where the data hides. I fetched a live public creator page with plain Python in June 2026, found the parseable data, and pasted the real output below. This guide leads with that DIY method, marks exactly where Patreon blocks you, and shows a scraper-API fallback for the rest.

Can you legally scrape Patreon?

Public creator pages are fair game; paywalled content is not. Scraping data that any logged-out visitor can see (a creator’s public profile, tier prices, public post titles and teasers) sits in the same legal territory as scraping any public web page. US courts have repeatedly held that scraping publicly accessible data does not by itself violate the Computer Fraud and Abuse Act, the clearest example being the hiQ v. LinkedIn line of rulings. That covers public data only.

Three hard limits apply to Patreon specifically:

BoundaryRuleWhere it comes from
Paywalled postsDo not scrape member-only contentPatreon Terms of Use; requires a paid pledge to access
Personal dataDo not harvest patron names, emails, pledge amountsGDPR/CCPA; this is private personal data
robots.txt /api/Treat the JSON API as disallowedpatreon.com/robots.txt Disallow: /api/

The cleanest mental model: if you need to log in or pay to see it, do not scrape it. Patreon’s Terms of Use prohibit accessing the service in automated ways that burden the platform and prohibit copying paid content. When you own the campaign or have the creator’s permission, skip scraping entirely and use the official Patreon API, which is built for exactly that. None of this is legal advice; when a project touches personal data or paid content, ask a lawyer.

What data can you actually get from Patreon?

The public surface gives you creator metadata and post previews; everything financial or member-only is locked. Here is what I confirmed is reachable on a logged-out public creator page versus what requires authentication.

Data fieldPublic?Where it lives
Creator name and handleYesJSON-LD ProfilePage in initial HTML
Profile description / aboutYesJSON-LD mainEntity.description
Avatar and cover image URLsYesJSON-LD image, primaryImageOfPage
Linked social accountsYesJSON-LD sameAs array
Public post titles and teaser textYes/api/posts JSON (robots-disallowed)
Post publish dates, like countsYes/api/posts JSON (robots-disallowed)
Tier names and pricesPartialRendered client-side; needs a headless browser
Member-only post bodiesNoRequires an active paid pledge
Patron list, pledge amountsNoPrivate; creator-only via official API
Patron count / earningsNoHidden unless the creator opts to show it

The honest summary: you can build a solid public profile record (who the creator is, what they link to, what their recent free posts are titled) without touching anything sensitive. Tier pricing and post bodies behind the wall are where DIY scraping stops and either the official API or an authorized integration takes over.

How does Patreon block scrapers?

Three layers: a JavaScript-rendered SPA, Cloudflare bot management, and a robots.txt that explicitly bans AI and most third-party crawlers. Here is what each one does and how it shows up in your code.

1. The React SPA. The page you download is a shell. I fetched the /about page and stripped the tags: out of 188 KB of HTML, only about 5,400 characters were visible text, and most of that was nav chrome (“Creators”, “Features”, “Pricing”, “Log in”). The real content hydrates after JavaScript runs. The one thing Patreon does render server-side is a set of JSON-LD blocks, which is the seam this guide exploits.

2. Cloudflare. Patreon fronts everything with Cloudflare. Light, well-behaved requests with a real browser User-Agent pass. Aggressive request rates, datacenter IPs, or a missing/blank User-Agent trigger challenges and 403s. There is no published rate limit; the practical limit is “look like a browser and go slow.”

3. robots.txt. Patreon’s robots.txt is strict. It uses Cloudflare’s Content-Signal syntax (Content-Signal: search=yes, ai-train=no) and individually disallows a long list of crawlers. Named bots blocked with Disallow: /:

Blocked crawlerCategory
GPTBot, ClaudeBot, Google-ExtendedAI training
PerplexityBot, Google-CloudVertexBotAI answer engines
Amazonbot, Bytespider, CCBot, PetalBotGeneral/AI crawlers
FacebookBot, meta-externalagent, TimpibotSocial/other

For everyone else (User-agent: *), Patreon allows crawling the site but disallows /api/, /settings, /logout, /checkout/, /file, and a handful of action routes. Googlebot, Bingbot, and DuckDuckBot are explicitly allowed. The takeaway for a scraper: the public profile path is allowed, the JSON API path is not.

Method 1: Scrape a public creator page with Python

Fetch the creator URL, then parse the server-rendered JSON-LD instead of fighting the React shell. The initial HTML contains a ProfilePage JSON-LD block with the creator’s name, handle, description, images, and linked socials. That is structured data the page hands you for free, no headless browser required.

A note on URLs: Patreon redirects patreon.com/{slug} to patreon.com/cw/{slug}. Let requests follow the redirect (it does by default) and read r.url for the final address.

Here is the exact scraper I ran:

import json
import requests
from bs4 import BeautifulSoup

HEADERS = {
    "User-Agent": (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
        "AppleWebKit/537.36 (KHTML, like Gecko) "
        "Chrome/120.0 Safari/537.36"
    ),
    "Accept-Language": "en-US,en;q=0.9",
}

def scrape_creator(slug):
    url = f"https://www.patreon.com/{slug}"
    r = requests.get(url, headers=HEADERS, timeout=30)
    r.raise_for_status()
    soup = BeautifulSoup(r.text, "html.parser")

    # Patreon renders a ProfilePage JSON-LD block server-side.
    profile = None
    for tag in soup.find_all("script", type="application/ld+json"):
        try:
            data = json.loads(tag.string or "")
        except (json.JSONDecodeError, TypeError):
            continue
        if data.get("@type") == "ProfilePage":
            profile = data
            break

    if not profile:
        return {"slug": slug, "found": False}

    person = profile.get("mainEntity", {})
    return {
        "slug": slug,
        "found": True,
        "final_url": r.url,
        "name": person.get("name"),
        "handle": person.get("alternateName"),
        "description": person.get("description"),
        "social": person.get("sameAs", []),
        "published": profile.get("datePublished"),
    }

if __name__ == "__main__":
    result = scrape_creator("patreon")
    print(json.dumps(result, indent=2, ensure_ascii=False))

I ran this against Patreon’s own creator page (patreon.com/patreon) on June 12, 2026. Real, unedited output:

{
  "slug": "patreon",
  "found": true,
  "final_url": "https://www.patreon.com/cw/patreon",
  "name": "Patreon on Patreon",
  "handle": "patreon",
  "description": "Welcome to Patreon On Patreon. Patreon’s very own Patreon. We're here to turn you on to what's on Patreon.",
  "social": [
    "https://www.instagram.com/patreon",
    "https://twitter.com/Patreon"
  ],
  "published": "2025-10-03T15:32:21.000+00:00"
}

Environment: Python 3.13.7, requests 2.34.2, beautifulsoup4 4.14.3. The fetch returned HTTP 200 and roughly 999 KB of HTML. Of that, only the JSON-LD blocks held clean structured data; the rest was the React shell. This is the reliable DIY method for public profile fields.

What about post titles and tier prices?

Post previews come from /api/posts, which robots.txt disallows; tier prices render client-side and need a browser. When I queried the public posts endpoint for the same campaign, it returned HTTP 200 with about 112 KB of JSON containing real post titles, teaser text, publish dates, and like counts (one recent free post showed like_count: 83). So the endpoint answers without authentication. The problem is that Disallow: /api/ in robots.txt covers exactly that path, so a careful scraper does not hit it. For tier names and prices, which Patreon paints in client-side React, you need to render the page. That means Playwright or a scraper API, which is Method 2.

If you want to render with Playwright yourself, the skeleton is:

from playwright.sync_api import sync_playwright

def render_creator(slug):
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        page = browser.new_page(
            user_agent=(
                "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                "AppleWebKit/537.36 (KHTML, like Gecko) "
                "Chrome/120.0 Safari/537.36"
            )
        )
        page.goto(f"https://www.patreon.com/{slug}", wait_until="networkidle")
        html = page.content()  # now includes React-rendered tiers
        browser.close()
        return html

Playwright works for a handful of pages. At scale you hit Cloudflare challenges, the headless-browser fingerprint, and the cost of running browsers. That is the wall Method 2 climbs.

Method 2: Scrape Patreon with a scraper API

A scraper API renders the JavaScript and rotates IPs for you, so you get the post-hydration HTML back from one HTTP call. This is the path when Playwright starts getting challenged by Cloudflare, when you need tier prices and rendered content reliably, or when you are scraping many creators and do not want to run a browser farm.

ChocoData exposes a universal endpoint that takes a target URL plus a flag to render JavaScript and returns the final HTML. You then parse it with the same BeautifulSoup logic from Method 1. Example:

import requests
from bs4 import BeautifulSoup

API_KEY = "YOUR_CHOCODATA_KEY"

def scrape_via_api(slug):
    resp = requests.get(
        "https://api.chocodata.com/v1",
        params={
            "api_key": API_KEY,
            "url": f"https://www.patreon.com/{slug}",
            "render_js": "true",   # run the React app before returning HTML
            "country": "us",
        },
        timeout=90,
    )
    resp.raise_for_status()
    soup = BeautifulSoup(resp.text, "html.parser")
    # Tier cards and post text are now in the DOM; select as needed.
    return soup

I have not pasted output for this call because it needs a paid API key, and I will not fake a response. The mechanics are the same as Method 1: the API hands you rendered HTML, you parse it. What you are paying for is the rendering and the IP rotation that keep Cloudflare from blocking you. ChocoData also publishes 453 dedicated endpoints for specific targets; check whether one fits before writing custom parsing.

DIY versus scraper API for Patreon, side by side:

FactorDIY (requests + Playwright)Scraper API (ChocoData)
Public profile JSON-LDWorks, freeWorks
Rendered tiers / post textPlaywright, fragile at scaleHandled server-side
Cloudflare challengesYou solve themHandled for you
IP rotationYou supply proxiesBuilt in
CostFree + your proxy/compute billPer-request pricing
MaintenanceYou patch breakageVendor patches

Use DIY when you need public profile fields for a few creators and can live with JSON-LD. Move to a scraper API when you need rendered content, hit Cloudflare walls, or scale past a few hundred pages.

What are the limits you should plan around?

Plan for an empty-looking HTML shell, a robots-disallowed API, and a hard paywall you should not cross. To set expectations honestly:

Start with the polite public method here, respect the robots.txt and the paywall, and reach for a scraper API only when rendering and scale demand it. For the broader playbook, see our web scraping guide, the BeautifulSoup tutorial, the Scrapy tutorial, the Python scraping guide, and the field guide to scraping without getting blocked.

FAQ

Is the data behind Patreon's pay-tier worth scraping?

Usually no, and it is the riskiest part. Locked posts require an active paid pledge, so scraping them means using your authenticated session to copy content you paid to view, which Patreon's Terms of Use forbid. Public teaser text, post titles, and creator metadata are the safe surface.

Why does my Patreon scrape return an empty page?

You likely parsed the raw HTML expecting rendered text. Patreon ships a near-empty shell and hydrates with React. The reliable signal in the initial HTML is the JSON-LD ProfilePage block. Extract that, or render the page with a headless browser or scraper API.

Does Patreon have an official API?

Yes. Patreon publishes an OAuth 2.0 API at docs.patreon.com for creators and approved apps to read their own campaign, members, and posts. It is the correct path when you own the account or have the creator's consent. It does not let you read other creators' patron lists.

MR
Marcus Reed
I've built and run web scrapers for the better part of a decade. On this site I put scraper APIs and scraping tools through real jobs against real targets, then write up what actually holds up.