Простым языком об использовании PHP с cURL на одном примере сайта с JavaScript-защитой.
1. Парсер для получения контента по ссылкам
Задача парсера тривиальная - агрегатор новостей: сбор контента с новостных сайтов.
На входе: файл с URL-ссылками на статьи для сбора.
Веб-интерфейс для администратора: php-страница с кнопкой для запуска скрипта парсера.
Скрипт: использование cURL - с минимальным набором опций.
function cURL_get_content($url){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$urlContent = curl_exec($ch);
curl_close($ch);
return $urlContent;
}
Кейс:
1) Пользователь:
заходит на страницу запуска скрипта,
-
нажимает кнопку запуска.
2) Скрипт:
обходит (в цикле) массив ссылок на сайты,
собирает контент статей,
формирует веб-страницы по заданному шаблону.
Проблема: наткнувшись на интересную статью с сайта о недвижимости (Домофонд), попытался добавить ее в файл ссылок для сбора контента.
Контент со страницы не собрался: 403 Forbidden.
Ссылка: https://www.domofond.ru/statya/kolichestvo_dolgostroev_uvelichilos_v_vosemnadtsati_regionah_rf_za_2021_god/102082
Спойлер: если сразу интересно найденное решение, то можно переходить к заключению, пропустив бесплодные попытки поиска.
2. Попытка решения в лоб за счет "имитации" браузера доп. заголовками в запросе
В поисках сначала натолкнулся на статьи о том, что некоторые сайты ставят защиту от ботов (чем и является парсер для сбора контента). Например, при использовании cURL надо "оживлять" запрос заголовками, что я и делал, но безуспешно - проиллюстрирую одной из попыток.
function cURL_get_content($url){
$url_1 = "https://www.domofond.ru/";
$url_2 = $url;
$headers = array(
'GET ' . $url_2 . ' HTTP/1.1',
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"Accept-Encoding: gzip, deflate",
"Accept-Language: ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7",
"Host: domofond.ru",
"Referer: domofond.ru",
"Upgrade-Insecure-Requests: 1",
'User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36',
'Cookie: dfuid=5e0dd723-40e6-46cf-a2f1-1db784e320e1; _ga=GA1.2.140590976.1641893876; rrpvid=42725831313485; rcuid=6166ad1a101fb8000139a8a9; _ym_uid=1641893879540019262; _ym_d=1641893879; __gads=ID=00752b7382f51452:T=1641893879:S=ALNI_ManEsFfeviPUR29VUUnBx_DcgsAxQ; _gid=GA1.2.589118605.1642400326; _ym_visorc=w; _ym_isad=2; cto_bundle=ellcGV9VT0c4Q0Vrd2E3dEhtVmhJNk1Ic20xaHNnNXhuSiUyRiUyQiUyRnZYMlFEV0tnNExTWmhQUjJJVzdUOHdkNmlKdnh1aEZtUWMzQ0dxV25Nb3hrYktOeUpEMjZLZ0xLTFElMkZDekxiSkh6elEyOFM0UHVZZ2xHTklpQ2RJOTRkb1Q3QUNTODRlR0hnU0Z1MEFkeDBtNTc1MVozdjVndyUzRCUzRA'
);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
...
}
Пример запроса с заголовками:
GET /statya/kolichestvo_dolgostroev_uvelichilos_v_vosemnadtsati_regionah_rf_za_2021_god/102082 HTTP/1.1 Host: www.domofond.ru User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36 Accept: ... +куки +смотрел/добавлял заголовки, отправленные в браузере
2.1. Заголовок с Host
Хотя при добавлении хост-заголовка все же возникла одна из мыслей.
Если значение заголовка менялось с "Host: www.domofond.ru" на "Host: domofond.ru" (без www.), то появлялась ошибка: 421 Misdirected Request.
Поэтому в браузере я вручную открывал страницу с/без www - естественно, визуально для меня ничего не менялось.
Перенаправление срабатывало нормально, но потом я еще решил изменить и протокол, чтобы проверить, какой будет редирект.
Сменив в браузере ссылку с https:// на http:// (с/без www) получал нормальный редирект на главное зеркало (https://www.) с нормальным отображением статьи.
Заменив ссылку на https://146.158.48.9/statya/kolichestvo_dolgostroev_uvelichilos_v_vosemnadtsati_regionah_rf_za_2021_god/102082 - с IP-адресом хоста, получил, что подключение к сайту не защищено (несмотря на протокол).
При этом вместо статьи отобразилась та же ошибка: 421 Misdirected Request.
3. Изучение веб-страницы, "проблемной" для парсера
Продолжив поиски по теме защиты на сайтах от ботов, столкнулся с выполнением JavaScript-кода на страницах для этой цели.
Сразу скажу, что в браузере отключил выполнение JavaScript-сценариев, но страница продолжила отображаться нормально.
Хотя при изучении кода проблемной страницы смутил следующий скрипт, включающий в виде JSON-блока контент статьи.
<script>window.__INITIAL_DATA__ = {...,"body":"<h2>В восемнадцати регионах России с начала 2021 года увеличилось число проблемных объектов, жилье в которых ждут обманутые дольщики. Об этом сообщает ТАСС со ссылкой на Фонд защиты прав граждан — участников долевого строительства.</h2><figure class=\"image\"><img src=\"https://st04.domofond.ru/image/1/1.8e4sara2-QaYKU3NkRHj4GaUXwecy78XUsJf.n2qDfSv5Xan97G3s9xyoTicCp2winXLLHxcVGotZqQg\" srcset=\"https://st29.domofond.ru/image/1/1.UJZ1xba2WH7Bxv-hwvxU-1U7_n_FZF58H2f-.mAwXzCu3zagIajLE0KwxA4pTSi_Rcg215CdmhiCxQdo 80w, https://st04.domofond.ru/image/1/1.8e4sara2-QaYKU3NkRHj4GaUXwecy78XUsJf.n2qDfSv5Xan97G3s9xyoTicCp2winXLLHxcVGotZqQg 1200w\" sizes=\"100vw\" width=\"1200\"><figcaption>markus thoenen/Fotolia</figcaption></figure><p> </p><p>В частности, новые долгострои находятся в Ленинградской области — 33 объекта, Ивановской области — 9 объектов, Красноярском крае — 9 объектов, Ульяновской области — 8 объектов. По данным фонда, общее число новых проблемных домов составило 90 объектов.</p><p>Однако за тот же период в 39 субъектах РФ количество долгостроев сократилось на 368 домов. В число данных регионов вошли Московская, Ростовская, Липецкая области, Башкирия, Краснодарский край и другие.</p><p>Добавим, что в настоящее время в стране зафиксировано 2,6 тыс. проблемных объектов, которые находятся в 72 регионах России.</p><p><strong>Не пропустите:</strong></p><p><a href=\"https://www.domofond.ru/statya/fond_dolschikov_zaymetsya_tolko_vyplatoy_denezhnyh_kompensatsiy/101990\" target=\"_blank\"><strong>Фонд дольщиков займется только выплатой денежных компенсаций</strong></a></p><p><a href=\"https://www.domofond.ru/statya/pri_perenose_sdachi_doma_dengi_s_eskrou_schetov_ne_budut_vozvraschatsya_dolschikam/102037\" target=\"_blank\"><strong>При переносе сдачи дома деньги с эскроу-счетов не будут возвращаться дольщикам</strong></a></p><p><a href=\"https://www.domofond.ru/statya/mozhno_li_vzyskat_neustoyku_s_zastroyschika_za_zaderzhku_sdachi_kvartiry/100823\" target=\"_blank\"><strong>Можно ли взыскать неустойку с застройщика за задержку сдачи квартиры?</strong></a></p><p><a href=\"https://www.domofond.ru/statya/kak_vzyskat_kompensatsiyu_s_zastroyschika_zaderzhavshego_sdachu_doma/6766\" target=\"_blank\"><strong>Как взыскать компенсацию с застройщика, задержавшего сдачу дома?</strong></a></p>","author":{"id":144,"name":"Анна Филонова","profile":"<p><strong>Редактор Domofond.ru</strong></p>"},...}
</script>
4. ПО для сбора контента с веб-сайтов
В ходе исследований попадались статьи на "безголовые" браузеры, среды наподобие селениума и т.п.
Предлагалась имитация действий пользователя, установка тайм-аутов при обращении к сайту для снижения нагрузки и т.д.
Но для решения задач по парсингу одной-двух ссылок, а не многопоточного скачивания всего сайта это не подходило.
Глубоко в этом направлении не разбирался и не копал.
5. Возврат к мысли про ссылку с IP-адресом сервера
Пришлось вернуться к начальному вопросу: как получить контент веб-страницы, если по ссылке с именем хоста не получилось.
Решил, в качестве эксперимента, скормить парсеру ссылку https://146.158.48.9/statya/kolichestvo_dolgostroev_uvelichilos_v_vosemnadtsati_regionah_rf_za_2021_god/102082 - с IP-адресом хоста.
В ответ получил содержимое страницы.
6. Выводы и решение
В чем проблема - до конца непонятно. Закроет ли собственник сайта (Домофонд) эту дыру и сможет ли это вообще сделать - неясно.
Но в статье приведено достаточно простое решение и оно работает, а на просторах интернета с такой легкой подсказкой я не сталкивался.
В решении дополнил скрипт методом gethostbyname, чтобы получать IPv4-адрес, соответствующий переданному имени хоста.
Парсер для получения контента по ссылке на входе имеет тот же набор URL-ссылок, но теперь обращается по IP-адресам серверов, что не мешает его работе в сборе данных.
Комментарии (32)
random1st
23.01.2022 16:09+3Скопировал CURL сгенерированный браузером, импортировал в Postman, поотключал все хидеры, защиту никакую не обнаружил. Все.
PS. Если дать себе труда немного подумать, то запрет ботов на новостном сайте равносильно запрету индексации и выдаче его в поисковиках. Более того, поисковики не приветствуют когда содержимое страницы отдаваемое пользователям не совпадает с тем, что забирают они.
dchizhikov Автор
23.01.2022 16:45-4Не хотелось лишнего ПО.
В роботс.тхт действительно стоит много запретов для ботов, но не на новостные статьи.
По имени и по айпи хоста - отдается одинаковый контент.
random1st
23.01.2022 16:55+1robots.txt пассивный механизм, не влияющий на отдачу контента. Что касается отдачи по IP и по имени - вообще касательства к делу не имеет равно как и лишнее ПО. Я пытаюсь сказать что проблема скорее всего на вашей стороне.
dchizhikov Автор
23.01.2022 17:00-2Например, какие проблемы? С другими сайтами проблем по сбору нет.
Хотел дополнить в статье, но напишу в комменте, что с авито - примерно та же ситуация возникла. Решено этим же подходом - успешно.
random1st
23.01.2022 17:44+3без понятия. Для того чтобы ответить, нужно для начала проблему воспроизвести. Мне не удалось. Сайт отдает контент всегда. Даже когда я пустил через Apache Benchmark 100 запросов с конкарренси в 10 мне не удалось активировать никаких механизмов защиты. Так что вопрос с кривым кодом на вашей стороне по-прежнему наиболее вероятен.
dchizhikov Автор
23.01.2022 18:00Можете скрин ответа привести - что именно отдает, какой контент?
random1st
23.01.2022 18:04контент страницы естественно. Скрины прикладывать не вижу смысла, еще раз повторюсь - мне отдает все то же самое и в браузере, и curl и postman. Разбирайтесь с проблемой на своей стороне. Бредово рассуждать о защите JS если у вас статически рендерится страница.
dchizhikov Автор
23.01.2022 18:42-2какая-то защита там есть по-любому - видимо, от парсинга предложений о жилье. проверю тогда сам в постмане, спасибо.
dchizhikov Автор
24.01.2022 14:41постман гетом по ссылке отдает контент - вообще без каких-либо заголовков.
koreychenko
23.01.2022 16:30+2Дичь лютейшую прочитал сейчас я.
Коллега, для ваших целей замечательно зайдёт вот такая штука:
https://splash.readthedocs.io/en/stable/
Он очень просто ставится. У него даже docker image готовый есть.
А дальше вы делаете к нему http запрос хоть тем же курлом с указанием урла сайта, который нужно спарсить - и он возвращает вам html.
Более того, можно писать свои скрипты парсинга на Lua, если нужно парсить по какому-то сценарию. Например, если сайты с AJAX подгрузкой контента, постраничником и т.п.
Из минусов:
- разработчики на него немного подзабили, походу
- падает на сложных Angular приложениях
zzzzzzzzzzzz
23.01.2022 16:44+3В решении дополнил скрипт методом gethostbyname, чтобы получать IPv4-адрес, соответствующий переданному имени хоста.
Плохая идея, т.к., начиная с HTTP/1.1, на одном IP может висеть несколько сайтов. Соответственно, скачиваться будет что-нибудь не то.
dchizhikov Автор
23.01.2022 17:06В этом случае (домофонд, авито) было как раз наоборот (изначально использовал gethostbynamel - для списка айпи). Думаю, что для крупных сайтов так и будет.
Но спасибо за коммент.
satoo
24.01.2022 01:30+2????♂️ как одним комментом показать непонимание работы http (а также того, что помогает ему: балансировщиков, проксей, cdn и пр)
dchizhikov Автор
24.01.2022 09:36Стояла задача работоспособности скрипта для 1 сайта с защитой - она решена.
О чем вкратце рассказано в статье.
NickyX3
24.01.2022 10:39Я тут намедни столкнулся с дригой забавной ситуацией.
cURL из под PHP на двух "одинаковых" версиях PHP на разных машинах при запросе одного и того же урла выдавал на одной машине заголовки как есть, а на другой в нижнем регистре. Так и не понял почему, тупо добавил в regexp case independed.
random1st
25.01.2022 00:04а кто сказал что проблема в версии PHP а не в web-сервере?
NickyX3
25.01.2022 10:54а зачем вебсерверу отдавать заголовки то в нормальном виде, то в нижнем регистре?
Тем более страница одна и таже, и по факту оно проявлялось именно на разных машинах, которые делают запрос. Я подозреваю, что "проблема" в версии cURL extension/OpenSSL. Ибо хоть версия php7.4 и там и там одна, но одна тачка Debian 10, другая Debian 11
apirk
25.01.2022 09:10+1О чём статья вообще? За 5 минут накидал скрипт, никакой защиты на веб-сервере нет.
$wc = New-Object system.Net.WebClient; ($wc.downloadString("https://www.domofond.ru/statya/kolichestvo_dolgostroev_uvelichilos_v_vosemnadtsati_regionah_rf_za_2021_god/102082") -split '\r?\n')[60].Replace('</script>','').Replace(' <script>window.__INITIAL_DATA__ = ', '') | ConvertFrom-Json
На выходе готовый JSON, дёргайте оттуда любые данные. В чём проблема-то? )
TheRikipm
А зачем скачивать весь сайт если можно скачать только контент этих одной-двух ссылок?
В вашем случае самым "правильным" решением будет как раз селениум с безголовым браузером. По крайней мере домофонду защититься от него будет куда сложнее чем от вашего итогового решения.
dchizhikov Автор
Не хотелось лишнего ПО.
amarao
Так что вы написали ещё ПО. Л - логика.
dchizhikov Автор
Скрипт уже был и прекрасно работает.
Потребовалось лишь минимально модернизировать.
amarao
Но вы же не хотели лишнего ПО, но вместо этого написали ещё ПО!
dchizhikov Автор
Скрипт не лишнее ПО, а работающий под задачу функционал.
amarao
А всё остальное ПО, которое решало поставленную задачу классом лучше, лишнее?
Поздравляю, у вас NIH-синдром. Гуглябельно.
dchizhikov Автор
Микроскопом тоже можно гвозди заколачивать)))
amarao
Да. Но вместо этого это изобретаете микроскоп из трубочки для туалетной бумаги и двух полиэтиленовых пакетиков с водой.
А потом всё равно им забиваете гвозди.
dchizhikov Автор
я-то как раз с молотком просто, юморист))
amarao
Нет, вы не с инструментом пришли, а со средством разработки, и написали ещё одно ПО. Лишнее.
dchizhikov Автор
Нет - см. выше))