laravel中队列的使用(二)

2017-08-15 16:37:38

    上一文我们已经实现了队列,任务的基本模型,现在我们来实现具体的需求。也就是说从我们五个测试url中,放入队列中,然后提取相应的title标签。

    我们在前面已经在任务中拿到了url,下面开始

    在控制器中执行

    php artisan make:model Title -m

    生成title模型,以及相应的数据库结构文件,迁移文件中内容为

<?php    
    
    use Illuminate\Support\Facades\Schema;
    use Illuminate\Database\Schema\Blueprint;
    use Illuminate\Database\Migrations\Migration;
    
    class CreateTitlesTable extends Migration
    {
        /**
         * Run the migrations.
         *
         * @return void
         */
        public function up()
        {
            Schema::create('titles', function (Blueprint $table) {
                $table->increments('id');
                $table->string('title')->comment('采集到的标题');
                $table->string('url')->comment('目标网址');
                $table->integer('status')->comment('是否采集成功,0为失败,1为成功');
                $table->string('message')->comment('采集返回信息');
                $table->timestamps();
            });
        }
    
        /**
         * Reverse the migrations.
         *
         * @return void
         */
        public function down()
        {
            Schema::dropIfExists('titles');
        }
    }

相应model中内容为:

<?php    
    
    namespace App;
    
    use Illuminate\Database\Eloquent\Model;
    
    class Title extends Model
    {
        protected $fillable = [
            
            'title','url','status','message'
        ];
        //
    }


    执行数据库迁移命令:

    php artisan migrate


    在getTitlesByUrlListener.php监听器中,写入采集title标签逻辑事件,因为采集这一块,用python做起来比较便利,所以我们决定使用python来配合php进行工作,我们在app文件夹下建立Extensions\python\getTitleByUrl.py文件,具体内容为

#encoding=utf-8
from __future__ import unicode_literals
from bs4 import BeautifulSoup
import sys
import json
import urllib2



if(len(sys.argv) == 1):
    print 'Error'
else:
    data = {}
    url = sys.argv[1]
    user_agent = 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:54.0) Gecko/20100101 Firefox/54.0'
    headers = { 'User-Agent' : user_agent }
    req = urllib2.Request(url,None,headers)
    try:
        response = urllib2.urlopen(req)
    except urllib2.URLError, e:
        if hasattr(e, "reason"):
            reason = str(e.reason)
            data['status'] = 0
            data['msg'] = reason
            print json.dumps(data)
    else:
        try:
            soup = BeautifulSoup(response.read(),'lxml')
            data['status'] = 1
            data['msg'] = soup.title.string
            print json.dumps(data)

        except AttributeError as e:
            data['status'] = 0
            data['msg'] = '此网站没有设置title标签,奇葩'
            print json.dumps(data)

    其中使用到了python中的两个包,如果没有的话,可以通过

    sudo pip install beautifulsoup4
    sudo pip install lxml

    进行安装。下面我们在php中,只需要调用此脚本,并且把url传过去即可。getTitlesByUrlListener.php文件相应内容

<?php    
    
    namespace App\Listeners;
    
    use App\Events\getTitlesByUrlEvent;
    use Illuminate\Queue\InteractsWithQueue;
    use Illuminate\Contracts\Queue\ShouldQueue;
    use Illuminate\Support\Facades\Log;
    
    class getTitlesByUrlListener implements ShouldQueue
    {
        /**
         * Create the event listener.
         *
         * @return void
         */
        public function __construct()
        {
            //
        }
    
        /**
         * Handle the event.
         *
         * @param  getTitlesByUrlEvent  $event
         * @return void
         */
        public function handle(getTitlesByUrlEvent $event)
        {
            //
    
            $result = shell_exec("python ".__DIR__."/../Extensions/python/getTitleByUrl.py '".$event->url."'");
    
            Log::info($result);
        }
    }

    这时我们尝试刷新一下网址,发现浏览器瞬间就完成了响应,同时数据库里jobs表中多了五条记录,我们在控制台执行

    php artisan queue:work --daemon

    然后五条命令一条一条的全部执行成功,同时日志文件中得到了相应的json文件,这时可能有人跳出来问了,你一开始的需求不是1万条吗,现在你拿5条过来测试,坑爹呢这是,好吧,现在我们把数据换成1万条试试,控制器文件修改为:

<?php    
    
    namespace App\Http\Controllers;
    
    use App\Events\getTitlesByUrlEvent;
    use Illuminate\Http\Request;
    
    class StartController extends Controller
    {
        //
        public function index()
        {
            $weburls = [
    
                'http://www.baidu.com/',
                'http://www.taobao.com/',
                'http://www.qq.com/',
                'http://www.youku.com/',
                'http://www.muzilong.cn/',
            ];
            for($i=0;$i<2000;$i++)
            {
                foreach($weburls as $key => $value)
                {
                    event(new getTitlesByUrlEvent($value));
                }
            }
    
    
            return "ok";
        }
    }

    这下有了1万条记录了,但是从代码上来看,唉呀,实例化1万次 getTitlesByUrlEvent这个类,代码肯定是有点问题,先执行一下,虽然最终生成了1万条记录,但是真的是好慢啊,大概20秒左右浏览器才转完吧,这肯定不行,我们需求是秒响应完成才对。还得修改代码。


    这时我们需要换个思路,假如说一个用户上传上1万条记录,这样我们首先应该根据这个用户的id,把这1万条记录先入库,然后根据此用户的id生成一条任务即可,这样响应时间预想应该会得到不少的提升,说干就干,我们先在控制台执行

    php artisan make:model url -m

    生成相应的model以及迁移文件,名字起的很随意,凑合看啦。

    相应迁移文件内容为

<?php    
    
    use Illuminate\Support\Facades\Schema;
    use Illuminate\Database\Schema\Blueprint;
    use Illuminate\Database\Migrations\Migration;
    
    class CreateUrlsTable extends Migration
    {
        /**
         * Run the migrations.
         *
         * @return void
         */
        public function up()
        {
            Schema::create('urls', function (Blueprint $table) {
                $table->increments('id');
                $table->unsignedInteger('uid')->comment('用户uid');
                $table->string('url')->comment('相应链接');
                $table->integer('status')->default(0)->comment('采集状态');
                $table->timestamps();
            });
        }
    
        /**
         * Reverse the migrations.
         *
         * @return void
         */
        public function down()
        {
            Schema::dropIfExists('urls');
        }
    }

    相应Model文件内容为:

<?php    
    
    namespace App;
    
    use Illuminate\Database\Eloquent\Model;
    
    class Url extends Model
    {
        //
        protected $fillable = [
    
            'uid','url','status'
        ];
    }