SSDを交換した話 教訓:diskのIO再試行警告を軽く見てはいけない

ヤバかったデータ用のWD 1T SSDを交換した。

症状

  • ストレージの速度がもう操作不能なレベルで極端に落ちる。
  • 仕方ないから再起動したら今度はOSが立ち上がらない。
  • 仕方ないからイメージバックアップからsystemを書き戻そうとしてもUSBからmacriumが立ち上がらない。今から思えばSSD読み取りに失敗してるのだと思われる。
  • 修復シークエンスが立ち上がり放置しても当然復旧失敗。しかし何故かそこからUSBからmacriumを起動できるようになる。ますます意味不明。
  • macriumからsystemを書き戻したら何とかOSが立ち上がるようになったのでバックアップを取ろうと思ったら失敗する。

この辺りでデータ用SSDがヤバいのではと気づく。思い返せばEvent ViewerでdiskのIO再試行がたびたび出てた。その時は別に警告だったし気にしてなかったけど読み取り不能セクタでIO再試行がかかりOS巻き込むのもこいつなのではと推測。

対応

  • 仕方ないのでchkdsk /rで修復を試みるが10時間かかる。当初のペースだと丸二日を覚悟してたが意外に速かった。
  • 終わったら終わったで今度はBIOS上でSSDがNOT DETECTEDとなるがケーブルの抜き差しで事なきを得る。
  • なんとか復旧したので急いでバックアップを取ったら今度は成功する。
  • もう交換だなと諦めて尼さんで同じWDを買う。Crucial辺りでも別に良かったが値段がそんなに変わらなかったし。

FirefoxのProfileをRefresh後元通りに作り直したい

FirefoxのProfileをRefreshして元通りに作り直すべくまずは情報収集。

Addonの移行

Addon本体
Profiles\Profile名\extensions
Addon設定
Profiles\Profile名\storage\default\moz-extension~~~

まずAddon本体のxpiを新しいProfileに放り込んでやればAddonが権限無許可の無効状態ではあるが認識する。あとはポチポチ有効化してやればいい。

最大の問題はAddonの設定を何とか移せないかという事。まずAddon設定の
moz-extension~~~というのを移せばいいのかと言うと全くそんな事はない。まず設定ファイルらしきものはplain textではないが中身を見るとどうもAddonのUUIDが見える。つまり移行元と移行先のUUIDが違うので認識しないんだろう。Addonの認証状態ごと移してUUIDを一緒にしてしまえばいいんだろうがRefreshしたいので無理。設定は難しそうなので諦めた。

about:profilesに「プロファイルを別のプロセスで起動」というのがあるのでこれで移行元と移行先のWindowを並べて設定をポチポチ地道に揃えていくしかないが、最近の大型Addonは設定のエクスポートインポートが大体あるので割となんとかなる。

ブラウザ本体の設定

本体はprefs.jsだがこれを移してしまうとこんな事始めた意味がなくなるのでAddonの設定同様地道にポチポチする。

ツールバーのカスタマイズ

prefs.jsのbrowser.uiCustomization.stateというのがそれ。

Bookmark及び各種Password

Refreshしてるだけなのでこれは最初から保持している。

cookies.sqliteを移してやればいい。

LocalStorage

設定項目名でいうとサイトデータになる。中々の量があるはずなので重要ならstorage\default下にドメインごとに分かれているので必要なものを移せばいい。

MIME TYPE

設定/一般/プログラム。MIMEタイプごとの挙動。handlers.jsonを移してやればいい。

Google Calendar APIをPythonで叩く

昔作ったGoogle Calendar Event登録ScriptをPythonで全面的に書き換えるべく作業を始めようと思ったはいいがもう何年も前なので完全にやり方を忘れた。またどうせ忘れるのでここにメモしておこうと思う。

大まかな流れは以下のようになる。

  1. プロジェクトの作成
  2. 認証情報を作成
  3. 認証情報を使ってAPIを叩く

認証情報を作成

まずGoogle Cloud Platformにアクセスして認証情報の項目を開く。ちなみにこのGoogle Cloud Platformという名前も昔はGoogle Developer Consoleとかだったのでまた必要になった頃には変わってる可能性もある。さてプロジェクトの作成は特に問題ないだろう。次に認証情報を作成というボタンを押したら以下の3種類が出てくる。昔はOAuth一択だった気がするが現状では3種類あるようだ。認証の概要に詳細な説明があるが難しい事はさておいて要するにAPIを叩くにあたってユーザー認証が必要になるがそれをどうするかの違い。

