首页 后端开发 php教程 用Swoole异步抓取网页实战分享

用Swoole异步抓取网页实战分享

Dec 21, 2017 am 11:50 AM
swoole 异步

php程序员都知道,使用php写的程序都是同步的,如何用php写一个异步程序呢,答案就是Swoole。这里以抓取网页内容为例,来展示如何用Swoole来编写异步程序。

php的同步程序

在写异步程序之前,不要着急,先用php实现一下同步的程序。

<?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 = &#39;#((http|ftp)://(\S*?\.\S*?))([\s)\[\]{},;"\&#39;:<]|\.\s|$)#i&#39;;
        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 &#39;Sync/Crawler.php&#39;;
$start = microtime(true);
$url = &#39;http://www.swoole.com/&#39;;
$ins = new Crawler($url);
$ins->visitOneDegree();
$timeUsed = microtime(true) - $start;
echo "time used: " . $timeUsed;
/* output:
time used: 6.2610177993774
*/
登录后复制

Swoole实现异步爬虫初探

先参考一下官方的异步抓取页面怎么搞。
使用示例

Swoole\Async::dnsLookup("www.baidu.com", function ($domainName, $ip) {
    $cli = new swoole_http_client($ip, 80);
    $cli->setHeaders([
        &#39;Host&#39; => $domainName,
        "User-Agent" => &#39;Chrome/49.0.2587.3&#39;,
        &#39;Accept&#39; => &#39;text/html,application/xhtml+xml,application/xml&#39;,
        &#39;Accept-Encoding&#39; => &#39;gzip&#39;,
    ]);
    $cli->get(&#39;/index.html&#39;, function ($cli) {
        echo "Length: " . strlen($cli->body) . "\n";
        echo $cli->body;
    });
});
登录后复制

貌似稍微改造一下同步的file_get_contents代码,就可以实现异步了,看起来成功轻而易举嘛。
于是,我们得到了下面的代码:

<?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 = &#39;#((http|ftp)://(\S*?\.\S*?))([\s)\[\]{},;"\&#39;:<]|\.\s|$)#i&#39;;
        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[&#39;host&#39;], function ($domainName, $ip) use($urlInfo, $root) {
            $cli = new swoole_http_client($ip, 80);
            $cli->setHeaders([
                &#39;Host&#39; => $domainName,
                "User-Agent" => &#39;Chrome/49.0.2587.3&#39;,
                &#39;Accept&#39; => &#39;text/html,application/xhtml+xml,application/xml&#39;,
                &#39;Accept-Encoding&#39; => &#39;gzip&#39;,
            ]);
            $cli->get($urlInfo[&#39;path&#39;], function ($cli) use ($root) {
                if ($root) {
                    $this->loadPage($cli->body);
                    $this->loaded = true;
                }
            });
        });
    }
}
登录后复制
<?php
/**
 * crawler.php
 */
require_once &#39;Async/CrawlerV1.php&#39;;
$start = microtime(true);
$url = &#39;http://www.swoole.com/&#39;;
$ins = new Crawler($url);
$ins->visitOneDegree();
$timeUsed = microtime(true) - $start;
echo "time used: " . $timeUsed;
/* output:
time used: 3.011773109436
*/
登录后复制

结果运行了3秒。注意一下我的实现,在发起抓取首页的请求以后,我会隔一秒轮询一次结果,轮询三次还没有就结束了。这里的3秒好像是轮询了3次还没有结果导致的退出。
看来是我太急躁了,给人家的准备时间还不够充分。好吧,那我们把轮询次数改为10次,看看结果。

time used: 10.034232854843
登录后复制

此时我的心情,你懂的。

难道说是swoole的性能问题?为什么10秒还没有结果,难道是我的姿势不对?马克思老人家说过:“实践是检验真理的唯一标准”。看来需要debug一下才知道原因了。

于是,我在

$this->visitAll();
登录后复制

$this->loadPage($cli->body);
登录后复制

两处加了断点。最后发现总是先执行到visitAll(),再去执行loadPage()。

想了一下,大概明白原因了。到底是什么原因呢?

我期望的异步动态模型是这样的:

1.png

然而真实的场景不是这样的。通过调试,我大致了解到实际的模型应该是这样的:


2.png

也就是说,无论我怎么提高重试次数,数据永远不会准备好,数据只有在当前函数准备好以后,才会开始执行,这里的异步,只是减少了准备连接的时间。
那么问题来了,我该如何让程序在准备数据之后执行我期望的功能呢。
先看一下Swoole官方执行异步任务的代码是如何写的

$serv = new swoole_server("127.0.0.1", 9501);
//设置异步任务的工作进程数量
$serv->set(array(&#39;task_worker_num&#39; => 4));
$serv->on(&#39;receive&#39;, function($serv, $fd, $from_id, $data) {
    //投递异步任务
    $task_id = $serv->task($data);
    echo "Dispath AsyncTask: id=$task_id\n";
});
//处理异步任务
$serv->on(&#39;task&#39;, function ($serv, $task_id, $from_id, $data) {
    echo "New AsyncTask[id=$task_id]".PHP_EOL;
    //返回任务执行的结果
    $serv->finish("$data -> OK");
});
//处理异步任务的结果
$serv->on(&#39;finish&#39;, function ($serv, $task_id, $data) {
    echo "AsyncTask[$task_id] Finish: $data".PHP_EOL;
});
$serv->start();
登录后复制

可以看到,官方是通过一个function匿名函数,将后续的执行逻辑传了进去。这么看,事情就变得简单多了。

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();
            });
        });
    }
}
登录后复制

