<?xml version="1.0" encoding="utf-8"?> 
<rss version="2.0">

<channel>

<title>Блог об аналитике, визуализации данных, data science и BI, заметки с тегом: AWS</title>
<link>http://test.leftjoin.ru/tags/aws/</link>
<description></description>
<generator>E2 (v3365; Aegea)</generator>

<item>
<title>How-to: модель GPT-2 для получения логических выводов с помощью Amazon SageMaker</title>
<guid isPermaLink="false">141</guid>
<link>http://test.leftjoin.ru/all/how-to-model-gpt-2/</link>
<comments>http://test.leftjoin.ru/all/how-to-model-gpt-2/</comments>
<description>
&lt;p&gt;Не так давно, на конференции Linq мы представили &lt;a href="http://whatmuskwouldtweet.com/"&gt;&lt;span style="color:#0071a1" class="has-inline-color"&gt;генератор твитов в стиле Илона Маска&lt;/span&gt;&lt;/a&gt;. Для его создания мы взяли готовую модель GPT-2 Medium и дообучили/стилизовали ее на своем датасете. По-английски такой процесс называется &lt;i&gt;fine-tuning&lt;/i&gt;. Модель требует достаточное количество ресурсов для этого, так что не каждый может это сделать локально на своем ПК. Однако, вопрос решается, если использовать, например, Google Colab. Мы же дообучали модель на платформе Kaggle.&lt;/p&gt;
&lt;p&gt;Но после этого появляется новая задача — модель нужно развернуть, чтобы создать полноценный сервис. Конечно, существует множество различных решений. Ранее, при создании &lt;a href="https://habr.com/ru/post/596035/"&gt;&lt;span style="color:#0071a1" class="has-inline-color"&gt;генератора телеграм-постов в стиле Артемия Лебедева&lt;/span&gt;&lt;/a&gt; мы обращались к сервису Yandex DataSphere, а после даже написали небольшой &lt;a href="http://test.leftjoin.ru/all/how-to-yandex-datasphere/"&gt;&lt;span style="color:#0071a1" class="has-inline-color"&gt;гайд&lt;/span&gt;&lt;/a&gt; о том, как там развернуть модель. Теперь же мы расскажем о развертывании модели GPT-2 для получения логических выводов в режиме реального времени (Real-time inference) с помощью Amazon SageMaker.&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Заходим в аккаунт AWS.&lt;/li&gt;
&lt;/ol&gt;
&lt;ol start="2"&gt;
&lt;li&gt;Так как мы хотим задеплоить стилизованную модель, нам необходимо загрузить в облако необходимые файлы. В поиске вбиваем S3, выбираем первый сервис.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(1).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="3"&gt;
&lt;li&gt;Нажимаем &lt;b&gt;Create bucket&lt;/b&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(2).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="4"&gt;
&lt;li&gt;Далее проводим конфигурацию бакета: вводим его имя, выбираем регион, настраиваем права доступа и т. д. Для простоты достаточно ввести данные только в графе &lt;b&gt;General configuration&lt;/b&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(3).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="5"&gt;
&lt;li&gt;После конфигурирования нажимаем &lt;b&gt;Create bucket&lt;/b&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(4).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="6"&gt;
&lt;li&gt;Затем нас перебросит на страницу Amazon S3, где можно будет увидеть что-то подобное:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(5).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="7"&gt;
&lt;li&gt;Далее надо загрузить сами файлы модели. Нажимаем на имя созданного бакета и в открывшемся окне нажимаем &lt;b&gt;Upload&lt;/b&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(6).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="8"&gt;
&lt;li&gt;Перетаскиваем архив с файлами модели в нужную область или нажимаем &lt;b&gt;Add files&lt;/b&gt;. При необходимости можно выстроить иерархию внутри бакета путем создания папок с помощью &lt;b&gt;Add folders&lt;/b&gt;. Здесь важно отметить, что файлы модели должны быть в архиве с расширением &lt;b&gt;tar.gz&lt;/b&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(7).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="9"&gt;
&lt;li&gt;Нажимаем &lt;b&gt;Upload&lt;/b&gt; и ждем завершение загрузки.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(8).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="10"&gt;
&lt;li&gt;После успешной загрузки архива, перейдем непосредственно к деплою модели. В поиске вбиваем SageMaker, выбираем первый сервис.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(9).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="11"&gt;
&lt;li&gt;Для работы с с этим сервисом необходимо предварительно настроить &lt;a href="https://docs.aws.amazon.com/sagemaker/latest/dg/gs-studio-onboard.html"&gt;&lt;span style="color:#0071a1" class="has-inline-color"&gt;SageMaker Domain&lt;/span&gt;&lt;/a&gt;, для этого нажимаем &lt;b&gt;Get Started&lt;/b&gt; на баннере &lt;b&gt;New to Sagemaker?&lt;/b&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(10).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="12"&gt;
&lt;li&gt;Для простой конфигурации 1 пользователя выбираем &lt;b&gt;Quick setup&lt;/b&gt; и нажимаем &lt;b&gt;Set up SageMaker Domain&lt;/b&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(11).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="13"&gt;
&lt;li&gt;Заполняем имя пользователя и настраиваем роль для исполнения. Для этого можем создать новую роль и указать в ней то, к каким бакетам S3 у пользователя будет доступ. Для простоты дадим доступ ко всем бакетам.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(12).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="14"&gt;
&lt;li&gt;Нажимаем &lt;b&gt;Submit&lt;/b&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(13).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="15"&gt;
&lt;li&gt;Придется немного подождать, пока SageMaker Domain и пользователь будут сконфигурированы.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(14).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="16"&gt;
&lt;li&gt;После завершения настройки, среди пользователей появится созданный нами и можно будет запустить &lt;b&gt;Studio&lt;/b&gt;, нажав на &lt;b&gt;Launch app&lt;/b&gt;. &lt;a href="https://aws.amazon.com/ru/sagemaker/studio/"&gt;&lt;span style="color:#0071a1" class="has-inline-color"&gt;SageMaker Studio&lt;/span&gt;&lt;/a&gt; — IDE, позволяющая работать работать с Jupyter  ноутбуками в облаке AWS.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(15).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="17"&gt;
&lt;li&gt;Тут тоже придется немного подождать.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(16).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="18"&gt;
&lt;li&gt;Наконец, мы попадем в SageMaker Studio. Переключаясь между вкладками с помощью панели слева, можно:&lt;br /&gt;
• Просмотреть рабочий репозиторий, где будут храниться ноутбуки и прочие файлы;&lt;br /&gt;
• Просмотреть запущенные инстансы и приложения, Kernel и Terminal Sessions;&lt;br /&gt;
• Работать с Git репозиторием;&lt;br /&gt;
• Управлять ресурсами SageMaker;&lt;br /&gt;
• Устанавливать разрешения для Jupyter ноутбуков.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(17).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="19"&gt;
&lt;li&gt;Отдельно выделим &lt;a href="https://docs.aws.amazon.com/sagemaker/latest/dg/studio-jumpstart.html"&gt;&lt;span style="color:#0071a1" class="has-inline-color"&gt;SageMaker JumpStart&lt;/span&gt;&lt;/a&gt;. Этот сервис предлагает предварительно обученные модели с открытым исходным кодом для широкого спектра задач. Вы можете обучить и настроить эти модели перед тем как развернуть их. JumpStart также предоставляет шаблоны решений для настройки инфраструктуры для распространенных случаев использования и исполняемые ноутбуки для машинного обучения с помощью SageMaker.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(18).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="20"&gt;
&lt;li&gt;Несмотря на наличие готовых решений, для деплоя нашей fine-tuned модели GPT-2 мы создадим новый ноутбук, где пропишем все, что нам нужно. Для этого нажмем на + в голубом прямоугольнике сверху слева. Откроется вкладка &lt;b&gt;Launcher&lt;/b&gt;, пролистаем вниз до секции &lt;b&gt;Notebooks and compute resources&lt;/b&gt; и выберем там &lt;b&gt;Notebook Python 3&lt;/b&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(19).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="21"&gt;
&lt;li&gt;Придется немного подождать, прежде чем ядро ноутбука будет готово к работе.&lt;/li&gt;
&lt;/ol&gt;
&lt;ol start="22"&gt;
&lt;li&gt;Наконец, можно писать код.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(20).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="23"&gt;
&lt;li&gt;Отдельно стоит отметить, что можно выбрать инстанс, на котором будет выполняться ноутбук. Например, если для вашей модели нужно больше ресурсов, вы запросто сможете переключиться. Но стоит помнить, что и платить придется соответственно.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(21).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="24"&gt;
&lt;li&gt;Во время работы с ноутбуком вы &lt;a href="https://aws.amazon.com/ru/sagemaker/pricing/"&gt;&lt;span style="color:#0071a1" class="has-inline-color"&gt;платите&lt;/span&gt;&lt;/a&gt; за время его использования с учетом типа выбранного инстанса.&lt;/li&gt;
&lt;/ol&gt;
&lt;ol start="25"&gt;
&lt;li&gt;Для простого деплоя нашей модели можем воспользоваться &lt;a href="https://huggingface.co/gpt2-medium"&gt;&lt;span style="color:#0071a1" class="has-inline-color"&gt;готовой конфигурацией от Hugging Face&lt;/span&gt;&lt;/a&gt;. Нажимаем на кнопку Deploy, выбираем там Amazon SageMaker, выбираем задачу (в нашем случае это Text Generation) и конфигурацию (в нашем случае это AWS), копируем код в наш ноутбук.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(22).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="26"&gt;
&lt;li&gt;Так как мы используем свою дообученную модель, а не готовую из репозитория Hugging Face, нам надо сделать небольшие изменения в коде. Комментируем в словаре &lt;b&gt;hub&lt;/b&gt; строку с ключом &lt;b&gt;‘HF_MODEL_ID’&lt;/b&gt; и в конструкторе &lt;b&gt;HuggingFaceModel&lt;/b&gt; добавляем ключ &lt;b&gt;model_data&lt;/b&gt;, куда пишем путь до нашего архива с файлами модели:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;# Hub Model configuration. https://huggingface.co/models
hub = {
	# 'HF_MODEL_ID':'gpt2-medium',
	'HF_TASK':'text-generation'
}
 
