Написать парсер на php самому пошаговая инструкция. Пишем парсер контента на PHP. Получение href ссылок


Чтобы написать хороший и работоспособный скрипт для парсинга контента нужно потратить немало времени. А подходить к сайту-донору, в большинстве случаев, стоит индивидуально, так как есть масса нюансов, которые могут усложнить решение нашей задачи. Сегодня мы рассмотрим и реализуем скрипт парсера при помощи CURL, а для примера получим категории и товары одного из популярных магазинов.

Если вы попали на эту статью из поиска, то перед вами, наверняка, стоит конкретная задача и вы еще не задумывались над тем, для чего ещё вам может пригодится парсер. Поэтому, перед тем как вдаваться в теорию и непосредственно в код, предлагаю прочесть предыдущею статью – , где был рассмотрен один из простых вариантов, да и я буду периодически ссылаться на неё.

Работать мы будем с CURL, но для начала давайте разберёмся, что эта аббревиатура обозначает. CURL – это программа командной строки, позволяющая нам общаться с серверами используя для этого различные протоколы, в нашем случаи HTTP и HTTPS. Для работы с CURL в PHP есть библиотека libcurl, функции которой мы и будем использовать для отправки запросов и получения ответов от сервера.


Как можно увидеть из скриншота все категории находятся в ненумерованном списке, а подкатегории:


Внутри отельного элемента списка в таком же ненумерованном. Структура несложная, осталось только её получить. Товары мы возьмем из раздела «Все телефоны»:


На странице получается 24 товара, у каждого мы вытянем: картинку, название, ссылку на товар, характеристики и цену.

Пишем скрипт парсера

Если вы уже прочли предыдущею статью, то из неё можно было подчеркнуть, что процесс и скрипт парсинга сайта состоит из двух частей:

  1. Нужно получить HTML код страницы, которой нам необходим;
  2. Разбор полученного кода с сохранением данных и дальнейшей обработки их (как и в первой статье по парсингу мы будем использовать phpQuery, в ней же вы найдете, как установить её через composer).

Для решения первого пункта мы напишем простой класс с одним статическим методом, который будет оберткой над CURL. Так код можно будет использовать в дальнейшем и, если необходимо, модифицировать его. Первое, с чем нам нужно определиться - как будет называться класс и метод и какие будут у него обязательные параметры:

Class Parser{ public static function getPage($params = ){ if($params){ if(!empty($params["url"])){ $url = $params["url"]; // Остальной код пишем тут } } return false; } }

Основной метод, который у нас будет – это getPage() и у него всего один обязательный параметр URL страницы, которой мы будем парсить. Что ещё будет уметь наш замечательный метод, и какие значения мы будем обрабатывать в нем:

  • $useragent – нам важно иметь возможность устанавливать заголовок User-Agent, так мы сможем сделать наши обращения к серверу похожими на обращения из браузера;
  • $timeout – будет отвечать за время выполнения запроса на сервер;
  • $connecttimeout – так же важно указывать время ожидания соединения;
  • $head – если нам потребуется проверить только заголовки, которые отдаёт сервер на наш запрос этот параметр нам просто будет необходим;
  • $cookie_file – тут всё просто: файл, в который будут записывать куки нашего донора контента и при обращении передаваться;
  • $cookie_session – иногда может быть необходимо, запрещать передачу сессионных кук;
  • $proxy_ip – параметр говорящий, IP прокси-сервера, мы сегодня спарсим пару страниц, но если необходимо несколько тысяч, то без проксей никак;
  • $proxy_port – соответственно порт прокси-сервера;
  • $proxy_type – тип прокси CURLPROXY_HTTP, CURLPROXY_SOCKS4, CURLPROXY_SOCKS5, CURLPROXY_SOCKS4A или CURLPROXY_SOCKS5_HOSTNAME;
  • $headers – выше мы указали параметр, отвечающий за заголовок User-Agent, но иногда нужно передать помимо его и другие, для это нам потребуется массив заголовков;
  • $post – для отправки POST запроса.

Конечно, обрабатываемых значений много и не всё мы будем использовать для нашей сегодняшней задачи, но разобрать их стоит, так как при парсинге больше одной страницы многое выше описанное пригодится. И так добавим их в наш скрипт:

$useragent = !empty($params["useragent"]) ? $params["useragent"] : "Mozilla/5.0 (Windows NT 6.3; W…) Gecko/20100101 Firefox/57.0"; $timeout = !empty($params["timeout"]) ? $params["timeout"] : 5; $connecttimeout = !empty($params["connecttimeout"]) ? $params["connecttimeout"] : 5; $head = !empty($params["head"]) ? $params["head"] : false; $cookie_file = !empty($params["cookie"]["file"]) ? $params["cookie"]["file"] : false; $cookie_session = !empty($params["cookie"]["session"]) ? $params["cookie"]["session"] : false; $proxy_ip = !empty($params["proxy"]["ip"]) ? $params["proxy"]["ip"] : false; $proxy_port = !empty($params["proxy"]["port"]) ? $params["proxy"]["port"] : false; $proxy_type = !empty($params["proxy"]["type"]) ? $params["proxy"]["type"] : false; $headers = !empty($params["headers"]) ? $params["headers"] : false; $post = !empty($params["post"]) ? $params["post"] : false;

Как видите, у всех параметров есть значения по умолчанию. Двигаемся дальше и следующей строчкой напишем кусок кода, который будет очищать файл с куками при запросе:

If($cookie_file){ file_put_contents(__DIR__."/".$cookie_file, ""); }

Так мы обезопасим себя от ситуации, когда по какой-либо причине не создался файл.

Для работы с CURL нам необходимо вначале инициализировать сеанс, а по завершению работы его закрыть, также при работе важно учесть возможные ошибки, которые наверняка появятся, а при успешном получении ответа вернуть результат, сделаем мы это таким образам:

$ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); // Далее продолжаем кодить тут curl_setopt($ch, CURLINFO_HEADER_OUT, true); $content = curl_exec($ch); $info = curl_getinfo($ch); $error = false; if($content === false){ $data = false; $error["message"] = curl_error($ch); $error["code"] = self::$error_codes[ curl_errno($ch) ]; }else{ $data["content"] = $content; $data["info"] = $info; } curl_close($ch); return [ "data" => $data, "error" => $error ];

Первое, что вы могли заметить – это статическое свойство $error_codes, к которому мы обращаемся, но при этом его ещё не описали. Это массив с расшифровкой кодов функции curl_errno(), давайте его добавим, а потом разберем, что происходит выше.

Private static $error_codes = [ "CURLE_UNSUPPORTED_PROTOCOL", "CURLE_FAILED_INIT", // Тут более 60 элементов, в архиве вы найдете весь список "CURLE_FTP_BAD_FILE_LIST", "CURLE_CHUNK_FAILED" ];

После того, как мы инициализировали соединения через функцию curl_setopt(), установим несколько параметров для текущего сеанса:

  • CURLOPT_URL – первый и обязательный - это адрес, на который мы обращаемся;
  • CURLINFO_HEADER_OUT –массив с информацией о текущем соединении.

Используя функцию curl_exec(), мы осуществляем непосредственно запрос при помощи CURL, а результат сохраняем в переменную $content, по умолчанию после успешной отработки результат отобразиться на экране, а в $content упадет true. Отследить попутную информацию при запросе нам поможет функция curl_getinfo(). Также важно, если произойдет ошибка - результат общения будет false, поэтому, ниже по коду мы используем строгое равенство с учетом типов. Осталось рассмотреть ещё две функции это curl_error() – вернёт сообщение об ошибке, и curl_errno() – код ошибки. Результатом работы метода getPage() будет массив, а чтобы его увидеть давайте им воспользуемся, а для теста сделаем запрос на сервис httpbin для получения своего IP.

Кстати очень удобный сервис, позволяющий отладить обращения к серверу. Так как, например, для того что бы узнать свой IP или заголовки отправляемые через CURL, нам бы пришлось бы писать костыль.
$html = Parser::getPage([ "url" => "http://httpbin.org/ip" ]);

Если вывести на экран, то у вас должна быть похожая картина:

Если произойдет ошибка, то результат будет выглядеть так:


При успешном запросе мы получаем заполненную ячейку массива data с контентом и информацией о запросе, при ошибке заполняется ячейка error. Из первого скриншота вы могли заметить первую неприятность, о которой я выше писал контент сохранился не в переменную, а отрисовался на странице. Чтобы решить это, нам нужно добавить ещё один параметр сеанса CURLOPT_RETURNTRANSFER.

Curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

Обращаясь к страницам, мы можем обнаружить, что они осуществляют редирект на другие, чтобы получить конечный результат добавляем:

Curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

Теперь можно увидеть более приятную картину:

Curl_setopt($ch, CURLOPT_USERAGENT, $useragent); curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $connecttimeout);

Для того, чтобы получить заголовки ответа, нам потребуется добавить следующий код:

If($head){ curl_setopt($ch, CURLOPT_HEADER, true); curl_setopt($ch, CURLOPT_NOBODY, true); }

Мы отключили вывод тела документа и включили вывод шапки в результате:


If(strpos($url, "https") !== false){ curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); }

Уже получается весьма неплохой скрипт парсера контента, мы добрались до кук и тут стоит отметить - частая проблема, когда они не сохраняются. Одной из основных причин может быть указание относительного пути, поэтому нам стоит это учесть и написать следующие строки:

If($cookie_file){ curl_setopt($ch, CURLOPT_COOKIEJAR, __DIR__."/".$cookie_file); curl_setopt($ch, CURLOPT_COOKIEFILE, __DIR__."/".$cookie_file); if($cookie_session){ curl_setopt($ch, CURLOPT_COOKIESESSION, true); } }

Предлагаю проверить, а для этого я попробую вытянуть куки со своего сайта:


If($proxy_ip && $proxy_port && $proxy_type){ curl_setopt($ch, CURLOPT_PROXY, $proxy_ip.":".$proxy_port); curl_setopt($ch, CURLOPT_PROXYTYPE, $proxy_type); } if($headers){ curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); } if($post){ curl_setopt($ch, CURLOPT_POSTFIELDS, $post); }

Это малая доля параметров, с которыми можно работать, все остальные находятся в официальной документации PHP . Вот мы завершили с нашей оберткой, и пришло время, что-нибудь спарсить!

Парсим категории и товары с сайта

Теперь, при помощи нашего класса Parser, мы можем сделать запрос и получить страницу с контентом. Давайте и поступим:

$html = Parser::getPage([ "url" => "https://www.svyaznoy.ru/catalog" ]);

Следующим шагом разбираем пришедший ответ и сохраняем название и ссылку категории в результирующий массив:

If(!empty($html["data"])){ $content = $html["data"]["content"]; phpQuery::newDocument($content); $categories = pq(".b-category-menu")->find(".b-category-menu__link"); $tmp = ; foreach($categories as $key => $category){ $category = pq($category); $tmp[$key] = [ "text" => trim($category->text()), "url" => trim($category->attr("href")) ]; $submenu = $category->next(".b-category-submenu")->find(".b-category-submenu__link"); foreach($submenu as $submen){ $submen = pq($submen); $tmp[$key]["submenu"] = [ "text" => trim($submen->text()), "url" => trim($submen->attr("href")) ]; } } phpQuery::unloadDocuments(); }

Чуть более подробно работу с phpQuery я разобрал в первой статье по парсингу контента. Если вкратце, то мы пробегаемся по DOM дереву и вытягиваем нужные нам данные, их я решил протримить, чтобы убрать лишние пробелы. А теперь выведем категории на экран:

  • " target="_blank">
    • " target="_blank">

$html = Parser::getPage([ "url" => "https://www.svyaznoy.ru/catalog/phone/224", "timeout" => 10 ]);

Получаем страницу, тут я увеличил время соединения, так как 5 секунд не хватило, и разбираем её, парся необходимый контент:

If(!empty($html["data"])){ $content = $html["data"]["content"]; phpQuery::newDocument($content); $products = pq(".b-listing__generated-container")->find(".b-product-block .b-product-block__content"); $tmp = ; foreach($products as $key => $product){ $product = pq($product); $tmp = [ "name" => trim($product->find(".b-product-block__name")->text()), "image" => trim($product->find(".b-product-block__image img")->attr("data-original")), "price" => trim($product->find(".b-product-block__misc .b-product-block__visible-price")->text()), "url" => trim($product->find(".b-product-block__info .b-product-block__main-link")->attr("href")) ]; $chars = $product->find(".b-product-block__info .b-product-block__tech-chars li"); foreach($chars as $char){ $tmp[$key]["chars"] = pq($char)->text(); } } phpQuery::unloadDocuments(); }

Теперь проверим, что у нас получилось, и выведем на экран:

" target="_blank" class="tovar"> " alt="" />

Вот мы и написали парсер контента PHP, как видите, нет нечего сложного, при помощи этого скрипта можно легко спарсить страницы любого сайта, но перед тем, как заканчивать статью, хотелось пояснить некоторые моменты. Во-первых, если вы хотите парсить более одной страницы, то не стоит забывать, что сам процесс парсинга ресурса затратная операция, поэтому в идеале лучше, чтобы скрипт был вынесен на отдельный сервер, где и будет запускаться по крону. Ещё один момент - к каждому донору стоит подходить индивидуально, так как, во-первых: у них разный HTML код и он, с течением времени, может меняться, во-вторых: могут быть различные защиты от парсинга и проверки, поэтому для подбора необходимого набора заголовков и параметров может потребоваться отладочный прокси (я пользуюсь Fiddler). И последние, что я добавлю - используйте для парсинга прокси и чем больше, тем лучше, так как, когда на сервер донора полетят тысячи запросов, то неизбежно IP, с которого осуществляется обращение будет забанен, поэтому стоит прогонять свои запросы через прокси-сервера.

Полный пример с библеотекай phpQuery вы найдете на github .

Отличная статья. Спасибо. Как раз сейчас разбираю пхп и тему парсеров.

Рад, что статья вам понравилась. В одной из следующих расскажу об уже готовых решениях для парсинга сайтов.

Часто бывает такое, что нужно вытащить определенную информацию с какого-либо сайта, а еще лучше, чтобы эта информация сразу же добавилась в базу данных или каким-либо другим образом была выведена уже на вашем ресурсе.

Способов сделать это существует уйма – например, имеется мощная программа, предназначение которой парсить сайты, называется content downloader . Среди минусов ее то, что она десктопная, то есть, работать с ней придется либо со своего компьютера, либо с удаленного сервера. Само собой программа платная, так что придется еще и заплатить какую-то сумму денег, чтобы использовать ее (имеется несколько типов лицензий).

Кроме того существует еще ZennoPoster , который обладает более широкими возможностями, так как может симулировать работу человека в браузере, однако и недостатков у него предостаточно.

Наконец, написать парсер можно на специальных скриптовых языках, вроде iMacros , однако это не всегда удобно, да и возможности таких языков сильно ограничены.

Самый лучший вариант – написать php скрипт , который будет подключаться с удаленного хостинга через прокси, например, к нужному ресурсу, и сразу же добавлять спарсенную информацию в базу данных.

Что для этого требуется? Основные знания php, то есть умение работать с данными, хорошее владение синтаксисом, и опыт работы с библиотекой cURL .

Как же выдрать нужные данные со страницы? Сначала обязательно следует скачать саму страницу, например, с помощью библиотеки cURL, хотя можно воспользоваться и стандартной функцией file_get_contents, если хостинг поддерживает удаленное подключение через fopen. cURL к слову очень мощный инструмент для составления POST, GET запросов, использования прокси и вообще всего, чего только душе угодно, плюс установлен на любом хостинге практически.

Теперь данные нужно обработать, тут следует выбрать, каким образом выдирать информацию со страницы. Можно воспользоваться стандартными функциями php, вроде strpos, substr и т.д., но это настолько криво, что лучше об этом даже не думать.

Вторая мысль приходит – использовать регулярные выражения для этих целей. И правда, регулярные выражения – отличный вариант для нахождения той или иной информации на странице, однако есть одно но, вам придется много писать, возможно, придется написать целую библиотеку, прежде чем вы приведете код к более или менее читаемому виду, без снижения гибкости и функционала. Иными словами регулярные выражения – хорошо, но не в данном случае.

Благо, что существуют уже готовые библиотеки, которые позволяют сосредоточиться непосредственно на работе со страницей, как с DOM (Document Object Model).

$doc = new DOMDocument(); $doc->loadHTML ($data);

Первая строчка создает объект, а вторая создает из обычных string данных (в которых должно находиться содержимое страницы) создает DOM.

$searchNodes = $doc->getElementsByTagName("a");

Теперь в переменной $searchNodes находится массив из найденных тегов "a".

Foreach ($searchNodes as $cur) { echo $cur->getAttribute("href"); }

А этот код выведет все значения полей href (обычно это адрес, куда попадает пользователь после нажатия на ссылку).

Более подробно с данной мощной библиотекой можно ознакомиться в официальной документации.

Но если вы хотите еще проще и удобней, то обратите внимание на библиотеку PHP Simple HTML DOM Parser. Она очень удобна и проста в освоении, разобраться, что к чему можно буквально за 10-15 минут, однако, с некоторыми типами данных она работает не слишком хорошо.

Существуют еще библиотеки, но эти две наиболее удобны и просты в освоении.


контент php парсить страниц

Заметка посвящается парсингу, в частности парсинг сайтов, парсинг страниц, парсинг в веб-среде, парсинг html-контента сайта.

В процессе разработки различных веб-сервисов очень часто приходится сталкиваться с задачами, в которых требуется быстро получить различного рода информацию в больших объемах. В основном это связано с граббингом, кражей информации, как хотите это называйте. Дело в том, что информация доступна и открыта. Особенность парсинга - это быстрый и автоматизированный сбор данных, контента со страниц сайта.

Сейчас очень популярно парсить в веб-среде, а именно парсить сайта, который содержать хоть какую-нибудь ценность и актуальность для людей. Особой ценностью является каталог товаров, включая картинки, базы данных справочников и многое другое, что может пригодиться для конкурентов.

Давайте попробуем спарсить нужную информацию в html, попробуем достать все ссылки с нескольких страниц нашего сайта.

Для начала нам необходимо получить контент сайта в формате html. Для этого нам достаточно знать адреса нужных страниц.

Хочу показать 2 основных способа получения контента со страницы сайта:

В первую очередь приготовим массив с нужными адресами страниц:

//3 ссылки нашего сайта: $urls = array("http://hello-site..ru/games/");

1 вариант - php функция file_get_contents . Функция возвращает html-строку, которую мы будем парсить на ссылки:

//помещаем каждую ссылку в функцию file_get_contents foreach($urls as $urlsItem){ $out .= file_get_contents($urlsItem); //и добавляем содержание каждой страницы в строку } echo $out; //здесь контент всех трех страниц

2 вариант - CURL . Библиотека, которая поддерживается php и имеет большой набор настроек, от POST-запросов до работы с FTP. Рассмотрим стандартный вызов библиотеки curl, который отдаст нам контент сайта:

foreach($urls as $urlsItem){ //пропускаем каждую ссылку в цикле $output = curl_init(); //подключаем курл curl_setopt($output, CURLOPT_URL, $urlsItem); //отправляем адрес страницы curl_setopt($output, CURLOPT_RETURNTRANSFER, 1); curl_setopt($output, CURLOPT_HEADER, 0); $out .= curl_exec($output); //помещаем html-контент в строку curl_close($output); //закрываем подключение } echo $out; //здесь контент всех трех страниц

Теперь в нашей строке $out находится контент всех трех страниц. Итак, переходим непосредственно к парсингу нашей строки.

Опять же хочу показать 3 варианта решения нашей задачи: "нативный" способ на php, с помощью встроенной библиотеки DOMDocument и библиотеки SimpleHTMLDOM.

1. php функция explode . Функция находит искомый символ или часть строки и делит целую строку на элементы массива.

Повторюсь, нам необходимо получить значения всех атрибутов href у тегов a, для этого будем делить общую строку на некоторые части\отрезки:

// explode $hrefs = explode("

Если распечатать наш массив, будет примерно следующее:

Array ( => / => /hello => /timer/ => /leftmenu/ => /faq/ => /blog/ => /web-notes/ => /ordersite/ => /games)

2. встроенная библиотека DOMDocument . Работаем с классом примерно следующим образом:

//domelement $dom = new DOMDocument; //создаем объект $dom->loadHTML($out); //загружаем контент $node = $dom->getElementsByTagName("a"); //берем все теги a for ($i = 0; $i < $node->length; $i++) { $hrefText = $node->item($i)->getAttribute("href"); //вытаскиваем из тега атрибут href } foreach($hrefText as $hrefTextItem){ //избавляемся от ссылок с пустым атрибутом href if($hrefTextItem!=""){ $clearHrefs=$hrefTextItem; } } $clearHrefs = array_unique($clearHrefs); //избавляемся от одинаковых ссылок print_r($clearHrefs); // в итоге у нас массив со всем ссылками с 3х страниц

Результат такого кода ровно такой же, что и с помощью функции explode.

3. библиотека SimpleHTMLDOM . Ее необходимо подключать из файла. Работа примерно схожа с DOMDocument. Работаем с классом:

//simplehtml include("simple_html_dom.php"); //подключаем файл с классом SimpleHTMLDOM $html = new simple_html_dom(); //создаем объект $html->load($out); //помещаем наш контент $collection = $html->find("a"); //собираем все теги a foreach($collection as $collectionItem) { $articles = $collectionItem->attr; //массив всех атрибутов, href в том числе } foreach($articles as $articlesItem){ $hrefText = $articlesItem["href"]; //собираем в массив значения подмассива с ключом href } foreach($hrefText as $hrefTextItem){ //избавляемся от ссылок с пустым атрибутом href if($hrefTextItem!=""){ $clearHrefs=$hrefTextItem; } } $clearHrefs = array_unique($clearHrefs); //избавляемся от одинаковых ссылок print_r($clearHrefs); // в итоге у нас массив со всем ссылками с 3х страниц

Повторюсь, результат в массив ровно такой же как и выше в двух вышеперечисленных.

Теперь, имея массив со всеми ссылками, собранными с трех страниц сайта, можно отправить ссылки в нужное русло, все зависит от задачи и фантазии. Имея такие возможности, можно спарсить большое количество данных самого разного вида информации, картинки, тексты, логи и т.д. Чужая информация в ваших руках, распоряжайтесь как вам угодно, но сами защищайтесь, хотя это невозможно)

Вебмастеры часто сталкиваются с такой проблемой, когда нужно взять с какого-либо сайта определенную информацию и перенести ее на другой. Можно сначала сохранить информацию на промежуточный носитель, а уже с него загрузить куда-либо, но подобный подход не всегда удобен. В некоторых случаях гораздо быстрее залить парсер на сам сайт, поддерживающий PHP и запустить его удаленно, чтобы он автоматически спарсил информацию и загрузил ее в базу данных ресурса.
Среди уже готовых решений имеются популярные вроде Content Downloader и ZennoPoster, они конечно очень удобны и понятны любому человеку, даже незнакомому с программированием, однако имеют некоторые минусы. К примеру, они платные и не обладают достаточной гибкостью, которую можно вдохнуть в обычный php скрипт. Тем более, что разработка сложного парсера на них нисколько не уступает по времени написанию аналога на php.
Еще есть такая бесплатная вещь как iMacros – скриптовый язык, который может эмулировать действия пользователя в браузере, но тоже не везде такой подход работает лучшим образом.

Многие думают, что программирование, и уж тем более написание парсеров, – очень сложное занятие. На самом деле php – один из самых простых языков, изучить который можно на достаточном уровне за пару недель или месяц.
Парсеры тоже просты в написании, именно поэтому начинающие программисты пишут именно их, чтобы освоить язык.
Первое, что приходит на ум человеку, который решил написать подобный скрипт, - нужно использовать функции для работы со строками (strpos, substr и аналогичные) или регулярные выражения. Это совершенно верно, однако есть один нюанс. Если парсеров нужно будет писать много, то придется разрабатывать свою библиотеку, чтобы не переписывать сто раз одни и те же конструкции, но на это уйдет тонна времени, а учитывая то, что уже существуют аналогичные библиотеки, такое занятие и вовсе оказывается бессмысленным.
Идеальным вариантом для новичка станет изучение библиотеки PHP Simple HTML DOM Parser. Как можно догадаться из названия, она очень проста в освоении. Рассмотрим базовый код:

$html = file_get_html("http://www.yandex.ru");
$a_links = $html->find("a");

Первая строка создает объект страницы, источником которой в данном случае является Яндекс, и записывает в переменную $html, которая имеет несколько функций, например find. Find – ищет элемент по какому-либо параметру, например find (‘a’) – вернет массив всех ссылок страницы. Find(‘#myid’) – вернет массив элементов, id которых равен "myid".
Доступ к параметру href первой попавшейся ссылки осуществляется так:

Echo $a_links[ 0 ]->href;

Более подробно можно посмотреть на сайте:
simplehtmldom.sourceforge.net

Библиотека, как уже было сказано выше, очень проста и лучше всего подходит для начинающего программиста, плюс ко всему она работает достаточно быстро и не сильно требовательна к ресурсам сервера.
Есть у этой библиотеки один минус – далеко не все страницы ей оказываются по зубам. Если какой-либо элемент не отображается, но точно известно, что он там есть, лучше воспользоваться библиотекой DOM (Document Object Model). Она хороша во всем, кроме скорости разработки и понятности.

$doc = new DOMDocument();
$doc->loadHTML ($data);
$searchNodes = $doc->getElementsByTagName("a");
echo $searchNodes[ 0 ]->getAttribute("href");

Этот скрипт создает сначала объект типа DOM, при этом в переменной $data должен находиться код страницы. Затем находит все теги a (ссылки), с помощью вызова $doc->getElementsByTagName, затем записывает их в массив $searchNodes. Доступ к параметру href первой ссылки на странице осуществляется с помощью вызова $searchNodes[ 0 ]->getAttribute("href").
В итоге скрипт получается более громоздкий, и писать его уже не так удобно, но иногда приходится использовать именно эту библиотеку.

Теги: php, парсер, программирование

Дата публикации: 03.01.2018

Приветствую вас, друзья! 🙂

Думаю, что, если не все, то, уж точно большинство из вас сталкивались на практике с необходимостью чтения информации из txt файлов на уровне серверных скриптов. У меня, по крайней мере, таких случаев было несколько, о последнем из которых я вам сегодня и расскажу.

Ничего в этом сложного нет, но иногда глаза разбегаются от обилия вариантов, предоставляемых средствами серверных языков. Если говорить конкретно о PHP, на котором я сейчас программирую, то с помощью его функций можно считывать содержимое файлов и построчно, и целиком в строку, и в массив, причём для последнего варианта существует ещё несколько способов… Вот такие пироги 🙂

К сожалению только, данные методы работают с различной скоростью для файлов разной структуры, и о скорости их работы нет ни единого слова в официальной документации; об этом можно судить лишь на практике, перебирая все возможные варианты.

Поэтому, сегодня я продемонстрирую вам работу различных функций PHP для чтения файлов, чтобы, когда вам нужно будет создать PHP парсер файла для решения реальных задач, вы знали, из чего выбирать. А также подскажу, как именно в «боевых условиях» сделать правильный выбор.

Поехали! 🙂

Создаём PHP парсер файла — начальные условия

Перед тем, как мы начнём, пару слов о задаче, для которой я создавал парсер файла на PHP, а затем выбирал из реализованных вариантов оптимальный.

Однажды у меня на работе возникла проблема, которая заключалась в том, что в БД хранились телефоны пользователей в неверном формате. Сам баг я, естественно, без проблем пофиксил.

Но, что делать с неверной информацией, которая на тот момент уже хранились в базе данных? Естественно, её нужно было заменить на корректную.

Для этого мне был предоставлен текстовый файл с идентификаторами пользователей и их телефонами, которые нужно было перенести в БД.

Должен сказать, он получился весьма увесистым: 352 Кбайта и 8223 строки текста, в каждой из которых содержался идентификатор пользователя и его телефон в формате id_пользователя:номер_телефона .

Словом, вся задача заключалась в построчном чтении файла PHP средствами, выделения из строки идентификатора и телефона с последующим обновлением значения телефона у пользователя в БД, найденного по айдишнику.

Мой проект был реализован на PHP фреймворке Yii, следовательно в дальнейших примерах кода вы встретите элементы его API для работы с БД, в частности, поэтому не пугайтесь 🙂

После анализа имеющихся в языке конструкций, а также опыта других разработчиков, по крупицам собранного в Интернете, мне удалось выделить 4 способа, которые я далее вам и продемонстрирую.

Ну, а после я расскажу, по каким критериям и как именно я выбирал среди них оптимальный вариант. И, естественно, поделюсь результатами 🙂

Так что данная статья — отличная тренировка терпеливости 🙂 Суть её будет заключаться в подробном изучении следующего материала вплоть до результатов, которые будут ждать вас в конце. По ходу, кстати, можете поработать ещё и над фантазией, предполагая, как именно будет выбираться идеальный вариант.

Чтение файла в PHP построчно с помощью fgets()

В итоге, PHP парсер файла, реализующий данный алгоритм, у меня принял следующий вид:

find("unique_id IN (:id1, :id2)", array(":id1" => strtolower($params), ":id2" => strtoupper($params))); if ($client) { $client->phone = str_replace(array("\r", "\n"), "", $params); $client->save(); } } } } if (!feof($fh)) { echo "Error: unexpected fgets() fail\n"; } fclose($fh); } else echo "Check the filename, file doesn"t exists!"; }

