diff --git a/spidy b/spidy new file mode 100644 index 0000000..c2aa1bf --- /dev/null +++ b/spidy @@ -0,0 +1,270 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# + +import random +import urllib2 +import mutex +import threading +import re, time +from optparse import OptionParser + +default_page = "http://poisson.phc.unipi.it" + +__author__ = "Leonardo Robol <leo@robol.it>" + +mtx_url_dict = mutex.mutex() + +N = 1000 +url_dict = {} +url_counter = range(N) + +max_steps = 5 + +def get_links(page): + """Restituisce una lista con i link + presenti nella pagina data, in forma canonica""" + content = get_page(page.url) + if(content == -1): + return -1 + + links = re.findall(r"<a href=\"(\S*)\"[^>]*>",content) + ret = [] + for link in links: + # Espando il link in modo da (speriamo!) + # garantire l'unicità + ret.append(expand_url(page.url, link)) + + return ret + + +def expand_url(parent, url): + """Questa funzione prende l'url della pagina parent + e l'url del link e dà all'url del link una forma unica + e canonica, del tipo + + http://www.example.com/pagina + http://www.example.com/pagina.html + """ + + ## Controllo che l'url non cominci con un punto + ## nel qual caso cerchiamo di rimediare subito, + ## ma non cadiamo nel tranello di ignorare i .. + if url[0] == ".": + if len(url) == 1: + url = parent + + else: + if(url[1] != "."): + url = url[1:] + + ## Se all'inizio dell'url c'è uno slash non ci serve tutto + ## il parent, ma solo la prima parte + if url.startswith("/"): + parent = re.search(".+//[^/]*", parent).group(0) + else: + # in caso contrario dobbiamo assicurarci di troncare + # l'ultima parte dell'url dopo il /, a meno che non + # finisca senza estensione (in quel caso aggiungiamo un /) + if re.search("\.[^/]*$", parent): + parent = re.sub("[^/]*$", "", parent) + else: + parent += "/" + + + + ## Controlliamo prima di tutto se nell'url c'è un + ## protocollo + protocol = re.search(r"(\w+):", url) + if protocol == None: + url = parent + url + return url + +def get_page(url): + """Cerca di scaricare l'url dato e restituisce + -1 se non ce la fa, il contenuto altrimenti""" + try: + req = urllib2.urlopen(url) + except: + return -1 + + return req.read() + +class Page(): + """Una pagina web. Questa classe, quando viene istanziata, + controlla se una pagina con quel nome è già presente (una + pagina è unica!) e se lo è restituisce lo stesso oggetto, + altrimenti ne crea uno nuovo con un nuovo ID""" + + def __repr__(self): + return "<Page object: %s>" % self.url + + def __init__(self, url=""): + + if(url != ""): + mtx_url_dict.lock(self.__new_page, url) + mtx_url_dict.unlock() + else: + mtx_url_dict.lock(self.__get_page, 0) + mtx_url_dict.unlock() + + def __get_page(self, num): + + if(len(url_counter) == 0): + self.exhausted = True + return + + page_found = False + + while(not page_found): + + for url in url_dict: + page = Page(url) + if not page.analyzed: + page_found = True + self.url = url + break + + if not page_found: + time.sleep(1) + + + + self.ID = page.ID + self.analyzed = page.analyzed + self.exhausted = False + url_dict[url].analyzed = True + + def __new_page(self, url): + # Questo ci serve per tenere il + # conto di tutti gli url + global url_dict + global url_counter + + self.exhausted = False + self.analyzed = False + self.url = url + + if(url_dict.has_key(url)): + # Preservo i parametri che esistono già! + self.ID = url_dict[url].ID + self.analyzed = url_dict[url].analyzed + + else: + try: + self.ID = url_counter.pop() + except IndexError: + self.exhausted = True + + + url_dict[url] = self + url_dict[url].links = [] + + def add_link(self, page): + + if(page.exhausted): + return -1 + print " => Adding link to %s" % page.url + mtx_url_dict.lock(self.__add_link, page.ID) + mtx_url_dict.unlock() + return 0 + + def __add_link(self, ID): + url_dict[self.url].links.append(ID) + + def links(self): + return url_dict[self.url].links + + + + +class Crawler(threading.Thread): + """Partendo da startpage, segue tutti i link registrando + i vari collegamenti fra le pagine. Una volta raggiunto il + limite di pagine da controllare termina""" + + def __init__(self, startpage=default_page): + threading.Thread.__init__(self) + self.start_page = startpage + + + def run(self): + + step_counter = 0 + + # Capiamo che pagina ci serve + page = Page(self.start_page) + + while(not page.exhausted): + + if(step_counter > max_steps): + page = Page(self.start_page) + step_counter = 0 + else: + page = Page() + step_counter += 1 + + if page.exhausted: + break + + # Come prima cosa devo fare il parsing dei + # link che ci sono nella pagina + # Diamo una mixata per simulare meglio + # il caso.. dato che tanto è probabile che + # alcuni link rimarranno non visitati! + links = get_links(page) + random.shuffle(links) + + ## A questo punto io che mi occupo della pagina devo + ## aggiungere tutti i link alla pagina + + if not links == -1: + for l in links: + lpage = Page(l) + + if not lpage.exhausted: + page.add_link(lpage) + else: + break + + + + +if __name__ == "__main__": + + parser = OptionParser() + parser.add_option("-c", "--concurrency", dest="concurrency", action="store", + help="Set level of concurrency (i.e. how many threads)", default=3) + + (option, args) = parser.parse_args() + + concurrency = option.concurrency + + print " => Starting with concurrency %d" % concurrency + + threads = [] + for i in range(0, concurrency): + threads.append(Crawler()) + threads[i].start() + + + ## Qui non c'è modo umano di terminare il + ## suo lavoro, bisognerà studiarci sopra + for i in range(0, concurrency): + threads[i].join() + + + ## A questo punto mi devo preoccupare di salvare + ## la matrice in un formato soddisfacente + + out = open("ji.txt", 'w') + + for page in url_dict: + for link in url_dict[page].links: + out.write(page + "\t" + str(url_dict[page].ID) + "\t" + str(link) + "\n") + + + + + + diff --git a/spidy.py b/spidy.py deleted file mode 100644 index d24725b..0000000 --- a/spidy.py +++ /dev/null @@ -1,258 +0,0 @@ -# -# -*- coding: utf-8 -*- -# - -import random -import urllib2 -import mutex -import threading -import re, time - -default_page = "http://poisson.phc.unipi.it" - -__author__ = "Leonardo Robol <leo@robol.it>" - -mtx_url_dict = mutex.mutex() - -N = 10000 -url_dict = {} -url_counter = range(N) - -max_steps = 5 - -def get_links(page): - """Restituisce una lista con i link - presenti nella pagina data, in forma canonica""" - content = get_page(page.url) - if(content == -1): - return -1 - - links = re.findall(r"<a href=\"(\S*)\"[^>]*>",content) - ret = [] - for link in links: - # Espando il link in modo da (speriamo!) - # garantire l'unicità - ret.append(expand_url(page.url, link)) - - return ret - - -def expand_url(parent, url): - """Questa funzione prende l'url della pagina parent - e l'url del link e dà all'url del link una forma unica - e canonica, del tipo - - http://www.example.com/pagina - http://www.example.com/pagina.html - """ - - ## Controllo che l'url non cominci con un punto - ## nel qual caso cerchiamo di rimediare subito, - ## ma non cadiamo nel tranello di ignorare i .. - if url[0] == ".": - if len(url) == 1: - url = parent - - else: - if(url[1] != "."): - url = url[1:] - - ## Se all'inizio dell'url c'è uno slash non ci serve tutto - ## il parent, ma solo la prima parte - if url.startswith("/"): - parent = re.search(".+//[^/]*", parent).group(0) - else: - # in caso contrario dobbiamo assicurarci di troncare - # l'ultima parte dell'url dopo il /, a meno che non - # finisca senza estensione (in quel caso aggiungiamo un /) - if re.search("\.[^/]*$", parent): - parent = re.sub("[^/]*$", "", parent) - else: - parent += "/" - - - - ## Controlliamo prima di tutto se nell'url c'è un - ## protocollo - protocol = re.search(r"(\w+):", url) - if protocol == None: - url = parent + url - return url - -def get_page(url): - """Cerca di scaricare l'url dato e restituisce - -1 se non ce la fa, il contenuto altrimenti""" - try: - req = urllib2.urlopen(url) - except: - return -1 - - return req.read() - -class Page(): - """Una pagina web. Questa classe, quando viene istanziata, - controlla se una pagina con quel nome è già presente (una - pagina è unica!) e se lo è restituisce lo stesso oggetto, - altrimenti ne crea uno nuovo con un nuovo ID""" - - def __repr__(self): - return "<Page object: %s>" % self.url - - def __init__(self, url=""): - - if(url != ""): - mtx_url_dict.lock(self.__new_page, url) - mtx_url_dict.unlock() - else: - mtx_url_dict.lock(self.__get_page, 0) - mtx_url_dict.unlock() - - def __get_page(self, num): - - if(len(url_counter) == 0): - self.exhausted = True - return - - page_found = False - - while(not page_found): - - for url in url_dict: - page = Page(url) - if not page.analyzed: - page_found = True - self.url = url - break - - if not page_found: - time.sleep(1) - - - - self.ID = page.ID - self.analyzed = page.analyzed - self.exhausted = False - url_dict[url].analyzed = True - - def __new_page(self, url): - # Questo ci serve per tenere il - # conto di tutti gli url - global url_dict - global url_counter - - self.exhausted = False - self.analyzed = False - self.url = url - - if(url_dict.has_key(url)): - # Preservo i parametri che esistono già! - self.ID = url_dict[url].ID - self.analyzed = url_dict[url].analyzed - - else: - try: - self.ID = url_counter.pop() - except IndexError: - self.exhausted = True - - - url_dict[url] = self - url_dict[url].links = [] - - def add_link(self, page): - - if(page.exhausted): - return -1 - print " => Adding link to %s" % page.url - mtx_url_dict.lock(self.__add_link, page.ID) - mtx_url_dict.unlock() - return 0 - - def __add_link(self, ID): - url_dict[self.url].links.append(ID) - - def links(self): - return url_dict[self.url].links - - - - -class Crawler(threading.Thread): - """Partendo da startpage, segue tutti i link registrando - i vari collegamenti fra le pagine. Una volta raggiunto il - limite di pagine da controllare termina""" - - def __init__(self, startpage=default_page): - threading.Thread.__init__(self) - self.start_page = startpage - - - def run(self): - - step_counter = 0 - - # Capiamo che pagina ci serve - page = Page(self.start_page) - - while(not page.exhausted): - - if(step_counter > max_steps): - page = Page(self.start_page) - step_counter = 0 - else: - page = Page() - step_counter += 1 - - if page.exhausted: - break - - # Come prima cosa devo fare il parsing dei - # link che ci sono nella pagina - # Diamo una mixata per simulare meglio - # il caso.. dato che tanto è probabile che - # alcuni link rimarranno non visitati! - links = get_links(page) - random.shuffle(links) - - ## A questo punto io che mi occupo della pagina devo - ## aggiungere tutti i link alla pagina - - if not links == -1: - for l in links: - lpage = Page(l) - - if not lpage.exhausted: - page.add_link(lpage) - else: - break - - - - -if __name__ == "__main__": - - concurrency = 25 - - threads = [] - for i in range(0, concurrency): - threads.append(Crawler()) - threads[i].start() - - - for i in range(0, concurrency): - threads[i].join() - - ## A questo punto mi devo preoccupare di salvare - ## la matrice in un formato soddisfacente - - out = open("ji.txt", 'w') - - for page in url_dict: - for link in url_dict[page].links: - out.write(page + "\t" + str(url_dict[page].ID) + "\t" + str(link) + "\n") - - - - - -