這個是我在一家網絡安全公司面試時的操作題,回來後經過多次修改後纔得到一個比較完整的程序。
整個模塊可以分成兩個大部分。一個是數據庫的操作,一個是信息的獲取(類似爬蟲?)
信息的獲取分爲四個小操作,ip的獲取、端口的獲取、服務器類型的獲取和標題的獲取。
IP的獲取:
ip的獲取代碼:
def getIp(self,url):
try:
str_ip = str(socket.gethostbyname(url[7:])) #通過socket方式以域名獲取IP
self.print_msg(0, str_ip, "")
self.ip = str_ip
if str_ip:
self.correct += 1
except socket.error, e:
print u"ip獲取失敗"
ip的獲取其實比較簡單,就是用socket模塊的方法根據域名訪問獲取ip。要注意的是,這個域名是不包括”http://“,所以代碼中用url[7:]
端口的獲取其實更簡單:
def getPort(self): #通過socket返回端口號
try:
str_port = str(socket.getservbyname('http', 'tcp'))
self.print_msg(1, str_port, "")
self.port = str_port
if str_port:
self.correct += 1
except socket.error,e:
print u"端口號獲取失敗"
用的也是socket模塊的方法。
服務器類型的獲取:
一開始我的想法是通過正則去匹配應答頭,但是過了兩天發現其實有更簡單的方法,直接用urllib2模塊的方法去訪問,回傳的數據中的header中直接找到"Server"這個鍵對應的鍵值就是服務器類型了。
def getServerType(self): #在header字典裏尋找Server對應的鍵值
self.server = str(self.header.get("Server"))
self.print_msg(2,self.server, "")
if self.server:
self.correct +=1
最後就是標題的獲取,這個其實算是最難的一個信息。標題沒有像服務器類型一樣存在header裏面,而是在HTML文本里聲明,其次在使用正則匹配時,我們首先要知道當前訪問的頁面的編碼方式,這又需要去獲取,最後不同的網頁並不會固定title標籤出現的位置,雖然大致位置一樣。
所以我的想法是,先在回傳數據的header裏面找到‘content-type’(正常網頁的編碼方式都會在這裏聲明)。
然後獲得這個字符串還不是編碼方式,還要通過正則匹配匹配 charset:() 括號裏面的纔是真正的編碼方式。
接着使用這個編碼方式去對獲取到的html文本解碼,再編碼成需要的類型(我的pycharm用utf-8)。
再就是對獲取到的文體再正則匹配,這次匹配的是title標籤裏面的內容。
最後輸出第一個匹配到的內容,輸出的時候還要注意不同編碼形式的字符串不能相連。
def getTitle(self): #通過正則匹配<title>標籤內的字符
data1 = self.header.get('content-type')
pattern = re.compile(u"charset=(.*)")
ress = pattern.findall(data1)
if not ress: #如果在header列表找不到該網頁的編碼形式,則在response裏匹配
pattern = re.compile(r"charset=(.*)")
ress = pattern.findall(self.response)
if ress:
temp = self.response.decode(ress[0],'ignore').encode("utf-8")
xx = u"<title>(.*)</title>"
pattern = re.compile(xx)
results = pattern.findall(temp)
if results:
self.print_msg(3, "", results[0])
self.title = results[0]
if results[0]:
self.correct += 1
else:
print u"找不到標題"
這四個內容都獲取到了,就存到mysql數據庫裏面。
總的代碼如下:
這是信息獲取部分
# *_*coding:utf-8 *_*
import Queue
import gevent
import urllib2
import socket
import sys
import re
import threading
import DbSave
class method1:
def __init__(self,strurl):
self.domain = ""
self.ip = ""
self.port = 0
self.server = ""
self.title = ""
self.correct = 0
self.problem_url = []
self.queue = Queue.Queue()
self.typeencode = sys.getfilesystemencoding()
if strurl:
for self.strurl in strurl:
if self.check_domain(self.strurl):
self.queue.put("http://"+self.strurl)
else:
print u"域名輸入格式不正確"
exit(0)
else:
print u"沒有輸入域名"
exit(0)
if not self.queue.empty():
threads = [gevent.spawn(self.declare, i) for i in range(5)]
try:
gevent.joinall(threads)
except KeyboardInterrupt, e:
msg = '[WARNING] User aborted.'
print self.problem_url
def declare(self,i):
while not self.queue.empty():
url = self.queue.get()
try:
self.domain = url
print u"域名:"+url
self.urlget(url)
self.getIp(url)
self.getPort()
self.getServerType()
self.getTitle()
print "-------------------------"
DbSave.DbInsert(self.domain, self.ip,int(self.port), self.server, self.title)
self.domain = ""
self.ip = ""
self.port = 0
self.server = ""
self.title = ""
self.correct = 0
except:
print "數據庫寫入錯誤"
if self.correct != 4:
self.problem_url.append(url)
self.correct = 0
def urlget(self,url):
try:
res = urllib2.urlopen(url) #urllib2的get方法訪問url
self.response = res.read() #獲取正文
self.header = res.headers #獲取應答頭
except urllib2.URLError,e:
print u"urllib2訪問失敗,退出.."
exit(0)
def getIp(self,url):
try:
str_ip = str(socket.gethostbyname(url[7:])) #通過socket方式以域名獲取IP
self.print_msg(0, str_ip, "")
self.ip = str_ip
if str_ip:
self.correct += 1
except socket.error, e:
print u"ip獲取失敗"
def getPort(self): #通過socket返回端口號
try:
str_port = str(socket.getservbyname('http', 'tcp'))
self.print_msg(1, str_port, "")
self.port = str_port
if str_port:
self.correct += 1
except socket.error,e:
print u"端口號獲取失敗"
def getServerType(self): #在header字典裏尋找Server對應的鍵值
self.server = str(self.header.get("Server"))
self.print_msg(2,self.server, "")
if self.server:
self.correct +=1
def getTitle(self): #通過正則匹配<title>標籤內的字符
data1 = self.header.get('content-type')
pattern = re.compile(u"charset=(.*)")
ress = pattern.findall(data1)
if not ress: #如果在header列表找不到該網頁的編碼形式,則在response裏匹配
pattern = re.compile(r"charset=(.*)")
ress = pattern.findall(self.response)
if ress:
temp = self.response.decode(ress[0],'ignore').encode("utf-8")
xx = u"<title>(.*)</title>"
pattern = re.compile(xx)
results = pattern.findall(temp)
if results:
self.print_msg(3, "", results[0])
self.title = results[0]
if results[0]:
self.correct += 1
else:
print u"找不到標題"
@staticmethod
def print_msg(signs, meg, title):
switcher = {0: u"ip地址:",
1: u"端口號:",
2: u"服務器信息:",
3: u"標題:",
4: u"協程開啓錯誤"}
if meg:
print switcher.get(signs)+meg
else:
print switcher.get(signs).encode("utf-8")+title
@staticmethod
def check_domain(domain):
pattern = re.compile(r'(?i)^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$')
return pattern.match(domain)
if __name__ == '__main__':
L = ["www.51.com", "www.baidu.com", "www.1688.com", "www.taobao.com", "www.hao123.com",
"www.sohu.com", "www.youku.com", "www.taobao.com", "www.ifeng.com", "www.jd.com",
"www.4399.com", "www.126.com", "www.scnu.edu.cn", "www.163yun.com", "www.58.com",
"www.37.com", "www.tmall.com", "www.7k7k.com", "www.youxia.com", "sports.cctv.com",
"www.163.com", "v.qq.com", "www.bilibili.com", "www.hupu.com", "www.qidian.com",
"www.zol.com", "mail.qq.com", "www.51job.com", "www.liepin.com", "www.10086.cn",
"www.189.cn", "www.12306.com", "www.10010.com", "www.zol.com", "www.stockstar.com",
]
method1(L)
這是數據庫部分:
# coding=utf-8
import MySQLdb
def Dbcreate():
try:
conn = MySQLdb.connect(host='localhost', user='root', passwd='', db='pydatabase', port=3306,charset='utf8')
cur = conn.cursor()
urlmanager = """CREATE TABLE URLMESSAGE(
DOMAIN CHAR(35) NOT NULL,
IP CHAR(15),
PORT INT,
SERVER VARCHAR(20),
TITLE TEXT
CHARACTER SET utf8 COLLATE utf8_general_ci
)
"""
cur.execute(urlmanager)
cur.close()
conn.close()
except MySQLdb.Error, e:
print "Mysql Error %d: %s" % (e.args[0], e.args[1])
def DbInsert(domain, ip, port, server, title):
try:
conn = MySQLdb.connect(host='localhost', user='root', passwd='', db='pydatabase', port=3306,charset="utf8")
cur = conn.cursor()
cur.execute("INSERT INTO urlmessage(DOMAIN,IP,PORT,SERVER,TITLE) VALUES('%s','%s','%d','%s','%s')"%(domain,ip,port,server,title))
cur.close()
conn.commit()
conn.close()
except MySQLdb.Error, e:
print "Mysql Error %d: %s" % (e.args[0], e.args[1])
尚未解決的問題:
1:在有些網頁得到訪問時間過長,會造成阻塞。
2:某些特殊的網頁獲取不到編碼方式(只有www.qq.com出現這個問題)...
3:數據庫的操作過於粗暴,正常來說不應該這麼簡單,導致一出現問題數據庫就不工作了..
3:有些網頁的標籤無法獲取。
解決方法:
1:應該使用多進程+協程的方法,我只是使用了協程(因爲面試有道題目就是用協程,我只是想嘗試一下..),通過多進程去設置訪問超時時間。
2:這個問題尚未想到有什麼解決的方案...因爲只有這個域名出問題..
3:由於學數據庫的時間太短,所以只會很簡單的操作。這裏我的想法是在信息獲取和數據庫操作之間加個中間層,設置一個緩存區,這樣纔可以一次打開數據庫進行所有操作,而不是獲取一個網頁信息就操作一次。還有,數據庫操作應該要很謹慎地處理數據,要有很好的異常處理機制(不是像我這樣這麼粗暴...),注意事務的提交和回滾。
4:這個問題的原因應該是正則匹配的不正確,因爲不同網頁HTML文本差的有點遠...,暫時還沒想到有什麼方法..
還可以提升的部分:
在獲取標題的部分,其實是把整個HTML都匹配一次,但其實我們只需要第一個獲取到的title標籤的內容.
輸出內容時,不應該每獲取到一個信息就調用一次print,可以統一輸出。
好像在bs4的beautlfulsoup方法中有更好的對HTML文本的處理方法