Als ich Phantomjs aus Pythons Selenbibliothek verwendete, wurde es zum Zombie

Als ich Phantomjs aus Pythons Selenbibliothek verwendete, wurde es zum Zombie

Dieser Artikel ist der 11. Tag von Crawler / Web Scraping Adventskalender 2016.

Im März 2016 wurde ein Buch mit dem Titel Web Scraping with Python veröffentlicht. Es ist ein bisschen Kraft, aber ich habe auch geholfen.

Die in Web Scraping with Python eingeführten Scraping-Tools Selen und Phantomjs, Beim Erstellen eines Scraper besteht das Problem, dass Phantomjs je nach Umgebung nicht geschlossen werden können. Dieses Mal möchte ich den Fall oder das Problem vorstellen.

Der Phantom-Prozess bleibt aus irgendeinem Grund bestehen

Führen Sie den folgenden Code aus. Es wird davon ausgegangen, dass Python, Node, Selen und Phantomjs bereits installiert sind.

run.py

from selenium.webdriver.phantomjs.webdriver import WebDriver

browser = WebDriver()
browser.close()
browser.quit()
print('Finished')

Führen Sie diese run.py aus.

$ python run.py
Finished

Wenn es eine Umgebung gibt, in der es ausgeführt werden kann, wird Finished an die Standardausgabe ausgegeben und beendet.

Überprüfen wir nun, ob der Phantomjs-Prozess vorhanden ist. Wenn Sie Probleme haben, sollte der Phantomjs-Prozess zurückgelassen werden.

$ ps aux | grep phantomjs
sximada          74272 100.0  0.0  2432804   2004 s006  S+    4:41PM   0:00.01 grep --color phantomjs
sximada          74267   0.0  0.7  3647068  59976 s006  S     4:41PM   0:02.01 /usr/local/bin/phantomjs --cookies-file=/var/folders/hx/xp4thw0x7rj15r_2w57_wvfh0000gn/T/tmp2vmrand3 --webdriver=50599

Es ist traurig, nicht wahr?

Es gibt mehrere ähnliche Beiträge zum Stapelüberlauf für dieses Problem, von denen einige wie "pkill phantomjs white" sind. Ich fühle mich ernst.

Versuchen Sie, eine Umgebung zu erstellen, in der Probleme auftreten

In der obigen Betriebsumgebung wurde Folgendes verwendet.

Da die Installation von OS, Python und Node alles andere als wichtig ist, werde ich die Erklärung überspringen. Führen Sie für Selen "Pip Install Selen" normalerweise durch.

Das Problem ist Phantomjs. Da es sich um einen Knoten handelt, möchte ich "npm install phantomjs" ausführen.

Die Heimat von Phantomjs ist https://github.com/ariya/phantomjs. Dies ist jedoch nicht etwas, das Sie mit npm eingeben können. npm install phantomjs installiert https://github.com/Medium/phantomjs. Dies ist der NPM-Wrapper für die Installation von Phantomjs, wie Sie in der Beschreibung des Repositorys sehen können. Ein Wrapper, mit dem Sie Phantomjs mit npm installieren / ausführen können.

Wenn Sie Folgendes tun, werden Sie wahrscheinlich auf Probleme stoßen (obwohl dies in einigen Fällen nicht der Fall ist): Erstellen Sie package.json.

$ npm init .

phantomjs(github.com/Medium/phantomjs)をインストールします。

$ npm install phantomjs

zombie1.py:

from selenium.webdriver.phantomjs.webdriver import WebDriver

browser = WebDriver(executable_path='./node_modules/.bin/phantomjs')  # MODIFIED
browser.close()
browser.quit()

print('Finished')

Führen Sie zombie1.py aus.

$ python zombie1.py
Finished

Überprüfen Sie, ob der Prozess bestehen bleibt.

(py3.5.2) $ ps aux | grep phantomjs
sximada           2426   0.0  0.0  2423392    408 s002  R+    5:51PM   0:00.00 grep --color phantomjs
sximada           2421   0.0  0.6  3645988  46780 s002  S     5:51PM   0:01.56 /usr/local/bin/phantomjs --cookies-file=/var/folders/hx/xp4thw0x7rj15r_2w57_wvfh0000gn/T/tmp8nobgxn7 --webdriver=50641

Hey da ist noch.

Tritt nicht bei Phatomjs auf, die mit Homebrew installiert wurden

Was ist mit Phantomjs, die mit Homebrew installiert wurden?

$ brew install phantomjs

zombie2.py:

from selenium.webdriver.phantomjs.webdriver import WebDriver

browser = WebDriver(executable_path='/usr/local/bin/phantomjs')
browser.close()
browser.quit()

print('Finished')

Führen Sie zombie2.py aus.

$ python zombie2.py
Finished

Überprüfen Sie, ob der Prozess bestehen bleibt.

$ ps aux | grep phantomjs
sximada           3530   0.0  0.0  2432804    796 s002  R+    6:11PM   0:00.00 grep --color phantomjs
$

Es sind keine mehr übrig. Was ist der Unterschied?

Phantomjs in Homebrew ist eine ausführbare Binärdatei

Wenn Sie mit dem Befehl file prüfen, ist phantomjs / usr / local / bin / phantomjs, das von homebrew eingegeben wurde, eine ausführbare Binärdatei. Der Einfachheit halber ist diese direkte Methode die Binärversion.

$ file /usr/local/bin/phantomjs
/usr/local/bin/phantomjs: Mach-O 64-bit executable x86_64

Das von npm eingegebene Phantom js ist ein Knoten-js-Skript

Wenn Sie dagegen die mit npm mit dem Befehl file eingegebenen Phantomjs überprüfen, handelt es sich um eine Textdatei. Der Einfachheit halber ist diese direkte Methode die npm-Version.

$ file node_modules/.bin/phantomjs
node_modules/.bin/phantomjs: a /usr/bin/env node script text executable, ASCII text

Der Inhalt wird im Skript von nodejs wie folgt beschrieben.

#!/usr/bin/env node

/**
 * Script that will execute the downloaded phantomjs binary.  stdio are
 * forwarded to and from the child process.
 *
 * The following is for an ugly hack to avoid a problem where the installer
 * finds the bin script npm creates during global installation.
 *
 * {NPM_INSTALL_MARKER}
 */

var path = require('path')
var spawn = require('child_process').spawn

var binPath = require(path.join(__dirname, '..', 'lib', 'phantomjs')).path

var args = process.argv.slice(2)

// For Node 0.6 compatibility, pipe the streams manually, instead of using
// `{ stdio: 'inherit' }`.
var cp = spawn(binPath, args)
cp.stdout.pipe(process.stdout)
cp.stderr.pipe(process.stderr)
process.stdin.pipe(cp.stdin)

cp.on('error', function (err) {
  console.error('Error executing phantom at', binPath)
  console.error(err.stack)
})

cp.on('exit', function(code){
  // Wait few ms for error to be printed.
  setTimeout(function(){
    process.exit(code)
  }, 20)
});

process.on('SIGTERM', function() {
  cp.kill('SIGTERM')
  process.exit(1)
})

Es scheint, dass Sie die Binärdatei als untergeordneten Prozess mit "var cp = spawn (binPath, args)" ausführen. Gegen Ende gibt es einen Handler für "SIGTERM", und wenn SIGTERM kommt, scheint er "SIGTERM" an den untergeordneten Prozess zu senden und zu beenden.

Die Binärversion hat eine zweistufige Prozessstruktur und die npm-Installationsversion hat eine dreistufige Prozessstruktur.

Wenn Sie Selen mit der Binärversion und der npm-Version starten, hat der Prozess die folgende Struktur.

Binäre Version:

$ pstree 3812
-+= 03812 sximada python zombie2.py
 \--- 03815 sximada /usr/local/bin/phantomjs --cookies-file=/var/folders/hx/xp4thw0x7rj15r_2w57_wvfh0000gn/T/tmpu8trzjh0 --webdriver=50761

npm Version:

$ pstree 3701
-+= 03701 sximada python zombie1.py
 \-+- 03704 sximada node ./node_modules/.bin/phantomjs --cookies-file=/var/folders/hx/xp4thw0x7rj15r_2w57_wvfh0000gn/T/tmp9c0y1sj7 --webdriver=50747
   \--- 03705 sximada /usr/local/bin/phantomjs --cookies-file=/var/folders/hx/xp4thw0x7rj15r_2w57_wvfh0000gn/T/tmp9c0y1sj7 --webdriver=50747

Wenn man sich den Prozess anschaut, scheint es, dass immer noch Enkelkinder am Ende hängen.

$ pstree 4537
-+= 04537 sximada python zombie1.py
 \-+- 04540 sximada node ./node_modules/.bin/phantomjs --cookies-file=/var/folders/hx/xp4thw0x7rj15r_2w57_wvfh0000gn/T/tmpg1eq1xst --webdriver=51406
   \--- 04541 sximada /usr/local/bin/phantomjs --cookies-file=/var/folders/hx/xp4thw0x7rj15r_2w57_wvfh0000gn/T/tmpg1eq1xst --webdriver=51406
$ ps aux | grep phantomjs
sximada           4554   0.0  0.0  2432804    632 s003  R+    6:50PM   0:00.00 grep --color phantomjs
sximada           4541   0.0  0.6  3646488  47532 s002  S     6:49PM   0:05.84 /usr/local/bin/phantomjs --cookies-file=/var/folders/hx/xp4thw0x7rj15r_2w57_wvfh0000gn/T/tmpg1eq1xst --webdriver=51406

Das Phänomen manuell reproduzieren

Nach dem Ausführen von node ./node_modules/.bin/phantomjs --cookies-file = / var / folders / hx / xp4thw0x7rj15r_2w57_wvfh0000gn / T / tmpg1eq1xst --webdriver = 51406 Versuchen Sie es mit "kill -KILL".

$ node ./node_modules/.bin/phantomjs --cookies-file=/var/folders/hx/xp4thw0x7rj15r_2w57_wvfh0000gn/T/tmpoazqtmx7 --webdriver=51448
[INFO  - 2016-12-10T09:57:42.829Z] GhostDriver - Main - running on port 51448

Wenn der Prozess beginnt, töte ihn mit SIGKILL.

$ ps -ef | grep phantom
  501  4662   763   0  6:57PM ttys002    0:00.12 node ./node_modules/.bin/phantomjs --cookies-file=/var/folders/hx/xp4thw0x7rj15r_2w57_wvfh0000gn/T/tmpoazqtmx7 --webdriver=51448
  501  4663  4662   0  6:57PM ttys002    0:01.73 /usr/local/bin/phantomjs --cookies-file=/var/folders/hx/xp4thw0x7rj15r_2w57_wvfh0000gn/T/tmpoazqtmx7 --webdriver=51448
  501  4666   764   0  6:57PM ttys003    0:00.00 grep --color phantom
$ kill -KILL 4662
$ ps -ef | grep phantom
  501  4663     1   0  6:57PM ttys002    0:03.63 /usr/local/bin/phantomjs --cookies-file=/var/folders/hx/xp4thw0x7rj15r_2w57_wvfh0000gn/T/tmpoazqtmx7 --webdriver=51448
  501  4670   764   0  6:58PM ttys003    0:00.00 grep --color phantom

Ich habe es reproduziert. Es ist deswegen. Sendet Selen SIGKILL beim Herunterfahren?

Verhalten von selenium.webdriver.phantomjs.webdriver.Webdriver.close ()

Von hier aus verwenden wir pdb.set_trace (), um herauszufinden, was Python mit dem Debugger tut. Lassen Sie uns pdb in zombie1.py einfügen und sehen, wie es funktioniert.

from selenium.webdriver.phantomjs.webdriver import WebDriver

browser = WebDriver(executable_path='./node_modules/.bin/phantomjs')
import pdb; pdb.set_trace()
browser.close()
browser.quit()

print('Finished')

Bei Betrachtung des Vorgangs scheint eine HTTP-Anforderung an selenium / webdriver / remote / remote_connection.py (470) _request () gesendet zu werden.

464                 if password_manager:
465                     opener = url_request.build_opener(url_request.HTTPRedirectHandler(),
466                                                       HttpErrorHandler(),
467                                                       url_request.HTTPBasicAuthHandler(password_manager))
468                 else:
469                     opener = url_request.build_opener(url_request.HTTPRedirectHandler(),
470                                                       HttpErrorHandler())
471  ->             resp = opener.open(request, timeout=self._timeout)
472                 statuscode = resp.code

Die Anfrage, die wir senden, lautet wie folgt.

-> request = Request(url, data=body.encode('utf-8'), method=method)
(Pdb) p url
'http://127.0.0.1:51524/wd/hub/session/57277cb0-bec1-11e6-a0b1-31edd9b29650/window'
(Pdb) p body
'{"sessionId": "57277cb0-bec1-11e6-a0b1-31edd9b29650"}'
(Pdb) p method
'DELETE'
(Pdb)

Davon abgesehen scheint es nichts Besonderes zu tun.

Verhalten von selenium.webdriver.phantomjs.webdriver.Webdriver.quit ()

Was ist mit quit ()?

Während des Prozesses "self.service.stop ()" von selenium / webdriver / phantomjs / webdriver.py (76) quit () gab es einen Ort, an den SIGTERM und SIGKILL gesendet wurden.

selenium/webdriver/common/service.py(154)stop():

(Pdb) list
149                             stream.close()
150                         except AttributeError:
151                             pass
152                     self.process.terminate()
153                     self.process.kill()
154  ->                 self.process.wait()
155                     self.process = None
156             except OSError:
157                 # kill may not be available under windows environment
158                 pass
159

Soweit ich den Code gelesen habe, sende ich SIGKILL nach dem Senden von SIGTERM.

Die npm-Version hatte jedoch einen SIGTERM-Signalhandler. Ist dieses SIGKILL überhaupt notwendig? Kommentieren Sie als Test self.process.kill () aus und führen Sie es aus.

selenium/webdriver/common/service.py:

                self.process.terminate()
                # self.process.kill()   ##Auskommentieren
                self.process.wait()

Ich werde das machen.

$ python  zombie1.py
Finished
$ ps aux | grep phantomjs
sximada           5270   0.0  0.0  2424612    500 s002  R+    7:34PM   0:00.00 grep --color phantomjs
$

Es sind keine Prozesse mehr vorhanden. Es scheint, dass der untergeordnete Prozess beendet wurde und der Enkelprozess aufgrund von "self.process.kill ()" bestehen bleibt.

Die Ursache ist, dass SIGKILL empfangen wird, bevor die SIGTERM-Handler-Verarbeitung des untergeordneten Prozesses abgeschlossen ist.

Anscheinend hat die npm-Version von phantomjs SIGTERM gesendet, das von self.process.terminate () gesendet wurde. Es scheint, dass der Handler von SIGKILL getötet wurde, bevor SIGTERM zum Enkelprozess geschickt wurde. Ich fühle mich wie ich mitten in der Grenze festsitze.

Dies kann vermieden werden, indem Phantomjs ohne Verwendung von npm installiert werden

Es scheint gut zu sein, wenn Sie nicht durch npm gehen, also nicht npm install phantomjs, Sie können es mit Homebrew installieren oder von http://phantomjs.org/download.html löschen.

Ich habe das Gefühl, einen Umweg gemacht zu haben. Ich bete, dass niemand den gleichen Umweg macht.

Recommended Posts

Als ich Phantomjs aus Pythons Selenbibliothek verwendete, wurde es zum Zombie
Phantomjs und Selen
Ich habe den Deep Learning Library Chainer installiert und verwendet
Erstellen Sie mit Python einen Entscheidungsbaum aus 0 und verstehen Sie ihn (3. Datenanalysebibliothek Pandas Edition)
Ich habe einen Server mit Python-Socket und SSL erstellt und versucht, über den Browser darauf zuzugreifen
Holen Sie sich ein Bild von einer Webseite und ändern Sie die Größe
[Python] Ich habe das Spiel von pip installiert und versucht zu spielen
Ein Memorandum beim automatischen Erwerb mit Selen
Ich habe versucht, mit Selenium und Python einen regelmäßigen Ausführungsprozess durchzuführen
Ich möchte eine Pipfile erstellen und im Docker wiedergeben
Ich habe einen Chat-Chat-Bot mit Tensor2Tensor erstellt und diesmal hat es funktioniert