В этой статье я расскажу, как с помощью Python, Flask и SNMP создать простой веб-мониторинг для МФУ в корпоративной сети. Решение позволяет видеть статус, уровень тонера, тип картриджа и серийный номер принтера прямо в браузере.

Для реализации на МФУ должен быть включен SNMP v1/v2c. Порт 161/UDP должен быть открыт.

Задача

  • Получать список принтеров с принт-сервера (CSV)

  • Опросить их по SNMP

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

Используемые технологии

  • Python 3.10

  • Flask

  • pysnmp

  • HTML + JavaScript (для таблицы)

  • CSV-файл с принтерами


Шаг 1. Получаем нужные OID

Что такое OID: OID (Object Identifier) — это уникальный идентификатор параметра в SNMP, например, уровень тонера, серийный номер, тип картриджа, статус принтера и т.д. любую информацию о принтере можно получить через OID.

Как получить OID для вашего принтера:

Включите SNMP на принтере (обычно через веб‑интерфейс устройства).

Установите SNMP‑утилиту: (Для Windows я использовал Net‑SNMP)

Выполните SNMP Walk: (В командной строке из директории Net‑SNMP):

snmpwalk -v 2c -c public <IP_принтера>

Найдите нужные OID в выводе (например, для тонера, серийного номера, типа картриджа).
Обычно для уровня тонера используются OID из Printer‑MIB: чаще всего они стандартные для всех моделей МФУ. я проверял только на Kyocera, Canon, HP совпали на 3 моделях.

o Текущее значение тонера: 1.3.6.1.2.1.43.11.1.1.9.1.1

o Максимальное значение тонера: 1.3.6.1.2.1.43.11.1.1.8.1.1

o Тип картриджа: 1.3.6.1.2.1.43.11.1.1.6.1.1

o Серийный номер: 1.3.6.1.2.1.43.5.1.1.17.1


Шаг 2. Готовим список принтеров

На Windows-принт-сервере выполните в PowerShell:

Get-Printer | Select-Object Name,PortName | Export-Csv printers.csv -NoTypeInformation -Encoding UTF8

Шаг 3. Устанавливаем нужные библиотеки

Какие библиотеки понадобятся:

Flask — для создания веб-интерфейса.

Pysnmp — для SNMP-опроса принтеров.

Pyasn1 —нужна для корректной работы SNMP запросов через pysnmp

Важно: для стабильной работы использовать Python 3.10 или 3.11,
а также установите библиотеки командой:

pip install flask==2.3.3

pip install pysnmp==4.4.8

pip install pyasn1==0.4.8

Шаг 4. Создаём структуру проекта

printer-monitor/

├── app.py                # Основной Python-скрипт (логика опроса и веб-сервер)

├── printers.csv          # Список принтеров (имя, IP)

└── templates/

    └── index.html        # HTML-шаблон для отображения таблицы в браузере

Для чего каждый файл:

1.     app.py — основной код: опрашивает принтеры по SNMP, отдаёт данные в веб-интерфейс.

2.     printers.csv — список принтеров, которые будут мониторится.

3.     templates/index.html — страница, где отображается таблица с результатами.


Шаг 5. Пишем код

В app.py реализуем загрузку списка принтеров, SNMP-опрос, обработку ошибок, отдачу данных через Flask.

app.py
from flask import Flask, jsonify, render_template   # Импортируем Flask и функции для API и шаблонов
from pysnmp.hlapi import *                          # Импортируем всё для SNMP-опроса
import csv                                          # Для работы с CSV-файлом
from concurrent.futures import ThreadPoolExecutor   # Для параллельного опроса принтеров

def is_ip(address): # Проверка, что строка — это IP-адрес
    parts = address.split('.')
    return len(parts) == 4 and all(p.isdigit() and 0 <= int(p) <= 255 for p in parts)

def load_printers(filename='printers.csv'): # Загрузка списка принтеров из CSV-файла
    printers = []
    with open(filename, 'r', encoding='utf-8-sig') as f:
        reader = csv.DictReader(f)
        for row in reader:
            # Приводим все ключи к стандартному виду без кавычек и пробелов
            clean_row = {k.strip().replace('"', ''): v for k, v in row.items()}
            # Для отладки: Если раскомментировать, то при запуске сервера в консоли увидим словари с ключами и значениями из каждой строки файла.
            # print(clean_row)
            name = clean_row.get('Name') or clean_row.get('Имя')  # если вдруг русское имя
            port = clean_row.get('PortName') or clean_row.get('Порт')  # если вдруг русское имя
            if port and is_ip(port):
                printers.append({'name': name, 'ip': port})
    return printers

PRINTERS = load_printers()

app = Flask(__name__) #Создаём Flask-приложение

def monitor_mfu_from_csv(file_path):
    try:
        with open(file_path, newline='', encoding='utf-8') as csvfile:
            reader = csv.DictReader(csvfile)
            for row in reader:
                mfu_name = row.get('МФУ')
                status = row.get('Статус')
                loaded = row.get('Загружено')
                print(f"МФУ: {mfu_name}, Статус: {status}, Загружено: {loaded}")
    except FileNotFoundError:
        print(f"Файл не найден: {file_path}")
    except Exception as e:
        print(f"Ошибка при чтении файла: {e}")