Немного расшифрую свою писанину, если у кого-то возникнут сложности в понимании.

В самом начале, переменной $filename присваивается значение имени файла, который будет парситься, с полным путём к нему. Далее следуют PHP проверка существования файла и читаем ли он с помощью функций file_exists() и is_readable() соответственно.

Если всё ОК, то открываем файл с помощью функции fopen() , которая вызывается с PHP оператором управления ошибками для того, чтобы отключить вывод ошибок, генерируемых данной функцией. Использовать я его решил, чтобы сгенерировать своё сообщение об ошибке вместо стандартного.

Если файл открыть получилось, то мы проходимся по всем его строкам в цикле, пока файл не закончится, и, если строка не пустая, разделяем её по символу двоеточия функцией explode() .

Затем проверяем, что id пользователя и его телефон не пустые, ищем пользователя в БД по айдишнику и, если таковой существует, то обновляем ему номер телефона, убрав из значения номера предварительно символы переноса и начала новой строки.

Ну, и ещё я использовал PHP функции strtolower() и strtoupper() для проверки существования в БД пользователя с идентификаторами, которые могли быть прописаны в различных регистрах, т.к. они в моём случае состояли из символов и цифр.

PHP парсинг файла в массив с помощью file()

Данный метод чтения файла в PHP предполагает использование функции file() , которая открывает файл и помещает его содержимое в массив. При этом элементами массива будут являться, как раз, строки считываемого файла, что в моей ситуации отлично подходит.

