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

浙公网安备 33010602011771号