Skript macht Probleme auf anderem Rechner

Python in C/C++ embedden, C-Module, ctypes, Cython, SWIG, SIP etc sind hier richtig.
Antworten
Pythagon
User
Beiträge: 52
Registriert: Mittwoch 3. Juli 2019, 18:21

Hallo Forumsgemeinde,

das folgende Skript zum Abruf von CVS Dateien läuft auf meinem Rechner reibungslos im Zusammenspiel mit einem VBA Tool. Nun wollte ich die Kombi bei einem Bekannten auf dem Rechner zum Laufen bringen aber es klappt nicht so richtig. Meine Fehlersuche hat folgendes gezeigt:

Starte ich das VBA Tool und lasse ich die übergebenen Parameter mittels Print in Python ausgeben dann übernimmt VBA diese wieder nach dem der Python Code durchgelaufen ist. Die Parameterübergabe klappt also in beide Richtungen. Ich habe auch sichergestellt, dass der http-Request funktioniert indem ich den Python Code alleine habe laufen lassen mit festen Werten für die Variablen. Python gibt die gewünschten CSV Dateien ohne Probleme aus. Das heißt der Python Code funktioniert "eigentlich" astrein.

Starte ich dann aber das VBA Tool in der Erwartung nun, wie gewohnt, mittels Print Ausgabe die CSV Dateien in VBA zu übernehmen dann geht zwar kurz die Python Shell auf aber sie schließt sich sofort wieder und von Daten keine Spur. Auf meinem heimischen Rechner bleibt die Shell in der Regel ein paar Sekunden offen, so lange bis die CSV Dateien ausgegeben wurden. In VBA ist bereits eine Warteschleife integriert, damit der Code wartet bis die CSV Dateien vorhanden sind aber hochsetzen der Wartezeit hat ebenfalls nichts gebracht.

Im folgenden der Python Code:

Code: Alles auswählen

#Get CSV-Files

import sys
#
arglist = sys.argv # Übergebene Werte aus VBA
#
txt = len(sys.argv)

#print (sys.argv[0])
#print (sys.argv[1])
#print (sys.argv[2])
#print (sys.argv[3])
#print (sys.argv[4])

symbol = sys.argv[1]
spoint = sys.argv[2]
epoint = sys.argv[3]
value = sys.argv[4]

import requests

session = requests.session()

if str(value) == ("Div"):
    url = "https://de.finance.yahoo.com/quote/" + symbol + "/history?period1=" + spoint + "&period2=" + epoint + "&interval=div%7Csplit&filter=div&frequency=1d"
else:
    url = "https://de.finance.yahoo.com/quote/" + symbol + "/history?period1=" + spoint + "&period2=" + epoint + "&interval=1d&filter=history&frequency=1d" 


page = session.get(url)

cookiecrumb = page.cookies.get


while True:

    if str(value) == ("Div"):
        link = "https://query1.finance.yahoo.com/v7/finance/download/" + symbol + "?period1=" + spoint + "&period2=" + epoint + "&interval=1d&events=div&crumb=%s/" % (cookiecrumb)
    else:
        link = "https://query1.finance.yahoo.com/v7/finance/download/" + symbol + "?period1=" + spoint + "&period2=" + epoint + "&interval=1d&events=history&crumb=%s/" % (cookiecrumb)
      
        
    csvfile = session.post(link)
    substring = "Method Not Allowed"
    string = csvfile.text
    if not substring in string:
        break
   
print(csvfile.text)

session.close
Da vermutlich die Frage kommen wird wie ich die CSV Daten in VBA übernehme dazu noch der entsprechende Teil des VBA Codes:

Code: Alles auswählen

' Der folgende Code übernimmt alles aus Python was dort mit "print" ausgegeben wird

Dim DivSheet As Worksheet

Set DivSheet = Workbooks(Projektname).Worksheets("Dividenden")

With DivSheet

.Activate
.Range("A1:B10000").ClearContents
.Range("A1:B10000").ClearFormats

End With


Dim output As Object, sLine As String, Zeile As Long

