PHP用Swoole实现爬虫(一)

基本概念

网络爬虫

网络爬虫(又被称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。另外一些不常使用的名字还有蚂蚁、自动索引、模拟程序或者蠕虫。

swoole

PHP的异步、并行、高性能网络通信引擎,使用纯C语言编写,提供了PHP语言的异步多线程服务器,异步TCP/UDP网络客户端,异步MySQL,异步Redis,数据库连接池,AsyncTask,消息队列,毫秒定时器,异步文件读写,异步DNS查询。 Swoole内置了Http/WebSocket服务器端/客户端、Http2.0服务器端。

技术方案

本来,公司的意愿是,写几个PHP脚本,使用linux定时任务crontab既可。后来我一琢磨,正好现在不是业务改动频繁期,而且,服务化是迟早要做的事情,因此,便开始我的爬坑旅程。 PHP是有一套比较成熟的异步常驻内存的框架的, workerman ,倒不是不采取,而是既然决定采用新的方案,正好也不赶工期,为何不挑战一下新技术呢?

爬坑之旅(一)

swoole协议选取

由于公司之前是没有进行过tcp连接的优化基础的大神在,因此我们采用是朴实的方案,http协议。 swoole是原生支持http服务器的,参见官网,开启一个http server是很简单的:

1$serv = new Swoole\Http\Server("127.0.0.1", 9502); 2 3$serv->on('Request', function($request, $response) { 4 var_dump($request->get); 5 var_dump($request->post); 6 var_dump($request->cookie); 7 var_dump($request->files); 8 var_dump($request->header); 9 var_dump($request->server); 10 11 $response->cookie("User", "Swoole"); 12 $response->header("X-Server", "Swoole"); 13 $response->end("<h1>Hello Swoole!</h1>"); 14}); 15 16$serv->start();

根据swoole官方的定义,http server是有几个常用事件的。

request,packet,pipeMessage,task,finish,receive,close,workerStart,workerStop,shutDown

仔细观察,一般的使用的情况下,由于receive事件swoole已经自动转发到了request事件,因此,最简单的例子,我们直接从request中激活一个爬虫就好了。

初始的代码
1class SwooleHttpServer implements Server 2{ 3 const EVENT = [ 4 'request'//,'packet','pipeMessage','task','finish','close' 5 ]; 6 protected $server; 7 protected $event = [ 8 9 ]; 10 //注意,这里使用了我上一篇文章关于PHP DI的实现 11 public function __construct(Config $config) 12 { 13 $server = $config->get('server'); 14 if(empty($server)) { 15 throw new \Exception('config not found'); 16 } 17 $this->server = new \swoole_http_server($server['host'], $server['port'], $server['mode'], $server['type']); 18 19 $extend = $config->get('event')['namespace'] ?? ''; 20 foreach (self::EVENT as $event) { 21 $class = $extend.'\\'.ucfirst($event); 22 23 if(!class_exists($class)) { 24 $class = '\\Kernel\\Swoole\\Event\\Http\\'.ucfirst($event); 25 } 26 /* @var \Kernel\Swoole\Event\Event $callback */ 27 $callback = new $class($this->server); 28 $this->event[$event] = $callback; 29 $this->server->on($event, [$callback, 'doEvent']); 30 } 31 $this->server->set($config->get('swoole')); 32 } 33 34 public function start(\Closure $callback = null): Server 35 { 36 if(!is_null($callback)) { 37 $callback(); 38 } 39 $this->server->start(); 40 return $this; 41 } 42 43 public function shutdown(\Closure $callback = null): Server 44 { 45 // TODO: Implement shutdown() method. 46 } 47 48 public function close($fd, $fromId = 0) : Server 49 { 50 $this->server->close($fd, $fromId = 0); 51 return $this; 52 }

为了方便业务的书写,我们把request事件解耦出单独的一个类

1namespace Kernel\Swoole\Event\Http; 2 3use Kernel\Swoole\Event\Event; 4use Kernel\Swoole\Event\EventTrait; 5 6class Request implements Event 7{ 8 use EventTrait; 9 /* @var \swoole_http_server $server*/ 10 11 protected $server; 12 protected $data = []; 13 14 public function __construct(\swoole_http_server $server) 15 { 16 $this->server = $server; 17 } 18 public function doEvent(\swoole_http_request $request, \swoole_http_response $response) 19 { 20 if(isset($request->server['request_uri']) and $request->server['request_uri'] == '/favicon.ico') { 21 $response->end(json_encode(empty($data)?['code'=>0]:$data)); 22 return; 23 } 24 $crawler = new Crawler(); 25 $crawler->run(); 26 }

现在我们请求IP所在端口,既可开始执行我们得爬虫类了

最初最简单的爬虫类
1namespace Library\Crawler; 2 3 4class Crawler 5{ 6 private $url; 7 private $toVisit = []; 8 9 public function __construct($url) 10 { 11 $this->url = $url; 12 } 13 14 public function visitOneDegree() 15 { 16 $this->visit($this->url, function ($content) { 17 $this->loadPage($content); 18 $this->visitAll(); 19 }); 20 } 21 22 23 private function loadPage($content) 24 { 25 $pattern = '#(http|ftp|https)://?([a-z0-9_-]+\.)+(com|net|cn|org){1}(\/[a-z0-9_-]+)*\.?(?!:jpg|jpeg|gif|png|bmp)(?:")#i'; 26 preg_match_all($pattern, $content, $matched); 27 foreach ($matched[0] as $url) { 28 if (in_array($url, $this->toVisit)) { 29 continue; 30 } 31 $this->toVisit[] = rtrim($url,'"'); 32 file_put_contents('urls',$url."\r\n",FILE_APPEND); 33 } 34 } 35 36 private function visitAll() 37 { 38 foreach ($this->toVisit as $url) { 39 $this->visit($url); 40 } 41 } 42 43 private function visit($url, $callback = null) 44 { 45 $urlInfo = parse_url($url); 46 \Swoole\Async::dnsLookup($urlInfo['host'], function ($domainName, $ip) use($urlInfo,$url,$callback) { 47 if($domainName == '' or $ip =='') { 48 return; 49 } 50 if(!isset($urlInfo['port'])) { 51 if($urlInfo['scheme'] == 'https') { 52 $urlInfo['port'] = 443; 53 }else{ 54 $urlInfo['port'] = 80; 55 } 56 } 57 if($urlInfo['scheme'] == 'https') { 58 $cli = new \swoole_http_client($ip, $urlInfo['port'], true); 59 }else{ 60 $cli = new \swoole_http_client($ip, $urlInfo['port']); 61 } 62 63 $cli->setHeaders([ 64 'Host' => $domainName, 65 "User-Agent" => 'Chrome/49.0.2587.3', 66 'Accept' => 'text/html,application/xhtml+xml,application/xml', 67 'Accept-Encoding' => 'gzip', 68 ]); 69 $cli->get($urlInfo['path']??'/', function ($cli) use (,$url) { 70 $data = $this->getMeta($cli->body); 71 //todo:将数据写到数据库 72 $cli->close(); 73 }); 74 75 }); 76 77 } 78 79 80 private function getMeta(string $data) 81 { 82 $meta = []; 83 ...... 84 return $meta; 85 } 86} 87

从现在开始,一套简单的爬虫程序既可使用了。 但是出现了一个问题:

swoole的worker数量受制于CPU有限,因此,一旦超出了worker是不会进行服务的,而我这里的爬虫,很明显是一个同步代码,举个栗子,开始我worker_num是5,我就最多同时开启5个爬虫任务,就算你想立即开启更多,也是会失败的【swoole分配不了更多的worker进程给请求】。相当于

1while(true){ 2 if($condition){ 3 break; 4 } 5}

引用一张官方的进程图:

输入图片说明

worker的数量是不能动态分配的。联系操作系统的知识,可以得到这样的结论,假如我有3个篮子(即worker[生产者]),每次有人来取走一个篮子去装东西(即request请求[消费者]),但是,篮子一直在被用着(while(true))没还回来。当有第4个人来拿篮子(请求)的时候,无法分配篮子,只能等待,假如之前的一直不释放,while(true)不退出,这就称之为死锁了。

因此,我们需要进行新一步的优化。 下一篇文章,我将讲述如何对swoole的进程进行优化,也就是最大的爬坑篇[swoole task]。

参考

swoole官方文档

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

手把手教你用Scrapy爬虫框架爬取食品论坛数据并存入数据库

大家好,我是杯酒先生,这是我第一次写这种分享项目的文章,可能很水,很不全面,而且肯定存在说错的地方,希望大家可以评论里加以指点,不胜感激!一、前言网络爬虫(又称为网页蜘蛛,网络机器人),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。另外一些不常使用的名字还有蚂蚁、自动索引、模拟程序或者蠕虫。百度百科    说人话就是,爬虫是用来海量规则化获取数据