API キー
一般公開データにユーザー認証なしでアクセスする。逆に言うとユーザー認証が必要なデータにはアクセス出来ない。
OAuth2.0クライアント
何処のサービスでもよく見る奴。トークンでユーザー認証する。何かするたびにこのトークンが必要。しかしこのOAuth同意画面の通過がいつの間にか非常に面倒臭くなった。個人でちょっと使うようなレベルの面倒さじゃない。どうも個人データを扱うAPIを有効にしてるとGoogle側の審査を求められるようになったらしい。公開ステータスをテストのままにしとけば一応使えはするが、ユーザーの種類が外部でかつ公開ステータスをテストのままにしておくとReflesh Tokenの有効期限が7日となる。ちょっと放っておくとすぐ期限切れになって面倒臭い。ユーザーの種類を内部に変えればいいんだろうがGoogle Workspaceユーザーしか出来ない。じゃあ対処法はと言うと一旦token.jsonを削除して取得し直ししかないようだ。
サービスアカウント
API専用のGoogleアカウントを作ってアクセスする。こいつはパスワードがないので通常のアカウントとしては使えない。アクセス先に認証ユーザー登録してもらう事が必要。今回の場合カレンダーごとに共有アカウントとしてサービスアカウントを予め手動で追加してやらねばならない。

OAuth2.0とサービスアカウントを併用する場合はcalendarのアクセス権限にくれぐれも注意する事。サービスアカウント権限で作成したcalendarはOAuth2権限からは見えないしその逆もある。考えてみれば当然の話。要するにサービスアカウントとはAccess Control Ruleで明示的に許可された事しか出来ない単なる一般ユーザー。Settingsの取得はおろかCalendarListの取得すらできない。

準備としてまず

OAuth2.0

使う言語はPythonなのでPython Quickstartを参考にする。サンプル読めば大体分かる。

if os.path.exists("token.json"):
	creds = Credentials.from_authorized_user_file("token.json", SCOPES)
if not creds or not creds.valid:
	if creds and creds.expired and creds.refresh_token:
		creds.refresh(Request())
	else:
		flow = InstalledAppFlow.from_client_secrets_file("credentials.json", SCOPES)
		creds = flow.run_local_server(port=0)
	with open("token.json", "w") as token:
		token.write(creds.to_json())

まあ要するに有効なrefresh_tokenを持っているかどうかで分岐させている。

サービスアカウント

こいつがquickstartに全く載ってないのであちこち探してなにやらこんな感じでいいらしいことを知る。

creds = Credentials.from_service_account_file("credentials.json")
creds = creds.with_scopes(["https://www.googleapis.com/auth/calendar"])

シンプルだし楽でいいね。ちなみにこれだとcreds.validがFalseを返す。どうもtokenを持ってないかららしい。OAuth2.0同様

creds.refresh(Request())

とやってやればいいだけなんだけど別にtokenがなくてもvalidがFalseでも立派に使える。

APIを叩く

サンプルを読むとまずservice とやらを作ってる。

service = build("calendar", "v3", credentials=creds)

これが何をしてるのかまず分からん。仕方ないのでgoogleapiclientのcodeを読んでみると細かい途中経過はさておいてこいつはResourceオブジェクトとやらを返すらしい。

now = datetime.datetime.utcnow().isoformat() + "Z"
print("Getting the upcoming 10 events")
events_result = service.events().list(calendarId="primary", timeMin=now, maxResults=10, singleEvents=True, orderBy="startTime").execute()

events()ってなんなんと1時間ほど悩んでgoogleapiclientのcodeを検索しまくったが分かってしまえばなんて事ない。Google CalendarAPIを叩いてるだけだった。という事は上記のResourceオブジェクトとやらはAPIを叩くインターフェイスであって中身を理解する必要は特になさげ。
ちなみにこれはquickstartに載ってる物なのでOAuth2.0の物だがサービスアカウントでやる場合calendarIdはprimaryではなくちゃんと指定する事。

サービスアカウントで出来ない事

  • 当然だがaclへのサービスアカウント追加はサービスアカウントでは出来ない。OAuth2が必要。
  • calendarListの取得はOAuth2じゃないと出来ないらしい。実行自体は出来るがちゃんとcalendarのaclにサービスアカウントを追加してるのにitemsが空で返ってくる。