Zeile = 0

Set output = oExec.StdOut

    While Not output.AtEndOfStream
        Zeile = Zeile + 1
        sLine = output.ReadLine
        If sLine <> "" Then ActiveWorkbook.Worksheets("Dividenden").Cells(Zeile, 1).Value = (sLine)
    Wend
    

' CSV Format umwandlen (Text to Columns)

With DivSheet

.Activate
.Columns(1).Select
.Range("A1:A10000").TextToColumns Destination:=Range("A1"), DataType:=xlDelimited, _
        TextQualifier:=xlDoubleQuote, ConsecutiveDelimiter:=False, Tab:=False, _
        Semicolon:=False, Comma:=True, Space:=False, Other:=False, FieldInfo _
        :=Array(Array(1, 4), Array(2, 1), Array(3, 1), Array(4, 1), Array(5, 1), Array(6, 1), _
        Array(7, 1)), DecimalSeparator:=".", ThousandsSeparator:=" ", _
        TrailingMinusNumbers:=True

End With
Hat jemand eine Idee weshalb das Ganze auf einem anderen Rechner Probleme machen könnte? Könnte es an unterschiedlichen Python Versionen liegen? Wobei ich bei mir und bei meinem Bekannten jeweils Python 3.7.4 installiert habe. Aber mein Dateipfad lautet auf "...\Phython37-32\..." und bei ihm nur auf "...\Phyton37\...".

VG,
Marc
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

Woher kommt denn oExec? Der entscheidende Teil fehlt.

Zum Pythoncode:
`arglist` wird nie benutzt. `txt` wird nie benutzt, und ist für eine Länge einer Liste auch ein äußerst schlechter Variablenname.
value ist schon ein String, das Umwandeln also unnötig. Ein literaler String braucht nicht geklammert zu werden. Strings stückelt man nicht mit + zusammen, sondern nutzt Stringformatierung. Noch besser ist es, die Möglichkeiten zu Query-Strings von Requests zu nutzen.
`session.close` sollte man auch aufrufen.

Dass ein Fenster aufgeht, finde ich komisch, aber vielleicht ist das ja unter Windows so. Schau Dir auch mal oExec.StdErr an.
Pythagon
User
Beiträge: 52
Registriert: Mittwoch 3. Juli 2019, 18:21

Hallo Sirius,

'arglist' und 'txt' hatte ich zu Testzwecken in dem Skript. Bitte ignorieren. Ich hatte das eben versehentlich nicht gelöscht vorm Posten.

oExec ist das Objekt mit dem ich die Python Shell aus VBA heraus starte:

Code: Alles auswählen

Dim pfad As String

pfad = ActiveWorkbook.Path & "\Scraper.py"

Dim wsh As Object

Set wsh = VBA.CreateObject("WScript.Shell")

Dim oExec As Object

Set oExec = wsh.Exec("C:\Users\Herbert_Meisel\AppData\Local\Programs\Python\Python37-32\python.exe" & " " & pfad & " " & Ticker & " " & UnixStart & " " & UnixEnd & " " & Value & "")
Deine Tipps werde ich mir ansehen und schauen was ich verbessern kann. Das war mein erstes Python Skript und ich hatte das nur geschrieben weil es mit VBA absolut nicht mehr möglich war an diese CSV dateien heran zu kommen.
Benutzeravatar
__blackjack__
User
Beiträge: 12984
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Pythagon: Upsi, bin etwas spät dran — einiges wurde schon gesagt.

Zum Python-Code: Importe gehören an den Anfang des Moduls.

`arglist` und `txt` werden definiert, aber nirgends verwendet. `txt` ist auch ein sehr komischer Name für die Länge einer Liste.

Man sollte keine Abkürzungen und/oder kryptische Prä-/Suffixe bei Namen verwenden. Falls `spoint` und `epoint` eigentlich `start_point` und `end_point` heissen sollte, dann sollte man die auch so nennen.

Die `Session` wird nicht geschlossen. Man muss `close()` auch *aufrufen*. Das passiert nicht wie in VBA bei Prozeduren ohne Argumente automatisch, nur weil man den Namen hinschreibt. Allerdings wäre es sowieso besser wenn man ``with`` benutzt, denn `Session`-Objekte sind Kontextmanager.

Die Elemente von `sys.argv` sind bereits Zeichenketten, da braucht man nicht noch einmal `str()` mit aufrufen. Die werden dadurch nicht ”zeichenkettiger” oder so.

Klammern um literale Werte oder einzelne Namen machen keinen Sinn. Also beispielsweise ``("Div")`` oder ``(cookiecrumb)``.

Zusammenstückeln von Werten und Zeichenkettenliteralen per ``+`` ist eher BASIC als Python. In Python gibt es dafür Zeichenkettenformatierung mit der `format()`-Methode und ab Python 3.6 f-Zeichenkettenliterale. Den ``%``-Operator würde ich in neuen Programmen nicht mehr dafür verwenden. ``+`` und ``%`` im gleichen Ausdruck zu vermischen ist schräg.

Die Query-Parameter würde ich hier nicht selbst schon an die URL basteln. Das kann `requests` aus einem Wörterbuch erstellen. Dann kann man am Code auch leichter ablesen wo die Unterschiede liegen.

`page` und `csvfile` sind keine passenden Namen für ein `Response`-Objekt.

Im Body (Text) der Antwort nach ”Method Not Allowed” zu suchen ist nicht robust. Das kann in der Gross-/Kleinschreibung variieren, in einer anderen Sprache als Englisch im Text stehen, oder was ganz anderes sein. Für solche Fälle kennt HTTP eindeutige, numerische Codes und `requests` Konstanten für diese Codes. Wobei es etwas komisch ist das bei allen anderen Fehlercodes trotzdem der Textkörper der Antwort gesendet wird, auch wenn das dann keine CSV-Daten sind‽

Zwischenstand:

Code: Alles auswählen

#!/usr/bin/env python3
import sys

import requests
from requests.status_codes import codes


def main():
    symbol, start_point, end_point, value = sys.argv[1:5]

    with requests.session() as session:
        parameters = {
            "period1": start_point,
            "period2": end_point,
            "frequency": "1d",
        }
        if value == "Div":
            parameters["interval"] = "div|split"
            parameters["filter"] = "div"
        else:
            parameters["interval"] = "1d"
            parameters["filter"] = "history"

        response = session.get(
            f"https://de.finance.yahoo.com/quote/{symbol}/history",
            params=parameters,
        )
        response.raise_for_status()
        #
        # FIXME This is wrong!
        #
        cookiecrumb = response.cookies.get

        while True:
            response = session.post(
                f"https://query1.finance.yahoo.com/v7/finance/download/{symbol}",
                params={
                    "period1": start_point,
                    "period2": end_point,
                    "interval": "1d",
                    "events": "div" if value == "Div" else "history",
                    "crumb": str(cookiecrumb),
                },
            )
            #
            # TODO Just METHOD_NOT_ALLOWED?  Other error codes are okay‽
            #
            if response.status_code != codes.METHOD_NOT_ALLOWED:
                break

        print(response.text)


if __name__ == "__main__":
    main()
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Pythagon
User
Beiträge: 52
Registriert: Mittwoch 3. Juli 2019, 18:21

Vielen Dank an beide! Mein Code ist natürlich suboptimal geschrieben, da ich mich mit Python nur wenig auskenne. Aber geht ihr davon aus, dass entsprechende Anpassungen dazu führen würden, dass der Code bei meinem Bekannten auf dem Rechner ein positives Resultat bringt? Falls ja, bei welchen Teile meines Codes seht ihr die mögliche Hauptursache dafür?
Benutzeravatar
__blackjack__
User
Beiträge: 12984
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Pythagon: Nicht die Ursache aber der Punkt an dem ich als erstes ansetzen würde: Ein externer Programmaufruf sollte so abgesichert sein das man erkennen kann das er nicht erfolgreich war und in dem Fall dann auch irgendwie die Ausgaben von Standardausgabe und Standardfehlerausgabe zur Verfügung stellen. Beim Auslesen davon muss man aufpassen/berücksichtigen, dass man beide Ausgabekanäle so abfragt das da keine Verklemmung stattfinden kann. Dann sollte es relativ trivial sein die Ursache heraus zu bekommen. Wie man das mit Python macht weiss ich, aber VBA — keine Ahnung.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Pythagon
User
Beiträge: 52
Registriert: Mittwoch 3. Juli 2019, 18:21

