БлогNot. Пишем граббер на PHP

Пишем граббер на 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 просмотра]


теги: интернет программирование php новосибирск

К этой статье пока нет комментариев, Ваш будет первым