staging.inyokaproject.org

Python3 "Segmentation fault" bei mehrfachem Aufrufen von PyQT5 Web Scraper

Status: Ungelöst | Ubuntu-Version: Server 16.04 (Xenial Xerus)
Antworten |

Capatcha

Anmeldungsdatum:
12. Dezember 2010

Beiträge: 151

Entschuldigt den Titel. Mir ist da leider nichts besseres eingefallen.

Der untere Code beruht zum Großteil auf dieser Antwort: https://stackoverflow.com/a/37755811.

Der Code ist etwas angepasst, damit einfach eine URL übergeben werden kann.

Leider erhalte ich folgende Ausgabe bzw. Fehlermeldung(?), wenn ich versuche mehrmals den Sourcecode von einer Seite zu laden:

1
2
Look at you shinin!
Segmentation fault

Erwartet habe ich, dass "Look at you shinin!" zwei Mal ausgegeben wird.

Das ganze soll später auf einem Raspberry Pi ohne grafische Oberfläche laufen. Momentan habe ich einen Server in einer VM laufen und dort wird das Skript getestet, da dort auch keine grafische Oberfläche zur Verfügung steht und mir so hoffentlich auch alle möglichen Fehler wiederfahren.

Ich hoffe, dass es nur eine Kleinigkeit und es somit leicht zu beheben ist. Leider bin ich noch in den Anfängen der Programmierung und weiß jetzt auch nicht mehr, wonach ich im Internet suchen bzw. worauf ich es eingrenzen kann. Bin schon knapp zwei Tage dran und komme mit google einfach nicht mehr vernünftig weiter.

Hier ist noch das komplette Skript, das über "python3 skript.py" aufgerufen wird.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import sys
from PyQt5.QtCore import QEventLoop
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWebEngineWidgets import QWebEngineView
from pyvirtualdisplay import Display
import bs4 as bs
import requests

def render(url):
    """Fully render HTML, JavaScript and all."""

    class Render(QWebEngineView):
        def __init__(self, url):
            self.html = None
            self.app = QApplication(sys.argv)
            QWebEngineView.__init__(self)
            self.loadFinished.connect(self._loadFinished)
            self.setHtml(requests.get(url).text)
            while self.html is None:
                self.app.processEvents(QEventLoop.ExcludeUserInputEvents | QEventLoop.ExcludeSocketNotifiers | QEventLoop.WaitForMoreEvents)
            self.app.quit()

        def _callable(self, data):
            self.html = data

        def _loadFinished(self, result):
            self.page().toHtml(self._callable)

    return Render(url).html



display = Display(visible=0, size=(800, 600))
display.start()

seiten = {
        "python"        : "https://pythonprogramming.net/parsememcparseface/",
        "python2"       : "https://pythonprogramming.net/parsememcparseface/"
        }

for name in seiten:
        page = render(seiten[name])
        soup = bs.BeautifulSoup(page, 'lxml')
        js_test = soup.find('p', class_='jstest')
        print(str(js_test.text))

noisefloor Team-Icon

Anmeldungsdatum:
6. Juni 2006

Beiträge: 29567

Hallo,

was ist denn das Ziel des ganzen? Im Moment sehe ich keinen Sinn darin, eine externe Webseite zu lokal rendern, damit diese mit BS durchsucht werden kann. Da kannst du die Seite doch direkt von extern mit BS einlesen...

Gruß, noisefloor

Capatcha

(Themenstarter)

Anmeldungsdatum:
12. Dezember 2010

Beiträge: 151

BS lädt zwar die Seite und ich kann mir damit den Quellcode ansehen aber vorhandenes Javascript wird nicht ausgeführt. D.h. wenn über Javascript weitere Tabellen geladen/erstellt werden, kann ich mir diese nur alleine mit BS nicht ansehen/auslesen/weiter verwenden/was auch immer.

noisefloor Team-Icon

Anmeldungsdatum:
6. Juni 2006

Beiträge: 29567

Hallo,

