"""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