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

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