YAMAGUCHI::weblog

海水パンツとゴーグルで、巨万の富を築きました。カリブの怪物、フリーアルバイター瞳です。

画面外にあるダウンロードボタンを押してファイルを取得する

はじめに

日常的にやってる作業を自動化するためにSeleniumを使っているわけだけど、どうでもいいことで無駄なハマり方をしたのでメモしておく。基本的にChrome Driverしか使ってないので他のブラウザの挙動は知らない。

画面外にあるボタンをクリックする

「要素がクリックできる状態になったらクリックする」というコードを最初適当に書いていたけれど、それだとエラーが出てクリックできなかったので、なぜかなーと思っていた。 たとえばこういう感じでコードを書いて実行すると

button_path = '//path/to/xpath'
WebDriverWait(driver, 10).until(
    expected_conditions.elements_to_be_clickable(
        (By.XPATH, button_path)
    )
)
button = driver.find_element_by_xpath(button_path)
button.click()

次のようなエラーが出ていた。

Traceback (most recent call last):
...
selenium.common.exceptions.WebDriverException: Message: unknown error: Element is not clickable at point (61, 6703)
  (Session info: chrome=61.0.3163.100)
  (Driver info: chromedriver=2.30.477690 (c53f4ad87510ee97b5c3425a14c0e79780cdf262),platform=Mac OS X 10.12.6 x86_64)

viewport外に要素がある場合にこういう挙動になる模様。ActionChainsで対応できるかなと思って次のようなコードに変更。

button = driver.find_element_by_xpath(button_path)
actions = ActionChains(driver)
actions.move_to_element(button)
actions.click()
actions.perform()

エラーは出なくなったけどダウンロード部分がうまくいかない。

ダウンロード用の設定と画面の調整

Chromeの起動オプションの変更

SeleniumでのChrome起動時にオプションを追加してダウンロードの際のプロンプトなどを滅殺する。

options = selenium.webdriver.ChromeOptions()
download_dir = os.path.dirname(__file__)
options.add_experimental_option("prefs", {
    "download.default_directory": download_dir,
    "download.prompt_for_download": False,
    "download.directory_upgrade": True
})
driver = selenium.webdriver.Chrome(chrome_options=options)

画面を強制的にスクロールする

ActionChains#move_to_element はマウスをそこまで持ってってくれるだけで画面のスクロールをしてくれないので(どうもviewport内のクリックでしかダウンロードイベントが発生しない)、仕方なくJavaScriptを無理やり実行して動かす。

driver.execute_script("arguments[0].scrollIntoView()", button)

Selenium for Pythonでタブの切替をする

はじめに

ここ数年はMoney Forwardとかのおかげでお金の管理はほぼ自動でいい感じにできてるんだけど、それでもまだ政治的な理由とかサービス側のセキュリティの問題で、利用している全サービスの自動管理とかできてない。というわけでエンジニアならその辺自動化するでしょ、とSeleniumでコツコツ始めたわけです。

で、ナビゲーションをしてると <a target="_blank"...> になってるaタグとかあって、ちょっとはまったのでメモ。

タブの切替方法

SeleniumはChromeDriverを使ってChromeを動かしている。タブを切り替える作業はいくつか方法があって

  1. WebDriver.send_keys() を使って手動で切り替えるのと同じキーを送る(例: Ctrl+Tabなど)
  2. WebDriver.ActionsChain() を使って 1 と同様にする
  3. WebDriverが持ってるwindowの一覧のなかから特定のwindowに WebDriver.switch_to.window() で切り替える

1と2は動かす環境(OS)が限定されているのであれば、手書きの作業書をそのままコードに書き直すだけなので簡単。ただ今回はOSをまたいで(MacLinux)実行する予定なので3を採用した。

スニペット

main_window = driver.current_window_handle  # 移動前のwindowを取っておく

for window in driver.window_handles:
    driver.switch_to.window(window)
    if driver.title == 'target title text':  # ページの <title> の内容で目的のタブと判定する
        break

do_some_process()
...
driver.switch_to.window(main_window)  # 処理が終わったら戻る

非常に泥臭いコードだけれども、新しいタブが開かれる前は window handle のインスタンスが無いので、結局こうやって処理するしかないと思う。もっといい方法があったら知りたい。

api.aiのdefault fallback intentをFirebase Functionsで受ける

はじめに

こんにちは、Slack Bot界のアラーキーです。今日はAlphaGoが柯潔との3局対決2勝目で中押し勝ちで2勝目を挙げましたね。Machine Learningの発展はすごいなあという素人の感想ですが、そんなMachine Learningの力をBot開発にも簡単にもたらしてくれるサービスの一つがapi.aiです。api.ai自体の説明は公式サイトや他のサイトに譲るとして、今日はapi.aiのdefault fallback intentをFirebase Functionsで実装したFulfillmentで受けるという簡単なデモを試したので、その作業ログを残します。

チャットボット AIとロボットの進化が変革する未来

チャットボット AIとロボットの進化が変革する未来

前提

まずは用語の説明から。

  • api.ai: 様々なチャットシステム用Bot(Actions on Google、Slack、LINE、Facebook Messanger)などに対応しているチャットボットミドルウェア
    • Agent: あるひとまとまりの応答をするNLU(Natural Language Understanding、自然言語理解)モジュール。複数のチャットアプリケーションと同時に連携が可能。
    • Intent: ユーザーからの特定のクエリに対する応答のマッピング。このintentをいかに多くの応答に対して作成できるかがapi.aiの肝。
    • Fulfillment: api.aiの先に用意する自前の応答システム。通常のbotシステムであればチャットシステムから直接メッセージデータを受け取るが、api.aiを利用する場合は前段となるapi.aiで捌けなかったものや複雑なトランザクションが発生するものをここで受ける。

api.ai自体を利用する場合は他にもEntityやContextといったものとapi.aiで内蔵している自然言語理解のトレーニング機構を使っていかに効率よくルールを作成していくかが肝心なのですが、今日は対応するIntentがなにもない場合のフォールバック先であるDefault Fallback Intentを自分で用意したFirebase Functionsのエンドポイントで受ける、ということをまずやってみます。

Firebase Functionsでのエンドポイントの作成

Firebaseプロジェクトの作成

Firebase FunctionsのGetting Startedのドキュメントを読めば簡単にできると思いますが、簡単にコマンドログだけ書くと

$ npm install -g firebase-tools
$ firebase login
$ firebase init functions
$ cd functions
$ npm install --save firebase-functions
$ npm install --save firebase-admin@4.2.1
$ tree -a -L 2 .
.
├── .firebaserc
├── firebase.json
└── functions
    ├── index.js
    ├── node_modules
    └── package.json

作成された firebase.json は空のオブジェクトが書いてあるだけですが、作成するFirebase Functionsのファイルを functions 以下に置くのであればそのままで大丈夫。functionsディレクトリの中のindex.jsに実際のエンドポイントの処理を書いていきます。

index.jsの作成

index.js をこんな感じで雑に作ります。Slackのみをつなげているため、dataの中にslackしかありませんが、つなげているアプリケーションが複数あればそれに応じてassistantfacebookなどのメッセージも追加することで対応できます。api.aiから飛んでくるリクエストと、そこに返すべきレスポンスのJSONスキーマに関しては api.ai のWebhookのドキュメントに書いてある。

const functions = require('firebase-functions');
const admin = require('firebase-admin');

admin.initializeApp(functions.config().firebase);

const slack_message = {
    "text": "hello slack from firebase"
}

exports.sampleBotAgent = functions.https.onRequest((req, res) => {
    console.log(req);
    res.status(200).send({
        "speech": "hello from firebase",
        "displayText": "hello from firebase",
        "data": {"slack": slack_message},
        "source": "foo"
    })
});

デプロイ

これで完了です。デプロイします。プロジェクトルート直下で firebase deploy コマンドを実行します。

$ firebase deploy --only functions
=== Deploying to 'sample-bot-agent'...

i  deploying functions
i  functions: ensuring necessary APIs are enabled...
i  runtimeconfig: ensuring necessary APIs are enabled...
✔  functions: all necessary APIs are enabled
✔  runtimeconfig: all necessary APIs are enabled
i  functions: preparing functions directory for uploading...
i  functions: packaged functions (1.55 KB) for uploading
✔  functions: functions folder uploaded successfully
i  starting release process (may take several minutes)...
i  functions: updating function sampleBotAgent...
✔  functions[sampleBotAgent]: Successful update operation.
✔  functions: all functions deployed successfully!

✔  Deploy complete!

Project Console: https://console.firebase.google.com/project/samplebot-agent/overview
Function URL (sampleBotAgent): https://us-central1-samplebot-agent.cloudfunctions.net/sampleBotAgent

ログの最後のFunction URLというのが作成されたエンドポイントのURLなのでこれをコピーします。

api.aiの設定

Agentの作成

