最近的工作,採集一個分類資訊網站的資訊
阿新 • • 發佈:2019-05-16
使用技術:
環境: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/>");
}
}