# create Hugging Face Model Class
huggingface_model = HuggingFaceModel(
	transformers_version='4.17.0',
	pytorch_version='1.10.2',
	py_version='py38',
	env=hub,
	role=role, 
      model_data='s3://my-bucket-for-gpt2/gpt2-medium-musk.tar.gz',
)&lt;/code&gt;&lt;/pre&gt;&lt;ol start="27"&gt;
&lt;li&gt;В методе &lt;b&gt;deploy&lt;/b&gt; объекта &lt;b&gt;huggingface_model&lt;/b&gt; мы можем выбрать, на каком инстансе произойдет развертывание нашей модели, указав его в параметре &lt;b&gt;instance_type&lt;/b&gt;. Большинство инстансов может быть недоступно в связи с отсутствием нужных квот и их придется запрашивать в поддержке AWS. В этом случае вы увидите подобную ошибку:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(23).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="28"&gt;
&lt;li&gt;Если модель была успешно создана и развернута (для этого придется немного подождать), то можно вызвать метод &lt;b&gt;predict&lt;/b&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(24).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="29"&gt;
&lt;li&gt;Для того, чтобы обращаться к инстансу извне AWS, придется создать Access key.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;В поиске вбиваем IAM, выбираем первый сервис.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(25).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;В открывшемся окне выбираем вкладку &lt;b&gt;User&lt;/b&gt; и нажимаем на имя пользователя, под которым мы работаем.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(26).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Переходим на вкладку &lt;b&gt;Security credentials&lt;/b&gt; и нажимаем &lt;b&gt;Create access key&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(27).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Копируем &lt;b&gt;Access key ID&lt;/b&gt; и &lt;b&gt;Secret access key&lt;/b&gt; и сохраняем их в надежном месте.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="30"&gt;
&lt;li&gt;Далее нужно узнать имя созданного эндпоинта с моделью. В студии на левой панели выбираем вкладку &lt;b&gt;SageMaker resources&lt;/b&gt;, выбираем ресурс &lt;b&gt;Endpoints&lt;/b&gt; и дважды кликаем по имени нашего эндпоинта. Откроется вкладка с деталями, откуда мы сможем скопировать его имя.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(28).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="31"&gt;
&lt;li&gt;Теперь напишем код для обращения к модели извне.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;import boto3
import json
import time
 
endpoint_name = '&amp;lt;my_endpoint_name&amp;gt;'
aws_access_key_id = '&amp;lt;my_aws_access_key_id&amp;gt;'
aws_secret_access_key = '&amp;lt;my_aws_secret_access_key&amp;gt;'
 
sagemaker_runtime = boto3.client(
    &amp;quot;sagemaker-runtime&amp;quot;, 
    region_name='us-east-1',
    aws_access_key_id=aws_access_key_id, 
    aws_secret_access_key=aws_secret_access_key
)
 
data = {
    &amp;quot;inputs&amp;quot;: &amp;quot;Weed is&amp;quot;,
}
 
response = sagemaker_runtime.invoke_endpoint(
    EndpointName=endpoint_name, 
    ContentType='application/json',
    Body=json.dumps(data, ensure_ascii=False).encode('utf8')
)
 
