安全街
一步步,我们的信息安全时代

Python是门好语言啊,写起来简单用起来方便,就是和C比慢了点儿。在信息安全的工作领域中,去重是很常见的活。URL要去重,字典要去重,只要是代码爬的,各种东西都要去重。

传统的快速去重

譬如对一个列表[1, 2, 3, 3, 3, 4]进行去重,按照逻辑,一些新手可能会这么写:

target = [1, 2, 3, 3, 3, 4]

def dup(t):
    res = []
    for i in target:
        if i not in res:
            res.append(i)
    return res

print dup(target) #[1, 2, 3, 4]

当列表内元素很多之后,这种方法就会比较慢。其实正确的写法应该是这样:

target = [1, 2, 3, 3, 3, 4]

def dup(t):
    return list(set(t))

print dup(target) #[1, 2, 3, 4]

先将列表转换为一个集合,利用集合的特性自动去除重复,再转化回列表。这样在性能上的提升不是一星半点儿,去重速度很快。但是这种操作有一个局限性,就是没办法做局限比对去重。
比如有一个列表['a1', 'a2', 'b3', 'b4'],我想根据开头的字母去重,去重后得到['a1', 'b3']的结果,这样集合快速去重大法就失效了。

URL去重过滤场景

最近有一个场景需要用到去重,当我的URL采集器采集好URL后,需要对URL去重。因为同一子域名下链接相似性太高,没有重复进行SQL注入测试的必要,所以我想每个网站的每个子域名只保留1条URL。并且之前就已经测试过很多URL,这次重复采集到,没有必要再进行一遍测试,浪费时间。好在sqlmap的output目录下的文件夹是以已经测试过的二级域名来命名的,只需要扫一遍就可以获得测试过哪些二级域名了,我们需要把这些已经测试过的二级域名筛出去。
任务目标:对二级域名相同的URL进行去重,将已测试过的二级域名的URL过滤,获得去重后的完整URL。于是传统的集合快速去重大法就失效了。
一开始我是这么写的:

def dup(urls, exist_domains):
    '''
    urls为采集到的url列表,
    exist_domains为已测试过的二级域名列表
    '''
    res = []
    for i in urls:
        domain = i.split('/')[2] # 获取二级域名
        if domain not in exist_domains: # 如果二级域名不在已测试域名中
            res.append(i) #就将这个URL添加到结果中
            exist_domains.append(domain) # 把这个二级域名添加到已测试域名中
    return res

使用一个not in,既能保证去重又能完成过滤。但问题是太慢了。因为我的两个列表都非常大。
于是我开始思考有没有局部性去重+过滤的办法呢?

字典局部快速去重过滤大法

我突然想起Python的字典添加方式很随意。
假如想要一个字典{'age': 22, 'name': 't1ddl3r'},我们只需要这样:

me = {}
me['name'] = 't1ddl3r'
me['age'] = 22

简单到看起来就像是在赋值,而如果真的赋值呢?

me = {}
me['name'] = 't1ddl3r'
me['age'] = 22
me['age'] = 25

年龄就会变成25。而我们正可以利用这一点来去重!

url_dict = {}
domain = url.split('/')[2]
url_dict[domain] = url

使用二级域名做Key,url做Value,由于字典定义和赋值的特性,Key是唯一的。这就保证了根据二级域名对url去重。这种方法是非常快的。因为只需要字典的定义和赋值。
最后,我们再来一手url_dict.values()的骚操作将字典的Value变成一个列表返回,即去重完毕。
去重搞定了,过滤还会远吗?经过对字典内置函数的查阅,我想到了快速过滤的方法:

for domain in exist_domains:
    if url_dict.has_key(domain):
        del url_dict[domain]

对已测试过的二级域名列表进行遍历,如果去重后的字典中存在要过滤掉的二级域名的Key,我们就把它连同对应的Value从字典里删除就可以了。
最终代码如下:

def dup(urls, exist_domains):
    '''
    urls为采集到的url列表,
    exist_domains为已测试过的二级域名列表
    '''
    url_dict = {} #一个临时使用的字典

    # 根据二级域名对url进行去重
    for url in urls: #对采集到的列表进行遍历
        domain = url.split('/')[2] #获取二级域名
        url_dict[domain] = url #二级域名为key,url为value,利用字典特性进行去重

    # 将已测试过的二级域名的url过滤掉
    for domain in exist_domains: #对已测试过的二级域名进行遍历
        if url_dict.has_key(domain): #如果字典的key中存在已测试过的二级域名
            del url_dict[domain] #那就把这个键和值删除

    return url_dict.values() #将所有的value组成一个列表返回

经过测试,利用字典特性的去重代码比上面那个传统的if not in1000倍

这篇文章还没有人发言,快抢第一!

发表评论