Търсещи паяци - борба с арахнофобията
Ако някога просто сте се чудили дали е трудно да се напише робот - не, не е. Трудната част идва, когато трябва да обработите събраната информация. И понеже сега са празници и мен, както обикновено, ме мързи - няма да се занимавам изобщо с обработване. Затова пък ще ви покажа как да напишете кратък скрипт, който да се разходи из списък от сайтове. Простият ни паяк ще изрови всички линкове до някаква дълбочина. Естествено, това е възможно да бъде променено, според нуждите ни - филтриране на нежелани сайтове, избягване на повторения, преглеждане само на определени линкове (например rss емисии). Но ние ще предположим, че мрежата ни от линкове е <внимание - математика> дърво, в което всички върхове са “добри”, тоест са във вида “протокол://домейн/адрес” <край на математиката>
< ?php class Crawler { private $history = array(); private $path = array(); private $curl; private $depth; public function __construct($agent, $links, $_depth) { foreach($links as $l) array_push($this->path, array( 'url' => $l, 'depth' => 0 )); $this->curl = curl_init(); curl_setopt($this->curl, CURLOPT_USERAGENT, $agent); $this->depth = $_depth; } private function getContent($url) { // url на текущата страница curl_setopt($this->curl, CURLOPT_URL, $url); // спираме ехото и пазим резултата в низ curl_setopt ($this->curl, CURLOPT_RETURNTRANSFER, 1); return curl_exec ($this->curl); } public function crawl() { while(count($this->path)) { if( isset($this->path[0]['url']) && $this->path[0]['deth'] < $this->depth ) { $document = $this->getContent($this->path[0]['url']); $links = $this->getSuccessors($document); foreach($links as $l) array_push($this->path, array( 'url' => $l, 'depth' => $this->path[0]['depth'] +1; )); } array_push($this->history, $this->path[0]['url']); array_shift($this->path); } return $this->history; } public function getSuccessors($document) { $page = new DOMDocument(); @$page->loadHTML($content); $xpath = new DOMXPath($page); // вадим всички <a> тагове от документа $anchors = $xpath->evaluate("//a"); // тук ще пазим линковете $hrefs = array(); foreach($anchors as $a) array_push($hrefs, $a->getAttribute('href')); return $hrefs; } }; $links = array("http://www4u.org"); $www4uCrawler = new Crawler("www4u crawler v.1.example", $links, 2); $www4uLinks = $www4uCrawler->crawl(); print_r $www4uLinks; ?></a>
Това е видът на класа ни. Параметрите, които подаваме на __construct са съответно $agent - User-agent, с който ще се представяме, $links - неасоциативен масив, пълен с линкове (които на практика са низове) и $_depth - максимална дълбочина на ровене. Добре е да се отбележи, че в този си вид паякът не се съобразява с <meta name=”robots” />, а се разхожда като слон в стъкларски магазин, без да гледа къде стъпва ;). Това, което правим е:
- да направим от всеки елемент на $links асоциативен масив ($path), като url е оригиналният елемент, а depth - текущата дълбочина
- ако $path[0]['depth'] е по-малко от максималната дълбочина (все още трябва да обхождаме) - добавяме в края на $path всички линкове от $path[0]['url'], добавяме $path[0]['url'] към $history и изтриваме $path[0]
- когато count($path) стане 0 - връщаме всички обходени линкове ($history)
- показваме историята на обхождането
И все пак - в общия случай този клас е неприложим. Написах го само, за да покажа някои основни идеи, без да го считам за нещо завършено. Не гарантирам, че при вас ще работи. Мислете какво ви трябва, не копирайте готов код.
—
Забележка: за статията ви трябват php-xml и php версията на библиотеката cURL (или, за по-кратко - PHP 5.1 и нагоре). Ако намерите бъг, който държите да бъде поправен (вероятно ще има, не съм толкова велик, че да пиша bugless код без да съм тествал) - кажете и ще го оправя

January 31st, 2009 09:41
Супер е! Аз имам някои малки забележки (като използването на
varза декларация на полета в PHP5 клас), но като цяло решението много ми харесва. С малко промени може да стане повече платформено независимо: в смисал може да се промени малко декларацията на класа за да върви на PHP4, и да се използва Snoopy за HTTP клиент (има го във всеки WordPress — http://www4u.org/wp-includes/class-snoopy.php). Snoopy си има и собствен метод за “хващане” на всички линкове, така че може да се прескочи и часта с Dom/Xpath. Като допълнение, Snoopy има някаква поддръжка на прихващане на пренасочвания (redirect-и), така че има допълнителни плюсовеJanuary 31st, 2009 15:50
А бе, да ти кажа, бях тръгнал да го пиша със Snoopy, ама реших, че ще е прекалено нагло да давам акъл за нещо, което дори не съм бутал
Смених var с private, да може съвсем чисто да не върви на PHP4. Не държа особено кодът ми да върви на неподдържани версии