# Пример вызова функции
# monitor_mfu_from_csv('mfu_status.csv')]

def get_printer_status(printer): # Основная функция опроса принтера по SNMP
    ip = printer['ip']
    name = printer['name']
    toner_current_oid = '1.3.6.1.2.1.43.11.1.1.9.1.1'  # уровень тонера
    toner_max_oid = '1.3.6.1.2.1.43.11.1.1.8.1.1'      # максимальное значение тонера
    toner_type_oid = '1.3.6.1.2.1.43.11.1.1.6.1.1'     # тип тонера
    serial_oid = '1.3.6.1.2.1.43.5.1.1.17.1'           # серийный номер
    try: # SNMP-запрос сразу по всем нужным OID
        iterator = getCmd(
            SnmpEngine(),
            CommunityData('public', mpModel=0),
            UdpTransportTarget((ip, 161), timeout=1, retries=1),
            ContextData(),
            ObjectType(ObjectIdentity(toner_current_oid)),
            ObjectType(ObjectIdentity(toner_max_oid)),
            ObjectType(ObjectIdentity(toner_type_oid)),
            ObjectType(ObjectIdentity(serial_oid))
        )
        errorIndication, errorStatus, errorIndex, varBinds = next(iterator)
        if errorIndication or errorStatus:
            return {
                'ip': ip,
                'name': name,
                'status': 'Error',
                'toner_level': None,
                'toner_type': None,
                'serial': None
            }
        else:
            current = int(varBinds[0][1])                   # Текущее значение тонера
            maximum = int(varBinds[1][1])                   # Максимальное значение тонера
            toner_type = str(varBinds[2][1])                # Тип тонера (модель/цвет/код)
            serial = str(varBinds[3][1])                    # Серийный номер принтера
            if maximum > 0 and current >= 0:
                percent = int(current / maximum * 100)      # Считаем процент тонера
            else:
                percent = None
            return {
                'ip': ip,
                'name': name,
                'status': 'OK',
                'toner_level': percent,
                'toner_type': toner_type,
                'serial': serial
            }
    except Exception as e:                      # Если возникла ошибка при SNMP-запросе — возвращаем статус Error
        print(f"Ошибка при запросе {ip}: {e}")
        return {
            'ip': ip,
            'name': name,
            'status': 'Error',
            'toner_level': None,
            'toner_type': None,
            'serial': None
        }

@app.route('/')
def index():
    return render_template('index.html')       # Отдаём HTML-страницу с таблицей         

@app.route('/api/printers')
def api_printers():                            # Параллельно опрашиваем все принтеры (ускоряет работу)
    with ThreadPoolExecutor(max_workers=10) as executor:
        results = list(executor.map(get_printer_status, PRINTERS))
        results.sort(key=lambda x: (x['status'] == 'Error',     # Сортировка: сначала без ошибок, потом по возрастанию тонера (ошибки внизу)
                                    x['toner_level'] is None or x['toner_level'] > 20, 
                                    x['toner_level'] if x['toner_level'] is not None else 101))
        return jsonify(results)                # Отдаём данные в формате JSON для таблицы

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)    # Запускаем сервер на нужном IP и порту         

В index.html — таблица и JS для авто обновления.

index.html
<!DOCTYPE html>
<html>
<head>
    <title>Статус принтеров</title>
</head>
<body>
<h1>Статус принтеров в реальном времени</h1>
<table border="1" id="printers-table">
    <tr>
        <th>Имя</th>
        <th>IP</th>
        <th>Статус</th>
        <th>Уровень чернил</th>
		<th>Тип тонера</th>
		<th>Серийный</th>
    </tr>
</table>

<script>
function fetchData() {
    fetch('/api/printers')
        .then(response => response.json())
        .then(data => {
            const table = document.getElementById('printers-table');
            table.innerHTML = `
                <tr>
                    <th>Имя</th>
                    <th>IP</th>
                    <th>Статус</th>
                    <th>Уровень чернил</th>
					<th>Тип тонера</th>
					<th>Серийный</th>
                </tr>`;
            data.forEach(printer => {
                const row = table.insertRow();
                row.insertCell().innerText = printer.name || '—';
                row.insertCell().innerText = printer.ip;
                row.insertCell().innerText = printer.status;
                row.insertCell().innerText = (printer.toner_level !== null) ? (printer.toner_level + ' %') : 'N/A';
				row.insertCell().innerText = printer.toner_type || '—';
				row.insertCell().innerText = printer.serial || '—';
            });
        });
}
setInterval(fetchData, 30000);
fetchData();
</script>
</body>
</html>
Шаг 6. Запускаем и используем

Из директории проекта выполняем в cmd:
pythonapp.py
Откройте в браузере:
http://<ваш_IP>:5000/
Видим таблицу с именами, IP, статусом, уровнем тонера, типом картриджа и серийным номером.

Результат
Результат

Комментарии (1)


  1. andreymal
    14.12.2025 14:02

    Берём Prometheus SNMP Exporter и получаем красивые графики в Grafana (хотя кто-нибудь скажет оверкилл)