2010-02-22 1 views
58

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

Кто-нибудь сделал это с PHP? В качестве ответа было бы достаточно общих рекомендаций и исправлений.

ответ

74

Мех. Не parse HTML with regexes.

Вот версия DOM вдохновленный Тату:

<?php 
function crawl_page($url, $depth = 5) 
{ 
    static $seen = array(); 
    if (isset($seen[$url]) || $depth === 0) { 
     return; 
    } 

    $seen[$url] = true; 

    $dom = new DOMDocument('1.0'); 
    @$dom->loadHTMLFile($url); 

    $anchors = $dom->getElementsByTagName('a'); 
    foreach ($anchors as $element) { 
     $href = $element->getAttribute('href'); 
     if (0 !== strpos($href, 'http')) { 
      $path = '/' . ltrim($href, '/'); 
      if (extension_loaded('http')) { 
       $href = http_build_url($url, array('path' => $path)); 
      } else { 
       $parts = parse_url($url); 
       $href = $parts['scheme'] . '://'; 
       if (isset($parts['user']) && isset($parts['pass'])) { 
        $href .= $parts['user'] . ':' . $parts['pass'] . '@'; 
       } 
       $href .= $parts['host']; 
       if (isset($parts['port'])) { 
        $href .= ':' . $parts['port']; 
       } 
       $href .= dirname($parts['path'], 1).$path; 
      } 
     } 
     crawl_page($href, $depth - 1); 
    } 
    echo "URL:",$url,PHP_EOL,"CONTENT:",PHP_EOL,$dom->saveHTML(),PHP_EOL,PHP_EOL; 
} 
crawl_page("http://hobodave.com", 2); 

Edit: я исправлены некоторые ошибки из версии Тату (работает с относительными URL, в настоящее время).

Редактировать: Я добавил новый бит функциональности, который не позволяет ему дважды следовать одному URL.

Edit: вторя вывод STDOUT теперь так вы можете перенаправить его на любой файл, который вы хотите

Edit: Исправлена ​​ошибка отметил Джордж в своем ответе. Относительные URL-адреса больше не будут добавляться к концу URL-адреса, но перезаписывают его. Спасибо Джорджу за это. Обратите внимание, что ответ Джорджа не учитывает ни одного из: https, user, pass или port. Если у вас загружено расширение PECL , это довольно просто сделать, используя http_build_url. В противном случае мне придется вручную склеивать, используя parse_url. Еще раз спасибо Джордж.

+0

Могу ли я рекомендовать использовать завиток для извлечения страницы, а затем манипулировать/проходить через библиотеку DOM. Если вы делаете это часто, завиток намного лучший вариант imo. –

+1

@Ben: Почему это лучше? – hobodave

11

Заканчивать PHP Crawler

http://sourceforge.net/projects/php-crawler/

Смотрите, если это помогает.

+0

Предлагая ссылки ссылки лучше делать как комментарии. – mickmackusa

+0

Хотя эта ссылка может ответить на вопрос, лучше включить здесь основные части ответа и предоставить ссылку для справки. Ответные ссылки могут стать недействительными, если связанная страница изменится. - [Из обзора] (/ review/low-quality-posts/18853597) – btl

7

В этом простейшей форме:

function crawl_page($url, $depth = 5) { 
    if($depth > 0) { 
     $html = file_get_contents($url); 

     preg_match_all('~<a.*?href="(.*?)".*?>~', $html, $matches); 

     foreach($matches[1] as $newurl) { 
      crawl_page($newurl, $depth - 1); 
     } 

     file_put_contents('results.txt', $newurl."\n\n".$html."\n\n", FILE_APPEND); 
    } 
} 

crawl_page('http://www.domain.com/index.php', 5); 

Эта функция будет получать содержание от страницы, а затем ползти все найденные ссылки и сохранить содержимое 'results.txt. Функции принимают второй параметр, depth, который определяет, как долго должны соблюдаться ссылки. Пропустите 1, если вы хотите разобрать только ссылки с данной страницы.

+0

-1: Мех для использования регулярных выражений. Не работает с относительными URL-адресами. Также использует неправильный URL-адрес в файле__процессы(). – hobodave

+0

@hobodave, все верно. –

+0

Что это должно делать? Я прополз по веб-сайту, и это дало мне кучу дерьма. Похоже, что он получает контент из другого места, но теперь с моего сайта. – erdomester

