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

How to Scrape Pinterest (Python Guide)

MR
Marcus Reed
Founder & lead tester · about the author
the short version
  • Pinterest blocks logged-out scraping. In my June 2026 probe the search and pin pages both returned 200, then served an empty JavaScript shell: the embedded __PWS_DATA__ blob held zero pins, no image URLs, no titles, just a login form.
  • Plain requests + BeautifulSoup gets you the page title "Pinterest" and nothing else. The pins render client-side from an API that needs a session, so there is no HTML payload to parse.
  • robots.txt explicitly Disallows /search/ and most /pin/ sub-paths across 653 rules, and offers a bot-submission allowlist. Pinterest's Terms also prohibit automated collection.
  • Realistic options: render with Playwright (expect login modals from datacenter IPs), or route through a scraper API like ChocoData that rotates residential IPs and renders the page for public boards and pins at volume.

Pinterest looks scrapable and is not. I spent June 2026 probing what a plain Python scraper can pull from a logged-out session, and the honest answer is the page title and nothing more. The search results page and individual pin pages both return a 200 status code, which looks encouraging, then hand you a JavaScript shell with an empty data object where the pins should be. This guide shows the real probe output, explains how Pinterest blocks anonymous requests, walks the legal picture with primary sources, and covers the two approaches that actually return data: a headless browser and a scraper API.

Can you legally scrape Pinterest?

Scraping public Pinterest data runs against Pinterest’s own rules, and three independent layers apply. Treat each one separately.

First, Pinterest’s contract. The Pinterest Terms of Service prohibit accessing the service “through automated or non-human means” and collecting data without prior written permission. Scraping Pinterest, logged in or out, breaches that contract.

Second, the crawl directives. I fetched Pinterest’s robots.txt in June 2026: it carries 653 Disallow lines and explicitly blocks /search/ and /search?, the exact paths a pin-search scraper would hit. It also disallows dozens of /pin/ sub-paths (/pin/*/comments, /pin/*/related-products, /pin/*/repins). It does Allow a handful of resource paths and points crawlers to a bot-submission allowlist form. robots.txt is not law, but ignoring an explicit Disallow weakens any “we crawled in good faith” defense.

Third, data-protection law, which applies regardless of the two above. Pinterest profiles and boards can contain personal data. The moment you store information on EU or UK residents, GDPR applies, and “it was public” is not a lawful basis on its own. US case law on scraping public data is unsettled: hiQ v. LinkedIn suggested scraping ungated public data is hard to frame as unauthorized access, but that addressed one statute on specific facts and did not bless scraping in general. I cover the framing in my is web scraping legal guide.

The practical posture: scraping public, non-personal Pinterest data (aggregate trend research, your own boards) carries the lowest risk. Collecting personal profiles or scraping behind a logged-in bot account carries the most.

What data is available on Pinterest?

Public Pinterest pages display rich data in a browser, and almost none of it reaches a logged-out scraper as parseable markup. Here is what each surface theoretically exposes versus what a plain logged-out request actually returns:

Data pointVisible in browserLogged-out scraper gets it?
Pin image (i.pinimg.com URL)YesNo, empty JS shell
Pin title and descriptionYesNo, empty JS shell
Source / destination linkYesNo, empty JS shell
Board name and pin countYesNo, empty JS shell
Saves / reactions countYesNo, empty JS shell
Pinner username and profileYesNo, empty JS shell
Search result gridYesNo, /search/ is robots-disallowed
Related pinsYesNo, loaded by internal API

The pattern is blunt: Pinterest renders everything client-side from an internal API that requires a session, so the initial HTML a logged-out scraper receives is a skeleton. The next section shows the proof I pulled in June 2026.

How does Pinterest block scrapers?

Pinterest blocks logged-out scrapers by shipping an empty JavaScript shell, gating the pin data behind a session-authenticated internal API, and disallowing the search path in robots.txt. I tested the public surfaces in June 2026 with plain Python. Here is the probe I ran:

import requests, re, json

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

# 1. Public search results page
url = "https://www.pinterest.com/search/pins/?q=minimalist%20desk%20setup"
r = requests.get(url, headers=H, timeout=30)
body = r.text
title = re.search(r"<title>(.*?)</title>", body, re.S)
print("SEARCH:", r.status_code, "| len", len(body))
print("TITLE:", title.group(1).strip() if title else "NONE")
print("i.pinimg.com image URLs in HTML:", body.count("i.pinimg.com"))

# 2. The embedded state blob Pinterest ships to the browser
m = re.search(r'id="__PWS_DATA__"[^>]*>(.*?)</script>', body, re.S)
if m:
    d = json.loads(m.group(1))
    s = json.dumps(d)
    print("__PWS_DATA__ len:", len(m.group(1)))
    print("pins in blob (pinimg.com refs):", s.count("pinimg.com"))
    print('grid_title fields in blob:', s.count("grid_title"))

# 3. A single pin detail page
pr = requests.get("https://www.pinterest.com/pin/10696099963436/",
                  headers=H, timeout=30)
pt = re.search(r"<title>(.*?)</title>", pr.text, re.S)
print("\nPIN PAGE:", pr.status_code, "| len", len(pr.text))
print("PIN TITLE:", (pt.group(1).strip() or "EMPTY") if pt else "NONE")
print("og:image present:", "og:image" in pr.text)

The real output, run June 2026:

SEARCH: 200 | len 895589
TITLE: Pinterest
i.pinimg.com image URLs in HTML: 1
__PWS_DATA__ len: 77811
pins in blob (pinimg.com refs): 0
grid_title fields in blob: 0

PIN PAGE: 200 | len 923224
PIN TITLE: EMPTY
og:image present: False

Read that carefully, because the 200 is a trap. The search request succeeded and returned an 895 KB body, then gave back a page titled “Pinterest” with exactly one i.pinimg.com reference in the entire document (a UI asset, not a result). Pinterest ships a 77 KB __PWS_DATA__ state object to bootstrap its React app, and that blob contained zero pin image URLs and zero grid_title fields: an empty shell waiting for the client to call the API. The individual pin page was worse: a 923 KB body with an empty <title> and no og:image tag, so even the Open Graph preview data was absent from the logged-out HTML.

So there is no public HTML payload to parse and no anonymous data in the bootstrap blob. This is why I am not pasting a “tested” stamp on a working DIY parser: the honest result is that plain requests against Pinterest returns a skeleton. The blocking stack is layered: client-side rendering, a session-and-CSRF-gated internal API, a robots.txt that disallows /search/, and behind those, IP and behavior fingerprinting that flags datacenter traffic and serves login modals or CAPTCHAs.

How do you scrape Pinterest with Python? (Method 1: DIY)

The DIY path means rendering the page in a real browser with Playwright, because requests alone only ever sees the empty shell my probe exposed. Even then you are fighting the blocks above, and an unauthenticated headless browser from a datacenter IP frequently gets a login modal over the content. The code below launches a headless Chromium, loads a pin page, waits for the network to settle, and reads whatever the rendered DOM exposes:

from playwright.sync_api import sync_playwright

def scrape_pin(pin_url: str):
    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/126.0.0.0 Safari/537.36")
        )
        page.goto(pin_url, wait_until="networkidle", timeout=30000)

        # After hydration, the pin image and title may appear in the DOM.
        # Pinterest often overlays a login/signup modal on logged-out loads.
        img = page.query_selector('img[src*="i.pinimg.com"]')
        print("pin image:", img.get_attribute("src") if img else "BLOCKED / login modal")

        og = page.query_selector('meta[property="og:title"]')
        print("og:title:", og.get_attribute("content") if og else "NONE")
        print("page title:", page.title())
        browser.close()

scrape_pin("https://www.pinterest.com/pin/10696099963436/")

I am not pasting output for this one, because I will not fabricate it and a headless run from a flagged datacenter IP routinely hits the login/signup modal my requests probe implied. Playwright is the right tool when a target is JS-heavy, and my Selenium and Playwright scraping guide covers the rendering mechanics. For Pinterest specifically, headless automation from a single datacenter IP gets detected quickly: you get the modal, a CAPTCHA, or a rate limit. Logging in to dismiss the modal breaches Pinterest’s Terms head-on and risks the account. That is the wall every DIY Pinterest scraper hits. For the parsing fundamentals under all of this, see my BeautifulSoup guide and the broader Python scraping guide.

What about the official Pinterest API?

The official Pinterest Developers API is the sanctioned route, and it works for accounts and boards that authorize your app. The v5 REST API uses OAuth: a user grants your app scopes, and you receive a token that returns their boards, pins, and (for business accounts) analytics and ad data through documented endpoints. It will not return an arbitrary public board by URL, only data the authorizing account owns or controls.

FeatureOfficial APIDIY scrapingScraper API
Sanctioned by PinterestYesNoNo
Arbitrary public boards / pinsNoBlocked in practiceYes
Your own / consenting accountsYesn/an/a
Analytics, ad dataYesNoNo
Stable (no selector breakage)YesNoPartial
Account ban riskNoneHigh (if logged in)None

The catch is the second row: the API will not hand you a competitor’s public board or trending search results. It serves accounts that authorized your app. For your own brand analytics or a client who connects their account, it is the correct and durable choice. For public competitive or trend data that no one will authorize, the official API is a dead end, which is where a scraper API comes in.

How do you scrape public Pinterest data at scale? (Method 2: scraper API)

Route the request through a scraper API so it renders the page from a real, residential session and rotates IPs, then you parse the structured response it returns. This is the practical answer for public boards and pins at volume, because it solves the exact failures my probe exposed: the empty JS shell, the session-gated internal API, and the datacenter-IP detection. The scraper API maintains the proxy pool and the rendering pipeline so you get usable data instead of a skeleton.

ChocoData is one such service, with a universal endpoint plus a set of dedicated endpoints. Its request shape is a single GET carrying your target URL, your API key, and a flag to render JavaScript. Here is the pattern for sending a Pinterest pin URL through the universal endpoint:

import requests

API_KEY = "your_chocodata_key"

target = "https://www.pinterest.com/pin/10696099963436/"

resp = requests.get(
    "https://api.chocodata.com/api/v1/universal",
    params={
        "api_key": API_KEY,
        "url": target,
        "render_js": "true",
    },
    timeout=60,
)

print("STATUS:", resp.status_code)
# resp.text holds the rendered HTML from a residential IP;
# parse the pin image, title, and link with BeautifulSoup
# as you would any rendered page.

I did not run this snippet, because it needs a paid key, so treat the field names as illustrative and confirm the exact parameters against ChocoData’s own docs before you rely on them. The principle holds across any scraper API: you hand it the URL, it returns the page from a rotating residential IP with JavaScript rendered, and the detection problem becomes the vendor’s problem instead of yours. ChocoData advertises residential proxy rotation and a universal endpoint alongside 453 dedicated endpoints, which are the features that matter against Pinterest’s rendering gate and IP fingerprinting.

The honest framing: a scraper API removes the technical block, and the legal one stays with you. Routing through residential proxies does not grant you permission under Pinterest’s Terms, and it does not exempt you from GDPR when you collect personal data. Use it for public, non-personal data, apply the same restraint you would to any scraping that needs to avoid getting blocked, and keep personal profiles of private individuals out of your dataset.

Should you build your own Pinterest scraper or use an API?

Use the official API for accounts you control, a scraper API for public data at volume, and skip the pure requests route for Pinterest entirely. Here is the decision in one table:

Your goalBest approachWhy
Your own boards and analyticsOfficial APISanctioned, stable, richer data
A client’s account (they authorize)Official APISame, with their consent
Public boards or pins, low volumeScraper API or PlaywrightDIY requests gets an empty shell
Public pins or trends at scaleScraper APIHandles IP rotation and rendering
One-off, public, hobbyPlaywrightFree, but expect login modals

The pattern I would follow: if the data is about your own or a consenting account, the official API is the clear winner. If you need public data that no one will authorize, a scraper API is the route that returns it reliably, and you carry the Terms and privacy risk yourself. Pure requests plus BeautifulSoup, the stack that works for so many sites, returns a 200 and an empty __PWS_DATA__ blob on Pinterest, as my June 2026 probe showed.

For the fundamentals under all of this, including headers, rendering, and parsing, see the web scraping guide. If you want to run a browser-based scraper inside a larger crawl, the Scrapy guide shows how to structure it, and the Python scraping guide covers the requests-and-parsing stack end to end.

FAQ

Is there a free Pinterest API for scraping pins?

Not for arbitrary public content. Pinterest offers an official Developers API (the v5 REST API), but it is OAuth-scoped: it returns data for accounts and boards that authorize your app, plus ad and analytics endpoints for business accounts. There is no free, official endpoint that hands you any public board's pins by URL. The old unofficial resource endpoints still exist but require a valid session and CSRF token, which is exactly what a logged-out scraper lacks.

Can I scrape Pinterest without logging in?

Barely. A logged-out GET of a search or pin URL returns a 200, but the body is a JavaScript shell, not data. In my June 2026 test the page title was just 'Pinterest', the embedded __PWS_DATA__ state object contained zero pin records and zero i.pinimg.com image URLs, and a login form was present. Everything substantive loads after authentication through an internal API. To get real public data you render the page in a browser or route through a scraper API.

Does Pinterest's robots.txt allow scraping?

No, not for the paths that matter. I fetched https://www.pinterest.com/robots.txt in June 2026: it carries 653 Disallow lines and explicitly blocks /search/ and /search?, plus dozens of /pin/ sub-paths like /pin/*/comments and /pin/*/related-products. It does Allow a few resource paths and links a bot-submission allowlist form for crawlers that want access. robots.txt is a crawling directive rather than a law. Combined with Pinterest's Terms, it signals that automated collection is unwelcome.

Will my Pinterest account get banned for scraping?

It can. Pinterest's Terms of Service prohibit accessing the service through automated means and collecting data without permission. Logging in a bot account to bypass the login wall is the fastest way to trigger a rate limit, a CAPTCHA challenge, or a suspension. The account-ban risk applies specifically when you scrape while authenticated. Scraping logged-out avoids the account risk but, as my probe showed, returns no usable data anyway.

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.