Ein Großteil des Webs verwendet Restful-APIs, um Daten zu und von Servern zu verschieben. Sich ausruhen ist ein einfaches Konzept, das nichts mit Schlafen zu tun hat; sich ausruhen meint Repräsentativer Staatstransfer , aber in diesem Artikel geht es nicht darum sich ausruhen . In diesem Artikel geht es um einen klobigen Mechanismus, um das Fehlen einer Rest-API zu umgehen, wenn Sie wirklich einige Daten von einer Site abrufen müssen.
Websites möchten dies möglicherweise aus verschiedenen Gründen nicht: Bandbreite für Daten kostet Geld, Datenlizenzierung kostet Geld, und das Nichtansehen der Werbung kann auch Geld kosten. Dieses Werkzeug, Selen , dient nominell zum Testen einer Website, nicht zum Überfahren einer Bremsschwelle. Die meisten Websites haben Nutzungsbedingungen; Sie finden die Bedingungen von JMP unten auf dieser Seite.
Eine vollständige JSL-Datei ist beigefügt. Es wurde für Firefox, Windows und die JMP-Website geschrieben, wie es am 8. Mai 2022 aussah. Der Firefox-Teil kann wahrscheinlich auf viele andere Browser geändert werden. Es könnte auch auf Mac funktionieren, es wurden keine Tests durchgeführt. Die JMP-Website wird sich im Laufe der Zeit ändern und die JSL muss angepasst werden. Das ist der Nachteil, wenn man keine offizielle API verwendet.
Laden Sie vor dem Start einen Treiber herunter und installieren Sie Selen wie in den Kommentaren unten gezeigt. Sie benötigen auch Firefox oder recherchieren Sie nach dem Treiber für Ihren bevorzugten Browser.
// JMP + Python + Selenium + Firefox (probably works with Chrome or Edge with a few tweaks)
// https://firefox-source-docs.mozilla.org/testing/geckodriver/index.html
// https://selenium-python.readthedocs.io/
// downloaded https://github.com/mozilla/geckodriver/releases -- geckodriver-...-win64.zip (pick latest)
// expand on desktop geckodriver.exe
// installed selenium python -m pip install selenium
// the following code is welded into the JMP.COM's HTML of 8May2022. It could change at anytime.
// the functions are sprinkled through the JSL below, near where I first needed them, reused later.
// you'll need similar functions, probably, and want to consult the Python Selenium bindings to write them.
// I only wrote enough to make this simple example work. There is no actual need to be logged in to JMP.
//
// Using try...except on snips of python code makes debugging much easier. JMP loses the exception message without it.
//
// the JSL functions are thin wrappers around the python calls to selenium which (I believe) is
// a thin wrapper to generate an API call to the gecko (etc) driver. I *think* it might be possible to
// remove the python layer and load/call the driver directly from JSL. But Selenium is documented.
Python Init(); // one-time startup...
// 0: startup
xrc = Python Execute( {}, {By_ID, By_XPATH, rc},
"\[
try:
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.service import Service
from selenium.webdriver.firefox.options import Options
#
options = webdriver.FirefoxOptions()
#options.add_argument("--private") # example. you most likely don't want private, some things don't work.
#
service=Service(r'C:\Users\v1\Desktop\geckodriver.exe')
# sometimes people use "browser" rather than "driver". It will be used below.
driver = webdriver.Firefox(service=service,options=options)
# return two magic values. you may need some others, just add them in the same way...
By_ID = By.ID
By_XPATH = By.XPATH
rc = "ok"
except Exception as e:
rc = repr(e)
]\"
);
If( xrc != 0 | rc != "ok", Throw( "start up Selenium failed" || Try( ": " || Char( rc ), "" ) ) );
Die PythonInit() muss nur einmal durchgeführt werden; Es verbindet JMP mit Python und dauert beim ersten Mal einige Sekunden. Sie können es ohne Strafe erneut anrufen. Die PythonExecute(...) sendet keine Variablen ein, bekommt aber drei von dem Code zurück, den sie ausführt. Es dauert ein bisschen, bis alles geladen und der Browser gestartet ist.
You are looking at an empty browser controlled by JMPSie sehen einen leeren Browser, der von JMP gesteuert wird
Öffnen Sie als Nächstes die Seite JMP.COM. Möglicherweise sehen Sie eine Weiterleitung, die normalerweise unbemerkt bleibt.
// 1: navigate to jmp.com
nav = Function( {url}, {rc},
Python Execute( {url}, {rc},
"\[
try:
driver.get(url)
rc = "ok"
except Exception as e:
rc = repr(e)
]\" );
return(rc);
);
rc = nav( "https://www.jmp.com/" );
if( rc != "ok", throw("nav: "||char(rc)));
Die nav-Funktion gibt "ok" oder eine Fehlermeldung zurück. Die JMP-Webseite wird im Browser geladen. Ignorieren Sie die Personen im Screenshot.
The icon means the browser is remote controlled.Das Symbol bedeutet, dass der Browser ferngesteuert wird.
Erstellen Sie ein Skript für die Anmeldung bei der JMP-Site. Klicken Sie mit der rechten Maustaste auf die Schaltfläche Anmelden, um den HTML-ID-Wert der Schaltfläche herauszufinden. Denken Sie daran, wie das geht; Ich überspringe diese Erklärung am Ende ...
F12 might bring you to the next screen, but this way the control will already be selected.F12 bringt Sie möglicherweise zum nächsten Bildschirm, aber auf diese Weise ist das Steuerelement bereits ausgewählt.
Und die Entwicklerkonsole wird geöffnet, wobei die ID des Steuerelements angezeigt wird. Weiter unten gibt es Steuerelemente, die eine Klasse, aber keine ID haben. Seleniums XPATH kann damit umgehen. Der Trick ähnelt der Displaybox-Navigation – einen Pfad zu finden, der nicht zu spröde und dennoch spezifisch genug ist.
Use the button id in the JSL that follows.Verwenden Sie die Schaltflächen-ID in der folgenden JSL.
IDs sind normalerweise die beste Wahl, wenn sie verfügbar sind, da sie auf der Seite eindeutig sind. WaitID wartet bis zu 10 Sekunden, bis der Login-Button erscheint. Es ist möglicherweise nicht erforderlich, zu warten. Es dauert keine Zeit, wenn es bereits da ist.
// 2: login with userid/password credentials
// wait for an id to be available
waitID = Function( {id, timeout = 10, BYformat=By_ID}, {rc},
Python Execute( {id, timeout, BYformat}, {rc},
"\[
try:
myElem = WebDriverWait(driver, timeout).until(EC.presence_of_element_located((BYformat, id)))
rc = "ok"
except TimeoutException:
rc = "timeout"
except Exception as e:
rc = repr(e)
]\"
);
Return( rc );
);
rc = waitID( "loginButton", 5 );
If( rc != "ok", Throw( "no login button: " || char(rc) ) );
// click a button ID
clickID = Function( {id, BYformat=By_ID}, {rc},
Python Execute( {id, BYformat}, {rc},
"\[
try:
driver.find_element(BYformat, id).click()
rc = "ok"
except Exception as e:
rc = repr(e)
]\"
);
return(rc);
);
rc = clickID( "loginButton" );
if( rc != "ok", throw("login button: "||char(rc)));
Kühl! Der Anmeldebildschirm erscheint. Suchen Sie als Nächstes das Feld für den Benutzernamen.
Now get the id for the user name field by right-click...Holen Sie sich jetzt die ID für das Benutzernamensfeld, indem Sie mit der rechten Maustaste klicken ...
Warten Sie erneut auf das erwartete Feld. Jetzt wird eine Tastendruckfunktion benötigt...
rc = waitID( "idp-discovery-username", 10 );
If( rc != "ok", Throw( "no username field: " || char(rc) ) );
// type a value into a field
keysToID = Function( {id, txt}, {rc},
Python Execute( {id, txt}, {rc},
"\[
try:
driver.find_element(By.ID, id).send_keys(txt)
rc = "ok"
except Exception as e:
rc = repr(e)
]\"
);
return(rc);
);
rc = keysToID( "idp-discovery-username", Include( "$documents/UserID.jsl" ) ); // file contains "AliBaba@1000&OneNights.com", in quotation marks, possibly encrypted
if( rc != "ok", throw("keysToID username: "||char(rc)));
Fake user name for fake password.Gefälschter Benutzername für gefälschtes Passwort.
Meine Benutzer-ID wird aus dem Bildschirm gescrollt und die nächste Schaltfläche ist sichtbar ... finden Sie ihren Namen ...
Click the Next button to get the password prompt.Klicken Sie auf die Schaltfläche Weiter, um die Passwortabfrage zu erhalten.
Klicken Sie darauf und warten Sie auf das Passwortfeld ...
// click Next button
rc = clickID( "idp-discovery-submit" );
if( rc != "ok", throw("click submit user name: "||char(rc)));
// wait for password field
rc = waitID( "okta-signin-password", 5 );
If( rc != "ok", Throw( "no password field: " || char(rc) ) );
Geben Sie dann das Passwort ein
// enter password
rc = keysToID( "okta-signin-password", Include( "$documents/password.jsl" ) ); // file contains "OpenSesame", in quotation marks, possibly encrypted
if( rc != "ok", throw("keysToID password: "||char(rc)));
und wiederholen Sie den Vorgang ... finden Sie die Anmeldeschaltfläche ...
After entering the password, click the sign in button.Klicken Sie nach Eingabe des Passworts auf die Schaltfläche Anmelden.
Klick es
// click Sign In
rc = clickID( "okta-signin-submit" );
if( rc != "ok", throw("click signin submit: "||char(rc)));
Wir sind angemeldet.
Must be signed in, there is an edit profile choice.Muss angemeldet sein, es gibt eine Option zum Bearbeiten des Profils.
Es gibt ein Suchfeld im Bild, geben Sie JSL ein und klicken Sie auf die Lupe. "Suchfeld" ist die ID. Auf die Lupe könnte geklickt werden, aber Selenium verfügt über einen Mechanismus zum Senden von Formularen, der das Suchfeld, das ein Eingabefeld im Formular ist, verarbeitet.
The search field needs a bigger window to be visible, here it is.Das Suchfeld braucht ein größeres Fenster, um sichtbar zu sein, hier ist es.
Es gibt ein asynchrones JavaScript, das einige Teile der Seite lädt. Das Warten auf ein bestimmtes Feld ist möglicherweise nicht erforderlich, wenn das Feld als Teil der Seite geladen wird.
// 3: query for articles about JSL
rc = waitID( "searchField" );
If( rc != "ok", Throw( "no search field: " || char(rc) ) );
// enter a search string. "jsl" currently retruns 3 pages
rc = keysToID( "searchField", "jsl" );
if( rc != "ok", throw("keysToID search field: "||char(rc)));
submitForm = Function( {id}, {rc},
Python Execute( {id}, {rc},
"\[
try:
driver.find_element(By.ID, id).submit()
rc = "ok"
except Exception as e:
rc = repr(e)
]\"
);
return(rc);
);
rc = submitForm( "searchField" ); // submit form works OK off of this input field.
if( rc != "ok", throw("submitForm searchField: "||char(rc)));
Machen Sie sich jetzt bereit, durch die Ergebnisse zu blättern. Die mehrseitigen Auflistungselemente sehen so aus
The pink outer element holds three inner parts: title, description, link.Das rosa äußere Element enthält drei innere Teile: Titel, Beschreibung, Link.
Es gibt eine Liste der rosa eingekreisten Datenelemente, die sich über mehrere Seiten erstreckt.
// 4: page through the results to capture them
getElements = Function( {id, BYformat=By_ID}, {rc},
Python Execute( {id, BYformat}, {rc},
"\[
try:
list = driver.find_elements(BYformat,id)
rc = "ok"
except Exception as e:
rc = repr(e)
]\"
);
Return( rc );
);
getNElements = function({},{n},
Python Execute( {}, {n},
"\[
try:
n = len(list)
except Exception as e:
print(repr(e))
n = -1
]\"
);
return(n);
);
getElementItext = function({i,id, BYformat=By_ID},{txt},
Python Execute( {i, id, BYformat}, {txt},
"\[
try:
txt = list[int(i)].find_element(BYformat, id).text
except Exception as e:
txt = "Error: getElementItext: " + repr(e)
]\"
);
return(txt);
);
getElementIattribute = function({i,id, BYformat=By_ID, attr},{txt},
Python Execute( {i, id, BYformat, attr}, {txt},
"\[
try:
txt = list[int(i)].find_element(BYformat, id).get_attribute(attr)
except Exception as e:
txt = "Error: getElementIattribute: " + repr(e)
]\"
);
return(txt);
);
Oben: einige Funktionen, die in der Schleife unten verwendet werden sollen. Unten auf der Seite befinden sich Schaltflächen, um zur nächsten Seite zu gelangen; Sie führen JavaScript aus, das die Liste der Elemente zerstört und neu erstellt. Die Funktionen werden erneut aufgerufen, um die neue Liste erneut zu erfassen. Die JSL und Python sind gut genug für dieses Beispiel. Sie werden zusammenbrechen, wenn es mehr als eine Liste gibt, die gleichzeitig verfolgt werden muss - siehe Python aufführen Variable. Ich bin mir ziemlich sicher, dass nach jeder nächsten Seite gewartet werden muss, bis die Daten geladen sind ...
dt = New Table( "articles",
New Column( "link", character,
Set Property("Event Handler",
Event Handler(
Click(JSL Quote(Function( {thisTable, thisColumn, iRow}, Web( Char( thisTable:thisColumn[ iRow ] ) ); );)),
Tip(JSL Quote(Function( {thisTable, thisColumn, iRow}, "Open " || Char( thisTable:thisColumn[ iRow ] ) || " in your browser."; );)),
Color(JSL Quote(Function( {thisTable, thisColumn, iRow}, RGBColor("link"); );))
)
)
),
New Column( "title", character ),
New Column( "description", character )
);
// this while loop will grab screens of answers and break() when the NextScreen button goes dim
while(1,
// not sure, yet, what to wait for. maybe the card(s)...
// this html is updated in the background by a json ajax mechanism that
// is hard to see.
rc = waitID("//div[@id='searchresults']//div[@class='result-card']",10,By_XPATH);
If( rc != "ok", Throw( "no search results" ) );
// get the current set
rc = getElements("//div[@id='searchresults']//div[@class='result-card']",By_XPATH);
if( rc != "ok", throw("getElements: "||char(rc)));
n = getNElements();
for(i=0,i<N></N>
An diesem Punkt ist der Browser geöffnet und diese Tabelle ist auf dem Bildschirm.
Today there were 57 entries spanning three pages.Heute waren es 57 Einträge auf drei Seiten.
Zeit, den Browser zu schließen.
// 5: quit
Python Submit(
"\[
driver.quit() # close the browser
]\" );
Python Term();
Gegen Ende gibt es einen XPATH
rc = clickID("//ul[@id='pager']//a[@class='pager-next']",By_XPATH);
das bedeutet
// - somewhere below the root of the document find a
ul - a <ul> tag (some sort of HTML list)
[@id='pager'] - the list has this id
// - more nested tags, followed by...
a = a <a> tag (link)
[@class='pager-next']
Es muss vielleicht gar nicht so kompliziert sein. Es ist ein Beispiel, das eine eindeutige ID verwendet, um ein Element zu finden, das möglicherweise nicht eindeutig ist, wenn nur die Klasse berücksichtigt wird.