print(response['Body'].read().decode('utf-8'))&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;И протестируем:&lt;/p&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(29).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="32"&gt;
&lt;li&gt;Стоит отметить, что если следовать описанным выше шагам, то модель будет использовать для генерации параметры по умолчанию. Чтобы добавить кастомную логику загрузки модели, пред- и постобработки данных, предсказания, можно создать файл &lt;b&gt;inference.py&lt;/b&gt; в студии рядом с вашим ноутбуком и там переопределить нужные вам методы. Подробнее о них можно почитать &lt;a href="https://huggingface.co/docs/sagemaker/inference"&gt;&lt;span style="color:#0071a1" class="has-inline-color"&gt;тут&lt;/span&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(30).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Чтобы этот скрипт использовался при развертывании модели, в конструкторе HuggingFaceModel нужно добавить еще один параметр:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;huggingface_model = HuggingFaceModel(
	transformers_version='4.17.0',
	pytorch_version='1.10.2',
	py_version='py38',
	env=hub,
	role=role, 
      model_data='s3://my-bucket-for-gpt2/gpt2-medium-musk.tar.gz',
      entry_point='inference.py'
)&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;Разумеется, для уже созданных эндпоинтов такое изменение не будет учтено. Нужно будет заново задеплоить модель.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;Приведем пример файла &lt;b&gt;inference.py&lt;/b&gt;, который можно использовать для модели GPT-2:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;import json
import torch
from transformers import GPT2Config, GPT2Tokenizer, GPT2LMHeadModel
 
def model_fn(model_dir):
    configuration = GPT2Config.from_pretrained(model_dir, output_hidden_states=False)
    tokenizer = GPT2Tokenizer.from_pretrained(
        model_dir,
        bos_token='&amp;lt;|sos|&amp;gt;', 
        eos_token='&amp;lt;|eos|&amp;gt;', 
        pad_token='&amp;lt;|pad|&amp;gt;'
    )
    model = GPT2LMHeadModel.from_pretrained(model_dir, config=configuration)
    model.resize_token_embeddings(len(tokenizer))
    model.eval()
    return (model, tokenizer)
 
def input_fn(request_body, request_content_type):
    if request_content_type == &amp;quot;application/json&amp;quot;:
        request = json.loads(request_body)
    else:
        request = request_body
    return request
 
def predict_fn(data, model_tokenizer):
    model, tokenizer = model_tokenizer
 
    inputs = data.pop(&amp;quot;inputs&amp;quot;, &amp;quot;&amp;quot;)
    max_length = data.pop(&amp;quot;max_length&amp;quot;, 50)
 
    input_ids = torch.tensor(tokenizer.encode(f'&amp;lt;|sos|&amp;gt;{inputs}')).unsqueeze(0)
    outputs = model.generate(
                input_ids, 
                max_length=max_length,
                bos_token_id=tokenizer.bos_token_id,
                pad_token_id=tokenizer.pad_token_id,
                eos_token_id=tokenizer.eos_token_id, 
                do_sample=True,
                top_k=0,
                top_p=0.95,
                no_repeat_ngram_size=4
    )
    decoded_output = tokenizer.decode(outputs[0])
 
    return {&amp;quot;decoded_output&amp;quot;: decoded_output}&lt;/code&gt;&lt;/pre&gt;&lt;ol start="33"&gt;
&lt;li&gt;В конце работы с ноутбуком в студии &lt;a href="https://docs.aws.amazon.com/sagemaker/latest/dg/notebooks-run-and-manage-shut-down.html"&gt;&lt;span style="color:#0071a1" class="has-inline-color"&gt;нужно будет обязательно вырубить все используемые для этого ресурсы&lt;/span&gt;&lt;/a&gt;. К сожалению, при простом закрытии вкладки со студией, ресурсы не освобождаются, поэтому приходится это делать самостоятельно. В противном случае, с вас будет списываться плата за их использование. Итак, вырубить все ненужное можно в самой студии, выбрав на панели слева вкладку &lt;b&gt;Running Terminal and Kernels&lt;/b&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker2.png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;После закрытия ноутбука проверить то, что все ресурсы освобождены, можно на странице Amazon SageMaker. Для этого нужно будет нажать на имя пользователя и посмотреть на статус вашего приложения, тип которого &lt;b&gt;KernelGateway&lt;/b&gt;. Статус должен быть &lt;b&gt;Deleted&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(32).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ol start="34"&gt;
&lt;li&gt;После того, как вы перестанете нуждаться в развернутой модели, нужно будет удалить эндпоинт. Если вы не освободили ресурсы, используемые ноутбуком в студии, то это можно будет сделать прямо оттуда, прописав строку:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;predictor.delete_endpoint()&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;Иначе вы можете удалить эндпоинт, перейдя на страницу сервиса Amazon SageMaker. Там на левой панели нужно будет выбрать вкладку &lt;b&gt;Inference&lt;/b&gt;, в выпадающем списке нажать &lt;b&gt;Endpoints&lt;/b&gt;, затем справа выбрать нужный эндпоинт, нажать &lt;b&gt;Actions&lt;/b&gt; и &lt;b&gt;Delete&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="http://test.leftjoin.ru/pictures/GPT-2-AWS-SageMaker-(33).png"  border="0" width="100%" height="100%"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Также можно будет удалить созданные модели, перейдя в &lt;b&gt;Inference→Models&lt;/b&gt;, и конфигурации эндпоинтов, перейдя в &lt;b&gt;Inference→Enpoint Configurations&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Итак, мы рассказали о том, как развертывать стилизованную модель GPT-2 для получения логических выводов в режиме реального времени (Real-time inference) с помощью Amazon SageMaker. Стоит отметить, что существует несколько вариантов развертывания, каждый из которых имеет свои особенности, например, асинхронность, пакетная обработка, наличие холодного старта, т.д. Использование того или иного варианта зависит от поставленных требований.&lt;/p&gt;
&lt;p&gt;Подробнее про другие механизмы деплоя с помощью Amazon SageMaker читайте &lt;a href="https://docs.aws.amazon.com/sagemaker/latest/dg/deploy-model.html"&gt;&lt;span style="color:#0071a1" class="has-inline-color"&gt;тут&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
</description>
<pubDate>Fri, 28 Oct 2022 10:48:49 +0300</pubDate>
</item>

