Ein Großteil des Webs verwendet Restful-APIs, um Daten zu und von Servern zu verschieben. Ausruhen ist ein einfaches Konzept, das nichts mit Schlafen zu tun hat; ausruhen bedeutet repräsentative Staatsübertragung , aber in diesem Artikel geht es nicht darum ausruhen . In diesem Artikel geht es um einen umständlichen Mechanismus, um das Fehlen einer Rest-API zu umgehen, wenn Sie wirklich einige Daten von einer Site abrufen müssen.
Websites möchten möglicherweise aus verschiedenen Gründen nicht, dass Sie dies tun: Bandbreite für Daten kostet Geld, Datenlizenzierung kostet Geld und das Nichtansehen der Werbung kann ebenfalls Geld kosten. Dieses Werkzeug, Selen , dient nominell dem Testen einer Website und nicht dem Überfahren einer Bodenschwelle. Für die meisten Websites gelten Nutzungsbedingungen. Die Bedingungen von JMP finden Sie unten auf dieser Seite.
Eine vollständige JSL-Datei ist beigefügt. Es ist für Firefox, Windows und die JMP-Website im Stand vom 8. Mai 2022 geschrieben. Der Firefox-Teil kann wahrscheinlich auf viele andere Browser umgestellt werden. Es könnte auch auf dem 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 keine offizielle API verwendet wird.
Bevor Sie beginnen, laden Sie einen Treiber herunter und installieren Sie Selenium, wie in den Kommentaren unten gezeigt. Sie benötigen auch Firefox oder recherchieren 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 ), "" ) ) );
Das PythonInit() muss nur einmal ausgeführt werden; Es verbindet JMP mit Python und dauert beim ersten Mal ein paar Sekunden. Sie können es ohne Strafe erneut callen. Das PythonExecute(...) sendet keine Variablen ein, erhält aber drei vom ausgeführten Code zurück. Es dauert etwas, alles zu laden und den Browser zu starten.
You are looking at an empty browser controlled by JMPYou are looking at an empty browser controlled by JMPYou are looking at an empty browser controlled by JMPYou are looking at an empty browser controlled by JMPYou are looking at an empty browser controlled by JMPYou are looking at an empty browser controlled by JMPYou are looking at an empty browser controlled by JMPYou are looking at an empty browser controlled by JMPYou are looking at an empty browser controlled by JMPYou 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 Navigationsfunktion 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.The icon means the browser is remote controlled.The icon means the browser is remote controlled.The icon means the browser is remote controlled.The icon means the browser is remote controlled.The icon means the browser is remote controlled.The icon means the browser is remote controlled.The icon means the browser is remote controlled.The icon means the browser is remote controlled.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 werde diese Erklärung am Ende überspringen ...
F12 might bring you to the next screen, but this way the control will already be selected.F12 might bring you to the next screen, but this way the control will already be selected.F12 might bring you to the next screen, but this way the control will already be selected.F12 might bring you to the next screen, but this way the control will already be selected.F12 might bring you to the next screen, but this way the control will already be selected.F12 might bring you to the next screen, but this way the control will already be selected.F12 might bring you to the next screen, but this way the control will already be selected.F12 might bring you to the next screen, but this way the control will already be selected.F12 might bring you to the next screen, but this way the control will already be selected.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 und die ID des Steuerelements wird angezeigt. Weiter unten gibt es Steuerelemente, die eine Klasse, aber keine ID haben. XPATH von Selenium 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.Use the button id in the JSL that follows.Use the button id in the JSL that follows.Use the button id in the JSL that follows.Use the button id in the JSL that follows.Use the button id in the JSL that follows.Use the button id in the JSL that follows.Use the button id in the JSL that follows.Use the button id in the JSL that follows.Use the button id in the JSL that follows.Verwenden Sie die Schaltflächen-ID in der folgenden JSL.
Wenn verfügbar, sind IDs normalerweise die beste Wahl, da sie auf der Seite eindeutig sind. WaitID wartet bis zu 10 Sekunden, bis die Anmeldeschaltfläche angezeigt wird. Es ist möglicherweise nicht notwendig, zu warten. Es dauert nicht lange, 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)));
Cool! Der Anmeldebildschirm wird angezeigt. Suchen Sie als nächstes das Feld „Benutzername“.
Now get the id for the user name field by right-click...Now get the id for the user name field by right-click...Now get the id for the user name field by right-click...Now get the id for the user name field by right-click...Now get the id for the user name field by right-click...Now get the id for the user name field by right-click...Now get the id for the user name field by right-click...Now get the id for the user name field by right-click...Now get the id for the user name field by right-click...Now get the id for the user name field by right-click...Rufen Sie nun per Rechtsklick die ID für das Feld „Benutzername“ ab ...
Warten Sie erneut auf das erwartete Feld. Nun 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.Fake user name for fake password.Fake user name for fake password.Fake user name for fake password.Fake user name for fake password.Fake user name for fake password.Fake user name for fake password.Fake user name for fake password.Fake user name for fake password.Fake user name for fake password.Falscher Benutzername für falsches Passwort.
Meine Benutzer-ID wird vom Bildschirm gescrollt und die Schaltfläche „Weiter“ ist sichtbar. Finden Sie ihren Namen.
Click the Next button to get the password prompt.Click the Next button to get the password prompt.Click the Next button to get the password prompt.Click the Next button to get the password prompt.Click the Next button to get the password prompt.Click the Next button to get the password prompt.Click the Next button to get the password prompt.Click the Next button to get the password prompt.Click the Next button to get the password prompt.Click the Next button to get the password prompt.Klicken Sie auf die Schaltfläche „Weiter“, um zur Passwortabfrage zu gelangen.
Klicken Sie darauf und warten Sie dann 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.After entering the password, click the sign in button.After entering the password, click the sign in button.After entering the password, click the sign in button.After entering the password, click the sign in button.After entering the password, click the sign in button.After entering the password, click the sign in button.After entering the password, click the sign in button.After entering the password, click the sign in button.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.Must be signed in, there is an edit profile choice.Must be signed in, there is an edit profile choice.Must be signed in, there is an edit profile choice.Must be signed in, there is an edit profile choice.Must be signed in, there is an edit profile choice.Must be signed in, there is an edit profile choice.Must be signed in, there is an edit profile choice.Must be signed in, there is an edit profile choice.Must be signed in, there is an edit profile choice.Sie müssen angemeldet sein, es gibt die Möglichkeit, das Profil zu bearbeiten.
Im Bild gibt es ein Suchfeld. Geben Sie JSL ein und klicken Sie auf die Lupe. „searchField“ ist die ID. Die Lupe könnte angeklickt werden, aber Selenium verfügt über einen Mechanismus zum Senden von Formularen, der mit dem Suchfeld arbeitet, das ein Eingabefeld im Formular ist.
The search field needs a bigger window to be visible, here it is.The search field needs a bigger window to be visible, here it is.The search field needs a bigger window to be visible, here it is.The search field needs a bigger window to be visible, here it is.The search field needs a bigger window to be visible, here it is.The search field needs a bigger window to be visible, here it is.The search field needs a bigger window to be visible, here it is.The search field needs a bigger window to be visible, here it is.The search field needs a bigger window to be visible, here it is.The search field needs a bigger window to be visible, here it is.Das Suchfeld benötigt ein größeres Fenster, um sichtbar zu sein, hier ist es.
Es gibt 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 nun bereit, die Ergebnisse durchzublättern. Die mehrseitigen Auflistungselemente sehen so aus
The pink outer element holds three inner parts: title, description, link.The pink outer element holds three inner parts: title, description, link.The pink outer element holds three inner parts: title, description, link.The pink outer element holds three inner parts: title, description, link.The pink outer element holds three inner parts: title, description, link.The pink outer element holds three inner parts: title, description, link.The pink outer element holds three inner parts: title, description, link.The pink outer element holds three inner parts: title, description, link.The pink outer element holds three inner parts: title, description, link.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 zur Verwendung in der Schleife unten. Unten auf der Seite befinden sich Schaltflächen, mit denen Sie zur nächsten Seite wechseln können. 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. Für dieses Beispiel reichen JSL und Python aus. Sie brechen zusammen, wenn mehr als eine Liste gleichzeitig im Auge behalten werden muss – siehe Python Liste Variable. Ich bin mir ziemlich sicher, dass man nach jeder nächsten Seite warten 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>
Zu diesem Zeitpunkt ist der Browser geöffnet und diese Tabelle wird auf dem Bildschirm angezeigt.
Today there were 57 entries spanning three pages.Today there were 57 entries spanning three pages.Today there were 57 entries spanning three pages.Today there were 57 entries spanning three pages.Today there were 57 entries spanning three pages.Today there were 57 entries spanning three pages.Today there were 57 entries spanning three pages.Today there were 57 entries spanning three pages.Today there were 57 entries spanning three pages.Today there were 57 entries spanning three pages.Heute gab es 57 Einträge auf drei Seiten.
Zeit, den Browser herunterzufahren.
// 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 tag (some sort of HTML list)
[@id='pager'] - the list has this id
// - more nested tags, followed by...
a = a tag (link)
[@class='pager-next']
Es muss vielleicht nicht so kompliziert sein. Es handelt sich um ein Beispiel, das eine eindeutige ID verwendet, um ein Element zu finden, das möglicherweise nicht eindeutig wäre, wenn nur die Klasse berücksichtigt würde.