結局サービスアカウントというのは要するに単なる一般ユーザーアカウントの域を出ないんだろう。

Google Calendar API

  • event作成を一度に大量にやるとたまに404が返ってくる事がある。アクセス拒否という訳ではなくすぐに問題なく実行できる。
  • calendar作成を一度に大量にやるとアクセス拒否されるようになる。たぶん丸一日ぐらい?しきい値は25ぐらいというのも見たが未確認。テスト実行中はくれぐれも注意する事。
Acl

Access Control Rule。設定項目でいう所の「特定のユーザーとの共有」の事。

delete
Access Control Ruleを削除する。

service.acl().delete(calendarId="primary", ruleId="ruleId").execute()

get
指定したruleIdのAccess Control Ruleを取得する。

service.acl().get(calendarId="primary", ruleId="ruleId").execute()

insert
Access Control Ruleを作成する。

rule = {
    "scope": {
        "type": "scopeType",
        "value": "scopeEmail",
    },
    "role": "role"
}
service.acl().insert(calendarId="primary", body=rule).execute()

list
Access Control RuleのListを取得する。

service.acl().list(calendarId="primary").execute()

patch
指定したruleIdのAccess Control RuleをPATCH Methodで更新する。

rule = service.acl().get(calendarId="primary", ruleId="ruleId").execute()
service.acl().update(calendarId="primary", ruleId=rule["id"], body={"role":"newRole"}).execute()

update
指定したruleIdのAccess Control RuleをPUTMethodで更新する。

rule = service.acl().get(calendarId="primary", ruleId="ruleId").execute()
rule["role"] = "newRole"
service.acl().update(calendarId="primary", ruleId=rule["id"], body=rule).execute()

watch
Access Control Ruleの変更を監視する。

calendarList

delete
calendarListからcalendarを削除する。あくまでListから消すのであってcalendar自体を消すのではない。

service.calendarList().delete(calendarId="hoge").execute()

get
calendarのListとそのmeta dataを取得する。

service.calendarList().get(calendarId="primary").execute()

insert
calendarListに既存のcalendarを追加する。新しく作成するのはcalendarsのinsert method。

body = {
	"id": "hoge"
}
service.calendarList().insert(body=body).execute()

list
calendarListをmeta dataと共に取得する。引数のpageTokenはいわゆる次のページという奴。calendarListの数が大きい時に使うはずだがそんな大きな数には普通はならない。使うとすればeventsのlistだろう。

page_token = None
while True:
	calendar_list = service.calendarList().list(pageToken=page_token).execute()
	for calendar_list_entry in calendar_list["items"]:
		print(calendar_list_entry["summary"])
	page_token = calendar_list.get("nextPageToken")
	if not page_token:
		break

patch
calendarList上のcalendarをPATCH Methodで更新する。

calendar_list_entry = service.calendarList().get(calendarId="calendarId").execute()
service.calendarList().patch(calendarId=calendar_list_entry["id"], body={"colorId":17}).execute()

update
calendarList上のcalendarをPUT Methodで更新する。patchもupdateも挙動は似てるがpatchはPATCH MethodでupdateはPUT Methodを使用している。従ってupdateは全ての要素を指定しないと指定したもの以外はクリアされてdefaultに戻る。patchとupdate共にcalendarIdがprimaryでは通らない。primaryでもきちんと指定する必要がある。

calendar_list_entry = service.calendarList().get(calendarId="calendarId").execute()
calendar_list_entry["colorId"] = "newColorId"

service.calendarList().update(calendarId=calendar_list_entry["id"], body=calendar_list_entry).execute()

watch
CalendarListの変更を監視する。

calendars

clear
primary calendar(アカウント紐付けcalendar)内のeventを全消去する。primary専用のMethodであってsecondary calendar(ユーザー作成calendar)には使えない。primary専用にも関わらずcalendarIdを指定しなければならない。

service.calendars().clear(calendarId="primary").execute()

delete
secondary calendar自体を削除する。当然だがsecondary calendar専用のMethod。

service.calendars().delete(calendarId="hoge@group.calendar.google.com").execute()

get
calendarのmeta dataを取得する。

service.calendars().get(calendarId="primary").execute()

insert
calendarを作成する。

body= {
	"summary": "hoge",
	"timeZone": "Asia/Tokyo"
}
service.calendars().insert(body=body).execute()

patch
calendarをPATCH Methodで更新する。

