Como faço um simples rastejante em PHP?

Tenho uma página na internet com vários links. Eu quero escrever um script que despejaria todos os dados contidos nesses links em um arquivo local.

Alguém fez isso com o PHP? Orientações gerais e gotchas seriam suficientes como resposta.

Author: Kara, 2010-02-22

15 answers

Meh. Não analisem o HTML com as expressões regulares .

Aqui está uma versão DOM inspirada em Tatu.
<?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: corrigi alguns erros da versão do Tatu (funciona agora com URLs relativos).

Edit: adicionei um novo bit de funcionalidade que o impede de seguir o mesmo URL duas vezes.

Editar: ecoando o resultado para o 'STDOUT' agora para que o possa redireccionar para o ficheiro que quiser

Editar: corrigiu um erro apontado pelo George na sua resposta. Os urls relativos não irão adicionar mais ao fim do Caminho do url, mas sobrepô-lo. Graças ao George por isto. Note que a resposta de George não conta para nenhum dos: https, usuário, passe ou porta. Se tiver a extensão http PECL carregada, isto é simplesmente feito usando http_ build_url . Caso contrário, tenho de colar manualmente usando parse_url. Mais uma vez obrigado, George.

 83
Author: hobodave, 2017-12-13 17:19:25

Aqui a minha implementação com base no exemplo/resposta acima.

  1. é baseado em classe
  2. usa o Curl
  3. suporta a autenticação HTTP
  4. ignorar o Url que não pertence ao domínio de base
  5. Devolve o código de resposta do cabeçalho Http para cada página
  6. hora de retorno para cada página

CLASSE CRAWL:

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);
    }
}

Utilização:

// 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();
 15
Author: WonderLand, 2014-06-11 18:38:15

Vê o Rastejador de PHP

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

Vê se ajuda.
 11
Author: GeekTantra, 2010-02-22 18:26:35

Na sua forma mais simples:

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);

Essa função irá obter o conteúdo de uma página, em seguida, rastejar todos os links encontrados e salvar o conteúdo para 'resultados.txt. As funções aceitam um segundo parâmetro, profundidade, que define quanto tempo as ligações devem ser seguidas. Passe 1 para lá se quiser analisar apenas links da página indicada.

 9
Author: Tatu Ulmanen, 2010-02-22 18:29:07

Com algumas pequenas alterações ao códigodo hobodave , Aqui está um codesnippet que você pode usar para rastejar páginas. Isto precisa que a extensão curl seja ativada no seu servidor.

<?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);
?>

Eu expliquei este tutorial neste tutorial script de crawler

 5
Author: Team Webgalli, 2017-05-23 12:10:54

Porque usar o PHP para isto, quando pode usar o wget , por exemplo

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

Para Como processar o conteúdo, Veja Os melhores métodos para processar o HTML e use a função de pesquisa para exemplos . Como processar HTML foi respondido várias vezes antes.

 4
Author: Gordon, 2017-05-23 11:33:26
Hobodave, eram muito chegados. A única coisa que eu mudei é dentro da declaração if que verifica para ver se o atributo href da marca de âncora encontrada começa com 'http'. Em vez de simplesmente adicionar a variável $ url que iria conter a página que foi passada em você deve primeiro removê-la para a máquina que pode ser feito usando a função 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);
?>
 3
Author: George, 2011-05-03 23:50:47

Como mencionado, existem rastreador quadros prontos para personalizar lá fora, mas se o que você está fazendo é tão simples como você mencionou, você poderia fazê-lo a partir do zero facilmente.

A raspar as ligações: http://www.phpro.org/examples/Get-Links-With-DOM.html

Resultados de Dumping para um ficheiro: http://www.tizag.com/phpT/filewrite.php

 2
Author: Jens Roland, 2010-02-22 18:39:45
A questão é como obter o código fonte do ajax? isto não é rastejado, por exemplo, como rastejar as imagens em um link como este? http://www.tiendeo.nl/Catalogi/amsterdam/16558&subori=web_sliders&buscar=Boni&sw=1366
 1
Author: user1392021, 2014-03-05 10:20:05
Pode tentar. pode ajudar-te.
$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();
 1
Author: Niraj patel, 2016-02-02 06:41:12
Obrigado @hobodave. No entanto, encontrei duas fraquezas no seu código. O seu processamento da url original para obter o segmento "host" pára na primeira barra simples. Isto pressupõe que todas as ligações relativas começam no directório raiz. Isto só é verdade às vezes.
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->
Resolve isto quebrando a última barra e não a primeira.

Um segundo erro não relacionado, é que $depth não segue realmente a profundidade de recursão, segue Largura do primeiro nível de recorrência.

Se eu acreditava que esta página estavam em uso ativo eu poderia depuração este segundo problema, mas eu suspeito que o texto que eu estou escrevendo agora nunca vai ser lido por alguém, humano ou robô, uma vez que este problema é de seis anos de idade e nem mesmo tenho o suficiente reputação para notificar +hobodave diretamente sobre esses defeitos por commmenting em seu código. Obrigado na mesma, vagabundo.

 1
Author: Dov Jacobson, 2018-02-23 18:47:07

Usei o código de @hobodave, com este pequeno ajuste para evitar rastejar todas as variantes de fragmentos do mesmo 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();
  ...

Então você também pode omitir a linha $parts = parse_url($url); dentro do laço for.

 0
Author: pasqal, 2015-09-16 10:36:13
Eu inventei o seguinte código de aranha. Eu adaptei um pouco do seguinte: PHP-existe uma forma segura de realizar uma recursão profunda? parece bastante rápido....
    <?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
Author: Ian, 2018-01-12 11:46:40

Vale a pena lembrar que ao rastejar links externos (eu aprecio o OP se relaciona com uma página própria do usuário) você deve estar ciente de robôs.txt. Eu encontrei o seguinte que esperamos ajudar http://www.the-art-of-web.com/php/parse-robots/.

 0
Author: Antony, 2018-02-05 10:32:44
 0
Author: Jake, 2018-02-05 11:01:22