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.php?id=1與/page.php?id=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.url
103             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 HOST
114         global CHECKING_URL
115         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 url
129     url = url[0:len(url) - 1] if url.endswith('/') else url
130 
131     for i in range(RETRY):
132         try:
133             response = urllib2.urlopen(url, timeout=TIMEOUT)
134             return url
135         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 RESULT
146     print "URL num: "+str(len(RESULT))
147     print "time: %d s" % (time.time() - t)
View Code

 

  對於二級域名的獲取,如果直接從主站爬取的鏈接中尋找,效率很低而且結果可能並不能讓人滿意。目前獲取二級域名有三種常用方法,第一種是利用域名字典進行猜解,類似於暴力破解。第二種種是利用各種二級域名查詢接口進行查詢,例如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 
  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 hosts
101 
102 
103 if __name__ == '__main__':
104     print(geturl("cnblogs.com"))
View Code

 

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                 break
104             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 url
112     url = url[0:len(url) - 1] if url.endswith('/') else url
113 
114     for i in range(RETRY):
115             try:
116                 response = urllib2.urlopen(url, timeout=TIMEOUT)
117                 if response.code == 200:
118                     return url
119             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_list
138 
139 
140 # 創建線程
141 def main(url):
142     global CMS
143     url = check_url(url)
144     cms_list = load_cms()
145     assert len(cms_list) > 0
146     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, CMS
164 
165 class Poc:
166     def run(self,target):
167         main(target)
168         cms = CMS
169         if cms == 'Unknown':
170             return cms, False
171         else:
172             return cms, True
173 
174 if __name__ == '__main__':
175     cms, is_succes = Poc().run('software.hrbeu.edu.cn')
176     print '[!] CMS ==> %s' % cms
177     print '[!] 用時:%f s' % (time.time()-t)
View Code

 

0x05 總結

  以上內容全部由我自己編寫爬蟲的經驗總結而來,如有問題,歡迎指正。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM