| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231 |
- <?php
- namespace App\Services\Parser\Portals;
- use Illuminate\Support\Facades\Http;
- class Encar
- {
- /**
- * Интервал между запросами в секундах.
- * Переопределяет глобальный request_interval из config/parser.php.
- */
- const REQUEST_INTERVAL = 2;
- /**
- * Папка для временного хранения HTML-файлов
- */
- const TMP_DIR = __DIR__ . '/../tmp';
- /**
- * User-Agent для HTTP-запросов
- */
- const USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36';
- // -------------------------------------------------------------------------
- // Public API
- // -------------------------------------------------------------------------
- /**
- * Точка входа: загружает страницы и сохраняет HTML во временную папку.
- */
- public function parse(): void
- {
- $contents = $this->getContent();
- $this->saveContent($contents);
- }
- /**
- * Читает все HTML-файлы из tmp и возвращает массив:
- * [
- * ['file' => '/absolute/path/to/file.html', 'html' => '<html>...'],
- * ...
- * ]
- */
- public function getResult(): array
- {
- $tmpDir = self::TMP_DIR;
- if (! is_dir($tmpDir)) {
- return [];
- }
- $files = glob($tmpDir . '/*.html') ?: [];
- $results = [];
- foreach ($files as $file) {
- $html = file_get_contents($file);
- if ($html === false) {
- error_log("[Encar] Не удалось прочитать файл: $file");
- continue;
- }
- $results[] = [
- 'file' => $file,
- 'html' => $html,
- ];
- }
- return $results;
- }
- /**
- * Удаляет все HTML-файлы из tmp.
- * Метод готов, но пока не вызывается из Parser.
- */
- public function clearData(): void
- {
- $tmpDir = self::TMP_DIR;
- if (! is_dir($tmpDir)) {
- return;
- }
- $files = glob($tmpDir . '/*.html') ?: [];
- foreach ($files as $file) {
- if (! unlink($file)) {
- error_log("[Encar] Не удалось удалить файл: $file");
- }
- }
- }
- // -------------------------------------------------------------------------
- // Private
- // -------------------------------------------------------------------------
- /**
- * Список ссылок для парсинга
- */
- private function getLinks(): array
- {
- return [
- 'https://fem.encar.com/cars/detail/41664398?pageid=dc_carsearch&listAdvType=pic&carid=41664398&view_type=checked&wtClick_korList=015&advClickPosition=kor_pic_p1_g1',
- 'https://fem.encar.com/cars/detail/41939059?pageid=dc_carsearch&listAdvType=pic&carid=41939059&view_type=checked&wtClick_korList=015&advClickPosition=kor_pic_p1_g2',
- ];
- }
- /**
- * Загружает HTML-контент по всем ссылкам.
- * Возвращает массив: [ ['url' => ..., 'html' => ...], ... ]
- */
- private function getContent(): array
- {
- $links = $this->getLinks();
- $results = [];
- foreach ($links as $index => $url) {
- $html = $this->fetchUrl($url);
- if ($html !== false) {
- $body = $this->extractBody($html);
- if ($body !== null) {
- $results[] = [
- 'url' => $url,
- 'html' => $body,
- ];
- } else {
- error_log("[Encar] Не удалось извлечь <body>: $url");
- }
- } else {
- error_log("[Encar] Не удалось загрузить: $url");
- }
- // Пауза между запросами (кроме последнего)
- if ($index < count($links) - 1) {
- sleep(self::REQUEST_INTERVAL);
- }
- }
- return $results;
- }
- /**
- * Извлекает содержимое тега <body> из HTML-строки.
- * Возвращает null если тег не найден.
- */
- private function extractBody(string $html): ?string
- {
- $dom = new \DOMDocument();
- // Подавляем ошибки парсинга (битый HTML, корейские символы и т.д.)
- libxml_use_internal_errors(true);
- // UTF-8 hint — иначе DOMDocument может неверно определить кодировку
- $dom->loadHTML('<?xml encoding="UTF-8">' . $html, LIBXML_NOWARNING | LIBXML_NOERROR);
- libxml_clear_errors();
- $body = $dom->getElementsByTagName('body')->item(0);
- if ($body === null) {
- return null;
- }
- // Сериализуем innerHTML тега <body> (без самих тегов <body></body>)
- $innerHTML = '';
- foreach ($body->childNodes as $child) {
- $innerHTML .= $dom->saveHTML($child);
- }
- return trim($innerHTML);
- }
- /**
- * Выполняет HTTP GET-запрос с кастомным User-Agent
- */
- private function fetchUrl(string $url): string | false
- {
- $response = Http::withHeaders([
- 'User-Agent' => self::USER_AGENT,
- 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
- 'Accept-Language' => 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7',
- 'Connection' => 'keep-alive',
- ])
- ->timeout(30)
- ->withOptions(['allow_redirects' => true])
- ->get($url);
- if ($response->failed()) {
- return false;
- }
- return $response->body();
- }
- /**
- * Сохраняет полученный HTML в файлы в папке tmp
- */
- private function saveContent(array $contents): void
- {
- if (! is_dir(self::TMP_DIR)) {
- mkdir(self::TMP_DIR, 0755, true);
- }
- foreach ($contents as $item) {
- $carId = $this->extractCarId($item['url']);
- $filename = self::TMP_DIR . '/' . $carId . '_' . time() . '.html';
- $written = file_put_contents($filename, $item['html']);
- if ($written === false) {
- error_log("[Encar] Не удалось сохранить файл: $filename");
- } else {
- echo "[Encar] Сохранено: $filename" . PHP_EOL;
- }
- }
- }
- /**
- * Извлекает carid из URL, либо возвращает md5-хэш URL
- */
- private function extractCarId(string $url): string
- {
- if (preg_match('/carid=(\d+)/', $url, $matches)) {
- return $matches[1];
- }
- return md5($url);
- }
- }
|