Skip to content

Instantly share code, notes, and snippets.

@Anton-V-K
Created November 26, 2025 13:33
Show Gist options
  • Select an option

  • Save Anton-V-K/0d5992d5a7c31b6b420828f55d5c6f24 to your computer and use it in GitHub Desktop.

Select an option

Save Anton-V-K/0d5992d5a7c31b6b420828f55d5c6f24 to your computer and use it in GitHub Desktop.
Python script to parse operations from OTP bank statement
# This script extracts operations from the statement (saved from online.otpbank.ru),
# and prints only the ones with bonuses
# Setup:
# a) install Python3
# b) pip install BeautifulSoup4
# Guide:
# 1. Go to online.otpbank.ru
# 2. Select the card
# 3. Select the period
# 4. Scroll to the bottom, so all operations are loaded
# 5. Save the page as 'OTP.html' (you can use any name)
# 6. Run this script: 'OTP.py OTP.html'
import argparse
import datetime
import decimal
from bs4 import BeautifulSoup # pip install BeautifulSoup4
from functools import reduce
def parse_statement(html_content):
months = \
(' января ', '.01.'), \
(' февраля ', '.02.'), \
(' марта ', '.03.'), \
(' апреля ', '.04.'), \
(' мая ', '.05.'), \
(' июня ', '.06.'), \
(' июля ', '.07.'), \
(' августа ', '.08.'), \
(' сентября ', '.09.'), \
(' октября ', '.10.'), \
(' ноября ', '.11.'), \
(' декабря ', '.12.'),
history = []
soup = BeautifulSoup(html_content, 'html.parser')
operations = soup.find_all('div', attrs={'data-name': 'accordion-item'})
for op in operations:
if not op.find('span', string='Бонусные рубли'):
continue
history.append({})
sum_span = op.find('span', attrs={'data-testid': 'operation-sum'})
history[-1]['sum'] = sum_span.get_text(strip=True)[:-2].replace(' ', '')
bonus_span = op.find('span', attrs={'data-testid': 'operation-bonus'})
history[-1]['bonus'] = bonus_span.get_text(strip=True)
container = op.find('div', attrs={'data-name': 'statistic-container'})
items = container.find_all('div', attrs={'data-name': 'statistic-item'}, recursive=False)
for item in items:
type_span = item.find('span')
if not type_span:
continue
type = type_span.get_text(strip=True)
value_span = item.find('span', attrs={'data-id': lambda x: x and x.startswith('statistic-item-value-')})
value = value_span.get_text(strip=True)
match type:
# case 'Бонусные рубли':
# history[-1]['bonus'] = value
case 'Дата':
# history[-1]['date'] = value
# (c) https://stackoverflow.com/a/9479972/536172
timestamp = reduce(lambda a, kv: a.replace(*kv), months, value)
history[-1]['date'] = datetime.datetime.strptime(timestamp, '%d.%m.%Y %H:%M:%S')
return history
def main():
parser = argparse.ArgumentParser(description='Prints bonus operations from the statement saved from online.otpbank.ru')
parser.add_argument('source', nargs='?', default='OTP.html', help='Source HTML file with statement')
args = parser.parse_args()
with open(args.source, 'r', encoding='utf-8') as file:
html_content = file.read()
history = parse_statement(html_content)
total_sum = 0
total_bonus = 0
for op in history:
print(f"{op['date']} {op['sum']:>8} {op['bonus']:>8}")
total_sum += decimal.Decimal(op['sum'])
total_bonus += decimal.Decimal(op['bonus'])
print(f"TOTAL {total_sum} {total_bonus}")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment