新闻采集补完
前几天又采集了新闻,改了下之前写过的采集程序。
之前的采集就是用三对标签卡内容,给一个列表的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是页面的全局变量,是之前分析取的域名,做拼接用。处理链接长度时遇到要找一个数组中出现次数最多的值,用了先排序再找次数最多的值。入库时候,取图片保存图片也用了比较久的时间。
这只是一个简单的采集程序,有很多地方做的不好,并发也没有考虑到。年前都写的一半,一直到今天才整理了下。