Tableau Serverにアクセスして画像、データを取得するプログラムを作ってみる（Python）

## はじめに
前回と同じく社内のTableauサーバに公開されているグラフの画像データとテキストデータをダウンロードします。

![Tableau画面スクリーンショット 2024-07-13 140909.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3837083/356e41f0-3ad7-3891-fe0f-e235728c3c63.png)

## 必要なライブラリ
・Pythonの実行環境　（v3.11.4で試しました）
・ライブラリ　requests, json

ライブラリがない場合は以下でインストールしてください
(jsonはもとから入っているようです)
```
pip install requests
```

## 必要な情報
・個人用アクセストークン：Tableauサーバの「マイアカウントの設定」画面より設定してください。
　詳細は[前回の記事](https://qiita.com/kktzn0920/items/005e60734b7bf801eadb)に記載しています。

## まず、認証してTokenを取得する
以下のsigninのAPIを利用して認証します。JSONのパースはResponse Bodyに書かれてあるタグを参考にしています。
POST /api/api-version/auth/signin
https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref.htm#sign_in
以下のコードです。TODOに書かれているサーバ情報とトークン情報を実行するサーバのものに修正してください。認証はrequests.postを実行します。このときにPersonal Access TokenをPayloadに設定します。実行した結果のHTTP Responseを分析してこの後の通信で使用するTokenとsite idを取得します。

```sample1.py
# -*- coding: utf-8 -*-
import requests, json.

TABLEAU_TOKEN_VALUE = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'  #TODO　Tableauの情報
TABLEAU_TOKEN_NAME = 'test'  #TODO  個人用アクセストークンの「トークン名」
TABLEAU_SERVER = 'tableauserver.tableauserver.jp'  #TODO Tableauサーバのアドレス
TABLEAU_VERSION = '3.19'  #TODO Tabeleau ServerのAPI version

#Tableauサーバーへサインインする
server_name = TABLEAU_SERVER
version = TABLEAU_VERSION
site_url_id = ""    # Site URLのパス、空白の場合はDefaultになる

# Personal Access Token を指定する
personal_access_token_name = TABLEAU_TOKEN_NAME      # Personal access tokenの名前
personal_access_token_secret = TABLEAU_TOKEN_VALUE   # Personal access token

signin_url = "https://{server}/api/{version}/auth/signin".format(server=server_name, version=version)   
payload = { "credentials": { "personalAccessTokenName": personal_access_token_name, "personalAccessTokenSecret": personal_access_token_secret, "site": {"contentUrl": site_url_id }}}

headers = {
	'accept': 'application/json',
	'content-type': 'application/json'
}
    
# サーバにRequestを送信する
req = requests.post(signin_url, json=payload, headers=headers, verify=False)
req.raise_for_status()

#HTTP Responseを取得
response = json.loads(req.content)

#credentialsのエレメントからTokenを取得する
token = response["credentials"]["token"]

#Site IDエレメントからSite IDを取得する
site_id = response["credentials"]["site"]["id"]

print('Sign in successful!')
print('Token: {token}'.format(token=token))
print('Site ID: {site_id}'.format(site_id=site_id))
```
## view idを検索する
以下のAPIを使ってview_idを検索します。
GET /api/api-version/sites/site-id/views?filter=viewUrlName:eq:view-name
https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_workbooks_and_views.htm#get_view_by_path
検索結果はJSON形式で返るようにして、JSONデータをパースして最初のview idを取得しています。複数のviewを持つ結果が返る場合はプログラムを修正してください。

```sample2.py
VIEW_URL_NAME = 'Order_Sales' #TODO viewUrlNameの値（シート名）を入れる
#view idを探す
query_value=  "filter=viewUrlName:eq:{viewUrlName}".format(viewUrlName=VIEW_URL_NAME)
headers = {'X-tableau-auth':token,
           'accept': 'application/json'}

#GET /api/api-version/sites/site-id/views?filter=viewUrlName:eq:view-name
view_get_url="https://{server}/api/{version}/sites/{siteid}/views?{query}".format(
        server=TABLEAU_SERVER, version=TABLEAU_VERSION, siteid=site_id,  query= query_value)

outDataOrig = requests.get(view_get_url,headers=headers).content
outDataDecode= outDataOrig.decode()
json_out = json.loads(outDataDecode)
view_id = json_out['views']['view'][0]['id']

print('view id: {view_id}'.format(view_id=view_id))
```

viewUrlNameからうまく検索できない場合は以下のコードのようにフィルタなしでviewの一覧を出すこともできます。この場合はprintされたXMLから目的のシートのview idを探してview_idセットしてください
```sample2-2.py
headers = {'X-tableau-auth':token}

#GET /api/api-version/sites/site-id/views?filter=viewUrlName:eq:view-name
view_get_url="https://{server}/api/{version}/sites/{siteid}/views".format(
        server=TABLEAU_SERVER, version=TABLEAU_VERSION, siteid=site_id)

outDataOrig = requests.get(view_get_url,headers=headers).content
print(outDataOrig.decode())
```

## 画像ファイルを取得する
以下のAPIでイメージを取得します。
GET /api/api-version/sites/site-id/views/view-id/image
https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_workbooks_and_views.htm#query_view_image
必要なものはsite-idとview-idなので取得した値をURLに入れます。
requests.getを実行するとファイルをダウンロードできます。認証情報はHTTPヘッダーの中にSignin時に取得したTokenを入れます。

```sample3.py
DOWNLOAD_IMAGE_PATH = 'sample.png'   #TODO 保存先のファイル名

query_value = "resolution=high" 
headers = {'X-tableau-auth':token}
  
#GET /api/api-version/sites/site-id/views/view-id/image?vf_<fieldname>=filter-value
image_get_url="https://{server}/api/{version}/sites/{siteid}/views/{viewid}/image?{query}".format(
        server=TABLEAU_SERVER, version=TABLEAU_VERSION, siteid=site_id, viewid=view_id, query= query_value)

urlData = requests.get(image_get_url,headers=headers).content
   
with open(DOWNLOAD_IMAGE_PATH, mode='wb') as f:
    f.write(urlData)
```

### テキストファイル（CSV）をダウンロードする
テキストファイルの取得は以下になります。
#GET /api/api-version/sites/site-id/views/view-id/data
https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_workbooks_and_views.htm#query_view_data
取得方法はイメージと同じですが、URLDecodeしないと文字化けするケースがありました。
サンプルではそのままCSVファイルに書き出していますが、openpyxlパッケージを使うとExcelファイルに書き出すこともできます。（もちろん、そのままファイル出力するだけでなくExcel表を読み込んである列に合うデータを入れるといったプログラムを書くこともできます。）

```sample4.py
DOWNLOAD_TEXT_PATH = 'sample.csv'   #TODO 保存先のファイル名

query_value = ""
    
headers = {'X-tableau-auth':token}
  
#GET /api/api-version/sites/site-id/views/view-id/data?vf_<fieldname>=filter-value
data_get_url="https://{server}/api/{version}/sites/{siteid}/views/{viewid}/data?{query}".format(
    server=TABLEAU_SERVER, version=TABLEAU_VERSION, siteid=site_id, viewid=view_id, query= query_value)
urlData = requests.get(data_get_url,headers=headers).content  
outtext = urlData.decode()

#取得したデータを出力してみる
print(outtext)

#ファイルに出力する
with open(DOWNLOAD_TEXT_PATH, mode='w') as f:
    f.write(outtext)
```

### サインアウトする
Tokenはタイムアウトまで有効なためデータ取得できたらサインアウトします。
以下のHTTP POSTの通信を行いますが、RequestのBodyは空になります。
POST /api/api-version/auth/signout
https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref.htm#sign_out
```sample5.py
server_name = TABLEAU_SERVER  #Tableau Serverの情報（Name/IPアドレス）
version = TABLEAU_VERSION     #Tabeleau ServerのAPI version
    
headers = {
	'accept': 'application/json',
	'content-type': 'application/json'
}
    
#サインイン時に取得した認証のTokenをヘッダにセットする
headers['X-tableau-auth']=token
    
#サインアウトのクエリをセット
signout_url = "https://{server}/api/{version}/auth/signout".format(server=server_name, version=version)

req = requests.post(signout_url, data=b'', headers=headers, verify=False)
req.raise_for_status()
print('Sign out successful!')
```

## まとめ
ソースコードをまとめておきます
```sample_all.py
# -*- coding: utf-8 -*-
import requests, json

#環境に応じて変更する箇所#######################
TABLEAU_TOKEN_VALUE = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'  #TODO　Tableauの情報
TABLEAU_TOKEN_NAME = 'test'  #TODO  個人用アクセストークンの「トークン名」
TABLEAU_SERVER = 'tableauserver.tableauserver.jp'  #TODO Tableauサーバのアドレス
TABLEAU_VERSION = '3.19'  #TODO Tabeleau ServerのAPI version

DOWNLOAD_IMAGE_PATH = 'sample.png'   #TODO 保存先のファイル名
DOWNLOAD_TEXT_PATH = 'sample.csv'    #TODO 保存先のファイル名
VIEW_URL_NAME = 'Order_Sales' #TODO viewUrlNameの値（シート名）を入れる
##############################################

#Tableauサーバーへサインインする
signin_url = "https://{server}/api/{version}/auth/signin".format(server=TABLEAU_SERVER, version=TABLEAU_VERSION)   
payload = { "credentials": { "personalAccessTokenName": TABLEAU_TOKEN_NAME, "personalAccessTokenSecret": TABLEAU_TOKEN_VALUE, "site": {"contentUrl": SITE_URL_ID }}}

headers = {
	'accept': 'application/json',
	'content-type': 'application/json'
}
    
# サーバにRequestを送信する
req = requests.post(signin_url, json=payload, headers=headers, verify=False)
req.raise_for_status()

#HTTP Responseを取得
response = json.loads(req.content)

#credentialsのエレメントからTokenを取得する
token = response["credentials"]["token"]

#Site IDエレメントからSite IDを取得する
site_id = response["credentials"]["site"]["id"]

print('Sign in successful!')
print('Token: {token}'.format(token=token))
print('Site ID: {site_id}'.format(site_id=site_id))

###########################################################################################
#view idを探す
query_value=  "filter=viewUrlName:eq:{viewUrlName}".format(viewUrlName=VIEW_URL_NAME)
headers = {'X-tableau-auth':token,
           'accept': 'application/json'}

#GET /api/api-version/sites/site-id/views?filter=viewUrlName:eq:view-name
view_get_url="https://{server}/api/{version}/sites/{siteid}/views?{query}".format(
        server=TABLEAU_SERVER, version=TABLEAU_VERSION, siteid=site_id,  query= query_value)

outDataOrig = requests.get(view_get_url,headers=headers).content
outDataDecode= outDataOrig.decode()
json_out = json.loads(outDataDecode)
view_id = json_out['views']['view'][0]['id']

print('view id: {view_id}'.format(view_id=view_id))

###########################################################################################
#Imageファイルのダウンロード
query_value = "resolution=high" 
headers = {'X-tableau-auth':token}
  
#GET /api/api-version/sites/site-id/views/view-id/image?vf_<fieldname>=filter-value
image_get_url="https://{server}/api/{version}/sites/{siteid}/views/{viewid}/image?{query}".format(
        server=TABLEAU_SERVER, version=TABLEAU_VERSION, siteid=site_id, viewid=VIEW_ID, query= query_value)

urlData = requests.get(image_get_url,headers=headers).content
   
with open(DOWNLOAD_IMAGE_PATH ,mode='wb') as f:
    f.write(urlData)

###########################################################################################
#テキストファイルのダウンロード
query_value = ""
    
headers = {'X-tableau-auth':token}
  
#GET /api/api-version/sites/site-id/views/view-id/data?vf_<fieldname>=filter-value
data_get_url="https://{server}/api/{version}/sites/{siteid}/views/{viewid}/data?{query}".format(
    server=TABLEAU_SERVER, version=TABLEAU_VERSION, siteid=site_id, viewid=VIEW_ID, query= query_value)
urlData = requests.get(data_get_url,headers=headers).content  
outtext = urlData.decode()

#取得したデータを出力してみる
#print(outtext)

with open(DOWNLOAD_TEXT_PATH ,mode='w') as f:
    f.write(outtext)

######################################################################################
#Signout   
headers = {
	'accept': 'application/json',
	'content-type': 'application/json'
}
    
#サインイン時に取得した認証のTokenをヘッダにセットする
headers['X-tableau-auth']=token
    
#サインアウトのクエリをセット
signout_url = "https://{server}/api/{version}/auth/signout".format(server=TABLEAU_SERVER, version=TABLEAU_VERSION)

req = requests.post(signout_url, data=b'', headers=headers, verify=False)
req.raise_for_status()
print('Sign out successful!')
```