1. 程式人生 > >基於行塊分佈函式的網頁正文抽取演算法程式碼實現

基於行塊分佈函式的網頁正文抽取演算法程式碼實現

     最近在在做一個與資訊相關的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;
	}
}