最近因为项目需要对一个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树遍历过程以及网页模板训练都写出来供大家参考学习。