Рубрики
Без рубрики

Враждебное извлечение данных Tableau Server

Я стараюсь изо всех сил не ненавидеть в Табличном. Это было сочетание программного обеспечения Po … Теги с табличкой, Python, Restapis.

Я стараюсь изо всех сил не ненавидеть в Табличном. Это была сочетание программного обеспечения власти и простота использования, которая проехала меня на покупку лицензии в первую очередь. С тех пор я нахожу новые и захватывающие пути в таблице намеренно блокируют пользователей из своих данных.

Я дал Client Tableau Server Client Библиотека Python Спилость в последнее время в надежде найти что-то полезное. Я решил (вздох, еще раз ) Разрешить таки в пользу сомнения: после толкания Четыре обновления за один месяц Может, все изменилось. Напротив, бизнес-стратегия Clabeau означает сильную: бушую, пылающуюся турную кучу. Идеальный пример этого – Вид Объект Tableau позволяет вам взаимодействовать на вашем сервере. Эти знакомые знают, что Виды сленг за Листы рабочие тетради хранится на сервере Tableau.

Подключение к экземпляру Tableau через Python для получения объектов вашего просмотра – это кусок пирога:

import tableauserverclient as TSC
tableau_auth = TSC.TableauAuth('username', 'password')
server = TSC.Server('http://servername')

with server.auth.sign_in(tableau_auth):
  all_views, pagination_item = server.views.get()
  print([view.name for view in all_views])

Этот простой фрагмент перечисляет все объект View на вашем сервере. Ух ты! Подумайте о том, что мы можем сделать со всеми, что табличные данные, которые мы так много работали, чтобы преобразовать, Rig- НЕПРАВИЛЬНО . Посмотрите, что на самом деле содержит объект Picton ‘View «View»

  • ID Идентификатор элемента просмотра.
  • Имя Название вида.
  • suder_id Идентификатор для владельца вида.
  • Preview_image Изображение миниатюры для вида.
  • total_views Статистика использования для вида. Указывает на общее количество раз просмотра доступно.
  • Workbook_id ID рабочей книги, связанной со всем видом.

Святой Моисей остановит прессы, мы можем получить миниатюрное изображение Из наших данных?! Спасибо, щедрые заметен!

Обратите внимание, как нет упоминания, вы знаете, фактические данные Отказ

Мы собираемся играть в игру. После того, как мое время было потрачено впустую, я чувствую, что теплое церковное чувство, которое, кажется, говорят «Ссыпно демонтировать амбиции заведения!» Могу ли я напомнить вам, мы говорим о том, что создание, которое счета на лицензии на клиента на основе Количество процессоров, используемых их серверной инфраструктурой. Это эффективно распознает ужасающую и неэффективную кодовую базу за заплетенным сервером Tableau и используя этот недостаток для монетизации. Да, вы платите больше денег, чтобы стимулировать худшие практики.

Давайте сделаем приложение для колбы. Злой.

В нашем последнем посте я поделился Маленький скрипт, чтобы помочь вам начать красть данных С вашего собственного сервера Tableau. Это не совсем царапает мой зуд больше. Я собираюсь построить интерфейс. Я хочу облегчить максимально возможным для системно ROB Tableau Server из каждой копейки. Это много копейки, когда мы рассмотрим уравнение: Данные + новые Отказ

Перед тем, как я понравился вам, вот быстрая демонстрация MVP, у нас есть:

Каждая таблица – это вид, вытянутый из сервера Tableau.

Этот POC демонстрирует, что это Очень Можно автоматизировать извлечение видов накидки из сервера Tableau. Успех Сообщение сигнализирует о том, что мы успешно сделали представление Tableau и создал соответствующую таблицу во внешней базе данных . Любые данные, которые мы манипулируем в Tableau, теперь действительно наше: мы можем использовать трансформации, которые мы применяли в рабочих тетрах, используем эти данные в других приложениях и используйте планировщик экстракта для хранения данных. Мы превратили инструмент BI в инструмент ETL. Другими словами, вы можете добраться до тех миниатюрных превью и засунуть его.

Я буду открытыми всеми, что все это, как и мой гражданский долг. Давайте быть понятным для предприятий: удержание свободы на собственные данные – это война. Модели цен, которые вознаграждают плохое мастерство, являются оскорблением нашего интеллекта. Для каждого высокомерного злодеяния, совершенное против потребителей, война будет заработать в два раза усердно. Я, вероятно, должен упомянуть, что эти мнения мои собственные.

Пролетариат ударяет назад

Почувствуйте, где мы направляемся с обязательным деревом проекта-файловой структуры:

