Scriptlaufzeit über 30 min. aufteilen

kramoo

Mitglied
Habe eine Script erstellt für die Controlle von Links. Von der Linkgebenden Seite werden alle Links ausgelesen und mit den Links in der DB verglichen. Das funktioniert sehr gut.
Das Problem ist das ich jetzt schon fast 3000 Links bzw. Seiten auf diese weise Prüfe. Daher ist die Scriptlaufzeit über 1 Stunde.

Die Frage ist nun wie geht man das Problem an? Kann man mehrere Abfragen gleichzeitig machen und immer nur 100 Links testen? Oder wie kann und sollte man das am besten lösen?
 
Die Frage ist doch eher, warum es über eine Stunde dauert, so was simples wie Link-Testing durchzuführen? Läufst du auf Timeouts oder was verlangsamt das ganze so sehr? Bei 3000 Links würde das bedeutet, dass das Script mehr als eine Sekunde pro Link benötigt? Rufst du das bei dir lokal ab? Wie gehst du vor? Benutzt du curl?
 
Die Frage ist doch eher, warum es über eine Stunde dauert, so was simples wie Link-Testing durchzuführen? Läufst du auf Timeouts oder was verlangsamt das ganze so sehr? Bei 3000 Links würde das bedeutet, dass das Script mehr als eine Sekunde pro Link benötigt? Rufst du das bei dir lokal ab? Wie gehst du vor? Benutzt du curl?

Es dauert so lange weil er sich zu jeder Seite zuerst verbinden muss und die Seite einlesen und dann die Links rausziehen. Schätze mal das Verbinden dauert am längsten.

Ich verwende dazu cUrl um die Seite einzulesen. Danach wird die htmlseite in ein DOMDocument geparst und mit DOMXPath alle Links von der Seite geholt. Jeder gefundene Link der Seite wird mit dem Link aus der Datenbank verglichen. Bei Übereinstimmung wird der Datensatz aktuallisiert. Dann wird die nächste Seite eingelesen und geparst....

Anbei mal der ganze Code. Habs versucht so gut wie möglich zu kommentieren.

PHP:
<?php
$datum = date("Y-m-d");

/* Datenbankverbindungsinformationen */
	$dbuser = "******";
	$dbpassword = "******";
	$db = "*****";
	$dbserver = "localhost";
	
	$connection =  mysql_pconnect( $dbserver, $dbuser, $dbpassword ) or
		die( 'Could not open connection to server' );
		mysql_query("SET CHARACTER SET utf8");
		mysql_query("SET NAMES utf8"); 
		
	mysql_select_db( $db, $connection ) or 
		die( 'Could not select database '. $db );
	
/* Holt Backlinkurl, Zielurl, Ankertext aus DB und prüft ob der Datensatz heute schon mal geprüft wurde mit $datum */
	$sql = "
		SELECT id, backlinkzielurl, backlinkurl, backlinktext
		FROM backlink_test
		WHERE linkart IN ('partner','kommentar')
		AND datumletztercheck <> '".$datum."'
	";
	$result = mysql_query( $sql, $connection ) or die(mysql_error());
	$row_allLinkCheck = mysql_fetch_assoc($result);
  $totalRows_allLinkCheck = mysql_num_rows($result);

//Start Zeitmessung
$start1 = microtime(true);