<item>
<title>Деплой дашборда на виртуальной машине Amazon EC2</title>
<guid isPermaLink="false">107</guid>
<link>http://test.leftjoin.ru/all/deploy-dashboard-ec2/</link>
<comments>http://test.leftjoin.ru/all/deploy-dashboard-ec2/</comments>
<description>
&lt;p&gt;Мы уже рассказывали о том, как развернуть дашборд с помощью сервиса Elastic Beanstalk от Amazon Web Services. В этом материале расскажем как развертывать дашборды на виртуальной машине Amazon EC2.&lt;/p&gt;
&lt;h2&gt;Подготовка&lt;/h2&gt;
&lt;p&gt;Начало работы с платформой AWS и создание сервера мы описали в материале &lt;a href="http://test.leftjoin.ru/all/stavim-clickhouse-na-aws/"&gt;Устанавливаем Clickhouse на AWS&lt;/a&gt;. Проект дашборда был подготовлен в предыдущей заметке &lt;a href="http://test.leftjoin.ru/all/dashboard-python-2/"&gt;Деплой дашборда на AWS Elastic Beanstalk&lt;/a&gt;. Все файлы можно скачать из нашего репозитория на &lt;a href="https://github.com/valiotti/leftjoin/tree/master/dashboard_deployment"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Работа с терминалом&lt;/h2&gt;
&lt;p&gt;Подключитесь к вашему серверу на EC2 через терминал, используя SSH-ключ.&lt;br /&gt;
Из домашней директории копируем архив с необходимыми файлами на сервер командой scp:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;scp -i /home/user/.ssh/ssh_key.pem /home/user/brewery_dashboard.zip ubuntu@api.sample.ru:/home/ubuntu/&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Распаковываем архив с помощью команды &lt;span class="inline-code"&gt;unzip&lt;/span&gt;, указав директорию:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;unzip -d /home/ubuntu/brewery_dashboard brewery_dashboard.zip&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;После этого в каталоге появится папка &lt;span class="inline-code"&gt;/brewery_dashboard/&lt;/span&gt;, в которой среди прочих будет текстовый файл &lt;span class="inline-code"&gt;requirements.txt&lt;/span&gt;. В нем находятся все библиотеки Python, которые нужны для корректной работы дашборда. Устанавливаем их следующей командой:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;pip install -r requirements.txt&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Запускаем дашборд&lt;/h2&gt;
&lt;p&gt;Создаем сервисный файл &lt;span class="inline-code"&gt;brewery.service&lt;/span&gt; в системной папке &lt;span class="inline-code"&gt;/etc/systemd/system&lt;/span&gt;:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;sudo touch brewery.service&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;В нем прописываем всю необходимую информацию для деплоя нашего дашборда. Текстовый редактор вызывается следующей командой:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;sudo nano brewery.service&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;В WorkingDirectory указываем папку, в которой находятся файлы проекта, а в ExecStart команду для запуска:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;[Unit]
Description=Brewery Dashboard
After=network.target

[Service]
User=ubuntu
Group=www-data
WorkingDirectory=/home/ubuntu/brewery_dashboard/
ExecStart=/usr/bin/gunicorn3 --workers 3 --bind 0.0.0.0:8083 application:application&lt;/code&gt;&lt;/pre&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/--2021-04-29--14.21.41.png" width="669" height="130" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Запускаем &lt;span class="inline-code"&gt;brewery.service&lt;/span&gt; следующей командой:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;sudo systemctl start brewery.service&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;И проверяем успешность запуска:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;sudo systemctl status brewery.service&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Система должна ответить, что все хорошо:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/--2021-04-29--14.24.57.png" width="991" height="257" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Теперь дашборд доступен по публичному адресу сервера с указанием порта . Можно открыть его в браузере или вставить на любой сайт с помощью тега &amp;lt;iframe&gt;:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;&amp;lt;ifrаme id='igraph' scrolling='no' style='border:none;'seamless='seamless' src='http://54.227.137.142:8083/' height='1100' width='800'&amp;gt;&amp;lt;/ifrаme&amp;gt;&lt;/code&gt;&lt;/pre&gt;</description>
<pubDate>Fri, 30 Apr 2021 14:33:38 +0300</pubDate>
</item>

<item>
<title>Деплой дашборда на AWS Elastic Beanstalk</title>
<guid isPermaLink="false">53</guid>
<link>http://test.leftjoin.ru/all/dashboard-python-2/</link>
<comments>http://test.leftjoin.ru/all/dashboard-python-2/</comments>
<description>
&lt;p&gt;Если под рукой имеется машина на Amazon Web Services и стоит задача развернуть веб-приложение, можно воспользоваться сервисом Elastic Beanstalk от AWS: он позволяет развертывать приложения под другими сервисами от Amazon, включая EC2.&lt;/p&gt;
&lt;h2&gt;Готовим приложение&lt;/h2&gt;
&lt;p&gt;В материале &lt;a href="http://test.leftjoin.ru/all/dashboard-python-1/"&gt;«Делаем дашборд с параметром на Python» &lt;/a&gt; мы создали проект с двумя файлами: application.py — скрипт с генерацией локального дашборда и get_plots.py — скрипт, возвращающий scatter plot с пивоварнями Untappd из материала &lt;a href="http://test.leftjoin.ru/all/scatter-plot-untappd/" class="nu"&gt;«&lt;u&gt;Строим scatter plot по пивоварням Untappd&lt;/u&gt;»&lt;/a&gt;. Немного подкорректируем файл application.py: чтобы приложение запускалось на Elastic Beanstalk, app.server в конце файла присвоим переменной application. Должно получиться вот так:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;application = app.server

if __name__ == '__main__':
   application.run(debug=True, port=8080)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Перед тем, как развернуть приложение, нужно собрать его в архив. В архиве должны присутствовать все необходимые файлы, включая requirements.txt — перечень зависимостей приложения. В нём перечислены пакеты и версии, необходимые для запуска приложения. Чтобы его создать, достаточно в директории с проектом и окружением ввести команду pip freeze и отправить вывод в файл:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;pip freeze &amp;gt; requirements.txt&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Теперь соберём архив. В unix для архивации и сжатия предусмотрена встроенная утилита zip.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;zip deploy_v0 application.py get_plots.py requirements.txt&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Создаём приложение и окружение&lt;/h2&gt;
&lt;p&gt;Переходим на &lt;a href="https://us-east-2.console.aws.amazon.com/elasticbeanstalk/home?region=us-east-2"&gt;Elastic Beanstalk&lt;/a&gt; в раздел «Applications». Жмём на «Create a new application».&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/01.png" width="915" height="170" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;В открывшейся странице заполняем наименование приложения и описание. Ниже предлагается присвоить приложению теги для упрощенной категоризации ресурсов. Формат вводимого тега похож на словарь Python: это пара ключ — значение, ключ должен быть уникален. После заполнения данных жмём на оранжевую кнопку «Create».&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/02-15.05.20.png" width="907" height="358" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Сразу после нам покажут список окружений для приложения: изначально он пустой, поэтому нажимаем на «Create a new environment».&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/03-2.png" width="912" height="326" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Так как мы работаем с веб-приложением, выбираем окружение веб-сервера:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/04-2.png" width="906" height="413" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;После предлагают ввести информацию о приложении, включая домен. Можно ввести свой домен, если таковой будет свободен:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/05-2.png" width="897" height="546" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Следом выбираем платформу веб-приложения. Наше написано на Python.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/06-1.png" width="898" height="413" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Теперь загружаем само приложение: так как код мы уже написали, выбираем «Upload your code» и прикрепляем файл с архивом. После жмём «Create environment».&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/07-1.png" width="893" height="834" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Следом откроется окно с логами создания окружения. Пару минут придётся подождать.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/08-1.png" width="912" height="326" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Если все сделали правильно, увидим экран с галочкой и подписью «OK»: это означает, что наше приложение успешно загружено и доступно. Если захотим загрузить новую версию, достаточно пересобрать архив с файлами и загрузить его по кнопке «Upload and deploy».&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/09-1.png" width="914" height="388" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;По ссылке, представленной выше можем пройти на сайт, где лежит дашборд. При помощи тега &amp;lt;iframe&amp;gt; этот дашборд можно также встроить на другой сайт:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;&amp;lt;iframe id=&amp;quot;igraph&amp;quot; scrolling=&amp;quot;no&amp;quot; style=&amp;quot;border:none;&amp;quot;seamless=&amp;quot;seamless&amp;quot; src=&amp;quot;http://dashboardleftjoin-env.eba-qxzgfj64.us-east-2.elasticbeanstalk.com&amp;quot; height=&amp;quot;1100&amp;quot; width=&amp;quot;800&amp;quot;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;В итоге получим такой дашборд на сайте:&lt;/p&gt;
&lt;div class="embed-responsive embed-responsive-4by3" style="min-width:500"&gt;&lt;p&gt;&lt;iframe id="igraph" scrolling="no" style="border:none;"seamless="seamless" src="http://leftjoinbreweriesscatterplotrus-env.eba-bjmemw3u.us-east-1.elasticbeanstalk.com/" height="1100" width="800"&lt;/p&gt;
&lt;/iframe&gt;&lt;/div&gt;&lt;p&gt;&lt;a href="https://github.com/valiotti/leftjoin/tree/master/dashboard_deployment"&gt;Полный код проекта на GitHub&lt;/a&gt;&lt;/p&gt;
</description>
<pubDate>Wed, 22 Jul 2020 16:24:47 +0300</pubDate>
</item>