api.aiに初めてログインするとすぐにAgentの作成画面に飛ばされると思います。すでにAgentがあるけれど新規に作る場合には、左ペインのエージェント名の右側にあるメニューボタンを押すと “Create new agent” というメニューがでてくるのでそこで作成。適当にAgentの名前やdescriptionを書きます。Sample Dataは無いと思うので無視。言語は英語が一番ちゃんと動きますが、日本語もちょっとは使えます。目下改良中の模様。今回はどうせDefault Fallback Intentしか作らないのであまり関係ないです。Google Projectはデフォルトで作ってくれるものでいいと思うので無視。

f:id:ymotongpoo:20170526002606p:plain

Slackとの連携

Agentは複数のチャットアプリケーションと連携できますが、今回はSlackのみと連携します。左ペインのメニューのIntegrationsを押して、Slackのトグルスイッチをオンにします。 f:id:ymotongpoo:20170526004756p:plain

するとSlackの各種トークンを設定するダイアログが出てくるので各々設定します。Slack側でもBotの設定をする必要がありますが、そのあたりは次のドキュメントにスクリーンショット付きで書いてあるのでそちらに譲ります。 先のダイアログ内のOAuth URLとEvent Request URLはSlack側のBotの設定で必要となります。その設定手順も次のドキュメントに書いてあります。

Fulfillmentの設定

AgentができてIntegrationができたのでFulfillmentを設定します。左ペインのメニュー内のFulfillmentを選択して設定画面にいきます。 f:id:ymotongpoo:20170526003017p:plain

WebhookのEnabledをオンにするといくつかフォームが出てきますが、ここのURLに先ほどコピーしたFirebase FunctionsのURLを貼ります。あとは設定しなくてOKです。 f:id:ymotongpoo:20170526003156p:plain

default fallback intentの設定

Fulfillmentの設定が終わったのでいよいよdefault fallback intentの設定です。初期値ではDefault Fallback IntentとDefault Welcome Intentがあると思います。 f:id:ymotongpoo:20170526003813p:plain

このDefault Fallback Intentを開くといくつかText Responseが設定されていることと思います。 f:id:ymotongpoo:20170526003953p:plain

このText Responseをまず全部消します。全消しです。Actionはそのまま “input.unknown” にしておいてください。その後、画面下にある Fulfillment という文字を押します。ここがわかりにくいですがこの Fulfillment の文字を押すと “Use webhook” のチェックボックスが出てくるのでチェックします。 f:id:ymotongpoo:20170526004257p:plain

これで設定完了です。

Slackで試す

適当なチャンネルに先ほど Agent と連携させた Bot を招待して、適当に書き込んでみます。 f:id:ymotongpoo:20170526005711p:plain

無事に動作しました!あとはapi.ai側のIntentを拡充させて、Fallback側での動作を諸々と考えるだけですね!楽しくなってきました!

おわりに

チャットシステムが普及し、様々な場面で文字・音声チャットが使われることが増えてきました。自分用のBotもより柔軟なものにできるように自分もいろいろ試そうと思います。

参照

macOSでパスワード付きzipを作成する

はじめに

いつから行われているのかわからないけれど、IT業界では「パスワード ハ オッテ ゴレンラクイタシマス」という呪文を唱え、パスワード付きzipとそのパスワードを2通のメールで送る慣習がある。普段は自分からそういうzipファイルを送ることはないのだけれども、今日たまたまどうしてもそうする必要があった。

zipcloak

で、調べてみたら、macOSでもzipcloakコマンドというのがあって、それを使えばパスワード付きzipを作れるということを今日知った。情弱乙。

$ zip -r path-to-dir/ archive.zip
$ zipcloak archive.zip
Enter password:
Verify password:

zipコマンドでまず普通のzipファイルを作成して、zipcloakコマンドでそれをキーフレーズで暗号化するという流れ。地味に便利だった。

zip -e

記事を公開してから力武さんにzipコマンド自体のオプションを教えてもらった。

$ man zip
...
       -e
       --encrypt
              Encrypt the contents of the zip archive using a password which is entered on the terminal in response to a prompt (this will not be echoed; if standard error is not a  tty,  zip  will  exit
              with an error).  The password prompt is repeated to save the user from typing errors.

ほんとだ、情弱すぎた。というかmanを読んでないのが悪い。

$ zip -e -r path-to-dir/ archive.zip
Enter password:
Verify password:

できた。

AMP Conf in NYC recap

はじめに

3月7日、8日にニューヨークでAMP Confという初のAMPチーム主催のAMPに関するカンファレンスが開催されました。

当日のツイートなど

リアルタイムで #ampconf ハッシュタグが付いていて良さそうなものをRTしつつ、自分でもメモとしてツイートしてました。改めてまとめて次のリンクに置いておきます。 twitter.com

