Motori di ricerca per parole chiave: un’analisi approfondita
L’applicazione che vogliamo analizzare è un semplice ma completo
motore di ricerca per parole chiave realizzato in VBScript: permette di recuperare da un database Access i record che contengono una o più parole appartenenti ad una data lista. Ancora una volta vedremo come, con questo potente linguaggio di scripting, possiamo realizzare un’applicazione completa e versatile.
Le parole chiave, come detto anche multiple, vengono cercate all’interno di uno o più campi specificati dall’utente. I records selezionati vengono presentati a video in ordine di numero di occorrenze delle parole chiave, a partire da quello che ne contiene il maggior numero, e le parole chiave vengono evidenziate in grassetto. Completano lo script una funzione di paginazione (avanti-indietro e vai-a-pagina...), con numero totale di pagine e numero di records selezionati, e alcuni controlli (javascript) di convalida dell’input.
Lo script è composto, in sostanza, da un form per inserire la lista
delle parole-chiave e per specificare i campi del database in cui effettuare la
ricerca (form_keywords.asp nel nostro esempio), dalla pagina che provvede all’elaborazione dell’input e alla presentazione dei risultati della ricerca (il file select_keywords.asp, il cui codice analizzeremo a fondo) oltrechè dal database e dal file connection.asp (connessione al database, da includere nella pagina principale come server-side-include).
Non sempre avremo bisogno di utilizzare tutte le funzioni implementate nel
nostro script (il più delle volte, per esempio, basterà effettuare
una ricerca nell’ambito di tutti i campi del database, senza specificarne
alcuno), ma conoscerlo a fondo ci permetterà di adattarlo agevolmente alle nostre esigenze specifiche.
Due parole sul database (database.mdb): nella tabella del nostro esempio (denominata “lorem"), il campo “id" è numerico, i campi (da campo1 a campo5) contenenti i dati sono testuali, e campo6, adibito al conteggio delle parole-chiave, è numerico. Poichè la funzione di conteggio delle parole-chiave richiede un aggiornamento dinamico dei dati, la cartella contenente il database deve avere settato il permesso di scrittura.
Per realizzare alcune delle funzionalità accessorie viene impegnata
una certa quantità di risorse a livello di connettività client-server (i dati vengono recuperati, aggiornati e richiamati di nuovo) e l’applicazione non è ancora stata testata su database di grandi dimensioni.
Ecco il file select_keywords.asp (non abbiamo dichiarato “option explicit", quindi possiamo tralasciare il dimensionamento delle variabili):
<html>
<head>
<title>Ricerca per parole chiave</title>
<style type="text/css">
<!--
#piedepagina{ ‘(posiziona la stringa “pagina n di N")
position: relative;
left: 20px;
bottom: 25px;
}
-->
</style>
</head>
<body>
<!--#include file="connection.asp" --> ‘connessione al database
<br/>
<%
call open_connection()
Attraverso la serie di istruzioni:
checked1 = request("campo1")
checked2 = request("campo2")
checked3 = request("campo3")
checked4 = request("campo4")
checked5 = request("campo5") ‘nota: il codice si può abbreviare notevolmente sostituendo questa e diverse altre serie di istruzioni più avanti con un ciclo “for i=1 to 5…", ma nell’esempio abbiamo scelto di non farlo per ragioni di chiarezza
parole=trim(request("parole")) ‘elimina gli eventuali spazi vuoti a destra e a sinistra della stringa
parole=lcase(parole) ‘trasforma maiuscole in minuscole
viene acquisito l’input inserito dall’utente.
I campi da “campo1" a “campo5" del form sono check-boxes che l’utente seleziona se la ricerca delle parole-chiave deve essere effettuata anche nell’omonimo campo del database. Generalmente la ricerca potrà essere effettuata su tutti i campi, oppure solo su determinati campi specificati a livello di codice: in questo caso la check-list non verrà inserita nel form.
Il campo “parole" del form è il campo di testo in cui viene inserita la lista delle parole-chiave, che devono essere separate da uno spazio vuoto. Nell’acquisire la lista, vengono eliminati gli eventuali spazi vuoti e destra e a sinistra, e vengono trasformate in minuscole le eventuali lettere maiuscole. La distinzione maiuscole-minuscole non costituisce un problema per la ricerca nel database (il confronto tra stringhe in SQL non è case-sensitive) ma lo sarebbe per il conteggio delle parole chiave e per l’evidenziazione delle stesse in grassetto, che avviene attraverso la ricerca di sottostringhe in variabili ASP.
Notiamo che il passaggio delle variabili avviene con l’istruzione generica “request". Ciò permette sia di acquisire l’input inserito nel form all’inizio della ricerca (l’istruzione specifica è request.form) sia di trasferire i valori da una pagina all’altra dell’output utilizzando la funzione di paginazione, che li passa nell’url della pagina (in questo caso dovremmo eseguire request.querystring).
Costruiamo la query sql per estrarre dal database i records contenenti le parole-chiave:
if len(parole) > 0 then ‘un ulteriore controllo, oltre i javascript lato client
Set rs = Server.CreateObject("ADODB.Recordset") ‘creazione recordset
sql = "SELECT * FROM lorem WHERE (("
ArrSearch = Split(parole) ‘divide la stringa nelle singole parole-chiave
x = 0
for each word In ArrSearch
word = trim(word)
If Len(checked1) <> 0 Then
sql = sql & " campo1 like '%" & word & "%'"
End If
If Len(checked2) <> 0 Then
If Len(checked1) <> 0 Then sql = sql & " or"
sql = sql & " campo2 like '%" & word & "%'"
End If
If Len(checked3) <> 0 Then
If Len(checked1) <> 0 Or Len(checked2) <> 0 Then sql = sql & "
or"
sql = sql & " campo3 like '%" & word & "%'"
End If
If Len(checked4) <> 0 Then
If Len(checked1) <> 0 Or Len(checked2) <> 0 Or Len(checked3) <>
0 Then sql = sql & " or"
sql = sql & " campo4 like '%" & word & "%'"
End If
If Len(checked5) <> 0 Then
If Len(checked1) <> 0 Or Len(checked2) <> 0 Or Len(checked3) <>
0 Or Len(checked4) <> 0 Then sql = sql & " or"
sql = sql & " campo5 like '%" & word & "%'"
End If
sql = sql &")"
If Not x = UBound(ArrSearch) Then sql = sql & " or ("
x = x + 1
Next
sql = sql &")"
È importante capire a fondo l’istruzione SQL per estrarre i records contenenti le parole-chiave. La query viene creata in modo dinamico in base ai campi su cui effettuare la ricerca, specificati dall’utente: per ognuno dei campi selezionati viene accodata alla query l’istruzione:
“where campo like ‘%"&parola&"%’"
La ricerca delle parole nel database può avvenire attraverso l’istruzione “like" (più adatta ad un motore di ricerca: con questa sintassi le parole-chiave vengono riconosciute anche se compaiono nel campo specificato come sottostringhe, cioè come parte di una frase o anche solo come parte di altre parole) oppure attraverso il segno di uguaglianza (“="), e in questo caso il contenuto del campo deve coincidere esattamente con la parola ricercata.
Le varie istruzioni “where" relative ai diversi campi sono collegate tra loro da “OR": infatti un record viene selezionato se una data parola è presente in un certo campo OPPURE in un altro e così via. Nel caso non sia necessario che i campi su cui effettuare la ricerca vengano specificati dall’utente, basterà sostituire nella query il nome delle variabili (nel nostro caso “campo1", “campo2", ecc.) con il nome effettivo dei campi del database. Saranno eliminati i gruppi di istruzioni “if….then"
e la procedura verrà semplificata.
Per ognuna delle parole, ottenute “splittando" la lista “parole", la procedura viene ripetuta. È importante notare che le “sottoquery" per eseguire la ricerca su ciascuna delle parole-chiave possono venire legate tra loro dalla clausola “OR" oppure da “AND" (mi riferisco ora dell’istruzione:
If Not x = UBound(ArrSearch) Then sql = sql & " or
("
che compare al termine del ciclo).
Nei due casi la ricerca ritornerà un risultato diverso: usando la clausola “OR" il numero dei records estratti aumenterà all’aumentare delle parole inserite, poiché basterà che un record contenga una sola di esse per venire selezionato, mentre, con “AND", inserire più parole-chiave restringerà il risultato ai soli records che le contengono tutte contemporaneamente.
La scelta dell’una o dell’altra sintassi dipenderà ovviamente dalle esigenze del particolare motore di ricerca che stiamo implementando.
rs.Open sql, objConn, 3, 3 ‘esecuzione della query
if rs.eof then ' se nessun record soddisfa la richiesta…
%>
<br/><br/>
<center><table border="0" width="80%">
<tr>
<td> </td>
<td align="center"><b>Nessuna inserzione soddisfa i criteri
di ricerca</b></td>
<td><a href="form_keywords.asp">Nuova ricerca</a></td>
</tr>
</table>
</center>
<br/>
<%
else
A questo punto, se esistono uno o più records che soddisfano le condizioni poste dalla query, ne inizia l’elaborazione. Conteggio delle parole-chiave: per ogni record estratto, il contenuto dei soli campi selezionati viene copiato e concatenato a formare la variable “prova":
do while not (rs.EOF)
id=rs.fields.item("id").value
if len(checked1) > 0 then
conto1 = lcase(rs.fields.item("campo1").value)
Nb: qui usiamo “lcase" (lettere minuscole) perchè l'istruzione "instr" usata per il conteggio è case-sensitive!
end if
if len(checked2) > 0 then
conto2 = lcase(rs.fields.item("campo2").value)
end if
if len(checked3) > 0 then
conto3 = lcase(rs.fields.item("campo3").value)
end if
if len(checked4) > 0 then
conto4 = lcase(rs.fields.item("campo4").value)
end if
if len(checked5) > 0 then
conto5 = lcase(rs.fields.item("campo5").value)
end if
x = 0
conteggio=0
for each word In ArrSearch
word = trim(word)
prova=conto1&conto2&conto3&conto4&conto5
tet=0
for y=1 to len(prova)
tet=instr(prova, word) ' instr è case-sensitive!
if tet > 0 then
conteggio=conteggio+1 ‘incrementa il contatore delle parole-chiave
prova=mid(prova,tet+1) ‘taglia ed elimina dalla stringa la parte già
analizzata
end if
next
x=x+1
next
Il contenuto della variabile “prova" viene confrontato con le
parole-chiave e il contatore “conteggio" viene incrementato di una
unità ad ogni occorrenza di ognuna di esse. Notiamo che la stringa “prova" viene costruita all’interno del ciclo di conteggio, perché viene distrutta ad ogni esecuzione della procedura e quindi deve venire ricreata all’inizio di ogni ciclo successivo!
Per ogni record aggiorniamo il campo adibito al conteggio delle parole-chiave
(campo6 nel nostro esempio), poi chiudiamo e distruggiamo il recordset:
SQLcont= "UPDATE lorem SET campo6="&conteggio&"
where id ="&id&""
objConn.Execute SQLcont
rs.movenext
loop
'chiudo rs
rs.close
set rs=nothing Ora la presentazione dei dati. Definiamo quanti records vogliamo
visualizzare per ogni pagina:
numero=4 'records per pagina
controllo=numero ‘contatore dei records per pagina
Set rs2 = Server.CreateObject("ADODB.Recordset") ‘creazione nuovo recordset
La variabile “pag" verrà accodata all’url della pagina.
Al primo caricamento, “pag" non è valorizzata e quindi viene
aperta, per default, la pagina 1:
pag = TRIM(Request.QueryString("pag")) 'paginazione
If pag="" Then
pag = 1
Else
pag = CInt(pag)
End If Nuova estrazione dei dati ……
sql2 = "SELECT * FROM lorem WHERE (("
x = 0
for each word In ArrSearch
word = trim(word)
If Len(checked1) <> 0 Then
sql2 = sql2 & " campo1 like '%" & word & "%'"
End If
If Len(checked2) <> 0 Then
If Len(checked1) <> 0 Then sql2 = sql2 & " or"
sql2 = sql2 & " campo2 like '%" & word & "%'"
End If
If Len(checked3) <> 0 Then
If Len(checked1) <> 0 Or Len(checked2) <> 0 Then sql2 = sql2 &
" or"
sql2 = sql2 & " campo3 like '%" & word & "%'"
End If
If Len(checked4) <> 0 Then
If Len(checked1) <> 0 Or Len(checked2) <> 0 Or Len(checked3) <>
0 Then sql2 = sql2 & " or"
sql2 = sql2 & " campo4 like '%" & word & "%'"
End If
If Len(checked5) <> 0 Then
If Len(checked1) <> 0 Or Len(checked2) <> 0 Or Len(checked3) <>
0 Or Len(checked4) <> 0 Then sql2 = sql2 & " or"
sql2 = sql2 & " campo5 like '%" & word & "%'"
End If
sql2 = sql2 &")"
If Not x = UBound(ArrSearch) Then sql2 = sql2 & " or ("
x = x + 1
Next
sql2 = sql2 &")"
…. come la prima, ma questa volta ordinati secondo il numero di ricorrenze
delle parole-chiave:
sql2=sql2 & " ORDER BY campo6 DESC"rs2.open sql2, objConn, 3, 3
tot = rs2.RecordCount 'conteggio dei records per la paginazione
%>
<table border="0" width="100%">
<tr>
<td> </td>
<td align="center"><b>La ricerca ha estratto <%=tot%>
records rispondenti ai criteri inseriti</b></td>
<td><a href="form_keywords.asp">Nuova ricerca</a></td>
</tr>
</table>
<br/>
<table width='100%' border='1' cellpadding="2" cellspacing="0">
<%
rs2.move numero*(pag-1) ‘paginazione: posizioniamo il cursore al primo record
della pagina selezionata
do while not (rs2.EOF or controllo=0)
id=rs2.fields.item("id").value
campo1=rs2.fields.item("campo1").value
campo2=rs2.fields.item("campo2").value
campo3=rs2.fields.item("campo3").value
campo4=rs2.fields.item("campo4").value
campo5=rs2.fields.item("campo5").value
Procedura per evidenziare in grassetto le parole-chiave: in ognuno dei campi selezionati, le parole-chiave, se esistono, vengono sostituite (istruzione “replace") con la stessa parola scritta in grassetto. Anche replace, come instr, è case-sensitive: usiamo di nuovo “lcase" per assicurarci che ogni parola venga riconosciuta ed evidenziata:
x=0
for each word In ArrSearch
word = trim(word)
if len(checked1) > 0 then
campo1=lcase(campo1) 'anche replace è case-sensitive:
senza lcase potrebbe non riconoscere la sottostringa "word"
(tutta minuscola)!
campo1=Replace(campo1, word, "<b>"&word&"</b>")
end if
if len(checked2) > 0 then
campo2=lcase(campo2)
campo2=Replace(campo2, word, "<b>"&word&"</b>")
end if
if len(checked3) > 0 then
campo3=lcase(campo3)
campo3=Replace(campo3, word, "<b>"&word&"</b>")
end if
if len(checked4) > 0 then
campo4=lcase(campo4)
campo4=Replace(campo4, word, "<b>"&word&"</b>")
end if
if len(checked5) > 0 then
campo5=lcase(campo5)
campo5=Replace(campo5, word, "<b>"&word&"</b>")
end if
x=x+1
next
Stampa a video del numero di records “controllo", a partire dal
primo record della pagina “pag":
%>
<tr>
<td><%=campo1%></td>
<td><%=campo2%></td>
<td><%=campo3%></td>
<td><%=campo4%></td>
<td><%=campo5%></td>
<% controllo = controllo - 1
rs2.Movenext
loop
%>
</tr></table>
<br/>
Funzioni pagina avanti/pagina indietro, vai a pagina.... e pag n di N : in
sostanza, viene costruita una piccola tabella contenente tutti i numeri di pagina
del recordset; per ogni pagina viene costruito un collegamento ipertestuale, trasferendo,
nel corpo dell’url, le informazioni relative alle parole-chiave e ai campi
selezionati:
<center><table border="0" width="250">
<tr>
<%
If ( pag > 1) Then
%>
<td width="50" align="left"><a href="select_keywords.asp?pag=<%=pag-1%>&parole=<%=parole%>&campo1=<%=checked1%>&
campo2=<%=checked2%>&campo3=<%=checked3%>
&campo4=<%=checked4%>&campo5=<%=checked5%>">
<img src="barrow.gif" border="0"/></a></td>
<% else %>
<td width="50"> </td>
<%
end if
%>
<td width="220" align="center">
<%
if tot mod(numero)=0 then
lastpage=int(tot/numero)
else
lastpage=int(tot/numero)+1 'deve numerare l'ultima pagina anche se incompleta!
end if
for c=1 to lastpage
%>
<a href="select_keywords.asp?pag=<%=c%>&parole=<%=parole%>
&campo1=<%=checked1%>&campo2=<%=checked2%>
&campo3=<%=checked3%>&campo4=<%=checked4%>
&campo5=<%=checked5%>"><%=c%></a>
<%
next
%></td><%
If not rs2.eof Then
%>
</td><td width="50" align="right"><a href="select_keywords.asp?pag=<%=pag+1%>&parole=<%=parole%>
&campo1=<%=checked1%>&campo2=<%=checked2%>
&campo3=<%=checked3%>&campo4=<%=checked4%>
&campo5=<%=checked5%>"><img src="arrow.gif" border="0"/></a></td>
<% else %> <td width="50"> </td>
<% end if %>
</tr>
</table>
</center>
<br/>
<div id="piedepagina">Pag. <%=pag%> di <%=lastpage%></div>
<%
Chiusura e distruzione recordset e connessione:
rs2.close
set rs2=nothing
end if
end if
'chiudo tutto
call close_connection()
%>
</body>
</html>
Ecco tutto. Generalmente il motore di ricerca per estrarre gli annunci economici, o i prodotti dal nostro catalogo online, o le news sul nostro sito, potrà essere più semplice e non richiederà l’utilizzo di tutte le funzioni viste nell’esempio. Meglio così: capirne a fondo il funzionamento ci aiuterà ad adattarlo alle nostre esigenze per arricchire la nostra homepage con una utility completa ed efficiente.