<item>
<title>Обрабатываем нажатие кнопки в Selenium</title>
<guid isPermaLink="false">46</guid>
<link>http://test.leftjoin.ru/all/selenium-button/</link>
<comments>http://test.leftjoin.ru/all/selenium-button/</comments>
<description>
&lt;p&gt;В материале &lt;a href="http://test.leftjoin.ru/all/parsim-dannye-kataloga-sayta-ispolzuya-beautiful-soup-i-selenium/" class="nu"&gt;«&lt;u&gt;Парсим данные, используя Buetiful Soup и Selenium&lt;/u&gt;»&lt;/a&gt; мы уже рассмотрели, как быть, когда данные на странице динамически подгружаются при скролле страницы. Но бывают ситуации, когда новые данные можно получить, только нажав на кнопку «Показать ещё» — сегодня узнаем, как через Selenium сымитировать нажатие кнопки для полного открытия страницы, соберём идентификаторы пива, оценки к каждому продукту и отправим данные в Clickhouse&lt;/p&gt;
&lt;h2&gt;Структура страницы&lt;/h2&gt;
&lt;p&gt;Возьмём случайную пивоварню — у неё 105 чекинов, то есть, отзывов. Страница с чекинами пивоварни показывает не более 25 чекинов и выглядит так:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/1-6.png" width="1186" height="735" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Если попробуем промотать в самый низ, столкнёмся с той самой кнопкой, мешающей нам взять все 105 за раз:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/2-6.png" width="350" height="65" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Мы поступим так: выясним, к какому классу относится элемент кнопки и будем на неё нажимать, пока это возможно. Так как Selenium запускает браузер, следующая кнопка «Показать ещё» может не успеть прогрузиться, поэтому между нажатиями поставим интервал в пару секунд. Как только страница раскроется полностью — мы возьмём её содержимое и распарсим нужные данные из чекинов. Зайдём в код страницы и найдём кнопку — она относится к классу &lt;span class="inline-code"&gt;more_checkins&lt;/span&gt;.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/3-6.png" width="849" height="94" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;У кнопки есть свойства стиля, а именно — &lt;span class="inline-code"&gt;display&lt;/span&gt;. В случае, если кнопка должна отображаться, &lt;span class="inline-code"&gt;display&lt;/span&gt; принимает значение &lt;span class="inline-code"&gt;block&lt;/span&gt;. Но когда промотаем страницу до самого конца, кнопку не нужно будет показывать, ведь открывать больше нечего — поэтому &lt;span class="inline-code"&gt;display&lt;/span&gt; кнопки примет значение &lt;span class="inline-code"&gt;none&lt;/span&gt;. В случае, если мы запросим у кнопки &lt;span class="inline-code"&gt;display&lt;/span&gt; и вернётся &lt;span class="inline-code"&gt;none&lt;/span&gt; будем знать, что открывать больше нечего и можно перестать жать на кнопку.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/4.1.png" width="562" height="107" alt="" /&gt;
&lt;/div&gt;
&lt;h2&gt;Пишем код&lt;/h2&gt;
&lt;p&gt;Начнём с импорта библиотек:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;import time
from selenium import webdriver
from bs4 import BeautifulSoup as bs
import re
from datetime import datetime
from clickhouse_driver import Client&lt;/code&gt;&lt;/pre&gt;&lt;p class="note"&gt;Chromedriver, необходимый для запуска браузера через Selenium, можно установить с &lt;a href="https://chromedriver.chromium.org/downloads"&gt;официальной страницы&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Подключимся к базе данных, зададим cookies:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;client = Client(host='ec1-23-456-789-10.us-east-2.compute.amazonaws.com', user='', password='', port='9000', database='')
count = 0
cookies = {
    'domain':'untappd.com',
    'expiry':1594072726,
    'httpOnly':True,
    'name':'untappd_user_v3_e',
    'path':'/',
    'secure':False,
    'value':'your_value'
}&lt;/code&gt;&lt;/pre&gt;&lt;p class="note"&gt;О том, как запускать Selenium с cookies можно прочитать в материале &lt;a href="http://test.leftjoin.ru/all/parsim-dannye-kataloga-sayta-ispolzuya-beautiful-soup-i-selenium/" class="nu"&gt;«&lt;u&gt;Парсим данные каталога сайта, используя Beautiful Soup и Selenium&lt;/u&gt;»&lt;/a&gt;. Нам нужен параметр untappd_user_v3_e.&lt;/p&gt;
&lt;p&gt;Так как мы планируем работать с пивоварнями, у которых более сотни тысяч чекинов, может оказаться так, что страница будет чересчур тяжёлой, и нагрузка на машину будет огромна. Чтобы этого избежать, отключим всё лишнее, а затем подключим cookie для авторизации:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;options = webdriver.ChromeOptions()
prefs = {'profile.default_content_setting_values': {'images': 2, 
                            'plugins': 2, 'fullscreen': 2}}
