Pinterest動画の「保存したい」を技術で解決:スクレイピングからCDN解析まで、実践的な実装メモ
こんにちは、SNSのメディアアーカイブを趣味でやっているエンジニアです。
先日、デザインのインスピレーション収集プロジェクトで「Pinterest」にピン留めされた動画コンテンツをローカルに保存する必要が出てきました。「え、Pinterestの動画ってダウンロードできるの?」と思われるかもしれませんが、実は適切なアプローチを取れば、技術的には十分解析可能です。
ただし、最初に断っておきます。本記事で紹介する手法は、あくまで個人の学習・研究・デザイン参考目的での利用を前提としています。Pinterestの利用規約や著作権法は必ずご自身で確認し、適法な範囲でご活用ください。クリエイターの作品を無断で再配布・商用利用することは絶対に避けましょう。
Pinterest動画の「見えない構造」を技術的に分解する
InstagramやTikTokと違い、Pinterestの動画配信にはいくつかの「ちょっと変わった」特徴があります。
-
GraphQL APIの採用
動画メタデータは、ページ読み込み時に埋め込まれたJSONか、GraphQLエンドポイント経由で取得されます。単純なHTMLパースだけでは不十分なケースが。 -
v.pinimg.com CDNの多重構造
動画本体はv.pinimg.com配下のCDNで配信されており、解像度ごとに異なるURLが用意されています。 -
Lazy Load + 動的読み込み
スクロールに応じて動画が動的に読み込まれるため、静的なrequestsだけではコンテンツを取得できないことがあります。 -
Pin IDとボード権限の絡み
公開ピンと非公開ピンで取得フローが異なり、認証トークンが必要なケースも。
これらの課題を「手動で」突破するのは現実的ではありません。そこで、私が実際に運用している**「最小依存・保守性重視」の解析アプローチ**を紹介します。
核心部分:動画メタデータの取得ロジック(Python例)
まず、Pinterestのピンページから動画URLを抽出する基本的な流れです。requestsとBeautifulSoup、そして正規表現を組み合わせて使います。
import requests
import re
import json
from bs4 import BeautifulSoup
def extract_pinterest_video_url(pin_url: str) -> dict:
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'ja-JP,ja;q=0.9,en;q=0.8',
'Referer': 'https://www.pinterest.com/'
}
# 1. Pinページを取得
resp = requests.get(pin_url, headers=headers)
resp.raise_for_status()
# 2. 埋め込みJSONから動画情報を抽出
# Pinterestは<script id="initial-state">に状態を埋め込んでいる
soup = BeautifulSoup(resp.text, 'html.parser')
script_tag = soup.find('script', id='initial-state')
if not script_tag:
raise ValueError("initial-stateが見つかりません")
# 3. JSONをパースして動画URLを探索
try:
initial_state = json.loads(script_tag.string)
# 構造は変更される可能性があるため、柔軟に探索
video_data = _traverse_video_info(initial_state)
return video_data
except json.JSONDecodeError:
# Fallback: 正規表現でvideo_urlを検索
return _fallback_regex_extract(resp.text)
def _traverse_video_info(obj):
"""再帰的にvideo/urlフィールドを探索する簡易実装"""
if isinstance(obj, dict):
if 'video' in obj and isinstance(obj['video'], dict):
video = obj['video']
if 'video_url' in video:
return {
'video_url': video['video_url'],
'thumbnail': video.get('poster'),
'duration': video.get('duration')
}
for v in obj.values():
result = _traverse_video_info(v)
if result:
return result
elif isinstance(obj, list):
for item in obj:
result = _traverse_video_info(item)
if result:
return result
return None
def _fallback_regex_extract(html: str) -> dict:
"""正規表現によるフォールバック抽出"""
# video_urlのパターンを検索(簡易版)
match = re.search(r'"video_url"\s*:\s*"([^"]+\.mp4[^"]*)"', html)
if match:
return {'video_url': match.group(1).replace('\\u0026', '&')}
raise ValueError("動画URLを抽出できませんでした")
💡 ポイント: Pinterestは頻繁にフロントエンドの構造を変更するため、
initial-stateのような実装詳細に依存しすぎないよう、複数の抽出方法を用意しておくのが吉です。
CDN URLの解決と高解像度取得のコツ
Pinterestの動画URLは、しばしば以下のような形式になっています:
https://v.pinimg.com/videos/mc/720p/ab/cd/ef/abcdef123456.mp4
ここで注目すべきは720pの部分。URLを少し編集するだけで、異なる解像度を取得できることがあります。
def get_available_resolutions(base_url: str) -> list:
"""URLパターンから利用可能な解像度を推定"""
resolutions = ['236p', '480p', '720p', '1080p']
available = []
for res in resolutions:
candidate = re.sub(r'/(?:\d+p)/', f'/{res}/', base_url)
# HEADリクエストで存在確認(実際の運用ではレートリミットに注意)
try:
resp = requests.head(candidate, timeout=3)
if resp.status_code == 200:
available.append((res, candidate))
except:
continue
return available
※ 実際のプロダクションでは、並列リクエストの制限・キャッシュ・エラーハンドリングを適切に実装してください。また、CDNの仕様変更に備えて、取得したURLは可能な限り早くダウンロードする設計が望ましいです。
「毎回スクリプトを動かすのは面倒」→ Webツール化の選択
上記のような処理を毎回Pythonスクリプトで実行するのは、デザイナーやマーケターの皆様にはハードルが高いですよね。そこで、私はこのロジックをWebインターフェースにラップし、URLを貼り付けるだけでダウンロードできるようにしました。
今回ご紹介している Pinterest動画ダウンローダー は、まさにそのような「技術的バックエンド + 直感的UI」を組み合わせたツールです。
技術スタックの裏側(少しだけ公開)
- フロントエンド: Vue 3 + Composition API。URL入力・解析状態・解像度選択をリアクティブに管理。Pinのサムネイルをプリフェッチして表示速度を最適化。
- バックエンド: Python(FastAPI) + asyncio。動画解析を非同期で処理し、同時リクエストへの対応を強化。
- キャッシュ戦略: 同じPinのメタデータはRedisに短期間キャッシュ。ユーザー体験の向上とサーバー負荷軽減を両立。
- セキュリティ: 入力値のサニタイズ、CORS制限、レートリミット(1IP/分間10リクエスト)により悪用を防止。
- プライバシー: 動画データの一時保存はメモリ内のみ。ダウンロード完了後即破棄。アクセスログにも動画URLを記録しません。
特に「サーバー側に動画を保存しない」設計は重要で、ストレージコストと法的リスクの両方を抑えつつ、ユーザーに「自分の責任でダウンロード」してもらう姿勢を明確にしています。
実際の使い流れ(技術者目線で深掘り)
-
URLの正規化とバリデーション
PinterestのURLには?utm_source=...や&pin_id=...のようなパラメータが付いていることがあります。ツール内部ではurllib.parseでクエリを整理し、pinterest.com/pin/またはpin.it/パターンにマッチするか検証しています。 -
ショートURLの展開処理
pin.it/xxxxのようなショートURLは、HTTPリダイレクトを追随して本来のPin URLに解決。requestsのallow_redirects=Trueを活用しつつ、リダイレクトループの検出も実装。 -
フォールバック機構の多層化
initial-stateからの抽出→GraphQL APIへの直接問い合わせ→ヘッドレスブラウザによるレンダリング、を順次試行。これにより「ページ構造が変わって動かない」というトラブルを最小限に抑えています。 -
解像度選択とフォーマット変換
取得した動画がWebM形式の場合、ユーザーの要望に応じてMP4に変換。バックエンドでffmpegを非同期実行し、進捗をWebSocketでフロントに通知しています。
開発中にハマったポイント(実体験・失敗談)
① initial-stateのJSON構造が環境によって異なる
最初は「PCブラウザと同じ構造がスマホでも取れる」と思い込んでいたのですが、実際はUser-Agentによって埋め込みデータが微妙に異なるケースがありました。対策として、ツールでは複数のUser-Agentパターンを試行し、最も豊富なメタデータが取得できるものを選択するロジックを実装。
② CDNの署名パラメータ有効期限
PinterestのCDN URLには?token=...のような署名パラメータが含まれることがあり、有効期限が短い(数時間)場合があります。対策として、メタデータ取得→ダウンロード開始を可能な限り短時間で完結させる設計に。ユーザーには「解析完了後はお早めにダウンロードください」と優しく案内しています。
③ 日本語エラーメッセージの「優しさ」バランス
「JSONパースエラー」と言われても一般ユーザーには伝わらない。かといって「何か問題が発生しました」だけだと技術者は困る。本ツールでは、エラーコード+平易な説明+技術者向けヒントの3層構造でエラー表示を実装。Qiita読者の皆様なら、この「使い手への配慮」、共感していただけるかと。
法的・倫理的なラインについて(ここはしっかり書きます)
技術的に「できる」ことと、「してよい」ことは別問題です。本ツールの利用にあたっては、以下の点を強く意識しています。
✅ 推奨される使い方
- デザインのインスピレーション収集のための個人アーカイブ
- 自分が作成したコンテンツのバックアップ
- 学術研究・市場調査での一時的な参考利用(再配布なし)
❌ 避けるべき使い方
- ダウンロードした動画をSNSやポートフォリオに無断で再アップロード
- クリエイターの作品を商用目的で無断利用
- 大量ダウンロードによるPinterestサーバーへの負荷攻撃
ツールページにも明記していますが、ユーザー自身の行動に対する法的責任はユーザー本人にあります。技術者は「できること」を提供するだけでなく、「どう使うべきか」のガイドラインも併せて提示する責任があると考えています。
Qiita読者への特別ヒント:自作ツールに「機能追加」するなら
もし本記事を読んで「自分でも作ってみよう」と思われた方へ、追加で実装すると喜ばれる機能を3つご紹介します。
-
ボード単位の一括取得
研究用途では「特定のボードにピン留めされた動画をまとめて取得したい」というニーズがあります。ボードURLからPin一覧を抽出→キューに投入→完了順にダウンロード、というフローを実装すると、プロユースに一気に近づきます。
// 簡易例:ボードURLからピン一覧を取得するフロントエンド処理
async function fetchBoardPins(boardUrl) {
const pins = [];
let bookmark = null;
while (true) {
const api = `https://api.pinterest.com/v5/boards/${boardId}/pins${bookmark ? `?bookmark=${bookmark}` : ''}`;
const resp = await fetch(api, {headers: {'Authorization': `Bearer ${token}`}});
const data = await resp.json();
pins.push(...data.items);
if (!data.bookmark) break;
bookmark = data.bookmark;
// レートリミット配慮
await new Promise(r => setTimeout(r, 500));
}
return pins;
}
-
メタデータのJSON/CSV出力
動画タイトル・投稿者・ピン日・ボード名などを構造化データで出力できるようにすると、分析用途での活用が格段に便利になります。 -
ブラウザ拡張機能との連携
Pinterest閲覧中に「この動画保存したい」と思った瞬間に、拡張機能からツールにURLを送信。UXが劇的に向上します。chrome.runtime.sendMessageとFastAPIのエンドポイントを組み合わせるだけなので、実装コストも低め。
おわりに:技術は「創造の補助」のために
Pinterest動画のダウンロードというテーマは、一見「便利ツール」の域を出ないように見えるかもしれません。しかし、裏側を覗くと、GraphQL・CDN解析・非同期処理・UI/UX設計・法遵守…と、フルスタックエンジニアの腕が試される要素が詰まっています。
今回ご紹介した Pinterest動画ダウンローダー は、そうした技術的課題を「誰でも使える形」に落とし込んだ一つの答えです。もちろん完璧ではなく、Pinterestの仕様変更には随時対応が必要ですが、オープンな技術スタックで構築しているため、コミュニティからのフィードバックも歓迎しています。
📌 最後に一言
技術の可能性を追求する一方で、クリエイターへのリスペクトを忘れずに。
「インスピレーションを得る」と「権利を守る」のバランスを取ることこそ、プロフェッショナルの腕の見せ所だと信じています。
何か質問や技術的な議論があれば、コメント欄で気軽にどうぞ。
それでは、よいピン留めライフを!📌✨
参考リンク
