Русский
Русский
English
Статистика
Реклама

Бесплатно

PyOpenRPA туториал. Управление WEB приложениями

16.08.2020 12:14:15 | Автор: admin

Долгожданный туториал по управлению сторонними WEB приложениями с помощью pyOpenRPA. Во 2-й части мы разберем принципы роботизированного воздействия на HTML/JS. А также своими руками сделаем небольшого, но очень показательного робота.


Этот робот будет полезен тем, для кого актуальна тема покупки/продажи недвижимости.


pyOpenRPA туториал. Управление WEB приложениями


Для тех, кто с нами впервые


pyOpenRPA это open source RPA платформа, которая в полной мере позволяет заменить топовые коммерческие RPA платформы.


Подробнее про то, чем же она полезна, можно почитать здесь.


Навигация по туториалам pyOpenRPA


Туториал сверстан в виде серии статей, в которых будут освещаться ключевые технологии, необходимые для RPA.


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


Перечень статей-туториалов (опубликованные и планируемые):



А теперь перейдем к самому туториалу.


Немного теории и терминов


[Из википедии]


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


Веб приложения стали широко использоваться в конце 1990-х начале 2000-х годов.


Ссылка на источник


Ок, с выдержкой из вики все #КрутоУмно, но от этого не легче (для тех, кто в этой теме дилетант). Продемонстрирую устройство WEB приложения на примере "Что видим мы?"/"Что видит робот?". Для этого отправимся на сайт одной известной WEB площадки по объявлениям по недвижимости


Что видим мы?


Мы видим красиво сверстанный сайт с интуитивно понятным интерфейсом, на котором можно найти интересные объявления о продаже/сдаче в аренду недвижимости.


Как мы видим WEB приложение


Что видит робот?


Робот видит огромную гипертекстовую разметку HTML с примесью алгоритмического кода JS и завернутого в каскадную таблицу стилей CSS. Увлекательно, правда? :)


Как робот видит WEB приложение


Интерпретация


WEB приложения это один из самых легко роботизируемых классов приложений. Обилие инструментов + технологий позволяют реализовывать практически любую поставленную задачу в кооперации с ними.


Управлять WEB страницей можно с помощью разных технологий адресации: CSS, XPath, id, class, attribute. Мы будем взаимодействовать со страницей с помощью CSS селекторов.


(По шагам) робот своими руками


В этом туториале мы будем заниматься разработкой робота, который оперирует на одном из самых популярных порталов по объявлениям по недвижимости в РФ (тема одна из актуальных для многих).


В качестве примера поставим себе следующую задачу: Разработать робота, который будет извлекать список всех объявлений по ранее преднастроенному фильтру. Все извлеченные объявления сохранить как датасет в файл .json со следующей структурой:


{    "SearchKeyStr": "МСК_Тверской", # Ключевое слово поиска    "SearchTitleStr": "Москва, район Тверской", # Заголовок поиска    "SearchURLStr": "https://www.cian.ru/cat.php?deal_type=sale&engine_version=2&in_polygon%5B1%5D=37.6166_55.7678%2C37.6147_55.7688%2C37.6114_55.7694%2C37.6085_55.7698%2C37.6057_55.77%2C37.6018_55.77%2C37.5987_55.77%2C37.5961_55.7688%2C37.5942_55.7677%2C37.5928_55.7663%2C37.5915_55.7647%2C37.5908_55.7631%2C37.5907_55.7616%2C37.5909_55.7595%2C37.5922_55.7577%2C37.5944_55.7563%2C37.5968_55.7555%2C37.6003_55.7547%2C37.603_55.7543%2C37.6055_55.7542%2C37.6087_55.7541%2C37.6113_55.7548%2C37.6135_55.756%2C37.6151_55.7574%2C37.6163_55.7589%2C37.6179_55.7606%2C37.6187_55.7621%2C37.619_55.7637%2C37.6194_55.7651%2C37.6193_55.7667%2C37.6178_55.7679%2C37.6153_55.7683%2C37.6166_55.7678&offer_type=flat&polygon_name%5B1%5D=%D0%9E%D0%B1%D0%BB%D0%B0%D1%81%D1%82%D1%8C+%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0&room1=1&room2=1", # URL of the CIAN search [str]    "SearchDatetimeStr": "2020-08-01 09:33:00.838081", # Дата, на которую была сформирована выгрузка    "SearchItems": { # Перечень извлеченных ценовых объявлений        "https://www.cian.ru/sale/flat/219924574/:": { # URL ссылка на ценовое объявление            "TitleStr": "3-комн. кв., 31,4 м, 5/8 этаж", # Заголовок ценового объявления            "PriceFloat": 10000000.0, # Стоимость общая            "PriceSqmFloat": 133333.0, # Стоимость на 1 кв. м.            "SqMFloat": 31.4, # Кол-во кв. м.            "FloorCurrentInt": 5, # Этаж лота по объявлению            "FloorTotalInt": 8, # Этажей в доме всего            "RoomCountInt": 3 # Кол-во комнат        }    }}

Шаг 0. Подготовим проект для нового робота (развернем pyOpenRPA)


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


Доступно несколько вариантов загрузки pyOpenRPA:


  • Вариант 1, простой. Скачать преднастроенную портативную версию с GitLab страницы проекта
  • Вариант 2, сложный. Установить pyOpenRPA в свою версию интерпретатора Python 3 (pip install pyOpenRPA)

Я рекомендую воспользоваться простым вариантом (вариант 1). Преднастроенная версия не требуется каких-либо настроек инфраструктуры. Здесь в лучших традициях pyOpenRPA реализован принцип, когда пользователь скачивает репозиторий, и у него уже все настроено из коробки пользователю остается лишь писать скрипт робота. #Enjoy :)


Шаг 1. Создать проект робота


Для того, чтобы начать проект робота, необходимо создать папку проекта. В дальнейшем я затрону тему организации папок проектов для промышленных программных роботов. Но на текущий момент не буду заострять внимание на этом, чтобы сконцентрироваться непосредственно на основном на логике работы с WEB страницами.


Ниже приведу зависимости проекта от сторонних компонентов:


  • Selenium WebDriver
  • Google Chrome или Mozilla Firefox или Internet Explorer
  • Python 3

Если вы пошли по варианту 1 (см. шаг 0), то у Вас все эти компоненты уже будут развернуты и настроены внутри скачанного репозитория pyOpenRPA (#Удобно). Репозиторий pyOpenRPA уже содержит все необходимые portable версии требуемых программ (Google Chrome, Mozilla Firefox, Python3 32|64 и т.д.).


Вы наверняка заметили, что в pyOpenRPA используется Selenium. Этот компонент является одним из лучших отказоустойчивых компонентов по внедрению в WEB. Именно поэтому мы его и будем использовать в pyOpenRPA.


Создадим следующую структуру проекта:


  • Репозиторий pyOpenRPA > Wiki > RUS_Tutorial > WebGUI_Habr:
    • Файл "3. MonitoringCIAN_Run_64.py" скрипт робота, который мониторит WEB площадку
    • Файл "3. MonitoringCIAN_Run_64.cmd" скрипт запуска робота с 1-го клика по аналогии с .exe файлами

Ниже приведу пример "3. MonitoringCIAN_Run_64.cmd" файла:


cd %~dp0..\..\..\Sources..\Resources\WPy64-3720\python-3.7.2.amd64\python.exe "..\Wiki\RUS_Tutorial\WebGUI_Habr\3. MonitoringCIAN_Run_64.py"pause >nul

Для инициализации Selenium WebDriver воспользуемся следующей функцией:


########################### Init the Chrome web driver###########################def WebDriverInit(inWebDriverFullPath, inChromeExeFullPath, inExtensionFullPathList):    # Set full path to exe of the chrome    lWebDriverChromeOptionsInstance = webdriver.ChromeOptions()    lWebDriverChromeOptionsInstance.binary_location = inChromeExeFullPath    # Add extensions    for lExtensionItemFullPath in inExtensionFullPathList:        lWebDriverChromeOptionsInstance.add_extension (lExtensionItemFullPath)    # Run chrome instance    lWebDriverInstance = None    if inWebDriverFullPath:        # Run with specified web driver path        lWebDriverInstance = webdriver.Chrome(executable_path = inWebDriverFullPath, options=lWebDriverChromeOptionsInstance)    else:        lWebDriverInstance = webdriver.Chrome(options = lWebDriverChromeOptionsInstance)    # Return the result    return lWebDriverInstance

Шаг 2. Запустить WEB инструменты разработчика и сформировать CSS селекторы


В нашем случае WEB инструменты разработчика мы будем использовать из Google Chrome, который предустановлен в репозитории pyOpenRPA (вариант 1 из шага 0).


Откроем Google Chrome и инструменты разработчика (pyOpenRPA repo\Resources\GoogleChromePortable\App\Chrome-bin\chrome.exe, после чего Ctrl + Shift + i)
Portable Google Chrome + Dev Tools


Откроем в браузере сайт, который мы будем анализировать. Сформируем область поиска и отобразить обнаруженные ценовые предложения в виде списка.


Пример поискового запроса


Список ценовых предложений по фильтру


Для того, чтобы подобрать CSS селектор нам помогут инструменты разработчика Google Chrome. Подробнее узнать про устройство CSS селекторов можно здесь по ссылке
Для проверки правильности CSS селектора я буду делать следующую проверку в инструментах разработчика на вкладке "Console". На картинке представлен пример того, как проводится проверки правильности CSS селектора для извлечения списка ценовых предложений.


Пример проверки CSS селектора


Подберем CSS селектор для выборки списка ценовых предложений на странице.


При составлении селектора выяснилось, что в список объявлений встроены рекламные баннеры, которые не содержат информацию о ценовом предложении.


И таких видов рекламных баннеров было обнаружено несколько видов:


  • div[data-name="BannerServicePlaceInternal"]
  • div[data-name="getBannerMarkup"]
  • div[data-name="AdFoxBannerTracker"]

В связи с этим CSS селектор должен быть скорректирован таким образом, чтобы исключить из выборки такие виды баннеров. Ниже приведен готовый CSS селектор.


  • CSS селектор, Список ценовых предложений: div[data-name="Offers"] > div:not([data-name="BannerServicePlaceInternal"]):not([data-name="getBannerMarkup"]):not([data-name="AdFoxBannerTracker"])

Подберем CSS селекторы по извлечению параметров ценового предложения: Заголовок, Стоимость общая, URL ссылка на карточку.


  • CSS селектор, Заголовок: div[data-name="TopTitle"],div[data-name="Title"]
  • CSS селектор, Стоимость общая: div[data-name="Price"] > div[class="header"],div[data-name="TopPrice"] > div[class="header"]
  • CSS селектор, URL ссылка на карточку: a[class*="--header--"]

Подберем CSS селектор для извлечения кнопки на следующую страницу.


  • CSS селектор, Указатель на следующую страницу: div[data-name="Pagination"] li[class*="active"] + li a

Шаг 3. Обработать/преобразовать получаемые данные


На предыдущем шаге мы успешно подобрали все необходимые CSS селекторы. Теперь нам нужно грамотно извлечь информацию, а потом и обработать ее.


В результате обработки ценового предложения у нас будет сформирована структура следующего вида:


lOfferItemInfo = { # Item URL with https    "TitleStr": "3-комн. кв., 31,4 м, 5/8 этаж", # Offer title [str]    "PriceFloat": 10000000.0, # Price [float]    "PriceSqmFloat": 133333.0, # CALCULATED Price per square meters [float]    "SqMFloat": 31.4, # Square meters in flat [float]    "FloorCurrentInt": 5, # Current floor [int]    "FloorTotalInt": 8, # Current floor [int]    "RoomCountInt": 3  # Room couint [int]}

Для начала получим список элементов ценовых предложений.


lOfferListCSSStr = 'div[data-name="Offers"] > div:not([data-name="BannerServicePlaceInternal"]):not([data-name="getBannerMarkup"]):not([data-name="AdFoxBannerTracker"])'lOfferList = inWebDriver.find_elements_by_css_selector(css_selector=lOfferListCSSStr)

Далее циклическая обработка каждого ценового предложения.


for lOfferItem in lOfferList:

Извлечем параметры из WEB страницы: Заголовок, Стоимость общая, URL на карточку.


lTitleStr = lOfferItem.find_element_by_css_selector(css_selector='div[data-name="TopTitle"],div[data-name="Title"]').text # Extract title textlPriceStr = lOfferItem.find_element_by_css_selector(css_selector='div[data-name="Price"]  > div[class*="header"],div[data-name="TopPrice"] > div[class*="header"]').text # Extract total pricelURLStr = lOfferItem.find_element_by_css_selector(css_selector='a[class*="--header--"]').get_attribute("href") # Extract offer URLlOfferItemInfo["TitleStr"] = lTitleStr # set the titlelPriceStr = lPriceStr.replace(" ","").replace("","") # Remove some extra symbolslOfferItemInfo["PriceFloat"] = round(float(lPriceStr),2) # Convert price to the float type

Извлечем недостающие параметры алгоритмическим путем.


  • Если в заголовке содержится слово "Апартаменты"


    lREResult = re.search(r".*, (\d*,?\d*) м, (\d*)/(\d*) эта.", lTitleStr)  # run the relOfferItemInfo["RoomCountInt"] = 1 # Room countlSqmStr = lREResult.group(1)lSqmStr= lSqmStr.replace(",",".")lOfferItemInfo["SqMFloat"] = round(float(lSqmStr),2) # sqm countlOfferItemInfo["FloorCurrentInt"] = int(lREResult.group(2)) # Floor currentlOfferItemInfo["FloorTotalInt"] = int(lREResult.group(3)) # Floor totallOfferItemInfo["PriceSqmFloat"] = round(lOfferItemInfo["PriceFloat"] / lOfferItemInfo["SqMFloat"],2) # Sqm per M
    

  • Если в заголовке не содержится слово "Апартаменты"


    lREResult = re.search(r".*(\d)-комн. .*, (\d*,?\d*) м, (\d*)/(\d*) эта.", lTitleStr) # run the relOfferItemInfo["RoomCountInt"] = int(lREResult.group(1)) # Room countlSqmStr = lREResult.group(2)lSqmStr= lSqmStr.replace(",",".")lOfferItemInfo["SqMFloat"] = round(float(lSqmStr),2) # sqm countlOfferItemInfo["FloorCurrentInt"] = int(lREResult.group(3)) # Floor currentlOfferItemInfo["FloorTotalInt"] = int(lREResult.group(4)) # Floor totallOfferItemInfo["PriceSqmFloat"] = round(lOfferItemInfo["PriceFloat"] / lOfferItemInfo["SqMFloat"],2) # Sqm per M
    


В примере выше применяется магия регулярных выражений


Для подбора правильных регулярных выражений я пользуюсь online валидаторами типа таких


По окончанию обработки ценовых предложений выполним проверку на наличие указателя на следующую страницу, и (если такой указатель имеется) выполним переход на нее.


Выше (на шаге 2) мы уже находили CSS селектор указателя на следующую страницу. Нам нужно выполнить действие клика .click() по этому элементу.
Но при тестировании выяснилось, что функция .click от Selenium отрабатывает некорректно для этой страницы (не происходит переключение). В связи с этим у нас есть уникальная возможность использовать функциональность JavaScript на самой странице через Selenium. А уже из JavaScript выяснилось, что функция нажатия по указателю страницы отрабатывает корректно. Для этого выполним следующую команду:


inWebDriver.execute_script("""document.querySelector('div[data-name="Pagination"] li[class*="active"] + li a').click()""")

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


# wait while preloader is activelDoWaitBool = Truewhile lDoWaitBool:    lPreloaderCSS = inWebDriver.find_elements_by_css_selector(css_selector='div[class*="--preloadOverlay--"]') # So hard to catch the element :)    if len(lPreloaderCSS)>0: time.sleep(0.5) # preloader is here - wait    else: lDoWaitBool = False # Stop wait if preloader is dissappear

Итоговую структуру сохраним в .json файл.


# Check dir - create if not existsif not os.path.exists(os.path.join('Datasets',lResult['SearchKeyStr'])):    os.makedirs(os.path.join('Datasets',lResult['SearchKeyStr']))# Save result in filelFile = open(f"{os.path.join('Datasets',lResult['SearchKeyStr'],lDatetimeNowStr.replace(' ','_').replace('-','_').replace(':','_').replace('.','_'))}.json","w",encoding="utf-8")lFile.write(json.dumps(lResult))lFile.close()

Шаг 4. Обработка нештатных ситуаций


Этап тестирования это один из самых важных этапов, который позволяет конвертировать вложенные усилия в реальный эффект. При тестировании кода на этом WEB приложении выяснилось, что могут происходить некоторые сбои. Ниже привожу те виды сбоев, которые встретились у меня:


  • Зависает ползунок загрузки при переключении на сл. страницу
  • При переключении на следующую страницу открывается совсем не следующая страница (иногда, но случалось :) )

Но роботы не боятся таких проблем (на то они и роботы :) ).


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


  • Зависает ползунок загрузки при переключении на сл. страницу


    # wait while preloader is active. If timeout - retry all joblTimeFromFLoat = time.time() # get current time in float (seconds)lDoWaitBool = Truewhile lDoWaitBool:lPreloaderCSS = inWebDriver.find_elements_by_css_selector(css_selector='div[class*="--preloadOverlay--"]')if len(lPreloaderCSS)>0: time.sleep(0.5) # preloader is here - waitelse: lDoWaitBool = False # Stop wait if preloader is dissappearif (time.time() - lTimeFromFLoat) > 15: # check if timeout is more than 15 seconds    lRetryJobBool = True # Loading error on page - do break, then retry the job    if inLogger: inLogger.warning(f"Ожидание загрузки страницы более {15} с., Робот повторит задание сначала")    break # break the loopif lRetryJobBool == True: # break the loop if RetryJobBool is truebreak
    

  • При переключении на следующую страницу открывается совсем не следующая страница (иногда, но случалось :) )


    lPageNumberInt = int(inWebDriver.find_element_by_css_selector(css_selector='li[class*="--active--"] span').text) # Get the current page int from web and check with iterator (if not equal - retry all job)if lPageNumberInt == lPageCounterInt:... Код робота ...else:lRetryJobBool = Trueif inLogger: inLogger.warning(    f"Следующая страница по списку не была загружена. Была загружена страница: {lPageNumberInt}, Ожидалась страница: {lPageCounterInt}")
    


