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