最近因爲項目需要對一個web網頁變更進行實時監控,之前項目組有人採用的是比較簡單 的文本對比的方式,只要發現文本中一處發生變動就進行告警,最後導致的結果就是誤報的情況比較多,比如在對首頁進行監控時,後臺對用戶訪問量的統計會在首頁實時更新,這樣就會被當做網站被改動來處理了,而這並不是我們所需要的,因爲我們感興趣的不是網頁動態數據區域的變化,這樣我們最終要解決的就是怎麼來分離網頁的動態區域和靜態區域。
動態區域和靜態區域的分離技術應用在很多方向,數據挖掘方面應用的較廣,如網頁數據抽取、網頁聚類、網頁篡改等等。
在參考了以下論文《 web網頁中動態數據區域的識別去抽取》《基於模板的網頁數據抽取》《基於局部變化性的網頁篡改識別模型及方法》《網頁篡改系統的研究與實現》之後,大體思路就是採用編輯距離來計算樹或字符串的相似度,對字符串的編輯距離計算相當於計算一維向量的編輯距離。
本系統大體也是採用了dom遍歷比較以及節點標記的方式來實現,最終形成一個節點集合,然後對形成的節點集合中相鄰的節點進行合併形成網頁模板。這個也是我大體的方案。
第一個難點是如何去遍歷兩顆dom樹,首先我們先解決比較兩個節點集合中的變化節點,如果採用論文中的for for語句來遍歷的話會出現相鄰節點順序變化卻無法檢測到的情況,並且節點會重複遍歷,而我們想要找出插入、刪除的節點,修改的節點相當於執行了刪除插入操作,而我們這裏將標籤 名沒變化內容或屬性變化的情況稱作修改操作。原先我們採用在for for循環中引入了一個移動標記位來解決也可以一次找出變化的節點,後來發現編輯距離不僅可以解決相似度的問題,而且通過矩陣回溯也可以一次找出變化的節點,一下是標準的字符串編輯距離算法。
值得說明的是,編輯距離計算的前提是隻允許插入、刪除、修改操作來達到字符串轉換的目的。
而我們的算法 也只需要這3中操作,而編輯距離算法本身卻不之侷限這3種操作,具體可參考http://blog.csdn.net/mishifangxiangdefeng/article/details/7925025
而我們也可以去掉某種操作或該重新定義某種操作的行爲,一下是標準的編輯距離計算方法。
// Strings of size m and n are passed.
// Construct the Table for X[0...m, m+1], Y[0...n, n+1]
int EditDistanceDP(char X[], char Y[])
{
// Cost of alignment
int cost = 0;
int leftCell, topCell, cornerCell;
int minCell = 0;
int m = strlen(X)+1;
int n = strlen(Y)+1;
// T[m][n]
int *T = (int *)malloc(m * n * sizeof(int));
//用於矩陣回溯
int *operation = (int *)malloc(m * n * sizeof(int));
// Initialize table
for(int i = 0; i < m; i++)
for(int j = 0; j < n; j++)
*(T + i * n + j) = SENTINEL;
// Set up base cases
// T[i][0] = i
for(int i = 0; i < m; i++){
*(T + i * n) = i;
*(operation+ i*n)=3;
}
// T[0][j] = j
for(int j = 0; j < n; j++){
*(T + j) = j;
*(operation+j)=2;
}
*operation=0;
// Build the T in top-down fashion
for(int i = 1; i < m; i++)
{
for(int j = 1; j < n; j++)
{
leftCell = *(T + i*n + j-1);
leftCell += EDIT_COST;
topCell = *(T + (i-1)*n + j);
topCell += EDIT_COST;
cornerCell = *(T + (i-1)*n + (j-1) );
// edit[(i-1), (j-1)] = 0 if X[i] == Y[j], 1 otherwise
cost = (X[i-1] == Y[j-1]?0:1);
cornerCell += cost; // may be replace
//如果不回溯則直接調用該函數
//minCell = Minimum(leftCell, topCell, cornerCell);
if(topCell>leftCell)
{
if(cornerCell>leftCell)
{
minCell=leftCell;
*(operation + (i)*n + (j))=2;
}
else
{
minCell=cornerCell;
*(operation + (i)*n + (j))=cost;
}
}
else
{
if(cornerCell>topCell)
{
minCell=topCell;
*(operation + (i)*n + (j))=3;
}
else
{
minCell=cornerCell;
*(operation + (i)*n + (j))=cost;
}
}
*(T + (i)*n + (j)) = minCell;
}
}
// 結果存儲在 T[m][n]
cost = *(T + m*n - 1);
backtrace(operation, X, Y);
free(T);
return cost;
}
通過矩陣回溯可計算出變化的位置
void backtrace(int* operation, char* a, char* b)
{
int insertion=0,deletion=0,substitution=0;
int i,j;
int len1=strlen(a);
int len2=strlen(b);
int m = strlen(a)+1;
int n = strlen(b)+1;
for (i=len1,j=len2;i>=0&&j>=0;)
{
switch(*(operation+i*n+j))
{
case 0:
//printf("(%d,%d) right\n",i,j);
printf("pos %d right\n",i);
i--;
j--;
continue;
case 1:
//printf("(%d,%d) substitute\n",i,j);
printf("pos %d substitute (%c-->%c)\n",i,a[i-1],b[j-1]);
i--;
j--;
substitution++;
continue;
case 2:
//printf("(%d,%d) insert\n",i,j);
printf("pos %d insert (%c)\n",i,b[j-1]);
j--;
insertion++;
continue;
case 3:
//printf("(%d,%d) delete\n",i,j);
printf("pos %d delete (%c)\n",i,a[i-1]);
i--;
deletion++;
continue;
}
}
printf("insert:%d,delete:%d,substitute:%d\n",insertion,deletion,substitution);
}
下面是計算HelloWorld和aHelloWored的編輯距離結果顯示,圖中矩陣爲回溯矩陣
系統對算法進行改進後重新定義了編輯操作,不是比較字符串而是比較節點集合,今天就先寫到這,下一篇文章中會將基於樹的編輯距離計算和dom樹遍歷過程以及網頁模板訓練都寫出來供大家參考學習。