function get_url( $url )  // Auslese des Inhaltes einer URL mit einer Browsersimulation
{
		$header[] = "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
		$header[] = "Accept-Language: de-de,de;q=0.8,en-us;q=0.5,en;q=0.3";
		$header[] = "Accept-Encoding: gzip,deflate";
		$header[] = "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7";
		$header[] = "Keep-Alive: 115";
		$header[] = "Connection: keep-alive";
		$header[] = "Pragma: no-cache";
		$header[] = "Cache-Control: no-cache";

		$cookie = tempnam ("../tmp", "CURLCOOKIE");
		$ch = curl_init();
		curl_setopt( $ch, CURLOPT_URL, $url );
		curl_setopt( $ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows; U; Windows NT 6.1; de; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13 GTB7.1" );
		curl_setopt( $ch, CURLOPT_HTTPHEADER, $header );
		curl_setopt( $ch, CURLOPT_COOKIEJAR, $cookie );
		curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1 );
		curl_setopt( $ch, CURLOPT_ENCODING, 'gzip,deflate' );
		curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
		curl_setopt( $ch, CURLOPT_AUTOREFERER, true );
		curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, 10 );
		curl_setopt( $ch, CURLOPT_TIMEOUT, 10 );
		curl_setopt( $ch, CURLOPT_MAXREDIRS, 5 );
		//curl_setopt( $ch, CURLOPT_REFERER, 'http://www.google.com');
		//curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false );    # wird für https urls benötigt
		//curl_setopt( $ch, CURLOPT_USERPWD, 'user:passw' );

  $html = curl_exec($ch); // führt curl aus
  curl_close($ch); // schließt curl wieder

  return $html; // gibt den Inhalt der Seite zurück 
} // schließt die Funktion get_url


