LoginSignup
1

posted at

updated at

pythonでMicrosoft Graphを操作する(OneNoteのページ取得)

Microsopf GraphはMSサービスのAPI

利用できるAPIを色々お試しできます。

利用するパターンをクリックするとgit上のサンプルコードが開かれるようになっています。
パターンによってOauth2.0でのトークン付与方法が異なります。
今回の場合はmacbookからpythonで個人アカウントのAPIを叩きます。
何のアプリも通さずに、pythonから直接APIを叩くのでBrowserless Appになります。

Screenshot_2022-09-05 14.42.29_2lKLoW.png

サンプルコードでinitiate_device_flow()を利用していたことからトークン付与の分類としてはdevicecode flowになるようです。
image.png

AzureのGUI操作

GUI操作は先人の知恵に頼るということで(丸投げ)
まずはこちらの動画の42秒から2分30秒あたりまでを参考にのGUI操作を行います。
それ以降はライブコーディングの動画なのでスキップしてもらってOKです。
MicrosoftのアカウントでAzureポータルにログインし、APIのアプリを作成し必要な設定をしていきます。

動画中の注意

  • 動画中ではMethod #1: CredentialCrilentApplicationという方法とMethod #2: PublicClientApplicationの二つのtoken付与方法が紹介されていますが、今回は#2の方法でtoken付与を行います。
  • GUI操作から情報を得られるもののうち、#2で必要なのはAPPLICATION_ID(CLIENT_IDと同義)とSCOPESの二つのみです。
    メモパッドなどに情報をコピペしてきましょう。
    CLIENT_SECRETは利用しません。
  • APIのアクセス許可(API premissions)の中で必要な権限を許可します。
    今回の場合だと、OneNoteの読み取りを行うNotes.Readなどです。

動画でカバーされていない操作

  • 動画にはありませんでしたが、認証の一番下にあるパブリッククライアントフローを許可するを"はい"にします。
    (これをしないとflow実行時にエラーが出ました。)
    Screenshot_2022-09-04 19.52.57_pV87Kj.png

python実行前の準備

  • pip install msal pyperclipで必要なパッケージのインストール
  • GUIからAPP_IDをコピペ
  • GUIで選択したSCOPESをList[str]型で書く
main.py
APP_ID = 'Your APP_ID'
SCOPES = ['User.Read', 'Notes.Read', 'Notes.ReadWrite', 'Notes.Create']

pythonの実行

上記のYouTubeの別動画Mediumの記事の合わせ技になっています。

main.py
import os
from pprint import pprint as pp
import requests
import webbrowser
import msal
import pyperclip

APP_ID = 'Your APP_ID'
SCOPES = ['User.Read', 'Notes.Read', 'Notes.ReadWrite', 'Notes.Create']


def generate_access_token(app_id=APP_ID, scopes=SCOPES):

    # read access_token_cache.json in current dir if exists
    access_token_cache = msal.SerializableTokenCache()
    if os.path.exists('access_token_cache.json'):
        access_token_cache.deserialize(
            open('access_token_cache.json', 'r').read())

    # init auth client
    client = msal.PublicClientApplication(
        client_id=app_id, token_cache=access_token_cache)

    # use access_token_cache.json
    accouts = client.get_accounts()
    if accouts:
        access_token = client.acquire_token_silent(scopes, accouts[0])
        pp('access_token_cache.json is used.')

    # generate access_token
    else:
        flow = client.initiate_device_flow(scopes)
        pyperclip.copy(flow['user_code'])
        print('User code is copied on your clipboard.\n')
        print(flow['user_code'])
        print('\n')

        # open vertification_uri
        # need to authenticate on GUI
        webbrowser.open(flow['verification_uri'])
        access_token = client.acquire_token_by_device_flow(flow)

        # write access_token_cache.json
        with open('access_token_cache.json', 'w') as _f:
            _f.write(access_token_cache.serialize())

    return access_token


def get_pages(token, pagination=True):
    url = 'https://graph.microsoft.com/v1.0/me//onenote/pages'
    headers = {'Authorization': 'Bearer ' + token['access_token']}
    graph_results = []

    # get data while nextLink exists
    while url:
        try:
            response = requests.get(url=url, headers=headers).json()
            graph_results.extend(response['value'])

            if pagination:
                url = response['@odata.nextLink']
            else:
                url = None

        except BaseException as e:
            pp(e)
            pp('Fail to get')
            break

    else:
        pass

    return graph_results


if __name__ == '__main__':
    access_token = generate_access_token()
    res = get_pages(access_token)
    print(len(res))


token発行

  • token取得のためのclientで、要求するスコープ(どのアプリで何をするか)を渡してflow認証を開始
example.py
client = msal.PublicClientApplication(client_id=app_id, token_cache=access_token_cache)
...
flow = client.initiate_device_flow(scopes)
  • 認証・認可の画面で最初に求められるuser_codeをクリップボードにコピーする
  • ブラウザを自動で起動し、認証・認可の画面を開く
  • tokenのcacheをaccess_token_cache.jsonとして保存
  • 次回以降は、tokenのcacheが有効であれば再利用する

OneNoteのページ情報取得

  • requests.getでAPIを叩く
  • 追加ページがなくなるまで繰り返しgetする

大量のデータがあるものについてはクエリに対して返すオブジェクトの数が限られています。
onenote/pagesでは20個が上限です。
データが続く場合はresponseに'@odata.nextlinkが含まれるのでそのリンクを使って続きからデータを取得します。
その挙動について参考になったのはこちら

やりたかったこと

私はOneNoteに勉強したことや知識のまとめ、ちょっとした日記を溜め込むようにしています。
細かく整理はせずにとにかくページを作っては書く、という使い方をしているため、
改めて眺めていると異なるセクションで同じ関連するページが存在することがわかりました。

例えば、、
NW, Cisco, Junos, Linux, pythonなどのセクションがあるのですが、

  • NWのセクションの中にはBGPの基礎知識のページ
  • Ciscoのセクションの中にはBGPのconfig例
  • Linuxのセクションの中にはbgpdumpのページ
  • pythonでBGPのshowコマンドを実行するページ

のように、各セクションにBGPに関するページが散乱しています。
各セクションに紐づいている状態は正しいのですが、
BGPという一つのテーマを横断的に見ていくのにはあまり良くない構造になっています。

このままだと、BGPの知識を引っ張り出したい時に
複数のセクションをGUIでチェックしていくことになり手間がかかります。。
そこで、BGP関連のページのリンク集を作る、あるいはマインドマップ的なものを作る、
ということができたらいいなと思ったのがGraphを触ることになったきっかけです。

はまったところ

そもそもAPIの認可ってなんだ

トークンが必要そうなのはわかるけど、なんでこんなに手間かかるねん、と思いながら試行錯誤していたので苦労しましたが、こちらのOAuthのわかりやすい解説記事でOAuthのイメージを掴んでおけばよかったです。

認証方式が多く、どれを使えばいいのかわからない

これは前述のpythonSDKのページを早く見つけられれば解決した気はしますが、、
公式ドキュメントを上から読んでいくのは気が遠くなる作業だったのでとりあえずyoutubeを参考に進めていった結果、何が正解か分からない中でたくさんのエラーに遭遇してなかなかストレスでした。。

Azureに不慣れでGUI操作がわかりづらい

APIのtoken発行にAzure上でAPIアプリの作成が必要になりますが、Azureを普段触ることもないので少し苦労しました。

pythonのスクリプト例が少ない

Graph Explorerのcode snippetsにpythonがありません。。
また、github上で使えそうなモジュールはいくつか出てくるものの、認証・認可について詳細な説明がなく、最終的には利用しませんでした。

遭遇したエラー

scopeのエラー

tokenを発行するために、認可して欲しい内容をSCOPESとして渡します。
通常はSCOPES = ['https://graph.microsoft.com/.default']とすれば、
GUI上で設定したスコープを読み込んでくれる?ようですが、私の場合だと以下のようなエラーが出てきてしまいました。
エラーコードのリファレンスはこちら。ちなみにMSサービス全体として、ここでエラーコードの検索をかけられるようです。

AADSTS70011: The provided value for the input parameter 'scope' is not valid. One or more scopes in 'https://graph.microsoft.com/.default openid offline_access' are not compatible with each other.

追加した覚えのないopenidoffline_accessがscopesの引数として渡されているようです。 
そもそもの/.defaultについての説明はこちらの公式ドキュメントにありますが、解読は諦めました。。
initiate_device_flow()を利用した場合のサンプルコードでSCOPESは具体的に書いていたため、
.defaultは利用せず以下のように直で書くので良しとしました。。

main.py
SCOPES = ['User.Read', 'Notes.Read', 'Notes.ReadWrite', 'Notes.Create']

ConfidentialClientApplicationだとAPIを叩けない

序盤で参考にしたMediumの記事ではmsal.ConfidentialClientApplicationを利用していました。tokenの付与はできたものの、エラーメッセージで「この認証方法ではAPI叩けない」と言われて悲しかったです。。
結局、msal.PublicClientApplicationを利用しています。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
1