"""Decode and inspect public KHL HTML without executing scripts or retrying restrictions.""" from __future__ import annotations import json import re from urllib.parse import urljoin, urlparse from bs4 import BeautifulSoup, UnicodeDammit BASE = 'https://www.khl.ru' KINDS = ('players', 'coaches', 'officials') def decode_response(response): # requests defaults text/html without a charset to ISO-8859-1. KHL's UTF-8 # meta declaration must be decoded from bytes before parsing Russian names. content = getattr(response, 'content', None) if isinstance(content, bytes) and content: decoded = UnicodeDammit(content, is_html=True) if decoded.unicode_markup is not None: return decoded.unicode_markup, decoded.original_encoding or '' return str(getattr(response, 'text', '') or ''), str(getattr(response, 'encoding', '') or '') def inspect_html(html): soup = BeautifulSoup(html, 'html.parser') compact = lambda value: re.sub(r'\s+', ' ', value).strip() title = compact(soup.title.get_text(' ', strip=True)) if soup.title else '' h1 = soup.find('h1') heading = compact(h1.get_text(' ', strip=True)) if h1 else '' for tag in soup(['script', 'style', 'noscript']): tag.decompose() body = compact(soup.get_text(' ', strip=True)).casefold().replace('ё', 'е') head = f'{title} {heading}'.casefold().replace('ё', 'е') status = next((int(m.group(1)) for text in (title, heading) if (m := re.match(r'^\s*(?:(?:error|ошибка|http)\s*[:\-]?\s*)?([45]\d{2})(?:\b|$)', text, re.I))), None) restricted = status in (401, 403, 429) or any(term in head for term in ( 'access denied', 'forbidden', 'captcha', 'just a moment', 'проверка браузера', 'проверка безопасности', 'доступ ограничен', 'доступ запрещен')) restricted = restricted or (len(body) < 4000 and any(term in body for term in ( 'access_deny@khl.ru', 'вредоносная активность', 'доступ временно ограничен', 'ваш доступ временно ограничили', 'verify you are human', 'подтвердите, что вы не робот', 'access denied', 'forbidden', 'captcha', 'проверка браузера', 'доступ запрещен'))) if restricted: reason = f'КХЛ ограничил доступ (HTML {status})' if status else 'КХЛ вернул страницу проверки или ограничения доступа' elif status: reason = f'КХЛ вернул страницу ошибки {status}' elif len(html) < 600 or ' 4_000_000: continue try: document = json.loads(raw) except (ValueError, RecursionError): continue pending = [document] visited = 0 while pending and visited < 100000: item = pending.pop(); visited += 1 if isinstance(item, dict): pending.extend(item.values()) elif isinstance(item, list): pending.extend(item) elif isinstance(item, str): link = person_url(kind, item) if link: found[link[0]] = link[1] return found