5

Зачем использовать PHP для этого, когда вы можете использовать wget, например.

wget -r -l 1 http://www.example.com 

Для как разобрать содержимое, см Best Methods to parse HTML и использовать функцию поиска для examples. Как разобрать HTML, ответ был получен несколько раз.

+0

Некоторые конкретные поля должны быть проанализированы и извлечены. Мне нужно будет написать код. –

+0

@Crimson, это требование, которое вы должны отметить в вопросе, затем;) – Gordon

+7

@Gordon: «Как создать простой сканер в PHP?» :-P – hobodave

2

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

скребковые ссылки: http://www.phpro.org/examples/Get-Links-With-DOM.html

самосвальные результаты в файл: http://www.tizag.com/phpT/filewrite.php

+0

phpro.org кажется мертвым – Gordon

2

Hobodave Вы были очень близки. Единственное, что я изменил, - это выражение if, которое проверяет, начинает ли атрибут href найденного тега привязки «http».Вместо того, чтобы просто добавлять переменную $ url, которая содержала бы пропущенную страницу, вы должны сначала разбить ее на хост, который можно выполнить с помощью функции parse_url php.

<?php 
function crawl_page($url, $depth = 5) 
{ 
    static $seen = array(); 
    if (isset($seen[$url]) || $depth === 0) { 
    return; 
    } 

    $seen[$url] = true; 

    $dom = new DOMDocument('1.0'); 
    @$dom->loadHTMLFile($url); 

    $anchors = $dom->getElementsByTagName('a'); 
    foreach ($anchors as $element) { 
    $href = $element->getAttribute('href'); 
    if (0 !== strpos($href, 'http')) { 
     /* this is where I changed hobodave's code */ 
     $host = "http://".parse_url($url,PHP_URL_HOST); 
     $href = $host. '/' . ltrim($href, '/'); 
    } 
    crawl_page($href, $depth - 1); 
    } 

    echo "New Page:<br /> "; 
    echo "URL:",$url,PHP_EOL,"<br />","CONTENT:",PHP_EOL,$dom->saveHTML(),PHP_EOL,PHP_EOL," <br /><br />"; 
} 

crawl_page("http://hobodave.com/", 5); 
?> 
+2

Спасибо, что указал на мою ошибку Джорджа! Ваше решение пренебрегает обработкой https, user, pass и port. Я обновил свой ответ, чтобы найти найденную вами ошибку, а также ошибки, внесенные вами. Еще раз спасибо! – hobodave

5

С некоторыми небольшими изменениями hobodave's кода, вот codesnippet вы можете использовать для обхода страниц. Для этого необходимо, чтобы расширение curl было включено на вашем сервере.

<?php 
//set_time_limit (0); 
function crawl_page($url, $depth = 5){ 
$seen = array(); 
if(($depth == 0) or (in_array($url, $seen))){ 
    return; 
} 
$ch = curl_init(); 
curl_setopt($ch, CURLOPT_URL, $url); 
curl_setopt($ch, CURLOPT_TIMEOUT, 30); 
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1); 
$result = curl_exec ($ch); 
curl_close ($ch); 
if($result){ 
    $stripped_file = strip_tags($result, "<a>"); 
    preg_match_all("/<a[\s]+[^>]*?href[\s]?=[\s\"\']+"."(.*?)[\"\']+.*?>"."([^<]+|.*?)?<\/a>/", $stripped_file, $matches, PREG_SET_ORDER); 
    foreach($matches as $match){ 
     $href = $match[1]; 
      if (0 !== strpos($href, 'http')) { 
       $path = '/' . ltrim($href, '/'); 
       if (extension_loaded('http')) { 
        $href = http_build_url($href , array('path' => $path)); 
       } else { 
        $parts = parse_url($href); 
        $href = $parts['scheme'] . '://'; 
        if (isset($parts['user']) && isset($parts['pass'])) { 
         $href .= $parts['user'] . ':' . $parts['pass'] . '@'; 
        } 
        $href .= $parts['host']; 
        if (isset($parts['port'])) { 
         $href .= ':' . $parts['port']; 
        } 
        $href .= $path; 
       } 
      } 
      crawl_page($href, $depth - 1); 
     } 
} 
echo "Crawled {$href}"; 
} 
crawl_page("http://www.sitename.com/",3); 
?> 