Код данного варианта PHP парсера файла получился следующий:

find("unique_id IN (:id, :id2)", array(":id" => strtolower($params), ":id2" => strtoupper($params))); if ($client) { $client->phone = str_replace(array("\r", "\n"), "", $params); $client->

Как видите, от предыдущего способа чтения файла в PHP данный отличается только своим началом, где файл открывается и сразу же считывается функцией file() вместо связки fopen() + fgets() , как ранее.

PHP чтение файла в переменную с помощью fread()

Ещё одной функцией PHP для разбора файла является fread() , с помощью которой можно читать различные фрагменты файла указанной длины. Чтобы прочитать файл в PHP целиком, в качестве размера фрагмента я указал размер файла, полученный с помощью функции filesize() :

find("unique_id IN (:id1, :id2)", array(":id1" => strtolower($params), ":id2" => strtoupper($params))); if ($client) { $client->phone = str_replace(array("\r", "\n"), "", $params); $client->save(); } } } } } else echo "Check the filename, file doesn"t exists!"; }

Данный способ чтения файла PHP средствами, на самом деле, очень похож на предыдущий, т.к., несмотря на то, что с помощью PHP данные из файла изначально считываются не в массив, а в строковую переменную, далее она всё равно преобразуется в массив, т.к. с ним проще работать, чем со строкой.

