Indizierung von Websites in Solr mit Python
In diesem Beitrag zeige ich Ihnen eine einfache, aber effektive Methode zur Indizierung von Websites in einem Solr-Index mit Scrapy und Python.
Wir sehen viele fortgeschrittene Solr-basierte Anwendungen mit ausgefeilten, benutzerdefinierten Datenpipelines, die Daten aus verschiedenen Quellen kombinieren, oder die große Anforderungen haben. Außerdem sehen wir oft Leute, die die Suche auf eine minimal-invasive Weise implementieren möchten, indem sie bestehende Websites als Integrationspunkte nutzen, anstatt eine tiefe Integration mit bestimmten CMS oder Datenbanken zu implementieren, die möglicherweise von anderen Gruppen in einer Organisation gepflegt werden. Das Crawlen von Websites hört sich zwar recht einfach an, aber Sie werden bald feststellen, dass es Probleme mit der Mechanik des Crawlings, aber vor allem mit der Struktur von Websites gibt.
Wenn Sie einfach nur den HTML-Code analysieren und den Text indizieren, werden Sie eine Menge Text indizieren, der für die Seite eigentlich nicht relevant ist: Navigationsabschnitte, Kopf- und Fußzeilen, Werbung, Links zu verwandten Seiten. Der Versuch, dies im Nachhinein zu bereinigen, ist oft nicht effektiv. Es ist viel besser, wenn Sie verhindern, dass dieser Müll überhaupt in den Index gelangt. Dazu müssen Sie den Inhalt der Webseite analysieren und Informationen auf intelligente Weise extrahieren. Und dafür gibt es ein großartiges Tool: Scrapy. In diesem Beitrag werde ich ein einfaches Beispiel für seine Verwendung geben. Im Scrapy-Tutorial finden Sie eine Einführung und weitere Informationen.
Krabbelnd
Meine Beispielseite ist mein persönlicher Blog. Ich schreibe den Blog in Markdown, generiere HTML mit Jekyll, stelle ihn über Git bereit und hosten ihn auf Lighttpd und CloudFront. Aber all das ändert nichts daran, dass wir diese Inhalte konsumieren, wir werden die Website nur crawlen.
Zunächst bereiten Sie die Ausführung von Scrapy in einer Python-Virtualenv vor:
PROJECT_DIR=~/projects/scrapy
mkdir $PROJECT_DIR
cd $PROJECT_DIR
virtualenv scrapyenv
source scrapyenv/bin/activate
pip install scrapy
Dann erstellen Sie eine Scrapy-Anwendung mit dem Namen blog
:
scrapy startproject blog
cd blog
Die Elemente, die wir indizieren möchten, sind die Blog-Einträge; ich verwende nur die Felder Titel, URL und Text:
cat > blog/items.py <<EOM
from scrapy.item import Item, Field
class BlogItem(Item):
title = Field()
url = Field()
text = Field()
EOM
Als Nächstes erstelle ich einen einfachen Spider, der meine Website durchsucht, Blogbeiträge anhand der URL-Struktur identifiziert,
und den Text aus dem Blogbeitrag extrahiert. Das Tolle daran ist, dass wir bestimmte Teile der Seite extrahieren können.
cat > blog/spiders/blogspider.py <<EOM
from scrapy.spider import BaseSpider
from scrapy.selector import HtmlXPathSelector
from scrapy.http import Request
from blog.items import BlogItem
from scrapy.item import Item
from urlparse import urljoin
import re
class BlogSpider(BaseSpider):
name = 'blog'
allowed_domains = ['www.greenhills.co.uk']
start_urls = ['http://www.greenhills.co.uk/']
seen = set()
def parse(self, response):
if response.url in self.seen:
self.log('already seen %s' % response.url)
else:
self.log('parsing %s' % response.url)
self.seen.add(response.url)
hxs = HtmlXPathSelector(response)
if re.match(r'http://www.greenhills.co.uk/d{4}/d{2}/d{2}', response.url):
item = BlogItem()
item['title'] = hxs.select('//title/text()').extract()
item['url'] = response.url
item['text'] = hxs.select('//section[@id="main"]//child::node()/text()').extract()
self.log("yielding item " + response.url)
yield item
for url in hxs.select('//a/@href').extract():
url = urljoin(response.url, url)
if not url in self.seen and not re.search(r'.(pdf|zip|jar)$', url):
self.log("yielding request " + url)
yield Request(url, callback=self.parse)
EOM
Der wichtigste Teil der Logik hier ist der Abgleich: Meine Blog-URLs beginnen alle mit einem Datum (/YYYY/MM/DD/
), also verwende ich das, um Blogbeiträge zu identifizieren, die ich dann mit XPath analysiere. Der Haken an der Sache ist, dass Sie absolute URLs aus relativen Pfaden in HTML A-Tags (mit urljoin
) erstellen müssen und dass ich Links zu binären Typen überspringe. Ich hätte den CrawlSpider verwenden und Regeln für das Extrahieren/Parsen definieren können, aber mit dem BaseCrawler ist es etwas klarer zu sehen, was passiert.
Um das Krabbeln zu starten:
scrapy crawl blog -o items.json -t json
die eine JSON-Datei items.json
mit einer Liste von Elementen wie:
{"url": "http://www.greenhills.co.uk/2013/05/22/installing-distributed-solr-4-with-fabric.html",
"text": ["Installing Distributed Solr 4 with Fabric", "22 May 2013", "I wrote an article ", "u201cInstalling Distributed Solr 4 with Fabricu201d", "nabout deploying SolrCloud with ", "Fabric", ".nCode is on ", "github.com/Lucidworks/solr-fabric", ".", "My ", "VM strategy", "nand ", "server", " worked great for developing/testing this!", "u00a9 2011 Martijn Koster u2014 ", "terms", "n", "n"],
"title": ["Installing Distributed Solr 4 with Fabric"]},
Indizierung
Um diese Daten in Solr zu übertragen, könnten wir den JSON Request Handler verwenden und die JSON-Daten einfach in die entsprechende Form umwandeln. Aber da wir Python verwenden, benutzen wir einfach pysolr.
So installieren Sie pysolr:
pip install pysolr
und schreiben Sie den Python-Code:
cat > inject.py <<EOM
import pysolr,json,argparse
parser = argparse.ArgumentParser(description='load json into python.')
parser.add_argument('input_file', metavar='input', type=str, help='json input file')
parser.add_argument('solr_url', metavar='url', type=str, help='solr URL')
args = parser.parse_args()
solr = pysolr.Solr(args.solr_url, timeout=10)
items = json.load(open(args.input_file))
for item in items:
item['id'] = item['url']
solr.add(items)
EOM
Das sieht zu einfach aus, oder? Der Trick ist, dass die Attributnamen title
/url
/body
in der JSON-Datei mit den Felddefinitionen in der Default Solr schema.xml übereinstimmen. Beachten Sie, dass das Feld text
so konfiguriert ist, dass es indiziert, aber nicht gespeichert wird. Das bedeutet, dass Sie den Inhalt der Seite nicht mit Ihrer Abfrage zurückbekommen und dass Sie keine Dinge wie Hervorhebungen vornehmen können.
Wir brauchen ein „id“-Feld, und dafür verwenden wir die URL. Ich hätte das auch im Crawler festlegen können, so dass es Teil der JSON-Datei geworden wäre, aber da es eine Solr-spezifische Anforderung ist (und ich so eine einfache Feldzuordnung veranschaulichen kann), habe ich mich entschieden, es hier zu tun.
Die url
zeigt auf einen Solr 4-Server auf dem Host „vm116.lan“ in meinem LAN. Passen Sie den Hostnamen und die Portnummer an Ihren an.
Weitere Informationen zur Ausführung von Solr finden Sie im Solr 4.3.0-Tutorial.
Zur Ausführung (ändern Sie die URL so, dass sie auf Ihre Solr-Instanz verweist):
python inject.py items.json http://vm116.lan:8983/solr/collection1
und um eine Abfrage zu machen, gehen Sie mit Ihrem Browser auf http://vm116.lan:8983/solr/collection1/browse
und suchen Sie nach Fabric. Dort sollten Sie den Beitrag Installing Distributed Solr 4 with Fabric finden. Oder verwenden Sie die untergeordnete Solr-Abfrageseite http://vm116.lan:8983/solr/#/collection1/query
und führen Sie eine Abfrage nach text:Fabric durch.
In diesem Beispiel werden Crawling und Indizierung in getrennten Phasen durchgeführt. Sie können auch direkt vom Crawler aus injizieren.
Lizenz und Haftungsausschluss
Die obigen Codeschnipsel werden von abgedeckt:
Die MIT-Lizenz (MIT)
Copyright (c) 2013 Martijn Koster
Hiermit wird jeder Person, die eine Kopie
dieser Software und der zugehörigen Dokumentationsdateien (die „Software“) erwirbt, die Erlaubnis erteilt,
mit der Software uneingeschränkt zu handeln, einschließlich und ohne Einschränkung der Rechte
die Software zu nutzen, zu kopieren, zu modifizieren, zusammenzuführen, zu veröffentlichen, zu vertreiben, Unterlizenzen zu vergeben und/oder
Kopien der Software zu verkaufen, und Personen, denen die Software
zur Verfügung gestellt wird, dies zu gestatten, vorbehaltlich der folgenden Bedingungen:Der obige Copyright-Hinweis und dieser Genehmigungshinweis sind in
in alle Kopien oder wesentlichen Teile der Software aufzunehmen.DIE SOFTWARE WIRD OHNE MÄNGELGEWÄHR UND OHNE JEGLICHE AUSDRÜCKLICHE ODER
STILLSCHWEIGENDE GARANTIE ZUR VERFÜGUNG GESTELLT, EINSCHLIESSLICH, ABER NICHT BESCHRÄNKT AUF DIE GARANTIE DER MARKTGÄNGIGKEIT,
EIGNUNG FÜR EINEN BESTIMMTEN ZWECK UND NICHTVERLETZUNG VON RECHTEN. IN KEINEM FALL SIND DIE
AUTOREN ODER URHEBERRECHTSINHABER HAFTBAR FÜR JEGLICHE ANSPRÜCHE, SCHÄDEN ODER ANDERE
HAFTUNG, SEI ES AUS VERTRAG, UNERLAUBTER HANDLUNG ODER ANDERWEITIG, DIE SICH AUS,
ODER IN VERBINDUNG MIT DER SOFTWARE ODER DER NUTZUNG ODER DEM SONSTIGEN UMGANG MIT
DER SOFTWARE ERGEBEN.