Шаг 5. Консолидировать код в проекте робота


Соберем все блоки воедино.


Получим следующий пакет (открыть на GitLab):


# Init Chrome web driver with extensions (if applicable)# Import sectionfrom selenium import webdriverimport timeimport re # Regexp to extract info from stringimport jsonimport datetimeimport osimport reimport copyimport logging# Store structure (.json)"""{    "SearchKeyStr": "МСК_Тверской",    "SearchTitleStr": "Москва, район Тверской", # Title of the search [str]    "SearchURLStr": "https://www.cian.ru/cat.php?deal_type=sale&engine_version=2&in_polygon%5B1%5D=37.6166_55.7678%2C37.6147_55.7688%2C37.6114_55.7694%2C37.6085_55.7698%2C37.6057_55.77%2C37.6018_55.77%2C37.5987_55.77%2C37.5961_55.7688%2C37.5942_55.7677%2C37.5928_55.7663%2C37.5915_55.7647%2C37.5908_55.7631%2C37.5907_55.7616%2C37.5909_55.7595%2C37.5922_55.7577%2C37.5944_55.7563%2C37.5968_55.7555%2C37.6003_55.7547%2C37.603_55.7543%2C37.6055_55.7542%2C37.6087_55.7541%2C37.6113_55.7548%2C37.6135_55.756%2C37.6151_55.7574%2C37.6163_55.7589%2C37.6179_55.7606%2C37.6187_55.7621%2C37.619_55.7637%2C37.6194_55.7651%2C37.6193_55.7667%2C37.6178_55.7679%2C37.6153_55.7683%2C37.6166_55.7678&offer_type=flat&polygon_name%5B1%5D=%D0%9E%D0%B1%D0%BB%D0%B0%D1%81%D1%82%D1%8C+%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0&room1=1&room2=1", # URL of the CIAN search [str]    "SearchDatetimeStr": "2020-08-01 09:33:00.838081", # Date of data extraction,  [str]    "SearchItems": {        "https://www.cian.ru/sale/flat/219924574/:": { # Item URL with https            "TitleStr": "3-комн. кв., 31,4 м, 5/8 этаж", # Offer title [str]            "PriceFloat": 10000000.0, # Price [float]            "PriceSqmFloat": 133333.0, # CALCULATED Price per square meters [float]            "SqMFloat": 31.4, # Square meters in flat [float]            "FloorCurrentInt": 5, # Current floor [int]            "FloorTotalInt": 8, # Current floor [int]            "RoomCountInt": 3 # Room couint [int]        }    }}"""########################### Init the Chrome web driver###########################gChromeExeFullPath = r'..\Resources\GoogleChromePortable\App\Chrome-bin\chrome.exe'gExtensionFullPathList = []gWebDriverFullPath = r'..\Resources\SeleniumWebDrivers\Chrome\chromedriver_win32 v84.0.4147.30\chromedriver.exe'def WebDriverInit(inWebDriverFullPath, inChromeExeFullPath, inExtensionFullPathList):    # Set full path to exe of the chrome    lWebDriverChromeOptionsInstance = webdriver.ChromeOptions()    lWebDriverChromeOptionsInstance.binary_location = inChromeExeFullPath    # Add extensions    for lExtensionItemFullPath in inExtensionFullPathList:        lWebDriverChromeOptionsInstance.add_extension (lExtensionItemFullPath)    # Run chrome instance    lWebDriverInstance = None    if inWebDriverFullPath:        # Run with specified web driver path        lWebDriverInstance = webdriver.Chrome(executable_path = inWebDriverFullPath, options=lWebDriverChromeOptionsInstance)    else:        lWebDriverInstance = webdriver.Chrome(options = lWebDriverChromeOptionsInstance)    # Return the result    return lWebDriverInstancefrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as EC# def to extract list of offers from one jobdef OffersByJobExtractDict(inLogger, inWebDriver, inJob):    # BUG 0 - if timeout - retry the job +    # BUG 1 - do mouse scroll to to emulate user activity - cian can hold the robot    # BUG 2 - check the page to retry job offer if page is not next +    # BUG 3 - RE fall on Апартаменты-студия, 85,6 м, 4/8 этаж +    lRetryJobBool = True # Init flag if some error is raised - retry    while lRetryJobBool:        lRetryJobBool = False  # Set false until some another action will appear        lResult = copy.deepcopy(inJob) # do copy the structure        lFilterURLStr = lResult["SearchURLStr"]        inWebDriver.get(lFilterURLStr) # Open the URL        lDatetimeNowStr = str(datetime.datetime.now())        lResult.update({            "SearchDatetimeStr": lDatetimeNowStr, # Date of data extraction,  [str]            "SearchItems": {} # prepare the result        })        # Get List of the page        lNextPageItemCSS = 'div[data-name="Pagination"] li[class*="active"] + li a'        lNextPageItem = inWebDriver.find_element_by_css_selector(lNextPageItemCSS)        lPageCounterInt = 1 # Init the page counter        while lNextPageItem:            lPageNumberInt = int(inWebDriver.find_element_by_css_selector(css_selector='li[class*="--active--"] span').text) # Get the current page int from web and check with iterator (if not equal - retry all job)            if lPageNumberInt == lPageCounterInt:                lOfferListCSSStr = 'div[data-name="Offers"] > div:not([data-name="BannerServicePlaceInternal"]):not([data-name="getBannerMarkup"]):not([data-name="AdFoxBannerTracker"])'                lOfferList = inWebDriver.find_elements_by_css_selector(css_selector=lOfferListCSSStr)                for lOfferItem in lOfferList: # Processing the item, extract info                    lOfferItemInfo = { # Item URL with https                        "TitleStr": "3-комн. кв., 31,4 м, 5/8 этаж", # Offer title [str]                        "PriceFloat": 10000000.0, # Price [float]                        "PriceSqmFloat": 133333.0, # CALCULATED Price per square meters [float]                        "SqMFloat": 31.4, # Square meters in flat [float]                        "FloorCurrentInt": 5, # Current floor [int]                        "FloorTotalInt": 8, # Current floor [int]                        "RoomCountInt": 3  # Room couint [int]                    }                    lTitleStr = lOfferItem.find_element_by_css_selector(css_selector='div[data-name="TopTitle"],div[data-name="Title"]').text # Extract title text                    if inLogger: inLogger.info(f"Старт обработки предложения: {lTitleStr}")                    lPriceStr = lOfferItem.find_element_by_css_selector(css_selector='div[data-name="Price"]  > div[class*="header"],div[data-name="TopPrice"] > div[class*="header"]').text # Extract total price                    lURLStr = lOfferItem.find_element_by_css_selector(css_selector='a[class*="--header--"]').get_attribute("href") # Extract offer URL                    lOfferItemInfo["TitleStr"] = lTitleStr # set the title                    lPriceStr = lPriceStr.replace(" ","").replace("","") # Remove some extra symbols                    lOfferItemInfo["PriceFloat"] = round(float(lPriceStr),2) # Convert price to the float type                    #Check if Апартаменты                    if "АПАРТАМЕНТ" in lTitleStr.upper():                        lREResult = re.search(r".*, (\d*,?\d*) м, (\d*)/(\d*) эта.", lTitleStr)  # run the re                        lOfferItemInfo["RoomCountInt"] = 1 # Room count                        lSqmStr = lREResult.group(1)                        lSqmStr= lSqmStr.replace(",",".")                        lOfferItemInfo["SqMFloat"] = round(float(lSqmStr),2) # sqm count                        lOfferItemInfo["FloorCurrentInt"] = int(lREResult.group(2)) # Floor current                        lOfferItemInfo["FloorTotalInt"] = int(lREResult.group(3)) # Floor total                        lOfferItemInfo["PriceSqmFloat"] = round(lOfferItemInfo["PriceFloat"] / lOfferItemInfo["SqMFloat"],2) # Sqm per M                    else:                        lREResult = re.search(r".*(\d)-комн. .*, (\d*,?\d*) м, (\d*)/(\d*) эта.", lTitleStr) # run the re                        lOfferItemInfo["RoomCountInt"] = int(lREResult.group(1)) # Room count                        lSqmStr = lREResult.group(2)                        lSqmStr= lSqmStr.replace(",",".")                        lOfferItemInfo["SqMFloat"] = round(float(lSqmStr),2) # sqm count                        lOfferItemInfo["FloorCurrentInt"] = int(lREResult.group(3)) # Floor current                        lOfferItemInfo["FloorTotalInt"] = int(lREResult.group(4)) # Floor total                        lOfferItemInfo["PriceSqmFloat"] = round(lOfferItemInfo["PriceFloat"] / lOfferItemInfo["SqMFloat"],2) # Sqm per M                    lResult['SearchItems'][lURLStr] = lOfferItemInfo # Set item in result dict                # Click next page item                lNextPageItem = None                lNextPageList = inWebDriver.find_elements_by_css_selector(lNextPageItemCSS)                if len(lNextPageList)>0:                    lNextPageItem = lNextPageList[0]                    try:                        #lNextPageItem = WebDriverWait(lWebDriver, 10).until(EC.visibility_of_element_located((By.CSS_SELECTOR, 'div[data-name="Pagination"]')))                        #lNextPageItem.click()                        inWebDriver.execute_script("""document.querySelector('div[data-name="Pagination"] li[class*="active"] + li a').click()""")                    except Exception as e:                        print(e)                    time.sleep(0.5) # some init operations                    # wait while preloader is active. If timeout - retry all job                    lTimeFromFLoat = time.time() # get current time in float (seconds)                    lDoWaitBool = True                    while lDoWaitBool:                        lPreloaderCSS = inWebDriver.find_elements_by_css_selector(css_selector='div[class*="--preloadOverlay--"]')                        if len(lPreloaderCSS)>0: time.sleep(0.5) # preloader is here - wait                        else: lDoWaitBool = False # Stop wait if preloader is dissappear                        if (time.time() - lTimeFromFLoat) > 15: # check if timeout is more than 15 seconds                            lRetryJobBool = True # Loading error on page - do break, then retry the job                            if inLogger: inLogger.warning(f"Ожидание загрузки страницы более {15} с., Робот повторит задание сначала")                            break # break the loop                    if lRetryJobBool == True: # break the loop if RetryJobBool is true                        break                lPageCounterInt = lPageCounterInt + 1 # Increment the page counter            else:                lRetryJobBool = True                if inLogger: inLogger.warning(                    f"Следующая страница по списку не была загружена. Была загружена страница: {lPageNumberInt}, Ожидалась страница: {lPageCounterInt}")        if lRetryJobBool == False:  # break the loop if RetryJobBool is true            # Check dir - create if not exists            if not os.path.exists(os.path.join('Datasets',lResult['SearchKeyStr'])):                os.makedirs(os.path.join('Datasets',lResult['SearchKeyStr']))            # Save result in file            lFile = open(f"{os.path.join('Datasets',lResult['SearchKeyStr'],lDatetimeNowStr.replace(' ','_').replace('-','_').replace(':','_').replace('.','_'))}.json","w",encoding="utf-8")            lFile.write(json.dumps(lResult))            lFile.close()# Инициализировать Google Chrome with selenium web driverlWebDriver = WebDriverInit(inWebDriverFullPath = gWebDriverFullPath, inChromeExeFullPath = gChromeExeFullPath, inExtensionFullPathList = gExtensionFullPathList)lFilterURLStr = "https://www.cian.ru/cat.php?deal_type=sale&engine_version=2&in_polygon%5B1%5D=37.6166_55.7678%2C37.6147_55.7688%2C37.6114_55.7694%2C37.6085_55.7698%2C37.6057_55.77%2C37.6018_55.77%2C37.5987_55.77%2C37.5961_55.7688%2C37.5942_55.7677%2C37.5928_55.7663%2C37.5915_55.7647%2C37.5908_55.7631%2C37.5907_55.7616%2C37.5909_55.7595%2C37.5922_55.7577%2C37.5944_55.7563%2C37.5968_55.7555%2C37.6003_55.7547%2C37.603_55.7543%2C37.6055_55.7542%2C37.6087_55.7541%2C37.6113_55.7548%2C37.6135_55.756%2C37.6151_55.7574%2C37.6163_55.7589%2C37.6179_55.7606%2C37.6187_55.7621%2C37.619_55.7637%2C37.6194_55.7651%2C37.6193_55.7667%2C37.6178_55.7679%2C37.6153_55.7683%2C37.6166_55.7678&offer_type=flat&polygon_name%5B1%5D=%D0%9E%D0%B1%D0%BB%D0%B0%D1%81%D1%82%D1%8C+%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0&room1=1&room2=1"lJobItem = {    "SearchKeyStr": "МСК_Тверской",    "SearchTitleStr": "Москва, район Тверской",  # Title of the search [str]    "SearchURLStr": lFilterURLStr,    # URL of the CIAN search [str]}OffersByJobExtractDict(inLogger = logging, inWebDriver = lWebDriver, inJob = lJobItem)

Подведем итоги


Уважаемые роботизаторы.


Мы успешно преодолели вторую серию туториалов по созданию роботов в WEB приложениях с помощью open source pyOpenRPA. Готовый проект робота Вы можете найти в репозитории pyOpenRPA по ссылочке.
В нашем аресенале уже имеются изученные технологии упраления Desktop и WEB приложениями. В следующей статье-туториале мы остановимся на особенностях роботизированного управления мышью и клавиатурой.


Пишите комменты, внедряйте бесплатных роботов, будьте счастливы :)


До скорых публикаций!

Подробнее..

Личный сервер shadowsocks за 10 минут без затрат

05.05.2021 06:21:57 | Автор: admin

Несколько слов о shadowsocks

shadowsocks - это шифрованный сетевой туннель, клиентская часть которого предоставляет доступ приложениям к сети как SOCKS-прокси, запущенный на этом же устройстве. В некотором смысле его можно использовать как VPN, потому что клиенты поддерживают прозрачное перенаправление трафика приложений в туннель.

С самого начала shadowsocks разрабатывался как средство для обхода блокировок в Китае. С некоторого момента он получил возможность подключения внешних модулей-плагинов для оборачивания трафика shadowsocks в различные виды сетевых соединений. Это значительно расширяет границы его применения.

На хабре shadowsocks уже освещался ранее в статье от @Barafu_Albino_Cheetah.

В этом руководстве описано развёртывание сервера shadowsocks с плагином v2ray на облачном провайдере Heroku. Платформа Heroku позволяет бесплатно разворачивать небольшие веб-приложения, а плагин v2ray позволяет пропускать трафик shadowsocks внутри websocket-соединения, что в итоге и позволяет запускать всю конструкцию на Heroku.

Развёртывание сервера

В этом руководстве используется замечательный проект готового приложения для Heroku, которое реализует всё необходимое автоматически. Ссылка на GitHub проекта.

Шаг 1. Регистрация в сервисе Heroku

Для этого нужно зайти на сайт Heroku, нажать Sign up и заполнить требуемые сведения. Для регистрации нужна только электронная почта.

Шаг 2. Начало развёртывания

Нажмите на эту ссылку.

Шаг 3. Конфигурирование

В появившейся форме заполните все поля как показано на скриншоте:

В качестве значений "App Name" и AppName впишите какое-то уникальное имя приложения, одинаковое в обоих полях. Это имя станет частью доменного имени <appname>.herokuapp.com, по которому станет доступен сервис.

Вместо PASSWORD задайте свой пароль. Можно задать подлиннее и понадёжнее - Вам, скорее всего, не придётся вводить его вручную.

Кроме того, желательно поменять пути QR и путь обработчика прокси V2_Path на какое-нибудь трудноугадываемое значение.

Шаг 4. Запуск

Заполнив форму, нажмите Deploy app.

После завершения сборки и запуска QR-код с конфигурацией для мобильных устройств будет доступен по адресу

https://APPNAME.herokuapp.com/qr/vpn.png

а строка с конфигурацией в виде URL будет доступна по адресу

https://APPNAME.herokuapp.com/qr/

где APPNAME - выбранное Вами имя приложения. Если Вы меняли путь к QR-коду, то ссылка изменится в соответственно. Во второй ссылке косая черта на конце обязательна.

Всё, можно пользоваться!

Настройка мобильного клиента на примере Android

  1. Установите на Ваше устройство клиент shadowsocks и плагин v2ray к нему.

  2. Запустите приложение и добавьте новый профиль кнопкой с плюсом в правом верхнем углу. Выберите сканирование QR-кода и отсканируйте его.

  3. Выберите созданный профиль касанием.

  4. Запустите соединение нажатием на круглую кнопку внизу.

  5. Готово!

Настройка настольного клиента на примере Windows

  1. Скачайте отсюда и распакуйте shadowsocks.

  2. Скачайте отсюда плагин v2ray, подходящий под вашу платформу. Достаньте из архива файл и переименуйте его в v2ray.exe (или просто v2ray, если ваш проводник не отображает расширения файлов). Поместите его в одну директорию с shadowsocks.

  3. Запустите shadowsocks.

  4. Скопируйте конфигурационный URL вашего личного сервера shadowsocks со страницы https://APPNAME.herokuapp.com/qr/ , где APPNAME - имя приложения, которое вы выбрали.

  5. Нажмите правой кнопкой мыши на значке shadowsocks в системном трее и выберите Servers - Import URL from Сlipboard.

  6. Включите прокси, выбрав в том же контекстном меню System Proxy - Global.

  7. Готово!

Ограничения Heroku

Для приложений, запущенных на Heroku, в настоящий момент действуют следующие ограничения:

  • Временная квота работы приложений при бесплатном уровне использования составляет 550 часов в месяц.

  • Контейнер приложения переходит в спящий режим после 30 минут отсутствия запросов к нему. С одной стороны это доставляет неудобства в виде задержек ответа до полминуты после перерыва в активности. С другой стороны, это экономит отведённую временную квоту.

  • Квота на передачу данных по сети равна 2 ТБ в месяц. То есть в случае с прокси это даёт чуть меньше 1 ТБ трафика в месяц.

Подробнее..

Личный сервер shadowsocks за 10 минут без затрат (часть 2)

09.05.2021 04:04:52 | Автор: admin

shadowsocks

Повторение - мать учения, а значит я снова приведу выжимку из статьи о shadowsocks в вики.

shadowsocks - это шифрованный сетевой туннель, клиентская часть которого предоставляет доступ приложениям к сети как SOCKS-прокси, запущенный на этом же устройстве. Существуют клиентские приложения shadowsocks, которые позволяют заворачивать в него трафик как в VPN.

С самого начала shadowsocks разрабатывался как средство для обхода блокировок в Китае, и достаточно в этом деле преуспевает. С некоторого момента он стал иметь возможность подключения внешних модулей-плагинов для оборачивания трафика shadowsocks в различные виды сетевых соединений. Это значительно расширяет границы его применения.

Цель этого руководства

Это руководство предлагает ещё одну альтернативу, которая отличается от предыдущего руководства деталями:

  • Физически серверы располагаются в GCP вместо AWS, как в предыдущем методе.

  • Доступно больше локаций датацентров. В частности, есть довольно близкие к нам серверы в Германии и Франции, с которыми задержка подключения минимальна.

  • Другие ограничения: нет ограничения по трафику (в пределах так называемого fair use), нет ограничения по часам работы в месяц, но есть ограничение на общую продолжительность триала в один месяц. Однако, на это как раз можно повлиять.

Мы развернём сервер shadowsocks с плагином v2ray на облачном провайдере platform.sh. platform.sh - это PaaS-провайдер с бесплатным триалом на месяц, позволяющий бесплатно запускать веб-приложения. Плагин v2ray пропускает трафик shadowsocks внутри websocket-соединения, чтобы мы могли связываться с нашим прокси через веб-фасад платформы.

Развёртывание

От Вас потребуется только две вещи: электронная почта и способность запушить в git-репозиторий файлы.

Шаг 1. Регистрация в сервисе platform.sh

Регистрация доступна по этой ссылке. Регистрация с главной страницы сайта работает в данный момент немного криво. Добраться вручную до указанной ссылки можно так: platform.sh -> Free Trial -> Login with Auth -> Sign up. Напрямую на странице Free Trial регистрация не работает.

Если Вы регистрируетесь в сервисе повторно, чтобы начать триал сначала, Вам потребуется указать другой почтовый ящик. Однако, не обязательно иметь много почтовых адресов для этого. Достаточно воспользоваться предусмотренной стандартом субадресацией. Вы можете дописать после знака плюс к локальной части адреса (то, что до собачки), любые символы. К примеру, если Ваша электронная почта user@example.org, то вы можете также получать письма на адрес user+test1@example.org.

Шаг 2. Создание проекта

Создайте пустой проект. Такая возможность будет предложена сразу, либо можно нажать соответствующую кнопку в панели управления потом.

Шаг 3. Добавление SSH-ключей в настройках аккаунта

Пока устанавливается контейнер под новый проект, добавьте Ваш SSH-ключ в настройках аккаунта, чтобы иметь возможность отправлять файлы в git-репозиторий. Из практических соображений лучше использовать отдельный SSH-ключ для этих целей, иначе возникнет путаница с аккаунтами, ключами и репозиториями проектов.

Сделать это можно на странице Account settings -> SSH Keys -> Add public key.

Шаг 4. Клонирование репозитория

На главной странице проекта (Overview) скопируйте команду, которая клонирует git-репозиторий проекта. Команда клонирования с адресом репозитория доступна в правом верхнем углу, по кнопке GIT.

Шаг 5. Наполнение репозитория

Пришло время положить файлы в репозиторий проекта из моего репозитория и запустить сборку проекта. Сделать это можно следующим образом запуская команды из директории проекта:

git remote add upstream https://github.com/Snawoot/shadowsocks-platform.sh.gitgit fetch upstreamgit reset --hard upstream/mastergit push --force-with-lease

После этого проект будет собран и уже развёрнут.

Подсказка: явно задать используемый SSH-ключ можно установкой вот такой переменной окружения в своей консоли:

export GIT_SSH_COMMAND='ssh -i /path/to/private/key -o IdentitiesOnly=yes'

Шаг 6. Конфигурация

Осталось настроить сервер. В панели управления проектом platform.sh выберите окружение Master в выпадающем списке в правой верхней части экрана. После этого перейдите в настройки переменных проекта (SETTINGS -> Variables). Добавьте новую переменную env:SS_PASSWORD кнопкой Add справа как показано ниже:

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

Кроме этого, желательно ещё определить значение переменной env:V2_PATH, назначив какой-то трудноугадываемый путь вроде такого: /dcfaeb49-33ec-4574-af45-7b846d182522. Если не указано, то по умолчанию оно равно /v2.

После изменения переменных сервер сразу автоматически пезапустится и будет готов к работе.

Настройка клиентов

Про установку клиентских приложений можно прочитать в предыдущей статье. В этом решении нет автоматической генерации URL или QR-кода с настройками. Ручные настройки таковы:

Параметр

Значение

server

Домен из URL, по которому доступен проект (отображается в Overview проекта)

server_port

443

password

Значение переменной env:SS_PASSWORD

method

chacha20-ietf-poly1305

plugin

v2ray

plugin_opts

path=V2_PATH;host=DOMAIN;tls, где DOMAIN - домен из URL проекта, а V2_PATH - значение переменной env:V2_PATH, либо /v2, если она не задана.

Конец

Подробнее..

Бесплатная онлайн-конференция по гипер-казуальным играм

28.01.2021 16:20:38 | Автор: admin

7 февраля 2021 года (воскресенье), с 13:00 и до 17:00, состоится Бесплатная онлайн-Конференция по гипер-казуальным играм!

Вас ждут 4 часа 2 потока вещания:

  • Выступления настоящих экспертов;

  • Прожарка проектов участников в прямом эфире;

  • Конкурс лучших проектов.

Для кого это мероприятие?

  • Для всех, кто хочет работать в геймдеве;

  • Для сотрудников игровых студий;

  • Для инди-разработчиков и всех, кто хочет разрабатывать собственные игры.

!!Если у вас вразработке есть гипер-казуальная игра(или вышедшая не ранее зимы 2020г.), вы можете принять участие вПрожарке проектов?. Вас ждут: полезный фидбэк, потенциальная аудитория вашей игры и крутые призы!Заявки принимаются до 31 января 2021 (включительно).

Среди призов:Смартфон Samsung Galaxy M31 6/128GB для тестирования своих новых ГКИ игр на Android.

Участие в мероприятии абсолютно бесплатно. Все подробности и регистрация доступныпо ссылке >>>

Регистрируйтесь на участиев Прожарке проектов? >>>

Подробнее..

Бесплатная онлайн-конференция Нарратив в играх

28.02.2021 14:11:42 | Автор: admin

21 марта 2021 года(воскресенье), с 12:00 и до 19:00, состоится Бесплатная онлайн-Конференция: Нарратив в играх.

Мероприятие посвящено всем, кто по-настоящему любит игры, хороший сюжет, и хочет продолжить свой рост в игровой индустрии или только начать свой путь в геймдев.

Кроме интересных лекций вас ждет конкурс на лучший сценарий игры (и не только) с крутыми призами.

На конкурс принимаются видео-игры и настолки, выпущенныев 2020-2021году (даже в Раннем доступе или в виде демо/beta) на русском языке.

Для участия в конкурсе необходимоподать заявкудо 05.03.2021

Участие в мероприятии абсолютно бесплатно.
Организатор конференции: Центр развития компетенций в бизнес-информатике Высшей школы бизнеса НИУ ВШЭ, при поддержке WN Conference, Talents in Games и Союза Литераторов РФ.
Регистрация на конференцию здесь>>>

Подробнее..

Бесплатные хостинги для веб-разработчиков

27.12.2020 00:14:52 | Автор: admin

Привет, Хабр!

Одним из наиболее популярных направлений разработки сегодня является веб. И когда нужно разместить свой проект где-нибудь, кроме localhost, многие сталкиваются с трудностями - ведь хостинг должен быть быстрым, удобным, и, желательно, бесплатным :)

В этом списке вы найдете 15 бесплатных сервисов, где легко сможете разместить свой проект и не заплатите ни копейки. Погнали!

Vercel

Данный сервис позволяет собирать и размещать статические веб-сайты на различных фреймворках (поддерживаются как JS-фреймворки, так и, например, генераторы статических сайтов - Hexo, Hugo, Jekyll и другие). Для каждого проекта выделяется несколько бесплатных доменных имен третьего уровня, есть возможность предпросмотра сборки.

Вот что включает в себя бесплатный тариф:

  • 50 пользовательских доменов

  • 100 Гб файлового пространства

  • 100 Гб ежемесячного трафика

  • Неограниченное количество проектов

  • CLI-интерфейс

  • Serverless, CDN, CI/CD

