Merge pull request 'Тесты разбора площадок: дом Авито, выдачи Циана и Яндекса снова проверяются на сохранённых страницах' (#3580) from fix/kit-parser-unit-tests into main
Some checks failed
Deploy Trade-In / changes (push) Successful in 17s
Deploy Trade-In / build-browser (push) Successful in 2m43s
Deploy Trade-In / build-frontend (push) Successful in 3m15s
Deploy Trade-In / test (push) Failing after 4m34s
Deploy Trade-In / build-backend (push) Has been skipped
Deploy Trade-In / deploy (push) Has been skipped
Deploy Trade-In / perimeter-smoke (push) Has been skipped
Deploy Trade-In / deploy-status (push) Failing after 1s

This commit is contained in:
bot-backend 2026-09-17 11:23:32 +00:00
commit 7da94f183f
5 changed files with 1832 additions and 0 deletions

View file

@ -0,0 +1,911 @@
<!doctype html>
<!--
#2422: живая выдача Циана, снята 2026-09-17 (curl_cffi impersonate=chrome, HTTP 200, ~3 МБ):
GET https://ekb.cian.ru/cat.php?deal_type=sale&engine_version=2&offer_type=flat&region=4743&room2=1
Урезано до того, что читает CianScraper._parse_serp_html / _offer_to_lot:
- из 154 записей _cianConfig['frontend-serp'] оставлены projectName и initialState;
- в initialState только results.totalOffers и 4 из 28 offers, по одному на ветку:
331661795 вторичка, 333599038 новостройка с домом в geo.address,
333279363 новостройка с decoration=fine, 321628098 новостройка без улицы и дома;
- у offer только ключи, которые читает парсер; phones/user/userId убраны (PII),
photos урезаны до 3 fullUrl, у geo.address/undergrounds только читаемые поля.
Форма записи (.concat([...]) с JSON-массивом) и значения полей не менялись.
-->
<html><head><meta charset="utf-8"></head><body>
<script type="text/javascript">
window._cianConfig = window._cianConfig || {};
window._cianConfig['frontend-serp'] = (window._cianConfig['frontend-serp'] || []).concat([
{
"key": "projectName",
"value": "frontend-serp",
"priority": 1000,
"filter": {}
},
{
"key": "initialState",
"value": {
"results": {
"totalOffers": 13895,
"offers": [
{
"cianId": 331661795,
"id": 331661795,
"fullUrl": "https://ekb.cian.ru/sale/flat/331661795/?context=4.JppPDee85nA.3hAoeZtPv9-CCGqBSDAI8EqioM8KBdEBjsgdLaVHspglG86Z8YBFyLQQuLYOzUIJ9UfTORZd8UX_kw&mlSearchSessionGuid=924c429bf7495771ff77b66074cdf292",
"bargainTerms": {
"bargainAllowed": null,
"vatType": null,
"agentFee": null,
"clientFee": null,
"paymentPeriod": null,
"leaseType": null,
"deposit": null,
"mortgageAllowed": true,
"vatPrice": null,
"utilitiesTerms": null,
"price": 7990000,
"leaseTermType": null,
"priceRur": 7990000,
"tags": [],
"priceType": "all",
"priceForWorkplace": null,
"saleType": "alternative",
"currency": "rur",
"agentBonus": null
},
"roomsCount": 2,
"flatType": "rooms",
"totalArea": "47.2",
"livingArea": "28.0",
"kitchenArea": "7.5",
"floorNumber": 2,
"building": {
"floorsCount": 17,
"parking": {
"locationType": null,
"type": "ground",
"priceEntry": null,
"purposeType": null,
"isFree": null,
"isAvailable": null,
"currency": null,
"priceMonthly": null,
"placesCount": null
},
"deadline": null,
"type": null,
"materialType": "panel",
"heatingType": null,
"accessType": null,
"classType": null,
"buildYear": 1980,
"cargoLiftsCount": 1,
"passengerLiftsCount": 1
},
"cadastralNumber": null,
"buildingCadastralNumber": null,
"geo": {
"coordinates": {
"lat": 56.820035,
"lng": 60.634287
},
"address": [
{
"type": "location",
"geoType": "location",
"name": "Свердловская",
"fullName": "Свердловская область",
"id": 4612
},
{
"type": "location",
"geoType": "location",
"name": "Екатеринбург",
"fullName": "Екатеринбург",
"id": 4743
},
{
"type": "raion",
"geoType": "district",
"name": "Октябрьский",
"fullName": "р-н Октябрьский",
"id": 290
},
{
"type": "mikroraion",
"geoType": "district",
"name": "Парковый",
"fullName": "мкр. Парковый",
"id": 1489
},
{
"type": "metro",
"geoType": "underground",
"name": "Геологическая",
"fullName": "м. Геологическая",
"id": 346
},
{
"type": "street",
"geoType": "street",
"name": "Большакова",
"fullName": "улица Большакова",
"id": 1486289
},
{
"type": "house",
"geoType": "house",
"name": "13",
"fullName": "13",
"id": 10385250
}
],
"undergrounds": [
{
"name": "Геологическая",
"time": 5,
"transportType": "transport",
"lineColor": "00701A",
"lineId": 26,
"isDefault": true
},
{
"name": "Чкаловская",
"time": 5,
"transportType": "transport",
"lineColor": "00701A",
"lineId": 26,
"isDefault": false
},
{
"name": "Площадь 1905 года",
"time": 6,
"transportType": "transport",
"lineColor": "00701A",
"lineId": 26,
"isDefault": false
}
]
},
"newbuilding": null,
"balconiesCount": 0,
"loggiasCount": 1,
"bedroomsCount": null,
"hasFurniture": true,
"isSoldFurnished": true,
"decoration": null,
"isByHomeowner": false,
"isPro": true,
"isApartments": false,
"isRosreestrChecked": false,
"description": "В продаже двухкомнатная квартира с качественным ремонтом. \n\nРайон расположения квартиры с развитой инфраструктурой. Рядом множество детских садов и школ, городской парк развлечений \"Маяковского\" Различные бесплатные спортивные школы и секции. \n\nВо дворе дома есть бесплатные парковочные места. Места общего пользования в доме после капитального ремонта, на стенах первого этажа керамогранит, оба лифта новые. Установлена качественная сейф-дверь. В прихожей на полу керамогранитная плитка. Имеется теплый пол в прихожей. Удобно содержать пол в прихожей в чистоте. \nОстается шкаф купе в прихожей. На кухне установлен кухонный гарнитур, оставляем бытовую технику - варочную панель, духовой шкаф, вытяжку, посудомоечную машину. Окна во всей квартире и на лоджии заменены на новые пластиковые. Сан узел раздельный, в кафеле. Стояки ГВС И ХВС заменены в 2017 году.\n\nВ квартире заменена вся электропроводка. Утеплена лоджия, на полу кафельная плитка, на лоджии имеется теплый пол. Так же в стоимость квартиры входят все светильники и шторы. Рассмотрим трейд ин: у Вас не продана своя недвижимость - у нас есть кому ее предложить.\n Найдем покупателя на Вашу квартиру с фиксацией цены на наш объект. Звоните и записывайтесь на просмотр! \nДобавляйте объявление в избранное, что бы не потерять его. Просто звоните или пишите и мы найдем способ как Вам приобрести квартиру в Вашем бюджете! 2-комнатная квартира в Екатеринбурге.",
"descriptionMinhash": [
16147461,
5781654,
4548911,
115530169,
2088518,
13989882,
5236906,
29626041,
89222106,
31854558,
1755821,
30053968,
4178831,
69970867,
23526500,
79279,
22630780,
105139689,
7157020,
7498881,
439139,
40231865,
5793994,
2284150,
9460550,
20249216,
16249532,
8291340,
9226251,
55362858,
4609260,
18591719
],
"photos": [
{
"fullUrl": "https://images.cdn-cian.ru/images/kvartira-ekaterinburg-bolshakova-ulica-2915101466-1.jpg"
},
{
"fullUrl": "https://images.cdn-cian.ru/images/kvartira-ekaterinburg-bolshakova-ulica-2914203429-1.jpg"
},
{
"fullUrl": "https://images.cdn-cian.ru/images/kvartira-ekaterinburg-bolshakova-ulica-2915101469-1.jpg"
}
],
"addedTimestamp": 1789387127,
"offerType": "flat",
"category": "flatSale",
"fromDeveloper": false,
"buildersIds": [],
"isLayoutApproved": null,
"isByCommercialOwner": null,
"isCianPartner": false,
"descriptionWordsHighlighted": []
},
{
"cianId": 333599038,
"id": 333599038,
"fullUrl": "https://ekb.cian.ru/sale/flat/333599038/?context=4.RIlzprvdmBE.EoIb2G8IrpAJRWmWSKXZB54SBE-BI5E9ffyir3i1BCpfutwDcbFaqH8RXGcSSW2eGb_oYww9PzpC-g&mlSearchSessionGuid=924c429bf7495771ff77b66074cdf292",
"bargainTerms": {
"price": 4834000,
"vatPrice": null,
"deposit": null,
"saleType": "fz214",
"bargainAllowed": null,
"vatType": null,
"agentBonus": null,
"priceForWorkplace": null,
"agentFee": null,
"priceRur": 4834000,
"leaseType": null,
"mortgageAllowed": null,
"clientFee": null,
"paymentPeriod": null,
"tags": [],
"priceType": "all",
"utilitiesTerms": null,
"leaseTermType": null,
"currency": "rur"
},
"roomsCount": 2,
"flatType": "rooms",
"totalArea": "36.04",
"livingArea": "19.8",
"kitchenArea": null,
"floorNumber": 22,
"building": {
"accessType": null,
"materialType": "monolith",
"cargoLiftsCount": null,
"deadline": {
"quarterEnd": "2028-12-31",
"year": 2028,
"quarter": "fourth",
"isComplete": false
},
"type": null,
"buildYear": null,
"heatingType": null,
"passengerLiftsCount": 7,
"classType": null,
"floorsCount": 32,
"parking": null
},
"cadastralNumber": null,
"buildingCadastralNumber": null,
"geo": {
"coordinates": {
"lng": 60.538293335967175,
"lat": 56.875540746307834
},
"address": [
{
"type": "location",
"geoType": "location",
"name": "Свердловская",
"fullName": "Свердловская область",
"id": 4612
},
{
"type": "location",
"geoType": "location",
"name": "Екатеринбург",
"fullName": "Екатеринбург",
"id": 4743
},
{
"type": "raion",
"geoType": "district",
"name": "Железнодорожный",
"fullName": "р-н Железнодорожный",
"id": 287
},
{
"type": "mikroraion",
"geoType": "district",
"name": "Старая Сортировка",
"fullName": "мкр. Старая Сортировка",
"id": 1494
},
{
"type": "metro",
"geoType": "underground",
"name": "Уральская",
"fullName": "м. Уральская",
"id": 343
},
{
"type": "street",
"geoType": "street",
"name": "Проходной",
"fullName": "переулок Проходной",
"id": 1486939
},
{
"type": "house",
"geoType": "house",
"name": "1Б",
"fullName": "1Б",
"id": 13406495
}
],
"undergrounds": [
{
"name": "Уральская",
"time": 9,
"transportType": "transport",
"lineColor": "00701A",
"lineId": 26,
"isDefault": true
},
{
"name": "Уралмаш",
"time": 9,
"transportType": "transport",
"lineColor": "00701A",
"lineId": 26,
"isDefault": null
},
{
"name": "Машиностроителей",
"time": 9,
"transportType": "transport",
"lineColor": "00701A",
"lineId": 26,
"isDefault": null
}
]
},
"newbuilding": {
"showJkReliableFlag": false,
"isFromDeveloper": true,
"name": "Астон. Движение Вверх",
"isFromLeadFactory": false,
"isFichering": true,
"isFromBuilder": true,
"isPremium": false,
"house": {
"finishDate": {
"year": 2028,
"quarter": 4
},
"section": null,
"name": "Дом 5",
"isFinished": false,
"id": 10930182
},
"hasFlatTourBooking": false,
"comparisonStatus": null,
"id": 5779142,
"isSalesLeader": null,
"isFromSeller": false,
"countPromos": 2,
"snippetType": "advanced"
},
"balconiesCount": null,
"loggiasCount": null,
"bedroomsCount": null,
"hasFurniture": null,
"decoration": null,
"isByHomeowner": null,
"isPro": true,
"isApartments": false,
"isRosreestrChecked": false,
"description": "2-комнатная квартира с предчистовой отделкой в ЖК \"Астон. Движение вверх\" на 22 этаже. Общая площадь 36.04 м², жилая 19.83 м². \n\n\"Астон. Движение вверх\" два монолитно-блочных дома на Новой Сортировке в Екатеринбурге: один высотой 16 этажей, другой 32 этажа. Проект продолжает историю соседнего квартала \"Астон. Движение\", который уже полюбился жителям района. Строительство ведётся на месте бывшего Виншампанкомбината у этого места есть своя душа и почти вековая история.\n\nАрхитектура обыгрывает наследие территории: палитра фасадов, плавные линии и изгибы переосмысляют фактуры продукции комбината. В проекте много внимания уделено свету увеличенное остекление, продуманные планировки без глухих коридоров и тёмных углов. Из окон верхних этажей открывается панорама города это самый высокий комфорт-класс в микрорайоне.\n\nВ квартирах просторные кухни-гостиные, мастер-спальни с гардеробными, балконы и тёплые лоджии. В некоторых планировках окна в ванной комнате, что наполняет утро солнечным светом. В 32-этажной секции пять грузопассажирских лифтов, в 16-этажной по два лифта на этаж.\n\nДвор закрыт от машин, с зонами для детей разного возраста: для малышей песочницы и качели, для подростков пространство для встреч с друзьями. В просторных входных группах на первых этажах есть место для колясок. Первая очередь квартала уже сдана по утрам здесь пахнет кофе из кафе на первых этажах, вечерами гуляют с колясками и собаками.\n\nРасположение пересечение улицы Пехотинцев и Проходного переулка, в 5 минутах от Бебеля. Рядом детские сады, школы, поликлиники, торговые и фитнес-центры, остановки общественного транспорта. До центра Екатеринбурга 2530 минут на машине. Срок сдачи IV квартал 2028 года.\n\nЗастройщик: федеральный девелопер Астон, реализующий проекты в пяти городах России. Уже передано более 400 000 м² жилья. Продам 2-комнатную квартиру в Екатеринбурге.",
"descriptionMinhash": [
18308017,
8359824,
15811915,
9618444,
2088518,
6143519,
1770676,
52418947,
12009655,
11955293,
15731187,
1006418,
4178831,
5064683,
5165062,
28709808,
27942,
105139689,
37372265,
6351486,
31972117,
53400966,
5793994,
30731728,
9460550,
5740363,
24836331,
794253,
37707718,
2676229,
24769842,
1768206
],
"photos": [
{
"fullUrl": "https://images.cdn-cian.ru/images/2957954042-1.jpg"
},
{
"fullUrl": "https://images.cdn-cian.ru/images/2957954049-1.jpg"
},
{
"fullUrl": "https://images.cdn-cian.ru/images/2957954053-1.jpg"
}
],
"addedTimestamp": 1789377541,
"offerType": "flat",
"category": "newBuildingFlatSale",
"fromDeveloper": true,
"buildersIds": [
10137
],
"isLayoutApproved": null,
"isByCommercialOwner": null,
"isCianPartner": false,
"descriptionWordsHighlighted": []
},
{
"cianId": 333279363,
"id": 333279363,
"fullUrl": "https://ekb.cian.ru/sale/flat/333279363/?context=4.eJtxwNSILJE.YF5Ute3CO5a6ojZp8vw4v4wbhDX3F1ibU5rfq6T0Mt7TEHmtX3_CR5L7_Brx_JZMKAgoly3KHiIsxg&mlSearchSessionGuid=924c429bf7495771ff77b66074cdf292",
"bargainTerms": {
"tags": [],
"currency": "rur",
"priceType": "all",
"paymentPeriod": null,
"mortgageAllowed": null,
"leaseTermType": null,
"priceForWorkplace": null,
"clientFee": null,
"agentBonus": null,
"saleType": "pdkp",
"price": 8523568,
"priceRur": 8523568,
"deposit": null,
"vatPrice": null,
"vatType": null,
"leaseType": null,
"agentFee": null,
"utilitiesTerms": null,
"bargainAllowed": null
},
"roomsCount": 2,
"flatType": "rooms",
"totalArea": "56.03",
"livingArea": "21.8",
"kitchenArea": null,
"floorNumber": 4,
"building": {
"floorsCount": 8,
"deadline": {
"year": 2026,
"isComplete": true,
"quarterEnd": "2026-09-30",
"quarter": "third"
},
"cargoLiftsCount": null,
"type": null,
"accessType": null,
"passengerLiftsCount": null,
"classType": null,
"parking": null,
"materialType": null,
"heatingType": null,
"buildYear": null
},
"cadastralNumber": null,
"buildingCadastralNumber": null,
"geo": {
"coordinates": {
"lat": 56.833388,
"lng": 60.520165
},
"address": [
{
"type": "location",
"geoType": "location",
"name": "Свердловская",
"fullName": "Свердловская область",
"id": 4612
},
{
"type": "location",
"geoType": "location",
"name": "Екатеринбург",
"fullName": "Екатеринбург",
"id": 4743
},
{
"type": "raion",
"geoType": "district",
"name": "Верх-Исетский",
"fullName": "рВерх-Исетский",
"id": 286
},
{
"type": "mikroraion",
"geoType": "district",
"name": "ВИЗ",
"fullName": "мкр. ВИЗ",
"id": 1468
},
{
"type": "metro",
"geoType": "underground",
"name": "Геологическая",
"fullName": "м. Геологическая",
"id": 346
},
{
"type": "street",
"geoType": "street",
"name": "Крауля",
"fullName": "улица Крауля",
"id": 1486179
},
{
"type": "house",
"geoType": "house",
"name": "170Б",
"fullName": "170Б",
"id": 16773087
}
],
"undergrounds": [
{
"name": "Геологическая",
"time": 9,
"transportType": "transport",
"lineColor": "00701A",
"lineId": 26,
"isDefault": true
},
{
"name": "Площадь 1905 года",
"time": 10,
"transportType": "transport",
"lineColor": "00701A",
"lineId": 26,
"isDefault": null
},
{
"name": "Уральская",
"time": 13,
"transportType": "transport",
"lineColor": "00701A",
"lineId": 26,
"isDefault": null
}
]
},
"newbuilding": {
"id": 49143,
"showJkReliableFlag": false,
"isFromSeller": false,
"isFichering": true,
"name": "Янтарная Долина",
"isPremium": true,
"house": {
"id": 10931059,
"name": "6 очередь",
"section": "Секция 2",
"finishDate": {
"quarter": 3,
"year": 2026
},
"isFinished": true
},
"hasFlatTourBooking": false,
"comparisonStatus": null,
"isSalesLeader": null,
"isFromDeveloper": true,
"isFromBuilder": true,
"isFromLeadFactory": false,
"countPromos": 3,
"snippetType": "maximum"
},
"balconiesCount": 1,
"loggiasCount": 1,
"bedroomsCount": null,
"hasFurniture": null,
"decoration": "fine",
"isByHomeowner": null,
"isPro": true,
"isApartments": false,
"isRosreestrChecked": false,
"description": "Жилой квартал 'Янтарная долина' расположен в районе Новый ВИЗ, вблизи Верх-Исетского пруда. Предлагаем вам квартиры в шестой очереди строительства — секциях 6.2-6.4 по адресу улица Крауля, 170Б. Все секции 8-этажные, в доме 147 квартир. Дом сдан. Продажа по ДКП.\nРайон\nРайон Новый ВИЗ сочетает городской комфорт и близость природных зон. Это одно из самых перспективных направлений в Екатеринбурге, идеальное для семейного проживания.\n\n5 минут пешком до остановки транспорта\n5 школ и 4 детсада поблизости\n10 минут пешком до набережной Верх-Исетского пруда\n15 минут на машине до центра города\n16 минут на машине до ТЦ 'МЕГА'\n\nДвор\nВ центре жилого квартала — зелёный двор для насыщенной жизни, игр и отдыха. Озеленение включает лиственные и хвойные деревья, декоративные кустарники и цветники.\n\nКонцепция 'двор без машин'\nЗакрытая территория с пропускной системой и без посторонних\nКруглосуточное видеонаблюдение и освещение территории в тёмное время суток\nДетские площадки для разного возраста, футбол и уличный баскетбол\n \n\nПодъезды\nПодъезд в 'Янтарной долине' — уютное и светлое пространство, в котором комфортно.\n\nДоступ в подъезды на уровне земли — без барьеров\nДомофоны в тёплых остеклённых тамбурах\nВместительный грузопассажирский лифт во всех секциях\nПомещения для колясок и велосипедов на 1 этаже\nУдобная навигация с номерами этажей и квартир\n\nПланировки\nНаш подход 'разумные метры' при проектировании квартир учитывает состав семьи, число жильцов, их потребности, а также эргономику пространства.\n\n58 уникальных планировок\nнесколько вариантов для каждого типа квартир: от студий до 3-комнатных\nобъединённые кухни-гостиные для простора и общения\nрациональное использование каждого метра\nквартиры с гардеробными и тёплыми лоджиями\n\nОтделка\nВ каждой квартире 'Янтарной долины' мы позаботились о чистовой отделке, чтобы ваш переезд прошёл быстро и без лишних хлопот.\n\nПрочный ламинат на полу в комнатах, прихожих и кухнях, плитка в санузлах, виниловые обои на флизелиновой основе на стенах, белые натяжные потолки\nМежкомнатные двери светлых тонов и сейф-дверь на входе в квартиру\nРадиаторы с терморегуляторами и горизонтальной разводкой, двухкамерные стеклопакеты с приточными клапанами вентиляции, розетки и выключатели\nВ санузлах установлены раковины и унитазы Продается 2-комнатная квартира в Екатеринбурге.",
"descriptionMinhash": [
1799783,
5781654,
15811915,
4788959,
35289122,
6143519,
18432622,
45820160,
2258400,
11955293,
25662122,
7185657,
23168773,
14391071,
12464540,
13379849,
41127868,
40846940,
36828281,
6351486,
53971654,
349072,
10292969,
2066855,
30469210,
5740363,
14611990,
794253,
929833,
501391,
122143391,
5031640
],
"photos": [
{
"fullUrl": "https://images.cdn-cian.ru/images/2950950202-1.jpg"
},
{
"fullUrl": "https://images.cdn-cian.ru/images/2950950220-1.jpg"
},
{
"fullUrl": "https://images.cdn-cian.ru/images/2950950228-1.jpg"
}
],
"addedTimestamp": 1789408293,
"offerType": "flat",
"category": "newBuildingFlatSale",
"fromDeveloper": true,
"buildersIds": [
10188
],
"isLayoutApproved": null,
"isByCommercialOwner": null,
"isCianPartner": false,
"descriptionWordsHighlighted": []
},
{
"cianId": 321628098,
"id": 321628098,
"fullUrl": "https://ekb.cian.ru/sale/flat/321628098/?context=4.ZmmZlV8JbZQ.lF2e-AgOjLa29ue-yfljyTo7TzR6qFjcVKH6jOXZzubqAE_l5Ll_VyYwlGbvrZUPDs24HvZ1DfBOXg&mlSearchSessionGuid=924c429bf7495771ff77b66074cdf292",
"bargainTerms": {
"bargainAllowed": null,
"vatType": null,
"agentFee": null,
"clientFee": null,
"paymentPeriod": null,
"leaseType": null,
"deposit": null,
"mortgageAllowed": null,
"vatPrice": null,
"utilitiesTerms": null,
"price": 7815000,
"leaseTermType": null,
"priceRur": 7815000,
"tags": [],
"priceType": "all",
"priceForWorkplace": null,
"saleType": "free",
"currency": "rur",
"agentBonus": null
},
"roomsCount": 2,
"flatType": "rooms",
"totalArea": "54.3",
"livingArea": "25.2",
"kitchenArea": "16.6",
"floorNumber": 6,
"building": {
"floorsCount": 31,
"parking": null,
"deadline": {
"quarter": "fourth",
"year": 2028,
"isComplete": false,
"quarterEnd": "2028-12-31"
},
"type": null,
"materialType": null,
"heatingType": null,
"accessType": null,
"classType": null,
"buildYear": null,
"cargoLiftsCount": null,
"passengerLiftsCount": 8
},
"cadastralNumber": null,
"buildingCadastralNumber": null,
"geo": {
"coordinates": {
"lat": 56.789383109239054,
"lng": 60.60020415337982
},
"address": [
{
"type": "location",
"geoType": "location",
"name": "Свердловская",
"fullName": "Свердловская область",
"id": 4612
},
{
"type": "location",
"geoType": "location",
"name": "Екатеринбург",
"fullName": "Екатеринбург",
"id": 4743
},
{
"type": "raion",
"geoType": "district",
"name": "Чкаловский",
"fullName": "р-н Чкаловский",
"id": 291
},
{
"type": "mikroraion",
"geoType": "district",
"name": "Вторчермет",
"fullName": "мкр. Вторчермет",
"id": 1509
},
{
"type": "metro",
"geoType": "underground",
"name": "Ботаническая",
"fullName": "м. Ботаническая",
"id": 348
}
],
"undergrounds": [
{
"name": "Ботаническая",
"time": 6,
"transportType": "transport",
"lineColor": "00701A",
"lineId": 26,
"isDefault": true
},
{
"name": "Чкаловская",
"time": 6,
"transportType": "transport",
"lineColor": "00701A",
"lineId": 26,
"isDefault": null
},
{
"name": "Геологическая",
"time": 9,
"transportType": "transport",
"lineColor": "00701A",
"lineId": 26,
"isDefault": null
}
]
},
"newbuilding": {
"isFromBuilder": true,
"isFichering": true,
"name": "Стадиум",
"id": 5705732,
"house": {
"section": "Секция 4",
"name": "Корпус 1",
"id": 10906188,
"isFinished": false,
"finishDate": {
"quarter": 4,
"year": 2028
}
},
"hasFlatTourBooking": false,
"isSalesLeader": null,
"comparisonStatus": null,
"isPremium": true,
"showJkReliableFlag": false,
"isFromSeller": false,
"isFromLeadFactory": false,
"isFromDeveloper": true,
"countPromos": 10,
"snippetType": "maximum"
},
"balconiesCount": null,
"loggiasCount": null,
"bedroomsCount": null,
"hasFurniture": null,
"decoration": null,
"isByHomeowner": null,
"isPro": true,
"isApartments": false,
"isRosreestrChecked": false,
"description": "ЖК \"Стадиум\" — это ваш шанс жить в одном из самых зеленых уголков Екатеринбурга, но при этом в шаговой доступности от всего важного. Комплекс строится между улицами 8 Марта, Рижским переулком, Московской и Объездной дорогой — в сердце развивающегося Чкаловского района на пересечении границ микрорайонов Ботаники, Автовокзала и Вторчермета. \n\nПредставьте: утром вы выходите в свой закрытый двор-парк с зонами для игр и спорта, а через 10 минут уже в ТЦ \"Ботаника Молл\" или деловом центре \"Астор\". Рядом — детсады, школы, поликлиники, и главное: буквально рукой подать до Парка завода РТИ, Юго-Западного лесопарка и легендарного Ботанического сада. Четыре корпуса разной этажности (от 9 до 31) спроектированы так, чтобы из квартир открывались виды на лесные массивы. \n\nЗдесь воздух по-настоящему свежий, а квартиры — это и евроформаты, и мастер-спальни с ванной комнатой, варианты с террасами и эксклюзивные планировочные решения с зенитными фонарями и панорамными окнами. Современные холлы с 4-метровыми потолками и зоной ресепшна, подземный паркинг и продуманное озеленение создают ощущение, что вы живете за городом, но со всеми плюсами мегаполиса. \n\n\"Стадиум\" — для тех, кто хочет просыпаться под пение птиц, но не готов отказываться от ритма большого города. Это баланс, который так ценят екатеринбуржцы.\" \n\nПреимущества: \n\nВидовые квартиры с террасами \n\nПодземный двухуровневый паркинг \n\nЭксклюзивные варианты с зенитными фонарями \n\nПанорамное остекление \n\nКрышная газовая котельная \n\nПодготовка под установку бризеров \n\nВытяжная вентиляция с механическим побуждением \n\nЧистая питьевая вода в каждой квартире \n\nЛобби с зоной ресепшн и хранением посылок \n\nРаздельные велосипедные и колясочные \n\nСобственная сервисная компания \n\nДизайнерская архитектура фасадов \n\nКонцепция спорта и здорового образа жизни \n\nМесто покоя и природы среди шума города\nЕкатеринбург, 8 Марта , 01 кв.324",
"descriptionMinhash": [
41920473,
15036167,
44672376,
14416152,
2088757,
6143519,
11414134,
2885405,
2746117,
42658346,
10643968,
39685766,
19735224,
5057571,
13742308,
95802641,
4256809,
38329502,
105651759,
59650532,
8686384,
35024833,
4368848,
38907129,
9460550,
6586538,
9615691,
3940137,
1195648,
10860617,
8412860,
37661809
],
"photos": [
{
"fullUrl": "https://images.cdn-cian.ru/images/2622692186-1.jpg"
},
{
"fullUrl": "https://images.cdn-cian.ru/images/2626766693-1.jpg"
},
{
"fullUrl": "https://images.cdn-cian.ru/images/2626766766-1.jpg"
}
],
"addedTimestamp": 1789634608,
"offerType": "flat",
"category": "newBuildingFlatSale",
"fromDeveloper": true,
"buildersIds": [
8624
],
"isLayoutApproved": null,
"isByCommercialOwner": null,
"isCianPartner": false,
"descriptionWordsHighlighted": []
}
]
}
},
"priority": 10,
"filter": {}
}
]);
</script>
</body></html>

View file

@ -0,0 +1,120 @@
{
"_comment": "#2422: живой ответ gate-API Яндекс Недвижимости, снят 2026-09-17 (curl_cffi impersonate=chrome, HTTP 200): rgid=559132 type=SELL category=APARTMENT newFlat=YES page=1. Урезано: 1 из 23 entities, у entity только читаемые _entity_to_lot ключи, author без agentName (PII), mainImages до 6. Значения полей не менялись.",
"response": {
"search": {
"offers": {
"entities": [
{
"offerId": "6216493807134609867",
"url": "//realty.yandex.ru/offer/6216493807134609867",
"price": {
"currency": "RUR",
"value": 27620850,
"period": "WHOLE_LIFE",
"unit": "WHOLE_OFFER",
"trend": "UNCHANGED",
"hasPriceHistory": false,
"valuePerPart": 366859,
"unitPerPart": "SQUARE_METER",
"valueForWhole": 27620850,
"unitForWhole": "WHOLE_OFFER",
"price": {
"value": 27620850,
"currency": "RUB",
"priceType": "PER_OFFER",
"pricingPeriod": "WHOLE_LIFE"
},
"pricePerPart": {
"value": 366859,
"currency": "RUB",
"priceType": "PER_METER",
"pricingPeriod": "WHOLE_LIFE"
},
"priceForWhole": {
"value": 27620850,
"currency": "RUB",
"priceType": "PER_OFFER",
"pricingPeriod": "WHOLE_LIFE"
},
"minMonthlyMortgagePayment": 282584,
"mortgageInfo": {
"bestMortgage": {
"id": "2974521",
"payment": "282584"
},
"minPreferentialMortgageRate": 6
}
},
"area": {
"value": 75.29,
"unit": "SQUARE_METER"
},
"livingSpace": {
"value": 29.03,
"unit": "SQUARE_METER"
},
"roomsTotal": 2,
"floorsOffered": [
12
],
"floorsTotal": 16,
"building": {
"builtYear": 2026,
"siteId": 2006795,
"siteName": "Александровский сад"
},
"location": {
"point": {
"latitude": 56.821068,
"longitude": 60.609924,
"precision": "EXACT"
},
"geocoderAddress": "Россия, Свердловская область, Екатеринбург, жилой район Центральный, ЖК Александровский Сад",
"streetAddress": "Свердловская область, Екатеринбург, Декабристов",
"metro": {
"metroGeoId": 101986,
"metroCityRgid": 559132,
"name": "Геологическая",
"metroTransport": "ON_FOOT",
"timeToMetro": 9,
"latitude": 56.827534,
"longitude": 60.602486,
"minTimeToMetro": 8,
"lineColors": [
"f03d2f"
],
"rgbColor": "f03d2f"
},
"metroList": [
{
"name": "Геологическая",
"timeToMetro": 9
}
]
},
"creationDate": "2026-03-17T11:04:44Z",
"description": "Александровский сад настоящий бизнес-класс в центре Екатеринбурга от девелопера Forum. Забудьте о суете города. Ваш двор это пейзажный сад, где царят умиротворение и гармония. Прогулочные дорожки с мягкой вечерней подсветкой проведут по уединенным",
"author": {
"category": "DEVELOPER"
},
"mainImages": [
"//avatars.mds.yandex.net/get-realty-offers/20177950/9476e26d-8cbc-4b69-a7bd-03fefa0a960a/main",
"//avatars.mds.yandex.net/get-realty-offers/16258130/d185b4dc-8386-4fa1-bdf9-0865da1f8e5d/main",
"//avatars.mds.yandex.net/get-realty-offers/13970442/da0f5415-d014-466b-b5e9-0fe70d5e8680/main",
"//avatars.mds.yandex.net/get-realty-offers/14824781/f882caeb-4d40-4775-9f0a-acb01177ff0e/main",
"//avatars.mds.yandex.net/get-realty-offers/20657783/1359e480-6c21-4c07-b1de-9e36149f83fd/main",
"//avatars.mds.yandex.net/get-realty-offers/14671802/38d6dc24-8366-4460-9e99-06a1605e1919/main"
]
}
],
"pager": {
"page": 0,
"pageSize": 20,
"sitesPageSize": 20,
"totalItems": 32234,
"totalPages": 25
}
}
}
}
}

View file

@ -0,0 +1,355 @@
{
"_comment": "#2422: живой ответ gate-API Яндекс Недвижимости, снят 2026-09-17 (curl_cffi impersonate=chrome, HTTP 200): rgid=559132 type=SELL category=APARTMENT newFlat=NO page=1. Урезано: 3 из 23 entities, у entity только читаемые _entity_to_lot ключи, author без agentName (PII), mainImages до 6. Значения полей не менялись.",
"response": {
"search": {
"offers": {
"entities": [
{
"offerId": "5228381943217465492",
"url": "https://novostroi-ekb.ru/catalog/sale/vtorichka/object/1064",
"price": {
"currency": "RUR",
"value": 4400000,
"period": "WHOLE_LIFE",
"unit": "WHOLE_OFFER",
"trend": "DECREASED",
"previous": 4450000,
"hasPriceHistory": true,
"valuePerPart": 129412,
"unitPerPart": "SQUARE_METER",
"valueForWhole": 4400000,
"unitForWhole": "WHOLE_OFFER",
"price": {
"value": 4400000,
"currency": "RUB",
"priceType": "PER_OFFER",
"pricingPeriod": "WHOLE_LIFE"
},
"pricePerPart": {
"value": 129412,
"currency": "RUB",
"priceType": "PER_METER",
"pricingPeriod": "WHOLE_LIFE"
},
"priceForWhole": {
"value": 4400000,
"currency": "RUB",
"priceType": "PER_OFFER",
"pricingPeriod": "WHOLE_LIFE"
}
},
"area": {
"value": 34,
"unit": "SQUARE_METER"
},
"kitchenSpace": {
"value": 7,
"unit": "SQUARE_METER"
},
"roomsTotal": 1,
"floorsOffered": [
8
],
"floorsTotal": 9,
"ceilingHeight": 2.5,
"building": {
"builtYear": 1978,
"buildingType": "BRICK"
},
"location": {
"point": {
"latitude": 56.903275,
"longitude": 60.6229,
"precision": "EXACT"
},
"geocoderAddress": "Россия, Свердловская область, Екатеринбург, Шефская улица, 85",
"streetAddress": "Шефская улица, 85",
"metro": {
"metroGeoId": 101947,
"metroCityRgid": 559132,
"name": "Проспект Космонавтов",
"metroTransport": "ON_FOOT",
"timeToMetro": 7,
"latitude": 56.901844,
"longitude": 60.61394,
"minTimeToMetro": 5,
"lineColors": [
"f03d2f"
],
"rgbColor": "f03d2f"
},
"metroList": [
{
"name": "Проспект Космонавтов",
"timeToMetro": 7
},
{
"name": "Машиностроителей",
"timeToMetro": 14
}
]
},
"creationDate": "2026-08-19T06:23:51Z",
"description": "1 комнатная квартира 34 кв .м с развитой инфраструктурой , станция метро «КОСМАНАВТОВ» в шаговой доступности . Фукциональная планировка правильной формы, дополнительное место хранения гардеробная , в квартире сделана качественная предчистовая отделка",
"author": {
"category": "AGENCY",
"organization": "САМОЛЕТ ПЛЮС"
},
"predictions": {
"predictedPrice": {
"min": "3795000",
"max": "4638000",
"value": "4217000"
}
},
"mainImages": [
"//avatars.mds.yandex.net/get-realty-offers/14333116/89f721ff-ad4b-49f1-8cec-432ad11baeab/main",
"//avatars.mds.yandex.net/get-realty-offers/13853239/caf4740f-dca8-47a3-8005-56968d20a3b1/main",
"//avatars.mds.yandex.net/get-realty-offers/14244677/f1943334-731e-4539-98f6-9d38d4a725ce/main",
"//avatars.mds.yandex.net/get-realty-offers/12726755/346626db-ba69-4a49-8940-76f08d6aa924/main",
"//avatars.mds.yandex.net/get-realty-offers/20074534/021cc849-6554-4059-9423-19193e3006b9/main",
"//avatars.mds.yandex.net/get-realty-offers/13912669/b240dbf0-1adc-4dbd-9ce8-c0ca6c478fc8/main"
]
},
{
"offerId": "6282460993454547968",
"url": "//realty.yandex.ru/offer/6282460993454547968",
"price": {
"currency": "RUR",
"value": 10290000,
"period": "WHOLE_LIFE",
"unit": "WHOLE_OFFER",
"trend": "UNCHANGED",
"hasPriceHistory": false,
"valuePerPart": 294000,
"unitPerPart": "SQUARE_METER",
"valueForWhole": 10290000,
"unitForWhole": "WHOLE_OFFER",
"price": {
"value": 10290000,
"currency": "RUB",
"priceType": "PER_OFFER",
"pricingPeriod": "WHOLE_LIFE"
},
"pricePerPart": {
"value": 294000,
"currency": "RUB",
"priceType": "PER_METER",
"pricingPeriod": "WHOLE_LIFE"
},
"priceForWhole": {
"value": 10290000,
"currency": "RUB",
"priceType": "PER_OFFER",
"pricingPeriod": "WHOLE_LIFE"
}
},
"area": {
"value": 35,
"unit": "SQUARE_METER"
},
"livingSpace": {
"value": 11,
"unit": "SQUARE_METER"
},
"kitchenSpace": {
"value": 14,
"unit": "SQUARE_METER"
},
"roomsTotal": 1,
"floorsOffered": [
27
],
"floorsTotal": 27,
"ceilingHeight": 2.75,
"building": {
"builtYear": 2023,
"buildingType": "MONOLIT",
"siteId": 1668651,
"siteName": "Парковый квартал"
},
"location": {
"point": {
"latitude": 56.815243,
"longitude": 60.62593,
"precision": "EXACT"
},
"geocoderAddress": "Россия, Свердловская область, Екатеринбург, Машинная улица, 4",
"streetAddress": "Машинная улица, 4",
"metro": {
"metroGeoId": 116399,
"metroCityRgid": 559132,
"name": "Чкаловская",
"metroTransport": "ON_FOOT",
"timeToMetro": 17,
"latitude": 56.807995,
"longitude": 60.610184,
"minTimeToMetro": 12,
"lineColors": [
"f03d2f"
],
"rgbColor": "f03d2f"
},
"metroList": [
{
"name": "Чкаловская",
"timeToMetro": 17
},
{
"name": "Ботаническая",
"timeToMetro": 13
},
{
"name": "Площадь 1905 Года",
"timeToMetro": 16
},
{
"name": "Геологическая",
"timeToMetro": 20
}
]
},
"creationDate": "2026-08-02T14:04:19Z",
"description": "Продам квартиру в 10 минутах от центра города. Рядом Парк Маяковского. Хорошая транспортная доступность: до метро можно дойти пешком. Великолепный вид из окна.Хороший обзор. Высокие потолки, 2,75.Нет соседей сверху. Качественный",
"author": {
"category": "OWNER"
},
"predictions": {
"predictedPrice": {
"min": "8741000",
"max": "10683000",
"value": "9712000"
}
},
"mainImages": [
"//avatars.mds.yandex.net/get-realty-offers/10211679/add.69def35c26379a08babb742069d310dc.realty-api-vos/main",
"//avatars.mds.yandex.net/get-realty-offers/14373170/add.e2e2b76847d9b69e3abe0c64efee75c4.realty-api-vos/main",
"//avatars.mds.yandex.net/get-realty-offers/14717586/add.c65fd15bb15736448bac2a597ffd0bae.realty-api-vos/main",
"//avatars.mds.yandex.net/get-realty-offers/11512705/add.b894c4e448f696251deaed148ee9c30e.realty-api-vos/main",
"//avatars.mds.yandex.net/get-realty-offers/13847384/add.f13072669ebf54647b1682fc70fa9e99.realty-api-vos/main",
"//avatars.mds.yandex.net/get-realty-offers/14527622/add.1708ed4ada10c2d407f64b0e744d8047.realty-api-vos/main"
]
},
{
"offerId": "8530722463088927619",
"url": "//realty.yandex.ru/offer/8530722463088927619",
"price": {
"currency": "RUR",
"value": 5600000,
"period": "WHOLE_LIFE",
"unit": "WHOLE_OFFER",
"trend": "UNCHANGED",
"hasPriceHistory": false,
"valuePerPart": 269231,
"unitPerPart": "SQUARE_METER",
"valueForWhole": 5600000,
"unitForWhole": "WHOLE_OFFER",
"price": {
"value": 5600000,
"currency": "RUB",
"priceType": "PER_OFFER",
"pricingPeriod": "WHOLE_LIFE"
},
"pricePerPart": {
"value": 269231,
"currency": "RUB",
"priceType": "PER_METER",
"pricingPeriod": "WHOLE_LIFE"
},
"priceForWhole": {
"value": 5600000,
"currency": "RUB",
"priceType": "PER_OFFER",
"pricingPeriod": "WHOLE_LIFE"
}
},
"area": {
"value": 20.8,
"unit": "SQUARE_METER"
},
"livingSpace": {
"value": 12,
"unit": "SQUARE_METER"
},
"floorsOffered": [
8
],
"floorsTotal": 11,
"ceilingHeight": 2.7,
"building": {
"builtYear": 2021,
"buildingType": "MONOLIT_BRICK",
"siteId": 1948687,
"siteName": "Северный квартал"
},
"location": {
"point": {
"latitude": 56.89804,
"longitude": 60.6199,
"precision": "EXACT"
},
"geocoderAddress": "Россия, Свердловская область, Екатеринбург, переулок Ритслянда, 15",
"streetAddress": "переулок Ритслянда, 15",
"metro": {
"metroGeoId": 101947,
"metroCityRgid": 559132,
"name": "Проспект Космонавтов",
"metroTransport": "ON_FOOT",
"timeToMetro": 5,
"latitude": 56.901844,
"longitude": 60.61394,
"minTimeToMetro": 5,
"lineColors": [
"f03d2f"
],
"rgbColor": "f03d2f"
},
"metroList": [
{
"name": "Проспект Космонавтов",
"timeToMetro": 5
},
{
"name": "Уралмаш",
"timeToMetro": 13
},
{
"name": "Машиностроителей",
"timeToMetro": 12
}
]
},
"creationDate": "2026-09-15T06:17:51Z",
"description": "Продается прекрасная студия в ЖК Северные кварталы, застройщик Брусника. Дом комфорт класса Площадь студии 20,8 плюс теплая лоджия 4кв.м (убрана перегородка) -благодаря этому площадь студии увеличена до 24,8кв.м - компактно и функционально для одного",
"author": {
"category": "AGENCY",
"organization": "DOMOS CLUB"
},
"predictions": {
"predictedPrice": {
"min": "4562000",
"max": "5576000",
"value": "5069000"
}
},
"mainImages": [
"//avatars.mds.yandex.net/get-realty-offers/14727395/1635cddb-a57c-4cce-a6c1-c70b9aef13cd/main",
"//avatars.mds.yandex.net/get-realty-offers/14621926/3ae41eaf-5b30-46ec-b844-b40af2bb697a/main",
"//avatars.mds.yandex.net/get-realty-offers/12719313/6921877e-51f6-4d02-93ef-0395b99776bb/main",
"//avatars.mds.yandex.net/get-realty-offers/13297592/8a8d583b-d205-460e-aec8-c9cb827eae73/main",
"//avatars.mds.yandex.net/get-realty-offers/10246351/8eee4902-aa24-42dc-aaa2-9259c82146d8/main",
"//avatars.mds.yandex.net/get-realty-offers/14621926/116dc1df-3288-4236-acfe-2f660fb2ac8b/main"
]
}
],
"pager": {
"page": 0,
"pageSize": 20,
"sitesPageSize": 20,
"totalItems": 4077,
"totalPages": 25
}
}
}
}
}

View file

@ -0,0 +1,45 @@
"""scraper_kit не импортирует app.* (#2422 п.2).
Инвариант держал test_kit_avito_has_no_app_imports, удалённый вместе с golden-parity
(#2419). Проверка обходит весь пакет, а не только providers: импорт app внутри kit —
цикл зависимостей, где бы он ни стоял. Смотрит AST, поэтому находит и ленивый импорт
внутри функции, и не срабатывает на слово «app» в комментарии.
"""
from __future__ import annotations
import ast
import os
from pathlib import Path
import scraper_kit
# conftest.py (autouse) тянет app.core.config, которому нужен DATABASE_URL; БД не нужна.
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost:5432/test")
KIT_ROOT = Path(scraper_kit.__file__).parent
def _app_imports(path: Path) -> list[str]:
found: list[str] = []
for node in ast.walk(ast.parse(path.read_text(encoding="utf-8"), filename=str(path))):
if isinstance(node, ast.Import):
names = [alias.name for alias in node.names]
elif isinstance(node, ast.ImportFrom) and node.level == 0:
names = [node.module or ""]
else:
continue
found += [f"{n} (строка {node.lineno})" for n in names if n.split(".")[0] == "app"]
return found
def test_kit_has_no_app_imports() -> None:
files = sorted(KIT_ROOT.rglob("*.py"))
# Обход не пустой и дошёл до провайдеров — иначе тест зелёный по построению.
assert KIT_ROOT / "providers" / "avito" / "houses.py" in files
assert len(files) > 40
offenders = {
str(p.relative_to(KIT_ROOT)): imports for p in files if (imports := _app_imports(p))
}
assert offenders == {}

View file

@ -0,0 +1,401 @@
"""Разбор kit-парсеров на сохранённых страницах площадок: фикстура → лот (#2422, #2408).
Golden-parity тесты сверяли kit с legacy-копией и ушли вместе с ней (#2419). После
этого разбор avito.houses, cian.serp и yandex.serp не проверял никто: правка
извлечения поля в kit проходила бы зелёной. Здесь ожидания записаны прямо значениями
из сохранённых страниц, без второй реализации в роли эталона.
Фикстуры:
- avito_house_572538.html, avito_houses_sample.html карточки домов Авито
(вторичка), рейтинг только в reviews.filterEntries (#1789);
- avito_novostroyka_12879380.html карточка ЖК (developmentPage);
- avito_serp_sample.html выдача Авито, собранная из живой страницы (#726);
- cian_serp_ekb_2room_trimmed.html, yandex_gate_ekb_*_trimmed.json живые выдачи
2026-09-17, урезанные до читаемых парсером полей (что и как урезано в шапке
каждого файла; разбор урезанной и полной выдачи совпал поле в поле, кроме числа
фото).
Не покрыто здесь, потому что уже покрыто на фикстурах в своих файлах: avito.detail
(test_avito_detail_fields_3047.py), cian.detail (test_cian_views_parse.py,
test_cian_bti_house_persist.py), domclick (test_domclick_sweep.py,
scrapers/test_domclick_detail.py), base (scrapers/test_base_clamp_date.py).
"""
from __future__ import annotations
import json
import os
from datetime import date
from pathlib import Path
from types import SimpleNamespace
from typing import Any
import pytest
# conftest.py (autouse) тянет app.core.config, которому нужен DATABASE_URL; БД не нужна.
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost:5432/test")
from scraper_kit.providers.avito.houses import (
HouseCatalogEnrichment,
_extract_preloaded_state,
parse_houses_state,
)
from scraper_kit.providers.avito.serp import AvitoScraper
from scraper_kit.providers.cian.serp import CianScraper
from scraper_kit.providers.yandex.serp import _extract_gate_data, _parse_gate_json
FIXTURES = Path(__file__).parent.parent / "fixtures"
def _read(name: str) -> str:
return (FIXTURES / name).read_text(encoding="utf-8")
# ── avito.houses: карточка дома (вторичка) ─────────────────────────────────────
def _house(name: str, url: str) -> HouseCatalogEnrichment:
state = _extract_preloaded_state(_read(name))
assert state is not None, name
return parse_houses_state(state, url)
@pytest.fixture(scope="module")
def house_572538() -> HouseCatalogEnrichment:
return _house("avito_house_572538.html", "https://www.avito.ru/catalog/houses/572538")
@pytest.fixture(scope="module")
def house_115188() -> HouseCatalogEnrichment:
return _house("avito_houses_sample.html", "https://www.avito.ru/catalog/houses/115188")
def test_avito_house_characteristics(house_572538: HouseCatalogEnrichment) -> None:
h = house_572538.house
assert h.ext_id == 572538
assert h.ext_id_hash == "MTE3LjQ1Nzk0Mg"
assert h.full_address == "Свердловская область, Екатеринбург, ул. Сони Морозовой, 190"
assert (h.lat, h.lon) == (56.832359, 60.625583)
assert h.year_built == 1994
assert h.total_floors == 10
assert h.house_type == "brick"
assert h.material_floors == "Железобетонные"
assert h.hot_water == "Центральное"
assert h.passenger_elevators == 1
assert h.cargo_elevators is None # параметра нет на странице — не 0
assert h.has_concierge is True
assert h.has_playground is True
assert h.closed_yard is True
def test_avito_house_rating_from_reviews_filter_entries_1789(
house_572538: HouseCatalogEnrichment, house_115188: HouseCatalogEnrichment
) -> None:
"""В обеих карточках housePage отдаёт бейдж без оценки (scoreString «—»), оценка
есть только в reviews.filterEntries[type=score]. Без запасного пути #1789
рейтинг и число отзывов были бы None."""
h = house_572538.house
assert h.rating_score == pytest.approx(14 / 3)
assert h.rating_string == "4,7"
assert h.reviews_count == 3
assert [(r["score"], r["count"]) for r in h.rating_distribution] == [
(5, 2),
(4, 1),
(3, 0),
(2, 0),
(1, 0),
]
h2 = house_115188.house
assert (h2.rating_score, h2.rating_string, h2.reviews_count) == (4.0, "4,0", 4)
def test_avito_house_reviews(house_572538: HouseCatalogEnrichment) -> None:
reviews = {r.ext_review_id: r for r in house_572538.reviews}
assert list(reviews) == [2360701, 2018777, 1757422]
full = reviews[2018777]
assert full.author_name == "Иван"
assert full.score == 5
assert full.rated_date == date(2025, 9, 14)
assert full.text_main is not None and full.text_main.startswith("Желательно добавить")
assert full.text_pros is not None and full.text_pros.startswith("Удобное расположение")
assert full.text_cons is not None and full.text_cons.startswith("Возможно, проблемы")
# Секции без заголовка нет — основной текст пуст, плюсы/минусы на месте.
no_main = reviews[1757422]
assert no_main.rated_date == date(2025, 7, 9)
assert no_main.text_main is None
assert no_main.text_pros is not None and no_main.text_pros.startswith("Двор закрытый")
# Отзыв 2360701 датирован «16 апреля» без года — такую дату разбор сейчас не
# читает (rated_date=None). Это дефект, а не контракт, поэтому здесь не закреплён.
def test_avito_house_reviews_next_page(
house_572538: HouseCatalogEnrichment, house_115188: HouseCatalogEnrichment
) -> None:
assert house_572538.reviews_next_page_url is None # запись pages без nextPageUrl
assert house_115188.reviews_next_page_url == (
"/web/3/rating/model?categoryId=24&catalogId=283&paths[]=115188&limit=10&cursor=3"
"&sortBy[]=modelTypeId&sortBy[]=publishedAt&modelTypeId=14&developmentId=115188"
)
def test_avito_house_placement_history(house_572538: HouseCatalogEnrichment) -> None:
history = house_572538.placement_history
assert len(history) == 8
first = history[0]
assert first.ext_item_id == "2304054124"
assert first.title == "3-к. квартира, 108\xa0м², 5/10\xa0эт."
assert (first.start_price, first.last_price) == (11_000_000, 14_000_000)
assert first.start_price_date == date(2022, 1, 8)
assert first.last_price_date == date(2026, 1, 17)
assert first.exposure_days == 677
assert first.removed_date is None # виджет даты снятия не отдаёт
assert first.raw_payload is not None and "itemImage" not in first.raw_payload
def test_avito_house_mini_serp_and_seller(house_572538: HouseCatalogEnrichment) -> None:
# Цена в виджете — строка «21 700 000 ₽» с неразрывными пробелами.
assert [(m.ext_item_id, m.price_rub) for m in house_572538.mini_serp] == [
(8007602481, 21_700_000),
(8029678809, 21_600_000),
]
first = house_572538.mini_serp[0]
assert first.metro_text == "Геологическая, 1,5 км"
assert first.metro_color == "#0A6F20"
assert first.seller is not None
assert first.seller.ext_seller_id == "i251003466"
assert first.seller.name == "Самолет Плюс Центральный"
assert first.seller.seller_type == "Компания"
def test_avito_house_recommendations(house_115188: HouseCatalogEnrichment) -> None:
recs = house_115188.recommendations
assert len(recs) == 14
assert recs[0].title == "1-к. квартира, 30,3\xa0м², 3/5\xa0эт."
assert recs[0].price_text == "4\xa0000\xa0000 ₽"
assert recs[0].address == "Свердловская область, Екатеринбург, улица 40-летия Октября, 32А"
assert recs[0].image_url is not None and recs[0].image_url.startswith(
"https://50.img.avito.st/"
)
# ── avito.houses: карточка ЖК (developmentPage) ────────────────────────────────
def test_avito_novostroyka_development_page() -> None:
e = _house(
"avito_novostroyka_12879380.html",
"https://www.avito.ru/catalog/novostroyki/ekaterinburg/semeynyy_kvartal_beregovoy/12879380",
)
h = e.house
assert h.ext_id == 12879380
assert h.ext_id_hash == "MGDKHN70PQ"
assert h.title == "Семейный квартал «Береговой»"
assert h.short_address == "ул. Щербакова, жилые дома"
assert h.full_address is None # на странице пустая строка
assert (h.lat, h.lon) == (56.77005, 60.67244)
assert h.house_type == "monolith"
assert h.house_class == "comfort"
assert (h.passenger_elevators, h.cargo_elevators) == (42, 60)
assert (h.developer_name, h.developer_key) == ("Талан", "6749804656-3468412387")
assert h.year_built is None # дом строится: «Срок сдачи», а не год постройки
assert h.rating_score is None
assert e.reviews == [] and e.placement_history == []
# ── avito.serp ─────────────────────────────────────────────────────────────────
def test_avito_serp_cards_to_lots() -> None:
scraper = AvitoScraper(SimpleNamespace(avito_serp_ekb_only=True)) # type: ignore[arg-type]
lots = scraper._parse_html(
_read("avito_serp_sample.html"),
source_url_base="https://www.avito.ru/ekaterinburg/kvartiry/prodam?p=1",
)
got = [
(lot.source_id, lot.price_rub, lot.rooms, lot.area_m2, lot.floor, lot.total_floors)
for lot in lots
]
assert got == [
("8043936560", 5_500_000, 2, 54.0, 5, 9),
("8163084615", 4_200_000, 1, 38.0, 3, 16),
]
assert lots[0].source_url == (
"https://www.avito.ru/ekaterinburg/kvartiry/2-k._kvartira_54m_59et._8043936560"
)
# sortTimeStamp в миллисекундах, дата по Москве (#726).
assert [lot.listing_date for lot in lots] == [date(2023, 11, 15), date(2023, 11, 16)]
assert {lot.listing_segment for lot in lots} == {"vtorichka"}
assert scraper.parse_failures == 0
# ── cian.serp ──────────────────────────────────────────────────────────────────
@pytest.fixture(scope="module")
def cian_lots() -> dict[str, Any]:
scraper = CianScraper(SimpleNamespace(glitchtip_dsn=None)) # type: ignore[arg-type]
html = _read("cian_serp_ekb_2room_trimmed.html")
lots = scraper._parse_serp_html(html)
assert scraper._extract_total_offers(html) == 13_895
assert (scraper.state_extraction_attempts, scraper.state_extraction_failures) == (2, 0)
return {lot.source_id: lot for lot in lots}
def test_cian_serp_all_offers_parsed(cian_lots: dict[str, Any]) -> None:
assert list(cian_lots) == ["331661795", "333599038", "333279363", "321628098"]
def test_cian_serp_secondary_offer(cian_lots: dict[str, Any]) -> None:
lot = cian_lots["331661795"]
assert lot.source == "cian"
assert lot.source_url.startswith("https://ekb.cian.ru/sale/flat/331661795/")
assert lot.listing_segment == "vtorichka"
assert (lot.house_source, lot.house_ext_id) == ("cian", None)
assert lot.price_rub == 7_990_000
assert (lot.rooms, lot.area_m2, lot.living_area_m2, lot.kitchen_area_m2) == (
2,
47.2,
28.0,
7.5,
)
assert (lot.floor, lot.total_floors, lot.year_built) == (2, 17, 1980)
assert lot.house_type == "panel"
assert lot.address == "р-н Октябрьский, мкр. Парковый, улица Большакова, 13"
assert (lot.lat, lot.lon) == (56.820035, 60.634287)
assert lot.listing_date == date(2026, 9, 14)
assert (lot.mortgage_available, lot.sale_type) == (True, "alternative")
assert (lot.is_homeowner, lot.is_pro_seller) == (False, True)
assert lot.phones == [] # #2217: телефоны не сохраняем
assert lot.metro_stations[0] == {
"name": "Геологическая",
"time": 5,
"mode": "transport",
"line_color": "00701A",
"line_id": 26,
"is_default": True,
}
# descriptionMinhash приходит списком — склеен через запятую в исходном порядке.
assert lot.description_minhash.startswith("16147461,5781654,4548911,")
assert len(lot.photo_urls) == 3
def test_cian_serp_newbuilding_with_house(cian_lots: dict[str, Any]) -> None:
lot = cian_lots["333599038"]
assert lot.listing_segment == "novostroyki"
assert (lot.house_source, lot.house_ext_id) == ("cian_newbuilding", "5779142")
assert lot.address == "р-н Железнодорожный, мкр. Старая Сортировка, переулок Проходной, 1Б"
assert lot.year_built == 2028 # buildYear нет, берётся год сдачи из deadline
assert lot.house_type == "monolith"
assert (lot.price_rub, lot.area_m2, lot.floor, lot.total_floors) == (4_834_000, 36.04, 22, 32)
assert lot.sale_type == "fz214"
def test_cian_serp_newbuilding_decoration_and_balcony(cian_lots: dict[str, Any]) -> None:
lot = cian_lots["333279363"]
assert lot.repair_state == "good" # decoration=fine
assert lot.has_balcony is True # balconiesCount=1
assert lot.house_type is None # materialType на странице null
assert lot.year_built == 2026
def test_cian_serp_newbuilding_without_street_keeps_address_houseless(
cian_lots: dict[str, Any],
) -> None:
"""В geo.address нет ни улицы, ни дома; в названии ЖК номера нет, кадастра нет.
Номер дома не выдумывается (#1773)."""
lot = cian_lots["321628098"]
assert lot.address == "р-н Чкаловский, мкр. Вторчермет"
assert (lot.house_source, lot.house_ext_id) == ("cian_newbuilding", "5705732")
# ── yandex.serp ────────────────────────────────────────────────────────────────
def _gate(name: str) -> dict[str, Any]:
return json.loads(_read(name))
@pytest.fixture(scope="module")
def yandex_secondary() -> dict[str, Any]:
lots = _parse_gate_json(_gate("yandex_gate_ekb_vtorichka_trimmed.json"), 1, "NO")
return {lot.source_id: lot for lot in lots}
def test_yandex_gate_pager() -> None:
extracted = _extract_gate_data(_gate("yandex_gate_ekb_vtorichka_trimmed.json"))
assert extracted is not None
entities, pager = extracted
assert len(entities) == 3
assert (pager["totalItems"], pager["totalPages"]) == (4077, 25)
def test_yandex_agency_offer_with_partner_url(yandex_secondary: dict[str, Any]) -> None:
lot = yandex_secondary["5228381943217465492"]
# url в выдаче — сайт агентства; source_url строится из offerId (#2235).
assert lot.source_url == "https://realty.yandex.ru/offer/5228381943217465492/"
assert lot.yandex_offer_id == "5228381943217465492"
assert lot.listing_segment == "vtorichka"
assert (lot.price_rub, lot.price_per_m2) == (4_400_000, 129_412)
assert (lot.rooms, lot.area_m2, lot.living_area_m2, lot.kitchen_area_m2) == (1, 34.0, None, 7.0)
assert (lot.floor, lot.total_floors, lot.year_built) == (8, 9, 1978)
assert lot.house_type == "brick" # BRICK
assert lot.ceiling_height_m == 2.5
assert lot.address == "Россия, Свердловская область, Екатеринбург, Шефская улица, 85"
assert (lot.lat, lot.lon) == (56.903275, 60.6229)
assert (lot.house_source, lot.house_ext_id) == (None, None)
assert lot.listing_date == lot.publish_date == date(2026, 8, 19)
assert (lot.is_homeowner, lot.is_pro_seller, lot.agency_name) == (None, True, "САМОЛЕТ ПЛЮС")
assert (lot.predicted_price_rub, lot.predicted_price_min, lot.predicted_price_max) == (
4_217_000,
3_795_000,
4_638_000,
)
assert (lot.price_trend, lot.price_previous_rub) == ("DECREASED", 4_450_000)
assert lot.metro_stations[0] == {"name": "Проспект Космонавтов", "min": 7}
# В фикстуре 6 фото с «//»: берутся первые 5 со схемой.
assert len(lot.photo_urls) == 5
assert all(u.startswith("https://avatars.mds.yandex.net/") for u in lot.photo_urls)
def test_yandex_owner_offer_in_complex(yandex_secondary: dict[str, Any]) -> None:
lot = yandex_secondary["6282460993454547968"]
assert lot.source_url == "https://realty.yandex.ru/offer/6282460993454547968"
assert (lot.is_homeowner, lot.is_pro_seller, lot.agency_name) == (True, None, None)
assert (lot.house_source, lot.house_ext_id) == ("yandex_realty_nb", "1668651")
assert lot.house_type == "monolith" # MONOLIT
assert (lot.floor, lot.total_floors) == (27, 27)
assert (lot.living_area_m2, lot.kitchen_area_m2) == (11.0, 14.0)
def test_yandex_studio_without_rooms_total(yandex_secondary: dict[str, Any]) -> None:
lot = yandex_secondary["8530722463088927619"]
assert lot.rooms == 0 # roomsTotal отсутствует — студия
assert lot.area_m2 == 20.8
assert lot.kitchen_area_m2 is None
assert lot.house_type == "monolith_brick" # MONOLIT_BRICK
def test_yandex_newbuilding_without_valuation() -> None:
lots = _parse_gate_json(_gate("yandex_gate_ekb_novostroyki_trimmed.json"), 1, "YES")
assert len(lots) == 1
lot = lots[0]
assert lot.source_id == "6216493807134609867"
assert lot.listing_segment == "novostroyki"
assert lot.price_rub == 27_620_850
assert (lot.predicted_price_rub, lot.predicted_price_min, lot.predicted_price_max) == (
None,
None,
None,
)
assert lot.ceiling_height_m is None
assert lot.house_type is None
assert (lot.house_source, lot.house_ext_id) == ("yandex_realty_nb", "2006795")
assert (lot.is_homeowner, lot.is_pro_seller, lot.agency_name) == (None, None, None)
assert lot.address == (
"Россия, Свердловская область, Екатеринбург, жилой район Центральный, "
"ЖК Александровский Сад"
)