Mercurial > hg > zopeSolr
view zopeSolr.py @ 6:c97b5cbcda52
random search in solr
author | dwinter |
---|---|
date | Tue, 28 May 2013 13:46:21 +0200 |
parents | f695be8f4f34 |
children | 3cb69c6820bd |
line wrap: on
line source
# -*- coding: utf-8 -*- #Verbindet Zope mit solr. Vorraussetzung ist das Paket sunburnt, @see http://opensource.timetric.com/sunburnt/ from OFS.SimpleItem import SimpleItem from Products.PageTemplates.PageTemplateFile import PageTemplateFile import os.path import sunburnt from Globals import package_home import httplib2 import urlparse import urllib import re import xml.etree.ElementTree as ET import json import random #Worte die nicht in der Termliste angezeigt werden sollen #TODO: make this configurable STOPLIST={'main_content':['forward','drucken','history','science','part','publications','projects', 'project','new','geschichte','institute','related','boltzmannstraße','14195'], 'title':['bd','10','11','12','18','19','20','abt','ad','di','history','geschichte','science'] } PURLSERVER="http://md.mpiwg-berlin.mpg.de/purls/" #TODO: only needed for getTermsAsJSON, solle irgendwie aus dem sunburnt kommen SOLRSERVER="/select?q=%s:%s&wt=xml&tv=on&qt=tvrh&fl=%s&tv.tf=true" def zptFile(self, path, orphaned=False): """returns a page template file from the product""" if orphaned: # unusual case pt=PageTemplateFile(os.path.join(package_home(globals()), path)) else: pt=PageTemplateFile(os.path.join(package_home(globals()), path)).__of__(self) return pt class ZopeSolr(SimpleItem): meta_type="ZopeSolr" manage_options= ({'label':'Main Config','action': 'changeMain'},) + SimpleItem.manage_options def __init__(self,id,title,solrURL): self.id=id self.title=title self.solrURL=solrURL #URL einer solr instance bzw. einer collection, falls nicht die default collection benutzt werden soll #Verbinde mit der solt Instance def connect(self): self._v_solr=sunburnt.SolrInterface(url=self.solrURL) def getRandom(self,number=3,field="title_s"): """hole zufaellige eintraege""" if not getattr(self,'_v_solr_',None): self.connect() #http://localhost:8983/solr/mpiwgSources/select/?q=*:*&sort=random_12xs34%20desc&rows=1&facet=false random.seed() rand = random.randrange(300000) results = self._v_solr.query("*.*").sort_by("-random_%s"%rand).paginate(start=0, rows=number).execute() return results #erzeuge den link auf die fulltext display version des bildviewers def createFullTextLink(self,page,facetSerch,search): fqs=[] for key in facetSerch.keys(): values = self.getList(facetSerch.get(key)) for value in values: if value!="*": fqs.append("%s=%s"%(key,urllib.quote(value))); ret="pf=%s"%page ret+="&query=(" ret+=" OR ".join(fqs) #if len(fqs)>0 and len(qs)>0 and qs[0]!="": if len(fqs)>0 and len(search)>0: #TODO das muss noch geandert werden, wenn das der normale suchstring nach feldern müssen alle "AND" in "OR" getauscht werden #da ja in der volltext-line die Zeile gedunfen werden soll wenn eines der Worte drin ist, vorher wurd eventuell und über die ganee seite gesucht #die worte müssen aber nicht in einer zeile sein und werden jetzt nicht gefunden ret+=" OR " ret+="("+search+")" ret+=")&viewLayer=search" return ret #erzeuge einen Link, insbesonder für faceted suche #@param search: ist suchparameter, wird einfach als search=%s weitergereicht #@param facetSearch: bekommt einen hash (feldname, liste der suchworte) def generateLink(self,search,facetSearch={},ranges={},dateranges={},args={}): ret="?search=%s"%search for facet in facetSearch.keys(): searchTerms = facetSearch[facet] if isinstance(searchTerms, basestring): searchTerms=[searchTerms] for searchTerm in searchTerms: try: ret+="&%s_fc=%s"%(facet,searchTerm.encode('utf-8')) except: ret+="&%s_fc=%s"%(facet,searchTerm) for key,values in ranges.items(): if isinstance(values, basestring): values=[values] for value in values: ret+="&%s_rg=%s"%(key,value) for key,values in dateranges.items(): if isinstance(values, basestring): values=[values] for value in values: ret+="&%s_drg=%s"%(key,value) for key,value in args.items(): if isinstance(value, basestring): value=[value] for val in value: ret+="&%s=%s"%(key,val) return ret #hilfsmethode erzeuget immer eine liste von einträgen def getList(self,param): if isinstance(param, basestring): param=[param] return param #erzeugt analog zu den Ranges in Velocity für ein numerisches Feld die Suche in ranges #@param field:ist der name des Feldes in dem in Ranges gesucht werden soll #@param begin anfang der ranges #@param end ende der Raanges #@param increment größe eines ranges #@param date wenn wahr, dann ist field ein Datumsfeld, begin, end,increment sind trotzdem zur Zeit, dan Integer für Jahre !!, d.h. zur Zeit sind nur JAhre als Schritte möglich # für alle andere paramter @see prepareSearch #@return gibt ein hash: mit range -> anzahl der Treffer in dem Ranage. e.g 1921-1950 -> 21 def ranges(self,field,begin,end,increment,solrQuery="",facetFields=[],start=0,rows=10,facetSearch=None,sortFacets=True,date=False,storeFields=False): q=self.prepareSearch(solrQuery,facetFields,start,rows,facetSearch,sortFacets) res={} #speiceher abzahl fls={} #speichere felder #for fl in storeFields: #initialisiere den hash # fls[fl]={} if not getattr(self,'_v_solr_',None): self.connect() for x in range(begin,end,increment): query={} #query["%s__gt"%field]=x #TODO __gt scheint nicht zu funktionieren wird zu gte (???) if not date: query["%s__gte"%field]=int(x)+1 query["%s__lte"%field]=x+increment key="%s-%s"%(x,x+increment) else: year="%s-01-01T00:00:00Z" query["%s__gte"%field]=year%(int(x)) query["%s__lte"%field]=year%(x+increment) key="%s@%s"%(year%(int(x)),year%(x+increment)) result = q.query(**query).highlight("text_german").execute() res[key]=result.result.numFound if storeFields: fls[key] = result.highlighting; if storeFields: return res,fls else: return res #prepareSearch erzeugt die Suchabfrage #solrQuery sucht im in schema.xml bzw. solrconfig.xml festgelegt generischen Feld, hierbei werden mit blanks getrennte eintrage in "AND" zerlegt. #TODO: erlaube auch suche nach phrasen mit "" #facetFields:Liste der Felder, nach denen facitiert werden sollen, wirdn in facet_by in solrburn uebersetzt. #start: Erste Eintrag für paginierung, anzahl der Treffer #rows: anzahl der Treffer #facetSearch: Hash mit Feldnamen: suchwort oder Feldname: liste von Suchworten, hast wird direkt an query von solrburnt weitergegeben, #sortfacets: if true, dann werden die Ergebnisse der facetierten suche alphabetisch sortiert, ACHTUNG: das ist nicht gleich der Funktion in solr die Liste #direkt sortiert zurückzubekommen, hier werden die haufigsten Werte genommen (einstellt in solrconfig.xml) und dann nur diese sortiert! #ausserdem werden beim sortieren, die stopworte gefiltert! # #neben den direkten parameter koennen auch parameter fuer die facetierte Suche über FORM im REQUEST übergeben werden, diese Felder müssen dann #mit "_fc" enden. # gibt als ergebnis den folgenden Hash, so wie in http://opensource.timetric.com/sunburnt/queryingsolr.html#executing-queries-and-interpreting-the-response, # http://opensource.timetric.com/sunburnt/queryingsolr.html#highlighting # und http://opensource.timetric.com/sunburnt/queryingsolr.html#faceting #dokumentiert. Highlighting selbst wird in sorlconfig.xml konfiguriert. #return ein Queryobjet, zur eigentlichen Suche muss darauf noch execute ausgeführt werden. def prepareSearch(self,solrQuery,facetFields=[],start=0,rows=10,facetSearch=None,sortFacets=True,orSearch=None,facetFields_limit=None): "search solr" ranges={} ## deal with a form if self.REQUEST: constr = self.REQUEST.form for field in constr.keys(): #facetes if field.endswith("_fc"): if facetSearch is None: facetSearch={} vals = constr[field] if not isinstance(vals,basestring): vals=[x.decode('utf-8') for x in vals] else: vals=vals.decode('utf-8') facetSearch[field.replace("_fc",'')]=vals #ranges form a-b if field.endswith("_or"): if orSearch is None: orSearch={} vals = constr[field] if not isinstance(vals,basestring): vals=[x.decode('utf-8') for x in vals] else: vals=vals.decode('utf-8') orSearch[field.replace("_or",'')]=vals #ranges form a-b if field.endswith("_rg"): splitted = "_".split(field) if len(splitted)==2: #ranges[field.replace("_rg","__gt")]=splitted[0] #TODO __gt scheint nicht zu funktionieren wird zu gte (???) ranges[field.replace("_rg","__gte")]=int(splitted[0])+1 ranges[field.replace("_rg","__lte")]=splitted[1] elif field.endswith("_drg"): splitted = "@".split(field) if len(splitted)==2: #ranges[field.replace("_rg","__gt")]=splitted[0] #TODO __gt scheint nicht zu funktionieren wird zu gte (???) ranges[field.replace("_drg","__gte")]=splitted[0] ranges[field.replace("_drg","__lte")]=splitted[1] #teste verbindung zu solr if not getattr(self,'_v_solr_',None): self.connect() solrQuery = solrQuery.decode('utf-8') #teile die suche nach " " daraus wird dann eine AND suche #TODO: sollte flexibler sein. insbesondere phrasen splitted= solrQuery.split(" ") res = self._v_solr res=res.query(splitted) if len(ranges.keys())>0: res=res.query(ranges) #ubergebe alle weiteren feld an die suche. if facetSearch: for key,vals in facetSearch.items(): if key.endswith("_rg"): #range if not isinstance(vals,basestring): vals=[x.decode('utf-8') for x in vals] else: vals=[vals.decode('utf-8')] for val in vals: splitted = val.split("-") if len(splitted)==2: #TODO __gt scheint nicht zu funktionieren wird zu gte (???) facetSearch[key.replace("_rg","__gte")]=int(splitted[0])+1 facetSearch[key.replace("_rg","__lte")]=int(splitted[1]) del facetSearch[key] # loesche das urspuerngliche feld elif key.endswith("_drg"): #daterange if not isinstance(vals,basestring): vals=[x.decode('utf-8') for x in vals] else: vals=[vals.decode('utf-8')] for val in vals: splitted = val.split("@") if len(splitted)==2: #TODO __gt scheint nicht zu funktionieren wird zu gte (???) facetSearch[key.replace("_drg","__gte")]=splitted[0] facetSearch[key.replace("_drg","__lte")]=splitted[1] del facetSearch[key] # loesche das urspuerngliche feld else: if not isinstance(vals, basestring): val = [x for x in vals if x!="*"] #siehe oben else: val = [vals] facetSearch[key]=val res=res.query(**facetSearch) #felder mit ODER-Suche if orSearch: for key,vals in orSearch.items(): qr = None if not "*" in vals: #dann ohne einschränkung (key:*) sucht nur nach eintragen in denen etwas im feld steht, wir wollen aber alle if isinstance(vals, basestring): vals = [vals] for val in vals: if not qr: qr=self._v_solr.Q(**{key:val}) else: qr=qr|self._v_solr.Q(**{key:val}) res=res.query(qr) #wenn facetField existieren dann rufe facetierung auf if len(facetFields)>0: #for facet in facetFields: # res = res.facet_by(facet) if facetFields_limit: res = res.facet_by(facetFields,limit=facetFields_limit) else: res = res.facet_by(facetFields) #res=res.paginate(start=start, rows=rows).highlight("main_content") res=res.paginate(start=start, rows=rows) #only highlighting if not searhc for only "*" - avoid max clause error if solrQuery=="*": res=res.paginate(start=start, rows=rows) else: res=res.paginate(start=start, rows=rows).highlight(usePhraseHighlighter=True) return res def replaceParameter(self,paramsNeu,queryString,ignore=[]): params=urlparse.parse_qs(queryString); for key in paramsNeu.keys(): params[key]=paramsNeu[key] for key in ignore: del params[key] retArray = [] for x in params.keys(): for y in self.getList(params[x]): retArray.append("%s=%s"%(x,urllib.quote(y))) print retArray return "&".join(retArray); #für die parameter @see prepareSearch #erzeugt eine Suchabfrage und führt diese aus. #return {"result":response.result, "hl":response.highlighting,"facetFields":facetedFields} def search(self,solrQuery,facetFields=[],start=0,rows=10,facetSearch=None,sortFacets=True,orSearch=None,facetFields_limit=None): res=self.prepareSearch(solrQuery,facetFields,start,rows,facetSearch,sortFacets,orSearch=orSearch,facetFields_limit=facetFields_limit) response= res.execute() #speichere faceted fields if len(facetFields)>0: facetedFields=response.facet_counts.facet_fields if sortFacets: facetedFields=self.sortFacetedFields(facetedFields) else: facetedFields={} return {"result":response.result, "hl":response.highlighting,"facetFields":facetedFields} #hilfsmethode zum sortieren über der ranges def sortRanges(self,ranges): x=list(ranges) x.sort() return x #sortiert die Werte der FacetedFields #(facetedFields ist ein hast mit feldname -> liste der (wert für das feld, anzahl der treffer für den wert) #ausserdem werden die werte gemäß des angegebenen Filter gefiltert. def sortFacetedFields(self, facetedFields,filter=STOPLIST): ret={} def cmpTuple(x,y): return cmp(x[0],y[0]) for key in facetedFields.keys(): ls = facetedFields[key] ls.sort(cmpTuple) ret[key]=ls if filter.get(key,None): ls2=[] for x in ls: if x[0].encode('utf-8') not in filter[key]: ls2.append(x) ret[key]=ls2 ret[key]=[x for x in ret[key] if x[1]!=0] return ret def changeMain(self,solrURL=None,title=None,REQUEST=None,RESPONSE=None): """change main settings""" if solrURL: self.solrURL=solrURL self.title=title self._v_solr=sunburnt.SolrInterface(url=solrURL) if RESPONSE is not None: RESPONSE.redirect('manage_main') else: pt=zptFile(self, 'zpt/ChangeZopeSolr.zpt') return pt() #sucht die je nach einsteillung in solrconfig.xml Werte mit den häufigsten Treffern oder alphabetisch sortiert zu einem #bestimmten eintrag aus solr #gedacht ist die methoden für die Anwendung nach dem Harvesten einer Website, es wird daher davon ausgegangen, dass sie hinter der #idfield eine url steht. Wir es z.b. bei Nutch passiert. index.htm/index_html als Teil der url wird dabei unterdrückt (analog zu den harbest einstellunge für # nutch für zope webseiten. #@param @idfield is hierbei der Name des Feldes, das in solrschema als id defniert wurde #field der Feldname von dem die Treffer gesurcht wernde sollen #url die url des textes def getTermsAsJSON(self,idfield,field,url): """getTerms""" ret=[] h = httplib2.Http() url = url.replace("/index.html","").replace("/index_html","") if url[-1]=="/": url=url[0:-1] urlq=url.replace(":","\:") urlq=urlq.replace("/","\/") #q ="http://localhost:8983/solr/mpiwgweb/select?q=%s:%s&wt=xml&tv=on&qt=tvrh&fl=%s&tv.tf=true"%(idfield,urlq,field) q =self.solrURL+SOLRSERVER%(idfield,urlq,field) resp, content = h.request(q) root = ET.fromstring(content) #uri = "http://127.0.0.1:18080/www_neu/de/aktuelles/features/feature28" xpstr = ".//lst[@name='termVectors']/lst[@name='%s']/lst[@name='%s']/lst"%(url,field) tvs = root.findall(xpstr) for tv in tvs: wd = tv.attrib['name'] for f in tv.findall("./int[@name='tf']"): fre = f.text if int(fre)>2: ret.append('{"text":"%s","size":%s}'%(wd,fre)) retStr="["+",".join(ret)+"]" return retStr #tauscht im request die in neewparams angegeben parameter aus. def replaceParam(self, newparams): x = self.REQUEST.form.copy() for key,value in newparams.items(): x[key]=value retls=[] for k,v in x.items(): if not isinstance(v,basestring): for y in v: retls.append((k,y)) else: retls.append((k,v)) return "?"+"&".join(["%s=%s"%(k,urllib.quote_plus(v,'/')) for (k, v) in retls]) #ruft @set ranges aus, gibt das ergebnis als json zurück def getRangesAsJSON(self,field,begin,end,increment): """ getRangesAsJSON""" res = self.ranges(field, int(begin), int(end), int(increment)) return json.dumps(res) #return only the values of resultList whicht start with startLetter or if starLetterNonAscii all values which #start with an non ascii character def filter (self,resultList,startLetter=None,startLetterNonAscii=0): ls=[] if startLetter: startLetter=startLetter.lower() matchStr = "[\[\]'\"]*"+startLetter ls = [x for x in resultList if re.match(matchStr,x[0].lower())] if startLetterNonAscii ==1: ls = [x for x in resultList if not re.match("[\[\]'\"a-zA-Z].*",x[0])] return ls def manage_addZopeSolrForm(self): """Form for external Links""" pt=zptFile(self, 'zpt/AddZopeSolr.zpt') return pt() def manage_addZopeSolr(self,id,title,solrURL,RESPONSE=None): """Add an external Link""" newObj=ZopeSolr(id,title,solrURL) self._setObject(id,newObj) if RESPONSE is not None: RESPONSE.redirect('manage_main')