>  기사  >  백엔드 개발  >  Swoole을 사용하여 웹 페이지를 비동기식으로 크롤링하는 실제 공유

Swoole을 사용하여 웹 페이지를 비동기식으로 크롤링하는 실제 공유

*文
*文원래의
2017-12-21 11:50:455276검색

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

지금 내 기분이 어떤지 아시죠?

스울의 성능 문제인가요? 10초가 지나도 결과가 나오지 않는 이유는 자세가 잘못되었기 때문일까요? 노인 마르크스는 "진리를 검증하는 유일한 기준은 실천이다"라고 말했습니다. 원인을 찾으려면 디버깅을 해봐야 할 것 같습니다.

그래서

$this->visitAll();

$this->loadPage($cli->body);

에 중단점을 추가했습니다. 마지막으로 항상 VisitAll()이 먼저 실행되고 그 다음에 loadPage()가 실행되는 것을 발견했습니다.

잠깐 생각해보면 이유를 알 것 같아요. 이유는 무엇입니까?

제가 예상했던 비동기 동적 모델은 이렇습니다.

Swoole을 사용하여 웹 페이지를 비동기식으로 크롤링하는 실제 공유

그러나 실제 장면은 그렇지 않습니다. 디버깅을 통해 실제 모델은 다음과 같아야 한다는 것을 대략적으로 이해합니다.


Swoole을 사용하여 웹 페이지를 비동기식으로 크롤링하는 실제 공유

즉, 재시도 횟수를 아무리 늘려도 데이터는 절대 준비되지 않고 데이터만 준비됩니다. 현재 기능이 준비된 후 실행이 시작됩니다. 여기서 비동기식은 연결을 준비하는 시간만 단축합니다.
그럼 질문은 데이터를 준비한 후 프로그램이 내가 기대하는 기능을 수행하도록 하려면 어떻게 해야 하느냐는 것입니다.
먼저 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();

공식이 함수 익명 함수를 통해 후속 실행 로직을 전달하는 것을 볼 수 있습니다. 이렇게 보면 상황이 훨씬 단순해진다.

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 개발에서 어디에서나 볼 수 있는 콜백에는 나름의 이유가 있다는 것을 느꼈습니다. 이제 나는 비동기 문제를 해결하기 위해 콜백이 존재한다는 것을 갑자기 이해합니다.
프로그램을 실행했는데 0.0007초밖에 안걸리고 시작하기도 전에 끝났어요! 비동기 효율성이 실제로 그렇게 많이 향상될 수 있습니까? 물론 대답은 '아니요'입니다. 코드에 문제가 있습니다.
비동기식 사용으로 인해 작업이 완전히 완료될 때까지 기다리지 않고 종료 시간을 계산하는 논리가 실행되었습니다. 이제 다시 콜백을 사용할 때가 된 것 같습니다.

/**
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.26초가 걸리고 비동기화는 0.068초가 소요됩니다. 이는 전체 6.192초의 차이입니다. 아니, 좀 더 정확하게 말하면 거의 10배는 더 나빠질 것입니다!
물론 효율성 측면에서는 비동기 코드가 동기 코드보다 훨씬 높지만, 논리적으로 말하면 비동기 로직은 동기 코드보다 더 복잡하고 코드가 많은 콜백을 가져오기 때문에 이해하기 쉽지 않습니다.
Swoole 공식에는 매우 적절한 비동기식 및 동기식 선택에 대한 설명이 있습니다.

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

관련 읽기:

How to install swoole Extension with php7

Swoole 개발 포인트 소개

php 비동기 멀티스레드 swoole 사용 예시

위 내용은 모두 이 글의 내용입니다. 학생들이 질문이 있는 경우 댓글 영역에서 토론할 수 있습니다. 아래~

위 내용은 Swoole을 사용하여 웹 페이지를 비동기식으로 크롤링하는 실제 공유의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.