php解析html类(2)

之前写过一个1.0版的。bug还是有不少的

1.将html分成块的时候,保存的是每个块的所有字符。这种方法会浪费时间和内存。换成保存块在源码中的其实index和长度,则好太多了。

2.分块时只对< > ' " ! / 六个字符进行处理。这是有逻辑问题的。像<br/>,会被认为是结束标签而不是单标签。所以要对[<>'"!/a-zA-Z]进行处理。

3.将所有块分层,以及将没有闭合的开始标签和结束标签设为单标签。原先的方法是对开始标签进行入栈,对结束标签进行匹配:匹配成功则进行配对归约操作,中间的都是没闭合的开始标签,都设为单标签并且出栈;匹配失败,则是不合时宜的结束标签,设为单标签。

思路很清晰,也很好。但1.0的版本中分块函数里有多个for循环,出栈时栈的长度减少时,没有及时改变依赖栈的长度的变量。

修改后测试了几次,嵌套深度(level)进入html开始标签时是1,退出html结束标签时也是1.

4.GetAttribute函数。有些许问题。

5.对于img标签。之前没做处理。不过既然是做采集系统,图片当然要down下来。有可能图片是相对路径,是要做处理的。


htmlparser2.0版本程序源码如下(其实在是第10+个版本了)

<?php
/*
    时间:2015-12-25 19:04:34
    作者:阮家友
    QQ:1439120442
*/
header('Content-Type: text/html; charset=utf-8');
//定义常量 html文件分4种类型块  
//开始标签
define("HTML_TAG_ST",1);
//结束标签
define("HTML_TAG_END",2);
// <!开头 文档声明和注释
define("HTML_COMMENT",3);
//文本
define("HTML_TEXT",4);
//单标签
define("HTML_SINGLE_TAG",5);

