Как автоматически получать сумму сбора с МоноБанки без API
Приветствую! Поделюсь простым решением: как получить сумму сбора с МоноБанки и подтянуть её на свой сайт, в чат-бот или другой сервис.
Задача появилась, когда я не нашёл адекватного решения в API МоноБанка. Там есть механизм Webhook: он должен присылать сумму после пополнения банки, но у меня он так и не заработал. Может, я что-то не так понял, может, торопился или были проблемы на сервере, но зарегистрировать listener так и не вышло. Написал в поддержку Моно — ответили только через сутки, и то в стиле «Читай документацию, там всё есть». Пока ждал, успел написать другое решение — не такое красивое, работает «тупо в лоб», но работает и задачу мою решает 🙂
Идея:
Идея простая — парсить страницу напрямую. Опасался, что МоноБанк генерирует CSS-классы динамически, чтобы защититься от парсинга, но, к счастью, все классы статические и всё очень просто.
План действий:
- Написать на Python веб-сервис: он принимает запрос с идентификатором страницы МоноБанки, парсит её и отдаёт JSON с результатами.
- На хостинге написать PHP-скрипт, который по CRON каждые 30 минут дёргает этот веб-сервис. Ответ складывает в базу или в файл, чтобы потом им пользоваться. Но тут уже как вам удобнее
Зачем cron и промежуточное хранение?
Почему я делаю это через CRON и кладу значение в промежуточную базу, а не дёргаю сервис каждый раз, когда пользователь заходит на сайт? Чтобы случайно не начать «штурмовать» страницу МоноБанка запросами всякий раз, когда кто-то открывает сайт. Лишняя нагрузка тут ни к чему. Обновлять данные раз в полчаса или даже раз в час — для такой копии вполне нормально: цифра остаётся актуальной, а систему это не грузит.
Что нам понадобится:
- Простой VPS, чтобы разместить сервис. У меня дешёвый: 1 Гб оперативки, 1 CPU и 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 servern”, 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