Einführung
Auf Seiten, die mit enormen Linklisten aus Datenbanken arbeiten, trifft man leider nur zu oft auf tote Links. Webmaster solcher Seiten benutzen Linkchecker-Programme um inaktive Links aus der Datenbank zu entfernen. Leider sind die meisten dieser Programme nicht gerade das Gelbe vom Ei.
Deshalb habe ich hier ein Script, das mit zogenannten Cron Jobs nach einer gewissen Zeit (z.B. alle 24 Stunden) automatisch alle Links aus einer Datenbank holt, diese einzeln checkt und inaktive Links löscht (das Script kann aber auch manuel ausgeführt werden). Cron Jobs sind Aufgaben, die der Server nach einer gewisse Zeit automatisch ausführt. Diese können Sie auf dem Server einstellen (fragen Sie Ihren Server-Administrator für genaueres oder suchen Sie bei Google, denn die Technik unterscheidet sich je nachdem welche Art von Server-Software einsetzt wird). Wenn Sie nicht über reguläre Cron Jobs verfügen, gucken Sie sich das PHP-Script von Pseudo-Cron an.
Sie können das Script nach Bedarf anpassen. Das Script ist hier in Teile zerlegt, am Ende des Artikels steht die vollständige Version.
Das Script
Die Datenbank
Als erstes müssen die Links aus einer Datenbank geholt und in einer Variable gespeichert werden.
In der Datenbank gibt es minimal diese 2 Felder:
- Id: die Identität, mit deren Hilfe der Link später im Script erkannt wird
- URI: die Internet-Adresse des Dokuments auf die der Link verweist
Und die Datenbank wird jetzt geöffnet. Ich möchte an dieser Stelle schon mal erwähnen, dass die Datenbank erst ganz am Ende geschlossen wird. Somit muss zum Löschen eines Links keine neue Verbindung hergestellt werden, was den Server sehr belastet. Danach wird in eine while-Schleife jede Seite einzeln getestet (Mehr zu while-Schleifen).
<?php $db = mysql_connect('host','user','passwort'); mysql_select_db('Datenbankname',$db); $result = mysql_query('select id,uri from tabellenname'); while(list($id,$url)=mysql_fetch_row($result)) {
Wenn Sie etwas mehr über die Arbeit mit MySQL-Datenbanken wissen wollen, empfehle ich Ihnen das MySQL-Tutorial auf dieser Seite.
Eine Verbindung herstellen
Als nächstes wird eine Verbindung zu der zu kontrollierenden Seite hergestellt.
$test = 0; $parts = parse_url($uri); $fp = fsockopen($parts["host"],$parts["port"],$errno,$errstr,30);
Zuerst kommt die Variable $test, aber darauf komme ich später zurück. Die eingebaute Funktion parse_url() zerlegt die URI in ihre Einzelteile (Protokoll, Adresse des Hosts, gesuchte Seite und Query für Variablen) (Mehr zur Funktion parse_url()). Diese Teile werden in einer Variable vom Typ Array (Mehr über Arrays) gespeichert, die sich $parts nennt.
Danach wird eine Verbindung zum Server der Seite hergestellt. Dies wird mit der Funktion fsockopen() gemacht (Mehr zur Funktion fsockopen()). Diese Funktion hat maximal 5 Parameter: den Host (die Server-Adresse), den Port (das Protocol-Nummer), die Variable $errno (speichert die Nummer des Fehlers, wenn etwas schief läuft), die Variable $errstr (wie $errno, enthält den String mit einer Fehlermeldung) und eine Zahl (die Dauer in Sekunden, die der Server maximal auf eine Antwort wartet).
Eigentlich sind nur die ersten 2 davon notwendig, die anderen 3 kann man aber benutzen (wobei die namen von $errno und $errstr nicht geändert werden dürfen). Es ist jedoch zu empfehlen um den fünften Parameter mit einzubauen, sodass der Server nicht unendlich lange auf eine Antwort wartet.
Der Port für eine HTML-Seite ist 80 (http://), jedoch wird dieser nur automatisch erkannt, wenn Sie Ihre Links folgender Weise schreiben: www.it-academy.cc:80. Sie können dies lösen, indem Sie in der Funktion einfach die Zahl 80 anstelle von $parts[„port“] schreiben. Wenn Sie z.B. auch FTP-Links überprüfen wollen, können Sie die Zahl nicht einfach standardmäßig in der Funktion speichern. Sie können dies mit der Funktion substr() lösen. Mit dieser Funktion können Sie ein Teil eines Strings (substring) auslesen. Das funktioniert so:
if (substr($uri,0,4) == 'http') {$port = 80;} $fp = fsockopen($uri,$port);
Die Funktion substr() nimmt aus der Variable $uri 4 Buchstaben, beginnend ab Buchstabe 1 (PHP fängt an von 0 zu zählen). Wenn diese 4 Buchstaben gleich http sind, wird der Variable $port der Wert 80 zugewiesen, und dieser wird dann in der Funktion fsockopen() verwendet anstatt $parts[„port“].
Dies kann an alle Ports angepasst werden, die Sie verwenden wollen.
Eine Liste der verschiedenen Internet-Ports
Mehr zur Funktion substr()
Nachdem die Funktion fsockopen() ausgeführt wurde, wird nachgegangen ob eine Verbindung hergestellt wurde. Wenn dies nicht der Fall ist, muss der Status der Seite nicht mehr gecheckt werden. Deshalb verwende ich hier eine If-Else-Abfrage (Mehr zu if-else-Abfragen).
if (!$fp) { $test = 1; }
Die Verbindung wurde vorher in der Variable $fp gespeichert. Es wird hier getestet, ob die Verbindung nicht hergestellt werden konnte (achten Sie auf das !-Zeichen vor dem Namen der Variable). Wenn keine Verbindung hergestellt werden konnte, bekommt die Variable $test den Wert 1. Am Ende des Scripts wird dann je nachdem welchen Wert dieser Variable hat, entschieden was mit diesem Link gemacht wird. Weil das Script jedesmal neu ausgeführt wird, bekommt $test am Anfang wieder den Wert 0.
Status checken
Wenn eine Verbindung zustande kommt, sendet das Script eine Status-Abfrage an den Server. Dafür müssen wir erst mal wissen, welche Seite auf dem Server gesucht wird.
else { if (!empty($url_parts["path"])) {$documentpath = $url_parts["path"];} else {$documentpath = "/";} if (!empty($url_parts["query"])) $documentpath .= "?" . $url_parts["query"]; $host = $parts["host"];
Zuerst wird abgefragt ob in der URI ein Pfad angegeben wurde. Ein Pfad ist so etwas wie /index.php. Wenn es diesen gibt, wird dieser an die Variable $documentpath hinzugefügt. Ansonsten wird einfach das /-Zeichen hinzugefügt. Danach wird noch geprüft ob eine Query existiert. Die Query ist eine Reihe von Variablen, die per GET-Methode an eine Seite übergeben werden, z.B.: ?var=wert&seite=index. Wenn es diesen gibt, wird zuerst ein Fragezeichen an die Variable $documentpath zugefügt (dieser wird durch parse_url() und nicht automatisch hinzugefügt) und danach die Query selbst, die wir mit der Funktion parse_url() in der Variable $url_parts gespeichert haben.
Jetzt wissen wir, wo der Server ist und welche Seite wir brauchen. Die Anfrage senden wir mit der Funktion fwrite() (Mehr über fwrite()). Die Antwort erfahren wir mit der Funktion fgets() (Mehr über fgets()).
fwrite ($fp, "HEAD ".$documentpath." HTTP/1.0\r\nHost: $host\r\n\r\n"); $antwort = fgets($fp, 40);
Wir wollen den Header der Seite abfragen. Im Header stehen Informationen, die der Besucher der Seite nicht zu Gesicht bekommt und die nur zur Kommunikation zwischen dem Browser des Besuchers und dem Server nötig sind.
Nachdem wir mit fgets() die Antwort bekommen haben, werden wir diese mit der Funktion eregi() durchsuchen. Mit der Funktion eregi() kann man einen String nach einer bestimmten Zeichenkette durchsuchen. Es gibt auch noch die Funktion ereg(). Der Unterschied liegt darin, dass bei einer Suche mit ereg() auf Schriftgrößen geachtet wird. (Mehr über eregi() und ereg())
Wir werden den Header als Beispiel mal durchsuchen nach einem 404-Fehler (File Not Found).
if ( eregi('200 OK',$antwort) ) {$test = 0;} elseif ( (eregi('404',$antwort)) || (eregi('410',$antwort)) ) {$test = 1;} else {$test = 2;} }
Wenn der String ‚200 OK‘ im Header gefunden wurde, gibt es keine Probleme. Die Variable $test bekommt dann den Wert null, da es keine Probleme gab. Wenn dagegen ein Code 404 (Dokument wurde nicht gefunden) oder eine Code 410 (Dokument ist verschwunden und der Server weiß aber nicht wohin) empfangen wurde, bekommt $test den Wert 1. In allen anderen Fällen bekommt $test den Wert 2. Auf diese Art kann man eine Negativliste aufbauen bzw. eine Positivliste.
Für eine Übersicht der verschiedenen Codes empfehle ich Ihnen den Artikel Meldungen des Browsers.
Link verarbeiten und Verbindungen schliessen
Zuletzt wird überprüft, was mit dem Wert der Variable $test gemacht werden soll und somit was mit dem Link passiert. Dafür wird eine sogenannte Switch() benutzt (mehr zu Switch()). Die Verarbeitung sollte immer stattfinden und daher kommt dieser nach der Else-Abfrage.
switch($test) { case '1':mysql_query("update tabelle set aktiv = 'nein' where id = $id");break; case '2':mysql_query("delete * from tabelle where id = $id");break; }
Im ersten Fall (wenn keine Verbindung hergestellt werden konnte) wird die Seite inaktiviert. Im zweiten Fall (wenn man ein Code 404 oder 410 bekommen hat) wird die Seite gelöscht.
Dies sind nur einige Beispiele, man muss sich selbst entscheiden was man damit macht.
Zuletzt werden noch die Verbindungen zur Seite und der Datenbank geschlossen.
fclose($fp); } mysql_close($db); ?>
Nach der Else-Abfrage wird die Verbindung zu der Seite geschlossen, da die Anzahl an Verbindung die ein Server zugleich haben kann, begrenzt ist (Mehr zu fclose()). Nachde die While-Schleife beendet wurde, wird die Verbindung zur Datenbank geschlossen.
Anmerkung:
Manchmal begegnet man Servern, die nicht richtig konfiguriert wurden. Es kann vorkommen, dass diese einen falschen Code zurück senden. So habe ich es z.B. erlebt dass ich einen 404-Fehler bekam, obwohl die Seite ganz in Ordnung war.
Das ganze Script
<?php $db = mysql_connect('host','user','passwort'); mysql_select_db('Datenbankname',$db); $result = mysql_query('select id,uri from tabellenname'); while(list($id,$url)=mysql_fetch_row($result)) { $test = 0; $parts = parse_url($uri); $fp = fsockopen($parts["host"],$parts["port"],$errno,$errstr,30); // wenn man den Port am Link schreibt if (substr($uri,0,4) == 'http') {$port = 80;} // wenn man den Port nicht am Link schreibt $fp = fsockopen($uri,$port); // und der Port variiert if (!$fp) { $test = 1; } else { if (!empty($url_parts["path"])) {$documentpath = $url_parts["path"];} else {$documentpath = "/";} if (!empty($url_parts["query"])) $documentpath .= "?" . $url_parts["query"]; $host = $parts["host"]; fwrite ($fp, "HEAD ".$documentpath." HTTP/1.0\r\nHost: $host\r\n\r\n"); $antwort = fgets($fp, 40); if ( eregi('200 OK',$antwort) ) {$test = 0;} elseif ( (eregi('404',$antwort)) || (eregi('410',$antwort)) ) {$test = 1;} else {$test = 2;} } switch($test) { case '1':mysql_query("update tabelle set aktiv = 'nein' where id = $id");break; case '2':mysql_query("delete * from tabelle where id = $id");break; } fclose($fp); } mysql_close($db); ?>