1. 程式人生 > >最近的工作,採集一個分類資訊網站的資訊

最近的工作,採集一個分類資訊網站的資訊

使用技術:

環境: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/>");



    }





}