Heim >Backend-Entwicklung >PHP-Tutorial >Praktische Weitergabe der Verwendung von Swoole zum asynchronen Crawlen von Webseiten
PHP-Programmierer wissen alle, dass in PHP geschriebene Programme alle synchron sind. Wie schreibt man ein asynchrones Programm in PHP? Hier nehmen wir das Crawlen von Webinhalten als Beispiel, um zu zeigen, wie man Swoole zum Schreiben asynchroner Programme verwendet.
PHP-Synchronisationsprogramm
Bevor Sie ein asynchrones Programm schreiben, machen Sie sich keine Sorgen, verwenden Sie zuerst PHP, um das Synchronisationsprogramm zu implementieren.
<?php /** * Class Crawler * Path: /Sync/Crawler.php */ class Crawler { private $url; private $toVisit = []; public function __construct($url) { $this->url = $url; } public function visitOneDegree() { $this->loadPageUrls(); $this->visitAll(); } private function loadPageUrls() { $content = $this->visit($this->url); $pattern = '#((http|ftp)://(\S*?\.\S*?))([\s)\[\]{},;"\':<]|\.\s|$)#i'; preg_match_all($pattern, $content, $matched); foreach ($matched[0] as $url) { if (in_array($url, $this->toVisit)) { continue; } $this->toVisit[] = $url; } } private function visitAll() { foreach ($this->toVisit as $url) { $this->visit($url); } } private function visit($url) { return @file_get_contents($url); } }
<?php /** * crawler.php */ require_once 'Sync/Crawler.php'; $start = microtime(true); $url = 'http://www.swoole.com/'; $ins = new Crawler($url); $ins->visitOneDegree(); $timeUsed = microtime(true) - $start; echo "time used: " . $timeUsed; /* output: time used: 6.2610177993774 */
Eine vorläufige Studie zur Implementierung asynchroner Crawler durch Swoole
Beziehen Sie sich zunächst auf die offizielle Seite zum asynchronen Crawlen.
Anwendungsbeispiel
Swoole\Async::dnsLookup("www.baidu.com", function ($domainName, $ip) { $cli = new swoole_http_client($ip, 80); $cli->setHeaders([ 'Host' => $domainName, "User-Agent" => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-Encoding' => 'gzip', ]); $cli->get('/index.html', function ($cli) { echo "Length: " . strlen($cli->body) . "\n"; echo $cli->body; }); });
Es scheint, dass durch eine leichte Änderung des synchronen file_get_contents-Codes eine asynchrone Implementierung erreicht werden kann. Es scheint, dass der Erfolg einfach ist.
Also haben wir den folgenden Code erhalten:
<?php /** * Class Crawler * Path: /Async/CrawlerV1.php */ class Crawler { private $url; private $toVisit = []; private $loaded = false; public function __construct($url) { $this->url = $url; } public function visitOneDegree() { $this->visit($this->url, true); $retryCount = 3; do { sleep(1); $retryCount--; } while ($retryCount > 0 && $this->loaded == false); $this->visitAll(); } private function loadPage($content) { $pattern = '#((http|ftp)://(\S*?\.\S*?))([\s)\[\]{},;"\':<]|\.\s|$)#i'; preg_match_all($pattern, $content, $matched); foreach ($matched[0] as $url) { if (in_array($url, $this->toVisit)) { continue; } $this->toVisit[] = $url; } } private function visitAll() { foreach ($this->toVisit as $url) { $this->visit($url); } } private function visit($url, $root = false) { $urlInfo = parse_url($url); Swoole\Async::dnsLookup($urlInfo['host'], function ($domainName, $ip) use($urlInfo, $root) { $cli = new swoole_http_client($ip, 80); $cli->setHeaders([ 'Host' => $domainName, "User-Agent" => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-Encoding' => 'gzip', ]); $cli->get($urlInfo['path'], function ($cli) use ($root) { if ($root) { $this->loadPage($cli->body); $this->loaded = true; } }); }); } }
<?php /** * crawler.php */ require_once 'Async/CrawlerV1.php'; $start = microtime(true); $url = 'http://www.swoole.com/'; $ins = new Crawler($url); $ins->visitOneDegree(); $timeUsed = microtime(true) - $start; echo "time used: " . $timeUsed; /* output: time used: 3.011773109436 */
Das Ergebnis lief 3 Sekunden lang. Achten Sie auf meine Implementierung. Nachdem ich eine Anfrage zum Crawlen der Homepage initiiert habe, werde ich jede Sekunde die Ergebnisse abfragen, und diese wird nach dreimaliger Abfrage beendet. Die 3 Sekunden hier scheinen der Exit zu sein, der durch dreimaliges Abfragen ohne Ergebnis verursacht wurde.
Es scheint, dass ich zu ungeduldig war und ihnen nicht genügend Vorbereitungszeit gegeben habe. Okay, ändern wir die Anzahl der Umfragen auf 10 und sehen uns die Ergebnisse an.
time used: 10.034232854843
Du weißt, wie ich mich in dieser Zeit fühle.
Ist es ein Leistungsproblem bei Swoole? Warum gibt es nach 10 Sekunden kein Ergebnis? Liegt es daran, dass meine Haltung falsch ist? Der alte Mann Marx sagte: „Übung ist das einzige Kriterium, um die Wahrheit zu prüfen.“ Es scheint, dass wir es debuggen müssen, um den Grund herauszufinden.
Also habe ich Haltepunkte bei
$this->visitAll();
und
$this->loadPage($cli->body);
hinzugefügt. Schließlich habe ich festgestellt, dass visitAll() immer zuerst und dann loadPage() ausgeführt wird.
Nachdem ich eine Weile darüber nachgedacht habe, verstehe ich wahrscheinlich den Grund. Was ist der Grund?
Das von mir erwartete asynchrone dynamische Modell sieht folgendermaßen aus:
Das tatsächliche Szenario sieht jedoch nicht so aus. Durch das Debuggen habe ich ungefähr verstanden, dass das tatsächliche Modell so aussehen sollte:
Mit anderen Worten, egal wie ich die Zahl erhöhe Bei vielen Wiederholungen werden die Daten erst dann ausgeführt, wenn die aktuelle Funktion bereit ist. Durch die Asynchronität wird nur die Zeit zum Vorbereiten der Verbindung verkürzt.
Dann stellt sich die Frage, wie ich das Programm dazu bringen kann, die Funktionen auszuführen, die ich nach der Aufbereitung der Daten erwarte.
Schauen wir uns zunächst an, wie Swooles offizieller Code zum Ausführen asynchroner Aufgaben geschrieben ist
$serv = new swoole_server("127.0.0.1", 9501); //设置异步任务的工作进程数量 $serv->set(array('task_worker_num' => 4)); $serv->on('receive', function($serv, $fd, $from_id, $data) { //投递异步任务 $task_id = $serv->task($data); echo "Dispath AsyncTask: id=$task_id\n"; }); //处理异步任务 $serv->on('task', function ($serv, $task_id, $from_id, $data) { echo "New AsyncTask[id=$task_id]".PHP_EOL; //返回任务执行的结果 $serv->finish("$data -> OK"); }); //处理异步任务的结果 $serv->on('finish', function ($serv, $task_id, $data) { echo "AsyncTask[$task_id] Finish: $data".PHP_EOL; }); $serv->start();
Sie können sehen, dass der Beamte die nachfolgende Ausführungslogik durch eine anonyme Funktion leitet. So gesehen wird es viel einfacher.
url = $url; } public function visitOneDegree() { $this->visit($this->url, function ($content) { $this->loadPage($content); $this->visitAll(); }); } private function loadPage($content) { $pattern = '#((http|ftp)://(\S*?\.\S*?))([\s)\[\]{},;"\':<]|\.\s|$)#i'; preg_match_all($pattern, $content, $matched); foreach ($matched[0] as $url) { if (in_array($url, $this->toVisit)) { continue; } $this->toVisit[] = $url; } } private function visitAll() { foreach ($this->toVisit as $url) { $this->visit($url); } } private function visit($url, $callBack = null) { $urlInfo = parse_url($url); Swoole\Async::dnsLookup($urlInfo['host'], function ($domainName, $ip) use($urlInfo, $callBack) { if (!$ip) { return; } $cli = new swoole_http_client($ip, 80); $cli->setHeaders([ 'Host' => $domainName, "User-Agent" => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-Encoding' => 'gzip', ]); $cli->get($urlInfo['path'], function ($cli) use ($callBack) { if ($callBack) { call_user_func($callBack, $cli->body); } $cli->close(); }); }); } }
Nachdem ich diesen Code gelesen habe, verspüre ich ein Déjà-vu-Gefühl. In der NodeJS-Entwicklung haben die allgegenwärtigen Rückrufe ihre eigenen Gründe. Jetzt verstehe ich plötzlich, dass es einen Rückruf gibt, um asynchrone Probleme zu lösen.
Ich habe das Programm ausgeführt, es dauerte nur 0,0007 Sekunden und es war vorbei, bevor es überhaupt gestartet war! Kann die asynchrone Effizienz wirklich so stark verbessert werden? Die Antwort lautet natürlich: Nein, mit unserem Code stimmt etwas nicht.
Aufgrund der Verwendung von Asynchronität wurde die Logik zur Berechnung der Endzeit ausgeführt, ohne auf den vollständigen Abschluss der Aufgabe zu warten. Es scheint, dass es an der Zeit ist, den Rückruf erneut zu verwenden.
/** Async/Crawler.php **/ public function visitOneDegree($callBack) { $this->visit($this->url, function ($content) use($callBack) { $this->loadPage($content); $this->visitAll(); call_user_func($callBack); }); }
<?php /** * crawler.php */ require_once 'Async/Crawler.php'; $start = microtime(true); $url = 'http://www.swoole.com/'; $ins = new Crawler($url); $ins->visitOneDegree(function () use($start) { $timeUsed = microtime(true) - $start; echo "time used: " . $timeUsed; }); /*output: time used: 0.068463802337646 */
Aus heutiger Sicht sind die Ergebnisse viel glaubwürdiger.
Vergleichen wir den Unterschied zwischen synchron und asynchron. Die Synchronisation dauert 6,26 Sekunden und die asynchrone dauert 0,068 Sekunden, was einem Unterschied von ganzen 6,192 Sekunden entspricht. Nein, um es genauer auszudrücken: Es müsste fast zehnmal schlimmer sein!
Natürlich ist asynchroner Code hinsichtlich der Effizienz viel höher als synchroner Code, aber logisch gesehen ist asynchrone Logik komplizierter als synchrone, und der Code bringt viele Rückrufe mit sich, was nicht leicht zu verstehen ist.
Swoole hat eine Beschreibung zur Auswahl von asynchron und synchron, die ich gerne mit Ihnen teilen möchte:
我们不赞成用异步回调的方式去做功能开发,传统的PHP同步方式实现功能和逻辑是最简单的,也是最佳的方案。像node.js这样到处callback,只是牺牲可维护性和开发效率。
Verwandte Lektüre:
So installieren Sie die Swoole-Erweiterung mit PHP7
Einführung in Swoole-Entwicklungspunkte
Beispiel zur asynchronen Verwendung von PHP mit mehreren Threads
Das Obige ist der gesamte Inhalt dieses Artikels. Wenn Schüler Fragen haben, können sie diese im diskutieren Kommentarbereich unten~
Das obige ist der detaillierte Inhalt vonPraktische Weitergabe der Verwendung von Swoole zum asynchronen Crawlen von Webseiten. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!