Hallo Black_Jack,

dass der Aufruf des externen Programms, in diesem Fall Python, erfolgreich ist hatte ich ja bereits verfiziert insofern, dass ich sehen konnte, dass die Parameter erfolgreich übergeben und von Python wieder ausgegeben bzw. zurückgegeben wurden an VBA.

Ich denke ich werde mal mit dem pdb Debugger versuchen das Skript zu stoppen und Step by Step zu schauen wo genau ein Fehler auftritt.
Benutzeravatar
__blackjack__
User
Beiträge: 12984
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Pythagon: Mit erfolgreich meine ich auch dass es durchläuft und das gewünschte Ergebnis liefert und nicht nur das es gestartet werden kann. Schreib da einfach mal ``1 / 0`` rein und schau ob/wie der daraufhin auftretende `ZeroDivisionError` in Deinem VBA-Programm ankommt. Wenn Du den da nicht als solchen erkennen/ablesen kannst, wirst Du auch keine anderen Probleme erkennen/ablesen können. Das wäre aber für die Fehlersuche sehr hilfreich/wichtig.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Pythagon
User
Beiträge: 52
Registriert: Mittwoch 3. Juli 2019, 18:21

@Black_Jack: Der ZeroDivisionError wurde nicht an VBA übergeben. Liegt das u.U. daran, dass ich mit StdOut und nicht mit StdErr arbeite?

Das Debuggen mit pdb.set_trace() klappt übrigens nicht wie ich das erhofft hatte. Der Code hält zwar an aber da ich das Skript über Fremdaufruf aus VBA heraus und nicht im stand alone Modus bzw. im Python Editor gestartet habe bekomme ich keine Eingabeaufforderung. Ich sehe nur einen blinkenden Cursor oben links in der Python Shell und der Code pausiert aber ich kann nichts eingeben.

Gibt es eine Möglichkeit den Code auch bei Fremdaufruf mit pdb zu debuggen?
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

Wie ich schon ganz zu Anfang geschrieben hatte, solltest du genau dieses StdErr in VBA ausgeben lassen, um den Fehler zu sehen.
Benutzeravatar
__blackjack__
User
Beiträge: 12984
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Pythagon: Du könntest `rpdb` versuchen. Aber wie gesagt würde ich ja eher die aufrufende Seite so ändern das alle Ausgaben und der Rückgabecode dort ankommen, ausgegeben, und ausgewertet werden können.

Ich persönlich verwende eigentlich keinen Schitt-für-Schritt-Debugger sondern lieber ein paar `print()`-Aufrufe oder Logging, und manchmal auch das `q`-Modul.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Pythagon
User
Beiträge: 52
Registriert: Mittwoch 3. Juli 2019, 18:21

Sirius3 hat geschrieben: Samstag 8. Februar 2020, 11:10 Wie ich schon ganz zu Anfang geschrieben hatte, solltest du genau dieses StdErr in VBA ausgeben lassen, um den Fehler zu sehen.
Ok, dann hatte ich das anfangs falsch verstanden. Ich hatte es so interpretiert als wolltest Du die Rückgabe der ausgegebenen CSV Daten über StdErr vornehmen, anstelle von StdOut. Sobald ich wieder am Rechner meine Bekannten bin werde ich versuchen ob mir damit irgendeine Fehlermeldung angezeigt wird.
Pythagon
User
Beiträge: 52
Registriert: Mittwoch 3. Juli 2019, 18:21

