diff --git a/README.md b/README.md new file mode 100644 index 0000000..287a65b --- /dev/null +++ b/README.md @@ -0,0 +1,252 @@ +# Marathon Stats — VBA парсер live-статистики + +VBA-модуль для Excel, который загружает live-статистику спортивных событий с сайта +[Marathon Bet (BY)](https://www.marathonbet.by/su/live/45356) и выводит результаты +в отформатированную таблицу. + +## Возможности + +- Загрузка HTML-страницы через встроенные COM-объекты (без внешних зависимостей) +- Корректная обработка кириллицы (UTF-8) через `ADODB.Stream` +- Парсинг HTML по DOM-маркерам Marathon: лиги, команды, счёт, время, коэффициенты +- Профессиональное оформление Excel: цветовые схемы, группировка по лигам, замороженные панели + +## Быстрый старт + +### Из Excel + +1. Открыть Excel, нажать `Alt+F11` (редактор VBA) +2. **File → Import File** → выбрать `MarathonStats.bas` +3. Закрыть редактор, нажать `Alt+F8` → выбрать `FetchMarathonStats` → **Run** + +### Из PowerShell (автоматизация) + +```powershell +powershell.exe -ExecutionPolicy Bypass -File RunMarathon.ps1 +``` + +> **Требование:** В Excel должен быть включён доступ к объектной модели VBA: +> `File → Options → Trust Center → Trust Center Settings → Macro Settings → +> Trust access to the VBA project object model` + +## Структура файлов + +| Файл | Описание | +|---|---| +| `MarathonStats.bas` | Основной VBA-модуль (импортируется в Excel) | +| `RunMarathon.ps1` | PowerShell-скрипт для автоматического запуска | +| `MarathonStats.xlsm` | Результат — Excel-книга с данными | + +--- + +## Подробное описание кода + +### Точка входа + +```vba +Public Sub FetchMarathonStats() +``` + +Главная процедура. Последовательно выполняет: + +1. Создаёт/очищает лист `LiveStats` +2. Загружает HTML-страницу (`FetchPage`) +3. Парсит события (`ParseHTML`) +4. Записывает данные и форматирует таблицу + +--- + +### Загрузка страницы (Fetch) + +Ключевая проблема: сервер Marathon отправляет ответы в **gzip-сжатии** и разрывает +соединение с клиентами, которые не умеют его обрабатывать. Решение — двухуровневая +стратегия с двумя COM HTTP-объектами. + +#### `FetchPage(url As String) As String` + +Диспетчер — пробует два метода по очереди: + +``` +FetchPage + ├─ FetchWithXMLHTTP() ← основной + └─ FetchWithWinHTTP() ← резервный +``` + +#### Основной метод: `FetchWithXMLHTTP(url)` + +Использует **`MSXML2.XMLHTTP.6.0`** — клиентский COM-объект (на базе WinInet, как IE). +Автоматически распаковывает gzip, что критично для Marathon. + +```vba +Set http = CreateObject("MSXML2.XMLHTTP.6.0") +http.Open "GET", url, False +http.setRequestHeader "User-Agent", "Mozilla/5.0 ..." +http.setRequestHeader "Accept-Language", "ru-RU,ru;q=0.9" +http.Send +``` + +Заголовки имитируют браузер Chrome: +- **User-Agent** — без него сервер может отклонить запрос +- **Accept-Language: ru-RU** — для получения русскоязычной версии страницы + +**Декодирование UTF-8** (критически важно для кириллицы): + +```vba +Dim bodyBytes() As Byte +bodyBytes = http.responseBody ' сырые байты ответа + +Dim stream As Object +Set stream = CreateObject("ADODB.Stream") +stream.Type = 1 ' adTypeBinary +stream.Open +stream.Write bodyBytes ' записать байты +stream.Position = 0 ' перемотать в начало +stream.Type = 2 ' adTypeText +stream.Charset = "UTF-8" ' указать кодировку +FetchWithXMLHTTP = stream.ReadText ' прочитать как текст +stream.Close +``` + +Почему нельзя просто использовать `http.ResponseText`: +- VBA работает в кодировке Windows-1251 (ANSI) +- `ResponseText` может неверно интерпретировать UTF-8 байты +- `ADODB.Stream` гарантирует корректное преобразование UTF-8 → Unicode + +#### Резервный метод: `FetchWithWinHTTP(url)` + +Использует **`WinHttp.WinHttpRequest.5.1`** с заголовком `Accept-Encoding: identity`, +который просит сервер отправить **несжатый** ответ. Обходит проблему gzip на уровне +протокола. + +```vba +http.setRequestHeader "Accept-Encoding", "identity" +``` + +Декодирование UTF-8 — аналогичное через `ADODB.Stream`. + +#### Почему не работают другие подходы + +| Метод | Результат | Причина | +|---|---|---| +| `MSXML2.ServerXMLHTTP.6.0` | Соединение разорвано | Не обрабатывает gzip автоматически | +| `.NET HttpWebRequest` | Соединение разорвано | Аналогичная проблема | +| `WinHttp` без `identity` | Мусор вместо текста | Получает gzip-байты как строку | +| `InternetExplorer.Application` | Пустая страница | Контент загружается через JavaScript | + +--- + +### Парсинг HTML + +#### `ParseHTML(html As String)` + +Последовательный однопроходный сканер. Ищет ближайший из трёх маркеров и обрабатывает +его в зависимости от типа: + +``` +Маркеры в HTML Marathon: +───────────────────────────────────────────────────────── +"category-label simple-live" → название лиги (в