За последние 24 часа нас посетили 16532 программиста и 1677 роботов. Сейчас ищут 879 программистов ...

[Нужен совет] Парсер страниц сайта

Тема в разделе "Прочие вопросы по PHP", создана пользователем xobotyi, 18 авг 2013.

  1. xobotyi

    xobotyi Новичок

    С нами с:
    18 авг 2013
    Сообщения:
    10
    Симпатии:
    0
    Доброго времени суток, ув.форумчане!
    Пришел за советом, в php пока не особо шарю, посему хотелось бы услышать совета и ответы на пару вопросов, собственно сабж:

    есть скрипт, парсит со всех сраниц сайта h1 и title:
    Код (Text):
    1. <meta charset="utf-8" />
    2. <?
    3. require_once '../../config.php';
    4. include('simple_html_dom.php');
    5.  
    6. class Page{
    7.     var $url, $h1, $title, $content, $content_type, $urls;
    8.    
    9.     #Подсасываем страницу и данные о ней
    10.     function getPage(){
    11.         $url = $this->url;
    12.         $uagent = "Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.9.0.8) Gecko/2009032609 Firefox/3.0.8";
    13.         $ch = curl_init( $url );
    14.  
    15.         curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // возвращает веб-страницу
    16.         curl_setopt($ch, CURLOPT_HEADER, 0); // не возвращает заголовки
    17.         curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); // переходит по редиректам
    18.         curl_setopt($ch, CURLOPT_ENCODING, ""); // обрабатывает все кодировки
    19.         curl_setopt($ch, CURLOPT_USERAGENT, $uagent); // useragent
    20.         curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 240); // таймаут соединения
    21.         curl_setopt($ch, CURLOPT_TIMEOUT, 240); // таймаут ответа
    22.         curl_setopt($ch, CURLOPT_MAXREDIRS, 5); // останавливаться после 10-ого редиректа
    23.  
    24.         $this->content = curl_exec( $ch );
    25.         $err = curl_errno( $ch );
    26.         $errmsg = curl_error( $ch );
    27.         $header = curl_getinfo( $ch );
    28.         curl_close( $ch );
    29.        
    30.         if($err){
    31.             echo "Ошибка! <br />Код ошибки: ".$err."<br />Сообщение ошибки: ".$errmsg;
    32.             return false;
    33.         }
    34.        
    35.         //определяем тип страницы     
    36.         $pagetype = $header['content_type'];
    37.         $pagetype = explode(";", $pagetype);
    38.         $this->content_type = $pagetype[0];
    39.        
    40.         return true;
    41.     }
    42.    
    43.     #Обработчик страницы
    44.     function handlerPage(){
    45.         $html = str_get_html($this->content);
    46.        
    47.         if($html->innertext!=''){
    48.             #Высасываем h1
    49.             foreach($html->find('h1') as $h1){
    50.                 $this->h1 .= $h1->innertext;
    51.             }
    52.             unset($h1);
    53.            
    54.             #Высасываем title
    55.             foreach($html->find('title') as $title){
    56.                 $this->title = $title->innertext;
    57.             }
    58.             unset($title);
    59.            
    60.             #высасываем все ссылки и помещаем их в массив
    61.             foreach($html->find('a') as $a){
    62.                 $this->urls .= $a->href."|";
    63.             }
    64.             unset($a);
    65.             $this->urls = explode("|",$this->urls);
    66.         }
    67.         else{
    68.             unset($html);
    69.             return false;
    70.         }
    71.        
    72.         $html->clear();
    73.         unset($html);
    74.        
    75.         $res = mysql_query("SELECT * FROM site WHERE url = '".$this->url."'") or die(mysql_error());
    76.         if(!mysql_num_rows($res)){
    77.             mysql_query("INSERT INTO site VALUES('".$this->url."','".$this->title."','".$this->h1."','1')") or die(mysql_error());
    78.         }
    79.         else
    80.             mysql_query("UPDATE site SET `title`='".$this->title."', `h1`='".$this->h1."', `parsed`='1' WHERE `url`='".$this->url."'") or die(mysql_error());
    81.            
    82.        
    83.         return true;
    84.     }
    85.    
    86.     #Обработчик урлов со страницы
    87.     function handlerUrls($domain){
    88.         foreach($this->urls as $key => $url){
    89.             if($url == "" || $url == "#"){
    90.                 unset($this->urls[$key]);
    91.             }
    92.             if($url == "/")
    93.                 $url = $this->urls[$key] = "http://www.".$domain;
    94.             else if(substr($url, 0, 1) == "/")
    95.                 $url = $this->urls[$key] = "http://www.".$domain.$url;
    96.             else if(substr($url, 0, 1) != "/" && !substr_count($url, "http://") && !substr_count($url, "www."))
    97.                 $url = $this->urls[$key] = "http://www.".$domain."/".$url;
    98.            
    99.             if(!(strpos($url, ".htm") || strpos($url, ".doc") || strpos($url, ".xls") || strpos($url, ".ppt") || strpos($url, ".psd") ||
    100.             strpos($url, ".pdf") || strpos($url, ".ai") || strpos($url, ".jpg") || strpos($url, ".png") || strpos($url, ".tif") || strpos($url, ".gif")) && substr($url, -1) != "/")
    101.                 $url = $this->urls[$key] = $url."/";
    102.            
    103.             if(strpos($url, "javascript") || strpos($url, "mailto") || strpos($url, "#") || strpos($url, ".doc") || strpos($url, ".xls") || strpos($url, ".ppt") || strpos($url, ".psd") ||
    104.             strpos($url, ".pdf") || strpos($url, ".ai") || strpos($url, ".jpg") || strpos($url, ".png") || strpos($url, ".tif") || strpos($url, ".gif"))
    105.                 unset($this->urls[$key]);
    106.             else if(!isInternal($domain, $url))
    107.                 unset($this->urls[$key]);
    108.         }
    109.         unset($url);
    110.        
    111.         foreach($this->urls as $url){
    112.             $res = mysql_query("SELECT * FROM site WHERE url = '".$url."'") or die(mysql_error());
    113.             if(!mysql_num_rows($res)){
    114.                 mysql_query("INSERT INTO site VALUES('".$url."','','','0')") or die(mysql_error());
    115.             }
    116.         }
    117.         unset($url);
    118.         return true;
    119.     }
    120.    
    121.     #Очистка параметров объекта
    122.     function clear(){
    123.         unset($this->url);
    124.         unset($this->h1);
    125.         unset($this->title);
    126.         unset($this->content);
    127.         unset($this->content_type);
    128.         unset($this->urls);
    129.     }
    130. }
    131.  
    132. function isInternal($domain, $url){
    133.    
    134.     if(substr($url, 0, 1) == "/" || $url == "/")
    135.         return true;
    136.     else if($domain == getDomain($url))
    137.         return true;
    138.     else{
    139.         if(!parse_url($url))
    140.             return true;
    141.         return false;
    142.     }
    143. }
    144.  
    145. function getDomain($url){
    146.     $domain = parse_url($url);
    147.     if($domain['scheme'] == "http")
    148.         $domain = $domain['host'];
    149.     else
    150.         return false;
    151.    
    152.     if(substr($domain, 0, 4) == "www."){
    153.         $domain = substr($domain, 4);
    154.     }
    155.    
    156.     return $domain;
    157. }
    158.  
    159. function nextUrl(){
    160.     $res = mysql_query("SELECT * FROM site WHERE parsed = '0'") or die(mysql_error());
    161.     if(mysql_num_rows($res)){
    162.         $row = mysql_fetch_array($res);
    163.         parse($row['url']);
    164.         nextUrl();
    165.     }
    166. }
    167.  
    168. function parse($url){
    169.    
    170.     echo "<br /><br />ТЕКУЩИЙ URL: ".$url."<br />";
    171.     $page = new Page;
    172.  
    173.     $page->url = $url;
    174.     if($page->getPage()){
    175.         if($page->content_type == "text/html"){
    176.             if(!$page->handlerPage()){
    177.                 mysql_query("UPDATE site SET `parsed`='1' WHERE `url`='".$url."'") or die(mysql_error());
    178.                 return false;
    179.             }
    180.  
    181.             global $domain;
    182.             $page->handlerUrls($domain);
    183.         }
    184.     }
    185.     else{
    186.         mysql_query("UPDATE site SET `parsed`='1' WHERE `url`='".$url."'") or die(mysql_error());
    187.     }
    188.    
    189.     $page->clear(); #ЧИСТОТА - ЗАЛОГ ЗДОРОВЬЯ!! :D
    190.     unset($page);
    191.    
    192.     nextUrl();
    193. }
    194.  
    195.  
    196. mysql_connect($dbHost,$dbUser,$dbPass) OR DIE("Не могу создать соединение<br />");
    197. mysql_select_db($dbName) or die(mysql_error());
    198.  
    199. $domain = getDomain("http://www.csat.ru/");
    200.  
    201. parse("http://www.csat.ru/");
    202. echo "DONE!";
    203. mysql_close();
    204. ?>
    Скрипт в общем-то прекрасно работает но, есть одно но!

    На хостинге, куда это дело планируется водрузить, ограничение на выполнение скрипта в 30с. как в общем-то почти везде, и это дело никак не изменить. А парсинг всего сайта, с локалки, занимает порой минут 15-20.
    Закономерный вопрос: каким образом это ограничение можно преодолеть?
    И еще вопрос, можно ли каким-либо образом это дело распараллелить?

    Ну и буду рад услышать комментарии по поводу самого кода.

    Заранее благодарен!
     
  2. Fell-x27

    Fell-x27 Суперстар
    Команда форума Модератор

    С нами с:
    25 июл 2013
    Сообщения:
    12.156
    Симпатии:
    1.771
    Адрес:
    :сердА
    Если хостер позволяет менять локально настройки PHP на акке, то да, можно, разумеется. Через ini_set() или, у моего хостера, к примеру, разрешено запиливать локальные ini-файлы.

    Теоретически, да.

    Вот еще какая-то приблуда для трединга: http://pthreads.org/
     
  3. xobotyi

    xobotyi Новичок

    С нами с:
    18 авг 2013
    Сообщения:
    10
    Симпатии:
    0
    если б было можно через локальные ини я бы сделал уже))) но вот проблема в том что менять нельзя, вообще никак, вот такой дибильный хостер у заказчика. Как быть в этом случае?
     
  4. hukmaos

    hukmaos Новичок

    С нами с:
    11 авг 2013
    Сообщения:
    4
    Симпатии:
    0
    не совсем хорошее решение но - где-то хранить смещение на котором закончил скрипт, и при повторном запуске начинать от туда.
    Либо сделать парсер более оптимальным по времени выполнения.
     
  5. Fell-x27

    Fell-x27 Суперстар
    Команда форума Модератор

    С нами с:
    25 июл 2013
    Сообщения:
    12.156
    Симпатии:
    1.771
    Адрес:
    :сердА
    если через ini_set() не получится время сменить, то стучаться в поддержку хостера и выносить мозг. Вы попробуйте все же в самом начале поставить
    Код (Text):
    1. ini_set('max_execution_time', 3600);
    Если сработает, скрипт будет жить час.
     
  6. xobotyi

    xobotyi Новичок

    С нами с:
    18 авг 2013
    Сообщения:
    10
    Симпатии:
    0
    не работает, ну, значит будем выносить мозг
    у меня скрипт не придирчив к месту окончания, работает он с базой и после парсинга одного урла стучится в базу за следующим неотпарсенным урлом.

    Есть у меня идея - засекать время начала парса, брать время жизни скрипта и если до окончания времени работы остается n секунд, - перезапускать скрипт, но вот самый главный вопрос - как его перезапускать?
     
  7. Avivar

    Avivar Новичок

    С нами с:
    6 авг 2013
    Сообщения:
    15
    Симпатии:
    0
    Может редиректом?
     
  8. Зверь

    Зверь Активный пользователь

    С нами с:
    2 ноя 2010
    Сообщения:
    80
    Симпатии:
    0
    Адрес:
    Екатеринбург
    делаешь для sql запроса limit, засечку времени делаешь и сохраняешь между запросами в сессию, ну а перезагрузку страницы можно делать с header refresh, в общем так, когда-то делал такую штуку, если найду кину алгоритм
     
  9. Avivar

    Avivar Новичок

    С нами с:
    6 авг 2013
    Сообщения:
    15
    Симпатии:
    0
    Можно еще при первом заходе сделать в переменную записать время $time = time(), ну а потом при парсинге сравнивать время разницу(time() - $time) с ограничением хостинга(при условии, что вы добавите величину n упомянутую вами выше).
     
  10. Fell-x27

    Fell-x27 Суперстар
    Команда форума Модератор

    С нами с:
    25 июл 2013
    Сообщения:
    12.156
    Симпатии:
    1.771
    Адрес:
    :сердА
    А можно сказать заказчику, чтобы не был жлобом. А то "хочу грузовик, но чтоб топливо жрал как мопед!"

    Требует ресурсоемкую приблуду? Пусть выделяет ресурсы.
     
  11. Your

    Your Старожил

    С нами с:
    2 июл 2011
    Сообщения:
    4.074
    Симпатии:
    7
    И я решил написать-это:
    sleeper.php
    Код (PHP):
    1. <?php
    2. function sleeper() {
    3.     sleep(2);
    4.     $file='sleeper.txt';
    5.     $content=file_get_contents($file);
    6.     file_put_contents($file,is_file($file) && $content?intval($content+1):1);
    7.     sleep(2);
    8.     sleeper();
    9. }
    10. sleeper();
    11. ?>
    LOL!
     
  12. bkm

    bkm Активный пользователь

    С нами с:
    22 окт 2009
    Сообщения:
    299
    Симпатии:
    0
    - ого, так можно что ли? беру на заметку.

    Добавлено спустя 2 минуты 45 секунд:
    как вариант поставить денвер (или что там у вас) на своем компе и запустить скрипт без лимита.
    можно крон повесить.
     
  13. Fell-x27

    Fell-x27 Суперстар
    Команда форума Модератор

    С нами с:
    25 июл 2013
    Сообщения:
    12.156
    Симпатии:
    1.771
    Адрес:
    :сердА
    Да здравствует вечная рекурсия? О_о
    Кстати, какая глубина у PHP максимальная?
     
  14. igordata

    igordata Суперстар
    Команда форума Модератор

    С нами с:
    18 мар 2010
    Сообщения:
    32.408
    Симпатии:
    1.768
    небольшая.

    а зачем так делать-то? =) если у нас есть ответ в $content - ето раз

    два, в пхп такая форма всегда отработает корректно:
    file_put_contents($file, (int)$content+1);

    и неебет, есть он или нет он, есть в нем или нет в нем и тд
     
  15. Your

    Your Старожил

    С нами с:
    2 июл 2011
    Сообщения:
    4.074
    Симпатии:
    7
    http://php.ru/manual/info.configuration.html

    igordata,
    Ну-это если фанарно, счетчик (и то который только с единицы начнется, а мож я сначала ноль хочу записать), а вот если, что -то большее.
    Без этой проверки не обойтись, по этому, лучше проверять.
     
  16. igordata

    igordata Суперстар
    Команда форума Модератор

    С нами с:
    18 мар 2010
    Сообщения:
    32.408
    Симпатии:
    1.768
    я к тому что можно проверить контентс на фалс без проверки файла на существование.
     
  17. Your

    Your Старожил

    С нами с:
    2 июл 2011
    Сообщения:
    4.074
    Симпатии:
    7
    Можно если без чего-то большего.