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

利用Python爬虫进行Web数据挖掘已经越来越普遍,网上的各种Python爬虫资料教程比较多,但是很少有人对Web数据挖掘进行系统地总结和分析。

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

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.phpid=1与/page.phpid=2认为是同一类型链接,不进行重复爬取。简单实现代码如下:

  1 # coding=utf-8  2 '''  3 爬取网站所有目录  4 Author: bsdr  5 Email: 1340447902@qq.com  6 '''  7 import urllib2  8 import re  9 from BeautifulSoup import BeautifulSoup 10 import time 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  22 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 = '' 33  34  35     def handle_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         ''' 43         global CHECKED_URL 44         global CHECKING_URL 45  46         # 去掉结尾的’/‘ 47         url = url[0:len(url) - 1] if url.endswith('/') else url 48  49         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 58  59         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) 74  75         return url 76  77  78     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 89  90  91     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')) 98  99 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 = url[8:]119         else:120             HOST = url[7:]121 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[0:len(url) - 1] 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/searchcount=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   9 import urllib2 10 import string 11 import urllib 12 import re 13 import random 14 from url_handle import split_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  27 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 38  39  40 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 47  48  49 def getMatch(regex,text): 50     res = re.findall(regex, text) 51     if res: 52         return res[0] 53     return '' 54  55  56 def is_get(url): 57  58     regex=r'(S*).*=.*' 59     res=re.match(regex,url) 60     if res: 61         return res.group(1) 62     else: 63         return 0 64  65  66 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) 75  76         for item in arrList: 77             regex = u"data-tools='{"title":"(.*)","url":"(.*)"}'" 78             link = getMatch(regex,item) 79             url=link[1] 80             try: 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  89                 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-8  2 '''  3 CMS识别  4 Author: bsdr  5 Email: 1340447902@qq.com  6 '''  7 import Queue  8 import re  9 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 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  32 class Cms: 33     def __init__(self, url, line): 34         self.url = url 35         self.line = line 36         print line 37  38  39     # 检测文件md5 40     def _get_md5(self, file): 41         m = hashlib.md5() 42  43         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) 51  52         return m.hexdigest() 53  54  55     # 检测每一行指纹 56     def check(self): 57             global CMS 58             global event 59             cms = re.findall(r'(.*)|', self.line) 60             path = cms[0] 61             cms_name = cms[1] 62             keyword = cms[2] 63             content = '' 64  65             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 74  75             if content is not None and content != '': 76  77                     if len(cms) == 3 and content.find(keyword) != -1: 78                         CMS = cms_name 79                         print cms 80                         event.set()             # 识别出cms后,改变event状态 81  82                     elif len(cms) == 4 and self._get_md5(content) == cms[3]: 83                         CMS = cms_name 84                         event.set() 85                         print cms 86  87  88  89 # 创建线程类,定义自己的线程 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 95  96  97     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[0:len(url) - 1] 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)

0x05 总结

以上内容全部由我自己编写爬虫的经验总结而来,如有问题,欢迎指正。

作者:BSDR

链接:http://www.cnblogs.com/bsdr/p/5419680.html

本文采用「CC BY-SA 4.0 CN」协议转载自互联网、仅供学习交流,内容版权归原作者所有,如涉作品、版权和其他问题请给「我们」留言处理。

(9)
上一篇 2016-04-23 22:00
下一篇 2016-04-26 16:13

相关文章

关注我们
关注我们
分享本页
返回顶部