Web の多くは、Restful API を使用してサーバーとの間でデータを移動します。 休み睡眠とは関係のない単純な概念です。休み意味代表的な状態の転送、しかし、この記事はについてではありません休み. この記事は、サイトから何らかのデータを取得する必要がある場合に、残りの API がないことを回避するための厄介なメカニズムに関するものです。
Web サイトでは、さまざまな理由でこれを行うことを望まない場合があります。たとえば、データの帯域幅にはお金がかかります。データ ライセンスにはお金がかかります。また、広告を見ないことにもお金がかかります。 このツール、セレン、名目上はウェブサイトをテストするためのものであり、スピードバンプをスピードアップするためのものではありません. ほとんどのサイトには利用規約があります。 JMP の条件は、このページの下部にあります。
完全な JSL ファイルが添付されています。 Firefox、Windows、および 2022 年 5 月 8 日の JMP Web サイト向けに書かれています。 Firefox の部分は、おそらく他の多くのブラウザーに変更できます。 Mac でも動作する可能性がありますが、テストは行われていません。 JMP Web サイトは時間の経過とともに変化し、JSL は微調整が必要になります。 これは、公式 API を使用しないことの欠点です。
開始する前に、以下のコメントに示すように、ドライバーをダウンロードしてセレンをインストールします。 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() は一度だけ実行する必要があります。 JMP を Python に接続し、初回は数秒かかります。 ペナルティなしで再度呼び出すことができます。 PythonExecute(...) は変数を送信しませんが、実行するコードから 3 つを返します。 すべてをロードしてブラウザを起動するには少し時間がかかります。
You are looking at an empty browser controlled by JMPJMP によって制御される空のブラウザが表示されています
次に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.アイコンは、ブラウザがリモート制御されていることを意味します。
JMP サイトへのサインインをスクリプト化します。 [サインイン] ボタンを右クリックして、ボタンの HTML id 値を確認します。 これを行う方法を覚えておいてください。この説明は最後にスキップします...
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.後続の 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...右クリックしてユーザー名フィールドの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.偽のパスワードの偽のユーザー名。
ユーザー ID が画面外にスクロールされ、次のボタンが表示されます...その名前を見つけてください...
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.パスワードを入力後、サインインボタンをクリックしてください。
クリックして
// 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.サインインする必要があります。プロファイルの編集を選択できます。
画像に検索フィールドがあります。JSL と入力し、拡大鏡をクリックします。 「searchField」は ID です。 拡大鏡をクリックすることもできますが、Selenium には、フォームの入力フィールドである searchField で機能する送信フォーム メカニズムがあります。
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.ピンクの外側の要素には、タイトル、説明、リンクの 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.今日は 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 <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']
それほど複雑である必要はないかもしれません。 これは、一意の ID を使用して、クラスのみを考慮した場合に一意ではない可能性のあるアイテムを見つける例です。