Gran parte de la web utiliza API tranquilas para mover datos hacia y desde los servidores. Descansar es un concepto simple que no tiene nada que ver con dormir; descansar medio Transferencia de estado representacional , pero este artículo no se trata de descansar . Este artículo trata sobre un mecanismo torpe para evitar la falta de una API de descanso cuando realmente necesita recuperar algunos datos de un sitio.
Es posible que los sitios web no deseen que haga esto por varias razones: el ancho de banda para los datos cuesta dinero, las licencias de datos cuestan dinero y no ver los anuncios también puede costar dinero. Esta herramienta, selenio , es nominalmente para probar un sitio web, no para acelerar sobre un bache. La mayoría de los sitios tienen términos de uso; Puede encontrar los términos de JMP en la parte inferior de esta página.
Se adjunta un archivo JSL completo. Está escrito para Firefox, Windows y el sitio web de JMP tal como se veía el 8 de mayo de 2022. La parte de Firefox se puede cambiar, probablemente, a muchos otros navegadores. También podría funcionar en Mac, no se realizó ninguna prueba. El sitio web de JMP cambiará con el tiempo y el JSL necesitará ajustes. Esa es la desventaja de no usar una API oficial.
Antes de comenzar, descargue un controlador e instale Selenium como se muestra en los comentarios a continuación. También necesitará Firefox, o investigue un poco sobre el controlador para su navegador preferido.
// 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 ), "" ) ) );
PythonInit() solo debe realizarse una vez; conecta JMP a Python y tarda unos segundos la primera vez. Puedes llamarlo de nuevo sin penalización. PythonExecute(...) no envía variables pero recupera tres del código que ejecuta. Tarda un poco en cargar todo e iniciar el navegador.
You are looking at an empty browser controlled by JMPEstá viendo un navegador vacío controlado por JMP
Abra la página JMP.COM a continuación. Es posible que vea una redirección que normalmente pasa desapercibida.
// 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)));
La función de navegación devuelve "ok" o un mensaje de error. La página web de JMP se carga en el navegador. Ignora a las personas en la captura de pantalla.
The icon means the browser is remote controlled.El icono significa que el navegador está controlado a distancia.
Escriba el inicio de sesión en el sitio de JMP. Haga clic con el botón derecho en el botón Iniciar sesión para averiguar el valor de identificación HTML del botón. Recuerda cómo hacer esto; Omitiré esta explicación al final...
F12 might bring you to the next screen, but this way the control will already be selected.F12 puede llevarlo a la siguiente pantalla, pero de esta manera el control ya estará seleccionado.
Y la consola del desarrollador se abre mostrando la identificación del control. Más abajo habrá controles que tienen una clase pero no una identificación. XPATH de Selenium puede manejarlo. El truco es similar a la navegación de cuadro de visualización: encontrar un camino que no sea demasiado frágil y lo suficientemente específico.
Use the button id in the JSL that follows.Use la identificación del botón en el JSL que sigue.
Las identificaciones suelen ser la mejor opción cuando están disponibles porque son únicas en la página. WaitID espera hasta 10 segundos para que aparezca el botón de inicio de sesión. Puede que no sea necesario esperar. No lleva tiempo si ya está allí.
// 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)));
¡Enfriar! Aparece la pantalla de inicio de sesión. Busque el campo de nombre de usuario a continuación.
Now get the id for the user name field by right-click...Ahora obtenga la identificación para el campo de nombre de usuario haciendo clic con el botón derecho...
Nuevamente, espere el campo esperado. Ahora se necesita una función de pulsación de tecla...
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.Nombre de usuario falso para contraseña falsa.
Mi ID de usuario se desplaza fuera de la pantalla y el siguiente botón está visible... encuentre su nombre...
Click the Next button to get the password prompt.Haga clic en el botón Siguiente para obtener la solicitud de contraseña.
haga clic en él, luego espere el campo de contraseña...
// 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) ) );
luego ingrese la contraseña
// 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)));
y repita el proceso... busque el botón de inicio de sesión...
After entering the password, click the sign in button.Después de ingresar la contraseña, haga clic en el botón Iniciar sesión.
Pinchalo
// click Sign In
rc = clickID( "okta-signin-submit" );
if( rc != "ok", throw("click signin submit: "||char(rc)));
Estamos registrados.
Must be signed in, there is an edit profile choice.Debe iniciar sesión, hay una opción de edición de perfil.
Hay un campo de búsqueda en la imagen, escriba JSL y haga clic en la lupa. "searchField" es la identificación. Se puede hacer clic en la lupa, pero Selenium tiene un mecanismo de formulario de envío que funcionará con el campo de búsqueda, que es un campo de entrada en el formulario.
The search field needs a bigger window to be visible, here it is.El campo de búsqueda necesita una ventana más grande para ser visible, aquí está.
Hay algo de JavaScript asíncrono que carga algunas partes de la página. Es posible que no sea necesario esperar por un campo en particular si el campo se carga como parte de la página.
// 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)));
Ahora prepárate para hojear los resultados. Los elementos del listado de varias páginas se ven así
The pink outer element holds three inner parts: title, description, link.El elemento exterior rosa contiene tres partes internas: título, descripción, enlace.
hay una lista de los elementos de datos con un círculo rosa que abarca varias páginas.
// 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);
);
Arriba: algunas funciones para usar en el bucle a continuación. Hay botones en la parte inferior de la página para ir a la página siguiente; ejecutan algo de JavaScript que destruye y recrea la lista de elementos. Las funciones se vuelven a llamar para recuperar la nueva lista. JSL y Python son lo suficientemente buenos para este ejemplo. Se desglosarán si hay más de una lista de la que hacer un seguimiento al mismo tiempo; consulte el Python lista variable. Estoy bastante seguro de que es necesario esperar a que se carguen los datos después de cada página siguiente...
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>
En este punto, el navegador está abierto y esta tabla está en la pantalla.
Today there were 57 entries spanning three pages.Hoy había 57 entradas que abarcaban tres páginas.
Es hora de cerrar el navegador.
// 5: quit
Python Submit(
"\[
driver.quit() # close the browser
]\" );
Python Term();
Hacia el final hay un XPATH
rc = clickID("//ul[@id='pager']//a[@class='pager-next']",By_XPATH);
eso significa
// - 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']
Puede que no sea necesario que sea tan complicado. Es un ejemplo que utiliza una identificación única para encontrar un elemento que podría no ser único si solo se considerara la clase.