PHP에서 간단한 크롤러를 어떻게 만듭니 까?
많은 링크가있는 웹 페이지가 있습니다. 해당 링크에 포함 된 모든 데이터를 로컬 파일에 덤프하는 스크립트를 작성하고 싶습니다.
아무도 PHP로 그렇게 했습니까? 일반적인 지침과 문제로 충분할 것입니다.
Meh. 정규식으로 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에 에코하여 원하는 파일로 리디렉션 할 수 있습니다.
편집 : George가 그의 답변에서 지적한 버그를 수정했습니다. 상대 URL은 더 이상 URL 경로 끝에 추가되지 않고 덮어 씁니다. 이것에 대해 George에게 감사합니다. George의 대답은 https, 사용자, 패스 또는 포트를 고려하지 않습니다. 당신이있는 경우 HTTP의 PECL 확장이가 아주 간단하게 사용하여 수행됩니다로드 http_build_url을 . 그렇지 않으면 parse_url을 사용하여 수동으로 붙여야합니다. 다시 감사합니다 George.
위의 예제 / 답변을 기반으로 한 구현입니다.
- 클래스 기반
- Curl 사용
- HTTP 인증 지원
- 기본 도메인에 속하지 않는 URL 건너 뛰기
- 각 페이지에 대한 HTTP 헤더 응답 코드 반환
- 각 페이지의 반환 시간
크롤 클래스 :
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'에 저장합니다. 함수는 링크를 따라야하는 시간을 정의하는 두 번째 매개 변수 인 depth를받습니다. 주어진 페이지의 링크 만 구문 분석하려면 거기에 1을 전달하십시오.
wget 을 사용할 수있을 때 PHP를 사용하는 이유 , 예 :
wget -r -l 1 http://www.example.com
콘텐츠를 구문 분석하는 방법은 HTML 구문 분석을위한 최상의 방법을 참조 하고 예제 검색 기능을 사용하십시오 . HTML을 구문 분석하는 방법은 이전에 여러 번 답변되었습니다.
hobodave의 코드가 약간 변경 되었으므로 다음은 페이지를 크롤링하는 데 사용할 수있는 코드입니다. 서버에서 컬 확장을 활성화해야합니다.
<?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);
?>
이 크롤러 스크립트 자습서 에서이 자습서를 설명했습니다.
Hobodave 당신은 매우 가까웠습니다. 내가 변경 한 유일한 것은 발견 된 앵커 태그의 href 속성이 'http'로 시작하는지 확인하는 if 문 내에 있습니다. 전달 된 페이지를 포함하는 $ 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);
?>
앞서 언급했듯이 커스터마이징 할 수있는 크롤러 프레임 워크가 모두 준비되어 있지만 수행중인 작업이 언급 한 것처럼 간단하다면 처음부터 쉽게 만들 수 있습니다.
링크 스크랩 : http://www.phpro.org/examples/Get-Links-With-DOM.html
결과를 파일로 덤프 : http://www.tizag.com/phpT/filewrite.php
동일한 URL의 모든 단편 변형을 다시 크롤링하는 것을 방지하기 위해 약간의 조정으로 @hobodave의 코드를 사용했습니다.
<?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 감사합니다.
그러나 귀하의 코드에서 두 가지 약점을 발견했습니다. "호스트"세그먼트를 얻기위한 원래 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
실제로 재귀 깊이를 추적하지 않고 첫 번째 재귀 수준의 폭 을 추적한다는 것 입니다.
이 페이지가 활발히 사용되고 있다고 생각되면이 두 번째 문제를 디버깅 할 수 있지만 지금 작성중인 텍스트는 사람이나 로봇이 아닌 사람이 읽을 수 없을 것입니다.이 문제는 6 년이 지났고 충분하지도 않기 때문입니다. 코드에 댓글을 달아 이러한 결함에 대해 + hobodave에게 직접 알리는 평판. 어쨌든 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에 감사드립니다) robots.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})"
}
}
]
오래된 질문입니다. 그 이후로 많은 좋은 일이 일어났습니다. 이 주제에 대한 2 센트는 다음과 같습니다.
방문한 페이지를 정확하게 추적하려면 먼저 URI를 정규화해야합니다. 정규화 알고리즘에는 여러 단계가 포함됩니다.
- 쿼리 매개 변수를 정렬합니다. 예를 들어 다음 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
아마도 다른 정규화 규칙
- 쿼리 매개 변수를 정렬합니다. 예를 들어 다음 URI는 정규화 후 동일합니다.
Not only
<a>
tag hashref
attribute,<area>
tag has it too https://html.com/tags/area/. If you don't want to miss anything, you have to scrape<area>
tag too.Track crawling progress. If the website is small, it is not a problem. Contrarily it might be very frustrating if you crawl half of the site and it failed. Consider using a database or a filesystem to store the progress.
Be kind to the site owners. If you are ever going to use your crawler outside of your website, you have to use delays. Without delays, the script is too fast and might significantly slow down some small sites. From sysadmins perspective, it looks like a DoS attack. A static delay between the requests will do the trick.
If you don't want to deal with that, try Crawlzone and let me know your feedback. Also, check out the article I wrote a while back 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
'program story' 카테고리의 다른 글
PHP (> = 5.0)에서 참조로 전달하는 것이 더 빠릅니까? (0) | 2020.11.20 |
---|---|
Rails에서 require, require_dependency 및 상수를 다시로드하는 방법은 무엇입니까? (0) | 2020.11.20 |
Java 클래스를 프로그래밍 방식으로 컴파일하고 인스턴스화하는 방법은 무엇입니까? (0) | 2020.11.20 |
튜플이 Python의 목록보다 빠른 이유는 무엇입니까? (0) | 2020.11.20 |
가능한 인터뷰 질문 : 모든 겹치는 간격을 찾는 방법 (0) | 2020.11.20 |