Jak zrobić prosty crawler w PHP?
Mam stronę z mnóstwem linków. Chcę napisać skrypt, który zrzuci wszystkie dane zawarte w tych linkach do lokalnego pliku.
Czy ktoś to zrobił z PHP? Jako odpowiedź wystarczyłyby ogólne wytyczne i gotchas.
15 answers
Meh. Nie należy analizować HTML za pomocą wyrażeń regularnych.
Oto wersja DOM inspirowana 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);
Edit: poprawiłem kilka błędów z wersji Tatu (działa teraz z względnymi adresami URL).
Edit: dodałem nową funkcjonalność, która uniemożliwia mu dwukrotne podążanie za tym samym adresem URL.
Edit: wyświetla teraz Echo wyjścia na STDOUT, dzięki czemu możesz przekierować je do dowolnego pliku
Edit: Naprawiono błąd wskazywany przez George ' a w jego odpowiedz. Względne adresy URL nie będą już dołączane do końca ścieżki url, ale zostaną nadpisane. Dzięki George ' owi za to. Pamiętaj, że odpowiedź George ' a nie uwzględnia żadnego z: https, user, pass lub port. Jeśli masz załadowane rozszerzenie http PECL, jest to po prostu zrobione przy użyciu http_build_url . W przeciwnym razie muszę ręcznie sklejać używając parse_url. Jeszcze raz dziękuję, George.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-13 17:19:25
Oto moja implementacja oparta na powyższym przykładzie / odpowiedzi.
- jest oparty na klasie
- używa Curl
- obsługa http Auth
- Pomiń adres URL nie należący do domeny podstawowej
- zwraca kod odpowiedzi nagłówka Http dla każdej strony
- Czas powrotu dla każdej strony
KLASA 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);
}
}
Użycie:
// 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();
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-11 18:38:15
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-22 18:26:35
W najprostszej formie:
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);
Ta funkcja pobierze zawartość ze strony, następnie wypełni wszystkie znalezione linki i zapisze zawartość do ' results.txt". Funkcje akceptują drugi parametr, depth, który określa, jak długo linki mają być przestrzegane. Przekaż tam 1, jeśli chcesz analizować tylko linki z podanej strony.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-22 18:29:07
Z niewielkimi zmianami w kodzie hobodave ' a , Oto kod, którego możesz użyć do indeksowania stron. Wymaga to włączenia rozszerzenia curl na serwerze.
<?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);
?>
Wyjaśniłem ten samouczek w tym crawler script tutorial
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:54
Po co używać do tego PHP, skoro można użyć wget , np.
wget -r -l 1 http://www.example.com
Aby dowiedzieć się, jak parsować zawartość, zobacz najlepsze metody parsowania HTML i użyj funkcji wyszukiwania dla przykładów. Jak parsować HTML był już wielokrotnie pytany.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:33:26
Hobodave byłeś bardzo blisko. Jedyną rzeczą, którą zmieniłem, jest instrukcja if, która sprawdza, czy atrybut href znalezionego znacznika kotwicy zaczyna się od 'http'. Zamiast po prostu dodać zmienną $url, która zawierałaby przekazaną stronę, musisz najpierw rozebrać ją do hosta, co można zrobić za pomocą funkcji php parse_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);
?>
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-03 23:50:47
Jak już wspomniano, istnieją frameworki gąsienicowe gotowe do dostosowania, ale jeśli to, co robisz, jest tak proste, jak wspomniałeś, możesz zrobić to całkiem łatwo.
Skrobanie linków: http://www.phpro.org/examples/Get-Links-With-DOM.html
Wrzucenie wyników do pliku: http://www.tizag.com/phpT/filewrite.php
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-22 18:39:45
Pytanie Jak uzyskać kod źródłowy ajax? to nie jest pełzanie, np. jak pełzać zdjęcia na takim linku? http://www.tiendeo.nl/Catalogi/amsterdam/16558&subori=web_sliders&buscar=Boni&sw=1366
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-05 10:20:05
Możesz spróbować to może być pomoc dla ciebie
$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();
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-02 06:41:12
Dziękuję @ hobodave.
Jednakże znalazłem dwie słabości w Twoim kodzie. Parsowanie oryginalnego adresu url w celu uzyskania segmentu "host" zatrzymuje się przy pierwszym pojedynczym ukośniku. Zakłada to, że wszystkie odnośniki względne rozpoczynają się w katalogu głównym. To prawda tylko czasami.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->
Napraw to przez złamanie przy ostatnim pojedynczym ukośniku, a nie pierwszym
Drugim niezwiązanym błędem jest to, że $depth
tak naprawdę nie śledzi głębokości rekurencji, lecz śledzi szerokość pierwszego poziomu rekurencji.
Gdybym wierzył, że ta strona była w aktywnym użyciu, mógłbym debugować ten drugi problem, ale podejrzewam, że tekst, który teraz piszę, nigdy nie zostanie przeczytany przez nikogo, człowieka lub robota, ponieważ ten problem ma sześć lat i nie mam nawet wystarczającej reputacji, aby powiadomić + hobodave bezpośrednio o tych wadach, komentując jego kod. Dzięki hobodave.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-23 18:47:07
Użyłem kodu @ hobodave, z tą małą poprawką, aby zapobiec ponownemu indeksowaniu wszystkich wariantów fragmentów tego samego adresu 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();
...
Wtedy można również pominąć linię $parts = parse_url($url);
wewnątrz pętli for.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-16 10:36:13
Wymyśliłem następujący kod pająka. Dostosowałem go trochę z następujących: PHP - czy istnieje bezpieczny sposób na wykonywanie głębokiej rekurencji? wydaje się dość szybki....
<?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 />';
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-12 11:46:40
Warto pamiętać, że podczas indeksowania linków zewnętrznych (doceniam OP odnosi się do własnej strony użytkownika) należy pamiętać o robotach.txt. Znalazłem następujące, które, mam nadzieję, pomogą http://www.the-art-of-web.com/php/parse-robots/.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-05 10:32:44
Stworzyłem małą klasę, aby pobierać dane z podanego adresu url, a następnie wyodrębniać wybrane elementy html. Klasa korzysta z CURL i DOMDocument.
Klasa 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;
}
}
Przykład użycia:
$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));
}
Przykładowa ODPOWIEDŹ:
[
{
"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})"
}
}
]
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-05 11:01:22