پرش به محتوا
آرش حاتمی

hatamiarash7/ iranleague-exporter

سال توسعه: ۱۴۰۳ متن‌باز

برنامهٔ بازی‌های لیگ ایران در Prometheus

اکسپورتر پایتونی برای تبدیل برنامهٔ مسابقات فوتبال ایران به متریک‌های Prometheus، با دریافت دوره‌ای داده‌ها، برچسب فارسی تیم‌ها و اجرای Docker.

اگر بخواهید زمان مسابقات فوتبال ایران را کنار نمودارهای مانیتورینگ ببینید یا برای نزدیک‌شدن زمان بازی هشدار بسازید، به داده‌ای نیاز دارید که Prometheus بتواند آن را بخواند. پروژهٔ iranleague-exporter برنامهٔ بازی‌ها را از سایت سازمان لیگ دریافت می‌کند و زمان هر مسابقه را به‌صورت متریک در اختیار سامانهٔ مانیتورینگ می‌گذارد.

این ابزار با Python نوشته شده است؛ Requests دریافت صفحه، BeautifulSoup استخراج جدول مسابقات و FastAPI ارائهٔ خروجی HTTP را بر عهده دارند. می‌توان از داده‌های آن در پرس‌وجوهای Prometheus یا پنل‌های Grafana استفاده کرد.

از جدول مسابقات تا متریک

برنامه هنگام شروع و سپس به‌صورت دوره‌ای، صفحهٔ برنامهٔ مسابقات را می‌خواند. ردیف‌هایی که ستون نتیجهٔ آن‌ها - است انتخاب می‌شوند؛ نام تیم میزبان و مهمان، تاریخ شمسی و ساعت بازی از همان جدول استخراج می‌شوند.

تاریخ با jdatetime به میلادی تبدیل می‌شود و زمان مسابقه به شکل Unix timestamp، بر حسب ثانیه، در متریک ir_league_matches قرار می‌گیرد. برچسب teams ترکیب نام دو تیم با جداکنندهٔ vs است. با تنظیم LABEL_LANG=FA نام‌های فارسی حفظ می‌شوند؛ حالت پیش‌فرض EN نام‌ها را با slugify به نویسه‌های لاتین تبدیل می‌کند و الزاماً نام رسمی انگلیسی باشگاه‌ها را تولید نمی‌کند.

فاصلهٔ پیش‌فرض به‌روزرسانی ۳۰ دقیقه است. درخواست به /metrics آخرین داده‌های ثبت‌شده در حافظه را برمی‌گرداند و هر بار دریافت تازه‌ای از سایت لیگ انجام نمی‌دهد. برای خطاهای موقت درخواست نیز تلاش مجدد با وقفهٔ افزایشی در نظر گرفته شده است. جزئیات استخراج داده در کد خزنده قابل بررسی است.

راه‌اندازی با Docker

برای این روش به Docker و دسترسی شبکه از کانتینر به سایت iranleague.ir نیاز دارید. ابتدا فایلی به نام iranleague.env بسازید:

AUTH_USERNAME=prometheus
AUTH_PASSWORD=example-only-change-this-password
LABEL_LANG=FA
UPDATE_INTERVAL=30
TZ=Asia/Tehran

رمز بالا فقط نمونه است؛ قبل از اجرا آن را با رمز اختصاصی خود جایگزین کنید و فایل را در مخزن منتشر نکنید. نام کاربری و رمز عبور هر دو الزامی‌اند و برنامه بدون آن‌ها شروع نمی‌شود.

سپس از ایمیج معرفی‌شده در راهنمای پروژه استفاده کنید:

docker run -d \
  --name iranleague-exporter \
  --env-file iranleague.env \
  -p 127.0.0.1:8000:8000 \
  hatamiarash7/iranleague-exporter:latest

این نمونه پورت را فقط روی Loopback میزبان منتشر می‌کند. برای بررسی متریک‌ها از همان میزبان اجرا کنید؛ curl رمز را از شما می‌پرسد:

curl --fail --user prometheus http://127.0.0.1:8000/metrics

منطقهٔ زمانی در این پروژه مهم است. کد، تاریخ و ساعت جدول را بدون منطقهٔ زمانی صریح می‌سازد و هنگام تبدیل به timestamp از منطقهٔ زمانی محیط اجرا استفاده می‌کند. برای برنامهٔ مسابقات به وقت ایران، TZ=Asia/Tehran را تنظیم کنید و مطمئن شوید داده‌های منطقهٔ زمانی در محیط اجرا موجودند؛ صرفاً تغییر منطقهٔ زمانی پنل Grafana خطای تبدیل در اکسپورتر را اصلاح نمی‌کند.

تنظیمات اصلی

تنظیمات از متغیرهای محیطی خوانده می‌شوند. گزینه‌های پرکاربرد و پیش‌فرض‌های آن‌ها:

متغیر پیش‌فرض کاربرد
HTTP_HOST 0.0.0.0 آدرس گوش‌دادن داخل محیط اجرا
HTTP_PORT 8000 پورت HTTP
HTTP_WORKERS 1 تعداد فرایندهای Uvicorn
AUTH_USERNAME / AUTH_PASSWORD بدون مقدار اطلاعات Basic Auth برای متریک‌ها؛ الزامی
LABEL_LANG EN نام تیم‌ها با حالت FA یا EN
UPDATE_INTERVAL 30 فاصلهٔ به‌روزرسانی بر حسب دقیقه؛ حداقل یک دقیقه
CRAWLER_CONNECT_TIMEOUT 5.0 مهلت اتصال بر حسب ثانیه
CRAWLER_READ_TIMEOUT 10.0 مهلت خواندن بر حسب ثانیه
CRAWLER_MAX_RETRIES 3 حداکثر دفعات تلاش مجدد
LOG_LEVEL INFO سطح ثبت لاگ

با CRAWLER_URL می‌توان نشانی صفحهٔ ورودی را تغییر داد، اما صفحهٔ جدید باید با ساختار HTML مورد انتظار خزنده سازگار باشد. فهرست کامل گزینه‌ها در راهنمای تنظیمات آمده است.

برای این کاربرد، مقدار پیش‌فرض یک Worker مناسب است: هر فرایند وضعیت، متریک‌ها و حلقهٔ دریافت دادهٔ خودش را دارد و افزایش Workerها باعث اشتراک یک حافظهٔ مرکزی بین آن‌ها نمی‌شود.

اتصال به Prometheus

اگر Prometheus مستقیماً روی همان میزبان Docker اجرا می‌شود، این Job را به فهرست scrape_configs اضافه کنید:

scrape_configs:
  - job_name: iranleague
    scrape_interval: 1m
    metrics_path: /metrics
    basic_auth:
      username: prometheus
      password_file: /etc/prometheus/secrets/iranleague-password
    static_configs:
      - targets: ["127.0.0.1:8000"]

فایل معرفی‌شده در password_file را با همان رمز AUTH_PASSWORD ایجاد کنید؛ محتوای آن فقط رمز است، نه خط AUTH_PASSWORD=.... این مسیر باید برای فرایند Prometheus خواندنی باشد. جزئیات این گزینه در مستندات احراز هویت HTTP در Prometheus آمده است.

اگر Prometheus داخل کانتینر دیگری اجرا می‌شود، 127.0.0.1 به همان کانتینر اشاره می‌کند. در آن حالت باید شبکهٔ مشترک و نام کانتینر اکسپورتر، یا آدرس قابل‌دسترسی دیگری را تنظیم کنید. فاصلهٔ Scrape یک‌دقیقه‌ای در مثال بالا، فاصلهٔ دریافت اطلاعات از سایت لیگ را تغییر نمی‌دهد.

مسیر متریک از Basic Auth استفاده می‌کند، اما HTTP داخلی برنامه رمزنگاری‌شده نیست. برای ارتباط از شبکهٔ نامطمئن، TLS را با یک reverse proxy فراهم کنید.

متریک‌ها و نمونهٔ استفاده

متریک نوع معنی
ir_league_matches Gauge timestamp زمان مسابقه با برچسب teams
ir_league_matches_total Gauge تعداد ردیف‌های مسابقهٔ پذیرفته‌شده در آخرین به‌روزرسانی موفق
ir_league_scrape_duration_seconds Gauge مدت آخرین تلاش برای دریافت و به‌روزرسانی داده‌ها
ir_league_scrape_success Gauge موفقیت آخرین به‌روزرسانی؛ یک برای موفقیت و صفر برای خطا
ir_league_exporter_info Info نسخه، زبان برچسب‌ها و فاصلهٔ به‌روزرسانی

با وجود پسوند _total، متریک شمار مسابقات Gauge است؛ مقدار آن تعداد فعلی ردیف‌هاست و نباید مثل Counter با rate تفسیر شود.

برای دیدن زمان باقی‌مانده تا هر بازی بر حسب ثانیه:

ir_league_matches{job="iranleague"} - time()

برای انتخاب بازی‌هایی که زمان ثبت‌شدهٔ آن‌ها در یک ساعت آینده است:

(ir_league_matches{job="iranleague"} - time() > 0)
and
(ir_league_matches{job="iranleague"} - time() <= 3600)

این عبارت می‌تواند مبنای یک پنل یا قانون هشدار باشد؛ ارسال اعلان به پیکربندی جداگانهٔ هشدارها در سامانهٔ مانیتورینگ نیاز دارد.

سلامت سرویس و محدودیت داده‌ها

دو مسیر /health و /ready بدون احراز هویت در دسترس‌اند:

curl --fail http://127.0.0.1:8000/health
curl --fail http://127.0.0.1:8000/ready

در پیاده‌سازی فعلی، /ready فقط وقتی پاسخ موفق می‌دهد که آخرین به‌روزرسانی موفق بوده باشد. /health نیز پس از شکست به‌روزرسانی، وضعیت 503 می‌دهد؛ بنابراین نتیجهٔ آن تنها نشان‌دهندهٔ زنده‌بودن فرایند نیست و به دریافت داده از سایت لیگ وابسته است.

برای تفسیر خروجی، چند نکته اهمیت دارد:

  • بازی آینده از روی ستون نتیجه تشخیص داده می‌شود. خزنده زمان بازی را با ساعت فعلی مقایسه نمی‌کند؛ اگر نتیجه همچنان - باشد، حتی timestamp گذشته هم می‌تواند در خروجی بماند.
  • ساعت خالی به نیمه‌شب تبدیل می‌شود. اگر تاریخ معتبر باشد ولی ساعت ثبت نشده باشد، کد 00:00 را در نظر می‌گیرد. زمان دقیق این بازی‌ها را باید از منبع بررسی کرد.
  • خطای دریافت، دادهٔ قبلی را نگه می‌دارد. در خطای خزنده، متریک موفقیت صفر می‌شود ولی زمان‌ها و شمار قبلی مسابقات باقی می‌مانند. مقدار up در Prometheus به‌تنهایی تازگی برنامهٔ بازی‌ها را ثابت نمی‌کند.
  • خروجی خالی الزاماً به معنی نبود مسابقه نیست. تغییر ساختار HTML یا ردیف‌های غیرقابل‌پردازش ممکن است به فهرست خالی منجر شود و همچنان به‌عنوان به‌روزرسانی موفق ثبت شود. پس موفقیت خزنده تضمین کامل‌بودن داده‌ها نیست.
  • برچسب مسابقه فقط نام دو تیم است. اگر چند ردیف با همان ترکیب میزبان و مهمان وجود داشته باشد، مقدار آخر روی همان سری نوشته می‌شود؛ شمار ردیف‌ها لزوماً با تعداد سری‌های یکتا برابر نیست.

این پروژه با مجوز MIT منتشر شده و راهی ساده برای آوردن برنامهٔ مسابقات فوتبال ایران به ابزارهای مانیتورینگ فراهم می‌کند. دقت خروجی به اطلاعات و ساختار صفحهٔ منبع وابسته است؛ برای نمایش زمان بازی یا ساخت هشدار، وضعیت دریافت داده و تنظیم منطقهٔ زمانی را هم در نظر بگیرید.