Add dryrun target to spider a URL without storing results

This commit is contained in:
Marian Steinbach 2024-03-04 17:15:10 +01:00
parent d3d3e05a1d
commit ce8a440d72
5 changed files with 55 additions and 47 deletions

View file

@ -4,7 +4,7 @@ DB_ENTITY := spider-results
VERSION = $(shell git describe --exact-match --tags 2> /dev/null || git rev-parse HEAD)
.PHONY: dockerimage spider export
.PHONY: dockerimage spider export dryrun test
# Build docker image
dockerimage: VERSION
@ -18,6 +18,19 @@ jobs:
docker compose up manager
venv/bin/rq info
# Spider a single URL and inspect the result
dryrun:
docker run --rm -ti \
-v $(PWD)/volumes/dev-shm:/dev/shm \
-v $(PWD)/secrets:/secrets \
-v $(PWD)/volumes/chrome-userdir:/opt/chrome-userdir \
--shm-size=2g \
$(IMAGE) \
python3 cli.py \
--credentials-path /secrets/datastore-writer.json \
--loglevel debug \
dryrun ${ARGS}
# Run the spider.
# OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES is a workaround for mac OS.
spider:

View file

@ -25,53 +25,36 @@ Alle Informationen zum Betrieb befinden sich im Verzeichnis [devops](https://git
## Entwicklung
Green Spider ist in Python 3 geschrieben und wird aktuell unter 3.6 getestet und ausgeführt.
Green Spider ist in Python geschrieben. Der Code ist darauf ausgelegt, in einem Docker Container ausführbar zu sein. Darüber hinaus _kann_ er möglicherweise in einer lokalen Python-Umgebung funktionieren. Für reproduzierbare Bedingungen beim Ausführen des headless Browsers (chromium, chromedriver) empfielt es sich jedoch, in einer Container-Umgebung zu testen.
Aufgrund zahlreicher Abhängigkeiten empfiehlt es sich, den Spider Code lokal in Docker
auszuführen.
Das aktuellste Container Image steht unter `ghcr.io/netzbegruenung/green-spider:latest` zur Verfügung. Alternative Versionen und Tags sind unter [Packages](https://github.com/netzbegruenung/green-spider/pkgs/container/green-spider) auffindbar.
Das Image wird über den folgenden Befehl erzeugt:
Lokal kann das Image mit diesem Befehl gebaut werden:
```nohighlight
make
make dockerimage
```
Das dauert beim ersten Ausführen einige Zeit, wiel einige Python-Module das Kompilieren diverser Libraries erfordern.
Nach dem ersten erfolgreichen Durchlauf dauert ein neuer Aufruf von `make` nur noch wenige Sekunden.
### Unittests ausführen
### Tests ausführen
In aller Kürze: `make test`
Nach dem Bauen des Container Image (siehe oben) werden die Unit Tests im Container über `make test` ausgeführt.
### Spider testweise ausführen (Debugging)
Der Spider kann einzelne URLs verarbeiten, ohne die Ergebnisse in eine Datenbank zu schreiben.
Am einfachsten geht das über den `make spider` Befehl, so:
Am einfachsten geht das über den `make dryrun` Befehl, so:
```nohighlight
make spider ARGS="--url http://www.example.com/"
make dryrun ARGS="http://www.example.com/"
```
Wenn nur eine einzelne Site gespidert werden soll, die Ergebnisse aber in die Datenbank geschrieben werden sollen, kann der Spider so mit `--job` und einem JSON-Object aufgerufen werden (Beispiel):
### Warteschlange und Worker
Für einen kompletten Durchlauf wird die Warteschlange gefüllt und dann abgearbeitet. Das passiert im Betrieb über das Script [devops/run-job.sh](https://github.com/netzbegruenung/green-spider/blob/main/devops/run-job.sh).
Lokal kann das über die folgenden Befehle getestet werden:
```nohighlight
docker run --rm -ti \
-v $(pwd)/volumes/dev-shm:/dev/shm \
-v $(pwd)/secrets:/secrets \
-v $(pwd)/screenshots:/screenshots \
-v $(pwd)/volumes/chrome-userdir:/opt/chrome-userdir \
--shm-size=2g \
ghcr.io/netzbegruenung/green-spider:latest python3 cli.py \
--credentials-path /secrets/datastore-writer.json \
--loglevel debug \
spider --job '{"url": "https://gruene-porta-westfalica.de/home/", "city": "Porta Westfalica", "country": "DE", "district": "Minden-Lübbecke", "level": "DE:ORTSVERBAND", "state":" Nordrhein-Westfalen", "type": "REGIONAL_CHAPTER"}'
```
Wenn Jobs in der rq Warteschlange vorliegen, kann der Spider auch durch Starten eines rq workers mit diesem Kommando angestoßen werden:
```shell
# Unter mac OS evtl benötigt:
export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES
rq worker --verbose --burst high default low
make jobs
make spider
```

13
cli.py
View file

@ -33,6 +33,10 @@ if __name__ == "__main__":
# subcommands
subparsers = parser.add_subparsers(help='sub-command help', dest='command')
# 'dryrun' subcommand to spider one URL without writing results back.
dryrun_parser = subparsers.add_parser('dryrun', help='Spider an arbitrary URL without storing results. ')
dryrun_parser.add_argument('url', help='Spider a URL instead of using jobs from the queue. For testing/debugging only.')
# manager subcommand
manager_parser = subparsers.add_parser('manager', help='Adds spider jobs to the queue. By default, all green-directory URLs are added.')
@ -63,16 +67,21 @@ if __name__ == "__main__":
logging.debug("Called command %s", args.command)
if args.command == 'manager':
import manager
manager.create_jobs(args.url)
elif args.command == 'export':
import export
datastore_client = datastore.Client.from_service_account_json(args.credentials_path)
export.export_results(datastore_client, args.kind)
elif args.command == 'dryrun':
from spider import spider
from export.datetimeencoder import DateTimeEncoder
result = spider.check_and_rate_site({"url": args.url, "type": "REGIONAL_CHAPTER", "level": "DE:KREISVERBAND", "state": "Unnamed", "district": "Unnamed"})
print(json.dumps(result, indent=2, sort_keys=True, ensure_ascii=False, cls=DateTimeEncoder))
else:
parser.print_help()
sys.exit(1)

View file

@ -11,17 +11,7 @@ from hashlib import md5
import json
import requests
class DateTimeEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime.datetime):
return obj.isoformat()
elif isinstance(obj, datetime.date):
return obj.isoformat()
elif isinstance(obj, datetime.timedelta):
return (datetime.datetime.min + obj).time().isoformat()
else:
return super(DateTimeEncoder, self).default(obj)
from export import datetimeencoder
def export_results(client, entity_kind):
"""
@ -45,4 +35,4 @@ def export_results(client, entity_kind):
output_filename = "/json-export/spider_result.json"
with open(output_filename, 'w', encoding="utf8") as jsonfile:
json.dump(out, jsonfile, indent=2, sort_keys=True, ensure_ascii=False, cls=DateTimeEncoder)
json.dump(out, jsonfile, indent=2, sort_keys=True, ensure_ascii=False, cls=datetimeencoder.DateTimeEncoder)

13
export/datetimeencoder.py Normal file
View file

@ -0,0 +1,13 @@
import json
import datetime
class DateTimeEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime.datetime):
return obj.isoformat()
elif isinstance(obj, datetime.date):
return obj.isoformat()
elif isinstance(obj, datetime.timedelta):
return (datetime.datetime.min + obj).time().isoformat()
else:
return super(DateTimeEncoder, self).default(obj)