Netlify

Netlify - прямой конкурент Vercel. Однако, кроме функций, которые предоставляет предыдущий сервис, тут на бесплатном тарифе присутствует:

  • Обработка до 100 отправленных форм в месяц

  • До 1000 авторизованных пользователей в месяц

  • Аналитика работы сайта

Heroku

Heroku позволяет запускать Full Stack приложения в контейнерах (так называемых Dynos). Поддерживается большое число языков программирования и фреймворков. Главный недостаток - после получаса бездействия проекты, размещенные на бесплатном тарифе, "засыпают", а повторный запуск контейнера требует определенного времени.

На стартовом тарифе доступны:

  • 550 часов/месяц работы Dynos (1000 после привязки банковской карты)

  • 512 Мб ОЗУ на 1 контейнер

  • CI/CD, CLI

  • По 1 домену на 1 контейнер

Stormkit

Stormkit позволяет размещать только проекты на JavaScript. Бесплатно доступны:

  • 1 проект

  • 50 Гб трафика

  • Неограниченное количество доменов

  • Неограниченное количество сборок

Hostman

Полноценный хостинг с поддержкой бэкенда и баз данных. Однако бесплатный тариф позволяет разместить до 10 веб-сайтов, и то статических:

  • 100 Гб/месяц трафика

  • Бесплатные домены и SSL

  • CI/CD, CDN

Glitch

Glitch позиционирует себя как коллаборативный сервис для упрощенной разработки веб-сайтов. В основном здесь находятся проекты на NodeJS, но поддерживается ряд других языков. Приложения запускаются в контейнерах, как на Heroku, и тут так же доступно 1000 бесплатных часов работы приложений в месяц. Однако, если на Heroku проекты заливаются через CLI или Git, здесь присутствует браузерная IDE и терминал.

Repl.it

Подобен предыдущему сервису. Бесплатно предоставляется 0.5 Гб ОЗУ и 0.5 Гб дискового пространства.

Surge

Хостинг статических сайтов. Бесплатно доступны:

  • 1 сайт с 1 доменом третьего уровня

  • Неограниченное количество сборок

Firebase

PaaS-сервис от Google, позволяющий разработать бэкенд без написания кода, а также разместить свой статический веб-сайт. Вот что входит в стартовый тариф:

  • 10 Гб дискового пространства

  • 360 Мб трафика/день

  • Базы данных

  • Serverless

  • Тестирование

  • Многое другое

Render

Сервис для размещения статических веб-сайтов. Бесплатно доступны:

  • 100 Гб трафика в месяц

  • CDN, CI/CD, SSL

GitHub Pages

С помощью этого инструмента из любого репозитория GitHub можно развернуть статический веб-сайт. Поддерживается Jekyll, доступен 1 бесплатный домен 3 уровня, SSL, неограниченный трафик.

begin

Хостинг для практически любых веб-приложений на NodeJS или Deno. Бесплатно доступно 5 веб-сайтов, поддерживается Serverless.

Deta

Достаточно интересный проект, предоставляющий возможность размещения веб-приложений на Python и NodeJS. К каждому приложению подключается NoSQL база данных. Главный минус - все взаимодействие с сервисом осуществляется через CLI. Бесплатно доступны:

  • 2 Гб дискового пространства, 2 Гб на базу данных

  • 50 000 обращений к контейнерам в месяц

  • 25 000 обращений к БД в месяц

Fly

Очередной сервис, позволяющий запускать приложения в виртуальных машинах. Со стартовым тарифом вы получите:

  • 8 миллионов секунд работы VM в месяц (хватит примерно на 3 VM, запущенных постоянно)

  • 160 Гб ежемесячного трафика

  • 10 активных SSL-сертификатов

Fleek

Позволяет размещать статические сайты, перед этим собирая их на ряде генераторов статических сайтов (если нужного нет в списке, можно выбрать Docker-образ, в котором будет осуществляться сборка, и указать все данные для нее). Бесплатный тариф имеет следующие ограничения:

  • 3 Гб дискового пространства

  • 50 Гб ежемесячного трафика

  • 250 минут на сборку в месяц

Заключение

На этом подборка бесплатных хостингов заканчивается. Надеюсь, вы уже нашли себе подходящий вариант и готовы разместить свое приложение.

Буду рад, если вы предложите свои варианты - достойные сервисы будут также внесены в этот список.

Подробнее..

Онлайн-Круглый стол Как создать свой игровой бизнес

11.08.2020 14:11:07 | Автор: admin
19 августа (Среда), в 19:00, состоится бесплатный Онлайн-круглый стол Как создать свой игровой бизнес.



Вы создаете собственные игры или давно хотите начать этим заниматься? Вы хотите, чтобы разработка игр приносила вам самореализацию и прибыль, но не знаете, как этого добиться?
На ивенте вы узнаете ответы на самые важные вопросы по этой теме.
Круглый стол соберет людей, которые шли к успеху разными путями и добились цели.

Мы приглашаем инди-разработчиков, студентов, сотрудников игровых компаний, бизнесменов и всех, кто хочет делать собственные игры, начать свой путь в игровой индустрии, а также тех, кто просто любит игры!

Во время онлайн-трансляции будет проводиться конкурс на самый интересный вопрос по теме круглого стола.

У каждого участника мероприятия будет возможность выиграть один из призов от компании-партнера "ZAVOD GAMES":

  1. Веб-камера AVerMedia Streamer
  2. Игровой стрим микрофон Redragon Quasar (2шт.)

Участие в мероприятии абсолютно бесплатно.

Все подробности и регистрация доступны по ссылке >>>
Подробнее..

Бесплатный онлайн-Лекционный вечер по геймдизайну

08.09.2020 16:22:26 | Автор: admin
16 сентября (Среда), в 18:00, состоится бесплатный Лекционный вечер по геймдизайну, в онлайн-формате.



Вас ждут три интересные лекции:

1) Карьера геймдизайнера: как понять чего вы хотите на самом деле и достичь этого.
Спикер: Константин Сахнов совладелец издательства JustForward, игровой продюсер.

2) Геймдизайн: что делает игру интересной.
Спикер: Владимир Агарев Креативный продюсер в компании Gaming Point.

3) Работа геймдизайнером в игровой компании.
Спикер: Георгий Миронов Game Designer в 1C Game Studios.

Мы приглашаем инди-разработчиков, сотрудников игровых компаний, студентов, всех, кто хочет делать собственные игры или играет в игры.

Участие в мероприятии абсолютно бесплатно.

Все подробности и регистрация доступны по ссылке >>>
Подробнее..

Бесплатный онлайн-круглый стол Тенденции игрового рынка 2021. Какие игры делать в новом году

12.12.2020 14:20:00 | Автор: admin
23 декабря 2020 года (среда), в 19:00, состоится Онлайн-круглый стол Тенденции игрового рынка 2021. Какие игры делать в новом году!

На круглом-столе вы узнаете о главных трендах в разных сегментах игрового рынка и сможете принять участие в фото-конкурсе Новогодний дух геймдева.



Вы узнаете:

  • Какие платформы в тренде? Какая экономическая привлекательность этих платформ ожидается в наступающем году?
  • Какие жанры в тренде? Какая экономическая привлекательность жанров ожидается?
  • Как обстоят дела у инди? На что делать упор инди-разработчикам? На какие сегменты лучше целить?
  • Как обстоят дела у небольших команд разработчиков (не инди, но и не корпорация)? Какие тренды, куда целить?
  • Что происходит с российским геймдевом? Куда всё движется?




Мы приглашаем инди-разработчиков, сотрудников игровых компаний, студентов и ВСЕХ, кто хочет делать собственные игры или играет в игры!

Участие в мероприятии абсолютно бесплатно. Все подробности на сайте>>>
Подробнее..

Как нас разводят с (бес)платной подпиской

16.03.2021 02:15:34 | Автор: admin

Речь пойдёт об очень известном и уважаемом сайте - ShutterStock, одном из самых популярных фотобанков в мире. 360 млн изображений в базе! Я оформил там "бесплатную" пробную подписку, а потом бился с ними за возврат денег, списанных с моей карты без спросу. Осторожно, под катом много скриншотов!

TL;DR>

С мой карточки сначала сняли сумму за месяц подписки, потом грозили ещё штрафом за отмену подписки - пришлось надавить, чтобы всё вернули.

Начиналось всё очень радужно. Мне как раз не хватало пары специфических картинок для дизайна, которые есть только в фотостоках, и тут на глаза попалась реклама:

Получите 10 изображений бесплатно

