はてなブックマークのREST APIを利用する

Table of Content

目的

はてなブックマークをREST API経由で登録してみます。
環境は以下の通りです。
・windows 10
・python 3.7.4

はてなブックマークのREST APIについては下記を参照してください。
http://developer.hatena.ne.jp/ja/documents/bookmark/apis/rest#authorization

事前準備

pythonを使用してはてなブックマークをREST API経由で登録するにはOAuth認証をする必要があります。
ここではOAuth認証を行うための事前準備について説明します。

Consumer Keyの取得

(1)Consumer Keyを取得するため、OAuth 開発者向け設定ページにアクセスします。この際、はてなのユーザーIDに紐づくパスワードの入力が要求されます。

(2)「新しいアプリケーション」の作成ボタンを押します。

(3)下記を参考に必要な項目を変更し、Consumer KeyとConsumer Secretをメモしておきます。

・アプリケーションの名称、説明、URLを変更します。スクリプトで使用するのでURLは127.0.0.1になっています。

・Consumer KeyとConsumer Secretをメモします。この値は外部に漏れないようにしましょう。

・必要な権限を割り当てます。はてなブックマークを登録するだけなら「write_public」が必要になります。

・「変更する」ボタンを押下します。
・「提供アプリケーション一覧に戻る」をクリックすると、新しいアプリケーションが追加されています。
※どうも削除する方法はないようで、使わなくなったら「無効化」するようです。

requests-oauthlibのインストール

OAuth認証を自前で実装するのは辛いのでrequests-oauthlibをインストールします。
https://github.com/requests/requests-oauthlib

はてなブックマークの登録

はてなブックマークを登録するためのサンプルコード

https://github.com/mima3/qiita_exporter/blob/master/hatena_api.py

"""はてなREST APIを操作する.

はてなのRESTAPIについては下記を参照してください。
http://developer.hatena.com/ja/documents/bookmark/apis/rest

また、OAuthの認証方法については下記のページを参考にしています.

"""

import requests
# https://github.com/requests/requests-oauthlib
from requests_oauthlib import OAuth1Session

class HatenaApiError(Exception):
    """HatenaApiErrorのエラーが発生したことを知らせる例外クラス"""

class HatenaApi:
    """はてなAPIを操作するクラス."""
    LOGIN_URL = 'https://www.hatena.ne.jp/login'
    session = None

    def _get_rk(self, hatena_id, password):
        """はてなIDとログインパスワードからrkを取得します。
        以下のコードを利用しています
        
        https://github.com/iruca/hatena-oauth-python/blob/master/get_access_token_util.py

        Args:
            hatena_id:  はてなID文字列
            password: はてなIDに対応するはてなログイン用のパスワード
        Returns:
            rk文字列
        Raises:
            HatenaApiError: rk文字列が取得できなかったとき。ID/パスワードが間違っているか、rkを取得するためのはてなAPIの仕様が変わった
        """
        payload = {'name': hatena_id, 'password': password}
        response = requests.post(self.LOGIN_URL, data=payload)
        if not "Set-Cookie" in response.headers:
            raise HatenaApiError("cannot get rk.ID/Password is wrong, or Hatena API spec changed.")
        if not "rk=" in response.headers['Set-Cookie']:
            raise HatenaApiError("cannot get rk.ID/Password is wrong, or Hatena API spec changed.")
        rk = response.headers["Set-Cookie"].split("rk=")[1].split(";")[0]
        return rk

    def _get_authorization_redirect_url(self, user_id, password, authorization_url):
        """authorization_urlにアクセスしてアプリケーションの許可を行う."""
        rk = self._get_rk(user_id, password)
        res_auth = requests.get(authorization_url, headers={"Cookie" : "rk="+ rk}).text
        rkm = res_auth.split("<input type=\"hidden\" name=\"rkm\" value=\"")[1].split("\"")[0]
        oauth_token = res_auth.split("<input type=\"hidden\" name=\"oauth_token\" value=\"")[1].split("\"")[0]
        res_redirect = requests.post(
            authorization_url,
            headers={"Cookie": "rk="+ rk},
            params={
                "rkm": rkm,
                "oauth_token": oauth_token,
                "name": "%E8%A8%B1%E5%8F%AF%E3%81%99%E3%82%8B"
            }
        )
        return res_redirect.url

    def authorize(self, user_id, password, consumer_key, consumer_secret, scope):
        """OAuth認証を行う.

        Args:
            hatena_id:  はてなID文字列
            password: はてなIDに対応するはてなログイン用のパスワード
            consumer_key: 「OAuth 開発者向け設定ページ」にて作成したアプリケーションのconsumer_key
            consumer_secret:「OAuth 開発者向け設定ページ」にて作成したアプリケーションのconsumer_secret
            scope : 取得権限. ex "read_public,write_public"
        """
        self.session = OAuth1Session(
            consumer_key,
            consumer_secret,
            callback_uri="http://localhost/hogehoge"  # このURLは実際にアクセスできる必要はありません.
        )
        self.session.fetch_request_token(
            "https://www.hatena.com/oauth/initiate?scope={}".format(scope)
        )
        authorization_url = self.session.authorization_url("https://www.hatena.ne.jp/oauth/authorize")
        redirect_response = self._get_authorization_redirect_url(
            user_id,
            password,
            authorization_url
        )
        self.session.parse_authorization_response(redirect_response)
        self.session.fetch_access_token("https://www.hatena.com/oauth/token")

    def add_bookmark(self, url, comment="", tags=None):
        """はてなブックマークの追加または更新を行う.
        このメソッドを実行する前に、authorizeを呼び出す必要がある.

        Args:
            url:  ブックマーク対象のURL
            comment: コメント
            tags: タグの一覧
        """
        if not self.session:
            raise HatenaApiError("call authorize method.")
        if tags:
            for t in tags:
                comment += "[{}]".format(t)
        return self.session.post(
            "https://bookmark.hatenaapis.com/rest/1/my/bookmark?url=",
            params={
                "url": url,
                "comment" : comment,
            }
        )

上記のクラスは以下のように使用します。

    api = HatenaApi()
    api.authorize("はてなのユーザ名", "パスワード", "「OAuth 開発者向け設定ページ」にて作成したアプリケーションのconsumer_key", "「OAuth 開発者向け設定ページ」にて作成したアプリケーションのconsumer_secret", "write_public")
    res = api.add_bookmark("http://developer.hatena.com/ja/documents", "test!", [])
    res.raise_for_status()
    print(res.text)

解説

OAuth認証を行ったあとに,はてなブックマーク用のAPIを実行するだけですが、WebアプリケーションではなくスクリプトだけでOAuth認証を行うのは結構面倒くさいです。

理由はOAuth認証の途中で以下のような画面を表示してユーザが該当のアプリケーションを許可するプロセスが入るためです。

この問題は以下のページにて解決策が書かれています。

はてなのOAuth API用のアクセストークンを簡単に取得する [python]
https://www.iruca21.com/entry/2017/05/24/090000

簡単にいうと、ユーザーが画面で許可するボタンを押したのと同じ動きを行っています。先のコードでいうと_get_authorization_redirect_urlが、その動きに当たります。

また、python3xで動くようにするのと、できるだけ処理をrequests_oauthlibで行うように直してあります。

「はてなブックマークのREST APIを利用する」への1件の返信

さくっとPocketのブックマークをはてなブックマークに移行 - hayashier's Tech Blogs へ返信する コメントをキャンセル

メールアドレスが公開されることはありません。 が付いている欄は必須項目です