Web-Scraping-JavaScript-Seite mit Python


178

Ich versuche einen einfachen Web Scraper zu entwickeln. Ich möchte Text ohne den HTML-Code extrahieren. Tatsächlich erreiche ich dieses Ziel, aber ich habe gesehen, dass ich auf einigen Seiten, auf denen JavaScript geladen ist, keine guten Ergebnisse erzielt habe.

Wenn beispielsweise ein JavaScript-Code Text hinzufügt, kann ich ihn nicht sehen, da ich ihn anrufe

response = urllib2.urlopen(request)

Ich erhalte den Originaltext ohne den hinzugefügten (da JavaScript im Client ausgeführt wird).

Ich suche nach Ideen, um dieses Problem zu lösen.


2
Klingt so, als ob Sie etwas Schweres brauchen, versuchen Sie es mit Selen oder Watir.
wim

2
Ich habe dies erfolgreich in Java durchgeführt (ich habe das Cobra-Toolkit lobobrowser.org/cobra.jsp verwendet ). Da Sie in Python hacken möchten (immer eine gute Wahl), empfehle ich diese beiden Optionen: - packtpub.com/article/ Web-Scraping-with-Python-Teil-2 - blog.databigbang.com/web-scraping-ajax-and-javascript-sites
bpgergo

Antworten:


203

EDIT 30 / Dec / 2017: Diese Antwort wird in den Top-Ergebnissen der Google-Suche angezeigt, daher habe ich beschlossen, sie zu aktualisieren. Die alte Antwort ist noch am Ende.

Dryscape wird nicht mehr gepflegt und die von Dryscape-Entwicklern empfohlene Bibliothek ist nur noch Python 2. Ich habe festgestellt, dass die Python-Bibliothek von Selenium mit Phantom JS als Web-Treiber schnell genug und einfach ist, um die Arbeit zu erledigen.

Stellen Sie nach der Installation von Phantom JS sicher, dass die phantomjsBinärdatei im aktuellen Pfad verfügbar ist:

phantomjs --version
# result:
2.1.1

Beispiel

Als Beispiel habe ich eine Beispielseite mit folgendem HTML-Code erstellt. ( Link ):

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Javascript scraping test</title>
</head>
<body>
  <p id='intro-text'>No javascript support</p>
  <script>
     document.getElementById('intro-text').innerHTML = 'Yay! Supports javascript';
  </script> 
</body>
</html>

ohne Javascript heißt es: No javascript supportund mit Javascript:Yay! Supports javascript

Scraping ohne JS-Unterstützung:

import requests
from bs4 import BeautifulSoup
response = requests.get(my_url)
soup = BeautifulSoup(response.text)
soup.find(id="intro-text")
# Result:
<p id="intro-text">No javascript support</p>

Scraping mit JS-Unterstützung:

from selenium import webdriver
driver = webdriver.PhantomJS()
driver.get(my_url)
p_element = driver.find_element_by_id(id_='intro-text')
print(p_element.text)
# result:
'Yay! Supports javascript'

Sie können auch Python Library Dryscrape verwenden , um Javascript-gesteuerte Websites zu kratzen.

Scraping mit JS-Unterstützung:

import dryscrape
from bs4 import BeautifulSoup
session = dryscrape.Session()
session.visit(my_url)
response = session.body()
soup = BeautifulSoup(response)
soup.find(id="intro-text")
# Result:
<p id="intro-text">Yay! Supports javascript</p>

16
Leider keine Windows-Unterstützung.
Expenzor

1
Gibt es Alternativen für diejenigen von uns, die unter Windows programmieren?
Hoshiko86

2
@ExpenzorIch arbeite an Fenstern. PhantomJS funktioniert gut.
Aakash Choubey

17
Bemerkenswert ist, dass PhantomJS eingestellt wurde und sich angesichts der Tatsache, dass Chrome jetzt Headless unterstützt, nicht mehr in der aktiven Entwicklung befindet. Die Verwendung von kopflosem Chrom / Firefox wird empfohlen.
Sytech

3
Es ist sowohl Selenunterstützung als auch PhantomJS selbst. github.com/ariya/phantomjs/issues/15344
Sytech

73

Wir erhalten nicht die richtigen Ergebnisse, da alle mit Javascript generierten Inhalte im DOM gerendert werden müssen. Wenn wir eine HTML-Seite abrufen, rufen wir die Initiale DOM ab, die nicht durch Javascript geändert wurde.

Daher müssen wir den Javascript-Inhalt rendern, bevor wir die Seite crawlen.

Da Selen in diesem Thread bereits oft erwähnt wird (und wie langsam es manchmal wird, wurde auch erwähnt), werde ich zwei weitere mögliche Lösungen auflisten.


Lösung 1: Dies ist ein sehr schönes Tutorial zur Verwendung von Scrapy zum Crawlen von mit Javascript generierten Inhalten. Wir werden genau dem folgen.

Was wir brauchen werden:

  1. Docker in unserer Maschine installiert. Dies ist bis zu diesem Zeitpunkt ein Plus gegenüber anderen Lösungen, da eine betriebssystemunabhängige Plattform verwendet wird.

  2. Installieren Sie Splash gemäß den Anweisungen für unser entsprechendes Betriebssystem.
    Zitat aus der Splash-Dokumentation:

    Splash ist ein Javascript-Rendering-Dienst. Es ist ein leichter Webbrowser mit einer HTTP-API, die in Python 3 mit Twisted und QT5 implementiert wurde.

    Im Wesentlichen werden wir Splash verwenden, um von Javascript generierten Inhalt zu rendern.

  3. Führen Sie den Splash-Server aus : sudo docker run -p 8050:8050 scrapinghub/splash.

  4. Installieren Sie das Scrapy-Splash- Plugin:pip install scrapy-splash

  5. Angenommen, wir haben bereits ein Scrapy-Projekt erstellt (wenn nicht, erstellen wir eines ), folgen wir der Anleitung und aktualisieren Folgendes settings.py:

    Gehen Sie dann zu Ihrem Scrapy-Projekt settings.pyund stellen Sie diese Middlewares ein:

    DOWNLOADER_MIDDLEWARES = {
          'scrapy_splash.SplashCookiesMiddleware': 723,
          'scrapy_splash.SplashMiddleware': 725,
          'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810,
    }

    Die URL des Splash-Servers (wenn Sie Win oder OSX verwenden, sollte dies die URL des Docker-Computers sein: Wie erhalte ich die IP-Adresse eines Docker-Containers vom Host? ):

    SPLASH_URL = 'http://localhost:8050'

    Und schließlich müssen Sie auch diese Werte einstellen:

    DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
    HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'
  6. Schließlich können wir Folgendes verwenden SplashRequest:

    In einer normalen Spinne haben Sie Anforderungsobjekte, mit denen Sie URLs öffnen können. Wenn die Seite, die Sie öffnen möchten, JS-generierte Daten enthält, müssen Sie SplashRequest (oder SplashFormRequest) verwenden, um die Seite zu rendern. Hier ist ein einfaches Beispiel:

    class MySpider(scrapy.Spider):
        name = "jsscraper"
        start_urls = ["http://quotes.toscrape.com/js/"]
    
        def start_requests(self):
            for url in self.start_urls:
            yield SplashRequest(
                url=url, callback=self.parse, endpoint='render.html'
            )
    
        def parse(self, response):
            for q in response.css("div.quote"):
            quote = QuoteItem()
            quote["author"] = q.css(".author::text").extract_first()
            quote["quote"] = q.css(".text::text").extract_first()
            yield quote

    SplashRequest rendert die URL als HTML und gibt die Antwort zurück, die Sie in der Callback-Methode (Parse) verwenden können.


Lösung 2: Nennen wir dies momentan experimentell (Mai 2018) ...
Diese Lösung ist (derzeit) nur für Pythons Version 3.6 .

Kennen Sie das Anforderungsmodul (na wer nicht)?
Jetzt hat es ein Web, das kleine Geschwister crawlt: Anfragen-HTML :

Diese Bibliothek soll das Parsen von HTML (z. B. Scraping des Webs) so einfach und intuitiv wie möglich gestalten.

  1. Installieren Sie request-html: pipenv install requests-html

  2. Stellen Sie eine Anfrage an die URL der Seite:

    from requests_html import HTMLSession
    
    session = HTMLSession()
    r = session.get(a_page_url)
  3. Rendern Sie die Antwort, um die von Javascript generierten Bits zu erhalten:

    r.html.render()

Schließlich scheint das Modul Scraping-Funktionen zu bieten .
Alternativ können wir die gut dokumentierte Art der Verwendung von BeautifulSoup mit dem r.htmlgerade gerenderten Objekt ausprobieren .


Können Sie nach dem Aufruf von .render () erläutern, wie Sie den vollständigen HTML-Inhalt mit geladenen JS-Bits abrufen können? Ich stecke nach diesem Punkt fest. Ich sehe nicht alle Iframes, die normalerweise von JavaScript im r.html.htmlObjekt in die Seite eingefügt werden .
anon58192932

@ anon58192932 Da dies im Moment eine experimentelle Lösung ist und ich nicht genau weiß, was Sie damit erreichen möchten, kann ich nichts wirklich vorschlagen ... Sie können hier auf SO eine neue Frage erstellen, wenn Sie dies nicht getan haben hat noch eine Lösung ausgearbeitet
John Moutafis

2
Ich habe diesen Fehler erhalten: RuntimeError: HTMLSession kann nicht in einer vorhandenen Ereignisschleife verwendet werden. Verwenden Sie stattdessen AsyncHTMLSession.
HuckIt

1
@ HuckIt dies scheint ein bekanntes Problem zu sein: github.com/psf/requests-html/issues/140
John Moutafis

47

Vielleicht kann Selen das.

from selenium import webdriver
import time

driver = webdriver.Firefox()
driver.get(url)
time.sleep(5)
htmlSource = driver.page_source

3
Selen ist wirklich schwer für solche Dinge, die unnötig langsam wären und einen Browserkopf erfordern, wenn Sie PhantomJS nicht verwenden, aber das würde funktionieren.
Joshua Hedges

@JoshuaHedges Sie können andere Standardbrowser im Headless-Modus ausführen.
reynoldsnlp

22

Wenn Sie das RequestsModul schon einmal für Python verwendet haben, habe ich kürzlich herausgefunden, dass der Entwickler ein neues Modul namens erstellt hat, Requests-HTMLdas jetzt auch JavaScript rendern kann.

Sie können auch https://html.python-requests.org/ besuchen , um mehr über dieses Modul zu erfahren. Wenn Sie nur Interesse am Rendern von JavaScript haben, können Sie https://html.python-requests.org/?#javascript besuchen -Unterstützung, um direkt zu lernen, wie man das Modul zum Rendern von JavaScript mit Python verwendet.

Wenn Sie das Requests-HTMLModul korrekt installiert haben , zeigt das folgende Beispiel, das unter dem obigen Link gezeigt wird , wie Sie mit diesem Modul eine Website kratzen und das auf der Website enthaltene JavaScript rendern können:

from requests_html import HTMLSession
session = HTMLSession()

r = session.get('http://python-requests.org/')

r.html.render()

r.html.search('Python 2 will retire in only {months} months!')['months']

'<time>25</time>' #This is the result.

Das habe ich kürzlich aus einem YouTube-Video erfahren. Klicke hier! um das YouTube-Video anzusehen, das zeigt, wie das Modul funktioniert.


3
Sollte beachten, dass dieses Modul nur Python 3.6 unterstützt.
Nat5142

1
Ich habe folgende Fehlermeldung erhalten: SSLError: HTTPSConnectionPool (host = 'docs.python-requests.org', port = 443): Maximale Wiederholungsversuche mit URL überschritten: / (verursacht durch SSLError (SSLError (1, '[SSL: TLSV1_ALERT_INTERNAL_ERROR] tlsv1-Warnung) interner Fehler (_ssl.c: 1045) ')))
HuckIt

@ HuckIt-Entschuldigungen Ich bin mit diesem Fehler nicht vertraut, aber der Fehler scheint, dass die Website, die Sie erreichen wollten, möglicherweise ein Problem mit der SSL-Zertifizierung hatte. Es tut uns leid, dass dies keine Lösung ist, aber ich würde Ihnen empfehlen, hier im Stapelüberlauf eine neue Frage zu stellen (falls diese noch nicht gestellt wurde) und möglicherweise weitere Details wie die von Ihnen verwendete Website-URL und Ihren Code anzugeben.
SShah