Преобразование строки в массив на PHP проще всего сделать с помощью уже применявшейся сегодня функции explode(), в качестве разделителя в которую был передан символ начала строки.

Создаём PHP парсер файла на базе file_get_contents()

Ну, и напоследок, я решил реализовать PHP парсинг файла с помощью функции file_get_contents() , которая, как раз и предназначена для чтения файла целиком в строку, т.е. работает, практически, как fread($fp, filesize($filename)) .

За тем лишь исключением, что file_get_contents() самостоятельно открывает файл и считывает его, в то время как для использования fread() нужно было предварительно открыть файл через fopen() и получить его указатель для дальнейшего использования.

В целом, код PHP парсера файла на базе file_get_contents() будет практически как и в предыдущем случае:

find("unique_id IN (:id1, :id2)", array(":id1" => strtolower($params), ":id2" => strtoupper($params))); if ($client) { $client->phone = str_replace(array("\r", "\n"), "", $params); $client->save(); } } } } } else echo "Check the filename, file doesn"t exists!"; }

На этом всё. Пришло время подвести итоги производительности всех перечисленных вариантов и выяснить, какой же PHP парсер файла оказался самым оптимальным для дальнейшего использования.

Какой способ обработки файлов в PHP является оптимальным?

Чтобы выбрать из найденных вариантов самый оптимальный, т.е. самый быстрый, я решил определить в каждом случае. Для этого я воспользовался методикой, описанной в статье по ссылке.

