Hoe maak ik een eenvoudige crawler in PHP? [gesloten]

Ik heb een webpagina met een heleboel links. Ik wil een script schrijven dat alle gegevens in die links in een lokaal bestand zou dumpen.

Heeft iemand dat gedaan met PHP? Algemene richtlijnen en valkuilen zouden volstaan ​​als antwoord.


Antwoord 1, autoriteit 100%

Mh. HTML niet ontleden met regexes.

Hier is een DOM-versie geïnspireerd door Tatu’s:

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

Bewerken: ik heb een aantal bugs in Tatu’s versie opgelost (werkt nu met relatieve URL’s).

Bewerken: ik heb een nieuw stukje functionaliteit toegevoegd waardoor het niet twee keer dezelfde URL kan volgen.

Bewerken: echo nu naar STDOUT zodat je het kunt omleiden naar welk bestand je maar wilt

Bewerken: een bug opgelost die door George in zijn antwoord werd opgemerkt. Relatieve url’s worden niet langer aan het einde van het url-pad toegevoegd, maar overschreven. Met dank aan George hiervoor. Merk op dat het antwoord van George geen rekening houdt met: https, gebruiker, pas of poort. Als je de http PECL-extensie hebt geladen, kan dit heel eenvoudig met http_build_url. Anders moet ik handmatig aan elkaar lijmen met parse_url. Nogmaals bedankt George.


Antwoord 2, autoriteit 18%

Hier mijn implementatie op basis van het bovenstaande voorbeeld/antwoord.

  1. Het is gebaseerd op klassen
  2. gebruikt krul
  3. ondersteuning voor HTTP-verificatie
  4. Sla URL over die niet bij het basisdomein hoort
  5. Retourcode voor HTTP-header voor elke pagina
  6. Retourtijd voor elke pagina

CRAWL KLASSE:

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

GEBRUIK:

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

Antwoord 3, autoriteit 12%

Bekijk PHP-crawler

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

Kijk of het helpt.


Antwoord 4, autoriteit 10%

In zijn eenvoudigste vorm:

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

Die functie haalt de inhoud van een pagina op, crawlt vervolgens alle gevonden links en slaat de inhoud op in ‘results.txt’. De functies accepteren een tweede parameter, depth, die bepaalt hoe lang de links moeten worden gevolgd. Geef daar 1 door als u alleen links van de gegeven pagina wilt ontleden.


Antwoord 5, autoriteit 6%

Waarom PHP hiervoor gebruiken, als je wget kunt gebruiken, bijvoorbeeld

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

Zie Beste methoden om HTML te ontleden en gebruik de zoekfunctie voor voorbeelden. Hoe HTML te ontleden is al meerdere keren eerder beantwoord.


Antwoord 6, autoriteit 6%

Na enkele kleine wijzigingen in de code van hobodave’s, is hier een codesnippet die u kunt gebruiken om pagina’s te crawlen. Hiervoor moet de curl-extensie op uw server zijn ingeschakeld.

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

Ik heb deze tutorial uitgelegd in deze crawlerscript-tutorial


Antwoord 7, autoriteit 3%

Hobodave, je was heel dichtbij. Het enige dat ik heb veranderd, is de if-statement die controleert of het href-attribuut van de gevonden ankertag begint met ‘http’. In plaats van simpelweg de $url-variabele toe te voegen die de pagina zou bevatten die werd doorgegeven, moet je deze eerst strippen naar de host, wat kan worden gedaan met behulp van de parse_url php-functie.

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

Antwoord 8, autoriteit 2%

Zoals eerder vermeld, zijn er crawler-frameworks die allemaal klaar zijn om aan te passen, maar als wat je doet zo eenvoudig is als je zei, zou je het vrij gemakkelijk helemaal opnieuw kunnen maken.

De links schrapen: http://www.phpro.org /voorbeelden/Get-Links-With-DOM.html

Resultaten dumpen naar een bestand: http://www.tizag.com/phpT/filewrite .php


Antwoord 9

Ik heb de code van @hobodave gebruikt, met deze kleine aanpassing om te voorkomen dat alle fragmentvarianten van dezelfde URL opnieuw worden gecrawld:

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

Dan kun je ook de regel $parts = parse_url($url); binnen de for-lus weglaten.


Antwoord 10

Je kunt dit proberen, het kan je helpen

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

Antwoord 11

Bedankt @hobodave.

Ik heb echter twee zwakke punten in uw code gevonden.
Uw parsering van de originele url om het “host”-segment te krijgen, stopt bij de eerste enkele schuine streep. Dit veronderstelt dat alle relatieve links in de hoofdmap beginnen. Dit is alleen soms waar.

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

verhelp dit door te breken bij de laatste slash, niet bij de eerste

een tweede niet-gerelateerde bug is dat $depth de recursiediepte niet echt volgt, maar de breedte van het eerste recursieniveau.

Als ik dacht dat deze pagina actief in gebruik was, zou ik fouten kunnen opsporen in dit tweede probleem, maar ik vermoed dat de tekst die ik nu schrijf nooit door iemand, mens of robot, zal worden gelezen, aangezien dit nummer zes jaar oud is en ik niet zelfs genoeg reputatie hebben om +hobodave direct op de hoogte te stellen van deze gebreken door commentaar te geven op zijn code. Toch bedankt hobodave.


Antwoord 12

Ik heb de volgende spidercode bedacht.
Ik heb het een beetje aangepast van het volgende:
PHP – Is er een veilige manier om te presteren diepe recursie?
het lijkt redelijk snel….

    <?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 />';
    }

Antwoord 13

Het is de moeite waard om te onthouden dat wanneer u externe links crawlt (ik waardeer dat de OP betrekking heeft op de eigen pagina van een gebruiker) u zich bewust moet zijn van robots.txt. Ik heb het volgende gevonden dat hopelijk http://www zal helpen. the-art-of-web.com/php/parse-robots/.


Antwoord 14

Ik heb een kleine klasse gemaakt om gegevens van de opgegeven url te halen en vervolgens html-elementen naar keuze te extraheren. De klas maakt gebruik van CURL en DOMDocument.

php klasse:

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

voorbeeld gebruik:

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

voorbeeldreactie:

[
    {
        "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})"
        }
    }
]

Antwoord 15

Het is een oude vraag. Sindsdien zijn er veel goede dingen gebeurd. Hier zijn mijn twee cent over dit onderwerp:

  1. Om de bezochte pagina’s nauwkeurig bij te houden, moet u eerst de URI normaliseren. Het normalisatie-algoritme omvat meerdere stappen:

    • Sorteer queryparameters. De volgende URI’s zijn bijvoorbeeld equivalent na normalisatie:

      GET http://www.example.com/query?id=111&cat=222
      GET http://www.example.com/query?cat=222&id=111
    • Converteer het lege pad.
      Voorbeeld: http://example.org > http://example.org/

    • Procentuele codering in hoofdletters plaatsen. Alle letters binnen een triplet met procentcodering (bijv. “%3A”) zijn niet hoofdlettergevoelig.
      Voorbeeld: http://example.org/a%c2%B1b > http://example.org/a%C2%B1b

    • Verwijder onnodige puntsegmenten.
      Voorbeeld: http://example.org/../a/b/../c/./d.html > http://example.org/a/c/d.html

    • Mogelijk enkele andere normalisatieregels

  2. Niet alleen de tag <a> heeft het kenmerk href, de tag <area> heeft het ook https://html.com/tags/area/. Als je niets wilt missen, moet je ook de <area>-tag schrapen.

  3. Houd de voortgang van het crawlen bij. Als de website klein is, is dat geen probleem. Integendeel, het kan erg frustrerend zijn als je de helft van de site crawlt en het mislukt. Overweeg het gebruik van een database of een bestandssysteem om de voortgang op te slaan.

  4. Wees aardig voor de site-eigenaren.
    Als u uw crawler ooit buiten uw website gaat gebruiken, moet u vertragingen gebruiken. Zonder vertragingen is het script te snel en kan het sommige kleine sites aanzienlijk vertragen. Vanuit sysadmins-perspectief lijkt het op een DoS-aanval. Een statische vertraging tussen de verzoeken is voldoende.

Als je daar geen zin in hebt, probeer dan Crawlzone en laat me je feedback weten . Bekijk ook het artikel dat ik een tijdje geleden schreef https:/ /www.codementor.io/zstate/this-is-how-i-crawl-n98s6myxm

LEAVE A REPLY

Please enter your comment!
Please enter your name here

10 + eleven =

Other episodes