Прямо сейчас оцените в деле бесплатную пробную подписку без каких-либо рисков и загрузите 10 изображений. Вы сможете отменить подписку в любой момент в течение первого месяца и ничего не платить, но изображения все равно останутся у вас

  • Прямо сейчас оцените в деле бесплатную пробную подписку без каких-либо рисков и загрузите 10 изображений. Вы сможете отменить подписку в любой момент в течение первого месяца и ничего не платить, но изображения все равно останутся у вас

  • Обратите внимание: "Вы ничем не рискуете" и "...можете в любой момент отменить". Или вот:

    Прекрасно же! То, что надо. Берём подписку, они, конечно же, требуют ввести реальную карту. Скачиваем пару картинок, заходим в аккаунт, видим там переключатель - "Авто-продление подписки". Ха! Выключаем его.

    И мы же умные, жизнью битые - убираем свои данные из методов оплаты, вежливо говорим монитору "большое спасибо" и расслабляемся. Тем более, что у них ясно написано:

    У нас есть помесячные планы, которые можно отменить в любой момент, просто отменив автопродление. Покупатели с годовым планом - для отмены свяжитесь с нами.

    Отлично!

    Но расслабляемся ненадолго. Потому что меньше, чем через месяц приходит смс-ка. Дзынь!

    Я был крайне удивлён и озадачен, но вслух выражал своё негодование менее цензурно. Ведь я точно помнил, что выключил авто-продление! Немедленно пишу им гневное послание "верните деньги которые у бедных артистов спёрли, маймуно веришвилло!" Пишу...

    Пишу в чат поддержки - не отправляется. Пишу в форму для почты - не отправляется. Самтхинг вент вронг, м***фака! Все операторы заняты, звоните в Спортлото! Причём в лучших традициях usability - текст письма теряется и при неудачной отправке, а о том, что чат не работает, ты узнаёшь только открыв его и начав набирать текст (внизу видео про это).

    День-два проходят в попытках всё же достучаться в разное время суток до чата или формы e-mail, естественно, безрезультатно, и до меня начинает доходить, что дело нечисто. Звонить я им не стал.

    Вместо этого нашёл их e-mail (на их сайте он не указан нигде - но Гугл находит где-то ещё). Пишу им снова вежливую просьбу вернуть деньги, которые они явно по ошибке списали. И получаю следующий великолепный ответ:

    Спасибо, что написали нам! Ваш план подразумевает оплату $29/в месяц со скидкой за использование в течение года, по сравнению с обычной платой $49/месяц.

    Поэтому мы готовы отменить вашу подписку только после того, как вы пришлёте ваше письменное согласие оплатить ещё и эту разницу в $20.

    Всегда рады вам помочь, обращайтесь.

    Я был обескуражен. Мало того, что они внаглую списали с меня за месяц, так ещё и хотят $20 отступных, чтобы прекратить эти грабежи?

    Написал следующее письмо, в котором упомянул ещё раз, что плата была списана у меня против моей воли - я же выключил автопродление! Попутно расписал, что я не хотел бы считать это грабежом или вымогательством, но если надо, то придётся. И заодно записал скрин-видео, где видно, как у них замечательно формы связи работают.

    На что получил ответ:

    Понимаете, Майк, вы не то выключили. Тот большой красный выключатель - он про авто-продление платной подписки, которое сработало бы через год.

    А вам надо было зайти в раздел "Планы" и нажать там хитроспрятанную ссылку "Ранняя отмена подписки". И вот тогда бы мы всё отменили, мамой клянусь.

    Но так и быть, деньги вернём, только больше ничего не качайте.

    И я думаю: вот я дурак, надо-то было всего лишь нужную ссылку кликнуть. Пойду, посмотрю, как это умные люди делают.

    Ан нет. Ссылка "Ранняя отмена подписки" ведёт на много текста, где английским по белому (весь остальной сайт - на русском) сказано: If you have an annual subscription and need to cancel early, pleasecontact usfor further assistance. А про это я уже писал выше - никакой метод contact us не работает. Так что никакой "ранней отмены подписки" бы и не случилось. Замкнутый круг:

    Дальше был счастливый финал - деньги вернули. Осадочек остался, это было прямо перед Новым годом, где каждая копейка на счету. Но как такое возможно в цивилизованной стране? Обратите внимание на страницу оплаты.

    1) Вы соглашаетесь с тем, чтоShutterstock автоматически продлит вашу годовую подписку и будет взимать за нее ежемесячную плату (29$) с использованием выбранного вами способа оплаты за 4 дня до окончания каждого месяца,

    2) Shutterstock сохраняет платежную информацию в зашифрованном виде для будущих заказов

    То есть вроде как они все формальности соблюдают. Про отмену тут ни слова! И второй нюанс - даже если бы я удалил информацию о кредитке из системы, они бы всё равно с неё денег списали. Если бы я наивно ждал конца месяца - тоже всё бы списали, причём за 4 дня до конца месяца. Всё для моего удобства и надёжности!

    В общем, цивилизованность спасла меня от ограбления, но не спасла от попытки это сделать.

    Друзья и коллеги, будьте осторожны - бесплатный сыр бывает, но прежде, чем его взять, мышеловку стоит очень тщательно изучить. TANSTAAFL!

    P.S. Моё ругательное письмо на плохом английском, которое подействовало :)
Подробнее..

Настройка собственного почтового сервера

26.02.2021 20:18:58 | Автор: admin

Есть три основных шага, чтобы установить и настроить собственный почтовый сервер.

  • Настройка IP и DNS

  • Выбор и запуск приложения почтового сервера

  • Добавление своего почтового сервера в белые списки

Настройка IP и DNS

Обеспечение внешнего статического IP-адреса, публичного домена и записи PTR

Это основные требования для запуска собственного почтового сервера.

  • Публичный статический IP-адрес
    IP-адрес почтового сервера должен быть общедоступным и постоянным во времени. Убедиться в этом можно у хостинг или Интернет-провайдера.

  • Доменное имя указывает на IP
    DNS-запись публичного доменного имени почтового сервера должна указывать на этот IP-адрес. Им можно управлять в настройках DNS провайдера доменного имени.

  • IP указывает на доменное имя
    Самое главное, обратная DNS-запись (именуемая PTR) должна указывать на доменное имя почтового сервера по IP-адресу. Можно попросить своего хостинг-провайдера или поставщика интернет-услуг настроить его. Его можно легко проверить по IP-адресу онлайн (например, тут), или с помощью команды nslookup в Windows и команды host в системах на основе UNIX.

Настройка MX записи в DNS

Запись почтового обмена (MX) указывает почтовый сервер, ответственный за прием сообщений электронной почты от имени домена.

Например, если наш домен - mycompany.com, почтовый сервер - mail.mycompany.com, то запись DNS для mycompany.com будет:

Type

Host

Value

Priority

TTL

MX

@

mail.mycompany.com

10

1 min

где:

  • Priority (приоритет) используется, когда в домене более одного почтового сервера.

  • TTL (время жизни) можно установить любое предпочтительное значение, а наименьшее значение используется для применения конфигурации DNS как можно скорее при отладке настроек.

Настройка DKIM записи в DNS

Почта, идентифицированная ключами домена (DKIM) это протокол безопасности электронной почты, который прикрепляет зашифрованную цифровую подпись к электронному письму. Принимающий сервер проверяет его с помощью открытого ключа, чтобы убедиться, что электронное письмо не было подделано.

Понадобятся приватный и открытый ключи. Их можно создать с помощью онлайн-инструментов, например Power DMARC Toolbox - DKIM Record Generator, или с помощью команд OpenSSL (приведен пример для Windows):

  • Создать приватный ключ
    openssl.exe genrsa -out private.key 2048

  • Создать публичный ключ из приватного
    openssl.exe rsa -in private.key -pubout -outform der 2>nul | openssl base64 -A > public.key.txt

И запись DNS будет выглядеть так:

Type

Host

Value

TTL

TXT

selector._domainkey

v=DKIM1; k=rsa; p=public_key

1 min

где:

  • selector - самостоятельно выбранный идентификатор (например, mysrv), который будет использоваться в приложении почтового сервера (смотрите ниже).

  • public_key - открытый ключ, закодированный алгоритмом base64 (содержимое public.key.txt).

  • TTL (время жизни) имеет то же значение, что и в предыдущем разделе.

Настройка SPF записи в DNS

Инфраструктура политики отправителя (SPF) это стандарт проверки подлинности электронной почты, который проверяет IP-адрес отправителя по списку авторизованных IP-адресов владельца домена для проверки входящей электронной почты.

Тут запись DNS будет выглядеть так:

Type

Host

Value

TTL

TXT

@

v=spf1 a mx include:relayer_name -all

1 min

где:

  • relayer_name - имя необязательного внешнего почтового сервера-ретранслятора (смотрите ниже). Если не нужно - убирается вместе с "include:".

  • TTL (время жизни) имеет то же значение, что и в предыдущем разделе.

Можно использовать удобный онлайн-генератор записи SPF.

Дополнительные записи DNS

Некоторые поля не обязательны, но желательно иметь.

  • DMARC
    Запись доменной проверки подлинности сообщений, отчетов и соответствия (DMARC) позволяет собственному почтовому серверу декларировать политику того, как другие почтовые серверы должны реагировать на недостоверные сообщения от него.

  • BIMI
    Индикаторы бренда для идентификации сообщений (BIMI) это новый стандарт, созданный для того, чтобы упростить отображение логотипа рядом с сообщением. Кроме того, BIMI предназначен для предотвращения мошеннических электронных писем и улучшения доставки.

  • TLS-RPT
    TLS-отчетность (TLS-RPT) дает ежедневные сводные отчеты с информацией о электронных письмах, которые не зашифровываются и не доставляются.

