fix: reduce chess-results.com request volume by ~20-50x
All checks were successful
CI/CD Pipeline / build-and-deploy (push) Successful in 5s
All checks were successful
CI/CD Pipeline / build-and-deploy (push) Successful in 5s
- check_all_subscriptions: quick pre-check (1 req, art=4 only) before full fetch; full fetch only when results_count or current_round changed. Interval 60s → 300s. Saves ~10,000 req/day per active subscription. - rescan_existing_tournaments: filter to tournaments ended within last 30 days (or no end_date). Drops from ~2000 cached TNRs to ~30-100 active ones per run, saving ~22,000 req/day. - warmup: sleep 1s per TNR instead of 20ms per 500 — spreads initial full-scan over hours rather than minutes. - parser: extract _normalize_url helper, add fetch_tournament_quick(). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
66b10d9425
commit
8058080c3a
3 changed files with 65 additions and 12 deletions
|
|
@ -734,7 +734,7 @@ def main():
|
||||||
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_url))
|
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_url))
|
||||||
|
|
||||||
app.job_queue.run_repeating(
|
app.job_queue.run_repeating(
|
||||||
tracker.check_all_subscriptions, interval=60, first=10)
|
tracker.check_all_subscriptions, interval=300, first=10)
|
||||||
app.job_queue.run_repeating(
|
app.job_queue.run_repeating(
|
||||||
tracker.rescan_new_tournaments, interval=3600, first=60)
|
tracker.rescan_new_tournaments, interval=3600, first=60)
|
||||||
app.job_queue.run_repeating(
|
app.job_queue.run_repeating(
|
||||||
|
|
|
||||||
|
|
@ -768,21 +768,31 @@ def _warmup_cache_sync():
|
||||||
if tnr % 100 == 0:
|
if tnr % 100 == 0:
|
||||||
set_tnr_state('warmup_tnr', str(tnr))
|
set_tnr_state('warmup_tnr', str(tnr))
|
||||||
if tnr % 500 == 0:
|
if tnr % 500 == 0:
|
||||||
elapsed = (start_tnr - tnr) // 500 * 500 # rough
|
|
||||||
print(f'warmup: progress TNR {tnr} (started at {start_tnr})', file=sys.stderr)
|
print(f'warmup: progress TNR {tnr} (started at {start_tnr})', file=sys.stderr)
|
||||||
time.sleep(0.02)
|
# Rate-limit: ~1 req/sec to stay within chess-results.com daily limit.
|
||||||
|
# Cached TNRs return instantly (no HTTP), so this only applies to misses.
|
||||||
|
time.sleep(1.0)
|
||||||
|
|
||||||
# Mark warmup as complete — next run will see this and skip
|
# Mark warmup as complete — next run will see this and skip
|
||||||
set_tnr_state('warmup_tnr', 'done')
|
set_tnr_state('warmup_tnr', 'done')
|
||||||
|
|
||||||
|
|
||||||
async def rescan_existing_tournaments(context):
|
async def rescan_existing_tournaments(context):
|
||||||
"""Every 2h: refetch art=0 for all cached TNRs, detect new players."""
|
"""Every 2h: refetch art=0 for recent/active cached TNRs, detect new players.
|
||||||
|
|
||||||
|
Only rescans tournaments that ended within the last 30 days (or have no
|
||||||
|
end_date). Old completed tournaments never get new players, so skipping
|
||||||
|
them keeps daily request volume within chess-results.com rate limits.
|
||||||
|
"""
|
||||||
import time
|
import time
|
||||||
|
from datetime import timedelta
|
||||||
from bots.client_bot import _md_escape as _md_esc
|
from bots.client_bot import _md_escape as _md_esc
|
||||||
|
|
||||||
|
cutoff = (date.today() - timedelta(days=30)).isoformat()
|
||||||
conn = _get_conn()
|
conn = _get_conn()
|
||||||
cached_tnrs = [r[0] for r in conn.execute('SELECT tnr FROM tnr_cache ORDER BY tnr DESC').fetchall()]
|
cached_tnrs = [r[0] for r in conn.execute(
|
||||||
|
'SELECT tnr FROM tnr_cache WHERE end_date = "" OR end_date >= ? ORDER BY tnr DESC',
|
||||||
|
(cutoff,)).fetchall()]
|
||||||
conn.close()
|
conn.close()
|
||||||
if not cached_tnrs:
|
if not cached_tnrs:
|
||||||
return
|
return
|
||||||
|
|
@ -840,7 +850,7 @@ async def rescan_existing_tournaments(context):
|
||||||
|
|
||||||
|
|
||||||
async def check_all_subscriptions(context):
|
async def check_all_subscriptions(context):
|
||||||
from swiss_calc.parser import fetch_tournament
|
from swiss_calc.parser import fetch_tournament, fetch_tournament_quick
|
||||||
from swiss_calc.swiss import calculate_next_round
|
from swiss_calc.swiss import calculate_next_round
|
||||||
from bots.client_bot import format_pairings, _render_chunks
|
from bots.client_bot import format_pairings, _render_chunks
|
||||||
from telegram.constants import ParseMode
|
from telegram.constants import ParseMode
|
||||||
|
|
@ -849,10 +859,22 @@ async def check_all_subscriptions(context):
|
||||||
if not subs:
|
if not subs:
|
||||||
return
|
return
|
||||||
|
|
||||||
today_str = date.today().isoformat()
|
|
||||||
for sub in subs:
|
for sub in subs:
|
||||||
if sub['tournament_url'] == 'fide_only':
|
if sub['tournament_url'] == 'fide_only':
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Quick check: 1 request — skip full fetch if nothing changed
|
||||||
|
try:
|
||||||
|
quick = fetch_tournament_quick(sub['tournament_url'])
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
quick_player = find_player(quick, sub['player_name'], sub['player_sno'])
|
||||||
|
quick_results = len(quick_player.get('results', [])) if quick_player else 0
|
||||||
|
quick_rd = quick.get('current_round', 0)
|
||||||
|
if quick_results <= sub['last_results_count'] and quick_rd <= sub['last_round_done']:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Something changed — do full fetch for accurate data and pairing calc
|
||||||
try:
|
try:
|
||||||
tournament = fetch_tournament(sub['tournament_url'])
|
tournament = fetch_tournament(sub['tournament_url'])
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
|
||||||
|
|
@ -334,16 +334,47 @@ def extract_tournament_meta(html: str) -> dict:
|
||||||
|
|
||||||
# ── Full tournament fetch ─────────────────────────────────────
|
# ── Full tournament fetch ─────────────────────────────────────
|
||||||
|
|
||||||
|
def _normalize_url(url: str) -> str:
|
||||||
|
base = re.sub(r'[&?]art=\d+', '', url)
|
||||||
|
base = re.sub(r'[&?]rd=\d+', '', base)
|
||||||
|
base = re.sub(r'[&?]turdet=\w+', '', base)
|
||||||
|
base = re.sub(r'[&?]SNode=\w+', '', base)
|
||||||
|
return base
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_tournament_quick(url: str) -> dict:
|
||||||
|
"""Lightweight change-detection fetch: only art=4 (1 HTTP request).
|
||||||
|
|
||||||
|
Returns standings from the page directly (no art=2 cross-reference).
|
||||||
|
Use to check if results_count or current_round changed before doing a
|
||||||
|
full fetch_tournament() call.
|
||||||
|
"""
|
||||||
|
base_url = _normalize_url(url)
|
||||||
|
try:
|
||||||
|
html = fetch_url(base_url + '&art=4&turdet=YES')
|
||||||
|
except Exception as e:
|
||||||
|
raise RuntimeError(f'Не удалось загрузить турнир: {e}')
|
||||||
|
meta = extract_tournament_meta(html)
|
||||||
|
standings = parse_standings(html)
|
||||||
|
current_round = meta['current_round']
|
||||||
|
if standings:
|
||||||
|
max_results = max(len(s['results']) for s in standings)
|
||||||
|
if max_results > current_round:
|
||||||
|
current_round = max_results
|
||||||
|
return {
|
||||||
|
'name': meta['name'],
|
||||||
|
'num_rounds': meta['num_rounds'],
|
||||||
|
'current_round': current_round,
|
||||||
|
'standings': standings,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def fetch_tournament(url: str) -> dict:
|
def fetch_tournament(url: str) -> dict:
|
||||||
"""Fetch full tournament data from any chess-results.com URL.
|
"""Fetch full tournament data from any chess-results.com URL.
|
||||||
|
|
||||||
Detects current state automatically (any number of completed rounds).
|
Detects current state automatically (any number of completed rounds).
|
||||||
"""
|
"""
|
||||||
# Normalize URL to base
|
base_url = _normalize_url(url)
|
||||||
base_url = re.sub(r'[&?]art=\d+', '', url)
|
|
||||||
base_url = re.sub(r'[&?]rd=\d+', '', base_url)
|
|
||||||
base_url = re.sub(r'[&?]turdet=\w+', '', base_url)
|
|
||||||
base_url = re.sub(r'[&?]SNode=\w+', '', base_url)
|
|
||||||
|
|
||||||
# Fetch standings
|
# Fetch standings
|
||||||
standings_url = base_url + '&art=4&turdet=YES'
|
standings_url = base_url + '&art=4&turdet=YES'
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue