2016-07-25 30 views
1

Ich habe ein Skript, um ~ 1000 Webseiten zu scrappen. Ich verwende Promise.all sie zusammen zu schießen, und es gibt, wenn alle Seiten fertig sind:Ich kann die Versprechungen wegen zu wenig Arbeitsspeicher nicht abschließen

Promise.all(urls.map(url => scrap(url))) 
    .then(results => console.log('all done!', results)); 

Das ist süß und richtig, mit einer Ausnahme - Maschine aus dem Speicher wegen der gleichzeitigen geht Anfragen. Ich benutze jsdom für die Verschrottung, es nimmt schnell ein paar GB Mem, was verständlich ist, wenn man bedenkt, dass es Hunderte von window instanziiert.

Ich habe eine Idee zu beheben, aber ich mag es nicht. Das heißt, ändern Steuerfluss nicht Promise.all zu verwenden, aber Kette meine Versprechen:

let results = {}; 
urls.reduce((prev, cur) => 
    prev 
     .then(() => scrap(cur)) 
     .then(result => results[cur] = result) 
     //^not so nice. 
, Promise.resolve()) 
    .then(() => console.log('all done!', results)); 

Dies ist nicht so gut wie Promise.all ... nicht performant wie es gekettet ist, und Rückgabewerte gespeichert werden müssen für die spätere Verarbeitung.

Irgendwelche Vorschläge? Soll ich den Kontrollfluss verbessern oder sollte ich die Speicherauslastung in scrap() verbessern, oder gibt es eine Möglichkeit, den Knoten auf die Speicherzuweisung zu beschränken?

+0

Ich verstehe nicht, was Sie meinen „Nicht performant wie es * gekettet ist“ – Bergi

+0

Btw, braucht es '.then (() => Schrott zu sein (cur)) ' – Bergi

+0

@Bergi vielleicht liege ich hier falsch. Ich denke, der langsame Teil ist eine Anfrage an eine URL. In der verketteten Version können Sie die nächste Anfrage nur auslösen, nachdem wir alle Schrottarbeiten an der vorherigen URL durchgeführt haben. In der Promise.all-Version können sie alle starten (sendende http-Anfrage ist natürlich async), und behandelt werden, wenn sie zurück sind – Boyang

Antwort

5

Sie versuchen, 1000 Web Scrapes parallel auszuführen. Sie müssen eine Zahl deutlich unter 1000 auswählen und nur N auf einmal ausführen, damit Sie weniger Speicher verbrauchen. Sie können immer noch ein Versprechen verwenden, um zu verfolgen, wann alles erledigt ist.

Bluebird's Promise.map() kann dies für Sie tun, indem Sie einfach einen Parallelitätswert als Option übergeben. Oder Sie könnten es selbst schreiben.

Ich habe eine Idee zu beheben, aber ich mag es nicht. Das heißt, Änderungssteuerung Fluss nicht Promise.all zu verwenden, aber Kette meiner Versprechen:

Was Sie wollen, ist N-Operationen im Flug zur gleichen Zeit. Die Sequenzierung ist ein Spezialfall, bei dem N = 1 oft viel langsamer ist als einige parallel (vielleicht mit N = 10).

Dies ist nicht so gut wie Promise.all ... nicht performant wie es gekettet ist, und Rückgabewerte für eine spätere Verarbeitung gespeichert werden müssen.

Wenn gespeicherte Werte ein Teil Ihres Speicherproblems sind, müssen Sie sie möglicherweise irgendwo auslagern. Sie müssen analysieren, wie viel Speicher die gespeicherten Ergebnisse verwenden.

Irgendwelche Vorschläge? Sollte ich den Kontrollfluss verbessern oder sollte ich Mem-Nutzung in scrap() verbessern, oder gibt es eine Möglichkeit, Knoten-Drossel mem Zuordnung zu lassen?

Verwenden Sie Bluebird's Promise.map() oder schreiben Sie selbst etwas ähnliches. Etwas zu schreiben, das bis zu N Operationen parallel läuft und alle Ergebnisse in Ordnung hält, ist kein Hexenwerk, aber es ist ein bisschen Arbeit, es richtig zu machen. Ich habe es bereits in einer anderen Antwort vorgestellt, aber ich kann es gerade nicht finden. Ich werde weiter schauen.

Gefunden meiner frühere ähnliche Antwort hier: Make several requests to an API that can only handle 20 request a minute

+0

Link zum Referenzcode hinzugefügt, der dieses Problem löst. – jfriend00

+0

Mein Problem wurde behoben! Die zusätzlichen Funktionen von bluebird sind sehr nützlich – Boyang