Я объяснил этот учебник в этом crawler script tutorial

14

Здесь моя реализация основана на приведенном выше примере/ответе.

  1. Это класс на основе
  2. использует Curl
  3. поддержка HTTP Auth
  4. Пропустить Url не принадлежащие к базовой области
  5. заголовок Return код ответа HTTP для каждой страницы
  6. Возврат времени для каждого страница

КРАСНЫЙ КЛАСС:

class crawler 
{ 
    protected $_url; 
    protected $_depth; 
    protected $_host; 
    protected $_useHttpAuth = false; 
    protected $_user; 
    protected $_pass; 
    protected $_seen = array(); 
    protected $_filter = array(); 

    public function __construct($url, $depth = 5) 
    { 
     $this->_url = $url; 
     $this->_depth = $depth; 
     $parse = parse_url($url); 
     $this->_host = $parse['host']; 
    } 

    protected function _processAnchors($content, $url, $depth) 
    { 
     $dom = new DOMDocument('1.0'); 
     @$dom->loadHTML($content); 
     $anchors = $dom->getElementsByTagName('a'); 

     foreach ($anchors as $element) { 
      $href = $element->getAttribute('href'); 
      if (0 !== strpos($href, 'http')) { 
       $path = '/' . ltrim($href, '/'); 
       if (extension_loaded('http')) { 
        $href = http_build_url($url, array('path' => $path)); 
       } else { 
        $parts = parse_url($url); 
        $href = $parts['scheme'] . '://'; 
        if (isset($parts['user']) && isset($parts['pass'])) { 
         $href .= $parts['user'] . ':' . $parts['pass'] . '@'; 
        } 
        $href .= $parts['host']; 
        if (isset($parts['port'])) { 
         $href .= ':' . $parts['port']; 
        } 
        $href .= $path; 
       } 
      } 
      // Crawl only link that belongs to the start domain 
      $this->crawl_page($href, $depth - 1); 
     } 
    } 

    protected function _getContent($url) 
    { 
     $handle = curl_init($url); 
     if ($this->_useHttpAuth) { 
      curl_setopt($handle, CURLOPT_HTTPAUTH, CURLAUTH_ANY); 
      curl_setopt($handle, CURLOPT_USERPWD, $this->_user . ":" . $this->_pass); 
     } 
     // follows 302 redirect, creates problem wiht authentication 
//  curl_setopt($handle, CURLOPT_FOLLOWLOCATION, TRUE); 
     // return the content 
     curl_setopt($handle, CURLOPT_RETURNTRANSFER, TRUE); 

     /* Get the HTML or whatever is linked in $url. */ 
     $response = curl_exec($handle); 
     // response total time 
     $time = curl_getinfo($handle, CURLINFO_TOTAL_TIME); 
     /* Check for 404 (file not found). */ 
     $httpCode = curl_getinfo($handle, CURLINFO_HTTP_CODE); 

     curl_close($handle); 
     return array($response, $httpCode, $time); 
    } 

    protected function _printResult($url, $depth, $httpcode, $time) 
    { 
     ob_end_flush(); 
     $currentDepth = $this->_depth - $depth; 
     $count = count($this->_seen); 
     echo "N::$count,CODE::$httpcode,TIME::$time,DEPTH::$currentDepth URL::$url <br>"; 
     ob_start(); 
     flush(); 
    } 

    protected function isValid($url, $depth) 
    { 
     if (strpos($url, $this->_host) === false 
      || $depth === 0 
      || isset($this->_seen[$url]) 
     ) { 
      return false; 
     } 
     foreach ($this->_filter as $excludePath) { 
      if (strpos($url, $excludePath) !== false) { 
       return false; 
      } 
     } 
     return true; 
    } 

    public function crawl_page($url, $depth) 
    { 
     if (!$this->isValid($url, $depth)) { 
      return; 
     } 
     // add to the seen URL 
     $this->_seen[$url] = true; 
     // get Content and Return Code 
     list($content, $httpcode, $time) = $this->_getContent($url); 
     // print Result for current Page 
     $this->_printResult($url, $depth, $httpcode, $time); 
     // process subPages 
     $this->_processAnchors($content, $url, $depth); 
    } 

    public function setHttpAuth($user, $pass) 
    { 
     $this->_useHttpAuth = true; 
     $this->_user = $user; 
     $this->_pass = $pass; 
    } 

