fix(tradein/domclick): «система защиты от протечек» в объявлении читалась как блок QRATOR #3268

Merged
lekss361 merged 2 commits from fix/3267-block-marker-in-listing-text into main 2026-08-30 09:14:18 +00:00
Owner

Найдено первым же сквозным прогоном свипа после #3266.

Что произошло

Прогон 5363 (30.08, уже с подзапросами): семь запросов подряд HTTP 200, страницы офферов по 103-128 КБ, 69 лотов собрано, ошибок ноль — и на восьмом свип объявил блок QRATOR, оборвал все комнатные корзины и забанил живой узел пула на 6 часов.

Ответ, на котором это случилось, был совершенно нормальным: HTTP 200, 107 183 байта, двадцать валидных офферов. Совпал маркер «система защиты» — внутри описания квартиры:

🔹 Двери скрытого монтажа с магнитными стопперами
🔹 Система защиты от протечек
🔹 Встроенная мебель и качественная техника

Продавец написал про защиту от протечек. Этого хватило, чтобы положить весь свип.

Причина

_extract_json искал DOMCLICK_BLOCK_MARKERS подстрокой по всему телу и делал это до разбора JSON — то есть и по пользовательскому тексту. Помимо «система защиты» в списке есть captcha, qrator, access denied: любой из них может встретиться в описании объявления, названии агентства или ссылке.

Показательно, что комментарий над проверкой прямо объяснял, почему сканируется весь ответ («block-маркер может стоять за пределами head в крупных challenge-страницах»), — расширение области поиска и создало ложные срабатывания.

Правка

Порядок перевёрнут: сначала разбор JSON, маркеры — только если разбор не удался. Разобранный JSON нужной формы блок-страницей быть не может (QRATOR отдаёт HTML), поэтому валидный разбор сам по себе доказывает отсутствие блока.

Это не новое правило, а выравнивание с уже существующим: в detail.py маркеры смотрят только когда __SSR_STATE__ не найден, то есть исключительно на неуспешных страницах. Свип был единственным местом с обратной логикой.

Заодно: мусор без маркеров теперь ValueError, а не блок. За неразобранный ответ неизвестной природы узел банить нельзя — цена ошибки шесть часов простоя узла.

Тесты

test_3267_block_marker_vs_listing_text.py, 11 проверок: каждый из семи маркеров внутри описания не считается блоком; дословный фрагмент объявления из прогона 5363; настоящая блок-страница по-прежнему блок; прежний путь с <pre>-обёрткой по-прежнему разбирается; мусор без маркеров — parse-ошибка, а не бан.

Проверено мутацией: со старым порядком 8 проверок из 11 краснеют. Прогон целиком — 261 passed, 1 skipped.

Что это меняет на проде

Свип уже собирает лоты (69 за неполный прогон против 0 во всех прошлых). Эта правка убирает единственную причину его обрыва, которую видно в логе 5363. Останется ли что-то ещё — покажет следующий сквозной прогон после деплоя.

Найдено первым же сквозным прогоном свипа после #3266. ## Что произошло Прогон 5363 (30.08, уже с подзапросами): семь запросов подряд `HTTP 200`, страницы офферов по 103-128 КБ, **69 лотов собрано, ошибок ноль** — и на восьмом свип объявил блок QRATOR, оборвал все комнатные корзины и забанил живой узел пула на 6 часов. Ответ, на котором это случилось, был совершенно нормальным: `HTTP 200`, 107 183 байта, двадцать валидных офферов. Совпал маркер `«система защиты»` — внутри описания квартиры: ``` 🔹 Двери скрытого монтажа с магнитными стопперами 🔹 Система защиты от протечек 🔹 Встроенная мебель и качественная техника ``` Продавец написал про защиту от протечек. Этого хватило, чтобы положить весь свип. ## Причина `_extract_json` искал `DOMCLICK_BLOCK_MARKERS` подстрокой по **всему** телу и делал это **до** разбора JSON — то есть и по пользовательскому тексту. Помимо «система защиты» в списке есть `captcha`, `qrator`, `access denied`: любой из них может встретиться в описании объявления, названии агентства или ссылке. Показательно, что комментарий над проверкой прямо объяснял, почему сканируется весь ответ («block-маркер может стоять за пределами head в крупных challenge-страницах»), — расширение области поиска и создало ложные срабатывания. ## Правка Порядок перевёрнут: **сначала разбор JSON, маркеры — только если разбор не удался.** Разобранный JSON нужной формы блок-страницей быть не может (QRATOR отдаёт HTML), поэтому валидный разбор сам по себе доказывает отсутствие блока. Это не новое правило, а выравнивание с уже существующим: в `detail.py` маркеры смотрят **только когда `__SSR_STATE__` не найден**, то есть исключительно на неуспешных страницах. Свип был единственным местом с обратной логикой. Заодно: мусор без маркеров теперь `ValueError`, а не блок. За неразобранный ответ неизвестной природы узел банить нельзя — цена ошибки шесть часов простоя узла. ## Тесты `test_3267_block_marker_vs_listing_text.py`, 11 проверок: каждый из семи маркеров внутри описания не считается блоком; дословный фрагмент объявления из прогона 5363; настоящая блок-страница по-прежнему блок; прежний путь с `<pre>`-обёрткой по-прежнему разбирается; мусор без маркеров — parse-ошибка, а не бан. **Проверено мутацией:** со старым порядком 8 проверок из 11 краснеют. Прогон целиком — `261 passed, 1 skipped`. ## Что это меняет на проде Свип уже собирает лоты (69 за неполный прогон против 0 во всех прошлых). Эта правка убирает единственную причину его обрыва, которую видно в логе 5363. Останется ли что-то ещё — покажет следующий сквозной прогон после деплоя.
lekss361 added 1 commit 2026-08-30 08:50:12 +00:00
fix(tradein/domclick): «система защиты от протечек» в объявлении читалась как блок QRATOR
Some checks failed
CI Trade-In / changes (pull_request) Successful in 10s
CI / changes (pull_request) Successful in 12s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Failing after 7m5s
aec05280fa
_extract_json искал маркеры блока подстрокой по ВСЕМУ телу ответа — до
разбора JSON, то есть и по пользовательским описаниям объявлений.

Прод 30.08, прогон 5363: продавец написал в описании квартиры «🔹 Система
защиты от протечек». Подстрока совпала с маркером «система защиты», ответ
на 107 183 байта с двадцатью валидными офферами был объявлен блок-
страницей, свип оборвал все комнатные корзины и забанил живой узел пула
на 6 часов. Совпасть так же могут «captcha» и «qrator» — в тексте
объявления, в имени агентства, в ссылке.

Порядок перевёрнут: сначала разбор JSON, маркеры — только если разбор не
удался. Разобранный JSON нужной формы блок-страницей быть не может,
QRATOR отдаёт HTML, так что валидный разбор сам по себе доказывает
отсутствие блока. Тот же порядок давно применён в detail.py — там
маркеры смотрят только когда __SSR_STATE__ не найден; свип был
единственным местом с обратной логикой.

Мусор без маркеров теперь ValueError, а не блок: за неразобранный ответ
неизвестной природы узел банить нельзя.

Тест проверен мутацией: со старым порядком 8 проверок из 11 краснеют,
включая дословный фрагмент описания из прогона 5363.
Author
Owner

Поправка по нумерации: ветка и коммит ссылаются на #3267, а этот номер занят другим PR (Авито, warm context), который уже смержен. Тикет под эту правку — #3272, PR закрывает его.

Отдельно: CI покраснел не из-за содержимого этого PR. 1 failed, 5186 passed, упавший — test_fifth_concurrent_estimate_gets_fast_429, и не ассертом, а Timeout (>120.0s) from pytest-timeout. Это гонка на sleep(0.05) в тесте потолка одновременных оценок, разобрана в #3270, чинится в #3271. После её мержа этот PR надо обновить из main, чтобы CI перегнался.

Поправка по нумерации: ветка и коммит ссылаются на `#3267`, а этот номер занят другим PR (Авито, warm context), который уже смержен. Тикет под эту правку — #3272, PR закрывает его. Отдельно: CI покраснел не из-за содержимого этого PR. `1 failed, 5186 passed`, упавший — `test_fifth_concurrent_estimate_gets_fast_429`, и не ассертом, а `Timeout (>120.0s) from pytest-timeout`. Это гонка на `sleep(0.05)` в тесте потолка одновременных оценок, разобрана в #3270, чинится в #3271. После её мержа этот PR надо обновить из main, чтобы CI перегнался.
bot-backend added 1 commit 2026-08-30 09:08:51 +00:00
Merge remote-tracking branch 'forgejo/main' into fix/3267-block-marker-in-listing-text
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 11s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 4m51s
657fdbd1ba
lekss361 merged commit e6f5c3d909 into main 2026-08-30 09:14:18 +00:00
lekss361 deleted branch fix/3267-block-marker-in-listing-text 2026-08-30 09:14:18 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#3268
No description provided.