axo. Dafür gibt es Headless-Browser wie z.B. phantom.js oder das Selenium Projekt (für das es auch Python-Bindings gibt), mit denen man dann sowas macht. Der Umweg über Qt und das virtuelle Display ist zu kompliziert.

Gruß, noisefloor

Capatcha

(Themenstarter)

Anmeldungsdatum:
12. Dezember 2010

Beiträge: 151

Das stimmt.

Aber das soll später auf einem ARM-Rechner laufen. PhantomJS habe ich jetzt nur für x86/x64 gefunden. Könnte es selbst kompilieren, habe allerdings keine Erfahrung mit und bevor ich wieder Stunden/Tage damit verschwende, suche ich nach anderen Lösungswegen.

Selenium habe ich auch schon von gelesen bzw. auch schon mit versucht in Kombination mit Firefox. Ich bekomme es gestartet, komme dort allerdings aufgrund von, zumindest für mich nicht verständlichen Fehlermeldungen, nicht weiter. Soweit ich es bisher überall gesehen habe, brauchen die alle X11 Instanzen um zu laufen, auch wenn ich mit übergebe, dass sie headless starten sollen.

Der Umweg über Qt ist der Einzige, der bei mir bisher ein Ergebnis geliefert hat. Leider auch nur ein Mal. Wenn ich versuche von mehr als einer Seite zu laden, bekomme ich den Fehler aus dem ersten Post.

Hier ist die Fehlermeldung, wenn ich es über Selenium und Firefox versuche, falls du mir da helfen kannst:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Traceback (most recent call last):
  File "scrap2.py", line 16, in <module>
    wd = webdriver.Firefox(firefox_options=options, executable_path="./geckodriver")
  File "/home/tester/.local/lib/python3.5/site-packages/selenium/webdriver/firefox/webdriver.py", line 162, in __init__
    keep_alive=True)
  File "/home/tester/.local/lib/python3.5/site-packages/selenium/webdriver/remote/webdriver.py", line 154, in __init__
    self.start_session(desired_capabilities, browser_profile)
  File "/home/tester/.local/lib/python3.5/site-packages/selenium/webdriver/remote/webdriver.py", line 243, in start_session
    response = self.execute(Command.NEW_SESSION, parameters)
  File "/home/tester/.local/lib/python3.5/site-packages/selenium/webdriver/remote/webdriver.py", line 312, in execute
    self.error_handler.check_response(response)
  File "/home/tester/.local/lib/python3.5/site-packages/selenium/webdriver/remote/errorhandler.py", line 242, in check_response
    raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.WebDriverException: Message: connection refused

Das bekomme ich im Terminal angezeigt.

1
2
3
4
5
6
7
8
1519420784557   geckodriver     INFO    geckodriver 0.19.1
1519420784563   geckodriver     INFO    Listening on 127.0.0.1:51529
1519420785703   mozrunner::runner       INFO    Running command: "/usr/bin/firefox" "-marionette" "--headless" "-profile" "/tmp/rust_mozprofile.W4xCQDKbLJeD"
1519420787484   Marionette      ERROR   Error on starting server: [Exception... "Component returned failure code: 0x804b0036 (NS_ERROR_SOCKET_ADDRESS_IN_USE) [nsIServerSocket.initSpecialConnection]"  nsresult: "0x804b0036 (NS_ERROR_SOCKET_ADDRESS_IN_USE)"  location: "JS frame :: chrome://marionette/content/server.js :: MarionetteServer.prototype.start :: line 95"  data: no]
[Exception... "Component returned failure code: 0x804b0036 (NS_ERROR_SOCKET_ADDRESS_IN_USE) [nsIServerSocket.initSpecialConnection]"  nsresult: "0x804b0036 (NS_ERROR_SOCKET_ADDRESS_IN_USE)"  location: "JS frame :: chrome://marionette/content/server.js :: MarionetteServer.prototype.start :: line 95"  data: no]
MarionetteServer.prototype.start@chrome://marionette/content/server.js:95:19
MarionetteComponent.prototype.init@resource://gre/components/marionette.js:217:5
MarionetteComponent.prototype.handle@resource://gre/components/marionette.js:112:5

