Пишем граббер на PHP
Грабберами в народе называют серверные скрипты, предназначенные для получения данных с различных серверов и встраивания их в свои страницы. В инете есть куча примеров RSS-грабберов, извлекающих тексты с новостных лент, но мне лично нужен не какой-то RSS, которым я ни разу в жизни не пользовался, а полноценный скрипт, который легко настроить для извлечения любой нужной мне информации с любой из доступных в сети страниц.
Так что эта небольшая статья - как раз пример написания граббера на языке PHP.
Задача состоит, собственно, из 3 этапов.
1. Получение данных с нужного нам URL
Для этого в PHP существует несколько возможностей:
Стандартная функция fopen, служащая для открытия файла
Применять ее не очень удобно, так как нельзя контролировать время соединения, получать ответы ошибок сервера и т.д. Кроме того, она может быть запрещена на хостинге через http. Тем не менее, вот пример откуда-то. Здесь мы парсим выдачу популярного сайта bash.org:
<? $url='http://www.bash.org.ru/best'; $file = @fopen ($url, 'r'); if ($file==false) print '<p>Не могу открыть сайт '.$url.'!'; else { $contents = fread ($file, 100000); $contents = preg_match_all('|<div>(.+)</div>|U',$contents,$frazes); for($i=0;$i<5;$i++){ if ($i<>5) echo "<hr>".$frazes[1][$i]."\r\n<hr>"; } fclose ($file); } ?>
Популярный вариант этого же подхода еще проще -
<? $file = file_get_contents('http://www.bash.org.ru/best'); $file = preg_match_all('|<div>(.+)</div>|U',$file,$frazes); for($i=0;$i<11;$i++){ if ($i<>5) echo "<hr>".$frazes[1][$i]."\r\n<hr>"; } ?>
$str=file_get_contents(”http://google.com/”);
(по сути, file_get_contents - это fopen, fread, fclose одной командой)
Библиотека cURL
Удобнее, но также может быть не установлена или запрещена на хостинге.
Соединение через сокеты
Именно его мы используем, чтоб HTTP-заголовок формировался полностью под нашим контролем. Полноценно проверять коды ошибок в учебной статье не будем, не надейтесь, но все же скрипт должен получиться похожим на человеческий.
Следующая функция получает содержимое, расположенное на хосте $host по абслютному пути $path. Имя хоста не включает в себя префиксов http://www, путь начинается с символа корневого каталога /.
function get_URL_by_socket ($host,$path) { //Получает URL $path с хоста $host через сокеты. $fp = fsockopen($host, 80); if (!$fp) { die ("Не могу получить данные с url http://$host/$path"); } else { $out = "GET $path HTTP/1.0\r\n"; $out .= "Accept: image/gif, application/xhtml+xml, */*\r\n"; $out .= "Accept-Language: ru\r\n"; $out .= "Host: $host\r\n"; //Имитируем браузер Opera Mini: $out .= "User-Agent: Opera/8.01 (J2ME/MIDP; ". "Opera Mini/2.0.4509/1716; ru; U; ssr)\r\n"; $out .= "Cache-Control: no-cache\r\n"; //Не кэшировать $out .= "Connection: Close\r\n\r\n"; fwrite($fp, $out); $headers = ""; while ($str = trim(fgets($fp))) $headers .= "$str\n"; $body = ""; while (!feof($fp)) $body .= fgets($fp); fclose($fp); } return $body; }
2. Извлечение содержимого из страницы
На следующем этапе мы должны извлечь из кода страницы, полученного функцией get_URL_by_socket, полезную для нас часть. Для этой цели в PHP существют регулярные выражения (ссылка на статью внизу страницы) и строковые функции. Я для простоты взял здесь случай, когда мы можем выделить в коде страницы куски текста, однозначно ограничивающие нужную нам часть снизу ($end) и сверху ($start). В принципе, при внимательном анализе исходного кода любой страницы (в браузере обратитесь к меню Вид, пункту "Исходный текст" или "Источник") легко выделить такие куски. Так как мы будем писать их внутрь строковых переменных, ограниченных двойными кавычками, то если в тексте строки встречается двойная кавычка ", ее нужно заменить на сочетание символов \", как здесь:
$start="<div class=\"temper\">";
Всю информацию будет обрабатывать следующая функция:
function process($s,$start,$end,$include) { //Парсит полученный файл - здесь-то и пишется главное //У нас это извлечение содержимого от $start до $end $s1=strpos ($s,$start); $s2=strpos ($s,$end); if (!is_integer($s1)) { return "Не найден начальный сегмент: ".htmlspecialchars($start); } if (!is_integer($s2)) { return "Не найден конечный сегмент: ".htmlspecialchars($end); } if ($s1>$s2) { return "Конечный сегмент предшествует начальному"; } if ($include) { //Включать начало и конец return substr ($s,$s1,$s2-$s1+strlen($end)); } else { //Исключить начало и конец $s1+=strlen($start); return substr ($s,$s1,$s2-$s1); } }
Параметр $include должен быть равен true, если строки $start и $end надо оставить в выводе или false, если их надо исключить.
3. Дополнительная обработка и вывод
Строку, возвращенную функцией process, можно дополнительно обработать (например, исключить лишние стили или ссылки, сделать относительные пути абсолютными и т.п.), либо сразу вывести ее на экран функцией PHP print или echo. В приведенном ниже примере единственная вызываемая пользователем парсера функция parser вызывает 2 остальные функции и дополнительно один раз шлет заголовок с кодировкой документа (если модуль работает из готового движка, блок с вызовом header нужно убрать).
function parser ($host,$path,$start,$end,$include) { //Основной вызов парсера: //$host, $path - хост без http://www. и путь к файлу, начиная с / //$start, $end - строки начала и конца извлекаемого содержимого //$include - если true, включать в вывод строки $start и $end static $first=true; $s= get_URL_by_socket ($host,$path); if ($first) { //Заголовок посылается только при 1-м вызове $first=false; //Если вызывается из "движка" - можно убрать этот блок header('Content-type:text/html;charset=windows-1251'); } return process($s,$start,$end,$include); }
Вызвать наш парсер можно, например, так:
$host="ngs.ru"; $path="/"; $start="<div class=\"temper\">"; $end="width=\"30\" height=\"15\"></td>\n </tr>\n</table>"; $include=true; print parser ($host,$path,$start,$end,$include);
Здесь мы вытаскиваем краткий прогноз погоды с новосибирского городского сервера НГС. Обратите внимание, что все пробелы, которые были в полученном по адресу файле, я сохранил в строке параметра $end, а переносы строк заменил на \n
Еще пример:
$s=parser ("pers.narod.ru","/index.html", "<title>","</title>",false); print'<br>'.$s;
Здесь просто берется титул (содержимое тега TITLE) моей домашней странички.
Думаю, на основе этой статьи нетрудно модифицировать граббер под свои задачи.
Скачать и ссылки
Скачать этот пример в одном файле .PHP (ZIP) (1 Кб)
Статья про регулярные выражения PHP - по-моему, одна из лучших
10.02.2009, 14:03 [21252 просмотра]