原文地址,向原作者表示感谢。

我们在Mapado的工作就是收集世界上所有“要做的事”。

为了得到大量数据,我们抓取整个网络,就像Goole做的那样,搜集所有关于演唱会、演出、访问、景点……当我们发现一个有趣的页面,我们就尝试从这个页面提取“好”的数据。

我们面临的一个主要挑战就是如何从垃圾信息中(广告、导航栏、页脚、相关内容……)分离我们感兴趣的内容(标题、简介、图片、日期……)

在这个挑战中,一个任务就是重组在视觉上相近的内容。通常,组成页面主要部分的元素相互之间距离是很近的。

当我们开始进行任务时,很天真的以为可以操作DOM来实现。在DOM中,元素以层级结构存储,所以在同一个父元素中的元素很大可能是相关的。

一个非常有趣的介绍页面分割的论文可以在这里找到 Page Segmentation by Web Content Clustering

使用DOM是一个好的起始点,但在更多情况下带来的却是混乱:

  • CSS样式表可以移动元素:元素可以被移动到任何地方,甚至浏览器窗口之外。
  • CSS样式表可以显示或隐藏元素:很多元素分享同一个视觉点,仅仅通过CSS或者JavaScript来移动或删除。
  • JavaScript甚至可以展示那些不在DOM中的东西。

所以我们开始考虑使用webkit作为虚拟渲染器来获取视觉特征。有一堆无头浏览器比如phantomjs、zombie.js或者casperjs。他们中的任何一个都可以渲染页面并且获取所有页面中经过计算的元素。

我们可以使用下列有用的特性之一来聚集视觉元素:

  • 元素在页面中的位置(来自上还是右)
  • 元素的宽和高

下图是来自Quai Branly Museum的一个页面,我们想对其进行聚类操作:
img

当构建聚类模型时,我们发现一个主要的特性就是每一个块最左边和最右边的像素位置。确实,如果你看一个页面,不同的内容块会被垂直空隙分割。

添加每个元素块的中心位置和DOM深度提高了聚类效果。

下面是第一个python版本实现那些概念,使用scikit-learn执行聚类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
from sklearn.cluster import DBSCAN # Use scikit-learn to perform clustering

# ElementList contains a line for each element we want to cluster with his top and left position, width and eight and xpath

xpath_dict = set() # Build a dictionnary of XPATH of each element
for item in ElementList:
path_split_idx = find(item["xpath"],"/")
for idx in path_split_idx:
xpath_dict.add(item["xpath"][:idx])
xpath_dict=list(xpath_dict)

# Build feature matrix with each element

features = [] # Table will store features for each element to cluster
for item in ElementList:
# Keep only inside browser visual boundary
if (item["left"] >0 and (item["top"] >0
and item["features"]["left"]+item["width"] <1200):
visual_features = (
[item["left"] ,
item["left"] + item["width"],
item["top"],
item["top"] + item["height"],
(item["left"] + item["width"] + item["left"]) / 2,
(item["top"] + item["top"] + item["height2"])/ 2)
dom_features = [0] * len(xpath_dict) # using DOM parent presence as a feature. Default as 0
path_split_idx = find(item["xpath"], "/")

for i, idx in enumerate(path_split_idx):
# give an empirical 70 pixels distance weight to each level of the DOM (far from perfect implementation)
dom_features[xpath_dict.index(item["xpath"][:idx])] = 800 / (i + 1)

# create feature vector combining visual and DOM features
feayures.append(visual_features + dom_features)

features = np.asarray(features) # Convert to numpy array to make DBSCAN work

# DBSCAN is a good general clustering algorithm
eps_value=900 # maximum distance between clusters
db = DBSCAN(eps=eps_value, min_samples=1, metric='cityblock').fit(features)

# DBSCAN Algorithm returns a label for each vector of input array
labels = db.labels_

上面的算法远不算完美,但是一个好的开始当尝试聚类视觉块时候。

下面就是对上图进行聚类后的结果:

img2

Ps.这13年的文章了,而且作者并没给出上述代码中某些缺失变量的解释。