当日のライブ配信の録画

当日はプレゼンはすべてライブ配信されて、その録画が残っています。各発表ごとの動画もいずれ公式チャンネルで公開される予定にはなっていますが、とりあえず公開されている録画の動画を貼っておきます。途中、画像だけで止まっているところがありますが、それはコーヒーブレイクなどで発表がない時間帯のものなので、適宜飛ばして再生して下さい。

多くのAMP運用企業からの発表があり、実際PinterestTumblrがとんでもない量のAMPページをリリースしている/することを報告していたり、wegoがどうやってAMPページと通常ページを両立させているかを共有していたり、その他もろもろこれまでAMPサイトを運用してきた知見が語られています。多くの日本のAMP対応サービス企業も言及されていて、特にYahoo! JapanのAMP対応は会場でも大きな話題となりました。

  • 1日目

www.youtube.com

  • 2日目

www.youtube.com

新しく発表されたこと

AMP Start

これまでAMPページを作る際にテンプレートがなくて苦労された方もいるかもしれません。AMP Startではそんな需要に対応すべく、様々な種類のページ向けにレスポンシブなテンプレートを用意しています。またページテンプレートだけではなく、ボタンやナビゲーションといったUIコンポーネントも提供しています。

AMP Start自体もオープンソースになっているので、もしいい感じのサンプルがあったらどんどんコントリビュートして下さい。

amp-bind

これまでAMPでは「一切のカスタムのJavaScriptが利用できない」ということが大きな制約となっていて、その制限によって利用を諦めた方も多いのではないかと思います。それはAMPを作った当初の思想の反映で「迂闊なJavaScriptのおかげで表示が遅くなることが多いので、まずは一番厳しい制限としてJavaScriptを書かせない」となったのですが、amp-listamp-sidebar などを使ってインタラクティブな動作もできるようにしてきました。

しかし、それでもまだEコマースサイトなどでは機能として足りないということで、状態管理とデータバインドなどに関するメソッドを提供し、かなり制限はあるものの、JavaScriptも少しは書けるようにしようとしたのがこの amp-bind です。(実際には amp-state などと組み合わせて使います)

www.ampproject.org

実際に amp-bind がどのような動作をするかは AMP by Example に簡単な例があるので、是非確認してみてください。

ampbyexample.com

発表の動画では amp-bindチューリング完全であることから、2048っぽいゲームをデモとして作って見せていましたが、発表者のWilliamが「頼むから実際のサイトでこんな実装しないでね」と言っていたのが印象的でした。

Google AMP Cacheでの高速化施策

現状ではAMPは機能を制限することでクライアントサイドレンダリングの高速化を図っているわけですが、これをされに推し進めて高速化を図る計画がAMP ProjectのTechnical LeadのMalteより発表されました。その施策は次の2つです。

  1. AMP JSのService Workerでのキャッシュ
  2. AMP Cacheサーバーでのサーバーサイドレンダリング

1のService WorkerでのAMP JSのキャッシュですが、現在Google AMP Cacheはcdn.ampproject.orgというドメインでホストされています。またAMP JSもこのドメインからホストされています。なので、このAMP JSでService Workerをインストールさせて、AMP JSをローカルにキャッシュさせて、AMPページにアクセスする際にそれを読み込ませるようにさせることでリモートにAMP JSを取りに行っていた通信の時間分速くなります。

2のサーバサイドレンダリングに関しては、現状AMPページにあるボイラープレートに対するクライアントサイドの読み込みフローを幾ばくかサーバサイドに任せて、読み込んだ瞬間にレンダリング計算が終わっている状態にしたい、というのが狙いです。

Ampersand by Cloudflare

www.cloudflare.com

これまでは外部から利用可能なAMPキャッシュはGoogle AMP Cacheしかありませんでしたが、今回CloudflareからAmpersandという独自ドメイン運用できるAMPキャッシュが公開されました。またそれに合わせて、見た目をカスタマイズできるAMPビューワーのSDKの公開もアナウンスしました。

codelab

2日目の午後はAMPのcodelabが開催されました。どれも新しくつくられたcodelabで、最新の情報を盛り込んだ内容になっています。

総括

ニューヨークでの開催でしたが、多くの国からの参加者が来ていて、参加者がすべてAMP対応企業だったこともあり、活発に意見交換がされていた印象でした。AMPはまだまだ発展途上のフレームワークですが、クライアントサイドにとどまらない点が他のフレームワークとの違いだと思うので、その特徴を活かしてよりモバイルウェブが良い方向に動くように発展していくきっかけになるように、これからもAMPがいい形で成熟していくことを願っています。