laravel的任务调度(cron)和队列一起使用制作采集器

说明!注意事项!

  • 看下面的教程!还是看一遍laravel中文文档是很有必要的!因为我是看中文文档、写出来的程序
  • 本人使用的是阿里云(1核CPU|1GB内存|1M带宽|centos6.5-64位系统|系统盘20G|laravel5.2)的配置
  • 所有的队列(queue)和任务调度(console)里面的数据库DB操作!都需要使用 DB类库!不可以使用orm!区别是mysql长链接和短链接……
  • 任务调度(console)里面只做数据库的查询——处理抛给队列处理!因为这样你的系统进程会很干净!不会又出现挂断或者假死等等!注意一点 程序结束必须有 return
  • 队列(queue)里面也是每一个 function 都要书写 return
  • 不管是在任务调度里面、队列任务里面、队列任务里面再次调用队列任务!都使用全局队列任务推送函数:dispatch!如何使用看下面操练代码
  • 重要一点!要排除所以的bug
  • 看代码为主!文字为辅助

    队列(queue)

  • 很多文档都在说队列、使用数据库、如果真正上线了使用数据库是不行的!一定修改成redis的!###一会说一下修改redis的流程
  • 做队列调试直接使用!数据库队列进行调试!

    数据库队列——开发与调试

  • 以下操作可以直接在现有项目上面执行!

    1
    2
    3
    php artisan queue:table #为数据库队列生成数据库表
    php artisan migrate #为数据库队列创建数据库表
    php artisan make:job ArtCaiji #创一个队列工作任务!这个是处理文章内容的采集
  • 以下是队列工作任务文件里面的代码 注意看!我给大家注释

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    <?php
    namespace App\Jobs;
    use App\Jobs\Job;
    use Illuminate\Queue\SerializesModels;
    use Illuminate\Queue\InteractsWithQueue;
    use Illuminate\Contracts\Queue\ShouldQueue;
    //以上都是自动生成的代码不需要管理
    use DB, phpQuery, Log; //这里调用了3个类库!DB是数据库操作类库、phpquery是采集类库、log是日志类库
    /*
    log类库主要是用来做调试 打印输出
    phpquery类库主要是用来做 网页上面的信息采集
    db类库主要是用来做 数据库的处理
    代码里面没有使用中间件!怕大家晕……
    类里面的
    public function __construct($row)
    {
    //
    $this->row = $row;
    }
    不能省略这是队列工作任务类最重要的、传值方式!变量$row我传入的是一个对象值!然后把它赋给ArtCaiji类里面的私有对象$row;
    */
    class ArtCaiji extends Job implements ShouldQueue
    {
    use InteractsWithQueue, SerializesModels;
    protected $row;
    /**
    * Create a new job instance.
    *
    * @return void
    */
    public function __construct($row)
    {
    //
    $this->row = $row;
    }
    /**
    * 下面是我们要执行的代码
    *
    * @return void
    */
    public function handle()
    {
    //
    $metas = array();
    $res = array();
    if($this->row){
    $html = $this->get_url_content($this->row['href']);
    if($html){
    phpQuery::newDoclamb($html);
    foreach(pq('meta') as $meta){
    $key = pq($meta)->attr('name');
    $value= pq($meta)->attr('content');
    $metas[strtolower($key)] = $value;
    }
    $title = trim(pq('.head-wrap .title')->text());
    $articles = DB::table('articles')->where('art_title', $title)->first();
    if($articles){
    //Log::info('文章已经存在===='.$title);
    phpQuery::$documents = array();
    return true;
    }else{
    $insertedId = DB::table('articles')->insertGetId([
    'user_id' => '10000',
    'cate_id' => $this->row['cate_id'],
    'art_title' => $title,
    'art_tags' => $metas['keywords'],
    'copy_from' => '本站原创',
    'copy_url' => 'http://www.webshowu.com',
    'art_intro' => $metas['description'],
    'art_content' => $this->ImgFindShift_5118(pq('.content')->html()),
    'art_views' => '10',
    'art_status' => '3',
    'created_at' => date('Y-m-d H:i:s',time()),
    'updated_at' => date('Y-m-d H:i:s',time()),
    ]);
    if($insertedId){
    $result = $this->zhanzhang_push_baidu("http://www.webshowu.com/artinfo-".$insertedId.".html");
    }
    }
    phpQuery::$documents = array();
    return true;
    }else{
    Log::info(var_export($this->row,true));
    return true;
    }
    }else{
    Log::info('对象是空值');
    return true;
    }
    }
    /*专业处理5118文章图片*/
    function ImgFindShift_5118($html){
    if($html){
    $array = array();
    $dochtml = phpQuery::newDoclamb($html);
    foreach(pq('img') as $img){
    $val['src1'] = pq($img)->attr('data-original');
    $val['src2'] = pq($img)->attr('src');
    $array[] = $val;
    }
    foreach($array as $key => $str){
    pq("img:eq($key)")->attr('src',$str['src1']);
    }
    return $dochtml;
    }else{
    return false;
    }
    }
    /** 获取指定URL内容 */
    function get_url_content($url, $proxy = true) {
    $data = '';
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HEADER ,0);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT,60);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER,1); // 设置不将爬取代码写到浏览器,而是转化为字符串
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
    if($proxy){
    //AppKey 信息,请替换
    $appKey = 'xxx';
    //AppSecret 信息,请替换
    $secret = 'xxxx';
    //示例请求参数
    $paramMap = array(
    'app_key' => $appKey,
    'timestamp' => date('Y-m-d H:i:s'),
    'enable-simulate' => 'false',
    );
    //按照参数名排序
    ksort($paramMap);
    //连接待加密的字符串
    $codes = $secret;
    //请求的URL参数
    $auth = 'MYH-AUTH-MD5 ';
    foreach ($paramMap as $key => $val) {
    $codes .= $key . $val;
    $auth .= $key . '=' . $val . '&';
    }
    $codes .= $secret;
    //签名计算
    $auth .= 'sign=' . strtoupper(md5($codes));
    curl_setopt($ch, CURLOPT_HTTPHEADER, array("Proxy-Authorization: {$auth}"));
    curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC); //代理认证模式
    curl_setopt($ch, CURLOPT_PROXY, 'xxxx'); //代理服务器地址
    curl_setopt($ch, CURLOPT_PROXYPORT, 'xxx'); //代理服务器端口
    curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); //使用http代理模式
    }
    $data = curl_exec($ch);
    if ($data === FALSE) {
    return false;
    }
    curl_close($ch);
    if (!$data) {
    return false;
    } else {
    $encode = mb_detect_encoding($data, array('ascii', 'gb2312', 'utf-8', 'gbk'));
    if($encode != 'utf-8'){
    if($encode == 'EUC-CN' || $encode == 'CP936'){
    $data = @mb_convert_encoding($data, 'utf-8', 'gb2312');
    }else{
    $data = @mb_convert_encoding($data, 'utf-8', $encode);
    }
    }
    return $data;
    }
    }
    /** 百度站长工具推送代码 **/
    function zhanzhang_push_baidu($url){
    $urls = array($url);
    $api = 'http://data.zz.baidu.com/urls?site=www.webshowu.com&token=6ujhg0alnRLbwZr7';
    $ch = curl_init();
    $options = array(
    CURLOPT_URL => $api,
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POSTFIELDS => implode("\n", $urls),
    CURLOPT_HTTPHEADER => array('Content-Type: text/plain'),
    );
    curl_setopt_array($ch, $options);
    $result = curl_exec($ch);
    return $result;
    }
    }

