Contents
- 1 Hatena Bookmark API の全体像と主要エンドポイント
- 2 エントリー取得 (GET /entries)
- 3 ブックマーク追加 (POST /entries)
- 4 ブックマーク削除 (DELETE /entries)
- 5 エントリー情報取得 (GET /entry)
- 6 複数 URL 件数取得 (GET /counts)
- 7 OAuth 認証フローと必須設定
- 8 実践活用事例①:Python+requests_oauthlib でブックマークを自動追加
- 9 実践活用事例②:複数 URL のブックマーク件数取得とダッシュボード化
- 10 実践活用事例③:トレンド分析・コンテンツ推薦システムの基本設計
- 11 API 利用時の制限・ベストプラクティス & 今すぐ実装へ
- 12 安全に利用するための最終チェックリスト
Hatena Bookmark API の全体像と主要エンドポイント
はてなブックマークのデータを外部サービスで活用するには、まず REST API の構造と認証方式を正しく理解しておくことが不可欠です。公式ドキュメント(Hatena Developer Center)は随時更新されるため、利用前に必ず最新情報を確認してください。本節では、主要エンドポイントの概要・必須パラメータ・代表的なレスポンス例をまとめ、実装上の注意点も併せて解説します。
エンドポイント一覧
以下に示すエンドポイントは 2026 年時点で公式に公開されているものですが、バージョンアップや仕様変更が行われる可能性があります。必ず公式リファレンスで最新のパラメータ名・レスポンス構造を確認してください。
| エンドポイント | 用途 |
|---|---|
GET /entries |
認証ユーザーがブックマークしているエントリー一覧取得 |
POST /entries |
指定 URL の新規ブックマーク登録 |
DELETE /entries |
自分のブックマーク削除 |
GET /entry |
任意 URL の件数・タグ情報のみ取得 |
GET /counts |
複数 URL のブックマーク件数を一括取得 |
エントリー取得 (GET /entries)
認証ユーザーが自分のブックマークに登録しているエントリーを一覧で取得します。取得対象は URL エンコード済み の url パラメータ 1 件だけです。
リクエスト例
|
1 2 3 4 |
GET https://bookmark.hatena.ne.jp/api/v1/entries?url=https%3A%2F%2Fexample.com Authorization: OAuth oauth_consumer_key="...", oauth_token="..." User-Agent: MyApp/1.0 (dev@example.com) |
主なパラメータ
| パラメータ | 必須 | 説明 |
|---|---|---|
url |
✅ | 取得対象ページの URL(必ず percent‑encoding) |
注意: 公式では
urlのみ受け付け、他のクエリは無視されます。
サンプルレスポンス
|
1 2 3 4 5 6 7 8 9 |
{ "entry": { "url": "https://example.com", "count": 1234, "tags": ["tech", "api"], "timestamp": "2026-05-28T14:22:00+09:00" } } |
ブックマーク追加 (POST /entries)
認証ユーザーが指定した URL をブックマークに登録します。POST ボディは application/x-www-form-urlencoded 形式で送信し、必ず URL エンコードしてください。
リクエスト例
|
1 2 3 4 5 6 7 |
POST https://bookmark.hatena.ne.jp/api/v1/entries Content-Type: application/x-www-form-urlencoded Authorization: OAuth … User-Agent: MyApp/1.0 (dev@example.com) url=https%3A%2F%2Fexample.com&comment=%E9%9D%A2%E5%88%87%E3%81%84%E8%A8%98%E4%BA%8B |
パラメータ
| パラメータ | 必須 | 説明 |
|---|---|---|
url |
✅ | ブックマーク対象 URL(必ずエンコード) |
comment |
❌ | 任意のコメント文字列(UTF‑8, percent‑encoding 推奨) |
成功時レスポンス
- HTTP ステータス 201 Created が返り、ボディは空です。
- 既に同一 URL が登録済みの場合は 409 Conflict が返ります。
ブックマーク削除 (DELETE /entries)
自分が登録したブックマークを解除します。url パラメータはエンコード必須です。
リクエスト例
|
1 2 3 4 |
DELETE https://bookmark.hatena.ne.jp/api/v1/entries?url=https%3A%2F%2Fexample.com Authorization: OAuth … User-Agent: MyApp/1.0 (dev@example.com) |
成功時レスポンス
- 204 No Content が返り、ボディはありません。
- 削除対象が存在しない場合は 404 Not Found となります。
エントリー情報取得 (GET /entry)
任意の URL に対するブックマーク件数・タグ・ユーザー一覧だけを取得したいときに使用します。エンドポイント名は /api/v1/entry(※複数形ではなく単数)です。
リクエスト例
|
1 2 3 |
GET https://bookmark.hatena.ne.jp/api/v1/entry?url=https%3A%2F%2Fexample.com User-Agent: MyApp/1.0 (dev@example.com) |
サンプルレスポンス
|
1 2 3 4 5 6 7 8 9 |
{ "count": 5678, "tags": ["news", "technology"], "users": [ {"name":"alice","timestamp":"2026-05-27T09:15:00+09:00"}, {"name":"bob","timestamp":"2026-05-26T20:30:00+09:00"} ] } |
複数 URL 件数取得 (GET /counts)
複数ページのブックマーク件数を一括で取得できるエンドポイントです。URL のカンマ区切りと個別エンコードが公式例で推奨されています。全体を 1 回だけ quote すると正しく解釈されないため、各 URL を独立に urllib.parse.quote_plus(または同等の関数)でエンコードし、その後カンマで結合してください。
リクエスト例(公式フォーマットに合わせた実装)
|
1 2 3 |
GET https://bookmark.hatena.ne.jp/api/v1/counts?url=https%3A%2F%2Fexample.com,https%3A%2F%2Ffoo.bar%2Fbaz User-Agent: MyApp/1.0 (dev@example.com) |
検証ポイント
公式ドキュメントのサンプルと照らし合わせ、上記クエリ文字列が正しく動作するか必ずテストしてください。万一結果が期待通りでない場合は、url=の後ろ全体を再度encodeURIComponent(JavaScript)やquote(Python)で二重エンコードし直すことがあります。
サンプルレスポンス
|
1 2 3 4 5 6 7 |
{ "counts": { "https://example.com": 1234, "https://foo.bar/baz": 56 } } |
OAuth 認証フローと必須設定
はてなブックマーク API は OAuth 1.0a によるユーザー認証が必須です。正しいトークン取得手順と User-Agent のフォーマットを守らないと、公式側で自動的にアクセス制限(403/429)が適用されます。
OAuth 1.0a 取得手順
- アプリ登録 – Hatena Developer Center にて「Consumer Key」と「Consumer Secret」を発行。
- リクエストトークン取得 –
POST https://www.hatena.com/oauth/initiateにキーを付与し、oauth_callback=oobを指定。 - ユーザー認証 – 取得した
oauth_tokenを使いhttps://www.hatena.ne.jp/oauth/authorize?oauth_token=...にリダイレクトさせ許可を得る。 - アクセストークン取得 – ユーザーが許可したら返ってくる
oauth_verifierを用いてPOST https://www.hatena.com/oauth/tokenへ再度リクエストし、最終的な Access Token / Secret を入手。
Python の
requests_oauthlibは上記フローを自動でハンドリングできるため、実装はシンプルです。
User-Agent ヘッダーの必須フォーマット
公式ドキュメントが示す例:
|
1 2 3 |
User-Agent: <アプリ名>/<バージョン> (<問い合わせメールアドレス>) 例: MyBookmarkTool/1.2 (dev@example.com) |
- ASCII のみで構成し、改行や余計な空白は入れないこと。
- この情報が無いと 403 エラーになるケースがあります。
実践活用事例①:Python+requests_oauthlib でブックマークを自動追加
Qiita に掲載されたサンプルコード(2025 年版)に、2026 年時点のベストプラクティスを加味した完全版です。認証・エラーハンドリング・重複チェックまで網羅しています。
スクリプト全体像
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
import os import urllib.parse from requests_oauthlib import OAuth1Session # ------------------------------------------------------------------ # 環境変数から機密情報を取得(コードに平文で書かない) # ------------------------------------------------------------------ CONSUMER_KEY = os.getenv('HATENA_CONSUMER_KEY') CONSUMER_SECRET = os.getenv('HATENA_CONSUMER_SECRET') ACCESS_TOKEN = os.getenv('HATENA_ACCESS_TOKEN') ACCESS_SECRET = os.getenv('HATENA_ACCESS_SECRET') API_BASE = 'https://bookmark.hatena.ne.jp/api/v1' def add_bookmark(target_url: str, comment: str = '') -> bool: """指定 URL をはてなブックマークに追加する。成功すれば True、失敗は False.""" # -------------------------------------------------------------- # 1. URL は必ず percent‑encoding(quote_plus)する # -------------------------------------------------------------- encoded_url = urllib.parse.quote_plus(target_url) payload = { 'url': encoded_url, 'comment': comment } # -------------------------------------------------------------- # 2. OAuth1Session が署名・ヘッダー生成を自動化 # -------------------------------------------------------------- oauth = OAuth1Session( client_key=CONSUMER_KEY, client_secret=CONSUMER_SECRET, resource_owner_key=ACCESS_TOKEN, resource_owner_secret=ACCESS_SECRET, ) headers = { 'User-Agent': 'MyBookmarkTool/1.0 (dev@example.com)' } # -------------------------------------------------------------- # 3. POST 実行とステータスコード別ハンドリング # -------------------------------------------------------------- response = oauth.post(f'{API_BASE}/entries', data=payload, headers=headers) if response.status_code == 201: print(f'✅ {target_url} をブックマークしました') return True elif response.status_code == 409: print(f'⚠️ {target_url} はすでに登録済みです') return False else: print(f'❌ エラー ({response.status_code}) : {response.text}') return False # ------------------------------------------------------------------ # スクリプト単体でも動作するエントリーポイント # ------------------------------------------------------------------ if __name__ == '__main__': url = 'https://example.com/awesome-article' add_bookmark(url, comment='技術的に興味深い') |
主なポイント解説
| 行 | 内容 |
|---|---|
| 1‑4 | 環境変数から機密情報を取得し、コード上の平文漏洩リスクを回避。 |
| 9‑10 | quote_plus による URL エンコーディングは必須(公式が要求)。 |
| 14‑20 | OAuth1Session が OAuth 1.0a の署名・ヘッダー生成を自動化。 |
| 23‑26 | 正しい User-Agent を設定しないと 403 が返ることがある。 |
| 28‑36 | ステータスコード別に成功、重複、その他エラーを分岐処理。 |
実装時の追加注意点
- 二重エンコード防止:
requestsは自動でエンコードしないため、必ずquote_plusした文字列をそのまま送信してください。 - リトライロジック:ネットワーク障害時は
requests.exceptions.RequestExceptionを捕捉し、指数バックオフで再試行すると安全です(次章参照)。
実践活用事例②:複数 URL のブックマーク件数取得とダッシュボード化
大量コンテンツの人気度を把握したい場合は /counts エンドポイントで一括取得し、可視化するのが最も効率的です。以下では Python でデータ取得 → pandas DataFrame 整形 → Plotly によるインタラクティブダッシュボード作成までを示します。
エンドポイント呼び出し手順
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
import urllib.parse import requests import pandas as pd API_BASE = 'https://bookmark.hatena.ne.jp/api/v1/counts' HEADERS = {'User-Agent': 'MyDashboard/2.0 (dev@example.com)'} def fetch_counts(url_list): """URL リストからブックマーク件数を取得し DataFrame を返す""" # -------------------------------------------------------------- # 各 URL を個別にエンコードし、カンマで結合 # -------------------------------------------------------------- encoded = ','.join([urllib.parse.quote_plus(u) for u in url_list]) resp = requests.get(f'{API_BASE}?url={encoded}', headers=HEADERS, timeout=10) if resp.status_code != 200: raise RuntimeError(f'API error {resp.status_code}: {resp.text}') data = resp.json()['counts'] df = pd.DataFrame(list(data.items()), columns=['URL', 'BookmarkCount']) return df # ------------------------------------------------------------------ # 使用例 # ------------------------------------------------------------------ urls = [ 'https://example.com/article1', 'https://news.site.jp/top', 'https://techblog.io/post/42' ] df_counts = fetch_counts(urls) print(df_counts) |
実装上のポイント
- エンコード方式:公式例と同様に「各 URL を
quote_plusしてカンマで連結」すれば、サーバ側で正しくパースされます。 - タイムアウト設定:ネットワーク遅延時のハングアップ防止として
timeout=10秒を推奨します。 - 例外処理:ステータスコードが 200 以外の場合は即座に例外化し、呼び出し側でリトライやログ記録ができるようにします。
データ集約と可視化
取得した DataFrame を Plotly Express で棒グラフ化し、Jupyter Notebook や Streamlit アプリへ埋め込む例です。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import plotly.express as px def plot_dashboard(df): """DataFrame → インタラクティブ棒グラフ""" fig = px.bar( df, x='URL', y='BookmarkCount', title='はてなブックマーク件数ダッシュボード', labels={'BookmarkCount': 'ブックマークリスト'}, hover_data=['URL'] ) fig.update_layout(xaxis_tickangle=-45) fig.show() plot_dashboard(df_counts) |
実運用向けベストプラクティス
| 項目 | 推奨策 |
|---|---|
| キャッシュ | ETag / If-None-Match を利用し、304 Not Modified 時はローカルデータを再利用。 |
| レートリミット回避 | 公式が示す「1 分間に 60 リクエスト」以下になるよう、URL バッチサイズは 20 前後に分割し time.sleep(1) を挟む。 |
| エラーハンドリング | 429 Too Many Requests が返ったら Retry-After ヘッダーの秒数だけ待機し再送。指数バックオフでリトライ回数は最大 3 回まで。 |
実践活用事例③:トレンド分析・コンテンツ推薦システムの基本設計
はてなブックマークデータは、ユーザー関心や業界トレンドをリアルタイムで把握する上で有効です。ここでは 取得 → 前処理 → 集計 → 推薦 のフローを概観し、最小構成のサンプルコードと API 設計例を示します。
データ取得フロー
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import requests, urllib.parse, pandas as pd API_ENTRY = 'https://bookmark.hatena.ne.jp/api/v1/entry' HEADERS = {'User-Agent': 'TrendAnalyzer/3.0 (dev@example.com)'} def get_entry_info(url): """単一 URL のエントリー情報を取得し JSON を返す""" encoded = urllib.parse.quote_plus(url) resp = requests.get(f'{API_ENTRY}?url={encoded}', headers=HEADERS, timeout=5) resp.raise_for_status() return resp.json() # 複数 URL の一括取得例 urls = ['https://example.com/a', 'https://news.site/b'] records = [get_entry_info(u) for u in urls] df = pd.json_normalize(records) |
ポイント:
json_normalizeを使うとネストされたusersやtagsがフラットなテーブルになるため、集計が楽になります。
分析・推薦ロジック(概要)
- トレンド集計
timestampを日付単位でグルーピングし、増減率上位 10 件を「今週の注目記事」として抽出。- タグ頻度分析
tags配列から語彙リスト化し、collections.Counterで出現回数を算出。上位キーワードはカテゴリ分類に利用。- シンプル協調フィルタリング
- 同一ユーザーがブックマークした記事同士の共起行列を作り、コサイン類似度上位の記事を推薦対象とする。
以下は Flask + SQLite で「おすすめエンドポイント」を実装した最小例です。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# app.py from flask import Flask, jsonify from sqlalchemy import create_engine, Table, Column, Integer, String, MetaData, select app = Flask(__name__) engine = create_engine('sqlite:///bookmark_trends.db') metadata = MetaData() articles = Table( 'articles', metadata, Column('id', Integer, primary_key=True), Column('url', String, unique=True), Column('count', Integer), Column('tags', String) # カンマ区切りで保存(簡易実装) ) metadata.create_all(engine) def load_top_n(n=5): """ブックマーク件数上位 n 件を取得""" with engine.connect() as conn: stmt = select([articles.c.url, articles.c.count])\ .order_by(articles.c.count.desc())\ .limit(n) return [row._asdict() for row in conn.execute(stmt)] @app.route('/api/recommend', methods=['GET']) def recommend(): top = load_top_n() return jsonify({'recommendations': top}) if __name__ == '__main__': app.run(debug=True) |
実装上の留意点
- データ更新頻度:はてなブックマークはリアルタイムに近いので、バッチ処理は 1 時間に 1 回程度で十分。
- タグ保存方式:検索性を高めたい場合は JSONB(PostgreSQL)や別テーブル正規化を検討してください。
- スケーラビリティ:初期は SQLite でも動作しますが、アクセス増加時は PostgreSQL + インデックス付与へ移行するのが自然です。
API 利用時の制限・ベストプラクティス & 今すぐ実装へ
はてなブックマーク API は便利ですが、レートリミット と 利用規約 を遵守しないとアクセス遮断やアカウント停止のリスクがあります。以下では安全に運用するための具体策をまとめました。
連続アクセス制限とリトライ戦略
| 制限項目 | 公式数値(2026 年) | 推奨回避策 |
|---|---|---|
| 1 分間あたりのリクエスト上限 | 60 回 | バッチサイズ ≤ 20、time.sleep(0.5~1) を挟む |
| 短時間連続エラー時 | 429 / 503 が返ることがある | Retry-After ヘッダーの秒数待機、指数バックオフ (2, 4, 8 秒) を実装 |
Python で安全に GET するサンプル
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import time, requests def safe_get(url, headers): while True: resp = requests.get(url, headers=headers) if resp.status_code == 429: wait = int(resp.headers.get('Retry-After', '60')) print(f'Rate limit hit. Waiting {wait}s...') time.sleep(wait) continue resp.raise_for_status() return resp |
レスポンスキャッシュ活用
- ETag / If-None-Match
-
GET 系エンドポイントは
ETagを返すことが多いので、取得時に保存し次回はIf-None-Matchヘッダーで送信。304 が返ればデータ転送量を削減できます。 -
ローカルキャッシュ実装例(Python)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import json, os, requests CACHE_FILE = 'hatena_cache.json' def cached_get(url): cache = {} if os.path.exists(CACHE_FILE): with open(CACHE_FILE) as f: cache = json.load(f) etag = cache.get(url, {}).get('etag') hdr = {'User-Agent': 'CacheClient/1.0 (dev@example.com)'} if etag: hdr['If-None-Match'] = etag resp = requests.get(url, headers=hdr) if resp.status_code == 304: return cache[url]['data'] resp.raise_for_status() data = resp.json() cache[url] = {'etag': resp.headers.get('ETag'), 'data': data} with open(CACHE_FILE, 'w') as f: json.dump(cache, f) return data |
エラーハンドリングの基本表
| HTTP ステータス | 主な原因 | 推奨対応 |
|---|---|---|
| 400 Bad Request | パラメータ不正、URL 未エンコード | 入力バリデーションを徹底 |
| 401 Unauthorized | OAuth トークン期限切れ・無効 | 再認証フローへ誘導 |
| 403 Forbidden | User-Agent 不正、利用規約違反 | 正しいフォーマットで再送 |
| 404 Not Found | 対象エントリ未登録 | 必要なら事前に /entries で確認 |
| 429 Too Many Requests | レート超過 | Retry-After 待機後リトライ |
| 5xx 系エラー | サーバ障害・一時的な不具合 | 最大 3 回まで指数バックオフで再試行し、継続失敗はアラート送信 |
安全に利用するための最終チェックリスト
- 公式ドキュメントを毎回確認
- エンドポイント URL・パラメータ仕様・レートリミットは随時変更される可能性があります。実装前後に必ず最新情報を参照してください。
User-Agentを正しい形式で送信- アプリ名、バージョン、問い合わせメールアドレスの 3 要素が必須です。ASCII のみで記述し、余計な改行は入れないようにします。
- URL エンコード方法を統一
/countsのクエリは「各 URL を個別にquote_plus→ カンマ結合」するのが公式例と一致します。二重エンコードや全体エンコードは避けましょう。- レートリミットを守る
- 1 分間 60 回以下、バッチサイズは 20 前後に抑える。超過したら
Retry-Afterに従い待機します。 - キャッシュと ETag の活用
- 同一リクエストの頻度が高い場合はローカルキャッシュで 304 を利用し、帯域とレートリミットを節約します。
- 例外・エラー処理を徹底
- ステータスコードごとのハンドリングと指数バックオフの実装は必須です。
以上の手順とベストプラクティスに沿って実装すれば、はてなブックマーク API を安定・安全に活用できるようになります。ぜひ本稿で紹介したコードを自社サービスや個人ツールに組み込み、ユーザー体験の向上やデータドリブンな意思決定に役立ててください。