Web の多くは、Restful API を使用してサーバーとの間でデータを移動します。 休む睡眠とは何の関係もない単純な概念です。休む手段表現的状態の転移、しかしこの記事はそれについてではありません休む。 この記事は、サイトからデータを取得する必要がある場合に、Rest API の欠如を回避するための厄介なメカニズムについて説明しています。
Web サイトでは、データの帯域幅にお金がかかる、データのライセンスにお金がかかる、広告を見ないとお金がかかるなど、さまざまな理由からこれを望まない場合があります。 このツール、セレン、名目上は Web サイトをテストするためのものであり、スピードバンプを越えるためのものではありません。 ほとんどのサイトには利用規約があります。 JMP の規約はこのページの下部にあります。
完全な JSL ファイルが添付されています。 これは、2022 年 5 月 8 日時点での Firefox、Windows、および JMP Web サイト向けに書かれています。 Firefox の部分は、おそらく他の多くのブラウザに変更できます。 Mac でも動作する可能性がありますが、テストは行われていません。 JMP Web サイトは時間の経過とともに変更されるため、JSL は調整が必要になります。 それが公式 API を使用しないことの欠点です。
開始する前に、以下のコメントに示すようにドライバーをダウンロードし、Selenium をインストールします。 Firefox も必要になるか、お好みのブラウザのドライバーについて調べてください。
// 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() は 1 回だけ実行する必要があります。 JMP を Python に接続するのに最初は数秒かかります。 ペナルティなしでもう一度呼び出すことができます。 PythonExecute(...) は変数を送信しませんが、実行するコードから 3 つの変数を返します。 すべてをロードしてブラウザを起動するには少し時間がかかります。
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 JMPあなたはJMPによって制御された空のブラウザを見ています。
次にJMP.COMページを開きます。 通常は気づかれないリダイレクトが表示される場合があります。
// 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)));
nav 関数は「ok」またはエラー メッセージを返します。 JMP Web ページがブラウザにロードされます。 スクリーンショット内の人物は無視してください。
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.アイコンは、ブラウザがリモート制御されていることを意味します。
JMP サイトへのサインインのスクリプトを作成します。 [サインイン] ボタンを右クリックして、ボタンの HTML ID 値を確認します。 これを行う方法を覚えておいてください。最後に説明は省きますが…
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 を押すと次の画面が表示される場合がありますが、この方法ではコントロールがすでに選択されています。
開発者コンソールが開き、コントロールの ID が表示されます。 さらに下には、クラスはあるが ID がないコントロールがあります。 Selenium の XPATH はそれを処理できます。 このトリックはディスプレイボックスのナビゲーションに似ています。つまり、不安定すぎず、十分具体的なパスを見つけることです。
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.後続の JSL でボタン ID を使用します。
ID はページ上で一意であるため、使用可能な場合は通常、ID が最適な選択となります。 WaitID は、ログイン ボタンが表示されるまで最大 10 秒間待機します。 待つ必要はないかもしれません。 すでに存在していれば時間はかかりません。
// 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)));
いいね! サインオン画面が表示されます。 次にユーザー名フィールドを見つけます。
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...次に、右クリックしてユーザー名フィールドの ID を取得します...
再度、期待されるフィールドを待ちます。 ここでキーストローク機能が必要になります...
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.偽のパスワードに対する偽のユーザー名。
私のユーザー ID が画面からスクロールされ、次のボタンが表示されます...その名前を見つけてください...
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.「次へ」ボタンをクリックすると、パスワードのプロンプトが表示されます。
それをクリックして、パスワードフィールドが表示されるまで待ちます...
// 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) ) );
次にパスワードを入力します
// 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)));
そしてプロセスを繰り返します...サインインボタンを見つけます...
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.パスワードを入力後、サインインボタンをクリックしてください。
クリックして
// click Sign In
rc = clickID( "okta-signin-submit" );
if( rc != "ok", throw("click signin submit: "||char(rc)));
サインインしています。
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.サインインする必要があります。プロファイルの編集を選択できます。
画像に検索フィールドがあります。「JSL」と入力し、拡大鏡をクリックします。 「searchField」がIDです。 拡大鏡をクリックすることもできますが、Selenium にはフォームの入力フィールドである searchField で機能する送信フォーム メカニズムがあります。
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.検索フィールドを表示するには、より大きなウィンドウが必要です。ここにあります。
ページの一部を読み込む非同期 JavaScript がいくつかあります。 フィールドがページの一部として読み込まれる場合、特定のフィールドを待つ必要がない場合があります。
// 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)));
次に、結果をページごとに確認する準備をしてください。 複数ページのリスト要素は次のようになります。
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.ピンク色の外側の要素には、タイトル、説明、リンクという 3 つの内側の部分が含まれています。
ピンクの丸で囲まれたデータ項目のリストが複数のページにまたがっています。
// 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);
);
上: 以下のループで使用するいくつかの関数。 ページの下部には次のページに移動するボタンがあります。項目のリストを破棄して再作成する JavaScript を実行します。 新しいリストを再取得するために、関数が再度呼び出されます。 この例では、JSL と Python で十分です。 同時に追跡する複数のリストがある場合、それらは分割されます -- Python を参照してください。リスト変数。 次のページごとにデータがロードされるまで待つ必要があると確信しています...
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>
この時点でブラウザが開いており、このテーブルが画面上に表示されています。
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.今日は 3 ページにわたる 57 件のエントリーがありました。
ブラウザをシャットダウンします。
// 5: quit
Python Submit(
"\[
driver.quit() # close the browser
]\" );
Python Term();
最後の方にXPATHがあります
rc = clickID("//ul[@id='pager']//a[@class='pager-next']",By_XPATH);
つまり
// - 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']
そこまで複雑にする必要はないかもしれません。 これは、クラスのみを考慮した場合には一意ではない可能性のある項目を、一意の ID を使用して検索する例です。