reddit-api について
reddit-apiは、アプリにRedditのデータを取り込む開発者向けのリファレンススキルです。Python、TypeScript/Node.js、HTTPによる認証、データ取得、投稿方法を扱います。
主な機能
- 01OAuth2によるAPI認証
- 02投稿、コメント、ユーザーデータの取得
- 03subredditコンテンツの検索
- 04新しい投稿やコメントのストリーミング
- 05投稿の送信と投票
活用シーン
- 01アプリでsubredditの人気投稿を表示
- 02新しい投稿やコメントをリアルタイム処理
- 03Redditのユーザー認証フローを構築
- 04スクリプトから投稿や投票を実行
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ alinaqi/ maggy --skill reddit-apiインストール後、Claude Code で/reddit-apiを入力して読み込まれたか確認します。
詳細
Reddit API スキル
アプリケーションに Reddit のデータを統合するための機能です。投稿、コメント、サブレディット、ユーザーデータを取得できます。
参照元: Reddit API ドキュメント | OAuth2 Wiki | PRAW ドキュメント
セットアップ
1. Reddit アプリを作成する
- https://www.reddit.com/prefs/apps にアクセスします
- 「Create App」または「Create Another App」をクリックします
- 次の項目を入力します。
- 名前:アプリ名
- アプリの種類:
script- 個人利用または自分で管理するボット向けweb app- ユーザー認証を行うサーバーサイドアプリ向けinstalled app- モバイルアプリ/デスクトップアプリ向け
- リダイレクト URI:
http://localhost:8000/callback(開発用)
client_id(アプリ名の下に表示)とclient_secretを控えます
2. 環境変数
# .env
REDDIT_CLIENT_ID=your_client_id
REDDIT_CLIENT_SECRET=your_client_secret
REDDIT_USER_AGENT=YourApp/1.0 by YourUsername
REDDIT_USERNAME=your_username # For script apps only
REDDIT_PASSWORD=your_password # For script apps only
User-Agent の形式:<platform>:<app_id>:<version> (by /u/<username>)
レート制限
| ティア | 制限 | 備考 |
|---|---|---|
| OAuth 認証済み | 100 QPM | OAuth クライアント ID ごと |
| 未認証 | ブロック | OAuth の使用が必須 |
- 制限は10分間のウィンドウで平均化されます
- ブロックを避けるため、
User-Agentヘッダーを含めてください X-Ratelimit-*レスポンスヘッダーを確認してください
Python:PRAW(推奨)
インストール
pip install praw
# or
uv add praw
スクリプトアプリ(個人利用/ボット)
import praw
from pydantic_settings import BaseSettings
class RedditSettings(BaseSettings):
reddit_client_id: str
reddit_client_secret: str
reddit_user_agent: str
reddit_username: str
reddit_password: str
class Config:
env_file = ".env"
settings = RedditSettings()
reddit = praw.Reddit(
client_id=settings.reddit_client_id,
client_secret=settings.reddit_client_secret,
user_agent=settings.reddit_user_agent,
username=settings.reddit_username,
password=settings.reddit_password,
)
# Verify authentication
print(f"Logged in as: {reddit.user.me()}")
読み取り専用(ユーザー認証なし)
import praw
reddit = praw.Reddit(
client_id="your_client_id",
client_secret="your_client_secret",
user_agent="YourApp/1.0 by YourUsername",
)
# Read-only mode - can browse, can't post/vote
reddit.read_only = True
一般的な操作
# Get subreddit posts
subreddit = reddit.subreddit("python")
# Hot posts
for post in subreddit.hot(limit=10):
print(f"{post.title} - {post.score} upvotes")
# New posts
for post in subreddit.new(limit=10):
print(post.title)
# Search posts
for post in subreddit.search("pydantic", limit=5):
print(post.title)
# Get specific post
submission = reddit.submission(id="abc123")
print(submission.title)
print(submission.selftext)
# Get comments
submission.comments.replace_more(limit=0) # Flatten comment tree
for comment in submission.comments.list():
print(f"{comment.author}: {comment.body[:100]}")
投稿と投票(認証が必要)
# Submit text post
subreddit = reddit.subreddit("test")
submission = subreddit.submit(
title="Test Post",
selftext="This is the body of my post."
)
# Submit link post
submission = subreddit.submit(
title="Check this out",
url="https://example.com"
)
# Vote
submission.upvote()
submission.downvote()
submission.clear_vote()
# Comment
submission.reply("Great post!")
# Reply to comment
comment = reddit.comment(id="xyz789")
comment.reply("I agree!")
ストリーミング(リアルタイム)
# Stream new posts
for post in reddit.subreddit("python").stream.submissions():
print(f"New post: {post.title}")
# Process post...
# Stream new comments
for comment in reddit.subreddit("python").stream.comments():
print(f"New comment by {comment.author}: {comment.body[:50]}")
ユーザーデータ
# Get user info
user = reddit.redditor("spez")
print(f"Karma: {user.link_karma + user.comment_karma}")
# User's posts
for post in user.submissions.new(limit=5):
print(post.title)
# User's comments
for comment in user.comments.new(limit=5):
print(comment.body[:100])
TypeScript/Node.js:Snoowrap
インストール
npm install snoowrap
# or
pnpm add snoowrap
セットアップ
import Snoowrap from "snoowrap";
const reddit = new Snoowrap({
userAgent: "YourApp/1.0 by YourUsername",
clientId: process.env.REDDIT_CLIENT_ID!,
clientSecret: process.env.REDDIT_CLIENT_SECRET!,
username: process.env.REDDIT_USERNAME!,
password: process.env.REDDIT_PASSWORD!,
});
// Configure rate limiting
reddit.config({
requestDelay: 1000, // 1 second between requests
continueAfterRatelimitError: true,
});
一般的な操作
// Get hot posts from subreddit
const posts = await reddit.getSubreddit("typescript").getHot({ limit: 10 });
posts.forEach((post) => {
console.log(`${post.title} - ${post.score} upvotes`);
});
// Search posts
const results = await reddit.getSubreddit("programming").search({
query: "typescript",
sort: "relevance",
time: "month",
limit: 10,
});
// Get specific post
const submission = await reddit.getSubmission("abc123").fetch();
console.log(submission.title);
// Get comments
const comments = await submission.comments.fetchAll();
comments.forEach((comment) => {
console.log(`${comment.author.name}: ${comment.body.slice(0, 100)}`);
});
投稿
// Submit text post
const post = await reddit.getSubreddit("test").submitSelfpost({
title: "Test Post",
text: "This is the body.",
});
// Submit link
const linkPost = await reddit.getSubreddit("test").submitLink({
title: "Check this out",
url: "https://example.com",
});
// Vote and comment
await post.upvote();
await post.reply("Great post!");
直接 API を利用する(ライブラリなし)
httpx を使用した Python
import httpx
import base64
from pydantic import BaseModel
class RedditClient:
def __init__(self, client_id: str, client_secret: str, user_agent: str):
self.client_id = client_id
self.client_secret = client_secret
self.user_agent = user_agent
self.access_token: str | None = None
self.client = httpx.AsyncClient()
async def authenticate(self) -> None:
"""Get application-only OAuth token."""
auth = base64.b64encode(
f"{self.client_id}:{self.client_secret}".encode()
).decode()
response = await self.client.post(
"https://www.reddit.com/api/v1/access_token",
headers={
"Authorization": f"Basic {auth}",
"User-Agent": self.user_agent,
},
data={
"grant_type": "client_credentials",
},
)
response.raise_for_status()
self.access_token = response.json()["access_token"]
async def get_posts(self, subreddit: str, sort: str = "hot", limit: int = 10) -> list[dict]:
"""Get posts from a subreddit."""
if not self.access_token:
await self.authenticate()
response = await self.client.get(
f"https://oauth.reddit.com/r/{subreddit}/{sort}",
headers={
"Authorization": f"Bearer {self.access_token}",
"User-Agent": self.user_agent,
},
params={"limit": limit},
)
response.raise_for_status()
return [post["data"] for post in response.json()["data"]["children"]]
async def close(self) -> None:
await self.client.aclose()
# Usage
async def main():
client = RedditClient(
client_id="your_id",
client_secret="your_secret",
user_agent="YourApp/1.0",
)
try:
posts = await client.get_posts("python", limit=5)
for post in posts:
print(f"{post['title']} - {post['score']} upvotes")
finally:
await client.close()
fetch を使用した TypeScript
interface RedditPost {
title: string;
score: number;
url: string;
selftext: string;
author: string;
created_utc: number;
}
class RedditClient {
private accessToken: string | null = null;
constructor(
private clientId: string,
private clientSecret: string,
private userAgent: string
) {}
async authenticate(): Promise<void> {
const auth = Buffer.from(`${this.clientId}:${this.clientSecret}`).toString("base64");
const response = await fetch("https://www.reddit.com/api/v1/access_token", {
method: "POST",
headers: {
Authorization: `Basic ${auth}`,
"User-Agent": this.userAgent,
"Content-Type": "application/x-www-form-urlencoded",
},
body: "grant_type=client_credentials",
});
const data = await response.json();
this.accessToken = data.access_token;
}
async getPosts(subreddit: string, sort = "hot", limit = 10): Promise<RedditPost[]> {
if (!this.accessToken) await this.authenticate();
const response = await fetch(
`https://oauth.reddit.com/r/${subreddit}/${sort}?limit=${limit}`,
{
headers: {
Authorization: `Bearer ${this.accessToken}`,
"User-Agent": this.userAgent,
},
}
);
const data = await response.json();
return data.data.children.map((child: any) => child.data);
}
}
OAuth2 Web フロー(ユーザー認証)
ユーザーが Reddit アカウントでログインするアプリ向けです。
from fastapi import FastAPI, Request
from fastapi.responses import RedirectResponse
import httpx
import secrets
app = FastAPI()
state_store: dict[str, bool] = {}
REDDIT_CLIENT_ID = "your_client_id"
REDDIT_CLIENT_SECRET = "your_client_secret"
REDIRECT_URI = "http://localhost:8000/callback"
@app.get("/login")
async def login():
state = secrets.token_urlsafe(16)
state_store[state] = True
auth_url = (
f"https://www.reddit.com/api/v1/authorize"
f"?client_id={REDDIT_CLIENT_ID}"
f"&response_type=code"
f"&state={state}"
f"&redirect_uri={REDIRECT_URI}"
f"&duration=permanent"
f"&scope=identity read submit vote"
)
return RedirectResponse(auth_url)
@app.get("/callback")
async def callback(code: str, state: str):
if state not in state_store:
return {"error": "Invalid state"}
del state_store[state]
# Exchange code for token
async with httpx.AsyncClient() as client:
response = await client.post(
"https://www.reddit.com/api/v1/access_token",
auth=(REDDIT_CLIENT_ID, REDDIT_CLIENT_SECRET),
data={
"grant_type": "authorization_code",
"code": code,
"redirect_uri": REDIRECT_URI,
},
headers={"User-Agent": "YourApp/1.0"},
)
tokens = response.json()
# Store tokens securely, associate with user session
return {"access_token": tokens["access_token"][:10] + "..."}
利用可能なスコープ
| スコープ | 説明 |
|---|---|
identity | ユーザー名と登録日へのアクセス |
read | 投稿とコメントへのアクセス |
submit | リンクとコメントの投稿 |
vote | コンテンツへの賛成票/反対票 |
edit | 投稿とコメントの編集 |
history | 投票履歴へのアクセス |
subscribe | サブレディットの購読管理 |
mysubreddits | 購読中のサブレディットへのアクセス |
privatemessages | プライベートメッセージへのアクセス |
save | コンテンツの保存/保存解除 |
全一覧:https://www.reddit.com/api/v1/scopes
プロジェクト構成
project/
├── src/
│ ├── reddit/
│ │ ├── __init__.py
│ │ ├── client.py # Reddit client wrapper
│ │ ├── models.py # Pydantic models for posts/comments
│ │ └── scraper.py # Data collection logic
│ └── main.py
├── .env
└── pyproject.toml
Pydantic モデル
from pydantic import BaseModel
from datetime import datetime
class RedditPost(BaseModel):
id: str
title: str
author: str
subreddit: str
score: int
upvote_ratio: float
url: str
selftext: str
created_utc: datetime
num_comments: int
is_self: bool
@classmethod
def from_praw(cls, submission) -> "RedditPost":
return cls(
id=submission.id,
title=submission.title,
author=str(submission.author),
subreddit=submission.subreddit.display_name,
score=submission.score,
upvote_ratio=submission.upvote_ratio,
url=submission.url,
selftext=submission.selftext,
created_utc=datetime.fromtimestamp(submission.created_utc),
num_comments=submission.num_comments,
is_self=submission.is_self,
)
class RedditComment(BaseModel):
id: str
author: str
body: str
score: int
created_utc: datetime
parent_id: str
is_submitter: bool
アンチパターン
- User-Agent を指定しない - 適切な User-Agent がないリクエストは Reddit にブロックされます
- レート制限を無視する - 100 QPM を守り、
X-Ratelimit-*ヘッダーを確認してください - 認証情報をコードに保存する - 環境変数を使用してください
MoreCommentsを処理しない - PRAW でreplace_more()を使用してください- ストリーミングではなくポーリングする - リアルタイムデータには
.streamを使用してください - エラー処理を行わない - 429(レート制限)、403(禁止)、404(見つからない)を処理してください
クイックリファレンス
# PRAW installation
pip install praw
# Snoowrap installation
npm install snoowrap
# Test authentication
python -c "import praw; r = praw.Reddit(...); print(r.user.me())"
エンドポイント
| 操作 | エンドポイント |
|---|---|
| 認証トークン | POST https://www.reddit.com/api/v1/access_token |
| API リクエスト | https://oauth.reddit.com/... |
| サブレディットの投稿 | GET /r/{subreddit}/{sort} |
| 投稿 | GET /comments/{id} |
| ユーザー情報 | GET /user/{username}/about |
| 投稿を送信 | POST /api/submit |
| 投票 | POST /api/vote |
コメント
Markdown 対応