options.add_experimental_option('prefs', prefs)
options.add_argument(&amp;quot;start-maximized&amp;quot;)
options.add_argument(&amp;quot;disable-infobars&amp;quot;)
options.add_argument(&amp;quot;--disable-extensions&amp;quot;)
driver = webdriver.Chrome(options=options)
driver.get('https://untappd.com/TooSunnyBrewery')
driver.add_cookie(cookies)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Напишем функцию, которая принимает ссылку, переходит по ней, полностью раскрывает страницу и возвращает нам soup, который можно будет распарсить. Получим &lt;span class="inline-code"&gt;display&lt;/span&gt; кнопки и запишем в переменную &lt;span class="inline-code"&gt;more_checkins&lt;/span&gt;: пока он не равен &lt;span class="inline-code"&gt;none&lt;/span&gt; будем нажимать на кнопку и снова получать её &lt;span class="inline-code"&gt;display&lt;/span&gt;. Сделаем интервал в две секунды между нажатиями, чтобы подождать прогрузку страницы. Как только будет получена вся страница, переведём её в &lt;span class="inline-code"&gt;soup&lt;/span&gt; библиотекой &lt;span class="inline-code"&gt;bs4&lt;/span&gt;.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;def get_html_page(url):
    driver.get(url)
    driver.maximize_window()
    more_checkins = driver.execute_script(&amp;quot;var more_checkins=document.getElementsByClassName('more_checkins_logged')[0].style.display;return more_checkins;&amp;quot;)
    print(more_checkins)
    while more_checkins != &amp;quot;none&amp;quot;:
        driver.execute_script(&amp;quot;document.getElementsByClassName('more_checkins_logged')[0].click()&amp;quot;)
        time.sleep(2)
        more_checkins = driver.execute_script(&amp;quot;var more_checkins=document.getElementsByClassName('more_checkins_logged')[0].style.display;return more_checkins;&amp;quot;)
        print(more_checkins)
    source_data = driver.page_source
    soup = bs(source_data, 'lxml')
    return soup&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Напишем следующую функцию: она тоже будет принимать &lt;span class="inline-code"&gt;url&lt;/span&gt; страницы, передавать его в &lt;span class="inline-code"&gt;get_html_page&lt;/span&gt;, получать &lt;span class="inline-code"&gt;soup&lt;/span&gt; и парсить его. Функция вернёт запакованные списки с идентификатором пива и оценкой к нему.&lt;/p&gt;
&lt;p class="note"&gt;О том, как парсить элементы страницы мы уже говорили в материале &lt;a href="http://test.leftjoin.ru/all/parsim-dannye-kataloga-sayta-ispolzuya-beautiful-soup-chast-1/" class="nu"&gt;«&lt;u&gt;Парсим данные каталога сайта, используя Beautiful Soup&lt;/u&gt;»&lt;/a&gt;.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;def parse_html_page(url):
    soup = get_html_page(url)
    brewery_id = soup.find_all('a', {'class':'label',
                                     'href':re.compile('https://untappd.com/brewery/*')})[0]['href'][28:]
    items = soup.find_all('div', {'class':'item',
                                  'id':re.compile('checkin_*')})
    checkin_rating_list = []
    beer_id_list = []
    count = 0
    print('Заполняю списки')
    for checkin in items:
        print(count, '/', len(items))
        try:
            checkin_rating_list.append(float(checkin.find('div', {'class':'caps'})['data-rating']))
        except Exception:
            checkin_rating_list.append('cast(Null as Nullable(Float32))')
        try:
            beer_id_list.append(int(checkin.find('a', {'class':'label'})['href'][-7:]))
        except Exception:
            beer_id_list.append('cast(Null as Nullable(UInt64))')
        count += 1 
    return zip(checkin_rating_list, beer_id_list)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Наконец, напишем вызов функций по пивоварням. В материале &lt;a href="http://test.leftjoin.ru/all/untappd-clickhouse-dictionaries/" class="nu"&gt;«&lt;u&gt;Использование словарей в Clickhouse на примере данных Untappd&lt;/u&gt;»&lt;/a&gt; мы уже рассмотрели, как получить список идентификаторов российских пивоварен — обратимся к нему через таблицу в Clickhouse&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;brewery_list = client.execute('SELECT brewery_id FROM brewery_info')&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Если посмотрим на &lt;span class="inline-code"&gt;brewery_list&lt;/span&gt;, то узнаем, что данные вернулись в неудобном формате: это список кортежей.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/5-6.png" width="378" height="141" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Небольшое лямбда-выражение позволит его «выпрямить»:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;flatten = lambda lst: [item for sublist in lst for item in sublist]
brewery_list = flatten(brewery_list)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Работать с таким списком значительно комфортнее:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/6-5.png" width="252" height="139" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Для каждой пивоварни в списке сформируем &lt;span class="inline-code"&gt;url&lt;/span&gt; — он состоит из стандартной ссылки и идентификатора пивоварни в конце. Отправим &lt;span class="inline-code"&gt;url&lt;/span&gt; в функцию &lt;span class="inline-code"&gt;parse_html_page&lt;/span&gt;, которая сама вызовет &lt;span class="inline-code"&gt;get_html_page&lt;/span&gt; и вернёт списки с &lt;span class="inline-code"&gt;beer_id&lt;/span&gt; и &lt;span class="inline-code"&gt;rating_score&lt;/span&gt;. Так как два списка вернутся упакованными можем пройти по ним итератором, сформировав кортеж и отправив его в Clickhouse.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;for brewery_id in brewery_list:
    print('Беру пивоварню с id', brewery_id, count, '/', len(brewery_list))
    url = 'https://untappd.com/brewery/' + str(brewery_id)
    returned_checkins = parse_html_page(url)
    for rating, beer_id in returned_checkins:
        tuple_to_insert = (rating, beer_id)
        try:
            client.execute(f'INSERT INTO beer_reviews VALUES {tuple_to_insert}')
        except errors.ServerException as E:
            print(E)
    count += 1&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;С кнопками на этом всё. Постепенно мы формируем отличный датасет для последующего анализа, который рассмотрим в следующем цикле статей, как только завершим сбор данных — возможно, через месяц.&lt;/p&gt;
</description>
<pubDate>Fri, 19 Jun 2020 12:30:09 +0300</pubDate>
</item>