tableau-exporter
├── application
│ ├── __init__.py
│ ├── database.py
│ ├── tableau.py
│ ├── routes.py
│ ├── static
│ │ ├── data
│ │ │ └── view.csv
│ │ ├── dist
│ │ │ ├── all.css
│ │ │ ├── packed.js
│ │ ├── img
│ │ │ └── tableaugithub.jpg
│ │ ├── js
│ │ │ └── main.js
│ │ └── scss
│ │ └── main.scss
│ └── templates
│ ├── export.html
│ ├── index.html
│ ├── layout.html
│ └── view.html
├── config.ini
├── config.py
├── app.yaml
├── start.sh
├── wsgi.py
├── Pipfile
├── README.md
└── requirements.txt

Как обычно, мы используем классическую колбу Завод приложений Настройте здесь.

Оружие выбора

Давайте посмотрим на наш основной арсенал:

  • Запросы : Мы достигаем нашей цели, эксплуатируя некоторые лазейки, выставленные в API Tableau Read.
  • панда : Справит все от извлечения разделенных запятую данные в CSV, рендеринг HTML-таблиц и вывод SQL.
  • FLASK_SQLALCHEMY : Используется в тандеме с Пандас Чтобы обработать доставку наших данных в других местах.
  • Flak_redis : Обрабатывать переменные сеанса.

Инициирование нашего применения

Вот как мы построим наше приложение:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_redis import FlaskRedis

# Set global entities
db = SQLAlchemy()
r = FlaskRedis()

def create_app():
    """Construct the core application."""
    app = Flask( __name__ , instance_relative_config=False)
    app.config.from_object('config.Config')

    with app.app_context():
        # Initiate globals
        db.init_app(app)
        r.init_app(app, charset="utf-8", decode_responses=True)

        # Set global contexts
        r.set('uri', app.config['SQLALCHEMY_DATABASE_URI'])
        r.set('baseurl', app.config['BASE_URL'])
        r.set('username', app.config['USERNAME'])
        r.set('password', app.config['PASSWORD'])

        # Import our modules
        from . import routes
        from . import tableau
        app.register_blueprint(routes.home_blueprint)

        return app

Это должно чувствовать себя как обычно. Ядро нашего применения разделено между Маршруты .py , который обрабатывает виды, а Tablea.py , который обрабатывает логику антистановления. Давайте начнем с последнего.

Жизнь, свобода и стремление к больным питаниям

Наш хороший друг Tablea.py может выглядеть знакомым тем, кто присоединился к нам в последний раз Отказ Tablea.py С тех пор был занят ударом в тренажерный зал с тех пор и выглядит резким для примычки:

import requests
import xml.etree.ElementTree as ET
from . import r
import pandas as pd
import io

class ExtractTableauView:
    """Class for working in a Tableau instance."""

    __baseurl = r.get('baseurl')
    __username = r.get('username')
    __password = r.get('password')
    __database = r.get('uri')
    __contenturl = r.get('contenturl')

    @classmethod
    def get_view(cls, site, xml, view, token):
        """Extract contents of a single view."""
        headers = {'X-Tableau-Auth': token,
                   'Content-Type': 'text/csv'
                   }
        req = requests.get(cls.__baseurl + '/api/3.2/sites/' + str(site) +'/views/' + str(view) + '/data', headers=headers, stream=True)
        csv_text = req.text
        view_df = pd.read_csv(io.StringIO(csv_text), header=0)
        return view_df

    @classmethod
    def list_views(cls, site, xml, token):
        """List all views belonging to a Tableau Site."""
        headers = {'X-Tableau-Auth': token}
        req = requests.get(cls.__baseurl + '/api/3.2/sites/' + site + '/views', auth=(cls.__username, cls.__password), headers=headers)
        root = ET.fromstring(req.content)
        views_arr = []
        for child in root.iter('*'):
            if child.tag == '{http://tableau.com/api}views':
                for view in child:
                    view_dict = {
                        'name': view.attrib.get('name'),
                        'id': view.attrib.get('id'),
                        'url': cls.__baseurl + '/' + view.attrib.get('contentUrl'),
                        'created': view.attrib.get('createdAt'),
                        'updated': view.attrib.get('updatedAt')
                    }
                    views_arr.append(view_dict)
        return views_arr

    @classmethod
    def get_token(cls, xml):
        """Receive Auth token to perform API requests."""
        for child in xml.iter('*'):
            if child.tag == '{http://tableau.com/api}credentials':
                token = child.attrib.get('token')
                return token

    @classmethod
    def get_site(cls, xml):
        """Retrieve ID of Tableau 'site' instance."""
        root = xml
        for child in root.iter('*'):
            if child.tag == '{http://tableau.com/api}site':
                site = child.attrib.get('id')
                return site

    @classmethod
    def initialize_tableau_request(cls):
        """Retrieve core XML for interacting with Tableau."""
        headers = {'Content-Type': 'application/xml'}
        body = ''
        req = requests.post(cls.__baseurl + '/api/3.2/auth/signin', auth=(cls.__username, cls.__password), headers=headers, data=body)
        root = ET.fromstring(req.content)
        return root