看了这段代码,竟然有种似曾相识的感觉,在nodejs开发中,随处可见的callback原来是有它的道理的。现在我才突然明白,原来callback的存在就是为了解决异步问题的。
执行了一下程序,竟然只用0.0007s,还没开始就已经结束了!异步的效率真的能提升这么多吗?答案当然是否定的,是我们的代码出问题了。
由于用了异步,没有等任务完全跑完,就已经执行了计算结束的时间的逻辑。看来又到了用callback的时候了。

/**
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 &#39;Async/Crawler.php&#39;;
$start = microtime(true);
$url = &#39;http://www.swoole.com/&#39;;
$ins = new Crawler($url);
$ins->visitOneDegree(function () use($start) {
    $timeUsed = microtime(true) - $start;
    echo "time used: " . $timeUsed;
});
/*output:
time used: 0.068463802337646
*/
登录后复制

现在来看,结果可信多了。
让我们比较一下同步的异步的差距,同步耗时6.26s,异步耗时0.068秒,差了整整6.192s。不,更准确地表述,应该是差了将近10倍!
当然,从效率上讲,异步远远高于同步的代码,但是从逻辑上讲,异步的逻辑比同步更绕,代码中会带来大量的callback,不便于理解。
Swoole官方里有一段关于异步与同步的选择的描述,非常中肯,分享给大家:

我们不赞成用异步回调的方式去做功能开发,传统的PHP同步方式实现功能和逻辑是最简单的,也是最佳的方案。像node.js这样到处callback,只是牺牲可维护性和开发效率。
登录后复制

相关阅读:

如何用php7安装swoole扩展

Swoole开发要点介绍

php异步多线程swoole用法实例

以上就是本篇文章的所有内容,同学们如果有疑问可以在下方评论区讨论哦~

以上是用Swoole异步抓取网页实战分享的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解锁Myrise中的所有内容
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

swoole协程如何在laravel中使用 swoole协程如何在laravel中使用 Apr 09, 2024 pm 06:48 PM

Laravel 中使用 Swoole 协程可以并发处理大量请求,优势包括:并发处理:允许同时处理多个请求。高性能:基于 Linux epoll 事件机制,高效处理请求。低资源消耗:所需服务器资源更少。易于集成:与 Laravel 框架无缝集成,使用简单。

swoole和workerman哪个好 swoole和workerman哪个好 Apr 09, 2024 pm 07:00 PM

Swoole 和 Workerman 都是高性能 PHP 服务器框架。Swoole 以其异步处理、出色的性能和可扩展性而闻名,适用于需要处理大量并发请求和高吞吐量的项目。Workerman 提供了异步和同步模式的灵活性,具有直观的 API,更适合易用性和处理较低并发量的项目。

swoole_process 怎么让用户切换 swoole_process 怎么让用户切换 Apr 09, 2024 pm 06:21 PM

Swoole Process 中可以让用户切换,具体操作步骤为:创建进程;设置进程用户;启动进程。

swoole框架怎么重启服务 swoole框架怎么重启服务 Apr 09, 2024 pm 06:15 PM

要重启 Swoole 服务,请按照以下步骤操作:检查服务状态并获取 PID。使用 "kill -15 PID" 停止服务。使用启动服务的相同命令重新启动服务。

swoole和java哪个性能好 swoole和java哪个性能好 Apr 09, 2024 pm 07:03 PM

性能比较:吞吐量:Swoole 凭借协程机制,吞吐量更高。延迟:Swoole 的协程上下文切换开销更低,延迟更小。内存消耗:Swoole 的协程占用内存更少。易用性:Swoole 提供更易于使用的并发编程 API。

Swoole实战:如何使用协程进行并发任务处理 Swoole实战:如何使用协程进行并发任务处理 Nov 07, 2023 pm 02:55 PM

Swoole实战:如何使用协程进行并发任务处理引言在日常的开发中,我们常常会遇到需要同时处理多个任务的情况。传统的处理方式是使用多线程或多进程来实现并发处理,但这种方式在性能和资源消耗上存在一定的问题。而PHP作为一门脚本语言,通常无法直接使用多线程或多进程的方式来处理任务。然而,借助于Swoole协程库,我们可以使用协程来实现高性能的并发任务处理。本文将介

Swoole进阶:如何优化服务器的CPU利用率 Swoole进阶:如何优化服务器的CPU利用率 Nov 07, 2023 pm 12:27 PM

Swoole是一款高性能的PHP网络开发框架,借助其强大的异步机制和事件驱动特点,可以实现快速构建高并发、高吞吐的服务器应用。然而,随着业务的不断扩展和并发量的增加,服务器的CPU利用率可能会成为一个瓶颈,影响服务器的性能和稳定性。因此,在本文中,我们将介绍如何优化服务器的CPU利用率,同时提高Swoole服务器的性能和稳定性,并提供具体的优化代码示例。一、

swoole协程是怎样调度的 swoole协程是怎样调度的 Apr 09, 2024 pm 07:06 PM

Swoole协程是一种轻量级并发库,允许开发者编写并发程序。Swoole协程调度机制基于协程模式和事件循环,使用协程栈管理协程执行,在协程让出控制权后挂起它们。事件循环处理IO和定时器事件,协程让出控制权时被挂起并返回事件循环。当事件发生时,Swoole从事件循环切换到挂起的协程,通过保存和加载协程状态完成切换。协程调度使用优先级机制,支持挂起、休眠和恢复操作以灵活控制协程执行。

See all articles