В этой статье я расскажу, как с помощью 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, статусом, уровнем тонера, типом картриджа и серийным номером.

