chhy 发表于 2016-6-22 15:16:46

Python爬虫进行Web数据挖掘总结和分析


文章来源:http://www.afenxi.com/post/14962
作者:BSDR
链接:http://www.cnblogs.com/bsdr/p/5419680.html


http://www.afenxi.com/wp-content/uploads/2016/04/1461515253.png
0x01 Web数据挖掘类型利用Python爬虫进行Web数据挖掘已经越来越普遍,网上的各种Python爬虫资料教程比较多,但是很少有人对Web数据挖掘进行系统地总结和分析。
从目标上来讲,Web数据挖掘分为三类。最常见的是对于网站内容的爬取,包括文本、图片和文件等;其次是对于网站结构的爬取,包括网站目录,链接之间的相互跳转关系,二级域名等;还有一种爬虫是对于Web应用数据的挖掘,包括获取网站CMS类型,Web插件等。
0x02 网站内容挖掘网站内容挖掘应用最广,最为常见,网上的Python爬虫资料大多也都属于这类。爬取下的内容也可用于很多方面。
Python编写这类爬虫的常见思路就是利用request或urllib2库定制请求,利用BeautifulSoup对原始网页进行解析,定位特定html标签,寻找目标内容。如果要提高性能,可以利用threading启用多线程,gevent启用协程(在windows上使用可能会有些问题),也可以用multiprocessing启动多进程。multiprocessing能突破python的GIL全局解释器锁的限制。其他的一些技巧可以看我的另一篇博客:常见的反爬虫和应对方法
这类爬虫资料实在太多,在这里不再赘述了。
0x03 网站结构挖掘网站结构挖掘并不是很常见,但在一些特殊的应用场景,我们也会用到。例如对于Web漏洞扫描器,爬取网站整站目录,获取二级域名是极为重要的。在第一类网站内容挖掘中,有时也需要将目标网站某个页面(通常是首页)作为入口,对整个网站所有内容进行获取和分析,这种情况下就需要对网站结构进行分析。
对于网站目录爬取,需要考虑的一个重要问题就是爬虫性能。通常网站的页面会比较多,如果直接获取所有目录,可能会耗费大量时间。另外,对于网站链接的搜索策略对爬虫的性能也会产生很大影响。一般情况下,我们会采用广度优先搜索,从入口页面开始,获取该页面内所有链接,并判断链接是否是站内链接,是否已经爬取过。为了提高速度,可以对链接进行归纳,将/page.php?id=1与/page.php?id=2认为是同一类型链接,不进行重复爬取。简单实现代码如下:
1 # coding=utf-82 '''3 爬取网站所有目录4 Author: bsdr5 Email: 1340447902@qq.com6 '''7 import urllib28 import re9 from BeautifulSoup import BeautifulSoup 10 import time 1112 t = time.time() 1314 HOST = '' 15 CHECKED_URL = []# 已检测的url规则 16 CHECKING_URL = []# 待检测的url 17 RESULT = []# 检测结果 18 RETRY = 3# 重复尝试次数 19 TIMEOUT = 2# 超时 202122 class url_node: 23   def __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 = '' 333435   def handle_url(self, url, is_next_url=True): 36         ''' 37         将所有url处理成标准格式 3839         :param url: String 40         :param is_next_url:Bool, 判断传入的url是当前需要检测的url还是下一层url 41         :return: 返回空或错误信息或正确url 42         ''' 43         global CHECKED_URL 44         global CHECKING_URL 4546         # 去掉结尾的’/‘ 47         url = url if url.endswith('/') else url 4849         if url.find(HOST) == -1: 50             if not url.startswith('http'): 51               url = 'http://' + HOST + url if url.startswith('/') else 'http://' + HOST + '/' + url 52             else: 53               # 如果url的host不为当前host,返回空 54               return 55         else: 56             if not url.startswith('http'): 57               url = 'http://' + url 5859         if is_next_url: 60             # 下一层url放入待检测列表 61             CHECKING_URL.append(url) 62         else: 63             # 对于当前需要检测的url 64             # 将其中的所有参数替换为1 65             # 然后加入url规则表 66             # 参数不同,类型相同的url,只检测一次 67             rule = re.compile(r'=.*?\&|=.*?$') 68             result = re.sub(rule, '=1&', url) 69             if result in CHECKED_URL: 70               return '[!] Url has checked!' 71             else: 72               CHECKED_URL.append(result) 73               RESULT.append(url) 7475         return url 767778   def __is_connectable(self): 79         # 验证是否可以连接 80         retry = 3 81         timeout = 2 82         for i in range(RETRY): 83             try: 84               response = urllib2.urlopen(self.url, timeout=TIMEOUT) 85               return True 86             except: 87               if i == retry - 1: 88                     return False 899091   def get_next(self): 92         # 获取当前页面所有url 93         soup = BeautifulSoup(self.content) 94         next_urls = soup.findAll('a') 95         if len(next_urls) != 0: 96             for link in next_urls: 97               self.handle_url(link.get('href')) 9899 100   def run(self):101         if self.url:102             print self.url103             if self.__is_connectable():104               try:105                     self.content = urllib2.urlopen(self.url, timeout=TIMEOUT).read()106                     self.get_next()107               except:108                     print('[!] Connect Failed')109 110 111 class Poc:112   def run(self, url):113         global HOST114         global CHECKING_URL115         url = check_url(url)116 117         if not url.find('https'):118             HOST = url119         else:120             HOST = url121 122         for url in CHECKING_URL:123             print(url)124             url_node(url).run()125 126 127 def check_url(url):128   url = 'http://' + url if not url.startswith('http') else url129   url = url if url.endswith('/') else url130 131   for i in range(RETRY):132         try:133             response = urllib2.urlopen(url, timeout=TIMEOUT)134             return url135         except:136             raise Exception("Connect error")137 138 139 if __name__ == '__main__':140   HOST = 'www.hrbeu.edu.cn'141   CHECKING_URL.append('http://www.hrbeu.edu.cn/')142   for url in CHECKING_URL:143         print(url)144         url_node(url).run()145   print RESULT146   print "URL num: "+str(len(RESULT))147   print "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-82 '''3 利用百度搜索二级域名4 Author: bsdr5 Email:1320227902@qq.com6 '''7   8   9 import urllib2 10 import string 11 import urllib 12 import re 13 import random 14 from url_handle import split_url 1516 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 (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)'] 252627 def baidu_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)) 32   try: 33         res=urllib2.urlopen(req) 34         html=res.read() 35   except: 36         html = '' 37   return html 383940 def getList(regex,text): 41   arr = [] 42   res = re.findall(regex, text) 43   if res: 44         for r in res: 45             arr.append(r) 46   return arr 474849 def getMatch(regex,text): 50   res = re.findall(regex, text) 51   if res: 52         return res 53   return '' 545556 def is_get(url): 5758   regex=r'(\S*?)\?.*=.*' 59   res=re.match(regex,url) 60   if res: 61         return res.group(1) 62   else: 63         return 0 646566 def geturl(domain,pages=10): 67   keyword = 'site:.'+domain 68   targets = [] 69   hosts=[] 70   for page in range(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) 7576         for item in arrList: 77             regex = u"data-tools='\{\"title\":\"(.*)\",\"url\":\"(.*)\"\}'" 78             link = getMatch(regex,item) 79             url=link 80             try: 81               domain=urllib2.Request(url) 82               r=random.randint(0,11) 83               domain.add_header('User-Agent', user_agents) 84               domain.add_header('Connection','keep-alive') 85               response=urllib2.urlopen(domain) 86               uri=response.geturl() 87               urs = split_url.split(uri) 8889               if (uri in targets) or (urs in hosts) : 90                     continue 91               else: 92                     targets.append(uri) 93                     hosts.append(urs) 94                     f1=open('data/baidu.txt','a') 95                     f1.write(urs+'\n') 96                     f1.close() 97             except: 98               continue 99   print "urls have been grabed already!!!"100   return hosts101 102 103 if __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-82 '''3 CMS识别4 Author: bsdr5 Email: 1340447902@qq.com6 '''7 import Queue8 import re9 import os 10 import time 11 import requests 12 import threading 13 import urllib2 14 import hashlib 15 import sys 16 from config import POC_PATH 1718 t = time.time()                  # 起始时间 1920 event = threading.Event()      # 全局event,用来控制线程状态 2122 RETRY = 3                        # 验证url时尝试次数 23 TIMEOUT = 3                      # 超时 24 THREADS = 300                  # 开启的线程数 25 CMS_PATH = os.path.join(POC_PATH, 'CMS2\\')            # CMS指纹文件目录 2627 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'} 303132 class Cms: 33   def __init__(self, url, line): 34         self.url = url 35         self.line = line 36         print line 373839   # 检测文件md5 40   def _get_md5(self, file): 41         m = hashlib.md5() 4243         try: 44             m.update(file) 45         except: 46             while True: 47               data = file.read(10240)          # 避免文件太大,内存不够 48               if not data: 49                     break 50               m.update(data) 5152         return m.hexdigest() 535455   # 检测每一行指纹 56   def check(self): 57             global CMS 58             global event 59             cms = re.findall(r'(.*?)\|', self.line) 60             path = cms 61             cms_name = cms 62             keyword = cms 63             content = '' 6465             try: 66               response = requests.get(self.url+path) 67               if response.status_code == 200: 68                     content = response.content 69             except: 70               try: 71                     content = urllib2.urlopen(self.url+path, timeout=TIMEOUT).read() 72               except: 73                     pass 7475             if content is not None and content != '': 7677                     if len(cms) == 3 and content.find(keyword) != -1: 78                         CMS = cms_name 79                         print cms 80                         event.set()             # 识别出cms后,改变event状态 8182                     elif len(cms) == 4 and self._get_md5(content) == cms: 83                         CMS = cms_name 84                         event.set() 85                         print cms 86878889 # 创建线程类,定义自己的线程 90 class myThread(threading.Thread): 91   def __init__(self, q, thread_id): 92         threading.Thread.__init__(self) 93         self.q = q 94         self.thread_id = thread_id 959697   def run(self): 98         global event 99         while not self.q.empty():100             # 检测event状态判断线程是否执行101             if event.is_set():102               print "\n[+] stop threading " + str(self.thread_id)103               break104             print "\n
threading " + str(self.thread_id) + " is running"105             objects = self.q.get()106             objects.check()107 108 109 # 初始化url,并验证是否可以连接110 def check_url(url):111   url = 'http://' + url if url.startswith('http') == False else url112   url = url if url.endswith('/') else url113 114   for i in range(RETRY):115             try:116               response = urllib2.urlopen(url, timeout=TIMEOUT)117               if response.code == 200:118                     return url119             except:120               raise Exception("Connect error")121 122 123 # 遍历指定目录下所有文件的每一行124 def load_cms():125   cms_list = []126 127   for root, dirs, files in os.walk(CMS_PATH):128         for f in files:129             fp = open(CMS_PATH + f, 'r')130             content = fp.readlines()131             fp.close()132             for line in content:133               if line.startswith('/'):134                     line = line.strip('\n')135                     cms_list.append(line)136 137   return cms_list138 139 140 # 创建线程141 def main(url):142   global CMS143   url = check_url(url)144   cms_list = load_cms()145   assert len(cms_list) > 0146   work_queue = Queue.Queue()147 148   # 装载任务149   for path in cms_list:150         work_queue.put(Cms(url, path))151   threads = []152   nloops = range(THREADS)153 154   # 启动线程155   for i in nloops:156         t = myThread(work_queue, i)157         t.start()158         threads.append(t)159 160   for i in nloops:161         t.join()162 163   #return True, CMS164 165 class Poc:166   def run(self,target):167         main(target)168         cms = CMS169         if cms == 'Unknown':170             return cms, False171         else:172             return cms, True173 174 if __name__ == '__main__':175   cms, is_succes = Poc().run('software.hrbeu.edu.cn')176   print '[!] CMS ==> %s' % cms177   print '[!] 用时:%f s' % (time.time()-t)

页: [1]
查看完整版本: Python爬虫进行Web数据挖掘总结和分析