Сами по себе PHP функции чтения файлов достаточно шустрые, поэтому, чтобы добиться хоть каких-то более-менее осязаемых цифр времени их работы, я специально оставил в тестируемых фрагментах операции с базой данных, которые во всех случаях были одни и те же.

Время работы PHP скрипта я также решил для удобства округлять до третьего знака после запятой, т.е. до тысячных долей секунд (хотя, можно было ограничиться и сотыми, на самом деле).

Помню, когда я учился в школе и писал свою научную работу по физике (да, был такой опыт 🙂) на её защите перед университетскими преподавателями меня постоянно упрекали за недостаточное количество экспериментов (я делал по 3 опыта для каждого случая). «Светилы науки» называли цифры в 100, ну или, хотя бы, в 10 экспериментов для сравнения различных ситуаций, чтобы можно было делать какое-то их сопоставление и минимизировать вероятность случайного превосходства одного над другим.

Да, досталось мне тогда от них крепко, но их рекомендации я хорошо усвоил, что даже сейчас об этом помню, хотя прошло уже более 10 лет с тех пор. Тем более, что данные рекомендации действительно были основаны на законах математической статистики и теории вероятности.

Ну, на научность своих нынешних экспериментов я в данной статье не претендую, поэтому число в 100 экспериментов я посчитал излишне большим, а процесс их проведения — слишком утомительным занятием.

