新闻采集补完

前几天又采集了新闻,改了下之前写过的采集程序。

之前的采集就是用三对标签卡内容,给一个列表的URL,分别从列表开始结束标签、标题开始结束标签、内容开始结束标签六个标签去卡内容,前面的列表开始结束标签用来获取到新闻的链接,然后去内容页去取标题和内容。这次前面两对标签都不需要,标题从列表的文字去取,有局限(比如a标签里有div等标签)不过实际效果可以接受,列表标签对去掉怎样获取页面的新闻终极页链接,答案是:分析,也是改动最大的地方。提出精简版以做分析,抛砖引玉。

获取列表页内容

//编码转换->utf-8
function safeEncoding($str){
  $cod=array('ASCII','GB2312','GBK','UTF-8');
  $code=mb_detect_encoding($str,$cod);//检测字符串编码
  if($code=="UTF-8"){
    $result=$str;
  }
  else{
    //$result=iso88591_to_utf8($str);
    //$result=utf8_encode($result);
    //$result=iconv("UTF-8",$code."//IGNORE",$str);
    $result=mb_convert_encoding($str,'UTF-8',$code);//将编码$code转换为utf-8编码	
  }
  return $result;
}
//curl获取页面内容
function curls($url){
	$ch=curl_init();
	curl_setopt($ch,CURLOPT_URL,$url);
	curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
	curl_setopt($ch, CURLOPT_FOLLOWLOCATION,1);
	curl_setopt($ch,CURLOPT_HEADER,0);
	$strs=curl_exec($ch);
	$strs=safeEncoding($strs);
	curl_close($ch);
	if(empty($strs)) { echo '<br/>error:EMPTY!!<br/>';exit;}
	return $strs;
}

preg_match('/http[sS]?:\/\/([\.a-zA-Z0-9-]+)/is', $url, $prefix);
if(empty($prefix[0])) {
	echo 'error:'.$url.'不是正确地址!';
	exit;
}
$prefix=$prefix[0];
$strs=curls($url);

$url为新闻列表页地址,判断是不是正确的网址(中文不考虑)。然后调用curls()函数通过cUrl获取内容,中途调用safeEncoding()函数转换编码,但是CP936测试中有个站没成功,把所有编码列出来是ISO-8859-1最后也没解决,真是奇怪的编码呢。

核心思想

接下来获取页面中属于新闻的链接,排除冗余链接,核心思路:获取整个页面的链接->取长度->取相同长度最多的长度(±1)->再分析相似度(这里查「字符串 相似度」时发现Levenshtein算法,并且发现PHP里有同名函数可用),其中,在取最长长度的时候,先排序整个字符串再取。

//获取所有链接
preg_match_all('/<a href.[^<]+<\/a>/is',$strs,$x);
if(empty($x[0])){echo '<br/>error:链接获取错误。(编码/标签)!'; exit;}
$arra=array();

//取链接长度
foreach($x[0] as $k=>$v){
	preg_match_all('/href[=\'\" ]+(.[^< \'\" >]+)?/is',$v,$href);
	if(isset($href[1][0]) && (strlen($href[1][0])>5 ) ){
		$arra[]=strlen($href[1][0]);
		//$newsUrl[]=$href[1][0];
	}
}

//排序链接长度
insertSort($arra);

//找长度出现次数最多的
$mostArr=mostUrl($arra);
$mostNum=isset($mostArr[0])?intval($mostArr[0]):0;
if($mostNum==0){
	echo '<br/>error:新闻对应太少!';
	exit;
}

//获取链接及新闻标题
$newNewsUrl=array();
$newNewsTitle=array();
$news=array();
foreach($x[0] as $k=>$v){
	preg_match_all('/href[=\'\" ]+(.[^< \'\" >]+)?/is',$v,$href);
	if(isset($href[1][0]) && (strlen($href[1][0])>=($mostNum-1) ) && (strlen($href[1][0])<=($mostNum+1) ) ){   //if(isset($href[1][0]) && (strlen($href[1][0])>=($mostNum-1) ) && (strlen($href[1][0])<=($mostNum+1) ) ){   | if(isset($href[1][0]) && (strlen($href[1][0])==$mostNum ) ){ 
		$news[]=$v;
		$newNewsUrl[]=$href[1][0];  //键值用$k就行,只做和title对应关系。其他地方没用
		preg_match_all('/>(.[^<>]+)</is', $v, $title);
		if(isset($title[1][0]) && (strlen($title[1][0])>3*3 ) ){  //标题长度判断,小于3*3(utf-8中文为3),留待
			$newNewsTitle[]=$title[1][0];
		}else{
			$newNewsTitle[]='';
		}
	}
}

//删除重复元素
$newNewsUrl=array_unique($newNewsUrl);
//相似度分析,保留所允许相似度以内的新闻链接
leven($newNewsUrl);
//拼接有效链接
finalUrl($newNewsUrl);

//最后通过链接和标题判断是否保留此条新闻
$news=array();
for($i=0;$i<(count($newNewsTitle));$i++){ //$newNewsUrl删过,所以此处用完全体$newNewsTitle来做最大循环
	if(isset($newNewsUrl[$i]) && !empty($newNewsTitle[$i]) ){
		$news[$i]['url']=$newNewsUrl[$i];
		$news[$i]['title']=$newNewsTitle[$i];
	}
}

echo '<br/><a href="'.$url.'">'.$url.'</a>共有待采集新闻'.count($news).'条<br/>';
//之后取新闻入库

用到的函数:

//插入排序
function insertSort(&$arr,$show=false){
	for($i=1;$i<count($arr);$i++){
		if($arr[$i]<$arr[$i-1]){
			$j=$i-1;
			$sentry=$arr[$i];
			$arr[$i]=$arr[$i-1];
			while(isset($arr[$j]) && ($sentry < $arr[$j])){
				$arr[$j+1]=$arr[$j];
				$j--;
			}
			$arr[$j+1]=$sentry;
		}
		if($show){
			echo $i.': ';
			for($k=0;$k<=$i;$k++){
				echo $arr[$k].' ';
			}
			echo '<br/>';
		}
	}
}
//出现次数最多的链接
function mostUrl($arr,&$href=array()){
	$count=1;//累计次数
	$maxCount=0;//当前最大次数
	$mostArr=array();
	foreach($arr as $k=>$v){
		if($k>0 && ($arr[$k]==$arr[$k-1]))
			$count++;
		else 
			$count=1;
		
		if($count>$maxCount){
			$mostArr=array();
			$maxCount=$count;
			$mostArr[]=$v;
		}
		else if($count == $maxCount){
			$mostArr[]=$v;
		}
	}

	return $mostArr;
}
//查相似度
function leven(&$arr){
	$sentry=NULL;
	$flag=intval(count($arr)/2);
	while(!isset($arr[$flag])){
		$flag--;
	}
	$sentry=$arr[$flag];
	foreach($arr as $k=>$v){
		$distance=levenshtein($sentry,$v);
		//echo '<br/>'.$k.'-'.$distance;
		if($distance>DISTANCE) unset($arr[$k]); 
	}
	 
}
//url拼接
function finalUrl(&$arr){
	if(!is_array($arr) || empty($arr)){
		echo '<br/>error:URL最终拼接失败,原因:不存在数组!';
		exit;
	}
	global $prefix;
	if(empty($prefix)){
		echo '<br/>warning:前缀不存在,请注意拼接错误!';
		exit;	
	}
	if(strpos(reset($arr),'http')!==false) return;
	 
	foreach($arr as $k=>$v){
			$arr[$k]=$prefix.$v;
	}
}  

总结

这种获取方式比较暴力初级,遇到国外站多是以英文名来做链接的站就无能为力了,因为长度差了允许范围太多。分析相似度函数leven()里的 DISTANCE 是页面上定义的一个常量,用来做相似度中字符串距离替换的最大值用,finalUrl()中出现的$prefix是页面的全局变量,是之前分析取的域名,做拼接用。处理链接长度时遇到要找一个数组中出现次数最多的值,用了先排序再找次数最多的值。入库时候,取图片保存图片也用了比较久的时间。

这只是一个简单的采集程序,有很多地方做的不好,并发也没有考虑到。年前都写的一半,一直到今天才整理了下。

 

posted @ 2016-12-28 17:02  姜小豆  阅读(217)  评论(0编辑  收藏  举报