上线之后修改成redis

  • 必须安装好redis
  • 把代码 “predis/predis”: “~1.0” 添加到composer.conf文件的require里面:代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    {
    "name": "laravel/laravel",
    "description": "The Laravel Framework.",
    "keywords": ["framework", "laravel"],
    "license": "MIT",
    "type": "project",
    "require": {
    "php": ">=5.5.9",
    "laravel/framework": "5.2.*",
    "jenssegers/agent": "^2.3",
    "stevenyangecho/laravel-u-editor": "~1.3",
    "overtrue/laravel-lang": "1.0.*",
    "predis/predis": "~1.0"
    },
    "require-dev": {
    "fzaninotto/faker": "~1.4",
    "mockery/mockery": "0.9.*",
    "phpunit/phpunit": "~4.0",
    "symfony/css-selector": "2.8.*|3.0.*",
    "symfony/dom-crawler": "2.8.*|3.0.*"
    },
    "autoload": {
    "classmap": [
    "database",
    "app/libs/phpQuery"
    ],
    "psr-4": {
    "App\\": "app/"
    }
    },
    "autoload-dev": {
    "classmap": [
    "tests/TestCase.php"
    ]
    },
    "scripts": {
    "post-root-package-install": [
    "php -r \"copy('.env.example', '.env');\""
    ],
    "post-create-project-cmd": [
    "php artisan key:generate"
    ],
    "post-install-cmd": [
    "php artisan clear-compiled",
    "php artisan optimize"
    ],
    "post-update-cmd": [
    "php artisan clear-compiled",
    "php artisan optimize"
    ]
    },
    "config": {
    "preferred-install": "dist"
    }
    }
  • 执行 composer update 代码!

  • 修改 redis 的配置信息 文件位置是:config/database.php 里面的 redis数组
  • 修改 队列 的配置信息 文件位置是: config/queue.php 里面的 default 值修改成redis
  • 如果你看到的都是这样书写代码 env(‘QUEUE_DRIVER’, ‘sync’) 请到项目跟目录下面修改 .env 文件
  • 啊……!就这样简单!是的

    执行队列

  • 建议调试的时候直接使用 php artisan queue:listen
  • 线上直接使用supervisor安装配置使用都在这个网址里面https://phphub.org/topics/2126

    任务调度(cron)

  • 重要的是要保证进程不会假死!挂掉!不能有一丝丝php错误信息!每一个 function 都需要return !正常执行完毕程序、进程会自动推出!我强烈建议每一个调度任务都建立一个console

    建立调度任务并编写代码

  • 下面的程序可以直接执行

    1
    php artisan make:console CaiJiQi #创建调度任务命令文件 位置是 app/Console/Commands 文件里面
  • 下面是CaiJiQi.php文件里面的代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    <?php
    namespace App\Console\Commands;
    use Illuminate\Console\Command;
    //以上是自动生成的
    use App\Jobs\RegList; //这里我调用了队列RegList 采集规则 处理程序
    use DB, Log; //这里调用了2个类库!DB是数据库类库、log是日志类库
    class caijiqi extends Command
    {
    /**
    * 设置用 php的artisan工具调用名字
    * 调用代码直接在命令这样写 php artisan caiji 就执行现在这个文件
    * @var string
    */
    protected $signature = 'caiji';
    /**
    * 设置写 这个命令工具的描述
    * 只能写英文的 中文会乱码 因为编码的问题
    * @var string
    */
    protected $description = 'zhu yao shi yong lai zuo ding shi cai ji';
    /**
    * 这一行可能有些朋友基础不好的!是看不明白!
    * 加载这个类的时候、直接执行父类的析构函数方法
    * @return void
    */
    public function __construct()
    {
    parent::__construct();
    }
    /**
    * 这才是我们的关键!可以开始写要执行的代码了!
    * 因为我使用不到传值!所以先不写了!你们需要那就去看laravel中文文档
    * @return mixed
    */
    public function handle()
    {
    //
    $reg_list = DB::table('Reg_List')->where('reg_status','3')->get(); //读取采集规则表里面所以规则
    foreach($reg_list as $str){
    dispatch(new RegList($str)); //然后遍历把它 推送给 RegList 采集规则队列
    }
    return true; //不能减少return啊!
    }
    }
  • 由于处理采集规则是一个长耗时的工作!所以我又创建了一个队列文件 RegList

  • 下面是RegList.php文件里面的代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    <?php
    namespace App\Jobs;
    use App\Jobs\Job;
    use Illuminate\Queue\SerializesModels;
    use Illuminate\Queue\InteractsWithQueue;
    use Illuminate\Contracts\Queue\ShouldQueue;
    //以上代码是自动生成的
    use App\Jobs\ArtCaiji;///这里我调用了队列ArtCaiji 采集文章 处理程序
    use DB, phpQuery, Log;
    /*
    log类库主要是用来做调试 打印输出
    phpquery类库主要是用来做 网页上面的信息采集
    db类库主要是用来做 数据库的处理
    代码里面没有使用中间件!怕大家晕……
    类里面的
    public function __construct($row)
    {
    //
    $this->row = $row;
    }
    不能省略这是队列工作任务类最重要的、传值方式!变量$row我传入的是一个对象值!然后把它赋给RegList类里面的私有对象$row;
    */
    class RegList extends Job implements ShouldQueue
    {
    use InteractsWithQueue, SerializesModels;
    protected $row;
    /**
    * Create a new job instance.
    *
    * @return void
    */
    public function __construct($row)
    {
    //
    $this->row = $row;
    }
    /**
    * Execute the job.
    *
    * @return void
    */
    public function handle()
    {
    //
    Log::info('调用了一次==='.$this->row->reg_url);
    $html = $this->get_url_content($this->row->reg_url);
    if($html){
    $data = array();
    phpQuery::newDoclamb($html);
    foreach(pq($this->row->reg_list) as $res){
    $title = trim(pq($res)->text());
    if($this->row->reg_ishost){
    $href = $this->row->reg_host.pq($res)->attr('href');
    }else{
    $href = pq($res)->attr('href');
    }
    $data[] = $title.'======'.$href;
    }
    phpQuery::$documents = array();
    if($this->row->reg_content == json_encode($data)){
    //Log::info('采集结果一样=='.$this->row->reg_url);
    }else{
    $result = $this->ArtCaiji_array_diff($data,json_decode($this->row->reg_content,true));
    if($result){
    DB::table('Reg_List')->where('reg_id',$this->row->reg_id)->update(array('updated_at'=> date('Y-m-d H:i:s',time()),'reg_content'=>json_encode($data)));
    $this->ArtCaiji_queue($result,$this->row->cate_id);
    return true;
    }
    }
    }else{
    Log::info('这个网址已经失去效果=='.$this->row->reg_url);
    }
    }
    /*文章一维数组进行对比返回不一样的值*/
    function ArtCaiji_array_diff($array1,$array2){
    if($array2!='' && $array1!=''){
    $result = array_diff($array1,$array2);
    return $result;
    }
    if($array2 == '' && $array1 !=''){}
    {
    $result = $array1;
    return $result;
    }
    return false;
    }
    /*文章队列提交*/
    function ArtCaiji_queue($array,$cate_id){
    if($array){
    foreach ($array as $value) {
    $str = explode('======',$value);
    $result['title'] = $str['0'];
    $result['href'] = $str['1'];
    $result['cate_id'] = $cate_id;
    if($result['title'] != '' && $result['href'] != '' && $result['cate_id']){
    dispatch(new ArtCaiji($result));
    }
    }
    return true;
    }else{
    return true;
    }
    }
    /** 获取指定URL内容 */
    function get_url_content($url, $proxy = true) {
    $data = '';
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HEADER ,0);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT,60);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER,1); // 设置不将爬取代码写到浏览器,而是转化为字符串
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
    if($proxy){
    //AppKey 信息,请替换
    $appKey = 'xx';
    //AppSecret 信息,请替换
    $secret = 'xxxx';
    //示例请求参数
    $paramMap = array(
    'app_key' => $appKey,
    'timestamp' => date('Y-m-d H:i:s'),
    'enable-simulate' => 'false',
    );
    //按照参数名排序
    ksort($paramMap);
    //连接待加密的字符串
    $codes = $secret;
    //请求的URL参数
    $auth = 'MYH-AUTH-MD5 ';
    foreach ($paramMap as $key => $val) {
    $codes .= $key . $val;
    $auth .= $key . '=' . $val . '&';
    }
    $codes .= $secret;
    //签名计算
    $auth .= 'sign=' . strtoupper(md5($codes));
    curl_setopt($ch, CURLOPT_HTTPHEADER, array("Proxy-Authorization: {$auth}"));
    curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC); //代理认证模式
    curl_setopt($ch, CURLOPT_PROXY, 'xxx'); //代理服务器地址
    curl_setopt($ch, CURLOPT_PROXYPORT, 'xxx'); //代理服务器端口
    curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); //使用http代理模式
    }
    $data = curl_exec($ch);
    if ($data === FALSE) {
    return false;
    }
    curl_close($ch);
    if (!$data) {
    return false;
    } else {
    $encode = mb_detect_encoding($data, array('ascii', 'gb2312', 'utf-8', 'gbk'));
    if($encode != 'utf-8'){
    if($encode == 'EUC-CN' || $encode == 'CP936'){
    $data = @mb_convert_encoding($data, 'utf-8', 'gb2312');
    }else{
    $data = @mb_convert_encoding($data, 'utf-8', $encode);
    }
    }
    return $data;
    }
    }
    }
文章目录
  1. 1. 说明!注意事项!
  2. 2. 队列(queue)
    1. 2.1. 数据库队列——开发与调试
    2. 2.2. 上线之后修改成redis
    3. 2.3. 执行队列
  3. 3. 任务调度(cron)
    1. 3.1. 建立调度任务并编写代码