<item>
<title>Создаём материализованное представление в Clickhouse</title>
<guid isPermaLink="false">40</guid>
<link>http://test.leftjoin.ru/all/materialized-view-in-clickhouse/</link>
<comments>http://test.leftjoin.ru/all/materialized-view-in-clickhouse/</comments>
<description>
&lt;p&gt;В этот раз разберёмся, как с помощью Python передавать в Clickhouse данные по рекламным кампаниям и построим агрегат, используя материализованное представление.&lt;br /&gt;
Для чего нам материализованные представления? Часто Clickhouse используется для работы с огромными объемами данных, а время получения ответа на запрос к таблице с сырыми данными постоянно растёт. Стандартно, чтобы решить такую задачу эффективным способом, чаще всего используют ETL-процессы или создают таблицы агрегатов, что не очень удобно, ведь их необходимо регулярно пересчитывать. Clickhouse обладает встроенной и эффективной возможностью для решения задачи — материализованными представлениями.&lt;br /&gt;
Материализованные представления физически хранят и обновляют данные на диске в соответствии с запросом &lt;span class="inline-code"&gt;SELECT&lt;/span&gt;, на основе которого представление было создано. При вставке данных в искомую таблицу &lt;span class="inline-code"&gt;SELECT&lt;/span&gt; преобразовывает данные и вставляет их в представление.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Настройка машины&lt;/b&gt;&lt;br /&gt;
Наш скрипт на Python из &lt;a href="http://test.leftjoin.ru/all/collecting-costs-from-facebook-api/"&gt;предыдущих материалов&lt;/a&gt; необходимо подключить к Clickhouse — он будет отправлять запросы, поэтому нужно открыть несколько портов. В Dashboard AWS переходим в Network &amp; Security — Security Groups. Наша машина входит в группу launch-wizard-1. Переходим в неё и смотрим на Inbound rules: нам нужно добавить правила как на скриншоте.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/1-2.png" width="969" height="293" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;b&gt;Настройка Clickhouse&lt;/b&gt;&lt;br /&gt;
Теперь настроим Clickhouse. Отредактируем файл config.xml в редакторе nano:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;cd /etc/clickhouse-server
sudo nano config.xml&lt;/code&gt;&lt;/pre&gt;&lt;p class="note"&gt;Воспользуйтесь &lt;a href="http://linux-bash.ru/mtext/6-nano.html"&gt;мануалом по горячим клавишам&lt;/a&gt;, если тоже не сразу поняли, как выйти из nano.&lt;/p&gt;
&lt;p&gt;Раскоментируем строку&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;&amp;lt;listen_host&amp;gt;0.0.0.0&amp;lt;/listen_host&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;чтобы доступ к базе данных был с любого IP-адреса:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/2-1.png" width="479" height="194" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;b&gt;Создание таблицы и материализованного представления&lt;/b&gt;&lt;br /&gt;
Зайдём в клиент и создадим нашу базу данных, в которой впоследствии создадим таблицы:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;CREATE DATABASE db1
USE db1&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Мы проиллюстрируем всё тот же пример сбора данных с Facebook. Информация по кампаниям может часто обновляться, и мы, в целях упражнения, хотим создать материализованное представление, которое будет автоматически пересчитывать агрегаты на основе собранных данных по затратам. Таблица в Clickhouse будет практически такой же, как &lt;span class="inline-code"&gt;DataFrame&lt;/span&gt; из прошлого материала. В качестве движка таблицы используем &lt;span class="inline-code"&gt;CollapsingMergeTree&lt;/span&gt;: он будет удалять дубликаты по ключу сортировки:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;CREATE TABLE facebook_insights(
	campaign_id UInt64,
	clicks UInt32,
	spend Float32,
	impressions UInt32,
	date_start Date,
	date_stop	 Date,
	sign Int8
) ENGINE = CollapsingMergeTree
ORDER BY (date_start, date_stop)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;И сразу создадим материализованное представление:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;CREATE MATERIALIZED VIEW fb_aggregated
ENGINE = SummingMergeTree()
ORDER BY date_start
	AS
	SELECT campaign_id,
		      date_start,
		      sum(spend * sign) as spent,
		      sum(impressions * sign) as impressions,
		      sum(clicks * sign) as clicks
	FROM facebook_insights
	GROUP BY date_start, campaign_id&lt;/code&gt;&lt;/pre&gt;&lt;p class="note"&gt;Подробности рецепта можно посмотреть в &lt;a href="https://clickhouse.yandex/blog/en/how-to-update-data-in-clickhouse"&gt;блоге Clickhouse&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;К сожалению, в Clickhouse &lt;span class="inline-code"&gt;UPDATE&lt;/span&gt; отсутствует, поэтому необходимо придумывать некоторые ухищрения. Мы воспользовались рецептом от команды Яндекса для обходного пути команды &lt;span class="inline-code"&gt;UPDATE&lt;/span&gt;. Идея состоит в том, чтобы в начале вставить строки, которые уже были в таблице с отрицательным &lt;span class="inline-code"&gt;Sign&lt;/span&gt;, а затем использовать &lt;span class="inline-code"&gt;Sign&lt;/span&gt; для сторнирования. Следуя этому рецепту старые данные не будут учитываться при суммировании.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Скрипт&lt;/b&gt;&lt;br /&gt;
Начнём писать скрипт. Понадобится новая библиотека — clickhouse_driver, позволяющая отправлять запросы к Clickhouse из скрипта на Python:&lt;/p&gt;
&lt;p class="note"&gt;В материале приведена только доработка скрипта, описанного в статье &lt;a href="http://test.leftjoin.ru/all/collecting-costs-from-facebook-api/" class="nu"&gt;«&lt;u&gt;Собираем данные по рекламным кампаниям в Facebook&lt;/u&gt;»&lt;/a&gt;. Всё будет работать, если вы просто вставите код из текущего материала в скрипт предыдущего.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;from datetime import datetime, timedelta
from clickhouse_driver import Client
from clickhouse_driver import errors&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Объект класса &lt;span class="inline-code"&gt;Client&lt;/span&gt; позволит отправлять запросы методом &lt;span class="inline-code"&gt;execute()&lt;/span&gt;. В &lt;span class="inline-code"&gt;host&lt;/span&gt; вводим свой public dns, &lt;span class="inline-code"&gt;user&lt;/span&gt; ставим default,  &lt;span class="inline-code"&gt;port&lt;/span&gt; — 9000 и в database базу данных для подключения.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;client = Client(host='ec1-2-34-56-78.us-east-2.compute.amazonaws.com', user='default', password=' ', port='9000', database='db1')&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Чтобы удостовериться, что всё нормально, можно написать следующий запрос, который должен вывести наименования всех баз данных на сервере:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;client.execute('SHOW DATABASES')&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;В случае успеха получим на экране такой список:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;[('_temporary_and_external_tables',), ('db1',), ('default',), ('system',)]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Пусть, например, мы хотим рассматривать данные за последние три дня. Получим эти даты библиотекой &lt;span class="inline-code"&gt;datetime&lt;/span&gt; и переведём в нужный формат методом &lt;span class="inline-code"&gt;strftime()&lt;/span&gt;:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;date_start = datetime.now() - timedelta(days=3)
date_end = datetime.now() - timedelta(days=1)
date_start_str = date_start.strftime(&amp;quot;%Y-%m-%d&amp;quot;)
date_end_str = date_end.strftime(&amp;quot;%Y-%m-%d&amp;quot;)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Напишем вот такой запрос, получающий все колонки таблицы за это время:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;SQL_select = f&amp;quot;select campaign_id, clicks, spend, impressions, date_start, date_stop, sign from facebook_insights where date_start &amp;gt; '{date_start_str}' AND date_start &amp;lt; '{date_end_str}'&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;И выполним запрос, поместив информацию в список &lt;span class="inline-code"&gt;old_data_list&lt;/span&gt;. А затем поменяем всем &lt;span class="inline-code"&gt;sign&lt;/span&gt; на -1 и добавим в &lt;span class="inline-code"&gt;new_data_list&lt;/span&gt;:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;new_data_list = []
old_data_list = []
old_data_list = client.execute(SQL_select)