calendar = service.calendars().get(calendarId="primary").execute()
service.calendars().patch(calendarId=calendar["id"], body={"summary":"hoge"}).execute()

update
calendarをPUT Methodで更新する。

calendar = service.calendars().get(calendarId="primary").execute()
calendar["summary"] = "hoge"
service.calendars().update(calendarId=calendar["id"], body=calendar).execute()

channels

stop
watchで設定した監視Channelを停止する。

colors

get
calendarとeventのcolor設定を取得する。

service.colors().get().execute()

events

eventのidはbase32hexつまり0-9およびa-vで作成できる5〜1024文字の文字列。これを指定しないと実行するごとに重複eventを作成してしまう。似たような物にiCalUIDがあるがidはcalendar内で一意であればよいが、iCalUIDはcalendar system全体で一意でなければならない。また繰り返しeventは同一のiCalUIDとそれぞれ違うidを持つ。event作成にはどちらか一方があればよいがimportにはiCalUIDが必要。

delete
eventを削除するというかeventのstatusをcancelledにする。これを実行してもeventが完全に削除されたわけではない。これを理解しておかないとid指定でevent作成をする時idが重複してエラーとなる。

service.events().delete(calendarId="primary", eventId="hoge").execute()

get
eventのmeta dataを取得する。

service.events().get(calendarId="primary", eventId="hoge").execute()

import
eventをimportする。

event = {
	"summary": "Appointment",
	"location": "Somewhere",
	"organizer": {
		"email": "organizerEmail",
		"displayName": "organizerDisplayName"
	},
	"start": {
		"dateTime": "2011-06-03T10:00:00.000-07:00"
	},
	"end": {
		"dateTime": "2011-06-03T10:25:00.000-07:00"
	},
	"attendees": [
		{
		"email": "attendeeEmail",
		"displayName": "attendeeDisplayName",
		},
		# ...
	],
	"iCalUID": "originalUID"
}

service.events().import_(calendarId="primary", body=event).execute()

insert
eventを作成する。

body = {
	"summary": "hoge",
	"start": {
		"dateTime": "2022-06-01T09:00:00+09:00",
		"timeZone": "Asia/Tokyo",
	},
	"end": {
		"dateTime": "2022-06-01T10:00:00+09:00",
		"timeZone": "Asia/Tokyo",
	}
}

service.events().insert(calendarId="primary", body=body).execute()

instances
繰り返しeventの展開した各eventを取得する。

page_token = None
while True:
	events = service.events().instances(calendarId="primary", eventId="eventId", pageToken=page_token).execute()
	for event in events["items"]:
		print event["summary"]
	page_token = events.get("nextPageToken")
	if not page_token:
		break

list
eventのlistを取得する。取得数はデフォルトだと250。これで足りない場合はmaxResultsで2500まで増やすことが出来るが問題はstartを降順でなどと出来ない事。大体大昔のeventなどどうでもよくて欲しいのは最新のeventなのでその時はtimeMaxもしくはtimeMinで時間指定するしかない。

service.events().list(calendarId="primary").execute()

move
別のcalendarにeventを移動する。

service.events().move(calendarId="primary", eventId="hoge", destination="secondary@group.calendar.google.com").execute()

patch
eventをPATCH Methodで更新する。

event = service.events().get(calendarId="primary", eventId="eventId").execute()
service.events().update(calendarId="primary", eventId=event["id"], body={"summary":"Appointment at Somewhere"}).execute()

quickAdd
簡単な文字列のみでeventを作成する。文字列をパースしてeventを作成するのだがそんな事わざわざせずともscriptからinsertを叩く分にはそう面倒じゃないので公式の解説もいまいち不親切。

service.events().quickAdd(calendarId="primary", text="Appointment at Somewhere on June 3rd 10am-10:25am").execute()

update
eventをPUT Methodで更新する。

event = service.events().get(calendarId="primary", eventId="eventId").execute()
event["summary"] = "Appointment at Somewhere"
service.events().update(calendarId="primary", eventId=event["id"], body=event).execute()

watch
eventの追加またはeventの変更を監視する。

freebusy

query
calendarのlistから空き時間を取得する。

settings

get
指定された設定を取得する。

service.settings().get(setting="settingId").execute()

list
全設定を取得する。

service.settings().list().execute()

watch
設定の変更を監視する。

PythonでWinHTTPRequestを叩くのだ

別に好き好んでこんな面倒な事してる訳ではもちろんない。Requestsでどうしても上手くいかなかったから仕方なくやってるのだ。テメエの事だよe-h〇n!なにやら302で2回リダイレクトさせてる上にcookie制御も相当厳密にやってる。まあそれだけならRequestsでも出来るはずなんだけど何故か上手くいかない。結局出来なかったので推測でしかないがブラウザで見るとSessionStorageを使ってるのでたぶん原因はこれじゃないか。しかし昔々大昔に作ったJScriptの奴は今でも元気に動いてるぞ?ひょっとしてWinHTTPRequestなら上手くいくのか?

WinHTTPRequestの叩き方

pywin32 libraryを使えばいいらしい。しかしlibrary名はpywin32なのにmodule名はwin32com.clientなのな。後はWinHTTPRequestのmethodを叩けばいいだけだ。

import win32com.client
winhttp = win32com.client.Dispatch("WinHTTP.WinHTTPRequest.5.1")
winhttp.Open("GET", url, False)
winhttp.SetRequestHeader("Content-Type", "text/plain;charset=Shift_JIS")
winhttp.SetRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:100.0) Gecko/20100101 Firefox/100.0 ")
winhttp.Send()

おーちゃんと200が返ってきた。後はこれをパースしてやればいいだけだ。ここはcharsetが今時Shift_JISなので一旦ResponseBodyでbyte列を取得しておいてそれをtextにしてやらねばならない。しかしこのResponseBodyがなにやらmemoryview型とやらで返ってくる。byte型じゃないのか。

<memory at 0x000001A96428CF40>

必死に検索すると要するにCのポインタを返してるのね。つまりこれメモリのアドレスか。しかしサラッと書いたけど調べて理解するのに半日程かかったよ。memoryview() in Python辺りが英語だけど分かりやすかった。

html = winhttp.ResponseBody.tobytes().decode("shift_jis")

後はこのhtmlをBeautifulSoupに食わせて処理するだけ。しかしそもそも何故ResponseBodyがmemoryview型で返るのかは不明。

SwitchBot Hub Miniのネットワーク仕様 ~MQTTプロトコル~

Echo Dotを買った。アラームが好きなだけ登録できる高性能目覚まし時計ぐらいのつもりで買ったんだけど中々便利。SwitchBot Hub Mini(以下面倒なのでHub)もあれば灯りやエアコンをベッドに寝たまま操作できるとかお外から操作出来るとか色々便利そうなので欲しいなーと思ってたところ、タイムセール+プロモーションコードで1000円程度で買えるので即ポチした。確かにお外からエアコン操作出来たし真夏は便利。だがしかしちょっと待て。そもそもなんでそんな事出来るんや。今お前どうやってルーター超えた。

ヒルに聞いたはいいが検索ワードが悪かったらしく設定方法しか出てこない。しかし公式のQ&Aに

プロトコルとポートの情報は「MQTT OVER TCP PORT 8883」となります

と書いてあるのを見つけた。OVER TCP PORT 8883は分かるがMQTTってなんだ。

MQTTプロトコル

MQTTとは何の略だというとMQプロトコルの発展形でMQ Telemetry Transportの略らしいが、そのMQプロトコルも聞いた事ないので別にどうでもよかった。
さて、通常のクライアントサーバーモデルだと直接かつ同期的にアクセスしていた。しかしMQTTプロトコル
Publisher---------->Broker---------->Subscriber
という形態をとりかつ非同期。Publisherつまりデータ送信者、Subscriberつまりデータ受信者、間にあるBrokerというのはプロキシみたいな中間サーバー。PublisherもSubscriberもお互いの通信先なんか知る必要はなくBrokerの通信先さえ知っておけばいい。HubとスマホはPublisherとSubscriberのどちらにもなり得る。Hubからのデータ取得とスマホからのコマンド送信で方向違うからね。そしてHubが定期的にBrokerに通信してアクセス先を通知してるんだろう。じゃないとスマホからHubのアドレスなんか分かる訳ないし。

Edge(Chromium) + Selenium(Python)を使ってみる ~導入編~

我が家のIEちゃんが余命いくばくもないのでブラウザ自動運転の後継を確保するべく簡単に調べた結果Seleniumとかいうのが今のところ一番メジャーらしい。ActiveX叩けば簡単に動かせてたIEに比べれば面倒臭いんだけど仕方ないね。もっと簡単なのがあればそっちがメジャーになってるだろうし。
なお資料は