    public function addFilterPath($path) 
    { 
     $this->_filter[] = $path; 
    } 

    public function run() 
    { 
     $this->crawl_page($this->_url, $this->_depth); 
    } 
} 

ПРИМЕНЕНИЕ:

// USAGE 
$startURL = 'http://YOUR_URL/'; 
$depth = 6; 
$username = 'YOURUSER'; 
$password = 'YOURPASS'; 
$crawler = new crawler($startURL, $depth); 
$crawler->setHttpAuth($username, $password); 
// Exclude path with the following structure to be processed 
$crawler->addFilterPath('customer/account/login/referer'); 
$crawler->run(); 
0

Я использовал код @ hobodave в, с этим немного подправить, чтобы предотвратить повторное ползет все осколочные варианты одного и того же URL:

<?php 
function crawl_page($url, $depth = 5) 
{ 
    $parts = parse_url($url); 
    if(array_key_exists('fragment', $parts)){ 
    unset($parts['fragment']); 
    $url = http_build_url($parts); 
    } 

    static $seen = array(); 
    ... 

Тогда вы можете также опускаем линия $parts = parse_url($url); в цикле for.

1

Вы можете попробовать это может быть помочь вам

$search_string = 'american golf News: Fowler beats stellar field in Abu Dhabi'; 
$html = file_get_contents(url of the site); 
$dom = new DOMDocument; 
$titalDom = new DOMDocument; 
$tmpTitalDom = new DOMDocument; 
libxml_use_internal_errors(true); 
@$dom->loadHTML($html); 
libxml_use_internal_errors(false); 
$xpath = new DOMXPath($dom); 
$videos = $xpath->query('//div[@class="primary-content"]'); 
foreach ($videos as $key => $video) { 
$newdomaindom = new DOMDocument;  
$newnode = $newdomaindom->importNode($video, true); 
$newdomaindom->appendChild($newnode); 
@$titalDom->loadHTML($newdomaindom->saveHTML()); 
$xpath1 = new DOMXPath($titalDom); 
$titles = $xpath1->query('//div[@class="listingcontainer"]/div[@class="list"]'); 
if(strcmp(preg_replace('!\s+!',' ', $titles->item(0)->nodeValue),$search_string)){  
    $tmpNode = $tmpTitalDom->importNode($video, true); 
    $tmpTitalDom->appendChild($tmpNode); 
    break; 
} 
} 
echo $tmpTitalDom->saveHTML(); 
0

я придумал следующий код паука. Я приспособил это немного из следующих действий: PHP - Is the there a safe way to perform deep recursion? , кажется, достаточно быстро ....

<?php 
function spider($base_url , $search_urls=array()) { 
    $queue[] = $base_url; 
    $done   = array(); 
    $found_urls  = array(); 
    while($queue) { 
      $link = array_shift($queue); 
      if(!is_array($link)) { 
       $done[] = $link; 
       foreach($search_urls as $s) { if (strstr($link , $s)) { $found_urls[] = $link; } } 
       if(empty($search_urls)) { $found_urls[] = $link; } 
       if(!empty($link)) { 
echo 'LINK:::'.$link; 
         $content = file_get_contents($link); 
//echo 'P:::'.$content; 
        preg_match_all('~<a.*?href="(.*?)".*?>~', $content, $sublink); 
        if (!in_array($sublink , $done) && !in_array($sublink , $queue) ) { 
          $queue[] = $sublink; 
        } 
       } 
      } else { 
        $result=array(); 
        $return = array(); 
        // flatten multi dimensional array of URLs to one dimensional. 
        while(count($link)) { 
         $value = array_shift($link); 
         if(is_array($value)) 
          foreach($value as $sub) 
           $link[] = $sub; 
         else 
           $return[] = $value; 
        } 
        // now loop over one dimensional array. 
        foreach($return as $link) { 
           // echo 'L::'.$link; 
           // url may be in form <a href.. so extract what's in the href bit. 
           preg_match_all('/<a[^>]+href=([\'"])(?<href>.+?)\1[^>]*>/i', $link, $result); 
           if (isset($result['href'][0])) { $link = $result['href'][0]; } 
           // add the new URL to the queue. 
           if((!strstr($link , "http")) && (!in_array($base_url.$link , $done)) && (!in_array($base_url.$link , $queue))) { 
            $queue[]=$base_url.$link; 
           } else { 
            if ((strstr($link , $base_url )) && (!in_array($base_url.$link , $done)) && (!in_array($base_url.$link , $queue))) { 
             $queue[] = $link; 
            } 
           } 
         } 
      } 
    } 


    return $found_urls; 
}  


    $base_url  = 'https://www.houseofcheese.co.uk/'; 
    $search_urls = array( $base_url.'acatalog/'); 
    $done = spider($base_url , $search_urls ); 

    // 
    // RESULT 
    // 
    // 
    echo '<br /><br />'; 
    echo 'RESULT:::'; 
    foreach( $done as $r) { 
     echo 'URL:::'.$r.'<br />'; 
    } 
0

Его стоит помнить, что при обходе внешних ссылок (я ценю ОП относится к пользователям собственной странице) вам должен знать о файле robots.txt. Я нашел следующее, которое, мы надеемся, поможет http://www.the-art-of-web.com/php/parse-robots/.

0

Я создал небольшой класс для захвата данных из предоставленного url, а затем извлеките элементы html по вашему выбору. Класс использует CURL и DOMDocument.

класс PHP:

class crawler { 


    public static $timeout = 2; 
    public static $agent = 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)'; 


    public static function http_request($url) { 
     $ch = curl_init(); 
     curl_setopt($ch, CURLOPT_URL,   $url); 
     curl_setopt($ch, CURLOPT_USERAGENT,  self::$agent); 
     curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, self::$timeout); 
     curl_setopt($ch, CURLOPT_TIMEOUT,  self::$timeout); 
     curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 
     $response = curl_exec($ch); 
     curl_close($ch); 
     return $response; 
    } 


    public static function strip_whitespace($data) { 
     $data = preg_replace('/\s+/', ' ', $data); 
     return trim($data); 
    } 


    public static function extract_elements($tag, $data) { 
     $response = array(); 
     $dom  = new DOMDocument; 
     @$dom->loadHTML($data); 
     foreach ($dom->getElementsByTagName($tag) as $index => $element) { 
     $response[$index]['text'] = self::strip_whitespace($element->nodeValue); 
     foreach ($element->attributes as $attribute) { 
      $response[$index]['attributes'][strtolower($attribute->nodeName)] = self::strip_whitespace($attribute->nodeValue); 
     } 
     } 
     return $response; 
    } 


} 

