利用Python爬虫进行Web数据挖掘已经越来越普遍,网上的各种Python爬虫资料教程比较多,但是很少有人对Web数据挖掘进行系统地总结和分析。
从目标上来讲,Web数据挖掘分为三类。最常见的是对于网站内容的爬取,包括文本、图片和文件等;其次是对于网站结构的爬取,包括网站目录,链接之间的相互跳转关系,二级域名等;还有一种爬虫是对于Web应用数据的挖掘,包括获取网站CMS类型,Web插件等。
0x02 网站内容挖掘
网站内容挖掘应用最广,最为常见,网上的Python爬虫资料大多也都属于这类。爬取下的内容也可用于很多方面。
Python编写这类爬虫的常见思路就是利用request或urllib2库定制请求,利用BeautifulSoup对原始网页进行解析,定位特定html标签,寻找目标内容。如果要提高性能,可以利用threading启用多线程,gevent启用协程(在windows上使用可能会有些问题),也可以用multiprocessing启动多进程。multiprocessing能突破python的GIL全局解释器锁的限制。其他的一些技巧可以看我的另一篇博客:常见的反爬虫和应对方法?
0x03 网站结构挖掘
网站结构挖掘并不是很常见,但在一些特殊的应用场景,我们也会用到。例如对于Web漏洞扫描器,爬取网站整站目录,获取二级域名是极为重要的。在第一类网站内容挖掘中,有时也需要将目标网站某个页面(通常是首页)作为入口,对整个网站所有内容进行获取和分析,这种情况下就需要对网站结构进行分析。在学习中有迷茫不知如何学习的朋友小编推荐一个学Python的学习q u n 227 -435- 450可以来了解一起进步一起学习!免费分享视频资料
对于网站目录爬取,需要考虑的一个重要问题就是爬虫性能。通常网站的页面会比较多,如果直接获取所有目录,可能会耗费大量时间。另外,对于网站链接的搜索策略对爬虫的性能也会产生很大影响。一般情况下,我们会采用广度优先搜索,从入口页面开始,获取该页面内所有链接,并判断链接是否是站内链接,是否已经爬取过。为了提高速度,可以对链接进行归纳,将/page.php?id=1与/page.php?id=2认为是同一类型链接,不进行重复爬取。简单实现代码如下:
1 # coding=utf-8
2 '''
3 爬取网站所有目录
4 Author: bsdr
5 Email: 1340447902@qq.com
6 '''
7importurllib2
8importre
9fromBeautifulSoupimportBeautifulSoup
10importtime
11
12 t = time.time()
13
14 HOST = ''
15 CHECKED_URL = [] # 已检测的url规则
16 CHECKING_URL = [] # 待检测的url
17 RESULT = [] # 检测结果
18 RETRY = 3 # 重复尝试次数
19 TIMEOUT = 2 # 超时
20
21
22classurl_node:
23def__init__(self, url):
24 '''
25 url节点初始化
26 :param url: String, 当前url
27 :return:
28 '''
29 # self.deep = deep
30 self.url = self.handle_url(url, is_next_url=False)
31 self.next_url = []
32 self.content = ''
33
34
35defhandle_url(self, url, is_next_url=True):
36 '''
37 将所有url处理成标准格式
38
39 :param url: String
40 :param is_next_url: Bool, 判断传入的url是当前需要检测的url还是下一层url
41 :return: 返回空或错误信息或正确url
42 '''
43globalCHECKED_URL
44globalCHECKING_URL
45
46 # 去掉结尾的’/‘
47 url = url[0:len(url) - 1]ifurl.endswith('/')elseurl
48
49ifurl.find(HOST) == -1:
50ifnoturl.startswith('http'):
51 url = 'http://' + HOST + urlifurl.startswith('/')else'http://' + HOST + '/' + url
52else:
53 # 如果url的host不为当前host,返回空
54return
55else:
56ifnoturl.startswith('http'):
57 url = 'http://' + url
58
59ifis_next_url:
60 # 下一层url放入待检测列表
61 CHECKING_URL.append(url)
62else:
63 # 对于当前需要检测的url
64 # 将其中的所有参数替换为1
65 # 然后加入url规则表
66 # 参数不同,类型相同的url,只检测一次
67 rule = re.compile(r'=.*?&|=.*?$')
68 result = re.sub(rule, '=1&', url)
69ifresultinCHECKED_URL:
70return'[!] Url has checked!'
71else:
72 CHECKED_URL.append(result)
73 RESULT.append(url)
74
75returnurl
76
77
78def__is_connectable(self):
79 # 验证是否可以连接
80 retry = 3
81 timeout = 2
82foriinrange(RETRY):
83try:
84 response = urllib2.urlopen(self.url, timeout=TIMEOUT)
85returnTrue
86except:
87ifi == retry - 1:
88returnFalse
89
90
91defget_next(self):
92 # 获取当前页面所有url
93 soup = BeautifulSoup(self.content)
94 next_urls = soup.findAll('a')
95iflen(next_urls) != 0:
96forlinkinnext_urls:
97 self.handle_url(link.get('href'))
98
99
100defrun(self):
101ifself.url:
102printself.url
103ifself.__is_connectable():
104try:
105 self.content = urllib2.urlopen(self.url, timeout=TIMEOUT).read()
106 self.get_next()
107except:
108 print('[!] Connect Failed')
109
110
111classPoc:
112defrun(self, url):
113globalHOST
114globalCHECKING_URL
115 url = check_url(url)
116
117ifnoturl.find('https'):
118 HOST = url[8:]
119else:
120 HOST = url[7:]
121
122forurlinCHECKING_URL:
123 print(url)
124 url_node(url).run()
125
126
127defcheck_url(url):
128 url = 'http://' + urlifnoturl.startswith('http')elseurl
129 url = url[0:len(url) - 1]ifurl.endswith('/')elseurl
130
131foriinrange(RETRY):
132try:
133 response = urllib2.urlopen(url, timeout=TIMEOUT)
134returnurl
135except:
136raiseException("Connect error")
137
138
139if__name__ == '__main__':
140 HOST = 'www.hrbeu.edu.cn'
141 CHECKING_URL.append('http://www.hrbeu.edu.cn/')
142forurlinCHECKING_URL:
143 print(url)
144 url_node(url).run()
145printRESULT
146print"URL num: "+str(len(RESULT))
147print"time: %d s" % (time.time() - t)
对于二级域名的获取,如果直接从主站爬取的链接中寻找,效率很低而且结果可能并不能让人满意。目前获取二级域名有三种常用方法,第一种是利用域名字典进行猜解,类似于暴力破解。第二种种是利用各种二级域名查询接口进行查询,例如bing的查询接口如下,domain为根域名:
http://cn.bing.com/search?count=50&q=site:domain&first=1
link的二级域名查询接口为:
http://i.links.cn/subdomain/?b2=1&b3=1&b4=1&domain=domain
aleax的二级域名查询接口为:
http://alexa.chinaz.com/?domain=domain
由这些接口都能直接查询到指定根域名的二级域名,这里就不附代码了。
还有一种获取二级域名的方法是通过搜索引擎直接搜索,如百度搜索:inurl:domain 或 site:domain。这种方法比较慢。具体代码如下:
1 # coding=utf-8
2 '''
3 利用百度搜索二级域名
4 Author: bsdr
5 Email:1320227902@qq.com
6 '''
7
8
9importurllib2
10importstring
11importurllib
12importre
13importrandom
14fromurl_handleimportsplit_url
15
16 user_agents = ['Mozilla/5.0 (Windows NT 6.1; WOW64; rv:23.0) Gecko/20130406 Firefox/23.0',
17 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:18.0) Gecko/20100101 Firefox/18.0',
18 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533+ (KHTML, like Gecko) Element Browser 5.0',
19 'IBM WebExplorer /v0.94', 'Galaxy/1.0 [en] (Mac OS X 10.5.6; U; en)',
20 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)',
21 'Opera/9.80 (Windows NT 6.0) Presto/2.12.388 Version/12.14',
22 'Mozilla/5.0 (iPad; CPU OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5355d Safari/8536.25',
23 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1468.0 Safari/537.36',
24 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0; Trident/5.0; TheWorld)']
25
26
27defbaidu_search(keyword,pn):
28 p= urllib.urlencode({'wd':keyword})
29 print(p)
30 req = urllib2.Request(("http://www.baidu.com/s?"+p+"&pn={0}&cl=3&rn=10").format(pn))
31 req.add_header('User-Agent', random.choice(user_agents))
32try:
33 res=urllib2.urlopen(req)
34 html=res.read()
35except:
36 html = ''
37returnhtml
38
39
40defgetList(regex,text):
41 arr = []
42 res = re.findall(regex, text)
43ifres:
44forrinres:
45 arr.append(r)
46returnarr
47
48
49defgetMatch(regex,text):
50 res = re.findall(regex, text)
51ifres:
52returnres[0]
53return''
54
55
56defis_get(url):
57
58 regex=r'(S*?)?.*=.*'
59 res=re.match(regex,url)
60ifres:
61returnres.group(1)
62else:
63return0
64
65
66defgeturl(domain,pages=10):
67 keyword = 'site:.'+domain
68 targets = []
69 hosts=[]
70forpageinrange(0,int(pages)):
71 pn=(page+1)*10
72 html = baidu_search(keyword,pn)
73 content = unicode(html, 'utf-8','ignore')
74 arrList = getList(u"<div class="f13">(.*)</div>", content)
75
76foriteminarrList:
77 regex = u"data-tools='{"title":"(.*)","url":"(.*)"}'"
78 link = getMatch(regex,item)
79 url=link[1]
80try:
81 domain=urllib2.Request(url)
82 r=random.randint(0,11)
83 domain.add_header('User-Agent', user_agents[r])
84 domain.add_header('Connection','keep-alive')
85 response=urllib2.urlopen(domain)
86 uri=response.geturl()
87 urs = split_url.split(uri)
88
89if(uriintargets)or(ursinhosts) :
90continue
91else:
92 targets.append(uri)
93 hosts.append(urs)
94 f1=open('data/baidu.txt','a')
95 f1.write(urs+' ')
96 f1.close()
97except:
98continue
99print"urls have been grabed already!!!"
100returnhosts
101
102
103if__name__ == '__main__':
104 print(geturl("cnblogs.com"))
0x04 Web应用数据挖掘
这种数据挖掘方式主要针对Web自身,旨在获取Web应用信息/Web指纹,在Web安全领域应用较多,这类代表有zoomeye、sodan等。通过获取大范围的Web应用信息,Web应用类型、版本,Web插件信息等,能够对大范围内的Web安全状况进行评估,分析特定漏洞在全球范围内造成的影响。当然也可以利用特定漏洞对大范围的Web应用进行定向攻击。
在这里我们不讨论那种大范围的扫描,我们只以CMS识别为例来简单说明Web应用数据的挖掘。CMS识别旨在判别网站所采用的CMS(内容管理系统,如WordPress),为后续的插件检测或漏洞检测做准备。
CMS识别一般从4个方面进行检测:检测特定目录是否存在;比对特定文件MD5;检测HTML页面中的关键字;检测robots文件。另外,一个巨大的CMS指纹库是保证识别效率的关键,如果指纹库太小,实际效果并不会很好。但是如果指纹库太大,又会影响到识别的速率。我搜集了一些简单的CMS指纹,写了一个简单的CMS识别脚本。代码如下:
1 # coding:utf-8
2 '''
3 CMS识别
4 Author: bsdr
5 Email: 1340447902@qq.com
6 '''
7importQueue
8importre
9importos
10importtime
11importrequests
12importthreading
13importurllib2
14importhashlib
15importsys
16fromconfigimportPOC_PATH
17
18 t = time.time() # 起始时间
19
20 event = threading.Event() # 全局event,用来控制线程状态
21
22 RETRY = 3 # 验证url时尝试次数
23 TIMEOUT = 3 # 超时
24 THREADS = 300 # 开启的线程数
25 CMS_PATH = os.path.join(POC_PATH, 'CMS2\') # CMS指纹文件目录
26
27 CMS = 'Unknown'
28 HEADER = {'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 5.1; '
29 'en-US; rv:1.9.1.11) Gecko/20100701 Firefox/3.5.11'}
30
31
32classCms:
33def__init__(self, url, line):
34 self.url = url
35 self.line = line
36printline
37
38
39 # 检测文件md5
40def_get_md5(self, file):
41 m = hashlib.md5()
42
43try:
44 m.update(file)
45except:
46whileTrue:
47 data = file.read(10240) # 避免文件太大,内存不够
48ifnotdata:
49break
50 m.update(data)
51
52returnm.hexdigest()
53
54
55 # 检测每一行指纹
56defcheck(self):
57globalCMS
58globalevent
59 cms = re.findall(r'(.*?)|', self.line)
60 path = cms[0]
61 cms_name = cms[1]
62 keyword = cms[2]
63 content = ''
64
65try:
66 response = requests.get(self.url+path)
67ifresponse.status_code == 200:
68 content = response.content
69except:
70try:
71 content = urllib2.urlopen(self.url+path, timeout=TIMEOUT).read()
72except:
73pass
74
75ifcontentisnotNoneandcontent != '':
76
77iflen(cms) == 3andcontent.find(keyword) != -1:
78 CMS = cms_name
79printcms
80 event.set() # 识别出cms后,改变event状态
81
82eliflen(cms) == 4andself._get_md5(content) == cms[3]:
83 CMS = cms_name
84 event.set()
85printcms
86
87
88
89 # 创建线程类,定义自己的线程
90classmyThread(threading.Thread):
91def__init__(self, q, thread_id):
92 threading.Thread.__init__(self)
93 self.q = q
94 self.thread_id = thread_id
95
96
97defrun(self):
98globalevent
99whilenotself.q.empty():
100 # 检测event状态判断线程是否执行
101ifevent.is_set():
102print" [+] stop threading " + str(self.thread_id)
103break
104print" [*] threading " + str(self.thread_id) + " is running"
105 objects = self.q.get()
106 objects.check()
107
108
109 # 初始化url,并验证是否可以连接
110defcheck_url(url):
111 url = 'http://' + urlifurl.startswith('http') ==Falseelseurl
112 url = url[0:len(url) - 1]ifurl.endswith('/')elseurl
113
114foriinrange(RETRY):
115try:
116 response = urllib2.urlopen(url, timeout=TIMEOUT)
117ifresponse.code == 200:
118returnurl
119except:
120raiseException("Connect error")
121
122
123 # 遍历指定目录下所有文件的每一行
124defload_cms():
125 cms_list = []
126
127forroot, dirs, filesinos.walk(CMS_PATH):
128forfinfiles:
129 fp = open(CMS_PATH + f, 'r')
130 content = fp.readlines()
131 fp.close()
132forlineincontent:
133ifline.startswith('/'):
134 line = line.strip(' ')
135 cms_list.append(line)
136
137returncms_list
138
139
140 # 创建线程
141defmain(url):
142globalCMS
143 url = check_url(url)
144 cms_list = load_cms()
145assertlen(cms_list) > 0
146 work_queue = Queue.Queue()
147
148 # 装载任务
149forpathincms_list:
150 work_queue.put(Cms(url, path))
151 threads = []
152 nloops = range(THREADS)
153
154 # 启动线程
155foriinnloops:
156 t = myThread(work_queue, i)
157 t.start()
158 threads.append(t)
159
160foriinnloops:
161 t.join()
162
163 #return True, CMS
164
165classPoc:
166defrun(self,target):
167 main(target)
168 cms = CMS
169ifcms == 'Unknown':
170returncms,False
171else:
172returncms,True
173
174if__name__ == '__main__':
175 cms, is_succes = Poc().run('software.hrbeu.edu.cn')
176print'[!] CMS ==> %s' % cms
177print'[!] 用时:%f s' % (time.time()-t)
0x05 总结
以上内容全部由我自己编写爬虫的经验总结而来,如有问题,欢迎指正
网友评论