を参照した。

まずは必要な物の確認

ブラウザ
これはEdge(Chromium)を使うものとする。Chromeは入れてないしFirefoxはなんだか面倒そうなので。
WebDriver
Windowsの設定からインストールすることが出来るらしい。なんと便利な。「設定→アプリ→オプション機能→機能の追加」からMicrosoft WebDriverを選ぶ。しかし後で問題発覚。素直にWebDriverを手動で落として来る事。
Selenium本体
言語はPythonとする。現在まだβのVer4推奨らしいがanaconda上にVer4が上がってない。公式推奨にも関わらず使う奴いないんだろうなたぶん。よく読むと「The Selenium Tools for Microsoft Edge」とやらを使えば同等の事が出来るらしい。仕方ないのでVer3に決定。

色々読むと基本これだけでいいらしい。

取り敢えずやってみるがWebDriverがおかしい

from selenium import webdriver
driver = webdriver.Edge(r"C:\windows\system32\MicrosoftWebDriver.exe")
driver.get("https://www.google.com")

こんな感じのコードをパクってきていざ実行・・・おい動かねーぞ!ブラウザは立ち上がるがぐぐるを開いてくれない。try-exceptでエラーコードを見るとどうもモジュール自体がエラーを吐いてる。WebDriverExceptionとか見えるが自動で落ちてくる奴がおかしいのか。仕方ないので検索して手動で探してきた奴を指定すると無事動いた。よく見ると自動で落ちてくる奴はMicrosoftWebDriver.exeだが手動で探してきた奴はmsedgedriver.exe。Selenium DocumentationによるとMicrosoftWebDriver.exeはLegacyつまり旧版Edge用でmsedgedriver.exeはChromium用だと。という事なので設定から自動で落ちてくる奴は使わない事。

The Selenium Tools for Microsoft Edge

Ver3の拡張パック的な物。これがあればVer4と同等の事が出来るらしい。

from msedge.selenium_tools import Edge, EdgeOptions
driver = Edge(executable_path="", capabilities=None, port=0, verbose=False, service_log_path=None, log_path=None, keep_alive=False, desired_capabilities=None, service_args=None, options=None)
WebDriver Class

インスタンス

引数 概要
executable_path string Edge用WebDriverのPathを指定する。未指定だとPathを通せば探しに行くらしい。
capabilities
desired_capabilities
dict 各ブラウザ共通の設定情報をdictで指定する。capabilitiesはLegacy Edge用でdesired_capabilitiesはChromium Edge用。だがちゃんと中でcapabilitiesとdesired_capabilitiesが振り分けれてるのでどっちでも問題ない。
port number サービスの実行ポートを指定する。defaultの0だと空きポートを適当に使う。
verbose boolean 詳細なログを吐く。
service_log_path string LogのPathを指定する。
log_path string 非推奨。service_log_pathを使う事。
keep_alive boolean EdgeRemoteConnectionでHTTP keep-aliveを有効にする。
service_args list serviceに渡す引数のListを指定する。
options object EdgeOptionsのインスタンスを指定する。

メソッド

メソッド名 引数 概要
launch_app id 指定したIDでEdgeアプリを起動する。オリジナルであるChromiumのドキュメントを読むと非公式のコマンドらしい。
get_network_conditions ネットワークの状態をdictで返す。
set_network_conditions offline
latency
download_throughput
upload_throughput
ネットワークの状態を設定する。
execute_cdp_cmd cmd
cmd_args
Edge Devtools Protocol commandを実行する。詳細はChrome DevTools Protocol参照。
quit ブラウザを閉じる。
create_options optionsのインスタンスを作る。後述するEdgeOptionsと全く一緒なのでどちらを使ってもいい。
Options Class

プロパティ

プロパティ名 概要
use_chromium boolean chromium版を使うかどうか。現状ほぼ必須。
use_webview boolean capabilityのbrowserNameにwebview2を設定する。Edgeアプリのレンダリングエンジンの事か。詳細不明。
page_load_strategy string capabilitiesのpageLoadStrategyを変更する。
capabilities read only、writeはset_capabilityを使う。
binary_location string ブラウザバイナリのpathを返す。
debugger_address string Devtoolsインスタンスのアドレスを返す。
arguments read only、writeはadd_argumentを使う。
extensions read only、writeはadd_extensionまたはadd_encoded_extensionを使う。
experimental_options read only、writeはadd_experimental_optionを使う。
headless boolean headlessモードを有効にするかどうか。GUIを表示せずに実行できるようになる。add_argumentで「--headless」を追加しても内部的には全く一緒だがheadlessプロパティはWindowsの場合「--disable-gpu」も一緒に追加しているので手間が一つ省ける。

