泰迪杯——楼层结构识别算法

前言

第一次参加泰迪杯,前期跟队友配合的不算太好,不过后期经过老师的教育指导,再通过大家的努力,终于把泰迪杯给解决了,这期间困难重重,收获也颇丰,其中收获最大的大概是把楼层结构识别算法做出来了,刚刚开始的时候还认为自己没办法写出这个算法,不过最后在队友的帮助之下,还是将它实现了,下面就来详细介绍一下这个算法。

发现

经过对BBS论坛源代码的研究,我们发现主题帖跟回帖都在同一个标签之下(div),并且他们都是此div的子节点,同时,我们还知道,帖与帖之间部分标签数量相差不大。由此,我们可以把主题帖和回帖看成一个个楼层,把帖子的标签作为楼层的特征,进而识别出楼层,并将其绝对路径找出来。

原理

将网页源代码转换为DOM树,使用层序遍历DOM树,通过余弦相似度识别相似楼层,根据统计出来的楼层数,与识别出来的楼层数进行比较进而得出正确楼层,输出楼层位置。

前期准备

  • 软件和开发环境:sublime text3、python-Anaconda、Win10系统
  • python包:lxml(你没看错,就一个包)

算法流程

  1. 构建DOM树

  2. 层序遍历DOM树

  3. 楼层矩阵筛选

  4. 楼层矩阵相似度比较

  5. 获得楼层矩阵绝对路径

1.构建DOM树

1.1 传入两个值(网页源代码,楼层数标准值)

  • 网页源代码(bs0bj):用于操作DOM树

  • 楼层数标准值(standard_value):用于比较楼层矩阵数,我们是通过“发表于”、“只看该作者”和时间把这个值确定下来


# 算法

def TreeAlgotithm(bs0bj, standard_value):

tree = etree.HTML(bs0bj)  # 用HTML方法解析文件

t = tree.getroottree()  # 获得一个节点对应的树

root = t.getroot()  # 用于存储当前根节点,即爸爸节点

save_children_tag = Queue()  # 队列,用于存储子节点

# 将body节点放到队列中

if(root[0].getnext()):

save_children_tag.enqueue(root[1])

else:

return None

网页正文都在body节点之下,所以直接定位body节点就可以减少遍历时间。

1.2 初始化存储容器


similar_tag = 0  # 用于计算有多少个相似的儿子对象

matrix_pack = []  # 矩阵容器,用于存放未进行相似度计算的矩阵

tag_pack = []  # 标签容器,用于存放未进行相似度计算的标签

one_path = []  # 用于临时存放某节点的绝对路径

new_matrix_pack = []  # 矩阵容器,用于存放已经进行相似度计算的矩阵

new_tag_pack = []  # 标签容器,用于存放相似度高的标签

test = []  # 观察数据进度

2.层序遍历DOM树

DOM树是一种普通的树结构,层序遍历是寻找楼层结构最快的方法,我们要寻找的网页正文和楼层结构基本都在body节点之下,用层序遍历,是最快找到楼层标签的方法。

DOM树

2.1 构造队列

数据结构知识,使用队列保存每层节点的信息,先进先出层序遍历树的每一个节点。


# 构造队列,用于树的层序遍历

class Queue(object):

def __init__(self):

self.items = []

def isEmpty(self):

return self.items == []

def enqueue(self, item):

self.items.insert(0, item)

def dequeue(self):

return self.items.pop()

def size(self):

return len(self.items)

2.2 遍历DOM树


while(similar_tag > standard_value + 2 or similar_tag < standard_value - 2):

similar_tag = 0  # 相似节点数初始化

if (save_children_tag.isEmpty()):

print("栈为空")

return None

else:

root = save_children_tag.dequeue()  # 队列弹栈

# 父节点遍历

for child in root:

save_children_tag.enqueue(child)

3.楼层矩阵筛选

3.1 初始化矩阵容器


matrix_pack = []  # 矩阵容器,用于存放未进行相似度计算的矩阵

tag_pack = []  # 标签容器,用于存放未进行相似度计算的标签

new_matrix_pack = []  # 矩阵容器,用于存放已经进行相似度计算的矩阵

new_tag_pack = []  # 标签容器,用于存放相似度高的标签

3.2 构建楼层矩阵

经过对楼层源代码的研究,发现这些标签出现频数较多,但是在过程中少考虑了一个问题,就某些标签会受到回帖内容的影响,比如img,回帖多一张照片就会多一个img标签。


def TagMatrix(tree, path):

# 统计标签频数

def List_Count_Tag(children):

List_tag = []

if 'a' in children:

List_tag.append(children.count('a'))

else:

List_tag.append(0)

if 'div' in children:

List_tag.append(children.count('div'))

else:

