it-swarm.com.de

Kann Scrapy verwendet werden, um dynamische Inhalte von Websites, die AJAX verwenden, zu entfernen?

Ich habe vor kurzem Python gelernt und tauche meine Hand in den Aufbau eines Web-Scrapers. Es ist nichts Besonderes; Der einzige Zweck besteht darin, die Daten von einer Wett-Website abzurufen und in Excel speichern zu lassen.

Die meisten Probleme sind lösbar und ich habe ein gutes Durcheinander. Allerdings stoße ich bei einer Ausgabe auf eine massive Hürde. Wenn eine Website eine Tabelle mit Pferden lädt und aktuelle Wettpreise auflistet, befinden sich diese Informationen in keiner Quelldatei. Der Hinweis ist, dass diese Daten manchmal live sind, wobei die Zahlen offensichtlich von einem Remote-Server aktualisiert werden. Das HTML auf meinem PC hat einfach eine Lücke, in der ihre Server alle interessanten Daten durchspielen, die ich brauche.

Jetzt sind meine Erfahrungen mit dynamischen Webinhalten gering, daher habe ich Schwierigkeiten, meinen Kopf herumzureißen. 

Ich denke, Java oder Javascript ist ein Schlüssel, dieser erscheint oft. 

Der Scraper ist einfach eine Quotenvergleichsmaschine. Einige Sites haben APIs, aber ich brauche dies für diejenigen, die dies nicht tun. Ich verwende die Scrapy-Bibliothek mit Python 2.7

Ich entschuldige mich, wenn diese Frage zu offen ist. Kurz gesagt, meine Frage ist: Wie kann diese dynamische Daten mit Hilfe von Scrapy so gekratzt werden, dass ich sie verwenden kann? Damit ich diese Wettquotendaten in Echtzeit kratzen kann?

124
Joseph

Webkit-basierte Browser (wie Google Chrome oder Safari) verfügen über integrierte Entwicklertools. In Chrome können Sie es Menu->Tools->Developer Tools öffnen. Auf der Registerkarte Network können Sie alle Informationen zu jeder Anfrage und Antwort anzeigen:

enter image description here

Im unteren Bereich des Bildes sehen Sie, dass ich die Anfrage bis zu XHR gefiltert habe - dies sind Anfragen, die durch Javascript-Code erfolgen.

Tipp: Das Protokoll wird jedes Mal gelöscht, wenn Sie eine Seite laden. Am unteren Rand des Bildes wird die schwarze Punkt-Schaltfläche das Protokoll beibehalten.

Nach der Analyse von Anforderungen und Antworten können Sie diese Anforderungen von Ihrem Web-Crawler simulieren und wertvolle Daten extrahieren. In vielen Fällen ist es einfacher, Ihre Daten zu erhalten als das Analysieren von HTML, da diese Daten keine Präsentationslogik enthalten und für den Zugriff durch Javascript-Code formatiert sind.

Firefox hat eine ähnliche Erweiterung, es heißt firebug . Einige werden argumentieren, dass Firebug noch mächtiger ist, aber ich mag die Einfachheit des Webkits.

74
Ski

Hier ist ein einfaches Beispiel für die Verwendung von scrapy mit ajax request . Besuchen Sie die Site http://www.rubin-kazan.ru/guestbook.html . Alle Nachrichten werden mit einer ajax request geladen. Mein Ziel ist es, diese Nachrichten mit all ihren Attributen (Autor, Datum, ...) abzurufen.

enter image description here

Wenn ich den Quellcode der Seite analysiere, kann ich nicht alle diese Meldungen sehen, da die Webseite die Ajax-Technologie verwendet. Aber ich kann mit Firebug von Mozila Firefox (oder einem Analogie-Instrument in einem anderen Browser) die HTTP-Anfrage analysieren, die die Meldungen auf der Webseite erzeugt .enter image description here

Zu diesem Zweck lade ich nicht alle Seiten neu, sondern nur den Teil der Seite, der Nachrichten enthält. Zu diesem Zweck klicke ich unten auf eine beliebige Anzahl von Seiten enter image description hereund ich beobachte die HTTP-Anforderung, die für den Nachrichtentext verantwortlich ist enter image description here

Nach dem Abschluss analysiere ich die Header der Anfrage (ich muss zitieren, dass ich diese URL aus der Quellenseite aus dem Var-Abschnitt extrahiere, siehe den Code unten) .enter image description here

und der Formulardateninhalt der Anforderung (die HTTP-Methode ist "Post")

enter image description here

und der Inhalt der Antwort, die eine Json-Datei ist,

enter image description here

welche alle Informationen darstellen, die ich suche.

Ab jetzt muss ich all dieses Wissen in Scrapy implementieren. Definieren wir die Spinne für diesen Zweck.

  class spider(BaseSpider):
      name = 'RubiGuesst'
      start_urls = ['http://www.rubin-kazan.ru/guestbook.html']

    def parse(self, response):
      url_list_gb_messages = re.search(r'url_list_gb_messages="(.*)"', response.body).group(1)
      yield FormRequest('http://www.rubin-kazan.ru' + url_list_gb_messages, callback=self.RubiGuessItem, formdata={'page': str(page + 1), 'uid': ''})
    def RubiGuessItem(self, response):
       json_file = response.body

In der Parser-Funktion habe ich die Antwort auf die erste Anfrage. In RubiGuessItem habe ich die Json-Datei mit allen Informationen. 

82
Badarau Petru

Beim Crawlen stoßen wir oft auf Probleme, bei denen Inhalte, die auf der Seite gerendert werden, mit Javascript generiert werden und daher Scrapy dafür nicht crawlen kann (z. B. Ajax-Anfragen, jQuery-Verrücktheit).

Wenn Sie jedoch Scrapy zusammen mit dem Web-Testframework Selenium verwenden, können wir alles kriechen, was in einem normalen Webbrowser angezeigt wird.

Einige Dinge zu beachten:

  • Sie müssen die Python-Version von Selenium RC installiert haben, damit dies funktioniert, und Sie müssen Selenium ordnungsgemäß eingerichtet haben. Auch dies ist nur ein Template-Crawler. Sie könnten viel verrückter und fortgeschrittener werden, aber ich wollte nur die Grundidee zeigen. Da der Code jetzt steht, werden Sie zwei Anfragen für eine bestimmte URL stellen. Eine Anfrage wird von Scrapy gestellt, die andere von Selenium. Ich bin sicher, es gibt Wege, um Selenium dazu zu bringen, die einzige Anforderung zu erfüllen, aber ich habe mir nicht die Mühe gemacht, dies zu implementieren. Wenn Sie zwei Anfragen stellen, können Sie die Seite auch mit Scrapy crawlen.

  • Dies ist sehr leistungsfähig, da Sie nun das gesamte gerenderte DOM zum Crawlen zur Verfügung haben und weiterhin alle Nice-Crawling-Funktionen in Scrapy verwenden können. Dies führt zwar zu einem langsameren Crawlen, aber je nachdem, wie viel Sie das gerenderte DOM benötigen, kann sich das Warten lohnen.

    from scrapy.contrib.spiders import CrawlSpider, Rule
    from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
    from scrapy.selector import HtmlXPathSelector
    from scrapy.http import Request
    
    from Selenium import Selenium
    
    class SeleniumSpider(CrawlSpider):
        name = "SeleniumSpider"
        start_urls = ["http://www.domain.com"]
    
        rules = (
            Rule(SgmlLinkExtractor(allow=('\.html', )), callback='parse_page',follow=True),
        )
    
        def __init__(self):
            CrawlSpider.__init__(self)
            self.verificationErrors = []
            self.Selenium = Selenium("localhost", 4444, "*chrome", "http://www.domain.com")
            self.Selenium.start()
    
        def __del__(self):
            self.Selenium.stop()
            print self.verificationErrors
            CrawlSpider.__del__(self)
    
        def parse_page(self, response):
            item = Item()
    
            hxs = HtmlXPathSelector(response)
            #Do some XPath selection with Scrapy
            hxs.select('//div').extract()
    
            sel = self.Selenium
            sel.open(response.url)
    
            #Wait for javscript to load in Selenium
            time.sleep(2.5)
    
            #Do some crawling of javascript created content with Selenium
            sel.get_text("//div")
            yield item
    
    # Snippet imported from snippets.scrapy.org (which no longer works)
    # author: wynbennett
    # date  : Jun 21, 2011
    

Referenz: http://snipplr.com/view/66998/

35
A T

Eine andere Lösung wäre die Implementierung eines Download-Handlers oder einer Download-Handler-Middleware. Im Folgenden finden Sie ein Beispiel für Middleware, die Selenium mit kopflosen Phantomjs-Web-Treibern verwendet:

class JsDownload(object):

@check_spider_middleware
def process_request(self, request, spider):
    driver = webdriver.PhantomJS(executable_path='D:\phantomjs.exe')
    driver.get(request.url)
    return HtmlResponse(request.url, encoding='utf-8', body=driver.page_source.encode('utf-8'))

Ich wollte die Möglichkeit haben, verschiedenen Spiders mitzuteilen, welche Middleware verwendet werden soll, also habe ich diesen Wrapper implementiert:

def check_spider_middleware(method):
@functools.wraps(method)
def wrapper(self, request, spider):
    msg = '%%s %s middleware step' % (self.__class__.__name__,)
    if self.__class__ in spider.middleware:
        spider.log(msg % 'executing', level=log.DEBUG)
        return method(self, request, spider)
    else:
        spider.log(msg % 'skipping', level=log.DEBUG)
        return None

return wrapper

einstellungen.py:

DOWNLOADER_MIDDLEWARES = {'MyProj.middleware.MiddleWareModule.MiddleWareClass': 500}

damit der Wrapper funktioniert, müssen alle Spinnen mindestens Folgendes haben:

middleware = set([])

eine Middleware hinzufügen:

middleware = set([MyProj.middleware.ModuleName.ClassName])

Der Hauptvorteil bei der Implementierung auf diese Weise und nicht in der Spinne besteht darin, dass Sie am Ende nur eine Anforderung stellen. In der Lösung von A T zum Beispiel: Der Download-Handler verarbeitet die Anfrage und gibt die Antwort an den Spider weiter. Der Spider stellt dann eine brandneue Anfrage in seiner Funktion parse_page - Das sind zwei Anfragen für denselben Inhalt.

24
rocktheartsm4l

Ich habe eine benutzerdefinierte Downloader-Middleware verwendet, war aber nicht sehr zufrieden damit, da ich es nicht geschafft habe, dass der Cache damit funktioniert.

Ein besserer Ansatz war die Implementierung eines benutzerdefinierten Download-Handlers.

Es gibt ein Arbeitsbeispiel hier . Es sieht aus wie das:

# encoding: utf-8
from __future__ import unicode_literals

from scrapy import signals
from scrapy.signalmanager import SignalManager
from scrapy.responsetypes import responsetypes
from scrapy.xlib.pydispatch import dispatcher
from Selenium import webdriver
from six.moves import queue
from twisted.internet import defer, threads
from twisted.python.failure import Failure


class PhantomJSDownloadHandler(object):

    def __init__(self, settings):
        self.options = settings.get('PHANTOMJS_OPTIONS', {})

        max_run = settings.get('PHANTOMJS_MAXRUN', 10)
        self.sem = defer.DeferredSemaphore(max_run)
        self.queue = queue.LifoQueue(max_run)

        SignalManager(dispatcher.Any).connect(self._close, signal=signals.spider_closed)

    def download_request(self, request, spider):
        """use semaphore to guard a phantomjs pool"""
        return self.sem.run(self._wait_request, request, spider)

    def _wait_request(self, request, spider):
        try:
            driver = self.queue.get_nowait()
        except queue.Empty:
            driver = webdriver.PhantomJS(**self.options)

        driver.get(request.url)
        # ghostdriver won't response when switch window until page is loaded
        dfd = threads.deferToThread(lambda: driver.switch_to.window(driver.current_window_handle))
        dfd.addCallback(self._response, driver, spider)
        return dfd

    def _response(self, _, driver, spider):
        body = driver.execute_script("return document.documentElement.innerHTML")
        if body.startswith("<head></head>"):  # cannot access response header in Selenium
            body = driver.execute_script("return document.documentElement.textContent")
        url = driver.current_url
        respcls = responsetypes.from_args(url=url, body=body[:100].encode('utf8'))
        resp = respcls(url=url, body=body, encoding="utf-8")

        response_failed = getattr(spider, "response_failed", None)
        if response_failed and callable(response_failed) and response_failed(resp, driver):
            driver.close()
            return defer.fail(Failure())
        else:
            self.queue.put(driver)
            return defer.succeed(resp)

    def _close(self):
        while not self.queue.empty():
            driver = self.queue.get_nowait()
            driver.close()

Angenommen, Ihr Schaber wird "Schaber" genannt. Wenn Sie den genannten Code in eine Datei namens handlers.py im Stammverzeichnis des Ordners "scraper" einfügen, können Sie Ihrer settings.py Folgendes hinzufügen:

DOWNLOAD_HANDLERS = {
    'http': 'scraper.handlers.PhantomJSDownloadHandler',
    'https': 'scraper.handlers.PhantomJSDownloadHandler',
}

Und voilà, der JS analysierte DOM, mit Cache-Speicher, Wiederholungen usw.

6
Ivan Chaer

wie kann Scrapie verwendet werden, um diese dynamischen Daten zu kratzen, damit ich .__ verwenden kann. es?

Ich frage mich, warum niemand die Lösung nur mit Scrapy veröffentlicht hat. 

Lesen Sie den Blogbeitrag des Scrapy-Teams SCRAPING INFINITE SCROLLING PAGES . Das Beispiel scraps http://spidyquotes.herokuapp.com/scroll website, die unendliches Scrollen verwendet. 

Die Idee ist, Entwicklertools Ihres Browsers zu verwenden und die Anforderungen von AJAX zu beachten. Anschließend werden basierend auf diesen Informationen Anforderungen für Scrapy erstellt.

import json
import scrapy


class SpidyQuotesSpider(scrapy.Spider):
    name = 'spidyquotes'
    quotes_base_url = 'http://spidyquotes.herokuapp.com/api/quotes?page=%s'
    start_urls = [quotes_base_url % 1]
    download_delay = 1.5

    def parse(self, response):
        data = json.loads(response.body)
        for item in data.get('quotes', []):
            yield {
                'text': item.get('text'),
                'author': item.get('author', {}).get('name'),
                'tags': item.get('tags'),
            }
        if data['has_next']:
            next_page = data['page'] + 1
            yield scrapy.Request(self.quotes_base_url % next_page)
1
Chankey Pathak

Ich erledige die Ajax-Anfrage mit Selenium und dem Firefox-Web-Treiber. Es ist nicht so schnell, wenn Sie den Crawler als Dämon benötigen, aber viel besser als jede manuelle Lösung. Ich habe ein kurzes Tutorial hier als Referenz geschrieben

1
narko

ja, Scrapy kann dynamische Websites, Webseiten, die über JavaScript gerendert werden, ausrangieren.

Es gibt zwei Ansätze, um diese Art von Websites zu beseitigen.

Zuerst,

sie können splash verwenden, um Javascript-Code zu rendern und dann das gerenderte HTML zu analysieren Sie können das Dokument und das Projekt hier finden. Scrapy splash, git

Zweite, 

Wie alle behaupten, durch die Überwachung von network calls, ja, können Sie den api-Aufruf finden, der die Daten abruft, und den Spott machen, der in Ihrer Scrapy-Spinne aufgerufen wird, um Ihnen zu helfen, gewünschte Daten zu erhalten.

0
ThunderMind