使用技术:
环境: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/>");
}
}