пример использования:

$data = crawler::http_request('https://stackoverflow.com/questions/2313107/how-do-i-make-a-simple-crawler-in-php'); 
$links = crawler::extract_elements('a', $data); 
if (count($links) > 0) { 
    file_put_contents('links.json', json_encode($links, JSON_PRETTY_PRINT)); 
} 

Пример ответа:

[ 
    { 
     "text": "Stack Overflow", 
     "attributes": { 
      "href": "https:\/\/stackoverflow.com", 
      "class": "-logo js-gps-track", 
      "data-gps-track": "top_nav.click({is_current:false, location:2, destination:8})" 
     } 
    }, 
    { 
     "text": "Questions", 
     "attributes": { 
      "id": "nav-questions", 
      "href": "\/questions", 
      "class": "-link js-gps-track", 
      "data-gps-track": "top_nav.click({is_current:true, location:2, destination:1})" 
     } 
    }, 
    { 
     "text": "Developer Jobs", 
     "attributes": { 
      "id": "nav-jobs", 
      "href": "\/jobs?med=site-ui&ref=jobs-tab", 
      "class": "-link js-gps-track", 
      "data-gps-track": "top_nav.click({is_current:false, location:2, destination:6})" 
     } 
    } 
] 
0

Спасибо @hobodave.

Однако я обнаружил две недостатки в вашем коде. Ваш синтаксический анализ исходного URL-адреса, чтобы получить сегмент «хозяин», останавливается при первом одиночном косе. Это предполагает, что все относительные ссылки начинаются в корневом каталоге. Это только иногда.

original url : http://example.com/game/index.html 
href in <a> tag: highscore.html 
author's intent: http://example.com/game/highscore.html <-200-> 
crawler result : http://example.com/highscore.html  <-404-> 

исправить это, разбив на последнем одной косой черты не первый

второй неродственного ошибка, что $depth не реально отслеживать глубину рекурсии, он отслеживает широту первого уровня рекурсии.

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