List_tag.append(0)

if 'p' in children:

List_tag.append(children.count('p'))

else:

List_tag.append(0)

if 'span' in children:

List_tag.append(children.count('span'))

else:

List_tag.append(0)

if 'img' in children:

List_tag.append(children.count('img'))

else:

List_tag.append(0)

if 'td' in children:

List_tag.append(children.count('td'))

else:

List_tag.append(0)

if 'dd' in children:

List_tag.append(children.count('dd'))

else:

List_tag.append(0)

return List_tag

tag_save = []

child_Node = tree.xpath(path + '//*')

for child in child_Node:

tag_save.append(child.tag)

return List_Count_Tag(tag_save)

3.3 矩阵初筛选

这一步至关重要,因为在初期没加这一步的时候,很多空矩阵被视为楼层,后来我们发现,加入了这个矩阵的初步筛选之后,不仅准确率提高了,连效率也大大提高了。


one_matrix = TagMatrix(tree, one_path)  # 获得儿子节点的矩阵

if(one_matrix[0] < 2 or one_matrix[1] < 2):  # 筛选没可能是楼层的元素

continue

# 存储没有进行相似度计算的矩阵和标签

matrix_pack.append(one_matrix)

tag_pack.append(child)

3.4 选择标准矩阵

由于下一步要进行矩阵相似度的比较,我们需要找出一个最有可能是楼层的矩阵(标准矩阵),与其他的矩阵进行相似度比较。经过研究我们发现,各个楼层的a标签基本一致,因此统计a标签频数,并找到跟a标签频数相等的第一个矩阵作为标准矩阵。

具体步骤
  1. 提取所有楼层矩阵的第一个元素,建立新列表

  2. 统计列表中,返回出现次数最多的元素

  3. 在所有楼层矩阵中找到第一个跟频数一致的矩阵,并返回作为标准矩阵


# 选出标准值

def StandardMatrix(tag_pack, matrix_pack):

# 计算矩阵第一个元素,用于选出标准值

def CountKey(matrix_pack):

temp = 0

temp_list = []

# 提取标签容器的第一个标签,

for one in matrix_pack:

temp_list.append(one[0])

result_list = collections.Counter(temp_list)

dict(result_list)

# 返回a标签的频数,用作选出标准矩阵

return max(result_list.items(), key=lambda x: x[1])[0]

if(len(tag_pack) > 1):

first_key = CountKey(matrix_pack)

for i in matrix_pack:

if(i[0] == first_key):

standar_matrix = i

return standar_matrix

4.楼层矩阵相似度比较

  • 相似度>0.9:判断为楼层矩阵

if(standar_matrix != None):

i = 0

for new_one_matrix in matrix_pack:

Cos_key = CosSimilarity(new_one_matrix, standar_matrix)

if(Cos_key > 0.90):

new_tag_pack.append(tag_pack[i])

new_matrix_pack.append(matrix_pack[i])

i = i + 1

4.1 余弦相似度比较

这个其实没什么好看的,有技术含量的内容都在上面,这个在网上都能找到。


# 两个矩阵余弦相似度计算

def CosSimilarity(hinsA, hinsB):

up_count = 0

down_left_count = 0

down_right_count = 0

result_count = 0

i = 0

# 计算两个向量的点积

while i < len(hinsA):

up_count = up_count + hinsA[i] * hinsB[i]

i = i + 1

i = 0

# 计算两个向量的模

while i < len(hinsA):

down_left_count = down_left_count + hinsA[i] ** 2

i = i + 1

i = 0

while i < len(hinsB):

down_right_count = down_right_count + hinsB[i] ** 2

i = i + 1

return up_count / (math.sqrt(down_left_count) * math.sqrt(down_right_count))

4.2 释放矩阵容器内存

这么做主要是为了避免下次计算的时候,把前一次的数据也算进去了,这一步也是很重要的。


if(similar_tag > standard_value + 2 or similar_tag < standard_value - 2):

del(matrix_pack)

del(tag_pack)

del(new_tag_pack)

del(new_matrix_pack)

5.获得楼层矩阵绝对路径

做到这一步也基本差不多了,把楼层矩阵的绝对路径拿到手,就可以定位每一个楼层的位置了。


tag_path = []

for tag in new_tag_pack:

tag_path.append(t.getpath(tag))

return tag_path

总结

  • 存在问题:
  1. 少于3个楼层识别困难

  2. 矩阵构造不严谨,部分标签会受回帖影响

  3. 依赖于标准值的准确性

  4. 没有树结构深度限制(一直识别到最后一个节点,但其实楼层不可能很深层的节点,一般在4到7层)

源代码

算法源代码

个人博客,最新更新的文章都发在这,欢迎关注:https://yeah-kun.github.io/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容