В этой статье рассматриваются принципы написания парсеров контента с других сайтов.

Мы рассмотрим пример написания парсера сайта для скачивания картинок. Описанные алгоритмы парсера подойдут для любых типов файлов и данных.

Написние парсера на примере сайта http://imgsrc.ru . Готовый и рабочий парсер Вы можете скачать здесь, но код закрыт Zend, элементы кода этого парсера приведены в данной статье.

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


Скачать все примеры>>.

Напомним, что заказать написание парсера Вы можете у нас.

И так приступим.

Парсер строится на регулярных выражениях. О них в будущих статьях.

Главная функция для того, что бы «взять» страницу и не привлечь подозрений (данная функция будет применятся во всех примерах данной статьи):

<?php
  
  function getPage( $url )
  {
  $ch = curl_init();
  curl_setopt ($ch, CURLOPT_URL,$url);
  curl_setopt ($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.6) Gecko/20070725 Firefox/2.0.0.6"); //агент которым мы представимся
  curl_setopt ($ch, CURLOPT_TIMEOUT, 15 ); // таймаут
  curl_setopt ($ch, CURLOPT_FOLLOWLOCATION, 0); 
  curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1);
  $result = curl_exec ($ch);
  curl_close($ch);
  //$result = iconv("Windows-1251", "UTF-8", $result); // в случае если кодировка отличается то перекодируем результат.
  //$result = convTegs( $result );
  return $result; //возвращаем  полученную страницу
  }
  
  ?>

ОБЯЗАТЕЛЬНО! Для начала необходимо представить себе структуру сайта и провести анализ элементов для парсера сайта.

Этап 1, собираем ссылки на разделы с фотографиями.

Анализируем исходный код главной страницы и находим уникальные последовательности для выбора ссылок и названий категорий.

<a href='/cat/4-avto-moto.html'>авто-мото</a><br><br />
&middot; <a href='/cat/45-accent-club.html'>accent-club</a><br><br />
&middot; <a href='/cat/70-clubpicanto.ru.html'>clubpicanto.ru</a><br><br />
&middot; <a href='/cat/64-kia-club.html'>kia-club</a><br><br />
&middot; <a href='/cat/57-mazda3-club.html'>mazda3-club</a><br><br />
&middot; <a href='/cat/44-primera-club.html'>primera-club</a><br><br />
&middot; <a href='/cat/46-sonata-club.html'>sonata-club</a><br><br />
&middot; <a href='/cat/61-avtoekzotika.html'>автоэкзотика</a><br><br />
&middot; <a href='/cat/5-tyuning.html'>тюнинг</a><br><br />
<br><a href='/cat/17-arhitektura.html'>архитектура</a><br><br />
&middot; <a href='/cat/40-gorod.html'>город</a><br><br />
<br><a href='/cat/67-baraholka.html'>барахолка</a><br><br />
<br><a href='/cat/36-druzya.html'>друзья</a><br><br />
&middot; <a href='/cat/6-zhivotnyie.html'>животные</a><br><br />
&middot; <a href='/cat/59-odnoklassniki.html'>одноклассники</a><br><br />
<br><a href='/cat/65-makro.html'>макро</a><br><br />
<br><a href='/cat/24-nyu.html'>ню</a><br><br />
<br><a href='/cat/7-portret.html'>портрет</a><br><br />
&middot; <a href='/cat/38-prosto ya.html'>просто я</a><br><br />
<br><a href='/cat/8-prazdnik.html'>праздник</a><br><br />
&middot; <a href='/cat/9-novyiy god.html'>новый год</a><br><br />
&middot; <a href='/cat/10-svadba.html'>свадьба</a><br><br />
И так далее 

Шаблон ссылки будет выглядеть так - <a href='ССЫЛКА'>НАЗВАНИЕ</a>  

