免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 4746 | 回复: 0

发布一个HTML正文提取程序PHP版 HTMLExtractor [复制链接]

论坛徽章:
0
发表于 2010-12-19 23:18 |显示全部楼层
本帖最后由 xjtdy888 于 2010-12-19 23:25 编辑

发布一个HTML正文提取程序HTMLExtractor,在线例子
http://dev.psm01.cn/c/html-extractor.php
程序主要是基于内容统计的方法,暂不包含自学习能力,仅是
一个分析程序而以,网上也有别人实现了的正文提取程序,不过
大部人都当宝,都不愿意公开完整代码,有些大人实现了一些简
单的,不过分析能力和识别能力都不太理想。所以自己做了一个
简单的,本来想用PHP DOM分析器,不过大部份网页都不规范,
缺个标签啥的都很正常,所以自已又造了个简单的轮子分析HTML标
签,功能比较简单,每个元素都生成一个对象,内存方面占用比较
高,不过在这里我只是为了实现,并没去做优化。因为我并不是在
做应用,所以希望不要让我改改成什么样去适用你们的业务(以前经常
有QQ加上让我把我的例子怎么改,很无语),
如果你们喜欢,可以和我一起开发完善他。

补充一下,因为写的着急,现在几个类的耦合性还比较大,下来再守善吧。

