2026-06-30 12:38:18 +03:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
use App\Services\Autopodbor\Agent\Extract\DirectoryParser;
|
|
|
|
|
|
|
|
|
|
|
|
it('берёт ссылки на карточки филиалов из списка 2ГИС', function () {
|
|
|
|
|
|
$html = '<a href="/krasnoyarsk/firm/111">Ф1</a><a href="/krasnoyarsk/firm/222">Ф2</a>'
|
|
|
|
|
|
.'<a href="/krasnoyarsk/firm/111">дубль</a><a href="/about">не филиал</a>';
|
|
|
|
|
|
$links = (new DirectoryParser)->parseBranchList($html);
|
|
|
|
|
|
expect($links)->toBe(['/krasnoyarsk/firm/111', '/krasnoyarsk/firm/222']);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-06-30 13:27:27 +03:00
|
|
|
|
it('берёт ссылки на филиалы, даже когда они в данных страницы, а не в href', function () {
|
|
|
|
|
|
// 2ГИС иногда рендерит карточки без чистого href — ссылка лежит в JSON-состоянии страницы
|
|
|
|
|
|
$html = '{"items":[{"link":"/krasnoyarsk/firm/111"},{"link":"/krasnoyarsk/firm/222"}]}'
|
|
|
|
|
|
.'<a href="/krasnoyarsk/firm/111">дубль</a>';
|
|
|
|
|
|
$links = (new DirectoryParser)->parseBranchList($html);
|
|
|
|
|
|
expect($links)->toBe(['/krasnoyarsk/firm/111', '/krasnoyarsk/firm/222']);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-06-30 12:38:18 +03:00
|
|
|
|
it('из карточки 2ГИС берёт телефон из tel: и адрес из заголовка', function () {
|
|
|
|
|
|
$html = '<html><head><title>КрасЛомбард, улица Ладо Кецховели, 30, Красноярск — 2ГИС</title></head>'
|
|
|
|
|
|
.'<body><h1>КрасЛомбард</h1><a href="tel:+73912920000">+7 (391) 2...</a></body></html>';
|
|
|
|
|
|
$cards = (new DirectoryParser)->parseFirmCard($html, 'https://2gis.ru/krasnoyarsk/firm/111', '2ГИС');
|
|
|
|
|
|
|
|
|
|
|
|
expect($cards)->toHaveCount(1);
|
|
|
|
|
|
expect($cards[0]->number)->toBe('+73912920000');
|
|
|
|
|
|
expect($cards[0]->office)->toBe('улица Ладо Кецховели, 30');
|
|
|
|
|
|
expect($cards[0]->url)->toBe('https://2gis.ru/krasnoyarsk/firm/111');
|
|
|
|
|
|
expect($cards[0]->source)->toBe('2ГИС');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('схлопывает неразрывные/двойные пробелы в адресе из заголовка', function () {
|
|
|
|
|
|
$html = "<title>КрасЛомбард,\u{00A0}улица Весны,\u{00A0}7а, Красноярск — 2ГИС</title>"
|
|
|
|
|
|
.'<a href="tel:+73912550000">x</a>';
|
|
|
|
|
|
$cards = (new DirectoryParser)->parseFirmCard($html, 'u', '2ГИС');
|
|
|
|
|
|
expect($cards[0]->office)->toBe('улица Весны, 7а');
|
|
|
|
|
|
});
|
2026-07-03 17:34:03 +03:00
|
|
|
|
|
|
|
|
|
|
// ── Яндекс.Карты ────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
it('parseYandexCard: извлекает телефон из div orgpage-phones-view и сайт из business-urls-link', function () {
|
|
|
|
|
|
$html = <<<'HTML'
|
|
|
|
|
|
<title>Автодом24, микрофинансовая организация, Красноярск, Караульная ул., 88 — Яндекс Карты</title>
|
|
|
|
|
|
<div class="orgpage-phones-view__phone-number" dir="ltr">+7 (913) 539-65-46</div>
|
|
|
|
|
|
<div class="business-urls-view__url">
|
|
|
|
|
|
<a itemprop="url" class="business-urls-view__link" href="https://automoney-krsk.ru/" target="_blank" rel="nofollow">
|
|
|
|
|
|
<span class="business-urls-view__text">automoney-krsk.ru</span>
|
|
|
|
|
|
</a>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
HTML;
|
|
|
|
|
|
|
|
|
|
|
|
$cards = (new DirectoryParser)->parseYandexCard($html, 'https://yandex.ru/maps/org/avtodom24/8905494012/');
|
|
|
|
|
|
|
|
|
|
|
|
expect($cards)->not->toBeEmpty();
|
|
|
|
|
|
// Номер хранится в сыром виде «+7 (913) 539-65-46», проверяем по цифрам
|
|
|
|
|
|
$digitsAll = implode('', array_map(fn ($c) => preg_replace('/\D/', '', $c->number), $cards));
|
|
|
|
|
|
expect($digitsAll)->toContain('9135396546');
|
|
|
|
|
|
expect($cards[0]->siteUrl)->toContain('automoney-krsk.ru');
|
|
|
|
|
|
expect($cards[0]->source)->toBe('Яндекс.Карты');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('parseYandexCard: дедуп телефонов — wa.me не дублирует основной номер', function () {
|
|
|
|
|
|
$html = <<<'HTML'
|
|
|
|
|
|
<title>Автодом24, микрофинансовая организация, Красноярск, Красная ул., 1 — Яндекс Карты</title>
|
|
|
|
|
|
<div class="orgpage-phones-view__phone-number" dir="ltr">+7 (913) 539-65-46</div>
|
|
|
|
|
|
<a href="https://wa.me/79135396546" role="button" itemprop="sameAs"></a>
|
|
|
|
|
|
<div class="business-urls-view__url">
|
|
|
|
|
|
<a class="business-urls-view__link" href="https://automoney-krsk.ru/"></a>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
HTML;
|
|
|
|
|
|
|
|
|
|
|
|
$cards = (new DirectoryParser)->parseYandexCard($html, 'https://yandex.ru/maps/org/avtodom24/1/');
|
|
|
|
|
|
|
|
|
|
|
|
// wa.me не должен дублировать тот же номер
|
|
|
|
|
|
expect($cards)->toHaveCount(1);
|
|
|
|
|
|
expect($cards[0]->siteUrl)->toContain('automoney-krsk.ru');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('parseYandexCard: если телефонов нет но сайт есть — одна карточка с пустым number', function () {
|
|
|
|
|
|
$html = <<<'HTML'
|
|
|
|
|
|
<title>Тест, Красноярск — Яндекс Карты</title>
|
|
|
|
|
|
<div class="business-urls-view__url">
|
|
|
|
|
|
<a class="business-urls-view__link" href="https://example.com/"></a>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
HTML;
|
|
|
|
|
|
|
|
|
|
|
|
$cards = (new DirectoryParser)->parseYandexCard($html, 'https://yandex.ru/maps/org/test/1/');
|
|
|
|
|
|
|
|
|
|
|
|
expect($cards)->toHaveCount(1);
|
|
|
|
|
|
expect($cards[0]->number)->toBe('');
|
|
|
|
|
|
expect($cards[0]->siteUrl)->toContain('example.com');
|
|
|
|
|
|
expect($cards[0]->source)->toBe('Яндекс.Карты');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('parseYandexCard: нет ни телефона, ни сайта — возвращает пустой массив', function () {
|
|
|
|
|
|
$html = '<title>Тест — Яндекс Карты</title><div>нет контактов</div>';
|
|
|
|
|
|
|
|
|
|
|
|
$cards = (new DirectoryParser)->parseYandexCard($html, 'https://yandex.ru/maps/org/test/2/');
|
|
|
|
|
|
|
|
|
|
|
|
expect($cards)->toBe([]);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('parseYandexCard: адрес офиса берётся из заголовка страницы Яндекса', function () {
|
|
|
|
|
|
$html = <<<'HTML'
|
|
|
|
|
|
<title>Автодом24, микрофинансовая организация, Красноярск, Караульная ул., 88 — Яндекс Карты</title>
|
|
|
|
|
|
<div class="orgpage-phones-view__phone-number" dir="ltr">+7 (913) 539-65-46</div>
|
|
|
|
|
|
HTML;
|
|
|
|
|
|
|
|
|
|
|
|
$cards = (new DirectoryParser)->parseYandexCard($html, 'https://yandex.ru/maps/org/avtodom24/1/');
|
|
|
|
|
|
|
|
|
|
|
|
expect($cards)->not->toBeEmpty();
|
|
|
|
|
|
// officeFromTitle должен отрезать «Автодом24» и «Яндекс Карты», оставить «микрофинансовая организация, Красноярск, Караульная ул., 88»
|
|
|
|
|
|
// Точнее: первый кусок = имя, последний = город; середина = адрес
|
|
|
|
|
|
expect($cards[0]->office)->not->toBeNull();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ── 2ГИС «вскрыть всё»: сайт + все телефоны + описание из карточки ───────────
|
|
|
|
|
|
|
|
|
|
|
|
it('parse2gisCard: сайт берёт из поля url объекта-контакта website, НЕ из value-редиректа', function () {
|
|
|
|
|
|
// Контакты 2ГИС — JSON-массив объектов. У сайта настоящий адрес в "url",
|
|
|
|
|
|
// а в "value" — редирект link.2gis.ru (его брать нельзя).
|
|
|
|
|
|
$html = '<title>AutoMoney, Красноярск, Ленина, 1 — 2ГИС</title>'
|
|
|
|
|
|
.'{"url":"http://automoney-krsk.ru","text":"automoney-krsk.ru","code":"70000001093406076","type":"website","value":"http://link.2gis.ru/redirect/xxx"}'
|
|
|
|
|
|
.'{"contact_text":"+7‒913‒539‒65‒46","type":"phone","value":"+79135396546"}';
|
|
|
|
|
|
|
|
|
|
|
|
$cards = (new DirectoryParser)->parse2gisCard($html, 'https://2gis.ru/krasnoyarsk/firm/111');
|
|
|
|
|
|
|
|
|
|
|
|
expect($cards)->not->toBeEmpty();
|
|
|
|
|
|
expect($cards[0]->siteUrl)->toContain('automoney-krsk.ru');
|
|
|
|
|
|
expect($cards[0]->siteUrl)->not->toContain('link.2gis.ru');
|
|
|
|
|
|
expect($cards[0]->source)->toBe('2ГИС');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('parse2gisCard: берёт ВСЕ телефоны из value объектов phone, без дублей', function () {
|
|
|
|
|
|
$html = '<title>Тест, Красноярск — 2ГИС</title>'
|
|
|
|
|
|
.'{"contact_text":"+7 391 279-33-79","type":"phone","value":"+73912793379"}'
|
|
|
|
|
|
.'{"contact_text":"+7 391 296-01-11","type":"phone","value":"+73912960111"}'
|
|
|
|
|
|
.'{"contact_text":"+7 391 240-00-68","type":"phone","value":"+73912400068"}'
|
|
|
|
|
|
.'{"type":"phone","value":"+73912793379"}'; // дубль первого
|
|
|
|
|
|
|
|
|
|
|
|
$cards = (new DirectoryParser)->parse2gisCard($html, 'https://2gis.ru/krasnoyarsk/firm/222');
|
|
|
|
|
|
|
|
|
|
|
|
$digits = array_map(fn ($c) => preg_replace('/\D/', '', $c->number), $cards);
|
|
|
|
|
|
expect($digits)->toContain('73912793379');
|
|
|
|
|
|
expect($digits)->toContain('73912960111');
|
|
|
|
|
|
expect($digits)->toContain('73912400068');
|
|
|
|
|
|
expect(array_unique($digits))->toHaveCount(3);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('parse2gisCard: достаёт описание «чем занимается» из og:description', function () {
|
|
|
|
|
|
$html = '<title>AutoMoney, Красноярск — 2ГИС</title>'
|
|
|
|
|
|
.'<meta property="og:description" content="Займы под залог ПТС/авто от 2,5% в месяц за 15 минут">'
|
|
|
|
|
|
.'{"contact_text":"+7 913 539-65-46","type":"phone","value":"+79135396546"}';
|
|
|
|
|
|
|
|
|
|
|
|
$cards = (new DirectoryParser)->parse2gisCard($html, 'https://2gis.ru/krasnoyarsk/firm/333');
|
|
|
|
|
|
|
|
|
|
|
|
expect($cards)->not->toBeEmpty();
|
|
|
|
|
|
expect($cards[0]->description)->toContain('Займы под залог');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('parse2gisCard: НЕ берёт служебную заглушку 2ГИС в описание (оценка/фото/отзывы)', function () {
|
|
|
|
|
|
$html = '<title>AutoMoney, Красноярск — 2ГИС</title>'
|
|
|
|
|
|
.'<meta property="og:description" content="Оценка 5, 11 фото, 93 отзыва. Вся информация об этом месте есть в 2ГИС">'
|
|
|
|
|
|
.'{"contact_text":"+7 913 539-65-46","type":"phone","value":"+79135396546"}';
|
|
|
|
|
|
|
|
|
|
|
|
$cards = (new DirectoryParser)->parse2gisCard($html, 'https://2gis.ru/krasnoyarsk/firm/xx');
|
|
|
|
|
|
|
|
|
|
|
|
expect($cards)->not->toBeEmpty();
|
|
|
|
|
|
expect($cards[0]->description)->toBeNull(); // заглушку не пишем
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('parse2gisCard: рубрика из заголовка идёт в описание, когда своего описания нет', function () {
|
|
|
|
|
|
// og-описания нет → «чем занимается» берём из рубрики (2-й кусок заголовка): «Имя, РУБРИКА, адрес…, Город».
|
|
|
|
|
|
$html = '<title>Ломбарджини, ломбард, Взлётная улица, 6а, Красноярск — 2ГИС</title>'
|
|
|
|
|
|
.'{"url":"http://lombardzhini.ru","text":"x","code":"1","type":"website"}';
|
|
|
|
|
|
|
|
|
|
|
|
$cards = (new DirectoryParser)->parse2gisCard($html, 'https://2gis.ru/krasnoyarsk/firm/1');
|
|
|
|
|
|
|
|
|
|
|
|
expect($cards[0]->description)->toBe('ломбард');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('parse2gisCard: 2-й кусок-адрес НЕ считается рубрикой (описание пустое)', function () {
|
|
|
|
|
|
// «КрасЛомбард, улица Ладо Кецховели, 30, Красноярск» — 2-й кусок это АДРЕС, не рубрика → описания нет.
|
|
|
|
|
|
$html = '<title>КрасЛомбард, улица Ладо Кецховели, 30, Красноярск — 2ГИС</title>'
|
|
|
|
|
|
.'{"url":"http://k.ru","text":"x","code":"1","type":"website"}';
|
|
|
|
|
|
|
|
|
|
|
|
$cards = (new DirectoryParser)->parse2gisCard($html, 'https://2gis.ru/krasnoyarsk/firm/2');
|
|
|
|
|
|
|
|
|
|
|
|
expect($cards[0]->description)->toBeNull();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('parseYandexCard: рубрика из заголовка идёт в описание', function () {
|
|
|
|
|
|
$html = '<title>Твои Займы, микрофинансовая организация, Ястынская ул., 19А, Красноярск — Яндекс Карты</title>'
|
|
|
|
|
|
.'<a class="business-urls-view__link" href="https://tvoizaymy.ru/"></a>';
|
|
|
|
|
|
|
|
|
|
|
|
$cards = (new DirectoryParser)->parseYandexCard($html, 'https://yandex.ru/maps/org/tvoi_zaymy/1/');
|
|
|
|
|
|
|
|
|
|
|
|
expect($cards[0]->description)->toBe('микрофинансовая организация');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('parse2gisCard: мусор-домены из <script src> не считаются сайтом', function () {
|
|
|
|
|
|
// Счётчики/трекеры (top100, mc.yandex) сидят в <script src>, это НЕ контакт-сайт.
|
|
|
|
|
|
$html = '<title>Тест, Красноярск — 2ГИС</title>'
|
|
|
|
|
|
.'<script src="https://st.top100.ru/top100/top100.js"></script>'
|
|
|
|
|
|
.'<script src="https://mc.yandex.ru/metrika/tag.js"></script>'
|
|
|
|
|
|
.'{"url":"http://realsite.рф","text":"realsite.рф","code":"1","type":"website","value":"http://link.2gis.ru/y"}'
|
|
|
|
|
|
.'{"contact_text":"+7 391 200-00-00","type":"phone","value":"+73912000000"}';
|
|
|
|
|
|
|
|
|
|
|
|
$cards = (new DirectoryParser)->parse2gisCard($html, 'https://2gis.ru/krasnoyarsk/firm/444');
|
|
|
|
|
|
|
|
|
|
|
|
expect($cards[0]->siteUrl)->toContain('realsite');
|
|
|
|
|
|
expect($cards[0]->siteUrl)->not->toContain('top100');
|
|
|
|
|
|
expect($cards[0]->siteUrl)->not->toContain('mc.yandex');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('parse2gisCard: телефонов нет, но сайт есть — одна карточка с пустым number', function () {
|
|
|
|
|
|
$html = '<title>Тест, Красноярск — 2ГИС</title>'
|
|
|
|
|
|
.'{"url":"http://onlysite.ru","text":"onlysite.ru","code":"1","type":"website","value":"http://link.2gis.ru/z"}';
|
|
|
|
|
|
|
|
|
|
|
|
$cards = (new DirectoryParser)->parse2gisCard($html, 'https://2gis.ru/krasnoyarsk/firm/555');
|
|
|
|
|
|
|
|
|
|
|
|
expect($cards)->toHaveCount(1);
|
|
|
|
|
|
expect($cards[0]->number)->toBe('');
|
|
|
|
|
|
expect($cards[0]->siteUrl)->toContain('onlysite.ru');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('parse2gisCard: нет ни телефона, ни сайта — пустой массив', function () {
|
|
|
|
|
|
$html = '<title>Тест — 2ГИС</title><div>нет контактов</div>';
|
|
|
|
|
|
|
|
|
|
|
|
$cards = (new DirectoryParser)->parse2gisCard($html, 'https://2gis.ru/krasnoyarsk/firm/666');
|
|
|
|
|
|
|
|
|
|
|
|
expect($cards)->toBe([]);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('parseYandexCard: достаёт ВСЕ три телефона из скрытого JSON, без дублей', function () {
|
|
|
|
|
|
// Яндекс показывает только первый телефон в видимом блоке,
|
|
|
|
|
|
// остальные прячет за раскрывашкой — но все три лежат в JSON на той же странице.
|
|
|
|
|
|
$html = <<<'HTML'
|
|
|
|
|
|
<title>Автокредит24, Красноярск, Взлётная ул., 5 — Яндекс Карты</title>
|
|
|
|
|
|
<script>
|
|
|
|
|
|
window.__REDUX_STATE__={"phones":[{"number":"+7 (391) 279-33-79","type":"phone","value":"+73912793379"},{"number":"+7 (391) 296-01-11","type":"phone","value":"+73912960111"},{"number":"+7 (391) 240-00-68","type":"phone","value":"+73912400068"}]};
|
|
|
|
|
|
</script>
|
|
|
|
|
|
<div class="orgpage-phones-view__phone-number" dir="ltr">+7 (391) 279-33-79</div>
|
|
|
|
|
|
<div class="business-urls-view__url">
|
|
|
|
|
|
<a class="business-urls-view__link" href="https://autocredit24.ru/"></a>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
HTML;
|
|
|
|
|
|
|
|
|
|
|
|
$cards = (new DirectoryParser)->parseYandexCard($html, 'https://yandex.ru/maps/org/autocredit24/1/');
|
|
|
|
|
|
|
|
|
|
|
|
// Должны получить ровно 3 карточки (по одной на телефон)
|
|
|
|
|
|
expect($cards)->toHaveCount(3);
|
|
|
|
|
|
|
|
|
|
|
|
// Проверяем по цифрам (без +, скобок и т.п.)
|
|
|
|
|
|
$allDigits = array_map(fn ($c) => preg_replace('/\D/', '', $c->number), $cards);
|
|
|
|
|
|
expect($allDigits)->toContain('73912793379');
|
|
|
|
|
|
expect($allDigits)->toContain('73912960111');
|
|
|
|
|
|
expect($allDigits)->toContain('73912400068');
|
|
|
|
|
|
|
|
|
|
|
|
// Нет дублей
|
|
|
|
|
|
expect(array_unique($allDigits))->toHaveCount(3);
|
|
|
|
|
|
|
|
|
|
|
|
// Сайт присутствует у каждой карточки
|
|
|
|
|
|
foreach ($cards as $card) {
|
|
|
|
|
|
expect($card->siteUrl)->toContain('autocredit24.ru');
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('parse2gisCard: НЕ берёт рекламный телефон-кнопку (caption/platforms), только настоящий контакт', function () {
|
|
|
|
|
|
// Живой случай: на карточке «Ломбард Бодрый» 2ГИС показал рекламный блок «Связаться с
|
|
|
|
|
|
// менеджером» — это кнопка-действие ЧУЖОЙ фирмы (Ломбард Айклауд). У неё caption/platforms и
|
|
|
|
|
|
// НЕТ code/print_text. Её номер брать нельзя: иначе чужой телефон попадает в контакты Бодрого
|
|
|
|
|
|
// и на «Предложениях» даёт ложное «объединить?».
|
|
|
|
|
|
$html = '<title>Ломбард Бодрый, ломбард, Красноярск — 2ГИС</title>'
|
|
|
|
|
|
// настоящий контакт фирмы: с code (id) и print_text
|
|
|
|
|
|
.'{"text":"+7 (391) 296-78-88","code":"70000001085640830","print_text":"+7 (391) 296-78-88","type":"phone","value":"+73912967888"}'
|
|
|
|
|
|
// рекламная кнопка чужой фирмы: caption + platforms, без code/print_text
|
|
|
|
|
|
.'{"caption":"Связаться с менеджером","name":"Связаться с менеджером","platforms":["ios","android","online"],"type":"phone","value":"+79237575575"}';
|
|
|
|
|
|
|
|
|
|
|
|
$cards = (new DirectoryParser)->parse2gisCard($html, 'https://2gis.ru/krasnoyarsk/firm/70000001085640828');
|
|
|
|
|
|
|
|
|
|
|
|
$digits = array_map(fn ($c) => preg_replace('/\D/', '', $c->number), $cards);
|
|
|
|
|
|
expect($digits)->toContain('73912967888'); // настоящий телефон Бодрого — берём
|
|
|
|
|
|
expect($digits)->not->toContain('79237575575'); // рекламный номер Айклауда — НЕ берём
|
|
|
|
|
|
});
|