В итоге, я решил ограничиться 10 экспериментами для каждого варианта PHP парсера файла, чего, как оказалось в итоге, оказалось вполне достаточно, чтобы выделить явного лидера без всякой подтасовки фактов и зацепок за сотые и тысячные доли секунды превосходства.

Результаты вычислений времени работы разработанных мною PHP парсеров файла представлены в следующей таблице и рассортированы по PHP функциям, на базе которых они работают.

Эксперимент fgets() file() fread() file_get_contents()
1 9,147 9,722 10,539 2,008
2 8,950 9,006 9,495 1,733
3 8,821 8,845 9,207 1,642
4 8,717 8,876 8,931 1,758
5 9,010 9,091 8,703 1,635
6 9,110 8,640 9,712 1,633
7 9,074 9,626 9,13 1,645
8 8,886 9,204 9,048 1,701
9 8,667 8,918 9,438 1,713
10 8,852 9,197 9,537 1,567
Среднее 8,923 9,113 9,374 1,704

Как видите, помимо значений времени выполнения скрипта в каждом из 10 экспериментов, я решил подсчитать среднюю температуру по больнице 🙂

А именно, арифметическое среднее время работы каждого PHP парсера файла, чтобы можно было выявить лидера.

И им оказался, как видите, последний вариант, реализованный на базе функции file_get_contents() , который выполняет чтение содержимого файла в строковую переменную с дальнейшим его преобразованием в массив и обработкой в цикле.