Scheint Chrom unter der Haube zu verwenden. Funktioniert aber super für mich
Sid

14

Dies scheint auch eine gute Lösung zu sein, entnommen aus einem großartigen Blog-Beitrag

import sys  
from PyQt4.QtGui import *  
from PyQt4.QtCore import *  
from PyQt4.QtWebKit import *  
from lxml import html 

#Take this class for granted.Just use result of rendering.
class Render(QWebPage):  
  def __init__(self, url):  
    self.app = QApplication(sys.argv)  
    QWebPage.__init__(self)  
    self.loadFinished.connect(self._loadFinished)  
    self.mainFrame().load(QUrl(url))  
    self.app.exec_()  

  def _loadFinished(self, result):  
    self.frame = self.mainFrame()  
    self.app.quit()  

url = 'http://pycoders.com/archive/'  
r = Render(url)  
result = r.frame.toHtml()
# This step is important.Converting QString to Ascii for lxml to process

# The following returns an lxml element tree
archive_links = html.fromstring(str(result.toAscii()))
print archive_links

# The following returns an array containing the URLs
raw_links = archive_links.xpath('//div[@class="campaign"]/a/@href')
print raw_links

12

Es hört sich so an, als ob auf die Daten, nach denen Sie wirklich suchen, über eine sekundäre URL zugegriffen werden kann, die von einem Javascript auf der primären Seite aufgerufen wird.

Während Sie versuchen könnten, Javascript auf dem Server auszuführen, um dies zu handhaben, besteht ein einfacherer Ansatz darin, die Seite mit Firefox zu laden und ein Tool wie Charles oder Firebug zu verwenden, um genau zu identifizieren, um welche sekundäre URL es sich handelt. Dann können Sie diese URL einfach direkt nach den Daten abfragen, an denen Sie interessiert sind.


@Kris Nur für den Fall, dass jemand darüber stolpert und es versuchen möchte, anstatt etwas so Schweres wie Selen, hier ein kurzes Beispiel. Dadurch wird die Teiledetailseite für eine Sechskantmutter auf der McMaster-Carr-Website geöffnet. Der Inhalt ihrer Website wird größtenteils mit Javascript abgerufen und enthält nur sehr wenige native Seiteninformationen. Wenn Sie Ihre Browser-Entwicklertools öffnen, zur Registerkarte Netzwerk navigieren und die Seite aktualisieren, können Sie alle von der Seite gestellten Anforderungen anzeigen und die relevanten Daten finden (in diesem Fall das Teiledetail-HTML).
SweepingsDemon

Dies ist eine andere URL auf der Registerkarte "Firefox devtool Network", die, wenn sie befolgt wird, den HTML-Code für die meisten Teileinformationen enthält und einige der Parameter enthält, die erforderlich sind, um zum leichteren Scraping einfach zu anderen Teileinformationen zu navigieren. Dieses spezielle Beispiel ist nicht besonders nützlich, da der Preis von einer anderen Javascript-Funktion generiert wird, sollte aber als Einführung für alle dienen, die Stephens Rat befolgen möchten.
SweepingsDemon

12

Selen eignet sich am besten zum Scrapen von JS- und Ajax-Inhalten.

In diesem Artikel finden Sie Informationen zum Extrahieren von Daten aus dem Web mit Python

$ pip install selenium

Laden Sie dann den Chrome-Webdriver herunter.

from selenium import webdriver

browser = webdriver.Chrome()

browser.get("https://www.python.org/")

nav = browser.find_element_by_id("mainnav")

print(nav.text)

Einfach richtig?


8

Sie können Javascript auch mit dem Webdriver ausführen.

from selenium import webdriver

driver = webdriver.Firefox()
driver.get(url)
driver.execute_script('document.title')

oder speichern Sie den Wert in einer Variablen

result = driver.execute_script('var text = document.title ; return var')

oder Sie können einfach die driver.titleEigenschaft nutzen
Corey Goldberg

7

