php解析html类(2)
2016-01-11 22:28
639 查看
之前写过一个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+个版本了)
图片相对路径转绝对路径:用于下载图片,如果本身是绝对路径则没影响
下载图片的源码:取名是当前年月日时分秒+4位随机数(0-9a-zA-Z)
采集的记录
某次记录的预览:
数据库的字段自行设置。
还有,这些都只是单页采集。输入url,并且选择采集模型,先预览,然后入库(添加记录)
我只是自己采集一些人为重要的新闻或文章,如果大家要自动采集某个网站得自己做修改。下图的编辑器是百度的UEditor。
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。
相关文章推荐
- 一个关于if else容易迷惑的问题
- PHP5.2.*防止Hash冲突拒绝服务攻击的Patch
- 深入理解PHP之匿名函数
- JSP/PHP基于Ajax的分页功能实现
- 关于PHP通过PDO用中文条件查询MySQL的问题。
- 什么是设计模式
- PHP数据库长连接mysql_pconnect的细节
- Php Installing An Expansion
- PHP+Apache在Windows 9x下的安装和配置
- IIS 6 的 PHP 最佳配置方法
- 安装Apache和PHP的一些补充
- Linux Apache+MySQL+PHP
- 建立Apache+PHP+MySQL数据库驱动的动态网站
- dedecms采集过滤常用代码集合
- PHP 5.3.0 安装分析心得
- apache 环境下 php 的配置注意事项
- ASP.NET、ASP、PHP、JSP之间有什么区别?
- PHP VBS JS 函数 对照表
- C语言实现的统计php代码行数功能源码(支持文件夹、多目录)