Все эти записи могут быть созданы с помощью Power DMARC Toolbox.

Выбор и запуск приложения почтового сервера

Конечно, хостинг должен позволять устанавливать программное обеспечение. Можно использовать любое подходящее приложение для почтового сервера. Например, есть бесплатный hMailServer для Windows, который предоставляет все необходимые функции с минимальным использованием ресурсов. Для систем на базе UNIX существует множество бесплатных почтовых серверов, таких как Exim Internet Mailer или iRedMail.

Если вы знаете ещё хорошие программы, то поделитесь ими в комментариях. Подробный обзор выбора такого программного обеспечения, конечно, заслуживает отдельной статьи.

Инициализация

Когда программное обеспечение выбрано и установлено, самое время его настроить.

  • Домен и пользователи
    Нужно добавить домен и начальный набор пользователей почтового сервера.

  • Безопасность
    Чтобы обеспечить соответствующий уровень безопасности, мы должны добавить сертификат SSL для домена.

  • Подпись сообщений
    Далее, следует настроить DKIM. Нужно указать полученные выше приватный ключ и селектор. Кроме того, методы заголовка и тела должны быть установлены на расслабленный, алгоритм подписи должен быть установлен на SHA256, иначе на некоторых SMTP серверах не проходит проверка (например, google).

  • Защита от спама
    Наконец, нужно настроить антиспам-проверку специальными узлами черных списков, такими как spamhaus.org, чтобы защитить пользователей почтового сервера от нежелательных сообщений.

Протоколы электронной почты

Нужно настроить три протокола электронной почты, которые необходимы для её отправки и получения.

SMTP

SMTP используется для приема входящей и исходящей почты с/на другие почтовые серверы. И это позволяет пользователям домена отправлять свои сообщения.

  • 25 порт
    Этот порт необходим для управления входящими подключениями от других почтовых серверов. Метод безопасности следует установить в STARTTLS.

  • 587 порт
    Он нужен для почтовых клиентов собственного почтового сервера. Метод безопасности следует установить в STARTTLS.

  • 465 порт
    Он не является официальным и может потребоваться для старых почтовых клиентов. И метод безопасности следует установить в SSL/TLS.

POP3, IMAP

POP3 и IMAP используются отдельными почтовыми клиентами, такими как Outlook на ПК или любой почтовый клиент на мобильных телефонах. Это позволяет пользователям домена управлять своими сообщениями.

Порт 993 следует использовать для защищенных соединений IMAP, а порт 995 - для POP3. Для обеспечения совместимости с большинством клиентов метод безопасности следует установить в SSL/TLS (не STARTTLS).

Также можно настроить порты 143 для IMAP и 110 для POP3, но они не шифруются и сейчас их уже мало кто использует.

Проверка

Итак, когда все настроено, нужно протестировать почтовый сервер, отправив письмо кому-нибудь из списка пользователей. Кроме того, в некоторых почтовых приложениях есть функция самодиагностики (см. ниже пример от hMailServer).

Теперь пора проверить отправку на внешний адрес.

Аккаунт Gmail.com

Если есть учетная запись Gmail.com (что наверняка), можно отправить тестовое письмо на свой адрес Gmail. Затем открываем свою электронную почту в браузере и нажимаем Показать подробности.

Если есть подписано: домен, подпись DKIM настроена правильно. Если есть отправлено по почте: домен, SPF в порядке.

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

Также, в Outlook можно видеть те же заголовки в свойствах сообщения.

Специальные онлайн-сервисы

Существует множество онлайн-сервисов, которые могут проверять отправку электронной почты. Ниже приведены некоторые из них.

  • AppMailDev
    Этот сервис позволяет тестировать конфигурацию почтового сервера, такую как DKIM и SPF, отправляя электронное письмо на указанный сгенерированный почтовый адрес. Нужно просто следовать инструкциям на экране и результаты теста будут отображены там же.

  • DKIMValidator
    Предоставляет те же функции, что и предыдущая служба. Результаты тестирования будут отправлены на адрес отправителя.

  • HAD Email Auth Tester
    Чтобы проверить отправку сообщения здесь, нужно отправить специальное сообщение на tester@email-test.had.dnsops.gov. Результаты тестирования будут отправлены на адрес отправителя.

  • PowerDMARC
    Этот сервис предоставляет только облегченную проверку всех атрибутов, но у него есть удобные инструменты, указанные в ссылках выше.

Итак, если всё настроено правильно, но сервер присутствует в чёрных списках спама, нужно внести его в белый список.

Добавление почтового сервера в белые списки

Итак, если всё вышеперечисленное настроено правильно, другие почтовые серверы по-прежнему могут отмечать сообщения как спам и отклонять их. Это бывает, когда IP (или его диапазон) домена попадает в какой-то черный список. Чаще всего причиной этого является использование соседних IP-адресов для рассылки спам-сообщений.

Внесение в белый список в публичных источниках

Итак, сначала проверим IP (и, если необходимо, домен) онлайн на наличие в каких-либо черных списках. Его можно проверить в любом онлайн-чекере, который можно найти через поиск. Например, MXToolBox проверяет самые популярные черные списки. Также, multirbl.valli.org показывает много источников черного списка и доверие к каждому из них.

Затем нужно последовательно просмотреть каждый элемент в результатах и прочитать рекомендации о том, как внести IP-адрес в белый список в конкретном источнике черного списка. Но не все из них могут позволить это сделать бесплатно, например, немецкий UCEPROTECT-Network.

Кстати, на тут на habr обсуждалась автоматизация мониторинга IP в блэклистах.

Внесение в белый список определенных почтовых серверов

Некоторые серверы, такие как Outlook, имеют свои собственные черные списки. Проверка проста - приложение почтового сервера уведомит о неудачной доставке в почтовом клиенте. Большинство почтовых серверов предоставляют в ответе URL-адреса разблокировки. Таким образом, нужно открыть такой URL и следовать инструкциям, например, как этот.

Обход черных списков

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

  • MailerSend
    Один из самых дешевых - позволяет бесплатно отправлять 20 тысяч писем в месяц и имеет низкую стоимость дополнительной отправки. Но есть особенность: поля CC и BCC пока не поддерживаются.

  • SendInBlue
    Это еще один хороший сервис, который позволяет бесплатно отправлять 9 тысяч писем в месяц с лимитом 200 в день. Но есть нюансы: встроенное отслеживание электронной почты нельзя отключить и высоковатая цена сверх бесплатного лимита.

В каждой службе нужно зарегистрироваться и получить подтверждение почтового домена. После подтверждения, каждый из них дает указания на то, что должно быть настроено для DNS (DKIM, SPF и DMARK) и почтового приложения (адрес сервера ретрансляции SMTP, порт и учетные данные).

Заключение

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

Подробнее..

Лекционный вечер по геймдизайну

30.03.2021 10:05:43 | Автор: admin

21 апреля 2021 года (среда) в Центре развития компетенций в бизнес-информатике Высшей школы бизнеса НИУ ВШЭ состоится Лекционный вечер по геймдизайну.

C 19:00 и до 22:00 наши преподаватели, эксперты игровой индустрии, будут делиться с гостями мероприятия своим опытом.

На мероприятии вас ждут три лекции:

  1. Как сделать интересную и прибыльную игру (Константин Сахнов, совладелец издательства JustForward).

  2. Как сделать интересную игровую механику (Владимир Агарев,креативный продюсер в компании Gaming Point).

  3. Как делается геймдизайн масштабной российской рпг (Юлия Черненко, гейм-дизайнер в Owlcat Games)

    Начало регистрации: 18:30. Начало мероприятия: 19:00

    Мероприятие пройдет в смешанном формате. Посетить его можно как очно, так и дистанционно. Если вы не сможете прийти очно или находитесь не в Москве, то сможете подключиться к лекциям в онлайн-формате: Zoom + трансляция нанашем канале на youtube.

    Более подробная информация и регистрация на мероприятие здесь>>>

Подробнее..

Бесплатный онлайн-фестиваль Хочу в геймдев

18.06.2021 20:15:41 | Автор: admin

Приглашаем наонлайн-фестиваль "Хочу в геймдев", который проводится в рамках Russian Creativity Week и состоится8, 9 и 11 июля 2021 г.

Организатор - Центр развития компетенций в бизнес-информатике Высшей школы бизнеса НИУ ВШЭ.

Участие для всех абсолютно бесплатное, нужна только предварительная регистрация.

Вас ждет:

3 дня посвященные геймдеву;

4 лекции от экспертов игровой индустрии;

16 участников пройдут отбор и представят идеи своих игр в прямом эфире - вы можете быть одним из них.

100000 грантами призовой фонд от издательства Geeky House и Zavod Games.

Подробности и регистрация на сайте:https://games.hse.ru/lecture/

Подать свою игру на питч можно здесь, до 1 июля 23:59:https://docs.google.com/forms/d/e/1FAIpQLSfvI5WH8T6M-hgcNEkSJ3pOC3umlfyu25FYvlpdaldjFOPByg/viewform

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru