sbi証券のお知らせをためすぎた
SBI証券のお知らせを、200件近く溜め込んでしまった。
SBI証券はログイン後にリダイレクトが走る
リダイレクト画面でのログイン
ログイン時に、もとのリクエストアドレスにリダイレクトされる
たとえば、特定の株価を見ていて、ログインして詳細を続けてみる
たとえば、SBIラップ口座を開いて、ログインして、ラップ口座の詳細を見る。
これが、「リダイレクトされる」ログイン画面

「お知らせ」があるとリダイレクトされない。
お知らせがあると、元いたページにリダイレクトされずに、「お知らせ一覧」に遷移してしまう。
SBIラップ口座でログインしているのに、SBIのお知らせ一覧に行ってしまうのだ。
そもそも、多くの人は「お知らせ一覧」が出てくるのが「正常状態」とすら思ってる。そのように誤解している人も多い。
お知らせ一覧が強制表示される例

お知らせを全部確認しようとしたが、しんどすぎた
SBIのお知らせは数年(2年)分が溜め込めれるので、お知らせを全部確認するのは非常につらい仕事になる。
自動化するか!
さすがに2年前のものはもういらないし、パスワード変更のお知らせ、何回あるんだよ。
私は、書面交付に設定してるので、本当に重要なものは、郵送で送られてくる。
ということを踏まえて自動化するか!
iframe を使う。
金融機関のウェブサイトは、iframe でページを読み込むと、JSでフレーム検出が走る。なのでiframeで読み込めないと思っていた。*1
しかし、iframeは進化している。iframeにはいまや、安全なSANDBOX機能がある。
iframeは「Javascriptオフ」できるのだ。JSをOffにすると、JSによるフレーム検出は無視できるのだ。
もしフレーム化を拒否したければJSはもうだめなのだ。HTTPヘッダでやるべきなのだ。*2
iframe に埋め込む。
お知らせ一覧をiframe に埋め込む

埋め込みさえできれば、DOMアクセスして、全自動できる。
埋め込むためのスクリプト
class iframeLoader {
constructor() {
this.iframe = null;
this.selector = '#MAINAREA01';
}
async create_iframe(selector,href) {
let iframe = null
let iframe_id = 'my_worker_iframe'
if (document.getElementById(iframe_id)) {
iframe = document.getElementById(iframe_id)
}
else {
// init
iframe = document.createElement('iframe')
iframe.setAttribute('sandbox', 'allow-forms allow-same-origin allow-top-navigation')
iframe.setAttribute('id', 'my_worker_iframe');
iframe.setAttribute('width', '100%')
iframe.setAttribute('height', '400px')
let ele = document.querySelector(selector)
let first = ele.firstElementChild
ele.insertBefore(iframe, first)
}
return new Promise((resolve, reject) => {
if (iframe) {
resolve(iframe)
}
})
}
async load_iframe(href) {
this.iframe.src = href
return new Promise((resolve, reject) => {
this.iframe.onload = function () { resolve(this.iframe.contentWindow.document) }
})
}
async load_notice_iframe() {
let ret = $x('//a[contains(./text() , "重要なお知らせ")]');
let a = ret[0]
let b = a.onclick.toString().match(/openMsgBox\('(.+)'\)/)[1]
let href = new URL(b, window.location.href).toString()
this.iframe = await this.create_iframe(this.selector)
let doc = await this.load_iframe(href)
return new Promise((resolve, reject) => {
resolve(this.iframe.contentWindow.document)
})
}
}
class NoticeClicker {
constructor() {
this.iframe_id = 'my_worker_iframe';
}
get iframe() {
return document.getElementById(this.iframe_id);
}
get iframe_document() {
return this.iframe.contentWindow.document
}
async iframe_send_button(button_name, doc) {
let do_onclick = () => {
function colSetSubmit(name, doc) {
if ("ACT_backViewInfoList" == name || "ACT_cancel" == name) {
doc.getElementById("ope_kbn_mb_impnotice_pop").value = "1";
} else if ("ACT_deleteInfoMessage" == name) {
doc.getElementById("ope_kbn_mb_impnotice_pop").value = "4";
} else if ("ACT_estimate" == name) {
doc.getElementById("ope_kbn_mb_impnotice_pop").value = "5";
}
}
};
let copy_submit_to_hidden = (button_name) => {
let selector = `input[type='submit'][name^=${button_name}]`
let form = doc.querySelector(selector).form
let value = doc.querySelector(selector).value
let input = document.createElement('input')
input.value = value
input.name = button_name
input.type = 'hidden'
form.appendChild(input)
}
do_onclick(button_name)
copy_submit_to_hidden(button_name)
doc.querySelector(`[type='submit'][name^=${button_name}]`).form.submit()
}
notice_exists() {
let doc = this.iframe_document
let link = doc.querySelector('td[class="mtext"][colspan="3"] a')
return link != null
}
async click_notice() {
let doc = this.iframe_document
let link = doc.querySelector('td[class="mtext"][colspan="3"] a')
link.click()
return new Promise((resolve, reject) => {
this.iframe.onload = function () { resolve(this.iframe_document) }
})
}
async click_confirm() {
let doc = this.iframe_document
this.iframe_send_button('ACT_estimate', doc)
return new Promise((resolve, reject) => {
this.iframe.onload = function () { resolve(this.iframe_document) }
})
}
async click_delete() {
let doc = this.iframe_document
this.iframe_send_button('ACT_deleteInfoMessage', doc)
return new Promise((resolve, reject) => {
this.iframe.onload = function () { resolve(this.iframe_document) }
})
}
async remove_notice() {
let doc = this.iframe_document
doc = await this.click_notice()
doc = await this.click_confirm()
doc = await this.click_delete()
return doc
}
async remove_all_notice() {
while (this.notice_exists()) {
await this.remove_notice()
}
}
}
起動用スクリプト
let loader = new iframeLoader() await loader.load_notice_iframe() let clicker = new NoticeClicker() clicker.remove_all_notice()
これをChrome Devで貼り付けて

ひたすら、自動でクリックさせる。
iframe は便利
ブラウザ操作とか。puppeeter や selenium ドライバやら、面倒は考えないで、サクッとページ遷移を伴う自動化を作ることができる。
スクレイピングとかもはかどるし、コンソールの変数が初期化されない。
テクニックとしては次の箇所の
constructor() {
this.iframe_id = 'my_worker_iframe';
}
get iframe(){
return document.getElementById(this.iframe_id);
}
get iframe_document(){
return this.iframe.contentWindow.document
}
iframe 内部のDOMドキュメントを取り出す部分。これを覚えておく。
iframe = document.getElementById(this.iframe_id) iframe.contentWindow.document
iframeを動的に作る部分、ここが大事。
sandboxでフォームは使えるようにする。
が、フレーム検出のJSは動かしてやんない(allow-scriptsを未指定)
iframe = document.createElement('iframe')
iframe.setAttribute('sandbox', 'allow-forms allow-same-origin allow-top-navigation')
iframe.setAttribute('id', 'my_worker_iframe');
iframe.setAttribute('width', '100%')
iframe.setAttribute('height', '400px')
let ele = document.querySelector(selector)
let first = ele.firstElementChild
ele.insertBefore(iframe, first)
最後に、iframeのロードをawait / promiseで待ち受ける箇所
同じコードが大量にあるのは、resolveがスコープで全部違うため。
async load_iframe(href) {
this.iframe.src = href
return new Promise((resolve, reject) => {
this.iframe.onload = function () { resolve(this.iframe.contentWindow.document) }
})
}
この繰り返しで、iframeに閉じ込めたら、フォーム送信のJavascriptが簡単にかけて、スクレイピングするより手軽に使える
ブックマークレットで呼び出したり、コンソールを叩くだけで、自動化しちゃえる。iframe神がかってた。
ただし、ちゃんとContent-Security-Policyが設定されるサイトでは使えないので注意。また、異なるドメインのiframeは操作できません。*3
今回のサイトはCSP非対応だったと、ドメインが同じだったのでJS コンソールからズルができた。
もうズルはしません。
今回は数年分を溜め込んだのでチート行為をしましたが、今後はこまめにチェックして削除するので許してください。さすがに180件をポチポチは嫌だし無理だよ。