source

어떻게 하면 PHP로 간단한 크롤러를 만들 수 있습니까?

nicesource 2023. 7. 28. 22:11
반응형

어떻게 하면 PHP로 간단한 크롤러를 만들 수 있습니까?

저는 많은 링크가 있는 웹 페이지를 가지고 있습니다.링크에 포함된 모든 데이터를 로컬 파일에 덤프하는 스크립트를 작성하고 싶습니다.

PHP로 그런 일을 한 사람이 있습니까?일반적인 지침과 gotchas는 답변으로 충분할 것입니다.

HTML을 정규식으로 구문 분석하지 마

Tatu에서 영감을 얻은 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);

편집: Tatu 버전의 버그를 수정했습니다(현재 상대 URL에서 작동합니다).

편집: 동일한 URL을 두 번 따라가지 못하게 하는 새로운 기능을 추가했습니다.

편집: 출력을 STDOUT로 에코하여 원하는 파일로 리디렉션할 수 있습니다.

편집: 조지가 답변에서 지적한 버그 수정.상대 URL은 더 이상 URL 경로의 끝에 추가되지 않고 덮어씁니다.이것에 대해 조지에게 감사합니다.George의 답변에는 https, 사용자, 패스 또는 포트가 포함되지 않습니다.http PECL 확장자를 로드한 경우 http_build_url을 사용하여 간단히 수행할 수 있습니다.그렇지 않으면 parse_url을 사용하여 수동으로 접착해야 합니다.다시 한 번 감사합니다, 조지.

위의 예/답변을 기반으로 한 구현입니다.

  1. 클래스 기반입니다.
  2. Curl
  3. HTTP 인증 지원
  4. 기본 도메인에 속하지 않는 URL 건너뛰기
  5. 각 페이지에 대한 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();

PHP 크롤러를 확인하세요.

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

도움이 되는지 보세요.

가장 간단한 형태로:

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'에 저장합니다.함수는 링크를 따라야 하는 기간을 정의하는 두 번째 매개 변수인 깊이를 허용합니다.지정된 페이지의 링크만 구문 분석하려면 1을 통과하십시오.

예를 들어 wget을 사용할 수 있는데 PHP를 사용하는 이유는 무엇입니까?

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

내용을 구문 분석하는 방법은 HTML을 구문 분석하고 검색 기능을 예제로 사용하는 최상의 방법을 참조하십시오.HTML을 구문 분석하는 방법은 이전에도 여러 번 답변되었습니다.

Hodave의 코드를 약간 변경하면 페이지를 탐색하는 데 사용할 수 있는 코드 스니펫이 있습니다.서버에서 컬 확장을 사용 가능으로 설정해야 합니다.

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

크롤러 스크립트 자습서에서 이 자습서를 설명했습니다.

호다브는 당신과 매우 가까웠습니다.내가 유일하게 변경한 것은 발견된 앵커 태그의 href 속성이 'http'로 시작하는지 확인하는 if 문 안에 있습니다.전달된 페이지를 포함하는 $url 변수를 단순히 추가하는 대신 parse_url php 함수를 사용하여 수행할 수 있는 호스트에 먼저 $url 변수를 제거해야 합니다.

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

앞서 언급했듯이, 커스터마이징할 수 있는 크롤러 프레임워크가 모두 준비되어 있습니다. 하지만 지금 하고 있는 작업이 말씀하신 것처럼 단순하다면 처음부터 쉽게 만들 수 있습니다.

링크 스크랩하기: http://www.phpro.org/examples/Get-Links-With-DOM.html

다음 파일에 결과 덤프: http://www.tizag.com/phpT/filewrite.php

나는 @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 루프 내의 라인입니다.

당신은 이것을 시도할 수 있습니다. 그것은 당신에게 도움이 될 것입니다.

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

@hobodave 감사합니다.

하지만 저는 당신의 코드에서 두 가지 약점을 발견했습니다."host" 세그먼트를 가져오기 위해 원본 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->

첫 번째 슬래시가 아닌 마지막 단일 슬래시에서 끊어짐으로써 이 문제를 해결합니다.

없는 두번째관없버그는련그은, 것은,▁that▁a,입니다.$depth재귀 깊이를 실제로 추적하지 않고, 첫 번째 수준의 재귀 폭을 추적합니다.

이 페이지가 현재 사용 중이라고 생각했다면 이 두 번째 문제를 디버그할 수도 있지만, 이 문제는 6년이 지났고 +hobodave의 코드에 주석을 달아 이러한 결함에 대해 직접적으로 알릴 충분한 평판이 없기 때문에 제가 지금 쓰고 있는 텍스트는 사람이든 로봇이든 누구도 읽지 못할 것이라고 생각합니다.어쨌든 감사합니다.

저는 다음과 같은 스파이더 코드를 생각해냈습니다.저는 다음과 같이 조금 수정했습니다: PHP - 딥 재귀를 안전하게 수행할 수 있는 방법이 있습니까?상당히 빠른 것 같습니다...

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

외부 링크를 탐색할 때(사용자 자신의 페이지와 관련된 OP에 감사합니다) 로봇에 대해 알아야 한다는 것을 기억할 가치가 있습니다.txt. http://www.the-art-of-web.com/php/parse-robots/ 에 도움이 될 만한 내용을 찾았습니다.

제공된 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})"
        }
    }
]

그건 오래된 질문입니다.그 이후로 많은 좋은 일들이 있었습니다.이 주제에 대한 저의 의견은 다음과 같습니다.

  1. 방문한 페이지를 정확하게 추적하려면 먼저 URI를 정규화해야 합니다.정규화 알고리즘에는 여러 단계가 포함됩니다.

    • 쿼리 매개 변수를 정렬합니다.들어 정규화. GET http://www.example.com/query?id=111&cat=222 GET http://www.example.com/query?cat=222&id=111
    • 빈 경로를 변환합니다. 예:http://example.org → http://example.org/

    • 인코딩의 대문자화 비율입니다.퍼센트 인코딩 삼중항(예: "%3A") 내의 모든 문자는 대소문자를 구분하지 않습니다. 예:http://example.org/a%c2%B1b → http://example.org/a%C2%B1b

    • 불필요한 점 세그먼트를 제거합니다. 예:http://example.org/../a/b/../c/./d.html → http://example.org/a/c/d.html

    • 일부 다른 정규화 규칙일 수 있습니다.

  2. 만아라 니라.<a>태그가 있습니다.href 기하다여,<area>태그에도 https://html.com/tags/area/ 이 있습니다.놓치고 싶지 않은 것이 있다면, 당신은 긁어모아야 합니다.<area>꼬리표도.

  3. 크롤링 진행 상황을 추적합니다.웹사이트가 작으면 문제가 되지 않습니다.반대로 사이트의 절반을 탐색했다가 실패하면 매우 좌절할 수 있습니다.데이터베이스 또는 파일 시스템을 사용하여 진행률을 저장해 보십시오.

  4. 사이트 소유자에게 친절하게 대합니다.웹사이트 밖에서 크롤러를 사용하려면 지연을 사용해야 합니다.지연이 없으면 스크립트 속도가 너무 빨라 일부 소규모 사이트의 속도가 크게 느려질 수 있습니다.sysadmins의 관점에서 보면 DoS 공격처럼 보입니다.요청 간의 정적 지연이 효과를 발휘합니다.

만약 당신이 그것을 다루기 싫다면, 크롤존을 사용해보고 당신의 피드백을 알려주세요.또한 제가 얼마 전에 쓴 기사를 확인해 보세요. https://www.codementor.io/zstate/this-is-how-i-crawl-n98s6myxm

언급URL : https://stackoverflow.com/questions/2313107/how-do-i-make-a-simple-crawler-in-php

반응형