使用 Qlik Engine 导出可视化数据 JSON API
Export visualization data using Qlik Engine JSON API
我们的组织使用 Qlik Sense Enterprise,我们正在寻求自动化用于可视化的数据下载过程(格式可以是 excel 或 csv),而不是导致以下结果的手动过程(裁剪屏幕截图显示):
对于我们的用例,假设只有一个应用程序内部有一个 sheet,并且 sheet 有 3 个可视化。
我写了一个 python 脚本,目前已连接到本地主机,我能够检索 app_id、sheet_id 和当前使用的 3 个图表的 ID Qlik 引擎 JSON API。代码按以下方式工作:
- 获取 doc_list (app_list)
- Select 应用程序,因为我们只有一个应用程序,所以我们选择索引为 0
- 创建会话对象(我在 Dev Hub 上看到 Qlik Engine 表现出这种行为,这就是我执行此步骤的原因)
- 获取应用的布局
- Select sheet, 因为我们有一个 sheet 我们选择索引为 0
- 遍历可视化并打印它们的名称
我提供了下面的代码供大家参考,pastebinlink可以访问here
import requests
import websocket, ssl
import json, csv
import os, time
from pprint import pprint
#Connecting to the server. The lines below will be replaced by the certificates and headers for enterprise usage later
ws = websocket.WebSocket()
ws.connect("ws://localhost:4848/app/")
#For getting the doc (app) list
doclist_req = {
"handle": -1,
"method": "GetDocList",
"params": [],
"outKey": -1,
"id": 1
}
ws.send(json.dumps(doclist_req))
result = ws.recv()
ws.send(json.dumps(doclist_req))
result = ws.recv()
result_json = json.loads(result)
print(result_json)
print()
#For opening the doc (app)
app_req = {
"jsonrpc": "2.0",
"method": "OpenDoc",
"handle": -1,
"params": [
#Can iterate if multiple apps are there
#Since only one app was present we used the index 0
result_json['result']['qDocList'][0]['qDocId']
],
"outKey": -1,
"id": 2
}
#The first call seems to be for establishing the connection and second to actually send the request body
ws.send(json.dumps(app_req))
result = ws.recv()
ws.send(json.dumps(app_req))
result = ws.recv()
result_json = json.loads(result)
print(result_json)
print()
app_req_handle = result_json['result']['qReturn']['qHandle']
#For creating the session object necessary for fetching dimensions, fields, etc.
session_req = {
"jsonrpc": "2.0",
"method": "CreateSessionObject",
"handle": app_req_handle,
"params": [
{
"qInfo": {
"qType": "SheetList"
},
"qAppObjectListDef": {
"qType": "sheet",
"qData": {
"title": "/qMetaDef/title",
"description": "/qMetaDef/description",
"thumbnail": "/thumbnail",
"cells": "/cells",
"rank": "/rank",
"columns": "/columns",
"rows": "/rows"
}
}
}
],
"outKey": -1,
"id": 3
}
ws.send(json.dumps(session_req))
result = ws.recv()
ws.send(json.dumps(session_req))
result = ws.recv()
result_json = json.loads(result)
print(result_json)
print()
session_req_handle = result_json['result']['qReturn']['qHandle']
#For fetching the layout of the sheets
layout_req = {
"jsonrpc": "2.0",
"method": "GetLayout",
"handle": session_req_handle,
"params": [],
"outKey": -1,
"id": 4
}
ws.send(json.dumps(layout_req))
result = ws.recv()
ws.send(json.dumps(layout_req))
result = ws.recv()
result_json = json.loads(result)
print(result_json)
print()
#Since only one sheet was present we used the index 0
list_of_charts = result_json['result']['qLayout']['qAppObjectList']['qItems'][0]['qData']['cells']
for chart in list_of_charts:
print(chart['name'])
print()
ws.close()
我在 Qlik 社区以及 SO 和 ExportData 上浏览了很多页面似乎是可行的方法,但我无法为其编写正确的 JSON 请求。作为参考,我在终端中执行 python 脚本时得到的输出如下所示。我对此很陌生,非常感谢大家的帮助。
{'jsonrpc': '2.0', 'id': 1, 'result': {'qDocList': [{'qDocName': 'Test_2.qvf', 'qConnectedUsers': 0, 'qFileTime': 44188.190983796296, 'qFileSize': 851968, 'qDocId': 'C:\Users\mohdm\Documents\Qlik\Sense\Apps\Test_2.qvf', 'qMeta': {'hassectionaccess': False, 'encrypted': False}, 'qLastReloadTime': '2020-12-22T19:12:22.245Z', 'qTitle': 'Test_2', 'qThumbnail': {}}]}}
{'jsonrpc': '2.0', 'id': 2, 'result': {'qReturn': {'qType': 'Doc', 'qHandle': 1, 'qGenericId': 'C:\Users\mohdm\Documents\Qlik\Sense\Apps\Test_2.qvf'}}, 'change': 1}
{'jsonrpc': '2.0', 'id': 3, 'result': {'qReturn': {'qType': 'GenericObject', 'qHandle': 2, 'qGenericType': 'SheetList', 'qGenericId': '4b344780-a350-48db-8b65-27bb5a2c62b2'}}, 'change': 1}
{'jsonrpc': '2.0', 'id': 4, 'result': {'qLayout': {'qInfo': {'qId': '4b344780-a350-48db-8b65-27bb5a2c62b2', 'qType': 'SheetList'}, 'qMeta': {'privileges': ['read', 'update', 'delete', 'exportdata']}, 'qSelectionInfo': {}, 'qAppObjectList': {'qItems': [{'qInfo': {'qId': '8a0f6a01-ef89-4d65-821f-371c26208dcf', 'qType': 'sheet'}, 'qMeta': {'privileges': ['read', 'update', 'delete', 'exportdata'], 'title': 'Sheet_1', 'description': ''}, 'qData': {'rank': None, 'thumbnail': {'qStaticContentUrl': {}}, 'columns': 24, 'rows': 12, 'cells': [{'name': 'kfxNpV', 'type': 'auto-chart', 'col': 0, 'row': 0, 'colspan': 15, 'rowspan': 6, 'bounds': {'y': 0, 'x': 0, 'width': 62.5, 'height': 50}}, {'name': 'qHzmARQ', 'type': 'qlik-barplus-chart', 'col': 0, 'row': 6, 'colspan': 21, 'rowspan': 6, 'bounds': {'y': 50, 'x': 0, 'width': 87.5, 'height': 50}}, {'name': 'BXBQmw', 'type': 'auto-chart', 'col': 15, 'row': 0, 'colspan': 9, 'rowspan': 6, 'bounds': {'y': 0, 'x': 62.5, 'width': 37.5, 'height': 50}}], 'title':
'Sheet_1', 'description': ''}}]}}}}
kfxNpV
qHzmARQ
BXBQmw
我最初也在 Qlik community 上发布了这个问题,但没有得到回复。
对于这种情况,我通常从浏览器“观察”Qlik 的通信。
(在Chrome)
- 打开应用程序
- 打开浏览器开发工具(按 F12)
- 导航至“网络”(1)
- 导航到“WS”(2)
- 按下所需的套接字会话 (3)
- 按“消息”(4)
- 检查套接字send/received中的内容
(如果您在“网络”选项卡中看不到套接字,请刷新页面)
下面是为一个对象导出数据的 Javascript/Node 代码。 (在我的案例中,我对对象 ID 进行了硬编码)
const fs = require('fs');
const axios = require('axios');
const enigma = require('enigma.js');
const WebSocket = require('ws');
const schema = require('enigma.js/schemas/12.20.0.json');
const session = enigma.create({
schema,
url: 'ws://localhost:9076/app/engineData',
createSocket: url => new WebSocket(url)
});
(async function () {
// open new session
let global = await session.open();
// open the app
let doc = await global.openDoc("C:\Users\USERNAME\Documents\Qlik\Sense\Apps\Consumer_Sales.qvf");
// get the required object
let qObj = await doc.getObject("MEAjCJ");
// OOXML - export the data in Excel (xlsx) format
let data = await qObj.exportData("OOXML");
// generate the full download link
let downloadLink = `http://localhost:4848${data.qUrl}`;
// download and save the file
await axios.get(downloadLink, { responseType: "stream" })
.then(response => {
response.data.pipe(fs.createWriteStream("export.xlsx"));
});
})()
ExportData 方法的官方文档是 here
更新 - Python 代码片段
通过 sheet 个对象的循环看起来像这样:
id = 5
list_of_charts = result_json['result']['qLayout']['qAppObjectList']['qItems'][0]['qData']['cells']
for chart in list_of_charts:
obj_req = {
"jsonrpc": "2.0",
"method": "GetObject",
"handle": app_req_handle,
"params": [ chart["name"] ],
"outKey": -1,
"id": id
}
ws.send(json.dumps(obj_req))
result = ws.recv()
ws.send(json.dumps(obj_req))
result = ws.recv()
result_json = json.loads(result)
# print(result_json)
obj_req_handle = result_json['result']['qReturn']['qHandle']
export_req = {
"jsonrpc": "2.0",
"method": "ExportData",
"handle": obj_req_handle,
"params": [ "OOXML" ],
"outKey": -1,
"id": 6
}
ws.send(json.dumps(export_req))
result = ws.recv()
ws.send(json.dumps(export_req))
result = ws.recv()
result_json = json.loads(result)
downloadURL = "http://localhost:4848" + result_json["result"]["qUrl"]
r = requests.get(downloadURL, allow_redirects=True)
open('export_python_' + chart["name"] + '.xlsx', 'wb').write(r.content)
id += 1
我不确定 Python,但我发现通常最困难的部分是正确进行身份验证。像 JavaScript 那样的库通常会大大简化这一过程。我个人倾向于将 C# 用于此类小工具,其中 .NET SDK 提供运算符来为您完成管道工作。这是使用这两个 nuget 包的示例:
- https://www.nuget.org/packages/QlikSense.NetSDK/
- https://www.nuget.org/packages/QlikSenseRestClient/
此代码会在应用程序的第一个 sheet 上下载所有可视化的 xlsx 文件。
var location = Location.FromUri(uri);
location.AsNtlmUserViaProxy();
var restClient = new RestClient(uri);
restClient.AsNtlmUserViaProxy();
using (var app = location.App(new AppIdentifier {AppId = appId}))
{
var theSheet = app.GetSheets().First();
var objs = theSheet.GetChildInfos().Select(info => app.GetGenericObject(info.Id));
foreach (var o in objs)
{
var exportResult = o.ExportData(NxExportFileType.EXPORT_OOXML);
var data = restClient.GetBytes(exportResult.Url);
using (var writer = new BinaryWriter(new FileStream(o.Id + ".xlsx", FileMode.OpenOrCreate)))
{
writer.Write(data);
}
}
}
我们的组织使用 Qlik Sense Enterprise,我们正在寻求自动化用于可视化的数据下载过程(格式可以是 excel 或 csv),而不是导致以下结果的手动过程(裁剪屏幕截图显示):
对于我们的用例,假设只有一个应用程序内部有一个 sheet,并且 sheet 有 3 个可视化。
我写了一个 python 脚本,目前已连接到本地主机,我能够检索 app_id、sheet_id 和当前使用的 3 个图表的 ID Qlik 引擎 JSON API。代码按以下方式工作:
- 获取 doc_list (app_list)
- Select 应用程序,因为我们只有一个应用程序,所以我们选择索引为 0
- 创建会话对象(我在 Dev Hub 上看到 Qlik Engine 表现出这种行为,这就是我执行此步骤的原因)
- 获取应用的布局
- Select sheet, 因为我们有一个 sheet 我们选择索引为 0
- 遍历可视化并打印它们的名称
我提供了下面的代码供大家参考,pastebinlink可以访问here
import requests
import websocket, ssl
import json, csv
import os, time
from pprint import pprint
#Connecting to the server. The lines below will be replaced by the certificates and headers for enterprise usage later
ws = websocket.WebSocket()
ws.connect("ws://localhost:4848/app/")
#For getting the doc (app) list
doclist_req = {
"handle": -1,
"method": "GetDocList",
"params": [],
"outKey": -1,
"id": 1
}
ws.send(json.dumps(doclist_req))
result = ws.recv()
ws.send(json.dumps(doclist_req))
result = ws.recv()
result_json = json.loads(result)
print(result_json)
print()
#For opening the doc (app)
app_req = {
"jsonrpc": "2.0",
"method": "OpenDoc",
"handle": -1,
"params": [
#Can iterate if multiple apps are there
#Since only one app was present we used the index 0
result_json['result']['qDocList'][0]['qDocId']
],
"outKey": -1,
"id": 2
}
#The first call seems to be for establishing the connection and second to actually send the request body
ws.send(json.dumps(app_req))
result = ws.recv()
ws.send(json.dumps(app_req))
result = ws.recv()
result_json = json.loads(result)
print(result_json)
print()
app_req_handle = result_json['result']['qReturn']['qHandle']
#For creating the session object necessary for fetching dimensions, fields, etc.
session_req = {
"jsonrpc": "2.0",
"method": "CreateSessionObject",
"handle": app_req_handle,
"params": [
{
"qInfo": {
"qType": "SheetList"
},
"qAppObjectListDef": {
"qType": "sheet",
"qData": {
"title": "/qMetaDef/title",
"description": "/qMetaDef/description",
"thumbnail": "/thumbnail",
"cells": "/cells",
"rank": "/rank",
"columns": "/columns",
"rows": "/rows"
}
}
}
],
"outKey": -1,
"id": 3
}
ws.send(json.dumps(session_req))
result = ws.recv()
ws.send(json.dumps(session_req))
result = ws.recv()
result_json = json.loads(result)
print(result_json)
print()
session_req_handle = result_json['result']['qReturn']['qHandle']
#For fetching the layout of the sheets
layout_req = {
"jsonrpc": "2.0",
"method": "GetLayout",
"handle": session_req_handle,
"params": [],
"outKey": -1,
"id": 4
}
ws.send(json.dumps(layout_req))
result = ws.recv()
ws.send(json.dumps(layout_req))
result = ws.recv()
result_json = json.loads(result)
print(result_json)
print()
#Since only one sheet was present we used the index 0
list_of_charts = result_json['result']['qLayout']['qAppObjectList']['qItems'][0]['qData']['cells']
for chart in list_of_charts:
print(chart['name'])
print()
ws.close()
我在 Qlik 社区以及 SO 和 ExportData 上浏览了很多页面似乎是可行的方法,但我无法为其编写正确的 JSON 请求。作为参考,我在终端中执行 python 脚本时得到的输出如下所示。我对此很陌生,非常感谢大家的帮助。
{'jsonrpc': '2.0', 'id': 1, 'result': {'qDocList': [{'qDocName': 'Test_2.qvf', 'qConnectedUsers': 0, 'qFileTime': 44188.190983796296, 'qFileSize': 851968, 'qDocId': 'C:\Users\mohdm\Documents\Qlik\Sense\Apps\Test_2.qvf', 'qMeta': {'hassectionaccess': False, 'encrypted': False}, 'qLastReloadTime': '2020-12-22T19:12:22.245Z', 'qTitle': 'Test_2', 'qThumbnail': {}}]}}
{'jsonrpc': '2.0', 'id': 2, 'result': {'qReturn': {'qType': 'Doc', 'qHandle': 1, 'qGenericId': 'C:\Users\mohdm\Documents\Qlik\Sense\Apps\Test_2.qvf'}}, 'change': 1}
{'jsonrpc': '2.0', 'id': 3, 'result': {'qReturn': {'qType': 'GenericObject', 'qHandle': 2, 'qGenericType': 'SheetList', 'qGenericId': '4b344780-a350-48db-8b65-27bb5a2c62b2'}}, 'change': 1}
{'jsonrpc': '2.0', 'id': 4, 'result': {'qLayout': {'qInfo': {'qId': '4b344780-a350-48db-8b65-27bb5a2c62b2', 'qType': 'SheetList'}, 'qMeta': {'privileges': ['read', 'update', 'delete', 'exportdata']}, 'qSelectionInfo': {}, 'qAppObjectList': {'qItems': [{'qInfo': {'qId': '8a0f6a01-ef89-4d65-821f-371c26208dcf', 'qType': 'sheet'}, 'qMeta': {'privileges': ['read', 'update', 'delete', 'exportdata'], 'title': 'Sheet_1', 'description': ''}, 'qData': {'rank': None, 'thumbnail': {'qStaticContentUrl': {}}, 'columns': 24, 'rows': 12, 'cells': [{'name': 'kfxNpV', 'type': 'auto-chart', 'col': 0, 'row': 0, 'colspan': 15, 'rowspan': 6, 'bounds': {'y': 0, 'x': 0, 'width': 62.5, 'height': 50}}, {'name': 'qHzmARQ', 'type': 'qlik-barplus-chart', 'col': 0, 'row': 6, 'colspan': 21, 'rowspan': 6, 'bounds': {'y': 50, 'x': 0, 'width': 87.5, 'height': 50}}, {'name': 'BXBQmw', 'type': 'auto-chart', 'col': 15, 'row': 0, 'colspan': 9, 'rowspan': 6, 'bounds': {'y': 0, 'x': 62.5, 'width': 37.5, 'height': 50}}], 'title': 'Sheet_1', 'description': ''}}]}}}}
kfxNpV
qHzmARQ
BXBQmw
我最初也在 Qlik community 上发布了这个问题,但没有得到回复。
对于这种情况,我通常从浏览器“观察”Qlik 的通信。
(在Chrome)
- 打开应用程序
- 打开浏览器开发工具(按 F12)
- 导航至“网络”(1)
- 导航到“WS”(2)
- 按下所需的套接字会话 (3)
- 按“消息”(4)
- 检查套接字send/received中的内容
(如果您在“网络”选项卡中看不到套接字,请刷新页面)
下面是为一个对象导出数据的 Javascript/Node 代码。 (在我的案例中,我对对象 ID 进行了硬编码)
const fs = require('fs');
const axios = require('axios');
const enigma = require('enigma.js');
const WebSocket = require('ws');
const schema = require('enigma.js/schemas/12.20.0.json');
const session = enigma.create({
schema,
url: 'ws://localhost:9076/app/engineData',
createSocket: url => new WebSocket(url)
});
(async function () {
// open new session
let global = await session.open();
// open the app
let doc = await global.openDoc("C:\Users\USERNAME\Documents\Qlik\Sense\Apps\Consumer_Sales.qvf");
// get the required object
let qObj = await doc.getObject("MEAjCJ");
// OOXML - export the data in Excel (xlsx) format
let data = await qObj.exportData("OOXML");
// generate the full download link
let downloadLink = `http://localhost:4848${data.qUrl}`;
// download and save the file
await axios.get(downloadLink, { responseType: "stream" })
.then(response => {
response.data.pipe(fs.createWriteStream("export.xlsx"));
});
})()
ExportData 方法的官方文档是 here
更新 - Python 代码片段
通过 sheet 个对象的循环看起来像这样:
id = 5
list_of_charts = result_json['result']['qLayout']['qAppObjectList']['qItems'][0]['qData']['cells']
for chart in list_of_charts:
obj_req = {
"jsonrpc": "2.0",
"method": "GetObject",
"handle": app_req_handle,
"params": [ chart["name"] ],
"outKey": -1,
"id": id
}
ws.send(json.dumps(obj_req))
result = ws.recv()
ws.send(json.dumps(obj_req))
result = ws.recv()
result_json = json.loads(result)
# print(result_json)
obj_req_handle = result_json['result']['qReturn']['qHandle']
export_req = {
"jsonrpc": "2.0",
"method": "ExportData",
"handle": obj_req_handle,
"params": [ "OOXML" ],
"outKey": -1,
"id": 6
}
ws.send(json.dumps(export_req))
result = ws.recv()
ws.send(json.dumps(export_req))
result = ws.recv()
result_json = json.loads(result)
downloadURL = "http://localhost:4848" + result_json["result"]["qUrl"]
r = requests.get(downloadURL, allow_redirects=True)
open('export_python_' + chart["name"] + '.xlsx', 'wb').write(r.content)
id += 1
我不确定 Python,但我发现通常最困难的部分是正确进行身份验证。像 JavaScript 那样的库通常会大大简化这一过程。我个人倾向于将 C# 用于此类小工具,其中 .NET SDK 提供运算符来为您完成管道工作。这是使用这两个 nuget 包的示例:
- https://www.nuget.org/packages/QlikSense.NetSDK/
- https://www.nuget.org/packages/QlikSenseRestClient/
此代码会在应用程序的第一个 sheet 上下载所有可视化的 xlsx 文件。
var location = Location.FromUri(uri);
location.AsNtlmUserViaProxy();
var restClient = new RestClient(uri);
restClient.AsNtlmUserViaProxy();
using (var app = location.App(new AppIdentifier {AppId = appId}))
{
var theSheet = app.GetSheets().First();
var objs = theSheet.GetChildInfos().Select(info => app.GetGenericObject(info.Id));
foreach (var o in objs)
{
var exportResult = o.ExportData(NxExportFileType.EXPORT_OOXML);
var data = restClient.GetBytes(exportResult.Url);
using (var writer = new BinaryWriter(new FileStream(o.Id + ".xlsx", FileMode.OpenOrCreate)))
{
writer.Write(data);
}
}
}