【Python】Atlassian/Confluence の REST API を Pythonでコールする

■ はじめに

Atlassian/Confluence の REST API を使って
情報を吸い上げることを急遽、業務で依頼があったので焦った。
その際の情報をメモ。

目次

【1】Atlassian/Confluence
【2】準備
 1)Access Tokenの発行
【3】Atlassian/Confluence の REST API
 1)Space
 2)Space Properties
 3)Page
 補足:ページネーションについて
【4】サンプル

【1】Atlassian/Confluence

Atlassian社が出している情報共有ツール(Wikiみたいなもん)
Confluence(コンフルエンス)

https://www.atlassian.com/ja/software/confluence

【2】準備

1)Access Tokenの発行

[1] Confluence にログイン
[2] [自分のユーザアイコン(画面右上)]-[Settings]-[Password]を選択
[3] 「Security」ページの「API Token」欄にある
 リンク「Create and manage API tokens」を押下
[4] 「Create API token」ボタン押下
[5] Label欄に任意の文字列を入力(今回は、「Hello API」)し、
 「Create」ボタン押下
[6] 「Copy」ボタン押下し、Textファイルなどにペーストして保存しておく
 => この文字列が、Access Tokenになる
[7] 「Close」ボタン押下

不要・削除したい場合

* 「Revoke」ボタン押下すれば、破棄できる

【3】Atlassian/Confluence の REST API

https://developer.atlassian.com/cloud/confluence/rest/v2/intro

* サンプルが付いていて、助かった。。。
 => 自己流でコーディングしたが、「405(これはPOSTしてただけ)」「404」
  だったり返ってきて、うまく欲しい情報を取れなかった、、、

1)Space

https://developer.atlassian.com/cloud/confluence/rest/v2/api-group-space/

2)Space Properties

https://developer.atlassian.com/cloud/confluence/rest/v2/api-group-space-properties/

3)Page

https://developer.atlassian.com/cloud/confluence/rest/v2/api-group-page/

補足:ページネーションについて

https://developer.atlassian.com/cloud/confluence/rest/v2/api-group-space/

などで、limit(Max250)があり、一気にデータを取れないので、
ページネーションを使う必要がある。
 => サンプルも参照。

関連するパラメータ

* cursor (string) : 値にカーソルトークンを付与することにより、ページネーションしてくれる
* limit (integer) : データの最大数(Max250)

Headerパラメータ

* Link (string) : ヘッダーLinkにカーソルトークン [opaque cursor token]が付与

Link Format: </wiki/api/v2/spaces?cursor=[opaque cursor token]>; rel="next"

実装の流れ

[1] REST API の レスポンスヘッダーの Link を取得
 => 「</wiki/api/v2/spaces?cursor=[opaque cursor token]>; rel="next"」
  になっているので、[opaque cursor token]を取得する
  (なければ、もう取得全てできている)
[2] 次のREST API を呼び出す際に「cursor=[opaque cursor token]」
 を付加して、コールする([1]を繰り返す)

【4】サンプル

import requests
from requests.auth import HTTPBasicAuth
import json
import re


# You have to change the parameters for your own account.
domain = 'your-domain'
id = '11111...'
user = 'email@example.com' # Email for Confluence
access_token = 'xe...' # 「【2】準備」 の「1)Access Tokenの発行」で用意した値

# https://developer.atlassian.com/cloud/confluence/rest/v2/api-group-space/#api-spaces-id-get
limit_page = 250 # Max limit = 250
api_page_url = f'https://{domain}/wiki/api/v2/spaces/{id}/pages?limit={limit_page}'

print('Start...')

auth = HTTPBasicAuth(user, access_token)

headers = {
  "Accept": "application/json"
}

while api_page_url is not None:
  response = requests.request(
    "GET",
    api_page_url,
    headers=headers,
    auth=auth
  )
  # Debug log
  # print(json.dumps(json.loads(response.text), sort_keys=True, indent=2, separators=(",", ": ")))
  api_page_url = None

  link = response.headers.get('link')
  # Debug log
  # e.g. Link = </wiki/api/v2/spaces/11111.../{id}/pages?limit=250&cursor=XXXX...==>; rel="next"
  print(f'Link = {link}')

  if link is not None:
    #  </wiki/api/v2/spaces?cursor=<opaque cursor token>>; rel="next"
    target_regular_ex = f'cursor=(\S+); rel="next"'
    pattern = re.compile(target_regular_ex)
    match_results = pattern.findall(link)
    if match_results:
      cursor_value = match_results[0]
      api_page_url = f'https://{domain}/wiki/api/v2/spaces/{id}/pages?limit={limit_page}&cursor={cursor_value}'

  page_list = json.loads(response.text).get('results')
  for page_info in page_list:
    page_id = page_info.get('id')
    # https://developer.atlassian.com/cloud/confluence/rest/v2/api-group-page/#api-pages-id-get
    api_page_url = f'https://{domain}/wiki/api/v2/pages/{page_id}/properties'
    response = requests.request(
      "GET",
      api_page_url,
      headers=headers,
      auth=auth
    )
    # Debug log
    print(json.dumps(json.loads(response.text), sort_keys=True, indent=2, separators=(",", ": ")))
    page_properties = json.loads(response.text).get('results')
    for page_property in page_properties:
      print(page_property)

print('Done')

関連記事

Python ~ 基本編 / urllib ~
https://dk521123.hatenablog.com/entry/2022/08/05/000000
Python ~ 基本編 / 正規表現
https://dk521123.hatenablog.com/entry/2019/09/01/000000
Python ~ 基本編 / 正規表現あれこれ ~
https://dk521123.hatenablog.com/entry/2020/10/15/000000