for elem in old_data_list:
    elem = list(elem)
    elem[len(elem) - 1] = -1
    new_data_list.append(elem)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Наконец, напишем наш алгоритм: вставляем те же самые данные с &lt;span class="inline-code"&gt;sign = −1&lt;/span&gt;, оптимизируем для удаления дубликатов движком &lt;span class="inline-code"&gt;CollapsingMergeTree&lt;/span&gt; и выполняем &lt;span class="inline-code"&gt;INSERT&lt;/span&gt; новых данных со знаком &lt;span class="inline-code"&gt;sign = 1&lt;/span&gt;.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;SQL_query = 'INSERT INTO facebook_insights VALUES'
client.execute(SQL_query, new_data_list)
SQL_optimize = &amp;quot;OPTIMIZE TABLE facebook_insights&amp;quot;
client.execute(SQL_optimize)
for i in range(len(insight_campaign_id_list)):
    client.execute(SQL_query, [[insight_campaign_id_list[i],
                                insight_clicks_list[i],
                                insight_spend_list[i],
                                insight_impressions_list[i],
                                datetime.strptime(insight_date_start_list[i], '%Y-%m-%d').date(),
                                datetime.strptime(insight_date_start_list[i], '%Y-%m-%d').date(),
                                1]])
    client.execute(SQL_optimize)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Вернёмся в Clickhouse. Выполним &lt;span class="inline-code"&gt;SELECT * FROM facebook_insights LIMIT 20&lt;/span&gt;, чтобы посмотреть первые 20 строк таблицы:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/3-2.png" width="754" height="375" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;И &lt;span class="inline-code"&gt;SELECT * FROM fb_aggregated LIMIT 20&lt;/span&gt;, чтобы проверить наше представление:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/4-1.png" width="748" height="385" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Отлично! Мы сделали материализованное представление — теперь новые данные, поступающие в таблицу &lt;span class="inline-code"&gt;facebook_insights&lt;/span&gt; будут поступать и в материализованное представление &lt;span class="inline-code"&gt;fb_aggregated&lt;/span&gt; и каждый раз пересчитываться благодаря &lt;span class="inline-code"&gt;SummingMergeTree&lt;/span&gt;. При этом трюк с &lt;span class="inline-code"&gt;sign&lt;/span&gt; позволяет отлавливать уже обработанные записи и не допускать их суммирования, а &lt;span class="inline-code"&gt;CollapsingMergeTree&lt;/span&gt; — чистить дубликаты.&lt;/p&gt;
</description>
<pubDate>Wed, 20 May 2020 11:30:36 +0300</pubDate>
</item>

<item>
<title>Устанавливаем Clickhouse на AWS</title>
<guid isPermaLink="false">38</guid>
<link>http://test.leftjoin.ru/all/stavim-clickhouse-na-aws/</link>
<comments>http://test.leftjoin.ru/all/stavim-clickhouse-na-aws/</comments>
<description>
&lt;p&gt;Сегодня поработаем с Clickhouse — поставим его на личную бесплатную машину с Amazon Web Services.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Аккаунт на AWS и машина на Ubuntu&lt;/b&gt;&lt;br /&gt;
Проще всего Clickhouse установить из deb пакетов на машину под управлением Ubuntu. Сегодня вовсе необязательно иметь такую под рукой — достаточно создать годовой бесплатный аккаунт на Amazon Web Services, который выделит нам нужную машину. Переходим на &lt;a href="https://aws.amazon.com"&gt;https://aws.amazon.com&lt;/a&gt; и создаём аккаунт и попадаем в Dashboard. В разделе «Build a solution» идём в «Launch a virtual machine» и подбираем виртуальную машину. Нам подойдёт та, на которой установлен Ubuntu Server.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;div class="fotorama" data-width="623" data-ratio="1.1537037037037"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/0.png" width="623" height="540" alt="" /&gt;
&lt;img src="http://test.leftjoin.ru/pictures/1-1.png" width="986" height="146" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Заодно создаём key pair — пара состоит из публичного ключа AWS и приватного ключа-файла. Последний нужно сохранить у себя на компьютере для подключения к машине.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/3-1.png" width="683" height="500" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;После откроется консоль EC2, где уже будет запущен наш Instance виртуальной машины. У него есть public dns — сохраняем его.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Подключаемся через Termius&lt;/b&gt;&lt;br /&gt;
Подключаться к виртуальной машине будем через протокол удалённого доступа ssh. Есть много клиентов, поддерживающих этот протокол — я буду использовать &lt;a href="https://termius.com"&gt;Termius&lt;/a&gt;. Жмём на «+ NEW HOST» и заполняем информацию.&lt;br /&gt;
В поле address вводим наш public dns, который заранее сохранили. В Username вводим «ubuntu», поле пароля оставляем пустым. Чтобы заполнить Key нужно добавить новый ключ — то есть, указать путь к файлу с расширением .pem, который мы получили при создании новой виртуальной машины. Должно получиться что-то такое:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/5-1.png" width="451" height="419" alt="" /&gt;
&lt;/div&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/5-2.png" width="450" height="673" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Сразу после авторизации подключаемся к машине и получаем новый экран с консолью:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/55.png" width="677" height="403" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Для начала установим Clickhouse — это быстро. Выполним следующую команду, чтобы добавить репозиторий Clickhouse к нашим APT репозиториям:&lt;/p&gt;
&lt;p class="note"&gt;Подробнее со всеми способами установки Clickhouse можно ознакомиться в &lt;a href="https://clickhouse.tech/docs/ru/getting-started/install/"&gt;документации&lt;/a&gt;&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;echo &amp;quot;deb http://repo.yandex.ru/clickhouse/deb/stable/ main/&amp;quot; | sudo tee /etc/apt/sources.list.d/clickhouse.list&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Обязательно обновляем пакеты:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;sudo apt-get update&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Наконец, установим клиент и сервер командой:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;sudo apt-get install -y clickhouse-server clickhouse-client&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Готово! Клиент и сервер Clickhouse установлены на виртуальной машине. Запустим сервер, чтобы подсоединиться позже через клиент:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;sudo service clickhouse-server start&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;И проверим успешность запуска командой:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;sudo service clickhouse-server status&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Получим примерно такой результат:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/6-1.png" width="881" height="125" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Clickhouse установлен! Для подключения к клиенту необходимо ввести:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;clickhouse-client&lt;/code&gt;&lt;/pre&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/8.png" width="505" height="122" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Чтобы убедиться, что всё работает как надо, документация предлагает ввести запрос&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;SELECT 1&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Вот, что мы должны получить:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/9-1.png" width="455" height="218" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Готово! А в следующем материале поработаем в связке Python + Clickhouse: вернёмся к нашему скрипту, который получает информацию по рекламным кампаниям и сделаем так, чтобы эти данные собирались в таблицу и материализованное представление.&lt;/p&gt;
</description>
<pubDate>Mon, 18 May 2020 12:10:48 +0300</pubDate>
</item>


</channel>
</rss>