Der inhalt von geckodriver.log

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
mport bs4 as bs
import sys
import urllib.request
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium import webdriver
from selenium.webdriver.firefox.options import Options
from pyvirtualdisplay import Display

display = Display(visible=0, size=(800, 600))
display.start()

options = Options()
options.add_argument("--headless")
wd = webdriver.Firefox(firefox_options=options, executable_path="./geckodriver")
wd = webdriver.Firefox()
wd.get("https://pythonprogramming.net/parsememcparseface/")
html_page = wd.page_source
wd.quit()

print(str(html_page))

und hier das dazugehörige Skript

Aber ich glaube dafür sollte ich eher ein neues Thema eröffnen.

noisefloor Team-Icon

Anmeldungsdatum:
6. Juni 2006

Beiträge: 29567

Hallo,

Selenium bzw. die Browserengine braucht eine Xserver, richtig. Der einfachste Weg für ein Headless System ist wohl via Xvfb. Habe ich aber auch noch nicht ausprobiert.

Für phantomjs gibt es inoffizielle Ports für ARM.

Gruß, noisefloor

barcc

Avatar von barcc

Anmeldungsdatum:
13. Juli 2007

Beiträge: 696

Das QApplication-Objekt darfst du nur einmal erzeugen, nicht bei jeden Aufruf von render.

Capatcha

(Themenstarter)

Anmeldungsdatum:
12. Dezember 2010

Beiträge: 151

noisefloor schrieb:

Hallo,

Selenium bzw. die Browserengine braucht eine Xserver, richtig. Der einfachste Weg für ein Headless System ist wohl via Xvfb. Habe ich aber auch noch nicht ausprobiert.

Für phantomjs gibt es inoffizielle Ports für ARM.

Gruß, noisefloor

Soweit ich das mitbekommen habe, greift "pyvirtualdisplay" auf xvfb zurück. Es wird also bereits benutzt.

barcc schrieb:

Das QApplication-Objekt darfst du nur einmal erzeugen, nicht bei jeden Aufruf von render.

Danke für den Hinweis.

Kannst du mir da mit Code aushelfen? Ich ging bisher davon aus, dass "self.app.quit()" dafür zuständig ist es zu beenden und es danach wieder geöffnet werden kann.

barcc

Avatar von barcc

Anmeldungsdatum:
13. Juli 2007

Beiträge: 696

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import sys
from PyQt5.QtCore import QEventLoop
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWebEngineWidgets import QWebEngineView
from pyvirtualdisplay import Display
import bs4 as bs
import requests


class Render(QWebEngineView):
    """Fully render HTML, JavaScript and all."""
    def __init__(self, url):
        self.html = None
        QWebEngineView.__init__(self)
        self.loadFinished.connect(self._loadFinished)
        self.setHtml(requests.get(url).text)
        while self.html is None:
            QApplication.instance().processEvents(QEventLoop.ExcludeUserInputEvents |
                                QEventLoop.ExcludeSocketNotifiers | QEventLoop.WaitForMoreEvents)

    def _callable(self, data):
        self.html = data

    def _loadFinished(self, result):
        self.page().toHtml(self._callable)


def render(url):
    return Render(url).html


def main():
    display = Display(visible=0, size=(800, 600))
    display.start()
    app = QApplication(sys.argv)

    seiten = {
        "python"        : "https://pythonprogramming.net/parsememcparseface/",
        "python2"       : "https://pythonprogramming.net/parsememcparseface/"
        }

    for name in seiten:
        page = render(seiten[name])
        soup = bs.BeautifulSoup(page, 'lxml')
        js_test = soup.find('p', class_='jstest')
        print(str(js_test.text))


if __name__ == '__main__':
    main()

Capatcha schrieb:

Kannst du mir da mit Code aushelfen? Ich ging bisher davon aus, dass "self.app.quit()" dafür zuständig ist es zu beenden und es danach wieder geöffnet werden kann.

"self.app.quit()" beendet nur eine mit "app.exec()" eingeleitete Event-Loop, die hast du hier aber nicht. http://doc.qt.io/qt-5.9/qapplication.html

Antworten |