Все остальные варианты PHP парсеров файлов работают примерно с одинаковой скоростью.

Почему именно он обогнал своих конкурентов я, если честно, не имею ни малейшего понятия. Могу лишь предположить, что операция чтения файла в строку с помощью file_get_contents() требует меньше ресурсов, чем формирование готового массива строк с помощью file() .

А превосходство над fgets() и fread() можно списать на то, что перед их использованием требуется открытие файла с помощью fopen(), на что требуется время.

Да, на самом деле, это и не важно, т.к. цифры говорят сами за себя: благодаря использованию функции file_get_contents() PHP парсер файла на его базе работает в 5 раз быстрее остальных, что и повлияло на моё решение использовать его на практике.

Разбор файла в PHP — выводы

Как я уже и говорил в начале, мои опыты не являются безупречными и опираться исключительно на полученные в их ходе результаты не стоит, т.к., несмотря на быстродействие file_get_contents() в моей ситуации, бывают случаи, когда намного удобнее и эффективнее использовать другие приведённые мною PHP парсеры файлов.

Кроме того, не стоит забывать, что PHP сам по себе является синхронным языком программирования, т.е. все серверные операции происходят последовательно без возможности настройки их параллельного выполнения, в том числе, и на разных ядрах серверного процессора.

Следовательно, на время выполнения операций, прописанных в PHP коде, может влиять целый ряд факторов, среди которых основным является нагруженность ядра в момент работы PHP приложения.

Я это особенно ощутил во время проведения опытов, когда один и тот же PHP парсер файла отработал за 9, затем за 12, а потом снова за 9 секунд на трёх последовательных итерациях из-за банального запуска проводника Windows во время второго случая, который, естественно, тоже требует серверных ресурсов.

Учитывая данные особенности, я проводил эксперименты практически одновременно, друг за другом, при одинаковом комплекте запущенных программ, чтобы не распылять ресурсы серверного железа.

Поэтому в дальнейшем, при проведении подобных экспериментов с PHP конструкциями действуйте аналогичным образом, т.к. это, по сути, единственный способ привести эксперименты к равным условиям.

Если же вы будете работать с асинхронными серверными языками (C#, Java) или технологиями (Node.js, например), то, по возможности, для экспериментов создавайте отдельный поток, который будет работать на выделенном ядре процессора.

Ну, а если найти полностью незадействованное ядро не получится (что при уровне современного ПО не удивительно), то вы хотя бы сможете найти самое слабонагруженное или, хотя бы, со статической нагрузкой, которая не меняется во времени.

Подытоживая, хочу сказать, что приведённые в статье фрагменты кода могут использоваться не только для парсинга текстовых файлов в PHP, но и отлично подойдут для других форматов, например, для разбора CSV файлов .

Пишите ваши отзывы, как положительные, так и отрицательные в комментариях под статьёй — мне необходимо любое ваше мнение для дальнейшего развития 🙂

До новых встреч! 🙂

P.S. : если вам нужен сайт либо необходимо внести правки на существующий, но для этого нет времени и желания, могу предложить свои услуги.

Более 5 лет опыта профессиональной разработки сайтов. Работа с PHP , OpenCart , WordPress , Laravel , Yii , MySQL , PostgreSQL , JavaScript , React , Angular и другими технологиями web-разработки.

Опыт разработки проектов различного уровня: лендинги , корпоративные сайты , Интернет-магазины , CRM , порталы . В том числе поддержка и разработка HighLoad проектов . Присылайте ваши заявки на email [email protected] .