몇 달 전, 우리 엔지니어 한 명이 주말에 직접 짠 Python 스크립트를 보여준 적이 있습니다. 시장 조사 프로젝트를 위해 Pinterest에서 제품 영감 이미지를 가져오려던 코드였죠. 그는 실행 버튼을 눌렀고, 결과는… 16개의 핀이었습니다. 2,000개가 넘는 보드에서 말이죠. 그는 화면을 한참 바라보다가 저를 쳐다보더니 이렇게 말했습니다. “Pinterest가 저를 놀리는 것 같아요.”
그는 혼자가 아닙니다. Python으로 Pinterest를 스크래핑하려는 개발자들이 가장 자주 겪는 좌절이 바로 이것입니다. requests와 BeautifulSoup로 Pinterest URL에 접속해 보면, 결과는 몇 개 안 되는 항목이거나 텅 빈 HTML 껍데기뿐이죠. 이유는 간단합니다. Pinterest는 JavaScript로 완전히 렌더링되는 싱글 페이지 앱이라서, 일반적인 HTTP 요청만으로는 실제 콘텐츠를 볼 수 없습니다. 이 가이드에서는 왜 이런 일이 생기는지, 어떤 방식이 실제로 통하는지(Playwright, 내부 API 인터셉트, 그리고 같은 노코드 도구), 그리고 핀·보드·사용자 프로필·무한 스크롤·원본 해상도 이미지까지 단계별 코드로 어떻게 가져오는지 알려드립니다. 운영 수준의 스크래퍼를 만들고 싶든, 아니면 빠르게 데이터를 수집하고 싶든, 이 글에서 필요한 내용을 모두 확인할 수 있습니다.
Pinterest 스크래핑이란?
Pinterest 스크래핑은 Pinterest에서 핀 이미지, 제목, 설명, 보드 이름, 팔로워 수, URL 같은 데이터를 프로그램으로 추출하는 작업입니다. 핀을 하나씩 직접 둘러보며 저장하는 대신, 코드나 도구를 이용해 검색 결과, 보드, 사용자 프로필에서 구조화된 데이터를 대량으로 수집하는 방식입니다.
2025년 말 기준 플랫폼에는 과 이 있습니다. Pinterest는 웹에서 가장 풍부한 시각 데이터 소스 중 하나입니다. 기업 입장에서는 이 데이터가 매우 가치 있습니다. 제품 트렌드를 추적하든, 경쟁사 콘텐츠를 벤치마킹하든, 인플루언서 리스트를 만들든 모두 활용도가 높습니다.
왜 Python으로 Pinterest를 스크래핑할까?
이제 Pinterest는 단순히 결혼식 무드보드용 서비스가 아닙니다. 엄연한 비즈니스 인텔리전스 플랫폼이죠. 가 브랜드 핀을 보고 무언가를 구매한 경험이 있고, 입니다. 즉, 사용자는 구매 의도를 가지고 들어오지만 특정 브랜드에 묶여 있지 않습니다. 발견의 기회가 매우 큰 셈이고, 그래서 많은 팀이 구조화된 Pinterest 데이터를 원합니다.
팀별로 보면 이런 식으로 활용할 수 있습니다:
| 팀 | 필요한 데이터 | 비즈니스 가치 |
|---|---|---|
| 이커머스 운영 | 제품 이미지, 가격, 인기 있는 미적 트렌드 | 경쟁력 있는 가격 책정, 트렌드 기반 재고 운영 |
| 마케팅 | 보드 성과, 핀 참여도, 경쟁사 콘텐츠 | 콘텐츠 전략, 캠페인 벤치마킹 |
| 세일즈 / 리드 발굴 | 크리에이터 프로필, 팔로워 수, 연락처 정보 | 인플루언서 아웃리치, 파트너십 타깃팅 |
| 부동산 | 홈 스테이징 핀, 인테리어 트렌드, 공간 레이아웃 | 매물 사진 전략, 스테이징 가이드 |
| 콘텐츠 크리에이터 | 인기 주제, 잘 먹히는 형식, 시즌별 테마 | 콘텐츠 캘린더, 비주얼 스타일 리서치 |
핵심은 이겁니다. Pinterest의 공식 API는 제한적입니다. 비즈니스 계정이 필요하고, 승인 과정에서는 앱 데모 영상까지 제출해야 하며, 접근 가능한 데이터도 내 계정 데이터로 한정됩니다. 공개 보드, 검색 결과, 경쟁사 프로필을 보고 싶다면 스크래핑이 현실적인 대안입니다. 그래서 많은 팀이 Python을 선택하고, 설정 없이 바로 결과가 필요한 경우에는 Thunderbit 같은 노코드 도구로 향합니다.
BeautifulSoup만으로는 Pinterest가 안 되는 이유와, 실제로 통하는 방식
requests + BeautifulSoup로 Pinterest를 스크래핑해 봤는데 16개만 나오거나 빈 페이지가 나온다면, 착각이 아닙니다. Pinterest는 React 기반으로 만들어져 있고, 콘텐츠를 전부 JavaScript로 렌더링합니다. 일반 HTTP 요청으로 Pinterest URL을 가져오면 서버는 최소한의 HTML 뼈대만 반환합니다. 몇 개의 <link>와 <script> 태그, 그리고 React가 앱을 심을 빈 <div> 정도만 있죠. 모든 핀 카드, 이미지, 제목, 그리드 레이아웃은 브라우저에서 JavaScript가 실행된 뒤에야 주입됩니다.
JavaScript 실행이 없으면 핀도 없습니다.
그렇다면 무엇이 통할까요? 주요 방식은 아래와 같습니다.
| 방식 | JS 처리 가능? | 전체 데이터 확보? | 복잡도 | 적합한 경우 |
|---|---|---|---|---|
requests + BeautifulSoup | 아니오 | 약 0~16개 | 낮음 | Pinterest에는 부적합 |
| Selenium / Playwright | 예 | 예, 스크롤 로직 포함 | 중간 | 완전한 제어, Python 파이프라인 |
| Pinterest 내부 API 인터셉트 | 예 | 예, 페이지네이션 JSON | 높음 | 최대 데이터, 브라우저 불필요 |
| 서드파티 Scraper API | 예 | 상황에 따라 다름 | 낮음 | 인프라 없이 대량 수집 |
| 노코드 도구(Thunderbit) | 예 | AI 구조화 | 매우 낮음 | 비기술 사용자, 빠른 결과 |
이 튜토리얼에서는 Python 방식으로 Playwright를 추천합니다. JavaScript를 렌더링할 수 있고, 스크롤 시뮬레이션도 지원하며, 유지보수도 잘 되고(, 채용 공고 수는 ), 벤치마크 기준 Selenium보다 . 노코드 방식도 원한다면 그 부분도 함께 다루겠습니다.
Pinterest 공식 API vs Python 스크래핑 vs 노코드: 어떤 방법을 선택할까
코드를 쓰기 전에 먼저 물어볼 가치가 있습니다. 정말 코드를 짜야 할까요? 아래 의사결정 표를 참고하세요.
| 기준 | Pinterest API | Python 스크래핑 | Thunderbit(노코드) |
|---|---|---|---|
| 승인 필요 여부 | 비즈니스 계정 + 데모 영상 | 없음 | 없음 |
| 공개 핀/보드 접근 | 제한적(내 데이터만) | 전체 가능 | 전체 가능 |
| 원본 해상도 이미지 다운로드 | 상황에 따라 다름 | 예, URL 파싱으로 가능 | 예, 이미지 추출로 가능 |
| 무한 스크롤 처리 | 해당 없음 | 예, 코드로 처리 | 자동 |
| 유지보수 필요성 | 낮음 | 높음(셀렉터가 깨짐) | 없음(AI가 적응) |
| Sheets/Airtable 내보내기 | 수동 | 사용자 정의 코드 | 기본 제공 |
| 설정 시간 | 몇 시간~며칠 | 30~60분 | 2분 |
마케터, 이커머스 운영 담당자, 또는 Python 스크립트를 작성하거나 유지보수하지 않고도 Pinterest 데이터를 스프레드시트로 받고 싶은 사람이라면 는 가장 빠른 선택입니다. Pinterest 페이지를 열고 "AI Suggest Fields"를 누른 뒤 "Scrape"를 클릭하면 Google Sheets, Excel, Airtable, Notion으로 바로 내보낼 수 있습니다. 하위 페이지 스크래핑 기능을 이용하면 개별 핀 링크까지 따라가 데이터를 자동으로 더 풍부하게 만들 수도 있습니다. 코드를 한 줄도 써본 적 없는 팀원들이 3분도 안 되어 Google Sheet에 500개가 넘는 핀을 넣는 걸 직접 본 적이 있습니다.
직접 제어가 필요하거나, Python 파이프라인에 스크래핑을 통합하고 싶거나, 단지 만드는 과정을 즐기는 분이라면 계속 읽어주세요.
Pinterest 스크래핑을 위한 Python 환경 설정
- 난이도: 중급
- 소요 시간: 약 30~60분(코딩 및 테스트 포함)
- 준비물: Python 3.9+, Chrome 브라우저(테스트용), 터미널/명령줄 접근 권한
Playwright와 의존성 설치
먼저 프로젝트 폴더를 만들고 가상환경을 설정합니다:
1mkdir pinterest-scraper
2cd pinterest-scraper
3python -m venv venv
4source venv/bin/activate # Windows에서는: venv\Scripts\activate
Playwright를 설치하고 Chromium 브라우저 바이너리를 내려받습니다:
1pip install playwright
2playwright install chromium
데이터 내보내기를 위해 Python 내장 json, os, csv 모듈도 사용합니다. 별도 설치는 필요 없습니다.
프로젝트 폴더 구조
처음부터 정리해 두는 것을 추천합니다:
1pinterest-scraper/
2├── scraper.py
3├── config.py
4├── output/
5│ ├── pins.json
6│ └── pins.csv
7└── images/
8 ├── board-name-1/
9 └── board-name-2/
config.py에서는 user agent 문자열을 설정하세요. Pinterest는 기본적인 헤드리스 브라우저 시그니처를 차단하므로, 그럴듯한 값을 써야 합니다:
1USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"
1단계: Pinterest 검색 URL 만들기
검색어를 템플릿에 넣어 검색 URL을 구성합니다:
1query = "mid century modern furniture"
2url = f"https://www.pinterest.com/search/pins/?q={query.replace(' ', '%20')}&rs=typed"
이 방식은 어떤 검색어에도 적용할 수 있습니다. rs=typed 파라미터는 해당 검색이 추천이 아니라 직접 입력된 검색어임을 Pinterest에 알려줍니다. 경우에 따라 결과 관련성에 영향을 줍니다.
2단계: 헤드리스 브라우저를 실행하고 페이지 불러오기
핵심 Playwright 설정은 아래와 같습니다. 맞춤 user agent를 꼭 보세요. 없으면 Pinterest가 요청을 막거나 로그인 화면을 띄울 가능성이 큽니다.
1import asyncio
2from playwright.async_api import async_playwright
3from config import USER_AGENT
4async def scrape_search(query, max_pins=100):
5 url = f"https://www.pinterest.com/search/pins/?q={query.replace(' ', '%20')}&rs=typed"
6 async with async_playwright() as p:
7 browser = await p.chromium.launch(headless=True)
8 page = await browser.new_page(
9 user_agent=USER_AGENT,
10 viewport={"width": 1920, "height": 1080}
11 )
12 await page.goto(url)
13 await asyncio.sleep(3) # JS가 초기 핀을 렌더링할 시간을 줌
이 코드가 실행되면 페이지에는 보통 25~50개 정도의 초기 핀이 로드되어 있어야 합니다.
3단계: 페이지에서 핀 데이터 추출하기
Pinterest는 각 핀을 data-test-id='pinWrapper'가 붙은 div로 감쌉니다. 내부에는 핀 URL과 제목이 담긴 링크(<a>)가 있고(제목은 aria-label로 확인), 썸네일 URL이 들어 있는 <img>도 있습니다.
1 results = []
2 pins = await page.query_selector_all("div[data-test-id='pinWrapper']")
3 for pin in pins:
4 link = await pin.query_selector("a")
5 if not link:
6 continue
7 title = await link.get_attribute("aria-label") or ""
8 href = await link.get_attribute("href") or ""
9 img = await pin.query_selector("img")
10 src = await img.get_attribute("src") if img else ""
11 results.append({
12 "title": title,
13 "url": f"https://www.pinterest.com{href}" if href.startswith("/") else href,
14 "image_url": src
15 })
이 시점의 results에는 첫 화면에 보이는 핀들이 들어 있습니다. 더 많이 가져오려면 스크롤이 필요합니다. 이제 가장 중요한 부분으로 넘어가겠습니다.
4단계: 결과를 JSON 또는 CSV로 저장하기
추출한 뒤에는 데이터를 파일로 저장해 두면 활용하기 쉽습니다:
1import json
2import csv
3def save_json(data, filepath="output/pins.json"):
4 with open(filepath, "w", encoding="utf-8") as f:
5 json.dump(data, f, ensure_ascii=False, indent=2)
6def save_csv(data, filepath="output/pins.csv"):
7 if not data:
8 return
9 with open(filepath, "w", newline="", encoding="utf-8-sig") as f:
10 writer = csv.DictWriter(f, fieldnames=data[0].keys())
11 writer.writeheader()
12 writer.writerows(data)
CSV를 Excel에서 열 계획이라면 utf-8-sig 인코딩을 사용하세요. 깨진 문자를 막아 줍니다.
전체 Pinterest 보드와 사용자 프로필 스크래핑하기
이 부분은 기존 튜토리얼에서 큰 공백입니다. 보드나 프로필 스크래핑을 깊게 다룬 경쟁 가이드를 하나도 찾지 못했습니다. 그런데 포럼에서는 가장 많이 요청되는 기능 중 하나입니다. 사용자들은 보드의 모든 핀을 내려받고, 이미지들을 보드별 폴더에 정리하고, 팔로워 수나 보드 목록 같은 프로필 수준 데이터도 가져오고 싶어 합니다.
보드 URL에서 모든 핀 스크래핑하기
보드 URL은 https://www.pinterest.com/{username}/{board-name}/ 형태입니다. DOM 구조는 검색 결과와 비슷합니다. 핀은 여전히 div[data-test-id='pinWrapper']로 감싸져 있지만, 전체를 로드하려면 스크롤이 필요합니다.
1async def scrape_board(board_url, max_pins=500):
2 async with async_playwright() as p:
3 browser = await p.chromium.launch(headless=True)
4 page = await browser.new_page(user_agent=USER_AGENT, viewport={"width": 1920, "height": 1080})
5 await page.goto(board_url)
6 await asyncio.sleep(3)
7 seen_ids = set()
8 all_pins = []
9 for scroll_round in range(100): # 안전 제한
10 pins = await page.query_selector_all("div[data-test-id='pinWrapper']")
11 new_count = 0
12 for pin in pins:
13 link = await pin.query_selector("a")
14 if not link:
15 continue
16 href = await link.get_attribute("href") or ""
17 if href in seen_ids:
18 continue
19 seen_ids.add(href)
20 new_count += 1
21 title = await link.get_attribute("aria-label") or ""
22 img = await pin.query_selector("img")
23 src = await img.get_attribute("src") if img else ""
24 all_pins.append({
25 "title": title,
26 "url": f"https://www.pinterest.com{href}" if href.startswith("/") else href,
27 "image_url": src
28 })
29 print(f"스크롤 {scroll_round + 1}: 고유 핀 {len(all_pins)}개 수집 완료")
30 if new_count == 0 or len(all_pins) >= max_pins:
31 break
32 prev_height = await page.evaluate("document.body.scrollHeight")
33 await page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
34 await asyncio.sleep(2.5)
35 curr_height = await page.evaluate("document.body.scrollHeight")
36 if curr_height == prev_height:
37 break # 더 이상 콘텐츠 없음
38 await browser.close()
39 return all_pins
주의할 점이 하나 있습니다. 보드 페이지에는 때때로 저장된 핀과 알고리즘 추천을 나누는 "More Ideas" 탭이 있습니다. 보드의 실제 저장 핀만 원한다면 이 구분선이 보일 때 스크롤을 멈추면 됩니다.
사용자 프로필에서 보드, 팔로워 수, 핀 추출하기
프로필 URL은 https://www.pinterest.com/{username}/ 형태입니다. 프로필 페이지에서 추출할 수 있는 항목은 다음과 같습니다:
- 팔로워/팔로잉 수:
div[data-test-id='follower-count']를 확인 - 보드 목록: 각 보드는
/{username}/{board-name}/로 연결되는 카드 - 총 핀 수: 프로필 헤더에 표시되는 경우가 있음
1async def scrape_profile(username):
2 url = f"https://www.pinterest.com/{username}/"
3 async with async_playwright() as p:
4 browser = await p.chromium.launch(headless=True)
5 page = await browser.new_page(user_agent=USER_AGENT, viewport={"width": 1920, "height": 1080})
6 await page.goto(url)
7 await asyncio.sleep(3)
8 # 팔로워 수 추출
9 follower_el = await page.query_selector("div[data-test-id='follower-count']")
10 followers = await follower_el.inner_text() if follower_el else "N/A"
11 # 보드 링크 추출
12 board_links = await page.query_selector_all("a[href*='/" + username + "/']")
13 boards = []
14 for bl in board_links:
15 href = await bl.get_attribute("href") or ""
16 name = await bl.get_attribute("aria-label") or href.split("/")[-2]
17 if href.count("/") >= 3 and href != f"/{username}/":
18 boards.append({"name": name, "url": f"https://www.pinterest.com{href}"})
19 await browser.close()
20 return {"username": username, "followers": followers, "boards": boards}
프로필의 모든 보드에 있는 핀까지 가져오려면 보드 목록을 반복하면서 각 보드마다 scrape_board()를 호출하면 됩니다. 다운로드한 이미지는 보드별 폴더에 자동으로 정리할 수 있습니다.
운영 수준의 무한 스크롤 처리기 만들기
이 부분이 장난감 스크래퍼와 실제 스크래퍼를 가릅니다. 가장 큰 고통 포인트는 — 포럼 글만 열두 개는 봤습니다 — 스크래퍼가 16~25개 정도만 가져오고 멈추는 경우입니다. 충분히 스크롤하지 않았거나, for i in range(5): scroll()처럼 고정 횟수만 돌리고 운에 맡기기 때문이죠.
이런 방식은 신뢰할 수 없습니다. Pinterest는 스크롤 이벤트가 발생할 때마다 약 25개씩 새 콘텐츠를 불러옵니다. 다섯 번만 스크롤하면 125개를 얻을 수도 있지만, 네트워크가 느리면 75개에 그칠 수도 있고, 배치가 더 작으면 150개가 될 수도 있습니다. 더 똑똑한 패턴이 필요합니다.
새 콘텐츠가 없을 때까지 스크롤하는 패턴
아래는 고유 핀 ID를 추적하고, 설정 가능한 타임아웃을 사용하며, 재시도 로직과 진행 상황 출력까지 포함한 견고한 스크롤 함수입니다:
1import time
2import random
3async def scroll_and_collect(page, max_pins=1000, max_scrolls=200, scroll_pause=2.5):
4 seen_ids = set()
5 all_pins = []
6 no_new_count = 0
7 for i in range(max_scrolls):
8 pins = await page.query_selector_all("div[data-test-id='pinWrapper']")
9 new_this_round = 0
10 for pin in pins:
11 link = await pin.query_selector("a")
12 if not link:
13 continue
14 href = await link.get_attribute("href") or ""
15 if href in seen_ids:
16 continue
17 seen_ids.add(href)
18 new_this_round += 1
19 title = await link.get_attribute("aria-label") or ""
20 img = await link.query_selector("img")
21 src = await img.get_attribute("src") if img else ""
22 all_pins.append({
23 "title": title,
24 "url": f"https://www.pinterest.com{href}" if href.startswith("/") else href,
25 "image_url": src
26 })
27 print(f" 스크롤 {i+1}: 새 핀 {new_this_round}개 | 총 고유 핀 {len(all_pins)}개")
28 if len(all_pins) >= max_pins:
29 print(f" max_pins 제한({max_pins})에 도달했습니다. 중지합니다.")
30 break
31 if new_this_round == 0:
32 no_new_count += 1
33 if no_new_count >= 3:
34 print(" 연속 3회 스크롤에서 새 핀이 없습니다. 콘텐츠 끝으로 판단합니다.")
35 break
36 else:
37 no_new_count = 0
38 prev_height = await page.evaluate("document.body.scrollHeight")
39 await page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
40 await asyncio.sleep(scroll_pause + random.uniform(0.5, 1.5))
41 curr_height = await page.evaluate("document.body.scrollHeight")
42 if curr_height == prev_height and new_this_round == 0:
43 print(" 페이지 높이가 변하지 않았고 새 핀도 없습니다. 피드가 끝난 것으로 보입니다.")
44 break
45 return all_pins
이 설계가 잘 작동하는 이유는 다음과 같습니다.
- href 기준 중복 제거: 각 핀 URL은 고유하므로 ID처럼 사용합니다. 스크롤 중 DOM이 다시 렌더링되어도 같은 핀을 두 번 세지 않습니다.
- 3회 연속 실패 규칙: 연속 세 번 스크롤했는데 새 핀이 없으면 중지합니다. 페이지가 아직 로딩 중이지만 실제 새 콘텐츠가 없는 경우를 처리할 수 있습니다.
- 랜덤 지연 추가: 스크롤 사이에 0.5~1.5초의 랜덤 지연을 넣으면 사람처럼 보이고, 봇 차단 가능성도 줄어듭니다.
- 최대 스크롤 제한: 문제가 생겼을 때 무한 루프를 막습니다.
예외 상황 다루기
- "More Ideas" 구분선: 보드 페이지에서 Pinterest가 때때로 "More Ideas" 섹션을 삽입합니다. 보드의 실제 핀만 원한다면 이 요소가 나타났을 때 스크롤을 중단할 수 있습니다.
- 긴 세션에서의 rate limiting: 수천 개의 핀이 있는 보드를 오래 스크롤하면 Pinterest가 응답을 늦추기 시작할 수 있습니다. 간헐적으로(연속 3회는 아니지만) 새 핀이 0개로 나오는 경우에는 스크롤 간격을 5초 이상으로 늘려 보세요.
Pinterest 이미지 원본 해상도로 받기(썸네일 말고)
이 문제는 정말 사람을 짜증 나게 합니다. 핀을 잔뜩 스크래핑해서 이미지를 받아 봤는데 전부 236px짜리 작은 썸네일인 경우가 많죠. 포럼 사용자들은 이를 “화질이 쓰레기다, 너무 작다”고 표현하기도 합니다. 해결책은 Pinterest 이미지 URL 구조를 이해하는 것입니다.
Pinterest 이미지 URL 경로 이해하기
Pinterest 이미지는 모두 https://i.pinimg.com/{size}/{hash}.jpg 형식으로 제공됩니다. 여기서 {size} 세그먼트가 해상도를 결정합니다:
| 크기 경로 | 해상도 | 용도 |
|---|---|---|
/236x/ | 너비 236px | 기본 그리드 보기(기본적으로 받는 값) |
/474x/ | 너비 474px | 중간 해상도 |
/736x/ | 너비 736px | 핀 상세/확대 보기 |
/originals/ | 원본 업로드 해상도 | 전체 해상도 |
유틸 함수: Pinterest 이미지 URL을 고해상도로 업그레이드하기
아래 함수는 가능한 가장 높은 품질로 Pinterest 이미지 URL을 다시 작성하고, 실패 시 자동으로 다른 크기를 시도합니다:
1import requests as req
2def upgrade_image_url(url, preferred_size="originals"):
3 """Pinterest 이미지 URL을 가능한 가장 높은 해상도로 바꿉니다."""
4 sizes = ["originals", "736x", "474x", "236x"]
5 if preferred_size not in sizes:
6 preferred_size = "originals"
7 for size in sizes[sizes.index(preferred_size):]:
8 upgraded = url
9 for s in sizes:
10 upgraded = upgraded.replace(f"/{s}/", f"/{size}/")
11 try:
12 resp = req.head(upgraded, timeout=5, allow_redirects=True)
13 if resp.status_code == 200:
14 return upgraded
15 except Exception:
16 continue
17 return url # 모두 실패하면 원본 반환
중요 참고 사항(2025년 기준): /originals/ 경로는 점점 더 자주 HTTP 403 Forbidden 오류를 반환합니다. 에서도 2025년 중반 기준 이 동작이 확인되었습니다. 안정적으로 확보 가능한 최고 해상도는 /736x/입니다. 위 함수는 /originals/를 먼저 시도한 뒤 자동으로 /736x/로 내려갑니다.
이미지를 정리된 폴더에 다운로드하기
1import os
2import time
3def download_images(pins, folder="images/default", delay=1.5):
4 os.makedirs(folder, exist_ok=True)
5 for i, pin in enumerate(pins):
6 img_url = upgrade_image_url(pin.get("image_url", ""), preferred_size="736x")
7 if not img_url:
8 continue
9 filename = f"pin_{i+1}.jpg"
10 filepath = os.path.join(folder, filename)
11 try:
12 resp = req.get(img_url, timeout=15)
13 if resp.status_code == 200:
14 with open(filepath, "wb") as f:
15 f.write(resp.content)
16 print(f" {filename} 다운로드 완료 ({len(resp.content) // 1024} KB)")
17 else:
18 print(f" {filename} 실패: HTTP {resp.status_code}")
19 except Exception as e:
20 print(f" {filename} 다운로드 중 오류: {e}")
21 time.sleep(delay + random.uniform(0.3, 0.8))
다운로드 사이에 속도 제한용 지연을 넣으세요. 저는 1.5~2.3초에 랜덤 지연을 더해 사용합니다. 이렇게 하지 않으면 Pinterest가 몇백 요청만에 IP를 차단할 수 있습니다.
스크래핑한 Pinterest 데이터 내보내기
CSV 또는 JSON으로 내보내기
앞서 기본은 다뤘습니다. 더 큰 데이터셋(1만 개 이상의 핀)이라면 JSON Lines 형식을 고려해 보세요. 한 줄에 JSON 객체 하나씩 저장하는 방식이라 스트리밍과 후처리가 더 쉽습니다:
1def save_jsonl(data, filepath="output/pins.jsonl"):
2 with open(filepath, "w", encoding="utf-8") as f:
3 for item in data:
4 f.write(json.dumps(item, ensure_ascii=False) + "\n")
Google Sheets, Airtable, Notion으로 내보내기
Python에서 바로 Google Sheets로 데이터를 보내려면 gspread 라이브러리와 Google Cloud 서비스 계정이 필요합니다. Airtable은 pyairtable, Notion은 notion-client를 사용합니다. 각각 API 키 설정이 필요하고, 파이프라인의 복잡도도 꽤 올라갑니다.
또는 — 편향이 좀 있지만 솔직히 가장 빠른 방법이기도 한데 — 를 사용해 Pinterest를 스크래핑한 뒤 한 번 클릭으로 이들 서비스에 바로 내보낼 수 있습니다. API 키도, 서비스 계정도, 추가 코드도 필요 없습니다. 이 내보내기를 기본 지원합니다.
Pinterest 스크래핑 중 차단을 피하는 팁
ScrapeOps 기준 Pinterest의 봇 차단 회피 난이도는 입니다. 아주 쉽진 않지만 가장 어려운 대상도 아닙니다. 브라우저 지문, 행동 분석, IP 기반 rate limiting을 사용합니다. 다음이 실제로 효과적입니다.
- User agent 순환: 실제 Chrome user agent 문자열을 여러 개 준비하고 세션마다 무작위로 선택하세요.
- 랜덤 지연 추가: 스크롤과 요청 사이에 2~5초, 여기에 jitter를 더하세요. 프록시 없이 진행할 경우 10~15초로 늘리는 것이 좋습니다.
- 현실적인 viewport 사용:
viewport={"width": 1920, "height": 1080}로 설정하세요. 너무 작거나 특이한 크기는 피하세요. - 대량 수집에는 프록시 고려: 수천 개의 핀을 스크래핑한다면 residential proxy 회전을 고려하세요. 없으면 몇백 요청 후 IP 차단이 올 수 있습니다.
robots.txt를 존중: Pinterest의robots.txt는 대부분의 자동 크롤러를 막고 있으며 이 있습니다. 준수 측면에서 염두에 두세요.- 로그인된 상태 스크래핑은 피하기: 로그인하지 않은 공개 콘텐츠만 다루세요. 로그인 뒤의 스크래핑은 법적·기술적 위험이 모두 커집니다.
Thunderbit는 AI 엔진을 통해 봇 차단과 CAPTCHA 대응을 자동으로 처리합니다. 노코드 방식이라면 유지보수할 일이 하나 줄어듭니다.
Pinterest 스크래핑의 법적·윤리적 고려사항
이 부분은 글의 핵심은 아니지만 중요하므로 짧게만 짚겠습니다.
Pinterest의 서비스 약관(2a항)에는 “당사의 명시적 사전 허가 없이 자동화 수단을 사용하여 Pinterest의 데이터나 콘텐츠를 무단으로 스크래핑, 수집, 검색, 복사하거나 기타 방식으로 접근하지 않을 것”에 동의한다고 명시되어 있습니다. 다만, 법원은 일반적으로 공개된 데이터의 스크래핑이 Computer Fraud and Abuse Act를 위반하지 않는다고 판단해 왔습니다. 예를 들어 과 Meta v. Bright Data(2024년 1월)에서는 로그인하지 않은 상태에서 공개적으로 보이는 데이터를 스크래핑하는 것이 합법이라고 판결했습니다.
기본 원칙은 다음과 같습니다.
- 로그인하지 않은 상태에서 공개 콘텐츠만 스크래핑하기
- 스크래핑한 데이터를 스팸이나 사용자 사칭에 사용하지 않기
- 이미지 저작권을 존중하기 — 가능한 경우 메타데이터만 추출하고, 허가 없이 저작권 이미지를 상업적으로 재배포하지 않기
- 스크래핑 데이터를 상업적으로 사용할 계획이라면 변호사와 상담하기
법적 쟁점을 더 깊게 보고 싶다면 를 참고하세요.
정리: 무엇을 배웠고, 다음엔 뭘 할까
이제 정적 스크래핑이 Pinterest에서 실패하는 이유(React SPA라서 JavaScript 없이는 데이터가 안 보임), Playwright로 검색 결과·보드·사용자 프로필을 스크래핑하는 방법, 16개에서 멈추지 않는 운영 수준의 무한 스크롤 처리기 만드는 법, 그리고 작은 썸네일이 아니라 원본 해상도 이미지를 얻는 방법까지 알게 되었습니다.
중요한 내용만 빠르게 정리하면:
requests+ BeautifulSoup는 Pinterest에서 거의 통하지 않습니다. 시간 낭비하지 마세요.- Playwright는 이 작업에 가장 적합한 Python 도구입니다. 빠르고, 지원도 잘되며, JS 렌더링을 자연스럽게 처리합니다.
- 무한 스크롤에는 고정 횟수 반복이 아니라 중복 제거 기반의 스크롤 루프가 필요합니다.
- 원본 해상도 이미지는 URL 경로를 바꿔야 합니다.
/736x/를 목표로 하세요./originals/는 자주 403을 반환합니다. - 보드와 프로필 스크래핑은 기존 튜토리얼에서 부족하지만, 올바른 셀렉터만 있으면 어렵지 않습니다.
- 비개발자나 속도가 중요한 팀이라면 으로 Pinterest를 2번 클릭만에 스크래핑하고 Google Sheets, Excel, Airtable, Notion으로 내보낼 수 있습니다. Python은 필요 없습니다. 으로 무료 체험해 보세요.
Python 파이프라인을 만들고 있다면, 이 가이드의 코드는 탄탄한 출발점이 됩니다. 단순히 데이터만 필요하다면 Thunderbit가 더 빠른 지름길입니다. 어느 쪽이든 이제 16개의 핀과 멍한 표정에 머물 필요는 없습니다.
스크래핑과 데이터 추출에 대해 더 알고 싶다면 , , 가이드를 확인해 보세요. 를 살펴보거나 에서 튜토리얼을 볼 수도 있습니다.
FAQ
1. BeautifulSoup만으로 Pinterest를 스크래핑할 수 있나요?
단독으로는 효과적이지 않습니다. Pinterest는 모든 콘텐츠를 JavaScript로 렌더링하므로 requests + BeautifulSoup는 빈 HTML 껍데기만 보게 됩니다. 페이지를 먼저 렌더링할 수 있는 Playwright나 Selenium 같은 헤드리스 브라우저가 필요하거나, JS 렌더링을 자동 처리하는 Thunderbit 같은 노코드 도구를 사용할 수 있습니다.
2. 한 번의 세션에서 Pinterest 핀을 얼마나 많이 스크래핑할 수 있나요?
스크롤 로직과 봇 차단 대응에 따라 다릅니다. 이 가이드의 운영 수준 무한 스크롤 처리기(중복 제거, 타임아웃, 재시도 로직 포함)를 사용하면 보드나 검색어당 수백~수천 개의 핀을 안정적으로 수집할 수 있습니다. 매우 큰 보드라면 스크롤과 수집에 몇 분 정도는 예상해야 합니다.
3. 스크래핑한 Pinterest 이미지가 왜 이렇게 작게 나오나요?
기본적으로 Pinterest는 그리드 보기에서 /236x/ 썸네일을 제공합니다. 더 높은 해상도를 원하면 이미지 URL 경로를 /736x/나 /originals/로 바꾸세요. 다만 2025년 기준 /originals/는 403 오류를 점점 더 자주 반환하므로, 안정적인 최대값은 /736x/입니다.
4. Pinterest 스크래핑은 합법인가요?
공개적으로 볼 수 있는 데이터의 스크래핑은 최근 판결들(예: hiQ v. LinkedIn, Meta v. Bright Data)에서 일반적으로 허용되는 것으로 받아들여지고 있지만, Pinterest의 서비스 약관은 무단 자동화 접근을 금지합니다. 공개 콘텐츠만 다루고, 스팸에 사용하지 않으며, 저작권을 존중하고, 상업적 목적이라면 법률 자문을 받으세요.
5. Pinterest를 스크래핑할 수 있는 최고의 노코드 대안은 무엇인가요?
는 Pinterest 핀 데이터 — 제목, 이미지, URL, 설명 — 를 2번 클릭만으로 추출할 수 있고, Google Sheets, Excel, Airtable, Notion으로 바로 내보낼 수 있습니다. JavaScript 렌더링, 무한 스크롤, 봇 차단도 자동으로 처리하므로 코드를 작성하거나 유지보수할 필요가 없습니다.
더 알아보기