Ich persönlich bevorzuge die Verwendung von Scrapy und Selen und das Andocken beider in getrennten Behältern. Auf diese Weise können Sie moderne Websites, die fast alle Javascript in der einen oder anderen Form enthalten, mit minimalem Aufwand installieren und crawlen. Hier ist ein Beispiel:

Verwenden Sie die scrapy startproject, um Ihren Schaber zu erstellen und Ihre Spinne zu schreiben. Das Skelett kann so einfach sein:

import scrapy


class MySpider(scrapy.Spider):
    name = 'my_spider'
    start_urls = ['https://somewhere.com']

    def start_requests(self):
        yield scrapy.Request(url=self.start_urls[0])


    def parse(self, response):

        # do stuff with results, scrape items etc.
        # now were just checking everything worked

        print(response.body)

Die wahre Magie findet in der Middleware statt. Überschreiben Sie zwei Methoden in der Downloader-Middleware __init__und zwar process_requestauf folgende Weise:

# import some additional modules that we need
import os
from copy import deepcopy
from time import sleep

from scrapy import signals
from scrapy.http import HtmlResponse
from selenium import webdriver

class SampleProjectDownloaderMiddleware(object):

def __init__(self):
    SELENIUM_LOCATION = os.environ.get('SELENIUM_LOCATION', 'NOT_HERE')
    SELENIUM_URL = f'http://{SELENIUM_LOCATION}:4444/wd/hub'
    chrome_options = webdriver.ChromeOptions()

    # chrome_options.add_experimental_option("mobileEmulation", mobile_emulation)
    self.driver = webdriver.Remote(command_executor=SELENIUM_URL,
                                   desired_capabilities=chrome_options.to_capabilities())


def process_request(self, request, spider):

    self.driver.get(request.url)

    # sleep a bit so the page has time to load
    # or monitor items on page to continue as soon as page ready
    sleep(4)

    # if you need to manipulate the page content like clicking and scrolling, you do it here
    # self.driver.find_element_by_css_selector('.my-class').click()

    # you only need the now properly and completely rendered html from your page to get results
    body = deepcopy(self.driver.page_source)

    # copy the current url in case of redirects
    url = deepcopy(self.driver.current_url)

    return HtmlResponse(url, body=body, encoding='utf-8', request=request)

Vergessen Sie nicht, diese Middlware zu aktivieren, indem Sie die nächsten Zeilen in der Datei settings.py auskommentieren:

DOWNLOADER_MIDDLEWARES = {
'sample_project.middlewares.SampleProjectDownloaderMiddleware': 543,}

Weiter zur Dockerisierung. Erstellen Sie Ihr DockerfileImage aus einem einfachen Image (ich verwende hier Python Alpine), kopieren Sie Ihr Projektverzeichnis in das Image und installieren Sie die Anforderungen:

# Use an official Python runtime as a parent image
FROM python:3.6-alpine

# install some packages necessary to scrapy and then curl because it's  handy for debugging
RUN apk --update add linux-headers libffi-dev openssl-dev build-base libxslt-dev libxml2-dev curl python-dev

WORKDIR /my_scraper

ADD requirements.txt /my_scraper/

RUN pip install -r requirements.txt

ADD . /scrapers

Und zum Schluss alles zusammen in docker-compose.yaml:

version: '2'
services:
  selenium:
    image: selenium/standalone-chrome
    ports:
      - "4444:4444"
    shm_size: 1G

  my_scraper:
    build: .
    depends_on:
      - "selenium"
    environment:
      - SELENIUM_LOCATION=samplecrawler_selenium_1
    volumes:
      - .:/my_scraper
    # use this command to keep the container running
    command: tail -f /dev/null

Ausführen docker-compose up -d. Wenn Sie dies zum ersten Mal tun, dauert es eine Weile, bis das neueste Selen / Standalone-Chrom abgerufen und Ihr Scraper-Image erstellt wurde.

Sobald dies erledigt ist, können Sie überprüfen, ob Ihre Container ausgeführt werden docker psund ob der Name des Selencontainers mit dem der Umgebungsvariablen übereinstimmt, die wir an unseren Scraper-Container übergeben haben (hier war es SELENIUM_LOCATION=samplecrawler_selenium_1).

Geben Sie Ihren Scraper-Container mit ein docker exec -ti YOUR_CONTAINER_NAME sh, der Befehl für mich lautete docker exec -ti samplecrawler_my_scraper_1 sh: CD in das richtige Verzeichnis und führen Sie Ihren Scraper mit aus scrapy crawl my_spider.

Das Ganze ist auf meiner Github-Seite und du kannst es von hier bekommen


5

Eine Mischung aus BeautifulSoup und Selen funktioniert bei mir sehr gut.

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from bs4 import BeautifulSoup as bs

driver = webdriver.Firefox()
driver.get("http://somedomain/url_that_delays_loading")
    try:
        element = WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.ID, "myDynamicElement"))) #waits 10 seconds until element is located. Can have other wait conditions  such as visibility_of_element_located or text_to_be_present_in_element

        html = driver.page_source
        soup = bs(html, "lxml")
        dynamic_text = soup.find_all("p", {"class":"class_name"}) #or other attributes, optional
    else:
        print("Couldnt locate element")

PS Weitere Wartebedingungen finden Sie hier


4

Sie möchten urllib-, request-, beautifulSoup- und Selenium-Webtreiber in Ihrem Skript für verschiedene Teile der Seite verwenden (um nur einige zu nennen).
Manchmal bekommen Sie mit nur einem dieser Module das, was Sie brauchen.
Manchmal benötigen Sie zwei, drei oder alle diese Module.
Manchmal müssen Sie das js in Ihrem Browser ausschalten.
Manchmal benötigen Sie Header-Informationen in Ihrem Skript.
Keine Website kann auf die gleiche Weise gekratzt werden, und keine Website kann für immer auf die gleiche Weise gekratzt werden, ohne dass Sie Ihren Crawler normalerweise nach einigen Monaten ändern müssen. Aber sie können alle abgekratzt werden! Wo ein Wille ist, ist auch ein Weg sicher.
Wenn Sie kontinuierlich Daten in die Zukunft kratzen müssen, kratzen Sie einfach alles, was Sie brauchen, und speichern Sie es in .dat-Dateien mit pickle.
Suchen Sie einfach weiter, wie Sie mit diesen Modulen versuchen können, und kopieren Sie Ihre Fehler und fügen Sie sie in Google ein.


3

Verwenden von PyQt5

from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import QUrl
from PyQt5.QtWebEngineWidgets import QWebEnginePage
import sys
import bs4 as bs
import urllib.request


class Client(QWebEnginePage):
    def __init__(self,url):
        global app
        self.app = QApplication(sys.argv)
        QWebEnginePage.__init__(self)
        self.html = ""
        self.loadFinished.connect(self.on_load_finished)
        self.load(QUrl(url))
        self.app.exec_()

    def on_load_finished(self):
        self.html = self.toHtml(self.Callable)
        print("Load Finished")

    def Callable(self,data):
        self.html = data
        self.app.quit()

# url = ""
# client_response = Client(url)
# print(client_response.html)

1

Ich habe zwei Tage lang versucht, eine Antwort auf diese Fragen zu finden. Viele Antworten leiten Sie zu verschiedenen Themen. Aber die obige Antwort von serpentr ist wirklich auf den Punkt. Es ist die kürzeste und einfachste Lösung. Nur zur Erinnerung, das letzte Wort "var" stellt den Variablennamen dar und sollte daher verwendet werden als:

 result = driver.execute_script('var text = document.title ; return text')

Dies sollte ein Kommentar zur Antwort von serpentr sein, keine separate Antwort.
Yserbius

1
Das ist offensichtlich. Aber ich habe noch keine 50 Wiederholungen, um die Antwort eines anderen zu kommentieren.
Abd_bgc

0

Ich musste mich bei einigen eigenen Web-Scraping-Projekten mit diesem Problem befassen. Ich habe damit umgegangen, indem ich mithilfe der Python-Anforderungsbibliothek eine http-Anforderung direkt an die API gesendet habe, anstatt die JS laden zu müssen.

Die Python-Anforderungsbibliothek funktioniert hierfür gut. Sie können die http-Anforderungen anzeigen, indem Sie das Inspect-Element verwenden und zur Registerkarte "Netzwerk" navigieren.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.