//左方括号
define("CHAR_L_BRACKET","<");
//右方括号
define("CHAR_R_BRACKET",">");
//单引号
define("CHAR_S_QUOTE","'");
//双引号
define("CHAR_D_QUOTE",'"');
//与
define("CHAR_AND","&");
//感叹号
define("CHAR_GanTanHao",'!');
//斜杠 除法  Unix中表示目录
define("CHAR_XieGang",'/');
//反斜杠 转义 
define("CHAR_FanXieGang",'\\');
//tab键
define("CHAR_TAB",'\t');
//空格
define("CHAR_SPACE",' ');
//回车
define("CHAR_ENTENR",'\r');
//换行
define("CHAR_NewLine",'\n');
abstract class parser{
    //成员变量
    //路径
    public $path;
    //html源码
    private $htmlcode;
    //分块数组
    private $BlockArr;
    //分析层级栈 永远是开始标签
    private $Zhan;
    //层级变量
    public $level;
    //属性数组 在标记单标签时借用一下
    private $AttributesArr;
    //构造函数
    function __construct($path){
        $this->htmlcode = "";
        $this->path = $path;
        $this->BlockArr = array();
        $this->AttributesArr = array();
        $this->Zhan = array();
        $this->level=0;
    }
    //析构函数
    function __destruct(){}
    function GetCharSet($url){
        //返回的编码字符集
        $CharSet = "";
        //获取编码字符集
        $CharSet = preg_match("/<meta.+?charset=[^\w]?([-\w]+)/i",$this->htmlcode,$temp) ? strtoupper($temp[1]):"UTF-8";
        return $CharSet;
    }
    //获取HTML源码
    function LoadHTML(){
        $this->htmlcode = file_get_contents($this->path);
        $encode = $this->GetCharSet($this->path);
        if($encode!="UTF-8"){
            $this->htmlcode = mb_convert_encoding($this->htmlcode, 'utf-8', $encode);
        }
        
    }
    //将HTML源码分割为块
    function Split2Block(){
        $tempstr="";//保存截取的临时字符串
        $temp = array();//匹配結果
        $tagname = "";//标签名
        $CurrentPos = 0;//当前字符在字符串中的索引 下标 index
        $N = strlen($this->htmlcode);//字符串的长度
        $posL = 0;//块的左端位置
        $posR = 0;//块的右端位置
        $type = 0;//当前块的类型
        $bCommentOEndTag = false;
        $bSingleTag = false;
        $bTextStart = false;
        $bEndTag = false;
        // < 开始
        $bLSBracketStart = false;
        // > 开始
        $bRSBracketStart = false;
        // " 开始
        $bSQuoteStart = false;
        // ' 开始
        $bDQuoteStart = false;
        for($i=0;$i<$N;$i++){
            //只对<>"'!/感兴趣  后来加了字母是因为标准单标签后面的/
            if(!preg_match('/[<>\'"!\/a-zA-Z]/',$this->htmlcode[$i],$temp)){
                continue;
            }
            // 检测到 ! / StartTag Comment EndTag判断 < 开始设置了默认类型是开始标签 后面第一个字符是 ! 则本块是注释 是/ 则是结束标签
            if(true == $bLSBracketStart &&true==$bCommentOEndTag){
                // : ! 本块是注释
                if(CHAR_GanTanHao == $this->htmlcode[$i]){
                    $type = HTML_COMMENT;
                }
                // : / 并且不再字符串内 本块是结束标签
                if(CHAR_XieGang == $this->htmlcode[$i]&&false==$bSQuoteStart&&false==$bDQuoteStart){
                    $type = HTML_TAG_END;
                }
                //本次块的类型判断结束 还原状态 等待下次
                $bCommentOEndTag = false;
            }
            //检测到 " '单引号或双引号 true == $bLSBracketStart && HTML_TAG_ST==$type &&(
            if($bLSBracketStart && HTML_COMMENT!=$type && (CHAR_S_QUOTE==$this->htmlcode[$i]|| CHAR_D_QUOTE==$this->htmlcode[$i])){
                //单引号
                if(CHAR_S_QUOTE==$this->htmlcode[$i]){
                    //单引号开始过则此单引号进行闭合操作
                    if(true == $bSQuoteStart){
                        $bSQuoteStart = false;
                    }
                    //双引号开始过则跳过
                    else{
                        if(true == $bDQuoteStart){
                            continue;
                        }
                        //单引号开头的字符串开启
                        else{
                            $bSQuoteStart = true;
                        }
                    }
                }
                //双引号
                else{
                    //双引号开始过则闭合
                    if(true == $bDQuoteStart){
                        $bDQuoteStart = false;
                    }
                    //单引号开始过则跳过
                    else{
                        if(true == $bSQuoteStart){
                            continue;
                        }
                        //双引号开头的字符串开启
                        else{
                            $bDQuoteStart = true;
                        }
                    }
                }
            }//引号处理结束
            //检测到 <字符
            if(CHAR_L_BRACKET==$this->htmlcode[$i]){
                //如果在注释或字符串中则跳过
                if(HTML_COMMENT == $type||$bSQuoteStart||$bDQuoteStart){
                    continue;
                }
                //当前块类型为文本或>开始后遇到<
                //将当前块设为文本并push 重新开启 <
                if($bTextStart||$bLSBracketStart){
                    $bTextStart = false;
                    $temp = trim(substr($this->htmlcode,$posL,$i-$posL));
                    if(""!=$temp && $posL!=$i){
                        array_push($this->BlockArr,array(HTML_TEXT,"",array($posL,$i-$posL)));
                    }
                }
                $posL=$i;
                $type = HTML_TAG_ST;
                $bLSBracketStart = true;
                $bCommentOEndTag = true;
                continue;
            }// < 结束
            //检测到 >字符
            if(CHAR_R_BRACKET==$this->htmlcode[$i]){
                //在字符串内则跳过 或<没开始
                if($bSQuoteStart||$bDQuoteStart||false==$bLSBracketStart){
                    continue;
                }
                $posR = $i;
                $bRSBracketStart = true;
                //开始块 结束块 注释块在此处理
                //文本块不在此处理
                //截取出当前块的内容
                $tempstr = substr($this->htmlcode,$posL,$posR-$posL+1);
                switch($type){
                    case HTML_TAG_ST:
                        //提取标签名
                        $tagname = preg_match("/<\s*([a-zA-Z][a-zA-Z0-9]*)/i",$tempstr,$temp) ? strtolower($temp[1]):"STerror";
                        //分析是否是单标签
                        $bSingleTag = preg_match('/(\/\s*>)$/',$tempstr,$temp)?true:false;
                        
                        if($bSingleTag){
                            //将块压到目标数组中 是单标签
                            array_push($this->BlockArr,array(HTML_SINGLE_TAG,$tagname,array($posL,$posR-$posL+1)));
                        }
                        else{
                            //将块压到目标数组中 是开始标签或没正确闭合的标签
                            array_push($this->BlockArr,array(HTML_TAG_ST,$tagname,array($posL,$posR-$posL+1)));
                        }
                    break;
                    case HTML_TAG_END:
                        //提取标签名
                        $tagname = preg_match("/<\s*\/\s*(\w+)/i",$tempstr,$temp) ? strtolower($temp[1]):"ENDerror";
                        if("ENDerror"==$tagname){
                            array_push($this->BlockArr,array(HTML_TEXT,"",array($posL,$posR-$posL+1)));
                        }
                        else{
                            array_push($this->BlockArr,array(HTML_TAG_END,$tagname,array($posL,$posR-$posL+1)));
                        }
                    break;
                    case HTML_COMMENT:
                        //直接将当前块的内容压到目标数组中
                        array_push($this->BlockArr,array(HTML_COMMENT,"",array($posL,$posR-$posL+1)));
                    break;
                    default:break;
                }
                //闭合 <
                $bLSBracketStart = false;
                $bTextStart = true;
                $type = 0;
                //为文本做准备
                $posL = $i+1;
            }
        }
    }
    function MarkSingleTag(){
        $bMatched = false;
        for($i=0;$i<count($this->BlockArr)-1;$i++){
            if(HTML_COMMENT == $this->BlockArr[$i][0]||HTML_TEXT == $this->BlockArr[$i][0]||HTML_SINGLE_TAG == $this->BlockArr[$i][0]||"div" == $this->BlockArr[$i][1]){
                continue;
            }
            //开始标签
            if(HTML_TAG_ST == $this->BlockArr[$i][0]){
                array_push($this->Zhan,$i);
            }
            //结束标签
            else{
                for($j = count($this->Zhan)-1;$j>=0;$j--){
                    if($this->BlockArr[$this->Zhan[$j]][1]==$this->BlockArr[$i][1]){
                        $bMatched = true;
                        for($k = count($this->Zhan)-1;$k>=0;$k--){
                            if($this->BlockArr[$this->Zhan[$k]][1]!=$this->BlockArr[$i][1]){
                                //没关闭的标签
                                $this->BlockArr[$this->Zhan[$k]][0]=HTML_SINGLE_TAG;
                                array_pop($this->Zhan);
                            }
                            else{
                                array_pop($this->Zhan);
                                break;
                            }
                        }
                    }
                    if($bMatched){
                        break;
                    }
                }
                
                if(false==$bMatched){
                    $this->BlockArr[$i][0]=HTML_SINGLE_TAG;
                }
                $bMatched = false;
            }
        }
    }
    //获取开始标签或单标签的属性数组
    //预处理 私有不可访问
    private function GetAttributes($str){
        $AttrArr = array();
        //属性字符串提取
        $AttrStr = preg_match("/<\s*?[a-zA-Z][a-zA-Z0-9]+?\s+?([^>]*?)[>]/",$str,$temp)?$temp[1]:"";
        //化为kv数组
        preg_match_all('/\s*?([a-zA-Z]+[-]?[a-zA-Z0-9]+)\s*?[=][\s]*?[\'\"]?([^\'\"]+)[\'\"]?\s*?/',$AttrStr,$match);
        for($i=0;$i<count($match[0]);$i++){
            $AttrArr[$match[1][$i]] = $match[2][$i];
        }
        return $AttrArr;
    }
    public function BeginParser(){
        //加载html源码
        $this->LoadHTML();
        //html分块
        $this->Split2Block();
        //寻找单标签
        $this->MarkSingleTag();
        //之前的代码 测试调用 重写父函数抽象方法 的结果
        //不过这个版本的每个BlockArr的块中 最后面的不是字符串而是array($index,$length)保存在字符串在原html中的下标和长度
        for($i=0;$i<count($this->BlockArr);$i++){
            switch($this->BlockArr[$i][0]){
                case HTML_TAG_ST:
                    $this->level=$this->level+1;
                    $this->StartTag($this->BlockArr[$i][1],$this->GetAttributes(substr($this->htmlcode,$this->BlockArr[$i][2][0],$this->BlockArr[$i][2][1])));
                    
                break;
                case HTML_TAG_END:
                    $this->EndTag($this->BlockArr[$i][1]);
                    $this->level=$this->level-1;
                break;
                case HTML_SINGLE_TAG:
                    $this->SingleTag($this->BlockArr[$i][1],$this->GetAttributes(substr($this->htmlcode,$this->BlockArr[$i][2][0],$this->BlockArr[$i][2][1])));
                break;
                case HTML_TEXT:
                    $this->TextBlock(substr($this->htmlcode,$this->BlockArr[$i][2][0],$this->BlockArr[$i][2][1]));
                break;
                case HTML_COMMENT:
                break;
                default:break;
            }
        }
    }
    //遇到开始标签时调用
    abstract function StartTag($tagname,$attrArr);
    //遇到结束标签时调用
    abstract function EndTag($tagname);
    abstract function SingleTag($tagname,$attrArr);
    //遇到文本时调用
    abstract function TextBlock($txt);
}

