Grafo di un sito internet: di cosa si tratta?
Tutte le tecnologie, anche quelle di uso quotidiano, hanno alla base un modello matematico ed algoritmi di supporto alla loro implementazione e non sono da meno le tecnologie web. Un esempio classico è il modello linguistico utilizzato nella ricerca semantica da Google e l’algoritmo di stemming per il filtraggio delle stopwords.
Un modello molto più ampio che viene utilizzato da molte tecnologie web è quello basato sui Grafi. Avete mai sentito parlare di Knowledge Graph di Google? O di grafo sociale (o rete sociale) riferito ai social network come Facebook e Linkedin?
Modelli basati sulla teoria dei grafi aiutano a stabilire a rigore le varie connessioni che vi sono tra oggetti o soggetti, stabilendone anche un grado di connessione (o un peso, un’importanza) nell’intero contesto in esame.
Google valuta il nostro sito internet facendo anche delle analisi di questo tipo: vede il nostro sito come un grafo e in buona sostanza stabilisce:
Un grafo è una struttura matematica discreta le cui proprietà sono definite dalla Teoria dei Grafi. Non è da confondere con il grafico, che non è altro che una rappresentazione grafica di qualcosa, incluso un grafo.
I grafi vengono utilizzati in diverse branche della scienza e della tecnologia, come in biochimica, nello studio delle interazioni in teoria quantistica dei campi, nello studio delle reti informatiche, nello studio delle topologie nei database geografici e, tra le altre infinità numerabili di cose, anche nel web, in particolare nei social network come Facebook e Linkedin e nel modello di Google per il suo motore di ricerca.
Come detto all’inizio dell’articolo, conoscere il grafo di un sito internet ci aiuta a capire come esso è strutturato, ci indica quali sono le pagine con più valore e di conseguenza ci aiuta a capire quali strategie SEO utilizzare. Ad esempio, possiamo scoprire che la pagina “chi sono” è quella che ha più valore nel nostro sito. Cosa facciamo? Cerchiamo di adottare soluzioni che diano più importanza ad un’altra pagina? o sfruttiamo questa? Ma un’altra informazione che può darci la struttura a grafo di un sito internet è la bontà delle connessioni tra le varie pagine interne.
Consideriamo tre stazioni ferroviarie: Milano, Bologna, Bari.
La rete ferroviaria che le collega è rappresentata da una linea spezzata: ogni tratto (ogni segmento o arco) unisce due punti, chiamati nodi. Questi nodi non sono altro che le stazioni intermedie, tra cui le stazioni di Milano, Bologna e Bari sono dei nodi.
Bari è un nodo che avrà le sue diramazioni verso tratte regionali e nazionali. Idem Bologna e Milano. Ma le tre stazioni sono importanti in egual misura? Ovviamente la risposta è no!
Bologna è un importante snodo fra tratte nazionali: se da Bari voglio arrivare a Milano devo passare per la stazione di Bologna, che è quindi più importante della stazione di Bari.
Bologna è quindi un nodo più importante rispetto al nodo Bari.
Milano ha un’importanza ancora maggiore perché è uno snodo verso tratte estere.
Per valutare l’importanza di una stazione ci chiediamo quindi:
Analogamente, la stessa cosa vale per il nostro sito.
Ora è più chiara l’importanza della valutazione del grafo di un sito internet.
Un articolo bellissimo di Enrico Altavilla, spiega come calcolare la distribuzione del PageRank, mostrando come alla base di questa valutazione vi sia la la struttura a grafo di un sito internet. Nell’articolo inoltre viene spiegata l’utilità di questo tipo di analisi, soprattutto a livello SEO, descrivendo una procedura non tanto complessa.
Le procedure di analisi sono complesse e per spiegarne il senso spesso si cerca di semplificare, cosa che farò nel seguito dell’articolo. Tuttavia è una buona base per approfondire il tema in futuro. Lo scopo in questo articolo è mostrare una semplificazione della procedura di Enrico Altavilla tramite un mio codice scritto in linguaggio Python, molto semplificato e che va ottimizzato in alcuni aspetti tecnici e numerici.
L’analisi andrebbe fatta nel dettaglio, valutando link con tag nofollow, valutazione dei link esterni, ed eventuali filtraggi e calcoli: mi riprometto di riaggiornare l’articolo non appena ne avrò l’occasione.
COSA CI SERVE
(il codice completo lo trovi a fine articolo)
Il codice viene eseguito nel modulo principale: main.
1 2 3 |
def main(home, sitemap, siteType): graph, siteLink = getGraph(sitemap, siteType) |
dove la prima riga esegue il parsing della sitemap, fornendo come risultato un dizionario ed una lista, ossia:
Questa operazione viene effettuata dal modulo getGraph()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
def getGraph(sitemap, site='wordpress'): """ get a dict from sitemap, to construct nodes and edges """ xml = getChild(sitemap) documents = {} graph0 = {} graph = {} siteLink = [] if site=='wordpress': for x in xml.keys(): for k in xml[x]: graph[k] = getLink(k) siteLink.append(k) print k else: for k in xml: graph[k] = getLink(k) siteLink.append(k) print k return graph, siteLink |
la riga
1 |
xml = getChild(sitemap) |
esegue un parsing della sitemap fornita. Successivamente per ogni url della sitemap viene effettuato un parsing html (url k) tramite
1 |
graph[k] = getLink(k) |
sfruttando la liberia beautiful soup (bs4). Le funzioni getChild() e getLink() sono esposte a fine articolo
La funzione fa una distinzione tra le sitemap tipiche fornite dal plugin Yoast WordPresse le sitemap fornite da siti implementati in Joomla.
In particolare una sitemap di Yoast è sviluppata come un albero di file xml:
post-sitemap.xml
page-sitemap.xml
portfolio-sitemap.xml
category-sitemap.xml
Le sitemap fornite dai componenti per Joomla invece sono del tipo:
Ritorniamo al modulo main. Dopo aver estratto url e link, passiamo a crearci il grafo del sito. Lo facciamo tramite la libreria networkx, importata con l’abbreviazione nx.
1 2 3 4 5 |
.... # creo il grafo G = nx.Graph() G.add_nodes_from(generateNodes(graph)) G.add_edges_from(generateEdges(graph)) |
generateNodes() e generateEdges() sono i moduli tramite i quali vengono estratti i nodi e gli archi secondo il formato richiesto da networkx.
Successivamente ci calcoliamo i valori di Eigenvectors Centrality
1 2 3 |
... print 'EIGENVECTORS CENTRALITY' ec = nx.eigenvector_centrality(G, max_iter=100) |
quindi scriviamo su file csv sia i valori di Eigenvectors Centrality, sia i gradi di ogni nodo
1 2 3 4 5 6 7 8 9 |
fec = open('ec.csv','w') for k in ec.keys(): fec.write(str(k)+';'+str(ec[k])+'\n') fec.close fdeg = open('nodeDegree.csv', 'w') for v in G: fdeg.write(str(v)+';'+str(float(G.degree(v)))+'\n' ) fdeg.close() |
Infine impostiamo i parametri di plottaggio per ottenere una rappresentazione grafica del grafo del sito in esame, salvandola in alta risoluzione su file pdf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
nomeFig = 'grafo.pdf' tipoFig = 'pdf' graph_pos=nx.spring_layout(G, iterations=400, scale=1)#, dim=10000) print 'GRAPG POS' for n in graph_pos: graph_pos[n] = graph_pos[n]*100 nx.draw_networkx_nodes(G,graph_pos,node_size=[float(G.degree(v))*0.3 for v in G], alpha=0.7, node_color=[float(ec[k]) for k in G.nodes(data=False)],with_labels=False) nx.draw_networkx_edges(G,graph_pos,width=0.1,alpha=0.5,edge_color='r', arrows=True) nx.draw_networkx_labels(G, graph_pos,font_size=0,font_family='sans-serif') plt.axis('off') plt.savefig(nomeFig, format=tipoFig, dpi='9000', figsize=(32.000, 32.000)) # save as png plt.show() |
NOTA: il codice è molto semplice e l’ho scritto velocemente a titolo di esempio. Tuttavia sono ben accetti suggerimenti!
Lancio del codice (esempio)
1 |
python main.py -s http://www.nicolastella.it/sitemap.xml -p http://www.nicolastella.it/ -t wordpress |
File main.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
import urllib2 import bs4 import numpy as np import requests from lxml import etree import networkx as nx import matplotlib.pyplot as plt from optparse import OptionContainer, OptionParser, OptionGroup, Option def getChild(linkXml): xmlDict = {} sitemapPages = {} r = requests.get(linkXml) root = etree.fromstring(r.content) for sitemap in root: children = sitemap.getchildren() xmlDict[children[0].text] = [] if children[0].text[-3:] == 'xml': rq = requests.get(children[0].text) subXml = etree.fromstring(rq.content) for el in subXml: pgs = el.getchildren() xmlDict[children[0].text].append(pgs[0].text) return xmlDict def getLink(link): connections = [] nodes = [] nodesName = [] beautiful = urllib2.urlopen(link).read() soup = bs4.BeautifulSoup(beautiful) links = soup.findAll('a') for l in links: ln = l.get('href') #cn = l.string #connections.append((cn, ln)) nodes.append(ln) return nodes def generateNodes(graph): """ return a list of nodes """ nodes = graph.keys() return nodes def generateEdges(graph): """ return a list of tuples thath represents edges """ edges = [] for vertex in graph: for neighbour in graph[vertex]: if {neighbour, vertex} not in edges: edges.append((vertex, neighbour)) return edges def getGraph(sitemap, site='wordpress'): """ get a dict from sitemap, to construct nodes and edges """ xml = getChild(sitemap) documents = {} graph0 = {} graph = {} siteLink = [] if site=='wordpress': for x in xml.keys(): for k in xml[x]: graph[k] = getLink(k) siteLink.append(k) print k else: for k in xml: graph[k] = getLink(k) siteLink.append(k) print k return graph, siteLink def main(home, sitemap, siteType): graph, siteLink = getGraph(sitemap, siteType) print 'GRAPH\n\n' #preparo i dati ## TUTTO G = nx.Graph() G.add_nodes_from(generateNodes(graph)) G.add_edges_from(generateEdges(graph)) #eccentricity = nx.eccentricity(G) #pr = nx.pagerank(G) #googleMatrix = nx.google_matrix(G) #print 'ECCENTRICITY' #print eccentricity #print 'PAGE RANK' #print pr #print 'GOOGLE MATRIX' #print googleMatrix print 'EIGENVECTORS CENTRALITY' ec = nx.eigenvector_centrality(G, max_iter=100) fec = open('ec.csv','w') for k in ec.keys(): fec.write(str(k)+';'+str(ec[k])+'\n') fec.close fdeg = open('nodeDegree.csv', 'w') for v in G: fdeg.write(str(v)+';'+str(float(G.degree(v)))+'\n' ) fdeg.close() nomeFig = 'grafo.pdf' tipoFig = 'pdf' graph_pos=nx.spring_layout(G, iterations=400, scale=1)#, dim=10000) print 'GRAPG POS' for n in graph_pos: graph_pos[n] = graph_pos[n]*100 nx.draw_networkx_nodes(G,graph_pos,node_size=[float(G.degree(v))*0.3 for v in G], alpha=0.7, node_color=[float(ec[k]) for k in G.nodes(data=False)],with_labels=False) nx.draw_networkx_edges(G,graph_pos,width=0.1,alpha=0.5,edge_color='r', arrows=True) nx.draw_networkx_labels(G, graph_pos,font_size=0,font_family='sans-serif') plt.axis('off') plt.savefig(nomeFig, format=tipoFig, dpi='9000', figsize=(32.000, 32.000)) # save as png plt.show() if __name__ == "__main__": home = None sitemap = None siteType = None parser = OptionParser(usage="%prog [options]") parser.add_option("-s", "--sitemap_url", action="store", dest="sitemap_url", default=None, help="Complete url of sitemap, with http://") parser.add_option("-p", "--homepage", action="store", dest="homepage", default=None, help="Complete url of homepage with http://") parser.add_option("-t", "--cms_type", action="store", dest="cms_type", default="wordpress", help="CMS Type: joomla or worpress. Default: wordpress") (options, args) = parser.parse_args() if options.sitemap_url == None: print 'You must specify a sitemap url' exit() elif str(options.sitemap_url[-4:]) != '.xml': print "You must specify an xml sitemap" exit() else: sitemap = str(options.sitemap_url) if str(options.homepage) == None: print 'You must specify a url' exit() else: homepage = str(options.homepage) if options.cms_type == None: print 'You must specify a cms type: wordpress or joomla' exit() elif str(options.cms_type) not in ['wordpress','joomla']: print "You must specify one choice between: wordpress, joomla" exit() else: siteType = str(options.cms_type) main(homepage, sitemap, siteType) |