メソッド

メソッド名 引数 概要
set_capability name
value
capabilitiesのブラウザ共通部分に項目を追加する。
to_capabilities capabilitiesオブジェクトを作成する。
add_argument argument capabilitiesのargsに追加する。
add_extension extension capabilitiesのextensionsに追加する。拡張機能のファイルPathを指定する事。
add_encoded_extension extension capabilitiesのextensionsに追加する。拡張機能のファイルをBase64エンコードした文字列を指定する事。
add_experimental_option name
value
capabilitiesのブラウザ固有部分に項目を追加する。
set_headless 廃止。headlessプロパティを使う事。
各ブラウザ共通capability

参考は下記。しかしW3Cにはproxyがあるのにseleniumの実装にはない。コマンドラインオプションにproxy関連があるからそっちでやれという事なんだろうか。

名前 概要
browserName string ブラウザ名。
browserVersion string ブラウザVer。
pageLoadStrategy string ページ読み込みの手順。使用可能な値は「normal, eager, none」の3つ。結局document.readyStateを見てるだけ。defaultのnormalはcomplete、eagerはeagerはinteractive、noneはAnyつまり何もしない。
platformName string 要はOS名。
acceptInsecureCerts boolean 名前通りでセキュアでない証明書を受け入れるかどうか。falseだとエラーを返す。
timeouts object タイムアウトを指定する。Script Timeout, Page Load Timeout, Implicit Wait TimeoutのObjectを指定する。
unhandledPromptBehavior string ブラウザがダイアログを出した場合の処理を指定する。
setWindowRect string ウィンドウのサイズと位置を変更する。内部でgetWindowRectを叩いてるだけ。
strictFileInteractability boolean type=fileのinput要素に厳密なチェックを行うかどうか。厳密なチェックとやらが何をするのか知らないが。
Edge固有capability

参考は下記。というかいちいち見に行くのが面倒なのでコピーしただけ。

ちなみにargsで指定できる値の一覧は下記。Chromiumの一覧だが「--headless」も「--disable-gpu」もちゃんとあるので大体あってるだろうたぶんきっと。

EdgeOptions オブジェクト

名前 概要
args list Microsoft Edge を起動するときに使用するコマンドライン引数の一覧です。 値が関連付けられた引数は、= 記号で区切る必要があります (['start-maximized', 'user-data-dir=/tmp/temp_profile'] など)。
binary string 使用する Microsoft Edge バイナリのパス (macOS では、アプリだけでなく実際のバイナリである必要があります。 例: /Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge)。
debuggerAddress string たとえば、接続先のデバッガー サーバーのアドレスを、次の hostname/ip:port 形式で指定します 127.0.0.1:38947 。
detach false WebDriver Microsoft Edgeがセッションを閉じていなくても、WebDriver サービスがシャットダウンすると終了します false 。 場合は、Microsoft Edge WebDriver ローカル エンドがセッションを閉じる場合 true にのみ終了します。 WebDriver ローカル エンドがセッションを閉じない場合は、ユーザー インスタンスで使用される一時的なユーザー true EdgeDriver データ フォルダー Microsoft Edgeされません。
excludeSwitches list Microsoft Edge の起動時に、既定で EdgeDriver によって除外される Microsoft Edge コマンド ライン スイッチの一覧。 スイッチの -- プレフィックスは使用しないようにします。
extensions list 起動時にインストールする拡張機能の一覧。 リスト内の各項目は、base-64 でエンコードされたパック拡張 (.crx) である必要があります。
localState dict 基本設定の名前と値で構成される各エントリを持つ辞書。 ユーザー設定は、ユーザー データ フォルダー内のローカル状態ファイルに適用されます。
minidumpPath string Microsoft Edge ミニダンプを保存するディレクトリ。 (Linux でのみサポートされています。)
mobileEmulation dict deviceName の値、または deviceMetrics および userAgent の値を含むディクショナリ。
perfLoggingPrefs dict パフォーマンス ログの設定を指定するオプションのディクショナリ。 詳細については、「perfLoggingPrefs オブジェクト」を参照してください。
prefs dict 基本設定の名前と値で構成される各エントリを持つ辞書。 ユーザー設定は、使用中のユーザー プロファイルにのみ適用されます。 たとえば、ファイルのユーザー データ Preferences フォルダー内のファイルに移動Microsoft Edge
wdpAddress string 接続先の Windowsバイス ポータル サーバーのアドレス。hostname/ip:port の形式で指定します。例: 127.0.0.1:50080。 詳細については、「リモート デバッグ-Windows 10 デバイス」を参照してください。
wdpPassword string Windowsバイス ポータル サーバーに接続するときに使用するオプションのパスワードです。 サーバーで認証が有効になっている場合は必須です。
wdpUsername string バイス ポータル サーバーへの接続時に使用Windowsオプションのユーザー名。 サーバーで認証が有効になっている場合は必須です。
windowsApp string 起動する Microsoft Edge アプリ パッケージのアプリケーション ユーザー モデル ID。例: Microsoft.MicrosoftEdge.Stable_8wekyb3d8bbwe!MSEDGE。 Windowsバイス ポータルを使って Windows 10X デバイスまたはエミュレーターに接続する場合は、binary の代わりに windowsApp を使います。
windowTypes list ウィンドウ ハンドルの一覧に表示されるウィンドウの型の一覧。 Android webview 要素にアクセスするには、一覧に webview を含めます。

perfLoggingPrefs オブジェクト

キー 既定値 詳細
bufferUsageReportingInterval 正の整数 1000 DevTools トレース バッファー使用量イベント間の要求されたミリ秒数。 たとえば、1000 の場合は 1 秒に 1 回、すべてのトレースバッファーの量が報告されます。 レポートにバッファー使用量が 100% と示されている場合は、警告が発生します。
enableNetwork ブール値 true ネットワーク ドメインからイベントを収集します (または収集しません)。
enablePage ブール値 true ページ ドメインからイベントを収集します (または収集しません)。
traceCategories 文字列 (空) トレース イベントを収集する Microsoft Edge トレース カテゴリのコンマ区切り文字列。 未指定または空の文字列は、トレースを無効にします。

Webdriver Manager for Python

ブラウザのVerとWebDriverのVerは合わせる必要がある。しかしEdgeは知らないうちにUpdateしてるので面倒臭い。とか思ってたらその辺をいい感じに解決してくれる便利アイテムがあるらしい。それはいいんだけど各ブラウザの公式からじゃなくてどのブラウザもWebDriverを置いておくスペースを別途用意してそこから落とすんだな。あとよくエラーを吐くがたぶんサーバーが不安定なんだろう。

from msedge.selenium_tools import Edge, EdgeOptions
from webdriver_manager.microsoft import EdgeChromiumDriverManager
options = EdgeOptions()
options.use_chromium = True
driver = Edge(executable_path = EdgeChromiumDriverManager().install(), options = options)
driver.get("https://www.google.co.jp")

こんな感じでWebDriverがなければ勝手に落としてきてくれあまつさえVerチェックまでしてくれる。特に何も指定しなければWebDriverのPathは「C:\Users\user名\.wdm\drivers\edgedriver\win64\Ver\msedgedriver.exe」となるが場所を指定したければ

from msedge.selenium_tools import Edge, EdgeOptions
from webdriver_manager.microsoft import EdgeChromiumDriverManager
options = EdgeOptions()
options.use_chromium = True
driver = Edge(executable_path = EdgeChromiumDriverManager(path="C:\webdriver").install(), options = options)
driver.get("https://www.google.co.jp")

とEdgeChromiumDriverManagerでpathを指定すればいい。

今月の読書日記

民主主義の基本的な仕組みを解説した本。保守リベラルには全く言及されておらずあくまで政治の構造一本。それでも政治学の素養が低い俺さんにはまあまあ難しかった。マネロンって具体的にナニをドウするのか解説してある珍しい本。だがこれが正しいのかどうなのかよく分からない。アフガンの紆余曲折を解説してある本。確かに詳細で分かりやすいのだが、筆者の名前で検索するとすぐに出てくるがIS大好きタリバン大好きっ子なので話を割り引いてどころか話半分で聞かざるを得ない。西洋史または中国史と比較して日本史を俯瞰するというのは歴史本の定番だがむしろ日本史を語る視点が面白くてよかった。江戸時代の経済政策辺りなんて目から鱗