С помощью preg_match_all, выбираем все ссылки в массив:
preg_match_all ("/<a.href='\/cat\/(.*?)<\/a>/",$result , $url_list)
и получаем такое содержания по каждой ссылке -
[5-tyuning.html'>тюнинг]

Почему именно эти ссылки выделились, ведь на сайте много других ссылок? В процессе визуального анализа мы выделили, что ссылки каталога начинаются с /cat/.

 

Далее обрабатываем каждую ссылку в цикле.

В итоге получится вот такая функция:

<?php
  $urlMain='http://imgsrc.ru';
  $result = getPage( $urlMain .'?lang=ru' );
  if (! preg_match_all ("/<a.href='\/cat\/(.*?)<\/a>/",$result , $url_list)) {
  $resultTXT = 'Категории не найдены! <br />Ошибка! ( Возможно, ошибка сети )';
  } else {
  $link =  array ();
  foreach ($url_list[1] as $key=>$value){
  $tmp = trim($value);
  $tmp = iconv('windows-1251','utf-8', $tmp); //перекодируем
  $nameCat = preg_replace("/^(.*?)'>/is", "", $tmp);
  $urlCat = preg_replace("/'>(.*?)$/is", "", $tmp );
  $urlCat = $urlMain.'/cat/'.$urlCat;
  //в этой точке записываем в Вашу базу данных значения (код не приводится)
  echo 'Ссылка - '.$urlCat.' ------- название: <b>'. $nameCat.'</b>';
  echo '<br />';
  }
  }
?>

Разберем, как же мы выделили названия и ссылки.

На каждом шаге цикла foreach ($url_list[1] as $key=>$value){ мы получаем примерно вот такое содержание переменной [5-tyuning.html'>тюнинг] .

Теперь нам нужно выделить ссылки и названия категорий, самый наглядный способ сделать это в несколько регулярных выражений:
   1. Выделяем имя категории обрезав начало до тега ['>]: $nameCat = preg_replace("/^(.*?)'>/is", "", $tmp); Получаем название [тюнинг].
   2. Обрезаем конец строки от тега ['>] получаем [5-tyuning.html].
   3. К ссылке добавляем $urlMain.'/cat/ и получаем рабочую ссылку.

На этом этапе мы получили ссылки и названия категорий с главной страницы сайта.


Скачать работающий пример парсера категорий (архивом) PHP.

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

Для примера возьмем ссылку раздела Друзья.
http://imgsrc.ru/cat/36-druzya.html

Парсим количество страниц и ссылки на них (внизу страницы 1.2.3.4…36), в листалке.
1. Обрезаем страницу до ссылок 1.2.3.4… preg_replace('/^(.*?)class=uilt>/is', "", $result);
2. Обрезаем страницу после ссылок 1.2.3.4… preg_replace('/<noindex>(.*?)$/is', "", $result );
3. Иногда попадается реклама, обрезаем и ее preg_replace('/<iframe(.*?)<\/iframe>/is', "", $result );
4. Парсим ссылки в массив - preg_match_all ("/<a.href='\/main\/search.php(.*?)\'/",$result , $url_list);
Опять же повторюсь, необходим тщательный визуальный анализ исходных текстов сайта!

Поучилась вот такая функция парсера ссылок на страницы внутри категории:

<?php 
function getPageCount($url){
global $urlMain;
$result = getPage( $url );
$result = preg_replace('/^(.*?)class=uilt>/is', "", $result); //обрезаем HTML до списка 1.2.3.4....36
$result = preg_replace('/<noindex>(.*?)$/is', "", $result ); // обрезаем концовку HTML
$result = preg_replace('/<iframe(.*?)<\/iframe>/is', "", $result );//удаляем рекламу
$resultTXT = '';
$countAdd = 0;
if ( preg_match_all ("/<a.href='\/main\/search.php(.*?)\'/",$result , $url_list)) {
$link =  array ();
foreach ($url_list[1] as $key=>$value){ 
$tmp = trim($value);
$urltmp= $urlMain.'/main/search.php'.$tmp;
$link[$urltmp]=true; // в этой точке нужно реализовать запись в базу данных
echo $urltmp.'<br />';
}

}
} ?>

Далее обходим все страницы и парсим ссылки на альбомы:

  <?php
  foreach ($arrayCountPages as $url=>$tmp){
  parceUrlAlbums($url);
  
  }
  ?>
  


  <?php
  function parceUrlAlbums($urlPage){
  global $urlMain;
  $result = getPage( $urlPage ); 
  if (!stripos ($result, "<a target=_blank href=")) {
  echo 'Error';
  exit;
  } else {
  preg_match_all ("/<tr(.*?)<\/tr>/",$result , $url_list); //выбираем ячейки таблицы
  $link =  array (); 
  foreach ($url_list[1] as $key=>$value){ 
  $tmp = trim($value);
  if (stripos ( $tmp, "<a target=_blank href=" )) { 
  //выделяем урл
  $urlTMP = cutTextStartEnd($tmp, '<a target=_blank href=/','>');
  $urlTMP = $urlMain.'/'.$urlTMP;
  //выделяем количество фото в альбоме
  $photos = trim(cutTextStartEnd($tmp,'<td align=center>','</td>')); 
  echo 'Сылка на альбом -'. $urlTMP .'    кол-во фотографий в альбоме - '. $photos;
  echo '<br />'; 
  }
  } 
  }
  } ?>
  

В примере применены функция cutTextStartEnd() о ней мы расскажем немного ниже.

Скачать работающий пример парсера ссылок на альбомы на PHP (архив).



Оптимизация работы парсера или замена регулярных выражений более быстрыми аналогами.

Ранее в примерах применялись регулярные выражения для обрезки строки:
$test = preg_replace("/^(.*?)'>/is", "", $tmp); - обрезка от начала строки.
$test = preg_replace("/'>(.*?)$/is", "", $tmp ); - обрезка концовки строки.
Но дело в том, что это очень медленные и ресурсоемкие функции и их необходимо заменить более высокоскоростными функциями, например stripos();

Вот примеры готовых к использованию функций по обрезки строки:

<?php
// обрезка по шаблону начало-текст-конец 
  // без учета регистра. со штампами включительно
  //жадный 
  function  cutTextStartEnd($text, $start, $end) {
  $posStart = stripos($text, $start  );
  if ($posStart === false) return  $text;     
  $text =  substr($text,$posStart+strlen( $start ));              
  $posEnd = stripos($text, $end );              
  if ($posEnd === false) return  $text;
  $result = substr($text,0,  0-(strlen($text)-$posEnd));
  return $result;
  }
  // обрезка по шаблону начало-текст 
  // без учета регистра. со штампами включительно
  //жадный 
  function  cutTextStart($text, $start) {
  $posStart = stripos($text,  $start );
  if ($posStart === false) return  $text;     
  $result =  substr($text,$posStart+strlen( $start ));
  return $result;
  }

  // обрезка по шаблону текст-конец 
  // без учета регистра. со штампами включительно
  //жадный 
  function  cutTextEnd($text, $end) {        
  $posEnd = stripos($text, $end );              
  if ($posEnd === false) return  $text;
  $result = substr($text,0,  0-(strlen($text)-$posEnd));
  return $result;
  }
  ?>


Функция cutTextStartEnd() обрежет текст с бережным отношением к ресурсам сервера.

Например, что бы вырезать ссылку достаточно указать теги начали и конца:

$urlTMP = cutTextStartEnd($tmp, '<a target=_blank href=/','>');

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

Завершающий этап. Парсим и скачиваем картинки с альбома


В три этапа:

1. Парсим ссылки на альбомы с одной страницы - http://imgsrc.ru/main/search.php?str=&tag=&nopass=&cat=36&page=2 получаем список прямых ссылок на альбомы пользователей.

2. Заходим в альбом пользователя, если есть кнопка далее (иногда бывает) то переходим по ней, а если пароль, то игнорируем. Дальше возможен вариант, что у пользователя несколько страниц с фото, поэтому придется обойти их все. В результате мы получим прямые ссылки на фото *.jpg.

3. Скачиваем фото сортируя по логину пользователя.

Скорее всего в результате выполнения этого скрипта у Вас возникнет таймаут в 30 секунд, ограничения выставляются в настройках PHP или обходятся с помощью асинхронных запросов. Описание обхода ограничения выходят за рамки данной статьи.


Вот исходный текст основного алгоритма:
<?php
foreach ($arrayUrlAlbum as $url=>$tmp){
echo 'Фльбом '.$url.'<br />';

//парсим ссылки на картинки внутри альбомов
$urlImgArray = parceUrlImg($url);

echo '<div style="margin-left:25px">';
foreach ($urlImgArray as $urlImg=>$tmp2){
downloadImg($urlImg); //скаичваем картинки по ссылке
}
echo '</div>';

} 
?>

И по функциям:

Функция, которая парсит ссылки на сами альбомы:
<?php
  //-----------------------------------------------------------------------------------------------------------
  // парсим ссылки на альбомы и получаем прямые ссылки и список альбомов
  //----------------------------------------------------------------------------------------------------------- 
  function parceUrlAlbums($urlPage){
  global $urlMain;
  $result = getPage( $urlPage );
  $arrayUrlAlbum = array();
  if (!stripos ($result, "<a target=_blank href=")) {
  echo 'Error';
  exit;
  } else {
  preg_match_all ("/<tr(.*?)<\/tr>/",$result , $url_list); //выбираем ячейки таблицы
  $link =  array (); 
  foreach ($url_list[1] as $key=>$value){ 
  $tmp = trim($value);
  if (stripos ( $tmp, "<a target=_blank href=" )) { 
  //выделяем урл
  $urlTMP = cutTextStartEnd($tmp, '<a target=_blank href=/','>');
  $urlTMP = $urlMain.'/'.$urlTMP;
  $arrayUrlAlbum [$urlTMP] = $photos;
  //выделяем количество фото в альбоме
  $photos = trim(cutTextStartEnd($tmp,'<td align=center>','</td>')); 
  // echo 'Сылка на альбом -'. $urlTMP .'    кол-во фотографий в альбоме - '. $photos;
  // echo '<br />'; 
  }
  } 
  }
  
  return  $arrayUrlAlbum;
  } 
?>


Функция которая парсит ссылки на картинки внутри альбомов, обходя все страницы альбомов, если их несколько:

<?php
  //------------------------------------------------------------------------------------------------------------------------
  // парсим ссылки на картинки внутри самих альбомов
  //------------------------------------------------------------------------------------------------------------------------
  function parceUrlImg($urlAlbum){
  global $urlMain;
  $urlImgArray = array();
  $url = $urlAlbum.'?lang=ru';
  $result = getPage( $url ); //скачиваем HTML странцу
  
  //случай если альбом удален или ошибка
  if (!$result) {
  return;
  }
  
  // если имеется кнопка далее в альбоме, то переходим по кнопке и получаем новую странцу
  if (preg_match('/>><\/a>/is',$result ,$tmp_2)) {
  $url = preg_replace("/^(.*?)window.location=\'/is", "", $result); 
  $url = preg_replace("/'(.*?)$/is", "", $url ); 
  $url = trim($url); 
  $result = getPage( $url.'&lang=ru' ); // перешли по кнопке далее
  } 
  
  
  if (stripos($result,'<form name=passchk action=') and $row['password']) { //если это пароль, то выходим. 
  //в дынной статье мы не рассматриваем ввод пароля
  return;
  } else {
 //----------------------------------------------------------------------------
  //переход по кнопке все на одной странице (так проще парсить)
  //----------------------------------------------------------------------------
  $result = preg_replace("/^(.*?)href='\/main\/pic_tape.php/is", "", $result);
  $result = preg_replace('/\'>(.*?)$/is', "", $result );
  $urlNextTmp = $result; 
  
  //обходим и собираем ссылки на фото
  //----------------------------------------------------------------------------
  $i = 1;
  while ($i <= 150) {//предположим, что более 150 страниц быть не может
  $urlNext = $urlMain.'/main/pic_tape.php'.$urlNextTmp.'<br />';
  //echo $urlNext.'<br />';
  $result = getPage(	$urlNext ); 
  $resultRez = $result;
  //----------------------------------------
  // сканируем ссылки на фото
  //----------------------------------------
 //пометка на пароль
  $passwordFlag = 0;
  if ($row['pwd_md5']){
  $passwordFlag = 1;
  }
  if (! preg_match_all ("/<img.class=big.src=(.*?).alt=/",$result , $url_list)) { 
  return;//ошибка
  } else {
  foreach ($url_list[1] as $key=>$value){ 
  $tmp = trim($value);
  $urlImgArray[$tmp]=true; //записываем готовую ссылку на картинку в массив (или в базу)
  }
  }
  
  if (preg_match('/shortcut.add\("Right"/is',$resultRez ,$tmp_2)) {
  //если есть, то переходим по следующей ссылке на следующую страницу внутри альбома
  $resultRez = preg_replace("/^(.*?)Right\",function\(\).\{window.location='\/main\/pic_tape.php/is", "", $resultRez);
  $resultRez = preg_replace('/\'(.*?)$/is', "", $resultRez );
  $urlNextTmp = $resultRez;
  } else { 
  $i = 1000;
  break; 
  }
  
  $i++;
  }
  }
  
  return 	$urlImgArray; 
  }
?>


Функция скачивания картинок в папки. Сортирует по логину.

<?php
//------------------------------------------------------------------------------------------------------------------------
// Скачиваем картинки
//------------------------------------------------------------------------------------------------------------------------

 function downloadImg($urlImg){ 
  $up_dir = 'download/'
  @mkdir( $up_dir, 0700);
  $fileName =  basename($urlImg);
  //организуем название для папки, по логину пользователя
  $nameUser =  trim(preg_replace('/'.$fileName.'/is', "", $urlImg ));
  $nameUser = preg_replace('/^http:\/\/(.*?)\//is', "", $nameUser);
  $nameUser = preg_replace('/^\//is', "", $nameUser);
  $nameUser = preg_replace('/^(.*?)\//is', "", $nameUser);
  $nameUser = preg_replace('/\/(.*?)\/$/is', "", $nameUser );
  
  
  @mkdir( $up_dir, 0700  ); 
  @mkdir( $up_dir.$idCatGet, 0700 );
  @mkdir( $up_dir.$idCatGet.'/'.$nameUser, 0700 );
  $path = $up_dir.$idCatGet.'/'.$nameUser.'/'.$fileName; 

  echo '<br />Скачиваем - '.$up_dir.$nameUser.'/'.$fileName.'<br />';
  $url = $row['url']; 
  if (file_exists( $path )){
  //если файл имеется, то не скачиваем
  } else {
  @file_put_contents($path, file_get_contents($urlImg));
  }
  }
?>


Скрипт создаст папку download/.

Скачать работающий пример парсера одной страницы на PHP (архив).


P.S. Эта статья не претендует на полностью законченный скрипт, хотя такой и имеется: ссылка.
Мы лишь в общих чертах показали принцип работы парсеров. Если данная тема вызвала интерес, то в следующих статьях мы опишем как сделать более сложные парсеры.

Мы можем написать парсеры любой сложности и для любых данных! Заказывайте.


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