Doorzoek je interne websites met Nutch

nutch_logo_tmIn een grotere organisatie heb je wel meerdere teams die een eigen website/wiki hebben naast het klassieke intranet. Vaak is dit niet geïntegreerd, terwijl dit een interessante bron van informatie kan zijn. Zoekmachines zijn de methode bij uitstek om disparate bronnen samen te brengen en doorzoekbaar te maken zonder alle sites samen te brengen op één platform.

In de open source wereld zijn Apache Lucene en Solr de vaakst genoemde oplossingen om een zoekmachine te bouwen. Het probleem is dat deze geen ‘web crawler’ bevatten, namelijk een systeem dat webpagina’s opvraagt, de inhoud indexeert en de aanwezige links op hun beurt verwerkt, net zoals Google doet. Apache Nutch is een zusterproject van Lucene dat zich op dit terrein waagt, namelijk een “Google” voor intern gebruik. In deze post gaan we eens kijken hoe je Nutch opzet en gebruikt. Merk op dat dit een technische uitleg zal zijn.

Voorbereiding

Nutch bestaat al sinds 2005 als Apache project en bestaat momenteel in twee versies: 1.6 (de stabiele versie waar er nog releases op gebeuren) en 2.1 (een vernieuwde versie met ondersteuning voor NoSQL backends). Voor de tests heb ik gekozen voor de laatste versie (2.1). Het eerste wat opvalt is het gebrek aan goede documentatie voor de 2.x reeks. Er zijn wat startpunten (Nutch op HBase en op MySQL) maar vaak ben je toch aangewezen op forums en blogposts van anderen.
Al snel bleek dat het opzetten niet zo evident was aangezien de onderliggende database tijdens het crawlen crashte. Na pogingen met HBase, MySQL en Cassandra, is het uiteindelijk toch gelukt met HBase, nadat de onderliggende machine genoeg RAM geheugen had gekregen (6GB in dit geval). Let op dat je niet de laatste versie gebruikt van HBase maar een versie die compatibel is met Nutch, zoals de versie 0.90.6.
Nutch zal volgende stappen doorlopen bij een eerste crawl:
  • initiële URL’s worden aan Nutch gegeven, bijv. de lijst met websites die je wenst te indexeren (inject)
  • en dan doorloopt Nutch een aantal maal het proces (dit wordt de depth genoemd)
    • generatie van lijst van URLs die moet behandeld worden (generate)
    • pagina’s ophalen (fetch)
    • inhoud van de pagina’s verwerken (parse). Hierbij kunnen zowel gewone html pagina’s geïndexeerd worden als documenten (PDF, DOC, …)
    • de Nutch DB update (updatedb)
    • opslag van de resultaten in Solr (solrindex)

Hier kan je een voorbeeldscript vinden dat alle stappen samenbrengt. Je kan ook gebruiken maken van het ‘crawl’-commando dat de stappen combineert, maar dan heb je minder zicht op de verschillende stappen van het proces.

Nutch kan zowel websites crawlen (http(s)://) als filesystemen (file:// en ftp://). Bij websites waar een HTTP authenticatie vereist is kunnen de nodige credentials in een configuratiebestand opgenomen worden. Let natuurlijk wel op dat dit credentials zijn die geen beschermde of gevoelige inhoud kunnen beschikbaar maken omdat de zoekresultaten publiek beschikbaar zullen zijn.

Configuratie

De configuratie van Nutch gebeurt in nutch-site.xml waar je de standaardparameters uit nutch-default.xml kan aanpassen. De interessante parameters zijn daar:
  • http.agent.name : welke browser agent string wordt er door doelwebsites gezien zodat je op die sites een crawler kan toelaten en ook een verschil kan maken tussen crawler-traffiek en gewone traffiek.
  • parser.character.encoding.default : welke encoding er moet gebruikt worden als deze ontbreekt (bijv. utf-8).
  • storage.data.store.class : welke backend er wordt gebruikt, bijv. org.apache.gora.hbase.store.HBaseStore voor Hbase.
  • plugin.includes : Nutch heeft een plugin architectuur die het mogelijk maakt om bepaalde functionaliteit toe te voegen. Er zijn een aantal plugins die standaard geconfigureerd zijn. In het geval van de tests werden volgende plugins gebruikt
protocol-httpclient|urlfilter-regex|parse-(html|tika)|index-(basic|anchor|more)|urlnormalizer-(pass|regex|basic)|scoring-opic
  • db.ignore.external.links : geeft aan of de crawler buiten de initieel aangegeven websites links mag volgen.
  • file.content.limit & http.content.limit: hoe groot mogen de geïndexeerde bestanden of webpagina’s zijn. Dit moet overeenkomen met wat je als groottebeperking opgeeft in de onderliggende databank.

Backend

De HBase backend kan ook getuned worden maar dat is eigenlijk een topic op zich. Je kan hier en hier wat  informatie vinden. Het belangrijkste is dat er genoeg geheugen beschikbaar is.

Plugins

Zoals gezegd zijn er verschillende plugins die het proces kunnen sturen. Je kan die plugins ook apart aanroepen om het effect van een bepaalde configuratie te testen, wat uitermate handig is.
De standaardplugins die mij goed van pas kwamen:
  • URLfilter-regex

Je kan URL’s filteren en bepalen welke URL’s in aanmerking komen en welke niet. In het configuratiebestand “regex-urlfilter.txt” kan je dit met behulp van reguliere expressies configureren. URL’s die voldoen aan de expressie kan je dan uitsluiten door er ‘-‘ voor te zetten of expliciet toevoegen door ‘+’. Zo zal de regel

-index.php/Help:.*

Wikimedia links met de hulppagina’s niet indexeren of kan je bijvoorbeeld instellen dat het url-pad niet dieper mag zijn dan 7 niveau’s:

"-.*/(?:[^/]+/){7,}

Je kan een regel toevoegen aan het bestand en dan testen met:

 <installatiedir>/bin/nutch plugin urlfilter-regex org.apache.nutch.urlfilter.regex.RegexURLFilter

Je geeft dan een url in en krijgt dan het resultaat of deze al dan niet gefilterd wordt.

  • URLNormalizer-regex

De opgehaalde URL’s kan je normalizeren naar een bepaalde vorm. Dit is vooral handig als je bijvoorbeeld een site indexeert waarvan documenten op verschillende locaties terugkomen maar met een verschillend pad. Dit was in de tests het geval met een website gebaseerd op IBM Lotus Domino. De url naar een document hangt af van het pad langswaar je er naar toe gaat.

Eenzelfde document heeft bijvoorbeeld als urls (met zijn identifier als laatste parameter):

http://website/databank.nsf/<id1>/<id>?OpenDocument
http://website/databank.nsf/<id2>/<id>?OpenDocument

Als je dan weet dat er een URL bestaat om het document rechtstreeks op te vragen door

http://website/databank.nsf/View/<id>

dan kan je de bovenstaande urls normalizeren met behulp van volgende expressies

<regex>
 <pattern>(\.nsf)/[^/]*/(.*OpenDocument$)</pattern>
 <substitution>$1/View/$2</substitution>
</regex>
  • index-more

Deze plugin indexeert onder andere ook het content-type van de gecrawlde inhoud. Dit kan je achteraf gebruiken om in de zoekmachine te kunnen vernauwen op bepaalde content types zoals een PDF.

Integratie met Solr

Nutch heeft zijn eigen index maar je kan de gecrawlde pagina’s in Solr stoppen. Dit heeft als voordeel dat je een centrale Solr server kan gebruiken voor allerlei zoekfuncties en dat je de kracht, flexibiliteit en mogelijkheden van Solr kan gebruiken. De configuratie van Solr valt echter buiten het bestek van deze post. Je kan facetten gebruiken op specifieke velden zoals de beschikbare websites, de content types en zelfs de taal die gedetecteerd werd.
Als je geen gebruik wenst te maken van Solr kan je ook ElasticSearch gebruiken.
Het feit dat er nog een extra index wordt gebruikt heeft natuurlijk wel als resultaat dat je twee backends hebt met de gegevens. Het voordeel is wel dat de Solr instantie los staat van Nutch en dus ook gevoed kan worden met andere data.

Aangezien Nutch 2.1 zelf geen userinterface heeft om de index te doorzoeken ben je zowiezo aangewezen op een UI die bijvoorbeeld bovenop Solr staat. In de tests werd geëxperimenteerd met Ajax-solr, een set Javascript libraries om een UI te bouwen.

3,2,1 … start

Na alles te hebben geïnstalleerd en configureerd kan je een crawl starten. Het blijkt al snel dat er redelijk wat tuning nodig is om de crawl snel genoeg te laten verlopen.

Snelheid

De standaardconfiguratie van Nutch beperkt sterk het aantal pagina’s per tijdseenheid die worden opgehaald. In een eerste run (7 iteraties van het hierboven beschreven proces) duurde het verschillende uren om 6000 pagina’s op te halen. Je kan dit sneller laten verlopen door te spelen met de parameter fetcher.server.delay (aantal seconden tussen requests op eenzelfde server, zodat je een server niet overspoelt met requests, ) en een aantal parameters van de Fetcher threads ( fetcher.threads.fetch,  fetcher.threads.per.queue, fetcher.threads.per.host). Met de waarden 3, 30, 15 en 3 voor deze vier parameters verliep dezelfde crawl een heel stuk sneller. Er werden uiteindelijk 80 000 urls verwerkt op 2.5 uur. Je moet wel in de gaten houden of de server nog meekan, de onderliggende backend en de Solr-instantie.

Onderhoud

De resultaten van de crawl kan je gaan bekijken door zoekopdrachten uit te voeren en te gaan kijken of er ongewenste resultaten bij zijn of andere vreemde URLs. Die kan je dan in de Nutch configuratie uitsluiten of omvormen.

Daarnaast moet je ook gaan kijken in de Solr index en configuratie wat er daar allemaal is van mogelijkheden en verbeteringen. Na aanpassingen te hebben gedaan in Solr kan je  de volledige Nutch-index opnieuw in Solr stoppen door volgend commando:

> bin/nutch solrindex <Url van solr> -reindex

Vernieuwen van pagina’s

Een onduidelijk punt dat bleef is het verversen van de pagina’s. Het zogenaamd “recrawlen” is niet goed gedocumenteerd. In de Nutch index wordt er bijgehouden wanneer een pagina opnieuw mag opgehaald worden. Deze waarde wordt ingesteld aan de hand van de parameters rond de “Fetchschedule” zoals db.fetch.interval.default. Standaard zal Nutch maar na 30 dagen een pagina opnieuw ophalen. Je kan voor een pagina gaan kijken wanneer ze de volgende keer zal opgehaald worden via volgend commando:

> bin/nutch readdb -url http://website.com/pagina
key: http://website.com/pagina
baseUrl: http://website.com/pagina
status: 2 (status_fetched)
fetchInterval:  83000
fetchTime:      1360246212783
prevFetchTime:  1360162351060
....
title:  Title
De parameter fetchTime geeft de datum & tijd vanaf wanneer de pagina opnieuw mag opgehaald worden. In dit geval is dit 1360246212(783) wat je via het commando date kan omzetten:
> date -u -d @1360246212
Thu Feb  7 14:10:12 UTC 2013

Het fetchInterval zal worden toegevoegd aan de moment dat de pagina opnieuw wordt opgevraagd, in dit geval 83000 seconden of 23 uur. Zo zal je dus elke dag de pagina’s kunnen vernieuwen.

Voor een recrawl kan je je bijvoorbeeld beperken tot 2 iteraties van het crawlproces. Zo worden bestaande pagina’s opnieuw overlopen en eventueel nieuwe links gedetecteerd. Deze worden dan in de volgende iteratie ook mee geïndexeerd. Het heeft ook als gevolg dat je index nog verder groeit als er bij vorige iteraties nog extra links werden gedetecteerd. Zo werd in de test een index van 80 000 pagina’s er snel eentje van 220 000 pagina’s.

Pagina’s die verdwijnen worden niet uit de Solr-index verwijderd. In Nutch 1.6 was er hiervoor een aparte job, voor Nutch 2.x is dit momenteel voorzien in de trunk en zal dit met de nieuwe versie beschikbaar zijn.

Gezien de indexatietijd redelijk beperkt is kan je er ook voor opteren om telkens een nieuwe crawl uit te voeren en de vorige index steeds te wissen.

Conclusie

Met de end-to-end implementatie van deze test is duidelijk dat er heel wat komt kijken bij de implementatie van een zoekoplossing, niet alleen voor de pure installatie/configuratie maar ook voor het iteratieve onderhoud zoals het opsporen/weren van ongewenste URLs.  Het ter beschikking stellen van de nodige Solr functionaliteit (zoals omgaan met meertaligheid, facetten, autocompletion, spellingscontrole, …) neemt ook een heel stuk van de implementatie in beslag. Dan spreken we nog niet over het nut van statistiek achteraf, zodat de zoekmachine meer kan aangepast worden naar de noden van de eindgebruiker. De implementatie van een zoekmachine is in dit aspect geen project maar een continu proces.

Nutch 2.1 is nog wat ruw, vooral op documentatievlak en ontbreekt ook nog een aantal belangrijke features zoals het schoonmaken van de Solr-index bij het verdwijnen van pagina’s. Maar het is alleszins een bruikbaar systeem. De nodige competenties gaan echter verder dan Nutch alleen want als je een NoSQL backend zoals HBase gebruikt heb je daar uiteraard ook kennis van nodig en met Solr heb je enorme mogelijkheden tot je beschikking.

Een gebruiker van een dergelijke zoekmachine kan alleen maar beter worden van een systeem dat alle interne websites ter beschikking stelt. Daar staat natuurlijk tegenover dat dit niet zo evident is om dit op een gebruiksvriendelijke manier aan te brengen. De aangeboden functionaliteit van een crawler is ook beperkt tot de gegevens die beschikbaar zijn in de webpagina. Standaard zal je dus de inhoud van de pagina in de index stoppen met wat metadata (zoals datum, content-type, url). Wil je meer specifieke informatie zoals de auteur van de tekst dan kan je dit niet met de crawler (tenzij je een specifiek veld hebt en de nodige logica om de auteur uit een metadata veld te halen op de pagina). Daarom dat een content management systeem dat zelf zijn inhoud in de zoekmachine stopt meer rijke informatie kan ter beschikking stellen.

Leave a Reply

Your email address will not be published. Required fields are marked *