域名遍歷搜索python實現

前記——

其實我的第一篇博客就是寫如何獲取給定網址的信息,也有那家公司的面試題。

最近一個星期想把那個面試題中的程序研究透徹..

畢竟既然有可以參考的大公司的大神的代碼,肯底要學習一番嘛,(師夷長技以制夷..不對,額反正就大概是這個意思啦)

正文——

程序的功能很簡單,就遍歷可能的域名組合,每個地址都訪問一次,獲取它的標題。
功能是很簡單,但是網址數一旦多就變得很麻煩。
實現的方式是採用生產者——消費者模式。定義兩個類,一個用於產生網址,另外一個用於判斷網址。
具體使用到的技術:
1:產生地址是採用yield生成器,以便後續的程序改進。
2:判斷地址的步驟分爲五步。1:開啓判斷地址的多進程(默認爲6個)
                                                 2:每個進程開啓一定數目的協程(默認50個)
                                                 3:在進程安全的隊列中取出網址,並初始化dns模塊,以dns模塊首先判斷網址是否可以解析。
                                                 4:再使用urllib模塊的urlopen方法判斷訪問網址的狀態碼。
                                                 5:採用urllib2模塊獲取網頁的正文。
                                                 6:使用beautifulsoup處理html文本,獲取標題。
簡單的大致流程就是這樣。
以下是代碼:
# coding=utf-8
import dns
import sys
sys.setrecursionlimit(10000)
import string

import urllib
import urllib2
import multiprocessing
from multiprocessing import Queue
from multiprocessing import RLock
import gevent
from gevent import monkey
import MySQLdb
import dns.resolver
from bs4 import BeautifulSoup
# 一定不能讓猴子補丁覆蓋掉線程thread類,不然程序不會運行。參考 http://xiaorui.cc/2016/04/27/%e6%ba%90%e7%a0%81%e5%88%86%e6%9e%90%e4%b9%8bgevent-monkey-patch_all%e5%ae%9e%e7%8e%b0%e5%8e%9f%e7%90%86/
monkey.patch_all(thread=False)
#socket.setdefaulttimeout(8)

domainqueue = Queue()
Message = Queue()
Mesdict = {}
usefulIp = Queue()
uselessIP = Queue()

class subsearch(object):
    def __init__(self):
        self.set = set()
        net = self._creatdomain()
        for i in range(100000):
            a = net.next()
            self.set.add('www.' + a + '.com')
        for i in self.set:
            domainqueue.put_nowait(i)

    def _creatdomain(self):
        ergodicword = ['', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
        for word in string.lowercase:
            ergodicword.append(word)  # 生成26個英文字母並添加進去
        for i in ergodicword:
            for j in ergodicword:
                for k in ergodicword:
                    for l in ergodicword:
                        for m in ergodicword:
                            yield i + j + k + l + m


class subvisit(object):
    def __init__(self, DNSSERVER, headers):
        self.dnsserver = DNSSERVER
        self.run()
        self.headers = headers

    def start(self, i):
        self.id = i
        self.work()

    def work(self):
        resolver1 = dns.resolver.Resolver()
        resolver1.lifetime = resolver1.timeout = 5.0
        resolver1.nameservers = [self.dnsserver]
        A = ''
        while not domainqueue.empty():
            try:
                url = domainqueue.get()
            except BaseException:
                break
            try:
                A = resolver1.query(url)
            except dns.resolver.NXDOMAIN as xxx_todo_changeme:
                dns.resolver.Timeout = xxx_todo_changeme
                continue
            except dns.exception.DNSException:
                continue
            if A:
                headers['Host'] = url
                try:
                    codenum = urllib.urlopen('http://' + url).getcode()
                    if codenum == 200 or codenum == 304:
                        pass
                    else:
                        continue
                    req = urllib2.Request('http://' + url, headers=headers)
                    res = urllib2.urlopen(
                        req, timeout=10)  # urllib2的get方法訪問url
                    response = res.read()  # 獲取正文
                    res.close()
                    del res
                    soup = BeautifulSoup(response)
                    title = soup.title
                except Exception as e:
                    #title = "Not Found"
                    continue
                if title:
                    title = str(title)[7:-8]
                    lock.acquire()
                    print str(url).ljust(13),str(A[0].address).ljust(15),title
                    lock.release()
                #Message.put_nowait((url, A[0].address,title))
                # usefulIp.put_nowait(url)

    def run(self):
        try:
            threads = [gevent.spawn(self.start, i) for i in range(50)]
            gevent.joinall(threads)
        except KeyboardInterrupt as e:
            pass


if __name__ == '__main__':
    lock = RLock()
    DnsServer = ['114.114.114.114', '114.114.115.115', '223.5.5.5',
                 '223.6.6.6', '112.124.47.27', '114.215.126.16']
    headers = {
        'Host': '',
        'User-Agent': 'Mozilla / 5.0(X11;Ubuntu;Linuxx86_64;rv:55.0) Gecko / 20100101Firefox / 55.0',
        'Accept': 'text / html, application / xhtml + xml, application / xml;q = 0.9, * / *;q = 0.8',
        'Accept-Language': 'zh - CN, zh;q = 0.8, en - US;q = 0.5, en;q = 0.3',
        'Accept-Encoding': 'gzip, deflate, br',
        'Connection': 'keep - alive',
    }
    b = subsearch()
    pool = multiprocessing.Pool(processes=6)
    result = []
    for i in xrange(6):
        result.append(pool.apply_async(subvisit, args=(DnsServer[i], headers)))
    pool.close()
    pool.join()
可以改進的部分:
1:把成功獲取的數據存入到數據庫,或者寫入文件。需要注意的是獲取到的標題有可能是韓文、日文、阿拉伯文等等,主要文本處理。
2:採用optparse模塊。不要使用ide編寫(特別是pycharm)太佔用內存了(分分鐘內存80以上)
3:有些網址可以訪問,可是沒有標題。在這裏我的處理是把它剔除,但其實beautifulsoup的處理能力很強,我只略懂皮毛,或許可以獲取網頁的小標題代替主標題。
4:dns模塊的處理可以更優化。在這裏我是每一個進程採用一個dns解析器,但其實這樣風險挺大,一旦一個解析器出現問題,整個進程所有協程都講停工。
5:程序有時會卡在urllib2.read()上面,這個問題煩擾我兩天還是解決不了。(網上的解決方案是採用超時的辦法,但其實好像..沒什麼用..),我覺得這個問題的出現有兩種可能,一是在訪問的時候確實沒有超時,只是在read()下載文檔的時候速度過慢,導致程序都在等待,而不會拋出異常;二就是read()的時候由於協程也都在工作,在處理gerrnlet.lock()程序內部出現阻塞或死鎖(雖然是協程,好像源碼也有lock(),但就算如此爲什麼不拋出異常?..)

下面是程序運行的截圖:



還是要打碼的...

網站遍歷的速度會與網速有挺大的關係。(可惜校園網比較渣,每次測試的速度都有較大程度的不同)
半個小時檢索出2000個有標題的網址(按照測試的經驗,大概判斷的域名與可使用的域名比例是15比1,即半小時判斷30000個,速度還是比較慢)
我會嘗試不同的協程數目以及優化代碼嘗試改進。
在這裏很感謝大公司的代碼,對我來說是寶貴的學習資料。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章