冰楓論壇

 找回密碼
 立即註冊
查看: 725|回覆: 0

[心得] php提取網頁正文內容的例子

[複製鏈接]

33

主題

0

好友

34

積分

新手上路

Rank: 1

UID
62810
帖子
64
主題
33
精華
0
積分
34
楓幣
2948
威望
32
存款
0
贊助金額
0
推廣
0
GP
32
閱讀權限
10
性別
保密
在線時間
41 小時
註冊時間
2014-5-26
最後登入
2019-5-7
發表於 2015-6-28 02:41:16 |顯示全部樓層
提取內容是一個比較有意思的項目了,今天要做一個這樣的小搜索引擎了,下面小編就給各位介紹一個php提取網頁正文內容的例子,希望能給各位帶來幫助。
Html2Article-php實現的提取網頁正文部分,最近研究百度結果頁的資訊採集,其中關鍵環節就是從採集回的頁面中提取出文章。
因為難點在於如何去識別並保留網頁中的文章部分,而且刪除其它無用的信息,並且要做到通用化,不能像火車頭那樣根據目標站來制定採集規則,因為搜索引擎結果中有各種的網頁。
抓回一個頁面的數據,如何匹配出正文部分,鄭曉在下班路上想了個思路是:
1. 提取出body標籤部分–>剔除所有鏈接–>剔除所有script、註釋–>剔除所有空白標籤(包括標籤內不含中文的)–>獲取結果。
2. 直接匹配出非鏈接的、 符合在div、p、h標籤中的中文部分???
還是會有不少其它多餘信息啊,比如底部信息等。。 如何搞?不知道大家有木有什麼思路或建議?
這個類是從網上找到的一個php實現的提取網頁正文部分的算法,鄭曉在本地也測試了下,準確率非常高。
  1. <?php
  2. class Readability {
  3.     // 保存判定結果的標記位名稱
  4.     const ATTR_CONTENT_SCORE = "contentScore";
  5.     // DOM 解析類目前只支持 UTF-8 編碼
  6.     const DOM_DEFAULT_CHARSET = "utf-8";
  7.     // 當判定失敗時顯示的內容
  8.     const MESSAGE_CAN_NOT_GET = "Readability was unable to parse this page for content.";
  9.     // DOM 解析類(PHP5 已內置)
  10.     protected $DOM = null;
  11.     // 需要解析的源代碼
  12.     protected $source = "";
  13.     // 章節的父元素列表
  14.     private $parentNodes = array();
  15.     // 需要刪除的標籤
  16.     // Note: added extra tags from http://www.111cn.net
  17.     private $junkTags = Array("style", "form", "iframe", "script", "button", "input", "textarea",
  18.                                 "noscript", "select", "option", "object", "applet", "basefont",
  19.                                 "bgsound", "blink", "canvas", "command", "menu", "nav", "datalist",
  20.                                 "embed", "frame", "frameset", "keygen", "label", "marquee", "link");
  21.     // 需要刪除的屬性
  22.     private $junkAttrs = Array("style", "class", "onclick", "onmouseover", "align", "border", "margin");

  23.     /**
  24.      * 構造函數
  25.      *      @param $input_char 字符串的編碼。默認 utf-8,可以省略
  26.      */
  27.     function __construct($source, $input_char = "utf-8") {
  28.         $this->source = $source;
  29.         // DOM 解析類只能處理 UTF-8 格式的字符
  30.         $source = mb_convert_encoding($source, 'HTML-ENTITIES', $input_char);
  31.         // 預處理 HTML 標籤,剔除冗餘的標籤等
  32.         $source = $this->preparSource($source);
  33.         // 生成 DOM 解析類
  34.         $this->DOM = new DOMDocument('1.0', $input_char);
  35.         try {
  36.             //libxml_use_internal_errors(true);
  37.             // 會有些錯誤信息,不過不要緊 :^)
  38.             if (!@$this->DOM->loadHTML('<?xml encoding="'.Readability::DOM_DEFAULT_CHARSET.'">'.$source)) {
  39.                 throw new Exception("Parse HTML Error!");
  40.             }
  41.             foreach ($this->DOM->childNodes as $item) {
  42.                 if ($item->nodeType == XML_PI_NODE) {
  43.                     $this->DOM->removeChild($item); // remove hack
  44.                 }
  45.             }
  46.             // insert proper
  47.             $this->DOM->encoding = Readability::DOM_DEFAULT_CHARSET;
  48.         } catch (Exception $e) {
  49.             // ...
  50.         }
  51.     }

  52.     /**
  53.      * 預處理 HTML 標籤,使其能夠準確被 DOM 解析類處理
  54.      *
  55.      * @return String
  56.      */
  57.     private function preparSource($string) {
  58.         // 剔除多餘的 HTML 編碼標記,避免解析出錯
  59.         preg_match("/charset=([\w|\-]+);?/", $string, $match);
  60.         if (isset($match[1])) {
  61.             $string = preg_replace("/charset=([\w|\-]+);?/", "", $string, 1);
  62.         }
  63.         // Replace all doubled-up <BR> tags with <P> tags, and remove fonts.
  64.         $string = preg_replace("/<br\/?>[ \r\n\s]*<br\/?>/i", "</p><p>", $string);
  65.         $string = preg_replace("/<\/?font[^>]*>/i", "", $string);
  66.         // @see https://github.com/feelinglucky/php-readability/issues/7
  67.         //   - from http://stackoverflow.com/questions/7130867/remove-script-tag-from-html-content
  68.         $string = preg_replace("#<script(.*?)>(.*?)</script>#is", "", $string);
  69.         return trim($string);
  70.     }

  71.     /**
  72.      * 刪除 DOM 元素中所有的 $TagName 標籤
  73.      *
  74.      * @return DOMDocument
  75.      */
  76.     private function removeJunkTag($RootNode, $TagName) {
  77.         
  78.         $Tags = $RootNode->getElementsByTagName($TagName);
  79.         
  80.         //Note: always index 0, because removing a tag removes it from the results as well.
  81.         while($Tag = $Tags->item(0)){
  82.             $parentNode = $Tag->parentNode;
  83.             $parentNode->removeChild($Tag);
  84.         }
  85.         
  86.         return $RootNode;
  87.         
  88.     }
  89.     /**
  90.      * 刪除元素中所有不需要的屬性
  91.      */
  92.     private function removeJunkAttr($RootNode, $Attr) {
  93.         $Tags = $RootNode->getElementsByTagName("*");
  94.         $i = 0;
  95.         while($Tag = $Tags->item($i++)) {
  96.             $Tag->removeAttribute($Attr);
  97.         }
  98.         return $RootNode;
  99.     }
  100.     /**
  101.      * 根據評分獲取頁面主要內容的盒模型
  102.      *      判定算法來自:http://code.google.com/p/arc90labs-readability/   
  103.      *      這裡由鄭曉博客轉發
  104.      * @return DOMNode
  105.      */
  106.     private function getTopBox() {
  107.         // 獲得頁面所有的章節
  108.         $allParagraphs = $this->DOM->getElementsByTagName("p");
  109.         // Study all the paragraphs and find the chunk that has the best score.
  110.         // A score is determined by things like: Number of <p>'s, commas, special classes, etc.
  111.         $i = 0;
  112.         while($paragraph = $allParagraphs->item($i++)) {
  113.             $parentNode   = $paragraph->parentNode;
  114.             $contentScore = intval($parentNode->getAttribute(Readability::ATTR_CONTENT_SCORE));
  115.             $className    = $parentNode->getAttribute("class");
  116.             $id           = $parentNode->getAttribute("id");
  117.             // Look for a special classname
  118.             if (preg_match("/(comment|meta|footer|footnote)/i", $className)) {
  119.                 $contentScore -= 50;
  120.             } else if(preg_match(
  121.                 "/((^|\\s)(post|hentry|entry[-]?(content|text|body)?|article[-]?(content|text|body)?)(\\s|$))/i",
  122.                 $className)) {
  123.                 $contentScore += 25;
  124.             }
  125.             // Look for a special ID
  126.             if (preg_match("/(comment|meta|footer|footnote)/i", $id)) {
  127.                 $contentScore -= 50;
  128.             } else if (preg_match(
  129.                 "/^(post|hentry|entry[-]?(content|text|body)?|article[-]?(content|text|body)?)$/i",
  130.                 $id)) {
  131.                 $contentScore += 25;
  132.             }
  133.             // Add a point for the paragraph found
  134.             // Add points for any commas within this paragraph
  135.             if (strlen($paragraph->nodeValue) > 10) {
  136.                 $contentScore += strlen($paragraph->nodeValue);
  137.             }
  138.             // 保存父元素的判定得分
  139.             $parentNode->setAttribute(Readability::ATTR_CONTENT_SCORE, $contentScore);
  140.             // 保存章節的父元素,以便下次快速獲取
  141.             array_push($this->parentNodes, $parentNode);
  142.         }
  143.         $topBox = null;
  144.         
  145.         // Assignment from index for performance.
  146.         //     See http://www.peachpit.com/articles/article.aspx?p=31567&seqNum=5
  147.         for ($i = 0, $len = sizeof($this->parentNodes); $i < $len; $i++) {
  148.             $parentNode      = $this->parentNodes[$i];
  149.             $contentScore    = intval($parentNode->getAttribute(Readability::ATTR_CONTENT_SCORE));
  150.             $orgContentScore = intval($topBox ? $topBox->getAttribute(Readability::ATTR_CONTENT_SCORE) : 0);
  151.             if ($contentScore && $contentScore > $orgContentScore) {
  152.                 $topBox = $parentNode;
  153.             }
  154.         }
  155.         
  156.         // 此時,$topBox 應為已經判定後的頁面內容主元素
  157.         return $topBox;
  158.     }

  159.     /**
  160.      * 獲取 HTML 頁面標題
  161.      *
  162.      * @return String
  163.      */
  164.     public function getTitle() {
  165.         $split_point = ' - ';
  166.         $titleNodes = $this->DOM->getElementsByTagName("title");
  167.         if ($titleNodes->length
  168.             && $titleNode = $titleNodes->item(0)) {
  169.             // @see http://stackoverflow.com/questions/717328/how-to-explode-string-right-to-left
  170.             $title  = trim($titleNode->nodeValue);
  171.             $result = array_map('strrev', explode($split_point, strrev($title)));
  172.             return sizeof($result) > 1 ? array_pop($result) : $title;
  173.         }
  174.         return null;
  175.     }

  176.     /**
  177.      * Get Leading Image Url
  178.      *
  179.      * @return String
  180.      */
  181.     public function getLeadImageUrl($node) {
  182.         $images = $node->getElementsByTagName("img");
  183.         if ($images->length && $leadImage = $images->item(0)) {
  184.             return $leadImage->getAttribute("src");
  185.         }
  186.         return null;
  187.     }

  188.     /**
  189.      * 獲取頁面的主要內容(Readability 以後的內容)
  190.      *
  191.      * @return Array
  192.      */
  193.     public function getContent() {
  194.         if (!$this->DOM) return false;
  195.         // 獲取頁面標題
  196.         $ContentTitle = $this->getTitle();
  197.         // 獲取頁面主內容
  198.         $ContentBox = $this->getTopBox();
  199.         
  200.         //Check if we found a suitable top-box.
  201.         if($ContentBox === null)
  202.             throw new RuntimeException(Readability::MESSAGE_CAN_NOT_GET);
  203.         
  204.         // 複製內容到新的 DOMDocument
  205.         $Target = new DOMDocument;
  206.         $Target->appendChild($Target->importNode($ContentBox, true));
  207.         // 刪除不需要的標籤
  208.         foreach ($this->junkTags as $tag) {
  209.             $Target = $this->removeJunkTag($Target, $tag);
  210.         }
  211.         // 刪除不需要的屬性
  212.         foreach ($this->junkAttrs as $attr) {
  213.             $Target = $this->removeJunkAttr($Target, $attr);
  214.         }
  215.         $content = mb_convert_encoding($Target->saveHTML(), Readability::DOM_DEFAULT_CHARSET, "HTML-ENTITIES");
  216.         // 多個數據,以數組的形式返回
  217.         return Array(
  218.             'lead_image_url' => $this->getLeadImageUrl($Target),
  219.             'word_count' => mb_strlen(strip_tags($content), Readability::DOM_DEFAULT_CHARSET),
  220.             'title' => $ContentTitle ? $ContentTitle : null,
  221.             'content' => $content
  222.         );
  223.     }
  224.     function __destruct() { }
  225. }
複製代碼
您需要登入後才可以回帖 登入 | 立即註冊

正在連接伺服器...
打開冰楓聊天室
廣告刊登意見回饋關於我們職位招聘

Copyright © 2011-2019 冰楓論壇, All rights reserved

免責聲明:本網站是以即時上載留言的方式運作,本站對所有留言的真實性、完整性及立場等,不負任何法律責任。

而一切留言之言論只代表留言者個人意見,並非本網站之立場,用戶不應信賴內容,並應自行判斷內容之真實性。

小黑屋|意見反饋|手機版|Archiver|冰楓論壇

GMT+8, 2019-10-23 21:19

APP Store下載 Play Store下載
回頂部