最近的工作,采集一个分类信息网站的信息

使用技术:

环境:php7+mysql5.5+apache

thinkphp5+queryList+CurlMulti

流程:

1.采集出所有的城市列表,字段有城市名,城市二级域名,等等  共374条信息

2.采集出所有的一级/二级栏目名称和url 共274条信息

3.采集出所有的栏目下面对应的标签,并去重而且关联.

4.采集根据城市和栏目交叉,得到 374*274 差不多八万多条url,也就是每个城市下的每个分类.

5.采集每个栏目下面的标签组,并与栏目相关联.

6.采集文章详情,然后把数据加入到本地缓存中.

下面是部分的代码逻辑和思路,感觉方法有点儿土,有点儿慢...但是能用...

//1.采集城市的控制器

<?php
namespace app\index\controller;


use think\Controller;
use QL\QueryList;
use think\Db;

$GLOBALS["main_url"]="http://***********";//采集的网站
$GLOBALS["cache_time"]=86400;//缓存时间
$GLOBALS["site_id"]=1;//站点id

class City extends Controller
{

    public function index(){
        $list=$this->get_citys();
        var_dump($list);
    }


    /**
     * @throws \think\Exception
     * @throws \think\exception\PDOException
     * @remark 更新城市列表信息
     */
    public function save_to_table(){
        $list=$this->get_citys();
        if(empty($list)){
            $this->error("采集数据为空!");
        }


        //删除过去的数据
        $del_res=Db::name("city")
            ->where(["site_id"=>$GLOBALS["site_id"]])
            ->delete();


        $Sugar=new Sugar();
        $city_data=[];
        foreach ($list as $k=>$v){

            $prov_data=[
                "site_id"=>$GLOBALS["site_id"],
                "pid"=>"0",
                "name"=>$v["name"],
                "original_url"=>$v["url"],
                "status"=>1,
                "initial_letter"=>$Sugar->getFirstCharter($v["name"])
            ];



            //插入一条省信息,并获取ID
            $pid=Db::name("city")
                ->insertGetId($prov_data);


            if(!empty($v["children"])){
                //设置市的数据
                foreach ($v["children"] as $k2=>$v2){
                    $city_data[]=[
                        "site_id"=>$GLOBALS["site_id"],
                        "pid"=>$pid,
                        "name"=>$v2["name"],
                        "original_url"=>$v2["url"],
                        "status"=>1,
                        "initial_letter"=>$Sugar->getFirstCharter($v2["name"])
                    ];

                }


            }

        }





        $res=Db::name("city")->insertAll($city_data);
        $msg="成功更新了".$res."条市表数据";
        $this->success($msg);


    }


    /**
     * @return array|void
     * @remark 获取城市信息和链接
     */
    public function get_citys(){

        $selector="#content>div:first-child";
        $content=QueryList::get($GLOBALS["main_url"])->find($selector)->html();


        //1.获取省

        //获取一级的省的链接和url
        $prov_range=".parent";
        $prov_rules=[
            "name"=>["span.weui_cell_bd","text"],
            "url"=>["","href"]
        ];
        $prov_list=QueryList::html($content)
            ->rules($prov_rules)
            ->range($prov_range)
            ->queryData();


        //2.获取直辖市

        //获取直辖市的名称和url
        $municipality_rules=[
            "name"=>[">a:not('.parent') span.weui_cell_bd","text"],
            "url"=>[">a:not('.parent')","href"]
        ];
        $municipality_list=QueryList::html($content)
            ->rules($municipality_rules)
            ->queryData();


        //3.获取市

        //获取市的数据
        $city_range=".children";
        $city_rules=[
            "list"=>["","html"]
        ];
        $city_list=QueryList::html($content)
            ->rules($city_rules)
            ->range($city_range)
            ->queryData(function ($item){
                $list_rules=[
                    "name"=>[".weui_cell .weui_cell_bd","text"],
                    "url"=>[".weui_cell","href"]
                ];
                $item["list"]=QueryList::html($item["list"])
                    ->rules($list_rules)
                    ->queryData();

                return $item;

            });



        //4.合并数据(将省市,直辖市 合并为一个数组)
        if(empty($prov_list)||empty($city_list)||empty($municipality_list)){
            //
            return $this->error("数据采集失败!");
        }

        $list=[];
        foreach ($municipality_list as $k=>$v){
            $list[]=$v;
        }
        foreach ($prov_list as $k=>$v){
            $v["url"]=$v["url"]=='javascript:;'?null:$v["url"];
            $v["children"]=$city_list[$k]["list"];
            $list[]=$v;
        }



        return $list;

    }







}

//2.采集标签表

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2019/5/6
 * Time: 11:41
 */

namespace app\index\controller;



use think\Cache;
use think\Controller;
use QL\QueryList;
use think\Db;


$GLOBALS["site_id"]=1;//站点id
$GLOBALS["main_url"]="http://******";//站点

/**
 * Class Tag
 * @package app\index\controller
 * 采集所有的标签
 */
class Tag extends Controller{

    /**
     * 1.栏目/二级栏目
     * 2.标签/二级标签
     * 3.文章
     *
     * 三者的联系?
     *
     *
     * 1.栏目/二级栏目 后缀url相同
     * 2.一级标签/二级标签 以ABCD+数字 为链接做筛选
     * 3.每一个二级栏目的 标签列表,都不完全相同
     * 4.  一组标签 对应  多个二级栏目
     * 5.采集的时候,可以获取的数据  (一二级的标签组+栏目url)=>栏目和标签对应表
     * 6.去重复 一级栏目去重(easy)  二级栏目去重(每一组二级栏目,判断三条是否重复  如果不重复,再插入数据库)
     */



    public function gather(){
        //获取等待采集的队列url
        $queue_list=Db::name("tag_queue")
            ->where(["status"=>0])
            ->limit(5)
            ->select();
        //如果为空,更新一下
        if(empty($list)){
            $this->set_gather_list();

            $queue_list=Db::name("tag_queue")
                ->where(["status"=>0])
                ->limit(5)
                ->select();

        }

        //采集好的数据列表
        $data_list=$this->get_tags($queue_list);


        $TagTask=new TagTask();
        $count=0;
        foreach ($data_list as $k=>$v){
            $list=$v;
            //队列信息
            $queue_info=$queue_list[$k];

            //数据不为空,进入数据库流程...
            if(!empty($list)){
                $tag_list=[
                    "cate_info"=>$queue_info,
                    "data_list"=>$list
                ];

                //调用入库方法
                $logs=$TagTask->index($tag_list);
                //修改队列状态
                $logs=json_encode($logs);
                Db::name("tag_queue")
                    ->where(["id"=>$queue_info["id"]])
                    ->update(["log"=>$logs,"status"=>1]);

                $count+=1;

            }

            //数据为空,跳过...
            if(empty($list)){
                continue;
            }



        }

        //采集开始



        $this->success("成功采集".$count."条!","tag/gather","",60);

    }

    //设置采集队列表
    /**
     * @return int|string
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\ModelNotFoundException
     * @throws \think\exception\DbException
     * 根据栏目,设置采集列表
     *
     */
    public function set_gather_list(){
        $cate_list=Db::name("category")
            ->where("pid","neq","0")
            ->order("id","asc")
            ->select();


        //将所有要采集的列表,加入到队列中
        $count=0;
        foreach ($cate_list as $k=>$v){


            $queue_data=[
                "cate_id"=>$v["id"],
                "cate_pid"=>$v["pid"],
                "status"=>0,
                "title"=>$v["title"],
                "original_link"=>$v["original_link"],
                "original_id"=>$v["original_id"],
            ];

            try{

                $res=Db::name("tag_queue")->insert($queue_data);
                $count=$count+1;

            }catch (\Exception $exception){
                //跳过错误;
            }

        }



        return $count;
    }


    /**
     * @param $queue_list
     * @return array
     * @throws \think\Exception
     * @throws \think\exception\PDOException
     * 采集一个单页里面的标签数组
     */
    public function get_tags($queue_list){

        //0.初始化
        $ql = QueryList::getInstance();
        $res=[];

        foreach ($queue_list as $k=>$v){
            $url=$GLOBALS['main_url'].$v["original_link"];

            try{

                //1.
                //获取网页内容
                $html=$ql->get($url)->find('body')->html();


                $data = $ql->html($html)->rules(array(
                    'title_list' => array('#search_title','html'),
                    'tag_list' => array('#search_selection','html')
                ))->range('')->queryData(function($item){

                    //获取title列表
                    $item["title_list"]= QueryList::html($item['title_list'])->rules(array(
                        'title' => array('a','text'),
                    ))->range('')->queryData();


                    //获取分片标签详情里面的数据块
                    $item["tag_list"]= QueryList::html($item['tag_list'])->rules(array(
                        'list' => array('','html'),
                    ))->range('.dd')->queryData(function ($item2){

                        //获取标签详情块里面的数据列表
                        $item2['list']=QueryList::html($item2['list'])->rules(array(
                            'name' =>['a','text']
                        ))->queryData();

                        return $item2;
                    });


                    return $item;


                });


            }catch (\Exception $e){

                if($e->getCode()=='403'||$e->getCode()=='500'){
                    $logs=[
                        "code"=>$e->getCode()
                    ];
                    $logs=json_encode($logs);
                    Db::name("tag_queue")
                        ->where(["id"=>$v["id"]])
                        ->update(["log"=>$logs,"status"=>-1]);
                }

                //设定数据为空
                $data=[];

                //销毁变量,释放内存
                unset($html);
            }



            //如果数据为空,直接返回空数组,跳过此次循环
            if(empty($data)){
                $res[]=$data;
                continue;
            }


            //数据不为空,合并结果集
            $list=[];

            $data=$data[0];
            foreach ($data["title_list"] as $k=>$v){
                $list[$k]["title"]=$v["title"];

                //
                $tag_list=$data["tag_list"][$k]["list"];
                unset($tag_list[0]);

                $list[$k]["list"]=$tag_list;
            }



            //销毁变量,释放内存
            unset($data);
            unset($html);

            $res[]=$list;
        }




        return $res;


    }



}

 

//3.采集的信息,入库操作

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2019/5/7
 * Time: 15:51
 * tag标签任务表
 */

namespace app\index\controller;


use think\Controller;
use think\Db;



/**
 * Class TagToDB
 * @package app\index\controller
 * remark:标签数据的创库和入库流程
 * 涉及到3张表 表1.一级标签的数据库表:tag_title_map  表2.标签与栏目关联表tag_relation  表3.各种标签详情表:tag_(拼音)_(数字)
 * 拆分流程
 * 1.根据一级标签的拼音找出所有的标签表=>1.找得到  2.找不到=>流程4
 * 2.查询数据表,每个表查找两条数据,查询是否重复
 * 3.  数据重复=>添加一条栏目与标签关联表信息,表2
 * 4.  数据未找到(不重复)=>创建表3,表1,表2 添加一条信息,表3插入列表
 *
 */
class TagTask extends Controller{

    public function test(){

    }

    public function index($tag_list){
        $log=[];//采集日志
        foreach ($tag_list["data_list"] as $k=>$v){
            $title=$v["title"];
            $res=$this->save_to_table($title,$v["list"],$tag_list["cate_info"]);
            $log[]=$res;
        }
        return $log;
    }

    public function save_to_table($title,$list,$cate_info){
        $Sugar=new Sugar();
        $pinyin=$Sugar->pinyin($title);
        $map_list=Db::name("tag_map")
            ->where(["pinyin"=>$pinyin])
            ->select();

        //判断list详情数据是否重复
        $is_repeat=false;
        $repeat_res=[];
        if(!empty($map_list)){
            //判断list数据是否重复,
            //得到数据如下:["is_repeat"=>"true/false","repeat_table_info"=>["id"=>"xx","table_name"=>"xxx","name"=>"xxx"]]
            $repeat_res=$this->check_repeat($list,$map_list);
            //结果为true/false
            $is_repeat=$repeat_res["is_repeat"];
        }

        //如果查询title的拼音为空表,或者list不重复
        if(empty($map_list)||$is_repeat==false){
            //1.进入创建表流程
            $create_res=$this->create_tag_table($title);
            //判断创建成功与否
            //创建成功=>添加数据,添加联系
            //创建失败=>跳过

            if($create_res["res"]==true){
                //2.往新建的表里面填充数据
                Db::name($create_res["table_name"])
                    ->insertAll($list);
                //3.添加一条栏目与标签表的联系
                $row_data=[
                    "cate_id"=>$cate_info["cate_id"],
                    "original_cate_id"=>$cate_info["original_id"],
                    "tag_map_id"=>$create_res["map_id"],
                    "table_name"=>$create_res["table_name"],
                ];
                $res=$this->add_tag_relation($row_data);
                $msg="创建新联系表:".$row_data["table_name"]."成功,添加数据成功!";
            }else{
                //创建表失败,跳过流程....
                $res=false;
                $msg="创建表失败,跳过流程!";
            }
        }

        if($is_repeat==true){
            //数据重复,添加一条栏目与标签的联系信息 ->tag_relation
            //res=>结果为true/false
            $row_data=[
                "cate_id"=>$cate_info["cate_id"],
                "original_cate_id"=>$cate_info["original_id"],
                "tag_map_id"=>$repeat_res["repeat_table_info"]["id"],
                "table_name"=>$repeat_res["repeat_table_info"]["table_name"]
            ];
            $res=$this->add_tag_relation($row_data);
            $msg="数据有重复,已成功关联相关栏目!";
        }


        return ["res"=>$res,"msg"=>$msg];

    }


    //检查采集的详情数据列表,在标签表里面是否有重复

    /**
     * @param $list
     * @param $map_list
     * @return array
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\ModelNotFoundException
     * @throws \think\exception\DbException
     */
    public function check_repeat($list,$map_list){


        $row1=array_shift($list);



        //初始化值
        $is_repeat=false;
        $repeat_table_info=[];

        //进入判断流程
        foreach ($map_list as $k=>$v){
            $info=Db::name($v["table_name"])
                ->where(["name"=>$row1["name"]])
                ->find();
            //如果在其中一张表里面,查询到了信息,那么说明该list数据已经存在
            if(!empty($info)){
                //赋值,跳出循环
                $is_repeat=true;
                $repeat_table_info=$v;
                break;
            }

        }


        //返回结果集
        $res=[
            "is_repeat"=>$is_repeat,
            "repeat_table_info"=>$repeat_table_info
        ];
        return $res;

    }


    //添加一条栏目与标签的联系数据
    public function add_tag_relation($row){
        $res=Db::name("tag_relation")->insert($row);
        return $res;
    }


    //创建表

    /**
     * @param $pinyin
     * @return array
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\ModelNotFoundException
     * @throws \think\exception\DbException
     * 创建tag标签详情表
     */
    public function create_tag_table($name){

        //1.初始化各种变量

        //获取拼音
        $Sugar=new Sugar();
        $pinyin=$Sugar->pinyin($name);

        //表前缀
        $table_prefix=config("database.prefix");
        //表中间
        $middle_table_str="tag";

        //表尾缀
        $table_suffix="1";
        //获取该拼音下最大的表尾缀
        $table_max_suffix_info=Db::name("tag_map")
            ->where(["pinyin"=>$pinyin])
            ->order("table_suffix","desc")
            ->find();
        if(!empty($table_max_suffix_info)){
            //最大尾缀+1 =第二张表的表尾缀
            $table_suffix=(int)$table_max_suffix_info["table_suffix"]+1;
        }


        //2.拼接表名

        //开始拼接表名
        $table_name=(string)$table_prefix.$middle_table_str."_".$pinyin."_".$table_suffix;


        //3.拼接sql

        //sql建表

        $res=false;
        try{

            $sql=<<<STR
CREATE TABLE `$table_name` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `pid` int(11) NOT NULL DEFAULT '0' COMMENT '父ID',
  `name` varchar(30) NOT NULL DEFAULT '' COMMENT '标签名',
  PRIMARY KEY (`id`),
  KEY `name` (`name`)
) ENGINE=MyISAM AUTO_INCREMENT=4800 DEFAULT CHARSET=utf8;
STR;



        //4.执行创表sql

            //执行创表操作
            Db::execute($sql);
            $res=true;

        //5.更新tag_map信息

            //更新一条tag_map信息
            $table_name=str_replace($table_prefix,"",$table_name);
            $map_data=[
                "name"=>$name,
                "pinyin"=>$pinyin,
                "table_name"=>$table_name,
                "table_suffix"=>$table_suffix
            ];
            $map_id=Db::name("tag_map")
                ->insertGetId($map_data);


        }catch (\Exception $exception){

            $res=false;
            $map_id="";

        }

        //6.返回信息和变量

        return [
            "res"=>$res,
            "table_name"=>$table_name,
            "map_id"=>$map_id
        ];

    }

}

//4.采集文章每个栏目下的文章列表数据(id,title,description,keywords)等等  //同事做的,给了一张news表

//5.根据news表,获取文章里面的详情数据

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2019/5/14
 * Time: 14:12
 */

namespace app\index\controller;
use QL\Ext\CurlMulti;
use QL\QueryList;
use think\Cache;
use think\Controller;
header("Content-type: text/html; charset=utf-8");
class ArcDetail extends Controller{
    public $site='**********';