Хотел бы я принять полный кредит на то, что показать дерьмо, этот класс, кажется, на первый взгляд, но я уверяю вас, что мы остались без выбора. Например: я упомянул, что API Tablea’s Read API возвращает XML настолько благодарен, что он разбивает анализаторы XML? Я не могу сказать некомпетентность от злоумышленника на данный момент.

Вот расщепление метода нашего класса:

  • Initialize_tableau_request () : Обрабатывает первоначальный auth и возвращает ценную информацию, такую как идентификатор сайта и токен API, который будет использоваться после этого.
  • get_site () : Извлекивает идентификатор сайта из XML, возвращаемый вышеупомянутым.
  • get_token () : Аналогичным образом извлекает наш токен.
  • list_views () : Компилируется список всех видов на сайте Tableau, что дает нам возможность выбрать для добычи.
  • get_view () : Делает вид на наш выбор и создает dataframe, который должен быть отправлен в иностранную базу данных.

Наша логика маршрутизации

Переезд на у нас есть Маршруты .py Создание просмотров и связанной с ними логика для нашего приложения:

from flask import current_app as app
from flask import render_template, Blueprint, request, Markup
from flask_assets import Bundle, Environment
from . import tableau
from . import database
import pandas as pd

home_blueprint = Blueprint('home', __name__ , template_folder='templates', static_folder='static')

assets = Environment(app)
js = Bundle('js/*.js', filters='jsmin', output='dist/packed.js')
scss = Bundle('scss/*.scss', filters='libsass', output='dist/all.css')
assets.register('scss_all', scss)
assets.register('js_all', js)
scss.build()
js.build()

@home_blueprint.route('/', methods=['GET', 'POST'])
def entry():
    """Homepage which lists all available views."""
    tableau_view_extractor = tableau.ExtractTableauView()
    xml = tableau_view_extractor.initialize_tableau_request()
    token = tableau_view_extractor.get_token(xml)
    site = tableau_view_extractor.get_site(xml)
    views = tableau_view_extractor.list_views(site, xml, token)
    return render_template(
        'index.html',
        title="Here are your views.",
        template="home-template",
        views=views,
        token=token,
        xml=xml,
        site=site
    )

@home_blueprint.route('/view', methods=['GET', 'POST'])
def view():
    """Displays a preview of a selected view."""
    site = request.args.get('site')
    xml = request.args.get('xml')
    view = request.args.get('view')
    token = request.args.get('token')
    tableau_view_extractor = tableau.ExtractTableauView()
    view_df = tableau_view_extractor.get_view(site, xml, view, token)
    view_df.to_csv('application/static/data/view.csv')
    return render_template(
        'view.html',
        title='Your View',
        template="home-template",
        view=view,
        token=token,
        xml=xml,
        site=site,
        view_df=Markup(view_df.to_html(index=False))
    )

@home_blueprint.route('/export', methods=['GET', 'POST'])
def export():
    """Exports view to external database."""
    view_df = pd.read_csv('application/static/data/view.csv')
    view_df.to_sql(name='temp', con=database.engine, if_exists='replace', chunksize=50, index=True)
    return render_template(
        'export.html',
        title='Success!',
        template="success-template",
    )

У нас есть только 3 страницы для нашего приложения. Они включают в себя наш список представлений, предварительный просмотр единого просмотра и страницы успеха для при условии, когда указано просмотр. Это вся основная логика колба.

Положить его на дисплей

Мы создаем наши страницы динамически основанными на значениях, которые мы передаем наши шаблоны Jinja. Домашняя страница использует некоторые вложенные петли для перечисления видов, которые мы вернулись из Tablea.py , а также использует строки запроса для прохождения значений на другие шаблоны.

{% extends "layout.html" %}

{% block content %}
{% endblock %}

Движение дальше: наш скромный View.html Страница имеет два целя: отобразить выбранный вид и экспортировать его во имя справедливости.

{% extends "layout.html" %}

{% block content %}

{{title}}

{{view_df}}
{% endblock %}

Война не закончилась

Этот репозиторий открыт для общественности и можно найти здесь Отказ Впереди нас все еще осталось крестовые походы: например, создавая этот интерфейс, чтобы принять учетные данные с помощью входа в систему, в отличие от файла конфигурации, а также планирование экспорта просмотра, а не по требованию.

Откуда мы идем отсюда, зависит от того, что мы решаем люди. Для всего, что я знаю, я мог бы кричать в пустую комнату здесь (я почти положительный, кто-нибудь, кто платит за пределы корпоративного программного обеспечения предпочитает слепой глаз отрицания). Если противоположное направится, я смею сказать, что революция только начинается.

Оригинал: “https://dev.to/hackersandslackers/the-hostile-extraction-of-tableau-server-data-2nl0”