项目代码 http://code.google.com/p/html-extractor/
QQ 339534039
邮箱 xjtdy888[at]163.com
BLOG http://hi.baidu.com/phps
  1. <?php
  2. /**
  3. *
  4. * 作者 言兑
  5. * 邮箱 xjtdy888[at]163.com
  6. * QQ 339534039
  7. * 项目托管 http://code.google.com/p/html-extractor/
  8. *
  9. */
  10. error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);
  11. header("Content-type:text/html; charset=utf-8");
  12. $url = $_REQUEST['url'];
  13. $v = $url ? $url : 'http://news.sina.com.cn/w/2010-11-03/063821404648.shtml';
  14. echo '<title>正文提取</title>';
  15. echo '<h3>PHP 网页正文提取程序</h2>';
  16. echo '<h3>作者: 言兑</h2>';
  17. echo '<h3>QQ: 339534039</h2>';
  18. echo '<h3> <a href="http://hi.baidu.com/phps" target="_blank">查看博客</a> <a href="http://code.google.com/p/html-extractor/" target="_blank">查看项目代码</a></h3>';
  19. echo '<form>';
  20. echo '请输入要提取的URL:<input type="text" name="url" size="50" value="'.$v.'" /><input type="submit" value="分析" />';
  21. echo '</form>';
  22. if (!$url){
  23.         exit;
  24. }
  25. echo '<b>分析结果:</b> <a href="'.$url.'" target="_blank">查看原文</a>:<br /><br />';

  26. $text = HTMLExtractor::getUrlMainContent($url,200,1);
  27. $text = HTMLExtractor::convertToUTF8($text);
  28. if (!$text) $text = "抓取失败...可能目标页不规范或者正文太短";

  29. echo ($text);

  30. echo "<br /><br />耗时:" . HTMLExtractor::$usageTime;
  31. if (function_exists('memory_get_usage')){
  32.         echo "内存占用:" . (memory_get_usage(true)/1024).'KB';
  33. }

  34. class HTMLExtractor
  35. {
  36.         #要删掉的元素
  37.         const PC_TAG_DELETE = 1;
  38.         #要删掉的标签
  39.         const PC_TAG_STRIP = 2;
  40.         static $cleanTags= array(
  41.                 array("script",self::PC_TAG_DELETE),
  42.                 array("style",self::PC_TAG_DELETE),
  43.                 array("link",self::PC_TAG_DELETE),
  44.                 array("link",self::PC_TAG_DELETE),
  45.                 array("object",self::PC_TAG_DELETE),
  46.                 array("embed",self::PC_TAG_DELETE),


  47.                 array("p",self::PC_TAG_STRIP),
  48.                 array("b",self::PC_TAG_STRIP),
  49.                 array("i",self::PC_TAG_STRIP),
  50.                 array("u",self::PC_TAG_STRIP),
  51.                 array("font",self::PC_TAG_STRIP),
  52.                 array("strong",self::PC_TAG_STRIP),
  53.         );
  54.         static $usageTime = 0;
  55.         static function preClean($html)
  56.         {
  57.                 foreach(self::$cleanTags as $t)
  58.                 {
  59.                         if (!$t) continue;
  60.                         $name = $t[0];
  61.                         $pc   = $t[1];
  62.                         $html = preg_replace("#<({$name})(>|\s[^>]*?>)(.*?)</\\1>#is",
  63.                                 $pc == self::PC_TAG_DELETE ? "" : "\\3",$html);                               
  64.                 }
  65.                 return $html;

  66.         }
  67.         static function getDataMainContent($data,$minlength,$maxdepth)
  68.         {
  69.                 $s = microtime(true);
  70.                 $data = self::preClean($data);
  71.                 $root = new htmlTag("document",htmlTag::DOM_TAG);
  72.                 $hand = new htmlExtractorHandler($root);
  73.                 $p = new htmlParse($data,$hand);
  74.                 $p->parse();
  75.                 $text =  self::getMainContent($root,$minlength,$maxdepth);
  76.                 $e = microtime(true);
  77.                 self::$usageTime = $e - $s;
  78.                 return $text;
  79.         }
  80.         static function getUrlMainContent($url,$minlength,$maxdepth)
  81.         {
  82.                 $data = self::getUrlHtml($url);
  83.                 if (!$data) return false;
  84.                 return self::getDataMainContent($data,$minlength,$maxdepth);
  85.         }
  86.         static function getDomText($dom,$depth)
  87.         {
  88.                 if ($dom->echoset) return ;
  89.                 $dom->echoset = true;
  90.                 if($dom->depth <= $depth){
  91.                         foreach($dom->getChildren() as $child){
  92.                                 if (is_object($child)){
  93.                                         $result .= self::getDomText($child,$depth);
  94.                                 }elseif(is_string($child)){
  95.                                         $result .= $child;
  96.                                 }
  97.                         }
  98.                 }
  99.                 return $result;
  100.         }

  101.         static function getMainContent($root,$textLength=100,$maxdepth)
  102.         {

  103.                 $result = '';
  104.                 $cn  = $root->tagNum + $root->textNum;
  105.                 $per = $root->tagNum ? $root->textLength/$textLength / $root->tagNum : 1;
  106.                 if ($root->textLength >= $textLength && $per>0.5){
  107.                         $result .= self::getDomText($root,$root->depth+$maxdepth);
  108.                 }
  109.                 foreach($root->getChildren() as $dom){
  110.                         if (is_object($dom)){
  111.                                 $result .= self::getMainContent($dom,$textLength,$maxdepth);
  112.                         }
  113.                 }
  114.                 return $result;
  115.         }

  116.        
  117.         static function checkTextType($url)
  118.         {  
  119.                 $url = parse_url($url);
  120.                 if($fp = @fsockopen($url['host'],empty($url['port'])?80:$url['port'],$error))
  121.                 {
  122.                         fputs($fp,"GET ".(empty($url['path'])?'/':$url['path'])." HTTP/1.1\r\n");
  123.                         fputs($fp,"Host:$url[host]\r\n\r\n");
  124.                         while(!feof($fp))
  125.                         {
  126.                                 $tmp = fgets($fp);
  127.                                 if(trim($tmp) == ''){
  128.                                         break;
  129.                                 }else if(preg_match('#Content-type: text/(.*)#si',$tmp,$arr)){
  130.                                         fclose($fp);
  131.                                         return true;
  132.                                 }
  133.                         }
  134.                         fclose($fp);
  135.                         return false;
  136.                 }else{
  137.                         return false;
  138.                 }
  139.         }

  140.         static function convertToUTF8($str) {
  141.                 $charset = mb_detect_encoding($str, array('ASCII','UTF-8','GB2312','GBK','BIG5','ISO-8859-1'));
  142.                 if (strcasecmp($charset,'UTF-8') != 0) {
  143.                         $str = mb_convert_encoding($str,'UTF-8',$charset);
  144.                 }
  145.                 return $str;
  146.         }


  147.         static function getUrlHtml($url){
  148.                 //return file_get_contents("txt.txt");
  149.                 if (!self::checkTextType($url)){
  150.                         exit();
  151.                 }
  152.                 return file_get_contents($url);
  153.         }
  154. }
  155. /**
  156. *
  157. * HTML 标签解析器
  158. * 该解析器以<>为单元 比如 <div id="cc"> 这是一个处理单元
  159. * 所以 <div></div> 这句是2个处理单元<div>和</div>
  160. * 解析器每处理一个单元都会产生回调函数,至于怎么来处理这个单元由处理器来决定
  161. * 也就是说该解析器并不去处理标签匹不匹配之类的问题
  162. * </span></span> 这样的字符串也是可以进行解析的,产生2次 endElement 事件回调。
  163. * 本来先用PHP自带的DOM对象类,不过由于大部份网页都不规范,解析起来大部份是会出错的
  164. * 所以自己写了这个简单的
  165. * 本类总共3个回调函数
  166. *  startElement($parser,$tagName)        发现开始标签
  167. *  endElement($parser,$tagName)        发现闭合标签
  168. *  characterData($parser,$char)        发现标签内容
  169. *  本类没做任何优化,所以回调的频率会相当的高。
  170. *
  171. */
  172. class htmlParse
  173. {
  174.         /**
  175.          *        要处理的HTML内容
  176.          */
  177.         protected $_html = '';
  178.         /**
  179.          * _html 的长度
  180.          */
  181.         protected $_htmlLength = 0;
  182.         /**
  183.          * 当前处理位置指针
  184.          */
  185.         protected $_pt = 0;
  186.         /**
  187.          * 标签状态栈
  188.          */
  189.         protected $_tagStatus = array();
  190.         /**
  191.          * 标签栈
  192.          */
  193.         protected $_tagStack = array();
  194.         /**
  195.          * 当前标签名称
  196.          */
  197.         protected $_tagName = '';
  198.        
  199.         /**
  200.          * 标签开始标识
  201.          */
  202.         const TAG_START = 10;
  203.         /**
  204.          * 标签结束标识
  205.          */
  206.         const TAG_END   = 20;

  207.         /**
  208.          * 标签名字开始
  209.          */
  210.         const TAGNAME_START = 30;
  211.         /**
  212.          * 标签名字结束
  213.          */
  214.         const TAGNAME_END = 40;
  215.         /**
  216.          * 注释开始(保留)
  217.          */
  218.         const COMMENT_START = 50;
  219.         /**
  220.          * 注释结束(保留)
  221.          */
  222.         const COMMENT_END = 60;


  223.         /**
  224.          *  事件回调对象
  225.          */
  226.         public $_elementHandler = null;
  227.         /**
  228.          *
  229.          * 构函方法
  230.          * @param striing $html 要解析的字符串
  231.          * @param object|array elementHandler 回调处理器可以是数组也可以是对象
  232.          *                对象只要实现相同的方法名就可以,注意这里没有用到接口
  233.                         如果是数组,方法名作为下标即可
  234.          *
  235.          */
  236.         public function htmlParse($html,$elementHander=null)
  237.         {
  238.                 $this->setHtml($html);
  239.                 $this->setElementHandler($elementHander);
  240.         }
  241.         /**
  242.          *
  243.          * 重新设定要解析的内容
  244.          * @param string $html
  245.          *
  246.          */
  247.         public function setHtml($html)
  248.         {
  249.                 $this->_html = $html;
  250.                 $this->_reset();
  251.         }
  252.         /**
  253.          *
  254.          * 重位处理指针,要处理的字符长度
  255.          *
  256.          */
  257.         public function _reset()
  258.         {
  259.                 $this->_pt = 0;
  260.                 $this->_htmlLength = strlen($this->_html);
  261.         }
  262.         /**
  263.          * 重新指定处理器
  264.          * @param object|array elementHandler
  265.          */
  266.         public function setElementHandler($elementHander)
  267.         {
  268.                 $this->_elementHandler = $elementHander;
  269.         }
  270.         /**
  271.          * 获取要处理的下一个字符 指针自动后移
  272.          * 到结尾了返回false
  273.          * @return char
  274.          */
  275.         public function nextChar()
  276.         {
  277.                 if ($this->_pt < $this->_htmlLength){
  278.                         return $this->_html[$this->_pt++];
  279.                 }
  280.                
  281.                 return false;
  282.         }
  283.         /**
  284.          * 获取处理过的上一个字符指针回退
  285.          * 到结尾了返回false
  286.          * @return char
  287.          */
  288.         public function preChar()
  289.         {
  290.                 if ($this->_pt > 0){
  291.                         return $this->_html[--$this->_pt];
  292.                 }
  293.                 return false;
  294.         }
  295.         /**
  296.          * 获得当前处理位置
  297.          * @return integer
  298.          */
  299.         public function getPt()
  300.         {
  301.                 return $this->_pt;
  302.         }
  303.         /**
  304.          * 设置处理位置 成功返回true 失败false
  305.          * @return bool
  306.          */
  307.         public function setPt($v)
  308.         {
  309.                 if ($v>-1 && $v < $this->_htmlLength){
  310.                         $this->_pt = $v;
  311.                         return true;
  312.                 }
  313.                 return false;
  314.         }

  315.         public function addTagStack()
  316.         {
  317.                 return array_push($this->_tagStack,$this->_tagName);
  318.         }

  319.         public function startElement($parse,$tagName)
  320.         {
  321.         }

  322.         public function endElement($parse,$tagName)
  323.         {
  324.         }
  325.         public function characterData($parse,$char)
  326.         {
  327.         }

  328.         public function endParse($parse)
  329.         {
  330.         }

  331.         public function callHandler($callback)
  332.         {
  333.                 $argv = func_get_args();
  334.                 array_shift($argv);
  335.                 array_unshift($argv,$this);
  336.                 if (is_array($this->_elementHandler) && $this->_elementHandler[$callback]){
  337.                         return call_user_func_array($this->_elementHandler[$callback],$argv);

  338.                 }else{
  339.                         $handler = is_object($this->_elementHandler) ? $this->_elementHandler : $this;
  340.                         if (method_exists($handler,$callback)){
  341.                                 return call_user_method_array($callback,$handler,$argv);
  342.                         }
  343.                 }
  344.         }

  345.         public function parse()
  346.         {
  347.                 while(($char=$this->nextChar()) !== false)
  348.                 {
  349.                         switch($char)
  350.                         {
  351.                                 case '<':
  352.                                         if (!$this->_tagStatus || end($this->_tagStatus) == self::TAG_END)       
  353.                                         {
  354.                                                 $pt = $this->getPt();

  355.                                                 $char1 = $this->nextChar();
  356.                                                 $char2 = $this->nextChar();
  357.                                                 $char3 = $this->nextChar();
  358.                                                 $refor = false;
  359.                                                 if ($char1 == '!' && ($char2 == '-' && $char3 == '-')) {
  360.                                                         //如果是注释
  361.                                                         while(($char1=$this->nextChar()) !== false)
  362.                                                         {
  363.                                                                
  364.                                                                 if ($char1 != '>') continue;
  365.                                                                 $pt2 = $this->getPt();
  366.                                                                 $this->preChar();
  367.                                                                 $char2 = $this->preChar();
  368.                                                                 $char3 = $this->preChar();
  369.                                                                 if ($char2 == '-' && $char3 == '-') {

  370.                                                                         $refor = true;
  371.                                                                         $this->setPt($pt2);
  372.                                                                         break;
  373.                                                                 }
  374.                                                                 $this->setPt($pt2);
  375.                                                         }
  376.                                                        
  377.                                                 }
  378.                                                 if ($refor){
  379.                                                         continue;
  380.                                                 }

  381.                                                 $this->setPt($pt);
  382.        
  383.                                                 array_push($this->_tagStatus,self::TAG_START);
  384.                                                 array_push($this->_tagStatus,self::TAGNAME_START);
  385.                                                 $this->_tagName = '';
  386.                                         }
  387.                                 break;
  388.                                 case ' ':  case '>':
  389.                                         if (!$this->_tagStatus || end($this->_tagStatus) == self::TAG_END){
  390.                                                 $callback = 'characterData';
  391.                                                 $this->callHandler($callback,$char);
  392.                                                 continue;
  393.                                         }
  394.                                         $callback = '';
  395.                                         if (end($this->_tagStatus) == self::TAGNAME_START) {
  396.                                                 array_pop($this->_tagStatus);
  397.                                                 array_push($this->_tagStatus , self::TAGNAME_END);
  398.                                                 if ($this->_tagName[0] == '/'){
  399.                                                         $this->_tagName = substr($this->_tagName,1);
  400.                                                         $callback = 'endElement';
  401.                                                 }else{
  402.                                                         $callback = 'startElement';
  403.                                                 }
  404.                                         }
  405.                                         // <p /> <p/> <p / >
  406.                                         // <link ... />
  407.                                         if (in_array(end($this->_tagStatus) ,array(self::TAGNAME_START,        self::TAGNAME_END)) && $char == '>'){
  408.                                                 $pt = $this->getPt();
  409.                                                 $this->setPt($pt-1);
  410.                                                 while(($char2=$this->preChar()) !== false && !preg_match("#\s#",$char2)){
  411.                                                         if ($char2 == '/'){
  412.                                                                 //自闭合标签
  413.                                                                 $callback = 'endElement';
  414.                                                                 array_pop($this->_tagStatus); //end tagname_start
  415.                                                                 array_push($this->_tagStatus , self::TAGNAME_END);
  416.                                                         }
  417.                                                         break;
  418.                                                 }
  419.                                                 $this->setPt($pt);
  420.                                         }
  421.                                         if ($callback == 'startElement'){
  422.                                                 $this->addTagStack();
  423.                                                 $this->callHandler($callback,$this->_tagName);
  424.                                         }elseif ($callback == 'endElement'){

  425.                                                 array_pop($this->_tagStatus); //end tagname
  426.                                                 array_pop($this->_tagStatus); // end tag
  427.                                                 $this->callHandler($callback,$this->_tagName);
  428.                                         }
  429.                                         if (end($this->_tagStatus) == self::TAGNAME_END && $char == '>'){
  430.                                                 array_pop($this->_tagStatus); //end tag name
  431.                                                 array_pop($this->_tagStatus); //end tag
  432.                                         }
  433.                                 break;
  434.                                 default:
  435.                                         if (end($this->_tagStatus) == self::TAGNAME_START)
  436.                                         {
  437.                                                 $this->_tagName .= $char;
  438.                                         }
  439.                                         if (!$this->_tagStatus || end($this->_tagStatus) == self::TAG_END){
  440.                                                 $callback = 'characterData';
  441.                                                 $this->callHandler($callback,$char);
  442.                                         }
  443.                                 break;
  444.                         }
  445.                 }
  446.                 $callback = 'endParse';
  447.                 $this->callHandler($callback,$char);
  448.         }
  449. }
  450. /**
  451. */
  452. class htmlTag
  453. {
  454.         public $tagName = '';
  455.         public $type = '';
  456.         public $depth = 0;
  457.         public $parent = null;
  458.         public $childs = array();

  459.         public $textLength = 0;

  460.         public $tagNum  = 0;
  461.         public $textNum = 0;

  462.         const DOM_TAG = 1;

  463.         public function __construct($tagName,$type)
  464.         {
  465.                 $this->type = $type;
  466.                 $this->tagName = $tagName;
  467.         }

  468.         public function addChild($child)
  469.         {
  470.                 array_push($this->childs,$child);
  471.                 if (!is_object($child)){
  472.                         $this->textLength += $this->_strlen($child,true);
  473.                         $this->textNum++;
  474.                 }else{
  475.                         $this->tagNum++;
  476.                 }
  477.         }
  478.         public function _strlen($text,$ignoreSpace=false)
  479.         {
  480.                 if ($ignoreSpace) $text = preg_replace("#\s*#s","",$text);
  481.                 return strlen($text);
  482.         }

  483.         public function getChildren()
  484.         {
  485.                 $result = array();
  486.                 foreach($this->childs as $dom)
  487.                 {
  488.                         $result[] = $dom;
  489.                 }
  490.                 return $result;
  491.                
  492.         }
  493.         public function getText()
  494.         {
  495.                 $text = '';
  496.                 foreach($this->childs as $dom){
  497.                         if (is_string($dom)) $text .= $dom;
  498.                 }
  499.                 return $text;
  500.         }
  501. }

  502. class htmlExtractorHandler
  503. {
  504.         public $ignoreTags=array(
  505.                 "!doctype","meta","link","hr","!--","base","basefont","br",
  506.                 "frame","frameset","noframes","iframe",
  507.                 "input","button","select","optgroup","option",
  508.                 "label","fieldset","legend","isindex",
  509.                 "img","map","area","style",
  510.                 "script","noscript","applet","object","param","marquee","embed");

  511.         protected $_dom = array();

  512.         private $_charBuffer = '';
  513.         private $_domDepth   = 0;

  514.         public function __construct($root)
  515.         {
  516.                 array_push($this->_dom,$root);
  517.         }

  518.         public function isIgnore($tag)
  519.         {
  520.                 $tag = strtolower($tag);
  521.                 return in_array($tag,$this->ignoreTags);
  522.         }

  523.         public function endParse()
  524.         {
  525.                 $this->updateChacter();
  526.         }

  527.         public function updateChacter()
  528.         {
  529.                 if ($this->_charBuffer != ''){
  530.                         end($this->_dom)->addChild($this->_charBuffer);
  531.                         $this->_charBuffer = '';
  532.                 }
  533.         }

  534.         public function startElement($parse,$tagName)
  535.         {
  536.                 $this->updateChacter();
  537.                 $tagName = strtolower($tagName);
  538.                 if ($this->isIgnore($tagName) === true) return false;

  539.                 $dom = new htmlTag($tagName,htmlTag::DOM_TAG);
  540.                 $parent = end($this->_dom);
  541.                 $dom->parent = $parent;
  542.                 //echo str_repeat("    ",$this->_domDepth)."[{$dom->tagName}_{$this->_domDepth}]\r\n";
  543.                 $dom->depth  = ++$this->_domDepth;
  544.                 $parent->addChild($dom);
  545.                

  546.                 array_push($this->_dom,$dom);
  547.         }

  548.         public function endElement($parse,$tagName)
  549.         {
  550.                 $this->updateChacter();
  551.                 $tagName = strtolower($tagName);
  552.                 if ($this->isIgnore($tagName) === true) return false;
  553.                 $dom = end($this->_dom);
  554.                 if (end($this->_dom)->tagName == $tagName) {
  555.                         array_pop($this->_dom);
  556.                         $this->_domDepth--;
  557.                 }
  558.                 //echo str_repeat("    ",$this->_domDepth)."[/{$dom->tagName}_{$this->_domDepth}]\r\n";
  559.         }
  560.         public function characterData($parse,$char)
  561.         {
  562.                 $this->_charBuffer .= $char;
  563.         }
  564. }
复制代码
把代码当附件也放一份上来吧
html-extractor.php.zip (5.51 KB, 下载次数: 137)
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP