InquirerDespite economic problems, Undas flower vendors strive to survivePunchUganda begins troop withdrawal from SomaliaThe Jerusalem PostRapper Ja Rule comes to Tel Aviv, speaks on legacy, aging, and Tupac-Biggie row - interviewZDF heuteAktuelle Pressemitteilungen des ZDF20 MinutenWie viel weisst du noch aus deiner Kindheit? Erzähls uns!The South AfricanHow the Springboks can lose World Rugby No 1 ranking this weekendNHK 社会鳴子峡近くの5人けが事故 「アクセルとブレーキ踏み間違えた」VanguardCatholic Priest leads congregation in song for Obi, KwankwasoPremium TimesUniversity cancels final-year students’ exam over ‘wild jubilation’GMA News2 sakay ng habal-habal, patay matapos mabangga at madaganan ng dump truck sa Negros OrientalEl ComercioResultados ONPE en Villa María del Triunfo al 99.676 %: sigue aquí el conteo oficial de las Elecciones Regionales y Municipales 2026GhaflaDCI Arrests Man Who Allegedly Spent KSh43,320 Mistakenly Sent to His Phone
The Daily Newsstand · Free, Always
Monday, November 2, 2026

Разбираем банковские реквизиты из карточки компании: БИК, корсчёт и контрольные суммы

Translate

Карточка компании — документ, который в России присылают друг другу все: поставщики, заказчики, подрядчики. В нём ИНН, КПП, адрес и банковские реквизиты. Формата у карточки нет: кто‑то делает таблицу в Word, кто‑то строку в PDF, кто‑то лист Excel. Подписи пишут как угодно: «Р/с», «Расчетный счет №», «р.сч.», «Счёт получателя».

Я делаю веб‑сервис, где пользователь загружает такую карточку, а программа заполняет реквизиты сама. Ниже — что сработало, а что нет, с кодом.

Почему не работает «искать по подписи»

Первая версия была очевидной: найти слово «БИК» и взять 9 цифр после него. Она ломалась в трёх местах.

  1. Подпись и значение в разных строках. При извлечении текста из таблиц PDF и DOCX ячейки часто приходят отдельными строками: сначала «БИК», потом «044525225».

  2. Подписей много. «Корреспондентский счет №», «К/с», «корр. счёт», «Кор/сч» — это одно поле.

  3. Подстрока «банк» внутри названия. Правило «строка со словом банк — это банк» на «ИНВЕСТТОРГБАНК АО» брало хвост после «банк» и получало «АО, г. Москва».

Выход — опираться на сами цифры: у банковских реквизитов жёсткий формат

Реквизиты по цифрам

Реквизит

Длина

Признак

БИК

9

начинается с 04

Корреспондентский счёт

20

начинается с 30 101

Расчётный счёт организации

20

начинается с 405–408

import re

BIK  = re.compile(r"(?<!\d)(04\d{7})(?!\d)")
CORR = re.compile(r"(?<!\d)(30101\d{15})(?!\d)")
ACC  = re.compile(r"(?<!\d)(40[5-8]\d{17})(?!\d)")

def uniq(xs):
    return list(dict.fromkeys(xs))  # порядок появления, без повторов

def find_numbers(text):
    return (uniq(BIK.findall(text)),
            uniq(CORR.findall(text)),
            uniq(ACC.findall(text)))

(?<!\d) и (?!\d) важны. Без них из 20-значного расчётного счёта легко вытащить «телефон» из 11 цифр. У нас это действительно случилось: в поле «Телефон» попало 70 281 067 000 — середина счёта 40702810670000000181.

Два банка в одной карточке

Когда у компании два банка, карточка выглядит так:

После извлечения текста порядок может перемешаться. Как понять, какой корсчёт к какому БИК?

У корсчёта банка последние три цифры совпадают с последними тремя цифрами БИК: …411 ↔ …411, …593 ↔ …593. Это следует из того, как устроены оба номера, и позволяет собрать пары при любом порядке строк.

def pair_banks(biks, cors, accs):
    banks = []
    for i, b in enumerate(biks):
        corr = next((c for c in cors if c[-3:] == b[-3:]),
                    cors[i] if i < len(cors) else "")
        banks.append({"bik": b, "corr": corr,
                      "account": accs[i] if i < len(accs) else ""})
    return banks

С расчётным счётом такого признака нет. Его мы проверяем контрольной суммой.

Контрольная сумма: ключевание счёта

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

  1. Берём 23 цифры: «приставку» из БИК и 20 цифр счёта.

    • для расчётного счёта приставка — последние 3 цифры БИК;

    • для корреспондентского — «0» + 5-я и 6-я цифры БИК.

  2. Умножаем цифры на веса 7, 1, 3, 7, 1, 3, ….

  3. Берём младший разряд каждого произведения и складываем.

  4. Если сумма делится на 10, счёт верный.

WEIGHTS = [7, 1, 3] * 8

def _check(prefix, account):
    digits = prefix + account
    if len(digits) != 23 or not digits.isdigit():
        return False
    s = sum((int(d) * w) % 10 for d, w in zip(digits, WEIGHTS))
    return s % 10 == 0

def account_ok(bik, account):
    return _check(bik[-3:], account)

def corr_ok(bik, corr):
    return _check("0" + bik[4:6], corr)

Проверка на примере выше:

>>> corr_ok("044525593", "30101810200000000593")
True
>>> account_ok("044525593", "40702810602490003955")
True
>>> account_ok("044525411", "40702810602490003955")  # счёт от другого банка
False

Этим же способом можно ставить расчётные счета к своим банкам, если их несколько: перебираем пары «БИК × счёт» и оставляем ту, где контрольная сумма сходится.

def match_accounts(banks, accs):
    left = list(accs)
    for b in banks:
        ok = next((a for a in left if account_ok(b["bik"], a)), None)
        if ok:
            b["account"] = ok
            left.remove(ok)
    return banks

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

Подпись и значение в разных строках

Для полей без жёсткого формата (ИНН рядом с КПП, ОГРН) ищем значение в той же строке, а если там пусто — в следующей:

def after_label(lines, label_rx, value_rx, skip_rx=None):
    for i, line in enumerate(lines):
        if not re.search(label_rx, line, re.I):
            continue
        if skip_rx and re.search(skip_rx, line, re.I):
            continue
        for s in (line, lines[i + 1] if i + 1 < len(lines) else ""):
            m = re.search(value_rx, s)
            if m:
                return m.group(1)
    return ""

inn  = after_label(lines, r"\bИНН\b",  r"(?<!\d)(\d{12}|\d{10})(?!\d)")
kpp  = after_label(lines, r"\bКПП\b",  r"(?<!\d)(\d{9})(?!\d)")
ogrn = after_label(lines, r"\bОГРН",   r"(?<!\d)(\d{15}|\d{13})(?!\d)")

skip_rx нужен для почты: «Электронная почта» нужна, а «Бухгалтерия, buh1_kp@maill.ru» — нет. Без исключения первой находится почта бухгалтерии.

Склеенные названия банков

Из ячеек DOCX названия двух банков могут прийти одной строкой, через двоеточие или пробел:

ФИЛИАЛ «ЦЕНТРАЛЬНЫЙ» БАНКА ВТБ (ПАО): АО «АЛЬФА‑БАНК»

Делим по разделителям и по началу нового названия — организационной форме после закрывающей скобки или кавычки:

SPLIT = re.compile(r'\s*[:|;]\s*|(?<=[)»"])\s+(?=(?:ФИЛИАЛ|ПАО|АО|ООО|АКБ|КБ)\b)')

def bank_names(text):
    lines = [l for l in text.splitlines()
             if re.search(r"банк", l, re.I)
             and not re.search(r"\b(бик|сч[её]т|р/с|к/с)\b|реквизит", l, re.I)]
    joined = " ".join(re.sub(r"(?i)^(наименование\s+банка|банк)\s*[:\-]?\s*", "", l) for l in lines)
    return [p.strip(" ,") for p in SPLIT.split(joined) if p and len(p.strip(" ,")) > 2]

Названия ставим к парам БИК/корсчёт по порядку. Если банков несколько, мы не угадываем, а показываем пользователю выбор: «В карточке 2 банка — какой поставить?».

Что получилось

На трёх реальных карточках с разной разметкой (строки в PDF, таблица в DOCX, таблица в PDF) после всех правок заполняются все поля. Главные выводы:

  • Опирайтесь на формат данных, а не на подписи. У реквизитов жёсткая структура, подписи свободны.

  • Проверяйте контрольные суммы. Это бесплатная защита от опечаток и от перепутанных счетов.

  • Не угадывайте, когда вариантов несколько. Спросить пользователя дешевле, чем исправлять платёжку.

  • Тестируйте на настоящих документах. Каждая новая карточка находила новую ошибку, и все они были в эвристиках, а не в коде.

Если у вас есть карточка, которая «точно не разберётся», — делитесь в комментариях, интересно посмотреть, что ещё придумывают.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.