by:

Ich habe mir gedacht, nachdem ich ja sonst nix zu tun habe, ein paar
Tage lang eine bestehende Applikation so umzuschreiben, dass
sie auf mehreren Servern läuft.
Als Basis werde ich meinen Reminder Service verwenden.

Der erste Schritt um die Performance zu verbessern ist immer
die Analyse der bestehenden Applikation, dazu gehört auch das Profiling, um
Bottlenecks besser zu lokalisieren.

Um die Skalierbarkeit zu verbessern, werde ich
gewisse Teile ausgliedern, man nennt dies auch funktionale
Partitionierung.
Man könnte bei einem typischen Blog zum Beispiel anfangen,
Kommentare, Benutzer und Nachrichten jeweils auf eine eigene Datenbank auszulagern.
Weiters könnte man mit Hilfe eines Lastenverteilers und mehreren Webservern die
Last aufteilen.

In unserem Beispiel haben wir zwei Kernbereiche, erstens das Userinterface,
worüber der Benutzer Daten anlegen, verändern und löschen kann und
zweitens das Backend, welches die eingegebenen Daten verarbeitet.

Im Idealfall können wir am Ende das Front und Backend auf N Servern horizontal skalieren.
Das bedeutet, wir erhöhen nicht nur die Geschwindigkeit, sondern schaffen zusätzlich Redundanzen,
die eine gewisse Ausfallsicherheit garantieren.

Schauen wir uns das bestehende Backend mal an:
Code

 
<? 
include("./config.php"); 
$connect=@mysql_connect("$db_host", "$db_user", "$db_pass") 
or die("<li>Cant connect to: $db_host  with user: $db_user</li>"); 
@mysql_select_db($db, $connect)or die("Cant select DB"); 
 
// hier wird jeder job aus der datenbank geholt, 
// der den staus planned hat und in der vergangenheit liegt. 
 
$sql="SELECT jid, job.rid, subject, description, email, reminder.interval, 
      reminder.remind_date, reminder.auth  
      FROM job, reminder  
      WHERE  
      status='planned' AND  
      planned<UNIX_TIMESTAMP() AND  
      reminder.rid=job.rid"; 
       
$result=mysql_query($sql); 
while($row=mysql_fetch_array($result)){ 
    $header  = 'MIME-Version: 1.0' . "n"; 
    $header .= 'Content-type: text/html; charset=iso-8859-1' . "n"; 
    $header .= 'To: "'.$row['email'].'" <'.$row['email'].'>' . "n"; 
    $header .= 'From: "Remind ME" <remind@codejungle.org>' . "n"; 
    $msg=' 
    <html> 
    <head> 
      <title>RemindME</title> 
    </head> 
    <body> 
      '.$row['description'].' 
          <br/><hr><a href="http://www.codejungle.org/reminder/uid/'.$row[auth].'">Modify your Reminder</a> 
    </body> 
    </html> 
    '; 
    if(mail($row['email'], $row['subject'], $msg, $header)){ 
        //wenn die email verschickt werden konnte, wird der job auf done gesetzt 
        //und ein neuer job mit neuem versand datum angelegt 
        $update="update job set status='done' where jid=".$row['jid']; 
        mysql_query($update); 
        $time=getdate($row['remind_date']); 
        if($row['interval']=="d"){ 
            $dstr=date("U", mktime($time['hours'], $time['minutes'], $time['seconds'], 
            $time['mon'], $time['mday']+1, $time['year']));     
        } 
        if($row['interval']=="w"){ 
            $dstr=date("U", mktime($time['hours'], $time['minutes'], $time['seconds'], 
            $time['mon'], $time['mday']+7, $time['year']));     
        } 
        if($row['interval']=="m"){ 
            $dstr=date("U", mktime($time['hours'], $time['minutes'], $time['seconds'],
            $time['mon']+1, $time['mday'], $time['year']));     
        } 
        if($row['interval']=="y"){ 
            $dstr=date("U", mktime($time['hours'], $time['minutes'], $time['seconds'],
            $time['mon'], $time['mday'], $time['year']+1));     
        } 
        if($row['interval']!=="n"){ 
            $update_setting="update reminder set remind_date=".$dstr." where rid=".$row[rid]; 
            mysql_query($update_setting);         
            $insert="insert into job values ('', ".$row['rid'].", 'planned', ".$dstr.")"; 
            mysql_query($insert); 
        } 
    } 

?> 


Im Grunde passiert in diesem Script folgendes:

Hole mir alle unerledigten Jobs
Versende eMail
Markiere Job als erledigt
Erstelle gegebenenfalls neuen Job

Dieser Teil zeigt ganz schön, was man beachten muss, wenn man eine Applikation umschreibt, damit
Sie auf mehreren Server laufen kann.

Da zwischen "Hole mir alle unerledigten Jobs" und "Markiere Job als erledigt" etwas Zeit vergehen kann,
kann es passieren das ein zweiter Server die gleiche Routine startet und
ebenfalls den gleichen Job zugeteilt bekommt. Das Resultat wäre, dass der Benutzer
zwei oder mehr gleiche Nachrichten erhält.

Dies werden wir umgehen, indem wir nach "Hole mir alle unerledigten Jobs" einen Task einbauen,
der dafür sorgt, dass andere Server nicht den gleichen Task bekommen.

Hole mir 10 unerledigte Jobs
Markiere Job als in Bearbeitung
Versende eMail
Markiere Job als erledigt
Erstelle gegebenenfalls neuen Job

Desweiteren werden wir die Anzahl der zu erledigenden Jobs pro Prozess auf 10 verringern.
Daher sollte unser Code nun folgendermaßen aussehen:
Code
 
<? 
include("./config.php"); 
$connect=@mysql_connect("$db_host", "$db_user", "$db_pass") 
or die("<li>Cant connect to: $db_host  with user: $db_user</li>"); 
@mysql_select_db($db, $connect)or die("Cant select DB"); 
 
// hier werden die letzen 10 jobs aus der datenbank geholt, 
// der den staus planned hat und in der vergangenheit liegt. 
 
$sql="SELECT jid, job.rid, subject, description, email, reminder.interval,
      reminder.remind_date, reminder.auth  
      FROM job, reminder  
      WHERE  
      status='planned' AND  
      planned<UNIX_TIMESTAMP() AND  
      reminder.rid=job.rid  
      limit 10 
      "; 
       
$result=mysql_query($sql); 
while($row=mysql_fetch_array($result)){ 
    //setze job auf in bearbeitung, damit kein anderer prozess ihn ebenfalls erledigt 
    mysql_query("UPDATE job set staus='in_progress' where id='".$row['jid']."'"); 
    $header  = 'MIME-Version: 1.0' . "n"; 
    $header .= 'Content-type: text/html; charset=iso-8859-1' . "n"; 
    $header .= 'To: "'.$row['email'].'" <'.$row['email'].'>' . "n"; 
    $header .= 'From: "Remind ME" <remind@codejungle.org>' . "n"; 
    $msg=' 
    <html> 
    <head> 
      <title>RemindME</title> 
    </head> 
    <body> 
      '.$row['description'].' 
          <br/><hr><a href="http://www.codejungle.org/reminder/uid/'.$row[auth].'">Modify your Reminder</a> 
    </body> 
    </html> 
    '; 
    if(mail($row['email'], $row['subject'], $msg, $header)){ 
        //wenn die email verschickt werden konnte, wird der job auf done gesetzt 
        //und ein neuer job mit neuem versand datum angelegt 
        $update="update job set status='done' where jid=".$row['jid']; 
        mysql_query($update); 
        $time=getdate($row['remind_date']); 
        if($row['interval']=="d"){ 
            $dstr=date("U", mktime($time['hours'], $time['minutes'], $time['seconds'], 
            $time['mon'], $time['mday']+1, $time['year']));     
        } 
        if($row['interval']=="w"){ 
            $dstr=date("U", mktime($time['hours'], $time['minutes'], $time['seconds'], 
            $time['mon'], $time['mday']+7, $time['year']));     
        } 
        if($row['interval']=="m"){ 
            $dstr=date("U", mktime($time['hours'], $time['minutes'], $time['seconds'],
            $time['mon']+1, $time['mday'], $time['year']));     
        } 
        if($row['interval']=="y"){ 
            $dstr=date("U", mktime($time['hours'], $time['minutes'], $time['seconds'],
            $time['mon'], $time['mday'], $time['year']+1));     
        } 
        if($row['interval']!=="n"){ 
            $update_setting="update reminder set remind_date=".$dstr." where rid=".$row[rid]; 
            mysql_query($update_setting);         
            $insert="insert into job values ('', ".$row['rid'].", 'planned', ".$dstr.")"; 
            mysql_query($insert); 
        } 
    } 

?> 

Nachdem wir den Code nun soweit umgeschrieben haben, dass er auf mehren Servern laufen kann,
wäre eine Variante, ihn dort via Cron alle x Minuten auszuführen.

Im zweiten Teil möchte mit Hilfe dieses Beispiels, Gearman vorstellen, es ist ein Framework,
mit dem man parallele und lastenverteile Systeme aufbauen kann.

"It allows you to do work in parallel, to load balance processing, and to call functions between languages"

Außerdem möchte ich im dritten Teil noch etwas näher auf die Datenbankebene, memcache und verschiedene Lastenverteiler eingehen.

To be continuted

Andreas


Kommentare

by:

Das ist der erste Co- Authoren Beitrag auf meiner Seite zum Thema Captchas.
Vielen Dank vorab an: Sereby (http://www.sereby.org/)


Man findet Sie überall und man kommt auch nicht mehr dran vorbei:
Captcha's (Completely Automated Public Turing test to tell Computers and Humans Apart)
Sie werden von Webseiten-Administratoren z.B. bei Kontaktformularen, Shoutboxen oder Downloads verwendet, um sicher zu stellen, dass Bots nicht die Möglichkeit haben die Shoutbox oder ähnliches voll zu spammen oder in einem Downloadbereich dafür zu sorgen, dass nicht unendlich viel Traffic verbraucht wird, weil der Downloadlink tausende Male ausgeführt wird.

Nun zum eigentlichen Code:
Download

zunächst muss die Klasse TCaptcha eingebunden werden um auf die Validierungsfunktionen zuzugreifen
Code


<?
include('captchaTCaptcha.php');
?>
<form action="" method="post" name="111">
    <!-- 
        Einbinden des Captcha's über ein <img> tag.
        Wenn mehrere Captcha's verwendet werden sollen, dann
        müssen Sie die ID verändern!
    -->
    <img src="captcha/show.php?id=1" alt="Sicherheitscode 1" />

    <input type="text" name="captcha_code1" size="13" /> <input type="submit" value="OK" />

    <?
        //code überprüfen
        if (isset($_POST['captcha_code1'])){
            // Validate(* Getippter Code *, * Captcha-ID die verglichen werden soll *) 
            if (TCaptcha::Validate($_POST['captcha_code1'], 1)){
                echo 'Code OK!';
            }else{
                echo 'Falscher Code eingegeben!';
            }
        }
    ?>
</form>


Nun die eigentliche Captcha Klasse

Code

<?php
session_start
();
define('CAPTCHA_SESSION_ID''php_captcha');
define('CAPTCHA_IGNORE_CASE'false);

class 
TCaptcha
{
    var 
$IgnoreCase;
    var 
$CharSet;
    var 
$font;
    var 
$font_size;
    var 
$width;
    var 
$height;
    var 
$bgimg;
    var 
$maxlen;

    public 
$Code;

    public function 
__construct($FontFile './xfiles.ttf'$iWidth 100$iHeight 40)
    {
        
$this->IgnoreCase CAPTCHA_IGNORE_CASE;
        
$this->font       $FontFile;
        
$this->bgimg      './bg.png';
        
$this->maxlen     5;
        
$this->font_size  20;
        
$this->CharSet    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
        
$this->SetWidth($iWidth);
        
$this->SetHeight($iHeight);
        
$this->Code '';
    }
    
    function 
SetWidth($iWidth)
    {
        
$this->width $iWidth;
        if (
$iWidth 500$this->width 500;
    }

    function 
SetHeight($iHeight)
    {
        
$this->height $iHeight;
        if (
$iHeight 200$this->height 200;
    }
    
    private function 
randomString($len)
    {
        
//zufallszahl
        
function make_seed()
        {
            list(
$usec $sec) = explode (' 'microtime());
            return (float) 
$sec + ((float) $usec 100000);
        }
        
srand(make_seed());

        
//zufallsstring
        
$str '';
        while (
strlen($str)<$len)
        {
            
$str .= substr($this->CharSet, (rand()%(strlen($this->CharSet))), 1);
        }
           return(
$str);
    }
   
    function 
GenerateCode()
    {
        
//captcha-id
        
$CaptchaID = (is_numeric($_GET['id']) ? $_GET['id'] : '1');
        
        
//code generieren
        
$this->Code $this->randomString($this->maxlen);
        
        
//groß / kleinschreibung beachten?
        
if ($this->IgnoreCase)
        {
            
$this->Code strtoupper($this->Code);
        } 

        
//in die Session schreiben
        
$_SESSION[CAPTCHA_SESSION_ID][$CaptchaID] = $this->Code;
    }

    function 
Create()
    {
        
//Hintergrundbild einlesen
        
$picinfos GetImageSize($this->bgimg);
        
$OldIMG   ImageCreateFromPNG($this->bgimg);
        
        
//größe anpassen
        
$img      ImageCreateTrueColor($this->width$this->height);
        
ImageCopyResized($img,$OldIMG,0,0,0,0,$this->width,$this->height,$picinfos[0],$picinfos[1]);

        
//Farbe der Schrift
        
$color ImageColorAllocate($img000); 
        
        
//ZufallsCode generieren
        
$this->GenerateCode();
        
        
//justierung
        
$spacing = ($this->width $this->maxlen);
        
$t_y_min min($this->height$this->font_size);
        
$t_y   rand($t_y_min 1.2$t_y_min 2.8) + $spacing;
        
$angle rand(0,7);
        
$t_x   rand(5,10);
        
$a     0;

        
//text auf das bild schreiben. Zeichen für Zeichen
        
for ($i 1$i <=$this->maxlen$i++)
        {
            
ImageTTFText($img$this->font_size$angle$t_x$t_y$color$this->font$this->Code{$a});
            
$t_x $t_x $spacing -3;
            
$a++; // nächstes zeichen
        
}
        
        
//bild anzeigen
        
header("Content-type: image/gif");
        
ImageGIF($img);
        
        
//bild freigeben
        
ImageDestroy($img);
    }
    
    function 
Validate($MyCode$ID 1$IgnoreCase CAPTCHA_IGNORE_CASE)
    {
        if (
$IgnoreCase)
        {
            
$MyCode strtoupper($MyCode);
        }

        if (!empty(
$_SESSION[CAPTCHA_SESSION_ID][$ID]) && $MyCode == $_SESSION[CAPTCHA_SESSION_ID][$ID])
        {
            unset(
$_SESSION[CAPTCHA_SESSION_ID][$ID]); //wiederbenutzen vermeiden
            
return true;
        }
        return 
false;
    }
}
?>


Fertig!

Nachtrag von Andreas:

Es gibt auch Möglichkeiten ein Captcha zu umgehen, dies sei an dieser Stelle noch erwähnt. In der Praxis sind die Methoden allerdings eher ineffektiv oder doch etwas kompliziert.

Trotzdem möchte ich kurz auf ein, zwei Möglichkeiten hinweißen.

OCR Software, also Texterkennungssoftware die das Captcha versucht zu interpretieren. Ist in
der Anwendung kompliziert und schwer zu implementieren, aber dennoch der erfolgsversprechendste Ansatz.

Ich habe auch mal von der Möglichkeit gehört, die Captcha bei seiner privaten Seite einzubinden
um seine Besucher die Arbeit erledigen zu lassen indem man sie dazu nötigt die Captchas zu entschlüsseln.
Dies gelingt natürlich nur auf einer interessanten Seite mit viel Traffic.

Andere Varianten nutzen meist nur schlecht programmierte Captcha aus, bei denen z.b. das Lösungswort
in einem hidden field steht...

Vielen Dank Sereby für deinen ersten Beitrag!

Andreas


Kommentare

by:

Was ist ein Redirect Service ?

Ein Redirect oder auch Alias Service ist eine Methode, mit der man aus einer langen Internetadresse, eine gekürzte Form machen kann ...
Bsp.:
Aus
http://maps.google.at/maps?hl=de&ie=UTF8&ll=48.203397,16.373062&spn=0.094503,0.305557&z=12
Wird
http://kickmeto.ath.cx/wien

Nötig wird so ein Service, wenn man bei der Zeilenlänge eingeschränkt ist,
wie etwa in einer eMail, bei einer SMS oder auch bei Twitter.

Was benötigen wir für unseren eigenen Redirect Service ?

Eine möglichst kurze Domain
Einen Webserver mit PHP und mod_rewrite (Apache)
Eine Datenbank (im Beispiel verwende ich MySQL)

Ich habe zum Demonstrieren http://kickmeto.ath.cx/ online gestellt, was
auf folgendem Code basiert.

Zuerst erstellen wir unsere MySQL Tabelle "redirect":

CREATE TABLE `redirect` (
`id` bigint(20) unsigned NOT NULL auto_increment,
`url` text NOT NULL,
`name` varchar(255) default NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;


Hier haben wir einen primären Schlüssel (id), die Adresse (url) welche wir in den Namen (name) umwandeln möchten.

Jetzt erstellen wir eine Konfigurationsdatei, in der wir unsere Datenbankparameter, wie user, passwort etc.
reinschreiben:

Code

<?
    /***   mysql connection information   ****/
    /*****************************************/
    $db      = "redirect";
    $db_host = "localhost";
    $db_user = "user";
    $db_pass = "pass";
    $url     = "http://kickmeto.ath.cx/";
    /*****************************************/

?>


Jetzt kommt das Herzstück der Weiterleitung, folgender Script (index.php) übernimmt alle Aufgaben, die wir benötigen

1. Weiterleitung der Kurzadressen
2. Speichern neuer Adressen
3. Fehlerüberprüfung

Code

<?
include("./config.php");
$sOut="";

//wir bauen eine verbindung zur datenbank auf
$connect=@mysql_connect("$db_host", "$db_user", "$db_pass")
or die("<li>Cant connect to: $db_host  with user: $db_user</li>");
@mysql_select_db($db, $connect)or die("Cant select DB");

//im folgenden teil versuchen wir einen redirect in der datenbank zu finden
//wenn nicht das eingabe formular gefragt ist
if($_SERVER[REQUEST_URI]!=="/"){

    $surl=mysql_escape_string(strip_tags(substr($_SERVER[REQUEST_URI], 1)));
    $sql='select url from redirect where name="'.$surl.'" or id="'.$surl.'" limit 1';
    $query=mysql_query($sql);
    $row=mysql_fetch_array($query);
    if($row[url]){
    //hier paasiert die eigentlich weiterleitung
    header("Location: $row[url]");
    }else{
    $sOut.= "Kurzadresse nicht gefunden";
    }

}
//wenn jemand speichern drückt
if($_POST){

    //überprüfe ob die url syntax richtig ist
    if(preg_match("/(((https?)|(ftp))://([-w]+.)+w{2,3}(/[%-w]+(.w{2,})?)*(([w-.?/+@&#;`~=%!]*)(.w{2,})?)*/?)/i", $_POST[url])){
        //maskiere die parameter und speichere sie in die datenbank
        $nurl=mysql_escape_string($_POST[url]);
        if($_POST[name]){
            $sqlstr="'".mysql_escape_string(strip_tags($_POST[name]))."'";
            $name=mysql_escape_string(strip_tags($_POST[name]));
        } else {
            $sqlstr="NULL";
        }
        //hier überprüfen wir ob der name schon existiert
        if(url_exist($name)){
            $sOut="Der Name existiert leider schon, versuchs mit einem anderem.";
        } else {
            //nun schreiben wir die neue kurzurl in die datenbank
            $sql="insert into redirect VALUES ('', '$nurl', $sqlstr)";
            mysql_query($sql);
            $id=mysql_insert_id();        
            $sOut.='Deine Adresse ist nun unter folgender Adresse erreichbar:<br>';
            if($_POST[name]){
                $sOut.='<a href="'.$url.''.$name.'">'.$url.''.$name.'</a> oder <br>';
            }
            $sOut.='<a href="'.$url.''.$id.'">'.$url.''.$id.'</a>';
        }
    }else{
        $sOut.="Bitte check deine URL";
    }
}


function url_exist($url){
    $sql='select url from redirect where name="'.$url.'" or id="'.$url.'" limit 1';
    $query=mysql_query($sql);
    $row=mysql_fetch_row($query);
    if($row[0]){
        return true;
    } else {
        return false;
    }

}


?>
<html>
<head>
<style type="text/css">
body{
    background-color:#000000; 
    color: #ffffff;
    text-align:center;
}
</style>
</head>
<body>
<div class="header">Banner

Short Url Service
</div>
<div class="msg">
<?=$sOut?>
</div>
<form method="post">
<table align="center">
    <tr>
        <td>Url:</td>
        <td><input type="text" name="url" value="http://"></td>
    </tr>
    <tr>
        <td>Name (Optional)</td>
        <td><input type="text" name="name"></td>
    </tr>
        <td> </td>
        <td><input type="submit" name="Save" name="Speichern"></td>
    </tr>
</table>
</form>
</body>
</html>




Das wichtigste noch zum Schluss, die ModRewrite Anweisung (.htaccess), die alle Anfragen umleitet..

RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]

Ich hoffe der Script ist weitgehend selbsterklärend, ansonsten kannst du mich gerne anschreiben, wenn
du Fragen oder Verbesserungsvorschläge hast.

Andreas


http://httpd.apache.org/docs/2.2/mod/mod_rewrite.html


Kommentare

by:

Wo sind denn nur die verflixten Schlüssel hin verschwunden? Und wer zur Hölle versteckt ständig die Fernbedienung?!?
Nun ja, wer kennt sie nicht die verzweifelte Suche nach allen möglichen Dingen?
Im realen Leben kann ich euch da auch nicht helfen (obwohl die Marktchancen für Schlüsselsuchroboter wohl gar nicht schlecht wären),
aber im Überlebenskampf in den unendlichen Weiten des www gibt es zum Glück eine Möglichkeit...

Anmerkung:
Wie man eMailadressen, Kreditkarten oder andere personenbezogene Daten aus dem Netz fischt werde ich an dieser Stelle nicht beantworten.
Wer wissen will wie man ansatzweise eine eigene Suchmaschine schreibt, wird hier geholfen,
etwas Programmiererfahrung in PHP wäre dabei von Vorteil.

Überblick:
Ich habe mich schon etwas länger mit der Problematik befasst eine Suchmaschine zu erstellen,
weniger aus der Ambition heraus Google Konkurrenz zu machen, als mehr aus Spass an der Technik.
Zuerst einmal werde ich in diesem Artikel eine kleine Übersicht geben und die Unterschiede zwischen
einem Crawler und Indexer erklären.
In weiterer Folge werden wir versuchen Ordnung ins Chaos zu bringen und eine kleine Suchmaschine programmieren.
Es wird am Ende auch auf das Thema SEO eingegangen, allerdings weniger aus der klassischen Perspektive der
selbsternannten Marketingexperten, als mehr aus Sicht des Suchmaschinenprogrammierers.


Auf dem Markt gibt es derzeit viele Suchmaschinen, allerdings wenige die so populär sind wie Google,
was nicht zuletzt auf deren Rankingfunktion zurückzuführen ist, diese ist dafür verantwortlich
dass ich möglichst relevante Suchergebnisse erhalte.
Wir werden uns also in weiterer Folge mit der Thematik beschäftigen müssen, wie wir möglichst
interessante Ergebnisse zu den Suchanfragen generieren.
Hierbei unterscheide ich generell zwischen zwei Arten, dem usergenerated Ranking (bedeutet der User bestimmt welche Inhalte zu welchem
Thema interessant sind) und dem rein computergestützten Ranking.
Am besten wäre natürlich eine Mischung aus beiden, aber dazu später mehr.

Die erste Frage die sich stellt, wenn man sich mit der Thematik beschäftigt: "Woher bekomme ich die ganzen Daten?".

Das kommt darauf an, was man durchsuchen will, wir werden uns in diesem kleinen Howto primär den Internetdaten widmen.
Also "Woher bekomme ich alle Webseiten?", nun ja, leider ist es bei der Grösse und dem Wachstum des Internets nicht
möglich alle Webseiten in seine Suchmaschine aufzunehmen, auch für Google nicht. (Ja, richtig, entgegegen hartnäckig
kursierender Gerüchte: auch Google weiß nicht alles.)
Daher können wir nur einen kleinen Teil des Internets überhaupt aufnehmen.
Diese Arbeit macht der sogenannte Webcrawler, er besucht Webseiten, folgt den Links (bis zu einer gewissen Tiefe) und
speichert die Daten in einer Datenbank.

Meine ersten Gehversuche habe ich mit einem externen Crawler (larbin und wget) und einem selbst geschriebenen Indexer gemacht.

Der Indexer hat lediglich die Aufgabe gehabt, vereinfacht gesagt die gesammelten Daten in die Datenbank zu schreiben.
In weiterer Folge hat er die Daten sortiert, Schlüsselwörter extrahiert und Datenbestände aktuell gehalten.

Bei grösseren Mengen (Bsp. 30 GB Daten) kann das schon mal an die Grenzen der Serverressourcen gehen.
Deshalb habe ich die Suchanfragen auf einen zweiten Table abgefragt, wo lediglich url, title, keywords und description
vorhanden sind und nicht die gesamten Rohdaten.

Okay was wissen wir bis jetzt? Wir benötigen einen Webcrawler, der uns die Webseiten runterlädt, und
einen Indexer der die Webseiten in die Datenbank speichert. Soweit sogut.
Damit wir unsere Daten nicht nur horten, sondern auch nutzen können, brauchen wir auch noch ein kleines
Frontend, mit der Möglichkeit eine Suchanfrage zu starten.
In meinem Beispiel wird das Frontend auch die Aufgabe des Webcrawlers übernehmen, das bedeutet man
kann nur einzelnene Seiten dem Index hinzufügen, allerdings ist diese Variante auch kompatibel
mit larbin, wget und anderen Webcrawlern.

Aber dazu mehr in Part 2, jetzt kommt erstmal etwas Code (es gibt auch das gesammelte Packet weiter unten):

Hier mal unsere config.php Datei, wo alle wichtigen Zugangsdaten für die MySQL DB gespeichert sind.

CREATE TABLE `data` (
`id` bigint(20) unsigned NOT NULL auto_increment,
`title` varchar(255) NOT NULL,
`keyword` varchar(255) NOT NULL,
`desc` varchar(255) NOT NULL,
`url` varchar(255) NOT NULL,
`pr` int(11) NOT NULL default 5,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

Das ist unsere Tabelle data, wo wir die Metadaten der Webseiten speichern werden.
Dies erledigt folgender Script.

add.php speichert die Metadaten einer Webseite in der Datenbank, wenn der title und description tag
vorhanden ist. Sollte die Url in der Datenbank bereits vorhanden sein, wird der vorhandene Datensatz aktualisiert.

Nun brauchen wir noch einen Script der die Daten durchsucht und im Idealfall auch ein Ergebnis ausspuckt.
(Beachte das dieser Script über ajax aufgerufen wird.)

result.php
schaut in der Datenbank, ob der übergebene Parameter $_POST[search] vorhanden ist und sortiert die Ergebnisse nach dem Feld pr.
In diesem Feld speichern wir das Userranking.

Nun benötigen wir noch eine Seite, die es dem Benutzer erlaubt die Suchanfragen einzugeben:

index.php
ist im wesentlichen dazu da, dem Benutzer ein Eingabefeld zu zeigen, von wo er die Suchanfrage
starten kann. Darüber hinaus übernimmt der Script auch das Uservoteing und somit ist er auch Bestandteil des Rankings.

Zu guter Letzt noch die ajax.js Datei, sie übergibt im Hintergund die eingegebenen Daten aus dem Textfeld der result.php Datei.

Tatatata! Fertig ist unsere Suchmaschine mit der beliebten usergenerierten Sortierung!

Gesamter Script: Downloaden | Demo
Licence GPL 2

In Part 2 werde ich noch näher darauf eingehen, wie man die Daten automatisiert in die Datenbank
schreibt, und wie man eine "Meinten Sie .." Funktion implementiert.
Ausserm werde ich das Thema SEO aus einem eher technischen Gesichtspunkt beleuchten und
versuchen die Suchergebnisse zu optimieren.

Ich hoffe euch hat der kleine Exkurs in die Welt der Suchmaschinen gefallen, wenn ja
freue ich mich immer über ein Kommentar, wenn nein siehe wenn ja. :)


Best Regards

Andreas


Quellen
http://yacy.net/
http://larbin.sourceforge.net/index-eng.html
http://www.gnu.org/software/wget/


Kommentare

by:

Ich möchte euch hier demonstrieren wie man eine XML Schnittstelle einfach und schnell
programmiert...
Wir haben auf der einen Seite einen Script, der eine XML Anfrage an den Server schickt
und auf der anderen Seite einen Script, der die Anfrage bearbeitet.
Angenommen wir wollen 3 Funktionalitäten über XML steuern können:
ADD, UPDATE und DELETE sprich Hinzufügen, Ändern und Löschen.

Als erstes zeige ich euch wie man ein XML erzeugt und an den Server sendet:

Code

<?php     
   
function createxml($array) {
            
            
$buf '';
            foreach(
$array as $key => $value)
            {
                if(
is_array($value))
                {
                
$buf .= "<".strtolower($key).">" createxml($value) . "</".strtolower($key).">\n";
                }
                else{
                
$buf .="<".strtolower($key)."><![CDATA[$value]]></".strtolower($key).">\n";
                }
            }
            return 
$buf;
        }

        
$aPOST["content"]["id"]        = "2";
        
$aPOST["content"]["author_id"] = "1";        
        
$aPOST["content"]["header"]    = "Wir schreiben eine XML Api";
        
$aPOST["content"]["content"]   = "Ich m?chte euch hier Demonstrieren wie man eine
XML Schnitstelle.."
;
        
$aPOST["content"]["date"]      = date("Y-m-d H:i:s");
        
        
$url 'http://www.codejungle.org/xml-api.php';
        
$POST["cmd"]="ADD";
        
$POST["apikey"]="kmlnjierg8795th2bughj3fg02jgkm0ggi";
        
$POST["data"]=createxml($aPOST);

        
$ch curl_init();
        
curl_setopt($chCURLOPT_URL$url);
        
//curl_setopt($ch, CURLOPT_USERPWD, $user . ':' . $pwd);
        
curl_setopt($chCURLOPT_POST1);
        
curl_setopt($chCURLOPT_POSTFIELDS$POST);
        
curl_setopt($chCURLOPT_RETURNTRANSFER1);
        
$feedback       curl_exec($ch);
        
$ErrNum         curl_errno($ch);
        
$ErrMsg         curl_error($ch);
        
curl_close($ch);
        echo 
$feedback;
?>


Wir erstellen hier mit Hilfe der createxml Funktion aus einem Array eine XML
Datei. Danach senden wir mit Hilfe von curl drei Parameter an die Datei xml-api.php,
cmd damit unser Script weiß, welche Funktion er aufrufen soll, in unserem Fall ADD,
den API Key um sicherzustellen, dass nicht jeder die Schnittstelle benutzen kann und
data die eigentliche XML Datei.

Nun müssen wir einen Script erstellen, der die Anfrage entgegennimmt und richtig
verarbeiten kann.

Code
<?php    
$allowed_api_keys
=array("kmlnjierg8795th2bughj3fg02jgkm0ggi");

//check if api key is valid
if(in_array($_POST[apikey], $allowed_api_keys)){
        switch(
$_POST[cmd]){

        case 
ADD;
        
$xml simplexml_load_string($_POST[data]);
        foreach(
$xml as $name => $value){
        echo 
"$name => $value";
        }
        break;
        default:
        echo 
"Please check your provided command";
        break;
        }
} else {

echo 
"Your API - Key is invalid";
}
?>

Diese Datei überprüft zuerst ob der gesendete API Key existiert,
wenn ja, überprüfen wir noch welche Funktion übergeben wurde (in unserem Fall ADD).
Hier laden wir die XML Datei mit Hilfe der simplexml_load_string Funktion ein und
geben sie anschließend aus.
Man kann nun eine Funktion schreiben, die die übergebenen XML Daten weiter verarbeitet.
Die meisten Probleme machen nach meiner Erfahrung Umlaute und Sonderzeichen,
sie führen oft zu parse errors. Dies kann man teilweise mit Hilfe des Tags
lösen, wobei der Tag selbst allerdings auch zu parse errors führen kann...
Auf Wunsch von einem guten Freund möchte ich auch noch auf XML-RPC und Soap hinweisen,
beide eignen sich ebenfalls zur Remote XML Verarbeitung.

Ich hoffe euch hat mein kleiner Exkurs gefallen und freue mich über eure Kommentare.

LG
Andreas


Kommentare


Seiten: