Quand j'ai utilisé phantomjs de la bibliothèque de sélénium de Python, il est devenu zombie

Quand j'ai utilisé phantomjs de la bibliothèque de sélénium de Python, il est devenu zombie

Cet article est le 11ème jour du Calendrier de l'Avent Crawler / Web Scraping 2016.

En mars 2016, un livre intitulé Web Scraping with Python a été publié. C'est un peu de puissance, mais j'ai aussi aidé.

Les outils de grattage sélénium et phantomjs introduits dans Web scraping with Python, Lors de la création d'un grattoir, il y a un problème en raison du fait que phantomjs ne peut pas être fermé en fonction de l'environnement. Cette fois, je voudrais présenter le cas ou le problème.

Le processus phantomjs reste pour une raison quelconque

Exécutez le code suivant. On suppose que Python, Node, sélénium et phantomjs sont déjà installés.

run.py

from selenium.webdriver.phantomjs.webdriver import WebDriver

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

Exécutez ce run.py.

$ python run.py
Finished

S'il existe un environnement dans lequel il peut être exécuté, il affichera Terminé sur la sortie standard et quittera.

Vérifions maintenant si le processus phantomjs existe. Si vous rencontrez des problèmes, le processus phantomjs doit être laissé pour compte.

$ 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

C'est triste, n'est-ce pas.

Il y a plusieurs articles similaires sur stackoverflow pour ce problème, dont certains sont comme pkill phantomjs white. J'ai l'impression que c'est sérieux.

Essayez de créer un environnement où des problèmes surviennent

L'environnement d'exploitation ci-dessus utilisait ce qui suit.

Étant donné que l'installation du système d'exploitation, de Python et de Node est loin d'être essentielle, je vais sauter l'explication. Pour le sélénium, faites "pip install selenium" normalement.

Le problème est phantomjs. Puisqu'il s'agit d'un nœud, je veux faire npm install phantomjs.

La maison de phantomjs est https://github.com/ariya/phantomjs. Mais ce n'est pas quelque chose que vous pouvez mettre en place avec npm. npm install phantomjs installe https://github.com/Medium/phantomjs. C'est le wrapper NPM pour l'installation de phantomjs, comme vous pouvez le voir dans la description du référentiel. Un wrapper qui vous permet d'installer / d'exécuter phantomjs avec npm.

En fait, vous pouvez rencontrer des problèmes (bien que dans certains cas ce ne soit pas le cas): Créez 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')

Exécutez zombie1.py.

$ python zombie1.py
Finished

Vérifiez si le processus persiste.

(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

Hé, il reste.

Ne se produit pas avec les phatomjs installés avec homebrew

Qu'en est-il des phantomjs installés avec homebrew?

$ 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')

Exécutez zombie2.py.

$ python zombie2.py
Finished

Vérifiez si le processus persiste.

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

Il n'en reste plus. Quelle est la différence?

Phantomjs mis en homebrew est un fichier binaire exécutable

Si vous vérifiez avec la commande file, phantomjs / usr / local / bin / phantomjs entré par homebrew est un fichier binaire exécutable. Pour plus de commodité, cette méthode directe est la version binaire.

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

Le phantom js entré par npm est un script node js

Par contre, si vous vérifiez les phantomjs saisis avec npm avec la commande file, c'est un fichier texte. Pour plus de commodité, cette méthode directe est la version npm.

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

Le contenu est décrit comme suit dans le script de nodejs.

#!/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)
})

Il semble que vous exécutiez le binaire en tant que processus enfant avec var cp = spawn (binPath, args). Il y a un gestionnaire pour SIGTERM vers la fin, et quand SIGTERM arrive, il semble envoyer SIGTERM au processus fils et quitter.

La version binaire a une structure de processus en deux étapes, et la version d'installation npm a une structure de processus en trois étapes.

Si vous démarrez sélénium en utilisant la version binaire et la version npm, le processus aura la structure suivante.

Version binaire:

$ 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

version npm:

$ 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

En regardant le processus, il semble qu'il y ait encore des petits-enfants suspendus au fond de cela.

$ 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

Reproduire manuellement le phénomène

Après avoir exécuté node ./node_modules/.bin/phantomjs --cookies-file = / var / folders / hx / xp4thw0x7rj15r_2w57_wvfh0000gn / T / tmpg1eq1xst --webdriver = 51406 Essayez kill -KILL sur ce processus.

$ 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

Lorsque le processus démarre, tuez-le avec 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

Je l'ai reproduit. C'est à cause de ça. Le sélénium envoie-t-il SIGKILL à l'arrêt?

Comportement de selenium.webdriver.phantomjs.webdriver.Webdriver.close ()

À partir de là, nous utiliserons pdb.set_trace () pour découvrir ce que fait Python en utilisant le débogueur. Mettons pdb dans zombie1.py et voyons comment cela fonctionne.

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')

En regardant l'opération, il semble qu'une requête HTTP soit envoyée à selenium / webdriver / remote / remote_connection.py (470) _request ().

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

La demande que nous envoyons est la suivante.

-> 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)

À part cela, il ne semble rien faire de particulier.

Comportement de selenium.webdriver.phantomjs.webdriver.Webdriver.quit ()

Et quit ()?

Dans le processus de self.service.stop () de selenium / webdriver / phantomjs / webdriver.py (76) quit (), il y avait un endroit où SIGTERM et SIGKILL ont été envoyés.

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

Pour autant que j'ai lu le code, j'envoie SIGKILL après avoir envoyé SIGTERM.

Cependant, la version npm avait un gestionnaire de signaux SIGTERM. Ce SIGKILL est-il nécessaire en premier lieu? Comme test, commentez self.process.kill () et exécutez-le.

selenium/webdriver/common/service.py:

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

Je le ferai.

$ 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
$

Il n'y a plus de processus. Il semble que le processus enfant a été tué et que le processus petit-enfant reste à cause de self.process.kill ().

La cause est que SIGKILL est reçu avant que le traitement du gestionnaire SIGTERM du processus enfant ne soit terminé.

Apparemment, la version npm de phantomjs a envoyé SIGTERM envoyé par self.process.terminate (). Il semble que le gestionnaire a été tué par SIGKILL avant d'envoyer SIGTERM au processus petit-enfant. J'ai l'impression d'être coincé au milieu de la frontière.

Cela peut être évité en installant phantomjs sans utiliser npm

Cela semble bien si vous ne passez pas par npm, alors n'installez pas npm phantomjs, Vous pouvez l'installer avec homebrew ou le supprimer depuis http://phantomjs.org/download.html.

J'ai l'impression d'avoir fait un détour. Je prie pour que personne ne fasse le même détour.

Recommended Posts

Quand j'ai utilisé phantomjs de la bibliothèque de sélénium de Python, il est devenu zombie
phantomjs et sélénium
J'ai installé et utilisé la bibliothèque Deep Learning Chainer
Créez un arbre de décision à partir de zéro avec Python et comprenez-le (3. Bibliothèque d'analyse de données édition Pandas)
J'ai créé un serveur avec socket Python et ssl et j'ai essayé d'y accéder depuis le navigateur
Récupérer une image d'une page Web et la redimensionner
[Python] J'ai installé le jeu depuis pip et j'ai essayé de jouer
Un mémorandum lors de l'acquisition automatique avec du sélénium
J'ai essayé de faire un processus d'exécution périodique avec Selenium et Python
Je veux créer un fichier pip et le refléter dans le menu fixe
J'ai créé un chat chat bot avec Tensor2Tensor et cette fois cela a fonctionné