Дворниченко Михайло

Blog

monoscreen0

Рішення, як автоматизувати отримання суми збору з МоноБанки без API

Вітаю! Поділюсь простим рішенням, як можна отримати значення суми збору з МоноБанки та інтегрувати це на свій сайт, чат-бот та інші сервіси.

Задача виникла, коли не вдалося знайти адекватне рішення в API МоноБанку. У них є механізм Webhook, який повинен відправляти значення суми після поповнення банки, але у мене він так і не запрацював. Можливо я щось не так зрозумів, можливо поспішав чи були проблеми на сервері, але реєстрація listener’а не вдалася. Звернувшись до підтримки Моно, отримав відповідь тільки через добу і відповідь була в стилі “Читай документацію там все є”. Поки чекав, встиг написати інше рішення, яке не таке гарне і працює “Тупо в лоб”, але працює і мою задачу виконує 🙂

Ідея:

Основна ідея проста — це прямий парсинг сторінки я. Були побоювання, що МоноБанк може використовувати динамічну генерацію CSS-класів для захисту від парсингу, але, на щастя, всі класи статичні і все дуже просто.

План дій:

  1. Написати веб-сервіс на Python, який буде отримувати запит з ідентифікатором сторінки МоноБанки, далі  буде парсити її та повертати JSON з результатами.
  2. На хостингу написати PHP-скрипт, який через CRON кожні 30 хвилин буде робити запит на веб-сервіс. Отриманий результат буде зберігати в базу або файл для подальшого використання. Алет тут вже як вам буде зручно

Навіщо cron і проміжне збереження?

Чому роблю через CRON і зберігаю значення в проміжну базу, а не роблю запит на сервіс кожного разу, коли користувач заходить на сайт? Для того, щоб випадково не почати “штурмувати” сторінку МоноБанку запитами кожного разу, коли хтось відкриває сайт. Не потрібно створювати зайве навантаження. Оновлення даних раз на пів години або навіть на годину — цілком нормальний результат для дублювання і це дозволяє отримувати актуальні дані, не перевантажуючи систему.

Що нам знадобиться:

  • Простий VPS для розміщення сервісу. Я використовую дешевий VPS на 1Гб оперативки, 1CPU та 20Гб диск;
  • Flask для веб-сервісу;
  • Selenium та Chrome WebDriver для парсингу сторінки.

Як працює:

На вхід Flask веб-сервісу подається GET-запит з ідентифікатором МоноБанки та токеном. Токен потрібен, щоб захистити сервіс від випадкових ботів та “незваних гостей”. Виключно для прикладу я прописав токен в коді.
На виході отримаємо JSON з назвою організації, назвою збору та зібраною сумою. За бажанням можна додати додаткові поля для парсингу.

Ось тут ідентифікатор банки:

Код Flask сервісу:

from flask import Flask, request, jsonify
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import re

app = Flask(__name__)
app.config['JSON_AS_ASCII'] = False
ACCESS_TOKEN = "YOUR_TOKEN_HERE"

def get_jar_data(account_code):
    chrome_options = Options()
    chrome_options.add_argument("--headless")
    chrome_options.add_argument("--no-sandbox")
    chrome_options.add_argument("--disable-dev-shm-usage")
    service = Service("/usr/local/bin/chromedriver")
    driver = webdriver.Chrome(service=service, options=chrome_options)

    try:
        driver.get(f'https://send.monobank.ua/jar/{account_code}')
        wait = WebDriverWait(driver, 20)

        amount = wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, '.stats-data-value'))).text
        name = wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, 'div.field.name h1'))).text
        owner_info = wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, 'div.field.jarOwnerInfo span'))).text
        clean_amount = re.sub(r'[^\d.,]', '', amount)

        return {"name": name, "owner_info": owner_info, "balance": clean_amount}

    except Exception as e:
        return {"error": f"Error: {e}"}

    finally:
        driver.quit()

@app.route('/get_balance', methods=['GET'])
def get_jar_data_route():
    account_code = request.args.get('account_code')
    access_token = request.args.get('access_token')

    if not account_code or not access_token:
        return jsonify({"error": "Error account_code и access_token"}), 400
    if access_token != ACCESS_TOKEN:
        return jsonify({"error": "Wrong Token"}), 403

    data = get_jar_data(account_code)
    return jsonify(data), (500 if "error" in data else 200)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)


Code language: Python (python)

В результаті отримаємо таку відповідь:

{
“balance”: “20214”,
“name”: “РЕБ для саперів 68 батальйону ТРО”,
“owner_info”: “БФ \”СЕЙВ ЮКРЕЙН ЛАЙФ\” created a fundraiser”
}Code language: JSON / JSON with Comments (json)

І це відповідає значенню на сторінці:

PHP скрипт для  CRON

Скрипт ставлю на виконання в CRON кожні 30 хвилин. Для прикладу зберігаю результат в json файл:

<?php
$url = ‘http://YOUR_SERVER_IP:PORT/get_balance?account_code=YOUR_ACCOUNT_CODE&access_token=YOUR_ACCESS_TOKEN’;

$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 60,
CURLOPT_TIMEOUT => 120,
CURLOPT_FAILONERROR => true,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false
]);

$response = curl_exec($ch);

if (curl_errno($ch)) {
file_put_contents(‘error_log.txt’, ‘Request error: ‘ . curl_error($ch) . “\n”, FILE_APPEND);
} elseif (!empty($response)) {
$decoded_response = json_decode($response, true);
if (json_last_error() === JSON_ERROR_NONE) {
file_put_contents(‘curcollect.json’, json_encode($decoded_response, JSON_PRETTY_PRINT));
} else {
file_put_contents(‘error_log.txt’, “JSON decoding error: ” . json_last_error_msg() . “\n”, FILE_APPEND);
}
} else {
file_put_contents(‘error_log.txt’, “Empty response from server\n”, FILE_APPEND);
}

curl_close($ch);
?>
Code language: HTML, XML (xml)

Сподіваюсь що інформація була корисною! Подивитися як це працює, ви можете за посиланнями нижче. 

Нагадую, що зараз йде збір на РЕБ для військових і Ваша підтримка дуже важлива:

Сторінка збору на сайті: https://save-ukraine.life/ua_fundraising/reb-dlia-saperiv-68-batalyonu-tro/
МоноБанка збору: https://send.monobank.ua/jar/8uw2Qx1C21

Leave a Reply

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *