ほぼ老人のプログラミング日記

定年後の平凡なサラリーマンの趣味の日記

Knowledge→GROWI 移行 (5)

GROWI API の使い方を調べる

GROWI API の使い方を調べます。ドキュメントは下記のページにあります。

docs.growi.org

私にはドキュメント読んでも具体的なイメージがわかなかったので、やはり Google で検索すると、私にピッタリなサイトが見つかりました。

kapibara-sos.net

まずは、そのまま使わせていただきます。requests モジュールが必要なので、.devcontainer/python/requirement.txt に requests を追加してコンテナを再ビルドしました。
そういえば、以前 PostgreSQL にアクセスするのに psycopg2 モジュールを使いました。この時も .devcontainer/python/requirement.txt に psycopg2 を追加してコンテナを再ビルドしました。

簡単に動かしてみます

from crclient import CrClient

crclient = CrClient('growi', 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', 'tiger', './knowledge_to_growi/contents')
crclient.fetch()

確認用に記事を 1 件だけ登録してあるのですが、なんか取得できているようです。

#  cd /workspace ; /usr/bin/env /usr/local/bin/python3.8 /root/.vscode-server/extensions/ms-python.python-2021.12.1559732655/pythonFiles/lib/python/debugpy/launcher 43313 -- /workspace/knowledge_to_growi/crclient_sample.py 
skip item: {'_id': '61f9120a32a52003888290b5', 'status': 'published', 'grant': 1, 'grantedUsers': [], 'liker': [], 'seenUsers': ['61f7bcc6834bc4803f0e2bfa'], 'commentCount': 0, 'createdAt': '2022-02-01T10:57:14.468Z', 'updatedAt': '2022-02-01T10:57:14.488Z', 'path': '/user/tiger/自分自身のこと', 'creator': '61f7bcc6834bc4803f0e2bfa', 'lastUpdateUser': {'_id': '61f7bcc6834bc4803f0e2bfa', 'isGravatarEnabled': False, 'isEmailPublished': True, 'lang': 'ja_JP', 'status': 2, 'admin': False, 'createdAt': '2022-01-31T10:41:10.624Z', 'username': 'tiger', 'email': 'tiger@example.com', 'lastLoginAt': '2022-02-01T10:49:07.549Z', 'imageUrlCached': '/images/icons/user.svg', 'name': 'tiger62shin'}, 'redirectTo': None, 'grantedGroup': None, '__v': 1, 'revision': '61f9120a32a52003888290b9'}
skip item: {'_id': '61f7bd73834bc4803f0e2c25', 'status': 'published', 'grant': 1, 'grantedUsers': ['61f7bcc6834bc4803f0e2bfa'], 'liker': [], 'seenUsers': ['61f7bcc6834bc4803f0e2bfa'], 'commentCount': 0, 'createdAt': '2022-01-31T10:44:03.214Z', 'updatedAt': '2022-01-31T10:44:03.239Z', 'path': '/user/tiger', 'creator': '61f7bcc6834bc4803f0e2bfa', 'lastUpdateUser': {'_id': '61f7bcc6834bc4803f0e2bfa', 'isGravatarEnabled': False, 'isEmailPublished': True, 'lang': 'ja_JP', 'status': 2, 'admin': False, 'createdAt': '2022-01-31T10:41:10.624Z', 'username': 'tiger', 'email': 'tiger@example.com', 'lastLoginAt': '2022-02-01T10:49:07.549Z', 'imageUrlCached': '/images/icons/user.svg', 'name': 'tiger62shin'}, 'redirectTo': None, 'grantedGroup': None, '__v': 1, 'revision': '61f7bd73834bc4803f0e2c2b'}```

このソースを読んで、動きを確認したことで、なんとなく感覚はつかめたような気がします。API である URL にパラメタを付けて GET / POST でリクエストすると結果が JSON で帰ってくるという一般的な Web API と理解しました。

当初、この CrClient をそのままか、若干の修正で使わせていただこうと思っていたのですが、

  • データ移行なので更新の機能は必要ない
  • 1 回だけ新規登録できれば良い。失敗したらマニュアルで削除して再実行すれば良い
  • 添付ファイルが付けられない

ということで、やはり自分で作ることにしました。
必要な機能は多くはなく

  • 記事の新規登録
  • 記事へのファイル添付
    添付したファイルの url が変わるはずなので、下記のような手順になると思う
    1. 記事を新規登録する
    2. 記事にファイルを添付する
    3. 記事の添付ファイル参照 URL を書き換える

以下では機能ごとに調べていきますが、先にこれまで記載したサイトの他に参考にさせて頂いたのですがサイトを載せておきます。

s-densan.hatenablog.com

あと、GROWI API のドキュメントのサイトにある OpenAPI specification はよく参照しました。
以下の GROWI の API 利用の例では、今回のデータ移行に必要なパラメータやレスポンスのみ記載しました。

記事の新規登録

項目
URL _api/v3/pages
Method POST
Query Paramaters access_token : API Token
user : ユーザID
Payload body : 本文
path : 記事のパス
Response id : 記事のID
path : 記事のパス
revision : 記事のリビジョン
import requests

url = 'http://growi:3000/_api/v3/pages'
params = {"access_token": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
          "user": "tiger"}

path = "新規記事登録のサンプル"
content = "この記事は GROWI API を使って登録した記事です。"
payload = {"body": content, "path": "/tiger/{}".format(path)}
res = requests.post(url, data=payload, params=params)
pages_res = res.json()
print('id : ' + pages_res['page']['id'])
print('path : ' + pages_res['page']['path'])
print('revision : ' + pages_res['page']['revision'])

このプログラムを実行するとコンソールに下記のように出力されます。

root@8949de830c08:/workspace#  /usr/bin/env /usr/local/bin/python3.8 /root/.vscode-server/extensions/ms-python.python-2021.12.1559732655/pythonFiles/lib/python/debugpy/launcher 39727 -- /workspace/knowledge_to_growi/growiapi_sample1.py 
id : 61fa6161a03e4c6720a5f36a
path : /tiger/新規記事登録のサンプル
revision : 61fa6161a03e4c6720a5f36e

ブラウザで確認してみます。

f:id:tiger62shin:20220202195004p:plain

ちゃんと登録されました。

記事へのファイル添付

項目
URL _api/attachments.add
Method POST
Query Paramaters access_token : API Token
user : ユーザID
Payload page_id : 記事のID
path : 記事のバス
記事の新規登録で戻ってきた値を指定
file ファイル名, ファイル本体
Response id : 添付ファイルのID
originalName : ファイル名
filePathProxied : 添付ファイルのパス

先に作成した記事に下記の画像を添付してみます。

f:id:tiger62shin:20220202195818p:plain

import requests

url = 'http://growi:3000/_api/attachments.add'
params = {"access_token": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
          "user": "tiger"}

file = {'file': ('setsubun_mamemaki.png',
                 open('./knowledge_to_growi/attachments/setsubun_mamemaki.png', 'rb'),
                 'image/png')}
payload = {'page_id': '61fa6161a03e4c6720a5f36a',
           'path': '/tiger/新規記事登録のサンプル'}
res = requests.post(url, data=payload, files=file, params=params)
res.raise_for_status
attachments_res = res.json()
print('id : {} , originalName : {} , filePathProxied : {} '
      .format(attachments_res['attachment']['id'],
              attachments_res['attachment']['originalName'],
              attachments_res['attachment']['filePathProxied']))

このプログラムを実行するとコンソールに下記のように出力されます。

  cd /workspace ; /usr/bin/env /usr/local/bin/python3.8 /root/.vscode-server/extensions/ms-python.python-2022.0.1786462952/pythonFiles/lib/python/debugpy/launcher 40267 -- /workspace/knowledge_to_growi/growiapi_sample2.py 
id : 61fe69c0f6ed08f016d32137 , originalName : setsubun_mamemaki.png , filePathProxied : /attachment/61fe69c0f6ed08f016d32137 
root@83fe8d69003a:/workspace# 

ブラウザで確認してみます。

f:id:tiger62shin:20220202201534p:plain

ちゃんと添付されました。

記事の更新

項目
URL _api/pages.update
Method POST
Query Paramaters access_token : API Token
user : ユーザID
Payload body : 本文
pageTags : タグのリスト
page_id : 記事のID (*)
path : 記事のバス (*)
revision_id : リビジョンID(*)
(*) 記事の新規登録で戻ってきた値を指定
Response revision : 記事のリビジョン (*)
(*)更新するたびにリビジョンが変わるので何度も更新する場合は注意が必要

データ移行では本来、記事を更新する必要はないのですが、記事内の添付ファイル参照の url を書き換える必要があります。
下のソースは最初に登録した記事に添付ファイルの画像を表示するように更新するものです。

import requests

url = 'http://growi:3000/_api/pages.update'
params = {"access_token": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
          "user": "tiger"}

content = 'この記事は GROWI API を使って登録した記事です。' \
          '\n\n' \
          '![setsubun_mamemaki.png](/attachment/61fe69c0f6ed08f016d32137)'
payload = {'body': content,
           'page_id': '61fa6161a03e4c6720a5f36a',
           'path': '/tiger/新規記事登録のサンプル',
           'revision_id': '61fa6161a03e4c6720a5f36e'}
res = requests.post(url, data=payload, params=params)
res.raise_for_status

ブラウザで確認してみます。

f:id:tiger62shin:20220202211747p:plain

記事が更新され、画像が表示されました。


以上で、データ移行に必要な GROWI API の使い方はわかったのですが、その他にも以下のような API について使い方を調べました。

記事の一覧

項目
URL _api/pages.list
Method GET
Query Paramaters access_token : API Token
user : ユーザID
limit : 最大取得件数、-1 を指定すると全件
Payload N/A
Response _id : 記事のID
path : 記事のパス
revision : 記事のリビジョン

今回のデータ移行では、最初に全ての記事のリストを取得しておき、移行対象記事の存在チェックに利用しました。

import requests

url = 'http://growi:3000/_api/pages.list'
params = {"access_token": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
          'user': 'tiger',
          'limit': -1}

res = requests.get(url, params=params)
res.raise_for_status
pages_list_res = res.json()
for page in pages_list_res['pages']:
    print('id : {} / path : {} / revision : {} '
          .format(page['_id'], page['path'], page['revision']))

このプログラムを実行するとコンソールに下記のように出力されます。

#  /usr/bin/env /usr/local/bin/python3.8 /root/.vscode-server/extensions/ms-python.python-2022.0.1786462952/pythonFiles/lib/python/debugpy/launcher 41573 -- /workspace/knowledge_to_growi/growiapi_sample4.py 
id : 61fa6161a03e4c6720a5f36a / path : /tiger/新規記事登録のサンプル / revision : 61fa7643a03e4c6720a5f4f2 
id : 61f9120a32a52003888290b5 / path : /user/tiger/自分自身のこと / revision : 61f9120a32a52003888290b9 
id : 61f7bd73834bc4803f0e2c25 / path : /user/tiger / revision : 61f7bd73834bc4803f0e2c2b

添付ファイルの一覧

項目
URL _api/v3/attachment/list
Method GET
Query Paramaters access_token : API Token
user : ユーザID
pageid : 記事の ID (*)
page : 添付ファイル一覧のページ番号
(*) 記事の新規登録 / 記事の一覧の取得で戻ってきた値を指定
Payload N/A
Response id : 添付ファイルのID
originalName : 添付ファイル名
filePathProxied : 添付ファイルの url パス

この API は少し注意が必要です。取得できる添付ファイル情報は画面に表示される添付ファイル一覧の 1 ページ分しか取得できません。幸い page パラメーターで取得する一覧のページ番号を指定できますので、データが取得できなくなるまでページ番号をインクリメントしながら取得します。
今回のデータ移行では、すでに存在している記事に対して移行する際の添付ファイルの存在チェックに利用しました。

import requests

url = 'http://growi:3000/_api/v3/attachment/list'
params = {"access_token": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
          'user': 'tiger',
          'pageId': '61fa6161a03e4c6720a5f36a',
          'page': 1}

res = requests.get(url, params=params)
res.raise_for_status
attachment_list_res = res.json()
for attachment in attachment_list_res['paginateResult']['docs']:
    print('id : {} , originalName : {} , filePathProxied : {} '
          .format(attachment['id'], attachment['originalName'],
                  attachment['filePathProxied']))

このプログラムを実行するとコンソールに下記のように出力されます。

#  cd /workspace ; /usr/bin/env /usr/local/bin/python3.8 /root/.vscode-server/extensions/ms-python.python-2022.0.1786462952/pythonFiles/lib/python/debugpy/launcher 41445 -- /workspace/knowledge_to_growi/growiapi_sample5.py 
id : 61fa6799a03e4c6720a5f490 , originalName : setsubun_mamemaki.png , filePathProxied : /attachment/61fa6799a03e4c6720a5f490 

添付ファイルの削除

項目
URL _api/attachments.remove
Method POST
Query Paramaters access_token : API Token
user : ユーザID
Payload attachment_id : 添付ファイルID
記事へのファイル添付 / 添付ファイルの一覧取得で戻ってきた値を指定
Response -

今回のデータ移行では、すでに存在している添付ファイルを置き換える際の削除に利用しました。

import requests

url = 'http://growi:3000/_api/attachments.remove'
params = {"access_token": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
          'user': 'tiger'}

payload = {'attachment_id': '61fa6799a03e4c6720a5f490'}
res = requests.post(url, data=payload, params=params)
res.raise_for_status

このサンプルでは「記事へのファイル添付」で添付したファイルを削除しています。

f:id:tiger62shin:20220204212349p:plain

添付ファイルが削除されたので表示されなくなりました。


以上です。
あと、既存の記事の取得とかあればよかったのでしょうが、今回のデータ移行には必要なかったので調べませんでした。