基於行塊分佈函式的網頁正文抽取演算法程式碼實現
阿新 • • 發佈:2019-02-16
最近在在做一個與資訊相關的APP,資訊是通過爬取獲得,但是獲取只有簡單的資訊,正文沒有獲取。所以在顯示的時候很麻煩,一個<a>標籤鏈到到別人的網頁,滿屏的廣告
,還有各種彈窗,雖然頁面確實做得很漂亮,但是不得不放棄這種簡單的方式了,所以接下來自己動手了。
首先我們做的是基於HTML5的APP,所以基本上就是和網頁打交道。但是接下來問題就來了,當用戶點選某一條資訊時,該由誰來解析這個網頁最後呈現給使用者看,手機端還是伺服器端?其實都有問題,最簡單的肯定是通過我們的伺服器來解析後然後在封裝成html給使用者呈現,但是這樣必定會增加伺服器的壓力,而且還有被封的可能性。在手機端,要取得html只有兩種可能,js或者jsp,但是前者是不能跨域訪問的,後者其實也是在伺服器端執行的。最終解決方法,手機端用一個原生態的介面來呈現,不經可以解析連結,而且效果應該也比網頁好,但是這和我們初心的不一致,那就是除了主介面其他都是網頁,目前暫定這種方式。那麼接下來問題又來了,改用什麼方式來獲取正文呢?有很多種演算法,提供一篇演算法介紹的部落格: 點選開啟連結
我使用的是基於行塊分佈函式的網頁正文抽取演算法,因為這個演算法比較簡單,準確率不是很高,基本思想:
1.首先將網頁中的html標籤全部去掉,再去掉空白行和空白部分,得到文字
2.再將文字的行按照一定的數量分成一個一個的塊(注意這個分的數量對提取的精度有影響,具體有多大的影響自己試試吧)
3.最後對這些塊進行分析,找出驟升和驟降的塊,最後分析取出驟升和驟降塊之間的內容。
程式碼如下(註釋已經很清楚了):
package com.spider.a; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import com.spider.util.DefaultHttpUtils; public class Ctext { /** * 行分塊的大小(塊大小=BLOCKS+1) */ private static int BLOCKS = 0; /** * 判斷為正文的文字驟變率 */ private static float CHANGE_RATE = 0.7f; /** * 每行最小長度 */ private static int MIN_LENGTH = 10; public static void main(String[] args) { String html = DefaultHttpUtils.downloadHtml("http://blog.csdn.net/csh624366188/article/details/8096989"); html = deleteLabel(html); Map<Integer, String> map = splitBlock(html); System.out.println(judgeBlocks(map)); } /** * 去除html標籤 * @param html 請求獲得的html文字 * @return 純文字 */ public static String deleteLabel(String html){ String regEx_script = "<script[^>]*?>[\\s\\S]*?<\\/script>"; // 定義script的正則表示式 String regEx_style = "<style[^>]*?>[\\s\\S]*?<\\/style>"; // 定義style的正則表示式 String regEx_html = "<[^>]+>"; // 定義HTML標籤的正則表示式 String regEx_anno = "<!--[\\s\\S]*?-->"; //html註釋 html = html.replaceAll(regEx_script, ""); html = html.replaceAll(regEx_style, ""); html = html.replaceAll(regEx_html, ""); html = html.replace(regEx_anno, ""); html = html.replaceAll("((\r\n)|\n)[\\s\t ]*(\\1)+", "$1").replaceAll("^((\r\n)|\n)", "");//去除空白行 html = html.replaceAll(" +| +| +", ""); //去除空白 return html.trim(); } /** * 將純文字按BLOCKS分塊 * @param text 純文字 * @return 分塊後的map集合,鍵即為塊號,值為塊內容 */ public static Map<Integer, String> splitBlock(String text){ Map<Integer, String> groupMap = new HashMap<Integer, String>(); ByteArrayInputStream bais = new ByteArrayInputStream(text.getBytes()); BufferedReader br = new BufferedReader(new InputStreamReader(bais)); String line = null,blocksLine = ""; int theCount = 0,groupCount = 0,count=0;//1.記錄每次新增的行數;2.記錄塊號;3.記錄總行數 try { while((line=br.readLine())!=null){ if (line.length()>MIN_LENGTH) { System.out.println(line); if (theCount<=BLOCKS) { blocksLine +=line.trim(); theCount++; } else { groupMap.put(groupCount, blocksLine); groupCount++; blocksLine = line.trim(); theCount = 1; } count++; } } if (theCount!=0) {//加上沒湊齊的給給定塊數的 groupMap.put(groupCount+1, blocksLine); } System.out.println("一共"+groupMap.size()+"個行塊,資料行數一共有"+count); } catch (IOException e) { e.printStackTrace(); } return groupMap; } /** * 分析每塊之間變化的情況 * @param map 塊集合 * @return 正文 */ public static String judgeBlocks(Map<Integer, String> map){ Set<Entry<Integer, String>> sets = map.entrySet(); List<Integer> contentBlock = new ArrayList<Integer>(); int currentBlock = map.get(0).length(); //當前行的長度 int lastBlock = 0; //上一行的長度 for(Entry<Integer, String> set:sets){ lastBlock = currentBlock; currentBlock = set.getValue().length(); float between = (float)Math.abs(currentBlock - lastBlock)/Math.max(currentBlock, lastBlock); if (between>=CHANGE_RATE) { contentBlock.add(set.getKey()); } } //下面是取多個峰值節點中兩個節點之間內容長度最大的內容 int matchNode = contentBlock.size(); System.out.println("一共有"+matchNode+"峰值點"); int lastContent = 0;//前一個兩節點之間的內容長度 String context = null;//結果 if (matchNode>2) { for(int i=1;i<matchNode;i++){ String result = ""; for(int j=contentBlock.get(i-1);j<contentBlock.get(i);j++){ result +=map.get(j); } if (result.length()>lastContent) { lastContent = result.length(); context = result; } } } return context; } }