__blackjack__ hat geschrieben: Samstag 8. Februar 2020, 11:15 @Pythagon: Du könntest `rpdb` versuchen. Aber wie gesagt würde ich ja eher die aufrufende Seite so ändern das alle Ausgaben und der Rückgabecode dort ankommen, ausgegeben, und ausgewertet werden können.

Ich persönlich verwende eigentlich keinen Schitt-für-Schritt-Debugger sondern lieber ein paar `print()`-Aufrufe oder Logging, und manchmal auch das `q`-Modul.
Nachdem ich nun ein wenig recherchiert habe zum Thema Remote Debugging stellt sich mir zunächst mal die Frage ob beispielsweise Visual Studio so angewendet werden kann, dass sich durch einen Haltepunkt bzw. Aufruf im Code automatisch Visual Studio öffnen lässt. Eine konkrete Antwort darauf habe ich in keinem Tutorial gefunden. Ich muss den Code ja Debuggen unter live Bedingungen, da er im Stand Alone Modus reibungslos läuft aber nicht bei Fremdaufruf.
Benutzeravatar
__blackjack__
User
Beiträge: 12984
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Pythagon: Das hat nichts mit IDEs zu tun – wenn Du mit `rpdb` im Code einen Haltepunkt auslöst, also dort an der entsprechenden Stelle `set_trace()` aufrufst, dann wartet der Debugger an der Stelle auf Ein-/Ausgaben auf einem TCP-Port. Da kannst Du Dich dann drauf verbinden, im einfachsten Fall beispielsweise mit `netcat`/`nc` und solltest dann so mit dem Debugger kommunizieren können. Was da interessant werden kann ist das der Debugger die `std*`-Kanäle dann auf diese TCP-Verbindung umleitet, also von der Kommunikation mit dem VBA weg.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Pythagon
User
Beiträge: 52
Registriert: Mittwoch 3. Juli 2019, 18:21

@Black_Jack:

Ich hab jetzt mal den ZeroDivisionError generiert und diesen mit StdErr in VBA ausgeben lassen (auf meinem Rechner). Das klappt ganz wunderbar. Ich sehe außerdem, dass der Python Code danach nicht weiter ausgeführt wird und die Python Shell sich sofort wieder schließt, schneller als üblich. Das Gleiche was bei meinem Bekannten auf dem Rechner geschieht. Das bedeutet, irgendwas im Python Code generiert auf dem Rechner meines Bekannten einen Fehler, das bei mir auf dem Rechner einwandfrei läuft. Was das ist werde ich sehen wenn ich wieder bei ihm am Rechner bin.

Danke schonmal soweit an euch. Ich habe auf jeden Fall wieder etwas dazu gelernt. Ich lasse den Thread noch offen so lange ich die Lösung noch nicht habe wenn das recht ist.
Pythagon
User
Beiträge: 52
Registriert: Mittwoch 3. Juli 2019, 18:21

Hallo nochmal,

also ich habe nun die Fehlermeldung welche das Problem verursacht und sie stellt mich vor ein Rätsel:
ModuleNotFoundError: No module named 'requests'
Wenn ich in der Entwicklungsumgebung das Modul importiere dann ist dies nämlich vorhanden. Ich arbeite mit Python 3.7.4 und da ist es bereits vorhanden ohne es erst installieren zu müssen. Rufe ich den Code von VBA aus auf dann bekomme ich seltsamerweise die genannte Meldung.

Woran könnte das liegen?

VG,
Marc
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

Du hast mehrere Python-Varianten gleichzeitig installiert, aber nur ein der einen ›requests‹.
Schau Dir mal mit

Code: Alles auswählen

print(sys.executable)
an, mit welchem Python das Skript aus VBA heraus ausgeführt wird.
Pythagon
User
Beiträge: 52
Registriert: Mittwoch 3. Juli 2019, 18:21

Ok, nachdem ich nun mit Hilfe von pip das Modul nochmal separat installiert habe geht es.

Herzlichen Dank nochmal an euch!!!!
Antworten