Miti e leggende SEO #3 - I motori di ricerca non leggono JavaScript e CSS

E' abitudine diffusa affermare che i crawler dei motori di ricerca non siano in grado di leggere file JavaScript o CSS. In realtà quest'affermazione è falsa e facilmente documentabile da una semplice analisi dei log.

Ma allora, come mai frasi come Google non legge i JavaScript o Yahoo! non capisce i CSS sono così diffuse? Come in ogni leggenda che si rispetti, anche in questa c'è un fondo di verità. In questo articolo cercherò di dimostrare entrambi i punti di vista.

I motori di ricerca leggono JavaScript e CSS

Cominciamo dalla parte più facile, ovvero dimostrare che i motori di ricerca leggono i file JavaScript e CSS. Per fugare definitivamente ogni dubbio possiamo analizzare i log di accesso di un comune sito web.

I motori di ricerca ed i file CSS

Proviamo, ad esempio, a controllare tutti gli accessi ai un file CSS di questo blog.

$ grep '.css' access.log
85-18-66-25.ip.fastwebnet.it - - [20/Sep/2008:13:03:31 -0700] "GET /blog/styles.css HTTP/1.1" 200 720 "https://simonecarletti.com/blog/2006/02/perche_ho_scelto_feeddemon.php" "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)"
85-18-66-25.ip.fastwebnet.it - - [20/Sep/2008:13:03:31 -0700] "GET /static/mt4/themes-base/blog.css HTTP/1.1" 200 9767 "https://simonecarletti.com/blog/2006/02/perche_ho_scelto_feeddemon.php" "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)"
85-18-66-25.ip.fastwebnet.it - - [20/Sep/2008:13:03:31 -0700] "GET /static/mt4/support/themes/professional-blue/professional-blue.css HTTP/1.1" 200 13665 "https://simonecarletti.com/blog/2006/02/perche_ho_scelto_feeddemon.php" "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)"
85-18-66-25.ip.fastwebnet.it - - [20/Sep/2008:13:03:32 -0700] "GET /static/stylesheets/blog.css HTTP/1.1" 200 5559 "https://simonecarletti.com/blog/2006/02/perche_ho_scelto_feeddemon.php" "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)"
85-18-66-25.ip.fastwebnet.it - - [20/Sep/2008:13:03:32 -0700] "GET /static/stylesheets/alignments.css HTTP/1.1" 200 1815 "https://simonecarletti.com/blog/2006/02/perche_ho_scelto_feeddemon.php" "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)"
85-18-66-25.ip.fastwebnet.it - - [20/Sep/2008:13:03:32 -0700] "GET /static/stylesheets/messages.css HTTP/1.1" 200 3198 "https://simonecarletti.com/blog/2006/02/perche_ho_scelto_feeddemon.php" "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)"
85-18-66-25.ip.fastwebnet.it - - [20/Sep/2008:13:03:32 -0700] "GET /static/mt4/SyntaxHighlighter/Styles/SyntaxHighlighter.css HTTP/1.1" 200 4352 "https://simonecarletti.com/blog/2006/02/perche_ho_scelto_feeddemon.php" "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)"
crawl-66-249-70-72.googlebot.com - - [20/Sep/2008:13:06:07 -0700] "GET /static/tools/style.css HTTP/1.1" 200 10046 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
host64-86-dynamic.61-82-r.retail.telecomitalia.it - - [20/Sep/2008:13:10:22 -0700] "GET /blog/styles.css HTTP/1.1" 200 721 "https://simonecarletti.com/blog/2007/03/individuare-cloni-fake.php" "Opera/9.52 (Windows NT 5.1; U; it)"
host64-86-dynamic.61-82-r.retail.telecomitalia.it - - [20/Sep/2008:13:10:22 -0700] "GET /static/mt4/themes-base/blog.css HTTP/1.1" 200 9767 "https://simonecarletti.com/blog/2007/03/individuare-cloni-fake.php" "Opera/9.52 (Windows NT 5.1; U; it)"
host64-86-dynamic.61-82-r.retail.telecomitalia.it - - [20/Sep/2008:13:10:22 -0700] "GET /static/stylesheets/blog.css HTTP/1.1" 200 5560 "https://simonecarletti.com/blog/2007/03/individuare-cloni-fake.php" "Opera/9.52 (Windows NT 5.1; U; it)"

Non notate nulla di strano? Alla riga numero 8 è presente un accesso da un client con useragent Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html): il crawler di Google!

La user agent è facilmente modificabile e chiunque può spacciarsi, senza troppa fatica, per il crawler di Google. Per rendere il test ancora più affidabile è quindi necessario eseguire un reverse DNS dell'indirizzo IP. Per semplicità ho delegato ad Apache questo lavoro, così che il log contenga direttamente il nome dell'host al posto del corrispondente IP.

Come potete vedere, l'host associato a quella richiesta corrisponde a crawl-66-249-70-72.googlebot.com, un host di proprietà di Google. A questo punto, vale la pena eseguire una ricerca mirata nei log solo per questo host, limitando ovviamente i risultati ai soli file CSS.

$ zgrep '.css ' access.* | egrep '^.*?.googlebot.com'
crawl-66-249-70-72.googlebot.com - - [20/Sep/2008:01:44:17 -0700] "GET /static/mt4/support/themes/professional-blue/professional-blue.css HTTP/1.1" 200 13666 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
crawl-66-249-70-72.googlebot.com - - [20/Sep/2008:13:06:07 -0700] "GET /static/tools/style.css HTTP/1.1" 200 10046 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
crawl-66-249-70-72.googlebot.com - - [22/Sep/2008:12:00:41 -0700] "GET /static/stylesheets/messages.css HTTP/1.1" 200 3199 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
crawl-66-249-70-72.googlebot.com - - [22/Sep/2008:16:28:17 -0700] "GET /static/stylesheets/messages.css HTTP/1.1" 304 256 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
crawl-66-249-70-72.googlebot.com - - [24/Sep/2008:21:32:38 -0700] "GET /mt4/mt-search.cgi?IncludeBlogs=1&search=css HTTP/1.1" 200 18391 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
crawl-66-249-70-72.googlebot.com - - [25/Sep/2008:09:11:06 -0700] "GET /static/stylesheets/blog.css HTTP/1.1" 200 5560 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
crawl-66-249-70-72.googlebot.com - - [25/Sep/2008:12:09:48 -0700] "GET /static/mt4/themes-base/blog.css HTTP/1.1" 200 9768 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
crawl-66-249-70-72.googlebot.com - - [03/Oct/2008:17:09:31 -0700] "GET /static/toolsdesign/style.css HTTP/1.1" 200 10046 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
crawl-66-249-70-72.googlebot.com - - [04/Oct/2008:11:06:55 -0700] "GET /static/tools/style.css HTTP/1.1" 200 10046 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"

In modo del tutto analogo, andiamo alla ricerca di tracce del bot di MSN e Yahoo!.

Il primo si identifica normalmente con un host che termina per search.msn.com.

$ zgrep '.css ' access.* | egrep '^.*?.search.msn.com'
msnbot-65-55-110-231.search.msn.com - - [06/Oct/2008:23:09:26 -0700] "GET /static/mt4/themes-base/blog.css HTTP/1.0" 200 9767 "https://simonecarletti.com/mt4/mt-search.cgi?tag=luca conti&blog_id=1" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322)"
msnbot-65-55-110-231.search.msn.com - - [06/Oct/2008:23:09:27 -0700] "GET /static/stylesheets/blog.css HTTP/1.0" 200 5535 "https://simonecarletti.com/mt4/mt-search.cgi?tag=luca conti&blog_id=1" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322)"
msnbot-65-55-110-231.search.msn.com - - [06/Oct/2008:23:09:27 -0700] "GET /static/stylesheets/alignments.css HTTP/1.0" 200 1815 "https://simonecarletti.com/mt4/mt-search.cgi?tag=luca conti&blog_id=1" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322)"
msnbot-65-55-110-231.search.msn.com - - [06/Oct/2008:23:09:27 -0700] "GET /static/stylesheets/messages.css HTTP/1.0" 200 3198 "https://simonecarletti.com/mt4/mt-search.cgi?tag=luca conti&blog_id=1" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322)"
msnbot-65-55-110-231.search.msn.com - - [06/Oct/2008:23:09:27 -0700] "GET /static/mt4/support/themes/professional-blue/professional-blue.css HTTP/1.0" 200 13666 "https://simonecarletti.com/mt4/mt-search.cgi?tag=luca conti&blog_id=1" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322)"
msnbot-65-55-110-231.search.msn.com - - [06/Oct/2008:23:09:27 -0700] "GET /static/mt4/SyntaxHighlighter/Styles/SyntaxHighlighter.css HTTP/1.0" 200 4352 "https://simonecarletti.com/mt4/mt-search.cgi?tag=luca conti&blog_id=1" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322)"

Il secondo, invece, utilizza un host tipo *.crawl.yahoo.net.

$ zgrep '.css ' access.* | egrep '^.*?.crawl.yahoo.net'
llf320045.crawl.yahoo.net - - [07/Oct/2008:00:02:22 -0700] "GET /blog/styles.css HTTP/1.0" 304 218 "https://simonecarletti.com/blog/2006/05/piu_trackback_per_tutti.php" "Mozilla/5.0 (compatible; Yahoo! Slurp/3.0; http://help.yahoo.com/help/us/ysearch/slurp)"
llf320045.crawl.yahoo.net - - [07/Oct/2008:00:02:25 -0700] "GET /static/mt4/themes-base/blog.css HTTP/1.0" 304 220 "https://simonecarletti.com/blog/styles.css" "Mozilla/5.0 (compatible; Yahoo! Slurp/3.0; http://help.yahoo.com/help/us/ysearch/slurp)"
llf320045.crawl.yahoo.net - - [07/Oct/2008:00:02:25 -0700] "GET /static/stylesheets/blog.css HTTP/1.0" 304 219 "https://simonecarletti.com/blog/styles.css" "Mozilla/5.0 (compatible; Yahoo! Slurp/3.0; http://help.yahoo.com/help/us/ysearch/slurp)"
llf320045.crawl.yahoo.net - - [07/Oct/2008:00:02:27 -0700] "GET /static/stylesheets/alignments.css HTTP/1.0" 304 218 "https://simonecarletti.com/static/stylesheets/blog.css" "Mozilla/5.0 (compatible; Yahoo! Slurp/3.0; http://help.yahoo.com/help/us/ysearch/slurp)"
llf320045.crawl.yahoo.net - - [07/Oct/2008:00:02:27 -0700] "GET /static/stylesheets/messages.css HTTP/1.0" 304 219 "https://simonecarletti.com/static/stylesheets/blog.css" "Mozilla/5.0 (compatible; Yahoo! Slurp/3.0; http://help.yahoo.com/help/us/ysearch/slurp)"
llf320045.crawl.yahoo.net - - [07/Oct/2008:00:02:35 -0700] "GET /static/mt4/support/themes/professional-blue/professional-blue.css HTTP/1.0" 304 220 "https://simonecarletti.com/blog/styles.css" "Mozilla/5.0 (compatible; Yahoo! Slurp/3.0; http://help.yahoo.com/help/us/ysearch/slurp)"
llf320045.crawl.yahoo.net - - [07/Oct/2008:00:02:35 -0700] "GET /static/mt4/SyntaxHighlighter/Styles/SyntaxHighlighter.css HTTP/1.0" 304 219 "https://simonecarletti.com/blog/2006/05/piu_trackback_per_tutti.php" "Mozilla/5.0 (compatible; Yahoo! Slurp/3.0; http://help.yahoo.com/help/us/ysearch/slurp)"

Fermi tutti, abbiamo dimenticato Ask.com! Quest'ultimo si identifica normalmente con un host tipo *.ask.com.

$ zgrep '.css ' access.* | egrep '^.*?.ask.com'

$ zgrep '.css ' access.* | egrep '^.*?.ask.com' | wc -l
0
$ egrep '^.*?.ask.com' access.* | wc -l
29

Niente da fare! Una ricerca per Ask.com non fornisce alcun risultato per i file CSS, mentre fornisce 29 risultati per richieste a normali pagine del blog. Possiamo quindi affermare che, tra Google, Yahoo, MSN ed Ask, Ask è l'unico motore di ricerca a non leggere alcun file .css.

I motori di ricerca ed i file JavaScript

Fino ad ora abbiamo parlato soltanto dei file CSS. Possiamo affermare che i motori di ricerca leggono anche i file JavaScript? Proviamo!

$ zgrep '.js ' access.* | egrep '^.*?.googlebot.com' | wc -l
26
$ zgrep '.js ' access.* | egrep '^.*?.search.msn.com' | wc -l
2485
$ zgrep '.js ' access.* | egrep '^.*?.crawl.yahoo.net' | wc -l
1
$ zgrep '.js ' access.* | egrep '^.*?.ask.com' | wc -l
79

Per i file JavaScript lo scenario è nettamente differente. Ask.com legge i file JavaScript mentre, così come Google e MSN che, nello specifico, non sembra preoccuparsi che file identici linkati da documenti differenti... sono identici!

La sorpresa, in questo caso, è Yahoo! che sembra aver letto 1 solo file in 30 giorni. Come se non bastasse, questo file non è in alcun modo collegato alle pagine del blog ma si tratta di un file che avevo creato per eseguire la registrazione di un nuovo handler di feed con Firefox 2.0.

llf520133.crawl.yahoo.net - - [23/Sep/2008:01:54:53 -0700] "GET /uploads/2006/11/firefox_new_feed_handler/firefox2-registerFeedHandler.js HTTP/1.0" 200 1623 "-" "Mozilla/5.0 (compatible; Yahoo! Slurp; http://help.yahoo.com/help/us/ysearch/slurp)"

Frequenza di accesso

Per i più curiosi, ecco la frequenza di accesso ai file JavaScript e CSS calcolata negli ultimi 30 giorni, suddivisa per motori.

Motore di Ricerca CSS JavaScript
Google 9 26
MSN 2915 2485
Yahoo! 1887 1
Ask 0 79

Scaricare non vuol dire leggere!

A questo punto qualcuno potrebbe obiettare che, analizzando i log, non è dato sapere se il crawler abbia realmente letto il contenuto file o se si è limitato a richiederlo. E' vero, ma è illogico pensare che un crawler decida di sua spontanea volontà di scaricare un file JavaScript collegato esternamente da un tag script... solo per generare accessi al server!

Inoltre ci sono diversi modi per dimostrare come il file, in seguito, in effetti venga letto. Non mi dilungo ulteriormente, alcuni di questi sono contenuti leggendo tra le righe nei paragrafi seguenti.

Leggere vs Interpretare vs Eseguire

Fermi, dove scappate... non è mica ancora finito l'articolo! Prima di tornare al menu di navigazione con link in JavaScript è importante essere a conoscenza della sottile differenza tra leggere, interpretare ed eseguire. Questi tre verbi potrebbero sembrare sinonimi, ma non è così.

I crawler normalmente leggono i file JavaScript e CSS ma, come scrissi un anno fa nell'articolo Cosa gli spider dei motori di ricerca non sanno fare, questo non significa necessariamente che intendano eseguirlo.

In altre parole. I crawler scaricano il contenuto dei file, i sorgenti all'interno vengono normalmente interpretati (soprattutto nel caso di JavaScript) ma quasi mai eseguiti. Questo significa, ad esempio, che un crawler può facilmente scoprire il significato della funzione seguente ma non vuol dire che il dominio paradise.god sarà indicizzato.

function redirect_me_to_paradise {
document.location.href = 'http://paradise.god';
}

Per questo motivo spesso si tende ad affermare, in modo improprio, che i crawler non leggono JavaScript o CSS indicando che il loro contenuto non viene eseguito come ci si potrebbe aspettare e come avviene invece con un normale browser.

Perché i motori di ricerca leggono i file JavaScript e CSS?

L'esempio precedente dovrebbe cominciare a chiarire alcune delle motivazioni per le quali i motori di ricerca hanno cominciato a leggere ed interpretare il contenuto di file JavaScript e CSS. In passato entrambi i linguaggi sono stati largamente utilizzati per nascondere agli occhi degli utenti contenuti realizzati esclusivamente per i motori di ricerca, all'unico scopo di sfruttarne il posizionamento a favore di terze pagine. Per approfondimenti sul tema vi consiglio l'articolo Testo Nascosto, scritto da Vincenzo.

Tecniche come redirect in javascript, doorway page, blocchi di testo nascosto sono oggi nel mirino dei filtri antispam di quasi i motori di ricerca. Per individuarle è necessario analizzare ed interpretare il contenuto di questi file, spesso inseriti esterni alla pagina nella (vana) speranza di eludere il crawler del motore di ricerca.

Per calcare ulteriormente la mano su questo punto vi consiglio alcune letture direttamente dal blog di Matt Cutts:

  1. SEO Mistakes: sneaky JavaScript
  2. SEO Mistakes: crappy doorway pages
  3. SEO Mistakes: Spam in other languages
  4. Ramping up on international webspam
  5. SEO Advice: clean house before press releases
  6. "Undetectable" spam

In conclusione

Webmaster, SEO, spammer ed utenti siete avvisati! Qualunque sia il vostro scopo finale, siate consapevoli del fatto che i motori di ricerca leggono JavaScript e CSS, normalmente li interpretano, raramente li eseguono. Ovviamente non è escluso che il comportamento possa variare in futuro.