//例子
/*
class newparser extends parser{
    public $bMainStart = false;
    public $levelpos=0;
    public $title="";
    public $btitle= false;
    function StartTag($tagname,$attr){
        if($tagname=="title"){
            $this->btitle = true;
        }
        if($tagname=="div" && isset($attr["class"])){
            if($attr["class"]=="article"){
                $this->bMainStart = true;
                $this->levelpos = $this->level;
            }
        }
    }
    function EndTag($tagname){
        if($tagname=="title"){
            $this->btitle = false;
        }
        if($tagname=="div" && $this->level==$this->levelpos && $this->bMainStart){
            $this->bMainStart = false;
        }
    }
    function SingleTag($tagname,$attr){
        //if(!empty($attr) && $this->bMainStart)
        //var_dump($attr);
    }
    function TextBlock($str){
        if($this->btitle && $this->title==""){
            $this->title = $str;
            echo "title:".$this->title."<br/>";
        }
        if($str=="点击查看专题"){
            $this->bMainStart = false;
        }
        if($this->bMainStart)
        echo $str."<br/>";
    }
}
$parser1 = new newparser("http://news.xinhuanet.com/politics/2015-12/27/c_1117591851.htm");
$parser1->BeginParser();
*/
?>
采集百度百家文章的代码:

<?php
header("Content-type: text/html; charset=utf-8"); 
require_once("../lib/connect.php");
require_once("../lib/htmlparser.php");
require_once("../lib/res2abs.php");
require_once("../lib/downimage.php");
/*
    时间:2015-12-31 20:02:32
    修改:2016-01-06 18:21:49
    作者:阮家友
    QQ:1439120442
*/
class newparser extends parser{
    public $bMainStart = false;
    public $levelpos=0;
    public $title="";
    public $content="";
    public $btitle= false;
    public $bScript = false;
    public $biframe = false;
    function StartTag($tagname,$attr){
        //echo("ST:".$tagname." level=".$this->level."<br/>");
        //var_dump($attr);
        if($tagname=="h1" && $this->title==""){
            $this->btitle = true;
        }
        if($tagname=="script"){
            $this->bScript = true;
        }
        if($tagname=="iframe"){
            $this->biframe = true;
        }
        if($tagname=="div"&&isset($attr["class"])&&$attr["class"]=="copyright"){
            $this->bMainStart = false;
        }
        if($this->bMainStart&&$this->bScript==false&&$this->biframe==false){
            $str="<".$tagname." ";
            foreach($attr as $k=>$v){
                $str.=$k.'"='.$v.'" ';
            }
            $str.=">";
            $this->content.=$str;
        }
        if($tagname=="div" && isset($attr["class"])&&$attr["class"]=="article-detail"){
            $this->bMainStart = true;
            $this->levelpos = $this->level;
        }
    }
    function EndTag($tagname){
        //echo("END:".$tagname." level=".$this->level."<br/>");
        if($tagname=="h1"){
            $this->btitle = false;
        }
        if($this->bMainStart&&$this->bScript==false&&$this->biframe==false){
            $this->content.="</".$tagname.">";
        }
        if($tagname=="div" && $this->level==$this->levelpos && $this->bMainStart){
            $this->bMainStart = false;
        }
        if($tagname=="script"){
            $this->bScript = false;
        }
        if($tagname=="iframe"){
            $this->biframe = false;
        }
    }
    function SingleTag($tagname,$attr){
        //echo("single:".$tagname."<br/>");
        if($this->bMainStart&&$tagname!="img"){
            $str="<".$tagname." ";
            foreach($attr as $k=>$v){
                $str.=$k.'="'.$v.'" ';
            }
            $str.="/>";
            $this->content.=$str;
            
        }
        if($this->bMainStart&&$tagname=="img"&&$this->bScript==false&&$this->biframe==false){
            $str = "<".$tagname." ";
            foreach($attr as $key=>$v){
                if($tagname=="img"&&$key=="src"){
                    $v = res2abs($v,$this->path);//获取图片绝对路径
                    //下载
                    grabImage($v);
                }
                $str .= $key."='".$v."'";
            }
            $str .=" />";
            $this->content.=$str;
        }
    }
    function TextBlock($str){
        //echo($str."<br/>");
        if($this->btitle){
            $this->title = $str;
        }
        if($this->bMainStart&&$this->bScript==false&&$this->biframe==false){
            $this->content.=$str;
        }
    }
}

if(isset($_GET["url"])){
    $parser1 = new newparser($_GET["url"]);
    $parser1->BeginParser();

    $result = array();
    $result["title"] = $parser1->title;
    $result["content"] = $parser1->content;
    //输出json
    echo(json_encode($result));
    //var_dump($result);
}
else{
    $parser1 = new newparser("http://synchuman.baijia.baidu.com/article/287343");
    $parser1->BeginParser();
}
?>

图片相对路径转绝对路径:用于下载图片,如果本身是绝对路径则没影响

<?php  
/*
//test
$a = 'http://www.abc.com/a/index.html';  
$b = '../abc/a.js';  
echo res2abs($b, $a);  
*/
function res2abs($srcurl, $baseurl) {  
  $srcinfo = parse_url($srcurl);  
  if(isset($srcinfo['scheme'])) {  
    return $srcurl;  
  }  
  $baseinfo = parse_url($baseurl);  
  $url = $baseinfo['scheme'].'://'.$baseinfo['host'];  
  if(substr($srcinfo['path'], 0, 1) == '/') {  
    $path = $srcinfo['path'];  
  }else{  
    $path = dirname($baseinfo['path']).'/'.$srcinfo['path'];  
  }  
  $rst = array();  
  $path_array = explode('/', $path);  
  if(!$path_array[0]) {  
    $rst[] = '';  
  }  
  foreach ($path_array AS $key => $dir) {  
    if ($dir == '..') {  
      if (end($rst) == '..') {  
        $rst[] = '..';  
      }elseif(!array_pop($rst)) {  
        $rst[] = '..';  
      }  
    }elseif($dir && $dir != '.') {  
      $rst[] = $dir;  
    }  
   }  
  if(!end($path_array)) {  
    $rst[] = '';  
  }  
  $url .= implode('/', $rst);  
  return str_replace('\\', '/', $url);  
}  
?>  

下载图片的源码:取名是当前年月日时分秒+4位随机数(0-9a-zA-Z)

<?php
header("Content-type:text/html ; charset=utf-8");
date_default_timezone_set('prc');
//下载图片函数
function grabImage($url,$filename=""){
	if ($url == "") return false;
    if($filename == "") {
		$ext=strrchr($url,"."); //获取扩展名
		$ext_arr = array(".gif",".png",".jpg",".bmp");
		//判断扩展名是否为图片
		if (!in_array($ext, $ext_arr)) return false;
		$str = "../resource/images/".date("Ymd");
        if(!is_dir($str)){
            $res=mkdir(iconv("UTF-8", "GBK", $str),0777,true/*表示创建多级目录*/);//$res = true success false failed
        }
        $str.="/".date("His").GetRandStr(4);
		$filename = $str.$ext;
	}
	ob_start(); //打开浏览器的缓冲区
	readfile($url); //将图片读入缓冲区
	$img = ob_get_contents(); //获取缓冲区的内容复制给变量$img
	ob_end_clean(); //关闭并清空缓冲
	$fp = @fopen($filename,"a"); //将文件绑定到流
	fwrite($fp,$img); //写入文件
	fclose($fp); //关闭文件之争
	return $filename;
}
function GetRandStr($len) 
{ 
    $chars = array( 
        "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k",  
        "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",  
        "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G",  
        "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R",  
        "S", "T", "U", "V", "W", "X", "Y", "Z", "0", "1", "2",  
        "3", "4", "5", "6", "7", "8", "9" 
    ); 
    $charsLen = count($chars) - 1; 
    shuffle($chars);   
    $output = ""; 
    for ($i=0; $i<$len; $i++) 
    { 
        $output .= $chars[mt_rand(0, $charsLen)]; 
    }  
    return $output;  
} 
//echo GetRandStr(4);//R6KS
//$str = "../resource/images/".date("Ymd");
//创建目录
//判断目录存在否
//if(!is_dir($str)){
//    $res=mkdir(iconv("UTF-8", "GBK", $str),0777,true/*表示创建多级目录*/);//$res = true success false failed
//}
//$str.="/".date("His").GetRandStr(4);
//echo($str);
//grabImage("http://news.xinhuanet.com/world/2015-12/31/128583824_14515167854501n.jpg",$str.".jpg");
?>

采集的记录

某次记录的预览:

数据库的字段自行设置。

还有,这些都只是单页采集。输入url,并且选择采集模型,先预览,然后入库(添加记录)

我只是自己采集一些人为重要的新闻或文章,如果大家要自动采集某个网站得自己做修改。下图的编辑器是百度的UEditor。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值