    public function gather(){

        $this->index();
        $this->success("采集成功","arc_detail/gather","",30);

        try{

        }catch (\Exception $e){
            $this->error("采集失败","arc_detail/gather","",30);
        }
    }

    public function index(){
        $ql=QueryList::getInstance();
        $ql->use(CurlMulti::class);


        $site=$this->site;
        $arc_list=db("news")
            ->where(["arc_detail_collect_status"=>0])
            ->field(['id','original_link'])
            ->order("id","asc")
            ->limit(0,5)
            ->select();


        $url_list=array_column($arc_list,"original_link");
        foreach ($url_list as $k=>$v){
            $url=$site.$v;
            if(strpos($url,"//")){
                $url=str_replace("//","/",$url);
            }

            $url_list[$k]=$url;
        }





        if(empty($url_list)){
            echo("采集完毕");
            die();
        }

        $ql->curlMulti($url_list)->success(function (QueryList $ql,CurlMulti $curl,$r){
            echo "Current url:{$r['info']['url']} <br/>";
            $data = $ql->rules([
                'view_detail' => ['.view_detail','html','a p'],
                'memo' => ['.memo','html','a p']
            ])->query()->getData();
            $data=$data->all();


            $original_url_list=explode("/",$r['info']['url']);
            $original_id=end($original_url_list);
            $original_id=str_replace(".html","",$original_id);

            $this->set_arc_detail_data($original_id,$data);

        })->error(function ($errorInfo,CurlMulti $curl){

            echo("采集出错");
            //var_dump($errorInfo["info"]);die();

            $original_url_list=explode("/",$errorInfo['info']['url']);
            $original_id=end($original_url_list);
            $original_id=str_replace(".html","",$original_id);

            $up_data=[
                "arc_detail_collect_status"=>-3,
                "arc_detail_collect_log"=>'采集中出错,错误提示:'.$errorInfo['info']
            ];
            db("news")->where(["original_id"=>$$original_id])->update($up_data);


        })->start([
            // 最大并发数,这个值可以运行中动态改变。
            'maxThread' => 10,
            // 触发curl错误或用户错误之前最大重试次数,超过次数$error指定的回调会被调用。
            'maxTry' => 3,
            // 全局CURLOPT_*
            'opt' => [
                CURLOPT_TIMEOUT => 10,
                CURLOPT_CONNECTTIMEOUT => 1,
                CURLOPT_CONNECTTIMEOUT=>10,
                CURLOPT_SSL_VERIFYPEER=>0,
                CURLOPT_SSL_VERIFYHOST=>0,
                CURLOPT_USERAGENT=>$_SERVER['HTTP_USER_AGENT'],
                CURLOPT_FOLLOWLOCATION=>1,
                CURLOPT_AUTOREFERER=>1,
                CURLOPT_ENCODING=>'gzip,deflate',
                CURLOPT_TIMEOUT=>30,
                CURLOPT_HEADER=>0,
                CURLOPT_RETURNTRANSFER=>1,
            ],
            // 缓存选项很容易被理解,缓存使用url来识别。如果使用缓存类库不会访问网络而是直接返回缓存。
            'cache' => ['enable' => false, 'compress' => false, 'dir' => null, 'expire' =>86400, 'verifyPost' => false]

        ]);

        $ql->destruct();


    }


    public function set_arc_detail_data($original_id,$data){
        $arc_id=db("news")->where(["original_id"=>$original_id])->value("id");


        //找到ID后,判断信息是否为空?
        $is_empty=empty($data);

        //如果为空,把这条ID相关的文章采集状态设置为-1
        if($is_empty==true){
            $up_data=[
                "arc_detail_collect_status"=>-1,
                "arc_detail_collect_log"=>"文章获取信息为空"
            ];
            db("news")->where(["id"=>$arc_id])->update($up_data);
        }

        //不为空,设置缓存信息
        //然后根据文章ID,把这条数据的文章采集状态 设置为1
        if($is_empty!==true){
            $cache_name="arc_detail_".$arc_id;
            Cache::store("arc")->set($cache_name,$data);

            $up_data=[
                "arc_detail_collect_status"=>1,
                "arc_detail_collect_log"=>"文章采集成功,cache名称为:".$cache_name
            ];
            db("news")->where(["id"=>$arc_id])->update($up_data);

        }

        echo("采集结果:".$up_data["arc_detail_collect_log"]."<br/>");



    }





}

 

 

 

 

 

 

 

 

 

转载于:https://my.oschina.net/laobia/blog/3050371

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值