// Abarbeiten und prüfen aller Backlinks Seite für Seite aus der DB mit der do while Schleife
do { 

$backlink_url = $row_allLinkCheck['backlinkurl']; // backlink aus Datenbank 
$ziel_url = $row_allLinkCheck['backlinkzielurl']; // Ziel Seite aus Datenbank 
$backlinktext = $row_allLinkCheck['backlinktext']; // Ankertext aus Datenbank 
$id = $row_allLinkCheck['id']; // wird für Datenbankupdate benötigt

$html = get_url($backlink_url); // funktion aufrufen cUrl und Übergabe der Backlinkurl

// parse html in ein DOMDocument
$dom = new DOMDocument();
@$dom->loadHTML($html);


// alle Links rausziehen
$xpath = new DOMXPath($dom);
$hrefs = $xpath->evaluate("/html/body//a");

// Link Checken: ob online, follow und Datum setzen
$followCheck = '';
$linkCheck = 'offline';

/* Durchläuft alle Links einer Seite */
    for ($i = 0; $i < $hrefs->length; $i++) {

		$href = $hrefs->item($i);
        $url = $href->getAttribute('href');
		
		$ziel_url = trim ($ziel_url, " \t\n\r\0\x0B/");
		$ziel_url = stripslashes($ziel_url);
		$url = trim ($url, " \t\n\r\0\x0B/");
		$url = stripslashes($url);
			
			/* Wenn $ziel_url aus DB und Url -> $url der geparsten Seite gleich sind		
				dann ist Seite online. Weiterer Check ob follow Link und Ankertext gleich sind */	
			if ($ziel_url == $url) { 
								
				$followCheck = 'follow';
				$linkCheck = 'online';
				$ankertext = $href->nodeValue;
							$rel = $href->getAttribute('rel');
							$rel = strtolower($rel); // in kleinbuchstaben umwandeln
										
				if ($backlinktext != $ankertext) {
						$linkCheck =  'falscher Ankertext';
				}
				
				if (preg_match("/\bnofollow\b/i", $rel)) {
									$followCheck = 'nofollow';
				}
						
			break; // wenn die URL gefunden wurde die schleife verlassen. Die restlichen Links der Seite werden so nicht mehr geprüft.
			} // Ende if  $ziel_url
		
    } // Ende for schleife


// Status in die Datenbank eintragen nach Durchlauf der for schleife 
// Status nur eintragen wenn lastDate nicht gleich $datum also heute
mysql_query("UPDATE backlink_test SET backlinkstatus='$linkCheck', backlinkfollow='$followCheck', datumletztercheck='$datum'
    WHERE id = '$id'") or die('Fehler beim Update der Datenbank:  '.mysql_error());

// RESET Variablen
$page = '';
   
} while ($row_allLinkCheck = mysql_fetch_assoc($result)) ;
// Ende do while

echo microtime(true)-$start1.' Sekunden verbraucht <br />';

mysql_free_result($result);
?>
 
Ok, dann hab ich das missverstanden. Interessant wäre wohl eher, wie lange das Script benötigt um

- alle Links aus der lokalen DB zu holen
- die Seite abzuholen, der die Links stehen
- die Seite zu parsen
- die Links mit der DB zu vergleichen
- die Links abzudaten.

Ich sehe hier schon ein paar Optimierungsmöglichkeiten:

- Ist ein Index auf der Spalte "linkart"?
- Ist die Spalte id ein Primärschlüssel oder wenigstens ein anderer Index-Typ (Unique, Index)?
- Kann man das Updaten evtl. auf Prepared-Statements umbauen, um damit den Vorgang zu beschleunigen?

Deine Aufgabe besteht also zunächst mal darin, herauszufinden, an welchen Stellen viel Zeit benötigt wird. Das kannst du mittels microtime() erledigen - ein Profiler ist bei der Menge an Daten und der Laufzeit wohl keine gute Idee.

Führst du das Script im Browser oder auf der Kommando-Zeile (evtl. auch mittels CRON) aus?
 
Das ist ein perfektes Beispiel, wo Threading oder Parallelisierung allgemein perfekt passt. Während dein Programm auf I/O wartet (curl_exec), verschwendet es Zeit, weil es ein blockierender Aufruf ist. Die einzelnen Links haben ja untereinander keine Abhängigkeiten, also können die völlig getrennt laufen. Jetzt ist die Frage, ob du bei PHP bleiben willst oder für diese Aufgabe auf etwas geeigneteres zurück greifst (Java oder irgendwas anderes als Background Worker).
Wenn du bei PHP bleiben willst, würde ich folgendes Vorschlagen. Anstatt dich irgendwie von Hand mit mehreren Prozessen herumzuschlagen (es scheint kein Threading in PHP zu geben?) nutzt du einfach den Webserver um das für dich zu machen. Du brauchst eine start.php, eine worker.php und ein shell script/batch script o.ä..

start.php
Durchläuft alle Einträge der Datenbank und startet via system() dein Skript im Hintergrund (http://stackoverflow.com/questions/1776517/run-and-forget-system-call-in-php)
Die start.php wird sehr schnell damit durch sein.


Shell Skript
Das enthält nichts anderes als einen Aufruf zu wget/curl an deine worker.php (localhost). Als Parameter kannst du die Adresse und User/Passwort angeben (die worker.php sollte nicht von außen aufrufbar sein). Dadurch, dass du für jede Adresse einen extra Aufruf an den Webserver schickt, verteilt dieser jede Adresse an eine eigene worker.php (PARALLELITÄT).


worker.php
Parsen etc. von genau EINER Adresse.



Das ganze hab ich mir jetzt aus dem Fingern gesogen, vielleicht kannst du was damit anfangen.
 
Ja und ich merke gerade, dass man auch einfach via system direkt wget aufrufen könnte, ohne das Skript dazwischen. Dann hat man auch nur einen producer und mehrere worker ohne den Zwischenschritt. Und das klingt ja alles nach viel Arbeit, aber das hat man doch in ein paar Minuten gemacht. Muss doch nur der Code auf die beiden Dateien verteilt werden.

Aber ich bin kein PHP Programmierer, also sind das alles nur Ideen ;-). Aber die Vorgehensweise Worker via GET Request zu starten ist üblich.
 
Danke Ihr beiden!
Das ist für mich noch eine Nummer zu groß!
Fakt ist das curl und das abholen der Seite den ganzen Prozess aufhält aber ich nicht in der Lage bin eure Vorschläge umzusetzen. Das ist mir dann doch etwas zu heftig. Entweder ich finde jemanden der so was für mich macht oder ich muss damit leben.
 
Zurück