PythonでSelenium_ウィンドウの切替え失敗とその対処について

はじめに

前回、Seleniumで職場のルーティンを自動化した記事を書いた
しかし実行中にエラーが発生することが何度かあったので、その原因と解決作を記録する
前回↓

atc.hateblo.jp

原因

それはウィンドウの切り替えである。

PDFの添付後、別ウィンドウがポップアップし、「OK」ボタンのクリックを要される。クリックをすると当該ウィンドウは自動で消える。これをSeleniumで動かしてみると以下の流れになる。

1.ポップアップされたウィンドウに切り換える  
2.「OK」ボタンのidに対して`.click()`若しくは`.send_keys(Keys.ENTER)`を実行    
3.ポップアップされたウィンドウが自動的に消えるので、元々開いていたウィンドウに切り換える  

上記操作をSeleniumで叶えるためには、windowhandleを知る必要がある。

windowhandleとは?

ウィンドウを識別するための情報
複数のウィンドウを操作したいとき、windowhandleを使うことでSeleniumの操作対象を切り替えることができる。

  • windowhandleを取得する方法

①. 現在操作対象であるウィンドウのwindowhandleを取得

handle = driver.current_window_handle

②. 全ウィンドウ(指定したインスタンス)のwindowhandleを取得(リスト)

handles = driver.window_handles
  • ウィンドウを切り替える方法
driver.switch_to_window(handles[2])

又、window_handlesの取得順は以下のとおりとなるらしい
index0 : 1(一番最初のウィンドウ)
index1 : 3(最新のウィンドウ)
index2 : 2(最新より1つ前のウィンドウ)
一番最初のウィンドウは0に、それ以降は新しく開いたウィンドウから割り振られていく

www.seleniumqref.com


以上がwindowhandleの概要である。
これを用いてポップアップに対してSeleniumを実行すると、体感的に30%程の確立で「OK」のクリックに失敗する。 エラーコードを見てみると、「OK」のidが見つからなかったと書いてある。しかし見つかるときもある。一体なぜなのか...原因を探してみたら次のとおりだった。

原因

window_handlesの取得順がたま〜にちぐはぐになってしまっていた
(逐次取得したwindow_handlesをprintしてみたら判明した)

index0 : 3
index1 : 1
index2 : 2

index0 : 1
index1 : 2
index2 : 3

など。順番が前述の通りにならないケースがあり、理由はわからなかった...
しかし、try - exceptを用いることでこの問題をクリアすることができた

解決策

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.common.exceptions import NoSuchElementException


wait.until(lambda d: len(driver.window_handles) > 2)
handle = driver.window_handles
driver.switch_to_window(handle[1])

try:
    driver.find_element_by_id(b1).send_keys(Keys.ENTER)
except NoSuchElementException:
    try:
        driver.switch_to_window(handle[2])
        driver.find_element_by_id(b1).send_keys(Keys.ENTER)
    except NoSuchElementException:
        driver.switch_to_window(handle[0])
        driver.find_element_by_id(b1).send_keys(Keys.ENTER)

インスタンスを用いて出現するウィンドウは全部で3つである。
1. ポップアップのウィンドウは最新 = index1に格納されるはずなので、handle[1]にスイッチ
2. その後「OK」のidを格納している変数b1に対してENTERを実行、実行されれば問題なし
3. idb1が見つからない場合、NoSuchElementExceptionエラーが発生するためexceptで救出してあげる
なお、exceptを用いる時は以下のとおり例外クラスをimportしてあげる必要がある
from selenium.common.exceptions import [TheNameOfTheExceptionClass]

7. WebDriver API — Selenium Python Bindings 2 documentation
4. except内で更にtryしてhandle[2]のウィンドウにスイッチ
5. idb1に対してENTER、実行されれば問題なし
6. idb1が見つからない場合、NoSuchElementExceptionエラーが発生するのでexceptで救出。
7. 最後のウィンドウhandle[0]にスイッチしてidb1に対してENTERを実行
8. 以上

上記であれば全windowhandleを捜索するので漏れはない。

結果

エラーが出ることはなくなりました

PythonでSeleniumを使い毎朝の業務を自動化させたらストレスから開放された件について、手こずった点を列挙する

1. はじめに

私は業務で毎朝以下のルーティンを任されています
行程 : 使用するシステム : 業務内容 : 所要時間
① : Aシステム : Bシステムにデータを送信(大体40のデータ) : 10秒
② : Bシステム : 1データに対し1つのPDFを作成 : 3分
③ : Aシステム : 1データに対し1つのPDFを添付して送信 : 30分

Aシステムはブラウザ上(IE専用)、Bシステムはjavaで作成されたシステムです
図化すると下記のような感じ f:id:Atc:20180724185101j:plain (なんか透けてる)


①,②はすぐに終わるので良いとして、③はひたすらPDFを添付し続けるという苦行を30分間も強いられます。しかもAシステムは画面が遷移する度に「読み込み中」ポップアップが3秒程(長いときは5秒以上)表示されるのです。もはや現代に人間が行うべき作業じゃない...!!ということで勉強してるPythonseleniumを使って自動化してみました
本記事ではその際に手こずった点をあげていこうと思います

ちなみに、自動化させた結果これまで感じていた多大なストレスから開放されました、Python様様です。
私は職業エンジニアではないのですが、Pythonを勉強したことで今の業務の効率化を図ることができました。
プログラミングを身につけることは仕事をする上で大きなメリットになりますね
又、今回Seleniumを使いましたが以前簡単な使い方を以下のとおり学んでいます

http://atc.hateblo.jp/entry/2018/06/20/python%E3%81%AESelenium%E3%81%AF%E3%82%B9%E3%82%AF%E3%83%AC%E3%82%A4%E3%83%94%E3%83%B3%E3%82%B0%E3%81%A7%E3%81%8D%E3%82%8B%E3%82%84%E3%81%A4atc.hateblo.jp

正直、上記の記事の時点ではSeleniumについてしっくり理解できていませんでした。なぜならSeleniumを使うには最低限のHTMLの知識を要するからです。しかし先週、職場のイントラの一部の制作を任されHTMLを学んだお陰でSeleniumで自由自在にスクレイピングできるようになりました。Seleniumを使いたいがHTMLを全く知らない場合はまずProgateで軽く触れてみたり、簡単なHPを作成してみるのがよいと思います。

2. 環境

OS : Windows 7 Professional
ブラウザ : InternetExplorer11
seleniumで操作するためにはドライバが必要。IEの場合IEDriverServerが必要となります。
Python : Python3.7

3. 詰まったところ

拡大率によるエラー

  • エラーコード
selenium.common.exceptions.SessionNotCreatedException: 
Message: Unexpected error launching Internet Explorer. Browser zoom level was set to 105%. 
It should be set to 100%
  • 原因
    拡大率が100%ではなかったこと

  • 解決法
    拡大率を100%にしてあげれば解決します。拡大させてよ...字が小さいの...
    普段のブラウザ操作では頻繁に拡大表示するので高確率でエラーがでちゃいます。これってIEだけなんでしょうか

プロキシの例外の設定によるエラー  

  • エラー
要求されたURLからデータを取り出せませんでした。
  • プロキシ(代理) とは?
    ネットサーフィンをする時、コンピュータはブラウザからサーバへ情報の提供を申請し、サーバはその申請に応じてブラウザへ情報を送信している
    プロキシはブラウザとサーバの間に存在する、中継役である
    プロキシが存在することでブラウザは身元を隠すことが出来るメリットがある
    インターネット接続を限定的に制限したい時などに設置される

  • 原因
    プロキシが設定されている場合localhost(127.0.0.)への接続にプロキシを経由することがある。
    プロキシに対してlocalhost(127.0.0.
    )でアクセスしようとするとプロキシ自体が通信を拒否してしまう。
    Python〜IEDriverServer間では通信にlocalhostや127.0.0.*が使われている。
    そのため、PythonからIEdriverServerにアクセスを行う際に、プロキシが接続を拒否してエラーになってしまう。

  • 解決法
    IE→インターネットオプション→接続→LANの設定→詳細設定→プロキシの例外、にlocalhostと127.0.0.*を追加する
    これにより、localhostへの接続時にプロキシを経由しなくなるため、エラーは発生しない

話がややこしくてつい書き言葉になってしまった

使用するドライバの指定(Ie)

使用するドライバのPATHを引数に指定し、IEのWebDriverのインスタンスを作成します。
webdriver.ブラウザ名(DriverのPATH)と書くのですが、IEの場合、Iは大文字、eは小文字 = Ieとします

Selenium python internet explorer - Stack Overflow

driver = webdriver.Ie(r"C:¥Users¥ユーザ名¥IEDriverServer.exe")

PATHの書き方

Windowsならではの問題ですが、windowsのPATHは¥マークで区切られます。
pythonでは¥マークを用いたエスケープシーケンスが存在するため、前述でドライバの在処を指定していますがPATHをコピペしてシングルクォートで囲むだけでは正確に在処を教えてあげることができません

www.pythonweb.jp

エスケープシーケンスを無効にするためにはraw文字列にしてあげればOKです

  • 方法
r'文字列'
r"文字列" #RでもOK

ウィンドウを切り換える

(下記サイトは今回Seleniumを使う上で何度も参考にさせていただきました。すごく見やすいです)

www.seleniumqref.com

Aシステムで業務を行っていると、2度新規ウィンドウが立ち上がります。普段IEで業務を行う上では、インターネットオプションでタグ ブラウズの設定ポップアップの発生時常に新しいタブでポップアップを開くにしているので新規タブで表示されるのですが、Seleniumで操作するブラウザでは関係ないらしく全部新規ウィンドウで表示されてしまいます。新規ウィンドウが自動的にSeleniumの操作対象になるのかと思いきや、ウィンドウの切り換え作業を行わないと新規ウィンドウの操作ができないようです。

ウィンドウを識別する方法としてwindowhandleという情報を用い、それを元にウィンドウ切り換えを行います。手順としては以下のとおり

1. windowhandleを取得する
2. 取得したwindowhandleを使ってウィンドウを切り換える

  • コード
driver.window_handles #全ウィンドウのwindowhandleを取得
driver.switch_to_window(windowhandle) #引数に取得したwindowhandleを指定して切り換え
  • 実用例
handle = driver.window_handles #全windowhandleを取得
driver.switch_to_window(handle[1]) #インデックス番号でwindowhandleを指定して切り換え

ちなみにdriver.current_window_handleとすると現在操作権のあるウィンドウのwindowhandleだけを取得できます

flameの指定

HTMLにはfleamという1つのページに複数のページを埋め込むことが出来るタグが存在します。fleamによって分割されているサイトでSeleniumを使いたい時は、欲しい要素がどこのfleamに存在するかをHTMLから判断し、fleam切り換えをする必要があります。現在のfleamに存在しない要素を指定して情報を取得しようとしても、エラーになってしまいます(fleamタグという存在を知らず長時間手こずってしまいました)

  • 切り換え方法
driver.switch_to_frame("frameName")

3. ナビゲート — Selenium Python Bindings 2 ドキュメント

ファイルのアップロード

手こずった訳ではないですが、予想を裏切られ感動したのでメモ
通常ファイルをアップロードする時は参照をクリックするとエクスプローラがポップアップされ、ファイルを選ぶ...といった流れになります。そのためSeleniumにもポップアップされたウィンドウを操作する機能が存在するのかな?それともまたウィンドウの切り換え?と想像しながら調べてみたところ、全く異なる簡単な方法がありました。

  • コード
driver.find_element_by_id('inputのidなど').send_keys(r"ファイルのPATH")

上記の例で言うと、HTMLから参照ボタンのinputを探し出してidなどで引数に指定、.send_keysメソッドの引数にアップロードしたいファイルのPATHを指定することでファイルのアップロードが可能です
すごくスマート、サイコー

https://www.dafuku.com/2014/12/selenium-file-upload.htmlwww.dafuku.com

待ち時間

私の毎朝のルーティンでは頻繁に画面を遷移します。遷移するたび読込時間が発生するので、コードの速さにブラウザが追いつかず、要素がありませんでしたとかエラーがでちゃいます。そんなときに使えるのがSelenium待機です。

5. 待機 — Selenium Python Bindings 2 ドキュメント

softwaretest.jp

待機

Selenium待機には2種類あります

1. 暗黙的な待機 -Implicit Wait-

待機しないと利用できない要素を見つける時に、指定した時間内ポーリング(条件を満たしたら処理を行わせること)させることができます
つまり、指定した時間内で要素が見つかればすぐに次のコードを実行し、待っても要素が見つからなければエラーになる、ってことですね。読み込んでいる間は要素の取得が不可能なので、その間待ってくれます。一度設定してあげればWebDriverが起動中有効だそうです。今回私はこれを使いました

  • コード
from selenium import webdriver

driver = webdriver.Ie(r"IEDriverのPATH")
driver.implicitly_wait(20) # seconds

私は最初の頃time.sleep()で個別に待機させてました。ブラウザの読込が終了してもtime.sleep()に指定した時間はしっかり待機するので、テストコードを試すのに時間がかかってうんざりしていましたが、暗黙的な待機であれば要素が見つかれば即座に次のコードが実行されるので快適です。

2. 明示的な待機 -Explicit Wait-

暗黙的な待機との違いは、個別の箇所に対してより詳細な条件で待機させることができる点です。今回は使わなかったので調べていません。 安定した動作を求めた結果、明示的な待機めちゃくちゃ使いました。主に visibility待機を多用しました。要素が表示されるまで待機する命令です。

締めは特にないです

PyCharmにvenvで作成した仮想環境を設定する

PyCharmを使い始めてみたので、venvで作成した仮想環境を追加する方法を備忘録として記録します

次の通りクリックしてけばOK

  1. preferencesを開く
  2. project:○○○
  3. project Interpreter
  4. 歯車アイコン
  5. Add local
  6. Existing environment
  7. ...アイコン
  8. venv環境のbinディレクト/python3
  9. OK
  10. OK
    pythonの新規プロジェクトを作成して当該venv環境にインストールしたモジュールが使えればOK

画像

f:id:Atc:20180723203254p:plain f:id:Atc:20180723203255p:plain f:id:Atc:20180723203255p:plain f:id:Atc:20180723203257p:plain f:id:Atc:20180723203257p:plain f:id:Atc:20180723203253p:plain

miChecker_アクセシビリティを考慮したWEBサイトの作成を支援するツール

はじめに

今回職場のイントラの一部のページの作成を任され、始めてイチからHTMLとCSSを書きました。半年前にドットインストールとProgateで初級編を学んだ時の印象が、入れ子めっちゃ見辛い,<>が全部同じに見える,行数が多くて全体が把握できない → 面倒くさいだったのですが、実際作ってみるとめちゃくちゃ簡単で楽しくて感動しました。
完成したイントラを職場内に公開する前に何か抜けていることはないかな〜と調べていたら、総務省のHPにアクセシビリティに対応したWEBサイトを作るための支援ツールmiCheckerというものがありました。すごく便利なツールで感動したので、この感動を忘れぬよう本記事を記したいと思います。

miCheckerとは

総務省|情報バリアフリー環境の整備|みんなのアクセシビリティ評価ツール:miChecker (エムアイチェッカー)Ver.2.0

アクセリビリティを考慮したWEBサイトの作成を支援するツールです
DLして実行ファイルを起動したら、アドレスバーに作成したHTMLのURLを貼り付けて当該URLに移動。検証ボタンを押すことでアクセシビリティに反している部分を教えてくれます。

アクセシビリティとは

高齢者や体の不自由な方でも容易に利用可能なことを指します
(例:拡大表示ができる,大きなボタン,音声読み上げに対応,わかりやすい名前,などなど... )
又、アクセシビリティについての決め事として、JIS X 8341-3というものがあります

JIS X 8341-3とは

幅広い人々が容易に情報を取得することができるように、アクセシビリティを考慮したコンテンツを作成することとした決め事。主に公的機関がHPを作成する際の決まり事だそうです。
miChekerはこのJIS X 8341-3を遵守したWEBサイトの作成を支援するためのツールのようです。

使ってみた

今回職場で作成したHTMLで当該チェッカーを使ってみました。
検証可能な対象は2つあります。
1.音声ユーザビリティ
2.ロービジョン

1.音声ユーザビリティ

テキストを音声で読み上げられるか否か等を検証します
検証結果は4種類あります
1.指摘箇所に問題がある項目
2.指摘箇所に問題が存在する可能性が高い項目
3.問題があるかどうかは人が判断する項目
4.ツールでは指摘できない項目

今回は1.2.の該当がありませんでした
3.についてはいくつかメッセージが出ましたが、確認した結果特に問題なさそうです
よかったよかった

2.ロービジョン

視覚的に見辛い箇所を検証します
本検証では2種類の項目を評価します
1.色に関する問題点
2.視力(フォントの大きさ)に関する問題点

今回どちらも指摘されていまいました。
私の作成したHTMLには職員が利用するシステムへのアクセスを促すボタンがあるのですが、 検証結果によると「ボタンのサイズが固定されている」「ボタンの背景色と文字色の彩度の差が少ない」とのことです。
当該ボタンはCSSfont-sizepxで指定していましたが、%にすることで解決、
文字色は"#FFFFFF"で背景色は"#33CC99"でしたが、背景色を"#006633"にすることで解決しました。

所感

趣味であれば自分の好きなように作っておしまいなので、アクセシビリティを意識することは少ないと思います。又、"誰もが使い易いと感じるコンテンツ"を意識して作るのは難易度が高い。しかしサービスをネット上に公開しようと思えば色々な人に使ってほしいのも事実。miChekcerを使えばアクセシビリティの知識がなくても、検証結果の問題箇所を改善するだけで、手軽に利便性を高めることができると感じました。行政のみならず一個人にとっても、コンテンツをより多くの人に使ってもらえるチャンスが増える便利なツールなのではと思います。

pipenvをやめてvenvへ

pipenvで作った環境にはいってpythonスクリプトを実行するシェルスクリプトを書きました
シェルスクリプト単体なら正常に動作するのですが、cron越しだと以下の通りエラーがでました

cd /User/takuto/pytho_1/weather_pop
piping run python weather_pop.py

atc.hateblo.jp


Error_1

Permission denied

これは権限がないとのことなので、$ chmod +xで当該.shに権限を付与
続いてのエラー

Error_2

make: pipenv: Command not found

これはPATHが/usr/bin:/binしか設定されていなかったので、vimPATH=/usr/local/binを追記
続いてのエラー

Error_3

Warning: the which -a system utility is required for Pipenv to find Python installations properly. Please install it.
Error: the command python3 could not be found within PATH or Pipfile's [scripts].

これが全然わからなくて挫折しました
「なんでだ〜〜〜」と頭を抱えながら環境について考え直していたら、私は複数バージョンを使わないのでパッケージ管理さえできる最低限の環境でよいのではと思い、pipenvを削除してvenvに切り替えることにしました
エラーにはpipenvのpipfile'sがなにか影響してるっぽいので、venvに変えちゃえば解決するのではと
やっぱ初心者がいろいろなツールに手を出すのはもう控えよう...

pipenvのアンインストール

まずホームディレクトリ下のpytho_1というディレクトリで仮想環境を構築していたので、下記で仮想環境を削除

# virtualenvを削除します  
$ pipenv --rm
Removing virtualenv (/Users/takuto/.local/share/virtualenvs/pytho_1-kjK1izHD)...

# インストールしてたパッケージを全削除します 
$ pipenv uninstall —all-dev
Un-installing [dev-packages]...
Locking [dev-packages] dependencies...
Locking [packages] dependencies...
# HomebrewでPipenvをアンインストールしました
$ brew uninstall pipenv
Uninstalling /usr/local/Cellar/pipenv/2018.7.1... (2,111 files, 25.8MB)

venvを使う

$ cd /Users/takuto/python1/weathwe_pop

# 仮想環境を作成
$ python3 -m venv venv  #後者の`venv`がディレクトリ名

# 仮想環境に入る
$ source /venv/bin/activate.fish #fishを使ってるため

# pythonのバージョン確認とスクリプトの実行  
$python —version
python3.7.0
$ python weather_pop.py  #実行された

weather_pop.pyをcronで動かす

まずシェルスクリプトを書きます

PYTHON=/Usres/takuto/python1/weather_pop
source PYTHON/venv/bin/activate
python3 $PYTHON/weather_pop.py

crontab -eで設定

30 7 * * * /Usres/takuto/scr/weather_pop.sh

実行されました
pipenvだと動かなかった理由、なんか.shの書き方が悪かったような気がしてきた
わがんねぇな〜オィ〜~~~

pyenvからpipenvにしてみた

python環境構築の見直し

pythonのライブラリ管理と仮想環境の構築のためにpyenv``pyenv-virtualenv``anacondaを用いているのですが、まだまだ初心者の私にとっては複雑で「なぜこのツールをインストールしているんだろう」と役割が分かっていない部分もあります
又、anacondaを導入することで様々なライブラリやツールが利用可能になりますが、現状宝の持ち腐れ状態なので必要なモノだけ導入し、今後入れたいと思ったモノについても1つずつ導入していくスタイルが私に合っているかなと思いました

そこでPython環境の再構築を視野に入れて調べていたところ、ライブラリ管理仮想環境構築の二役を一手に担っているパッケージ管理ツールPipenvを見つけました

ドキュメントを見ていると導入・管理・運用の全てがシンプルに行えそうだったので、今の環境をリセットしてPipenvへチャレンジしたいと思います。今回はそのために行った手順を掲載します

Pipenvとは

https://pipenv-ja.readthedocs.io/ja/translate-ja/

Pipenvは、手動でパッケージのインストールおよびアンインストールを行うのと同じように Pipfile に対してパッケージの追加および削除を行うのに加え、自動でプロジェクト用の仮想環境を作成し管理します。 またPipenvは、いかなるときも重要な Pipfile.lock を生成し、これを利用しビルドが常に同じ結果になるようにします。

Pipenvは主にアプリケーションのユーザーと開発者に、簡単に作業環境を作れる方法を提供するためのツールです。 ライブラリとアプリケーションの違いや、依存関係を定義するための setup.py と Pipfile の使い方の違いについては、 ? Pipfile vs setup.py を参照してください。

つまりライブラリ管理と仮想環境構築ができるということですかね

現在の環境

  • PC : macOS High Sierra MacBook Pro (13-inch, 2017, Two Thunderbolt 3 ports)
  • python : pyenv, pyenv-virtualenv, anaconda python自体はanaconda内に入っているものを使ってます

現在のpython環境について詳しく

$ pyenv versions
  system
* anaconda3-5.1.0 (set by /Users/ユーザ名/.pyenv/version)
  anaconda3-5.1.0/envs/ml
  anaconda3-5.1.0/envs/test

1. anaconda(python)をバージョン毎に管理

  • pyenvでanaconda(python)をバージョン毎にインストール
  • pyenv global anacondahoge(version)でバージョンを切り替え

2. anaconda(python)の各バージョンから派生し、名前で管理

  • conda create -n hoge(名前) anacondahoge(ver)でanaconda(python)を1.から派生して環境を構築
    上記コマンドによってanacondaに入ってるpythonモジュール等をフルで備えたpython環境が生成できます
    又、ケツをanacondahoge(ver)でなくpythonとすることで純粋なpython単体の環境も生成できます
  • pyenv global anacondahoge(ver)/hoge(名前)で派生した環境に切りかえる

    condaはanacondaに入っているパッケージ管理&仮想環境構築その他諸々を可能にするツールです
    2.において名前付き仮想環境をcondaで作成しているわけですが、切り替えはpyenvで行ってます
    ここで疑問としてpyenv-virtualenvが、何に対してどのように作用しているのか分かってません
    色々調べたのですが結局分からなかったので、いっそ消しちゃおうかと思ったのが今回の環境移行の動機です

    文字だとわけわかめなので図化してみる
    f:id:Atc:20180703145427j:plain

作業手順

  1. 現在のPython環境を消去(macデフォで入ってたPython2は消さない)
  2. Python3をインストール
  3. Pipenvをインストール
  4. Pipenvで仮想環境を構築&ライブラリ管理

01.現在のPython環境を消去

の前に、TimeMachineでバックアップを取っておきました
では、早速削除していきます
どういう順番で削除していくべきか...
まぁ導入したときの逆順を追っていけばいいかな...? ということで

01.の手順

01-1. condaでcreateした名前付きの各環境とpyenvで入れたanacondaを削除
01-2. pyenv-virtualenvを削除
01-3. pyenvを削除


01-1. condaでcreateした名前付きの各環境を削除 anacondaの操作_参考
シェルで$ conda remove -n 名前 --allとすればよい
まずはtestから消してみる

$ conda remove -n test --all
Remove all packages in environment /Users/ユーザ名/.pyenv/versions/anaconda3-5.1.0/envs/test:

## Package Plan ##
  environment location: /Users/ユーザ名/.pyenv/versions/anaconda3-5.1.0/envs/test

The following packages will be REMOVED:

    ca-certificates: 2018.03.07-0           
    certifi:         2018.4.16-py36_0       
    libcxx:          4.0.1-h579ed51_0       
    libcxxabi:       4.0.1-hebd6815_0       
    libedit:         3.1.20170329-hb402a30_2
    libffi:          3.2.1-h475c297_4       
    ncurses:         6.1-h0a44026_0         
    openssl:         1.0.2o-h26aff7b_0      
    pip:             10.0.1-py36_0          
    python:          3.6.5-hc167b69_1       
    readline:        7.0-hc1231fa_4         
    setuptools:      39.1.0-py36_0          
    sqlite:          3.23.1-hf1716c9_0      
    tk:              8.6.7-h35a86e2_3       
    wheel:           0.31.0-py36_0          
    xz:              5.2.3-h727817e_4       
    zlib:            1.2.11-hf3cbc9b_2      

Proceed ([y]/n)? y

$ conda info -e
# conda environments:
#
base                  *  /Users/ユーザ名/.pyenv/versions/anaconda3-5.1.0
ml                       /Users/ユーザ名/.pyenv/versions/anaconda3-5.1.0/envs/ml

削除する環境の場所とパッケージが出てきた後、yを入力して消すことができた
この調子で仮想環境を全て消す
(testは純粋にpythonのみの環境なのでまだパッケージは少なかったが、anacondaを入れた環境mlはパッケージ数がめちゃくちゃ多かった) mlも削除した後、baseと書かれている環境も同様にconda removeしたら以下の通りエラー

$ conda remove -n base  --all
CondaEnvironmentError: cannot remove current environment. deactivate and run conda remove again

非アクティブ化してcondaを再度実行する...とでてきたのですが、調べてみてもよくわからなかったのと、当該環境はpyenvでインストールしたanacondaだったので、pyenvでアンインストールすればよいかと判断

$ pyenv uninstall anaconda3-5.1.0
pyenv: remove /Users/ユーザ名/.pyenv/versions/anaconda3-5.1.0?   y

$ pyenv versions
* system (set by /Users/ユーザ名/.pyenv/version)

これでmacに元々入っていたpythonだけになりました(たぶん)


01-2. pyenv-virtualenvを削除 参考

$ brew uninstall pyenv-virtualenv
Uninstalling /usr/local/Cellar/pyenv-virtualenv/1.1.1... (20 files, 60.6KB)


01-3. pyenvを削除 参考

$ which pyenv
/usr/local/bin/pyenv

$ brew uninstall pyenv
Uninstalling /usr/local/Cellar/pyenv/1.2.4... (603 files, 2.4MB)

$ which pyenv
表示なし


macに元々入っていたpythonしかないことを確認

$ which python
/usr/bin/python

OK

02. Python3をインストール The Hitchhiker’s Guide to Python

& brew install python
See: https://docs.brew.sh/Homebrew-and-Python
==> Summary
🍺  /usr/local/Cellar/python/3.7.0: 4,788 files, 102.2MB

$ which python3
/usr/local/bin/python3

$ python3 --version
Python 3.7.0

OK
Python3.7がリリースしたので最新が入りました

03. Pipenvのインストール Pipenv公式ドキュメント, 参考

$ brew install pipenv

==> Summary
🍺  /usr/local/Cellar/pipenv/2018.7.1: 1,358 files, 18.8MB

04. Pipenvで仮想環境を構築&ライブラリ管理 参考

私はカレントディレクトリの後にpytho_1という名前のディレクトリがあり、そこにpythonファイルを置いているので、ひとまずそこに移動します
その後pipenvによって仮想環境を作ります

$ cd pytho-1
$ pipenv install #仮想環境を作成
Creating a virtualenv for this project...
Pipfile: /Users/ユーザ/pytho_1/Pipfile
Using /usr/local/Cellar/pipenv/2018.7.1/libexec/bin/python3.7 (3.7.0) to create virtualenv...
⠋Already using interpreter /usr/local/Cellar/pipenv/2018.7.1/libexec/bin/python3.7
Using real prefix '/usr/local/Cellar/python/3.7.0/Frameworks/Python.framework/Versions/3.7'
New python executable in /Users/ユーザ/.local/share/virtualenvs/pytho_1-kjK1izHD/bin/python3.7
Also creating executable in /Users/ユーザ/.local/share/virtualenvs/pytho_1-kjK1izHD/bin/python
Installing setuptools, pip, wheel...done.
Setting project for pytho_1-kjK1izHD to /Users/ユーザ/pytho_1

Virtualenv location: /Users/ユーザ/.local/share/virtualenvs/pytho_1-kjK1izHD
Creating a Pipfile for this project...
Pipfile.lock not found, creating...
Locking [dev-packages] dependencies...
Locking [packages] dependencies...
Updated Pipfile.lock (a65489)!
Installing dependencies from Pipfile.lock (a65489)...
  🐍   ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 0/0 — 00:00:00
To activate this project's virtualenv, run pipenv shell.
Alternatively, run a command inside the virtualenv with pipenv run.

すると当該ディレクトリに以下のファイルが生成される

Pipfile  Pipfile.lock

中を見てみる
* Pipfile

$ cat pipfile
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]

[dev-packages]

[requires]
python_version = "3.7"

バージョンが書いてあった。よかったpython2じゃなかった
* Pipfile.lock

$ cat pipfile.lock
{
    "_meta": {
        "hash": {
            "sha256": "7e7ef69da7248742e869378f8421880cf8f0017f96d94d086813baa518a65489"
        },
        "pipfile-spec": 6,
        "requires": {
            "python_version": "3.7"
        },
        "sources": [
            {
                "name": "pypi",
                "url": "https://pypi.org/simple",
                "verify_ssl": true
            }
        ]
    },
    "default": {},
    "develop": {}
}

JSON形式になっているpipfile.lockにはパッケージが書き込まれる模様

  • 作成した環境にパッケージをインストールしてみる
$ pipenv install requests

仮想環境でpyを実行する方法

  1. 作成した仮想環境をactivateする
  2. pipenv run pythonで実行

  3. 作成した仮想環境をactivateする

$ python --version
Python 2.7.10
$ pipenv shell #仮想環境に入る
Launching subshell in virtual environment…
bash-3.2$  . /Users/ユーザ/.local/share/virtualenvs/pytho_1-kjK1izHD/bin/activate
(pytho_1-kjK1izHD) bash-3.2$ python --version
Python 3.7.0

$ cd weather_pop
$ python3 weather_pop.py

(pytho_1-kjK1izHD) bash-3.2$ exit #シェルから抜ける


2. pipenv run pythonで実行

$ pipenv run python3 #requestsがインストールされている対話モードが実行
$ pipenv run python3 weather_pop.py #weather_pop.py(requestsを用いる)が実行された

pipenvを導入して、モジュールをインストール、環境を切り替えるまではできるようになりましたが正直わからないことだらけなので勉強しなきゃ...

毎朝自動で降水確率を教えてくれるプログラムを作りました

こんにちは
この度初めてイチからプログラミングをしてみました

毎朝7:30、その日の6時間ごとの降水確率をmacOSの通知機能で教えてくれるプログラムです
感動を忘れぬようブログに掲載したいと思います

コード

import requests, xml.etree.ElementTree as ET, os

# XMLをwebから取得
def get_xml():
    r = requests.get('https://www.drk7.jp/weather/xml/08.xml')
    r.encoding = r.apparent_encoding
    return r.text

a = get_xml()
# XMLから降水確率を取得
def get_rainfallchance():
    root = ET.fromstring(a) #パース
    area = root.findall(".//area[@id]")  #北部と南部
    south = area[1] #南部エリアのノード
    info = south.findall('.//info[@date]') #南部の7日分
    today = info[0] #南部の今日の分のノード
    period = today.findall('.//period[@hour]') #南部の今日の降水確率(6時間毎)
    return period

#取得した降水確率をテキスト化してリストにappend
rain = get_rainfallchance()
ch = []
for i in rain:
    ch.append(i.text)
    
#通知センターに表示
os.system("osascript -e 'display notification\"{0[0]}%     {0[1]}%     {0[2]}%     {0[3]}%\" with title \"今日の降水確率\"subtitle\"00-06  07~12  13-18  19-24\"'".format(ch))

これを実行すると以下のように通知が出てきます

https://twitter.com/Cerven12/status/1011554159282143232

こんなかんじ

使わせて頂いた気象情報のデータ

今回は、以下のブログ様にて気象庁の気象情報をXML化したものを配布していたので、ありがたく使わせていただきました

気象庁の天気予報情報を XML で配信 - drk7jp

当該XMLは以下のような情報が入っています
(私は茨城県の南部在住のため茨城県XMLを使いました)
f:id:Atc:20180626194353j:plain
茨城の中に南部北部
南部の中に7日分の気象情報
各日毎に、風速気温等が入っています
当該XMLは毎朝6時頃に更新されるらしいので、私が取得すべき情報は当日分の6時間ごとの降水確率となります
つまりコレ↓

<Response [200]>
<?xml version="1.0" encoding="UTF-8"?>
<weatherforecast>
<title>weather forecast xml</title>
<link>http://www.drk7.jp/weather/xml/08.xml</link>

中略

</area>
    <area id="南部">
    <geo>
        <long>140.1714</long>
        <lat>36.1234</lat>
    </geo>
    
    <info date="2018/06/26"> ←操作日
        <weather>くもり</weather>
        <img>http://www.drk7.jp/MT/images/MTWeather/200.gif</img>
        <weather_detail>南西の風 のち 南の風 海上 では 南の風 やや強く くもり 所により 霧</weather_detail>
        <wave>波 1.5メートル のち 2メートル</wave>
        <temperature unit="摂氏">
        <range centigrade="max">31</range>
        <range centigrade="min">19</range>
        </temperature>
        <rainfallchance unit="%">
        <period hour="00-06">0</period> ←コレ
        <period hour="06-12">0</period> ←コレ
        <period hour="12-18">0</period> ←コレ
        <period hour="18-24">10</period> ←コレ
        </rainfallchance>
    </info>

コレを取得して、後はうまい具合お知らせしてくれるようにすればOK

各コードの解説

import requests, xml.etree.ElementTree as ET, os

3つのモジュールを呼び出しています
requestsはwebから情報を取得できるモジュール
osは使用してるos依存の機能をpython越しで使えるモジュール
xml.etree.ElementTreeXMLファイルやテキストを解析して辞書型的なノリで扱えるようにするモジュール

# XMLをwebから取得
def get_xml():
    r = requests.get('https://www.drk7.jp/weather/xml/08.xml')
    r.encoding = r.apparent_encoding
    return r.text

関数get_xml()を定義してます
requestsモジュールで先程のXMLの情報を取得してます
そのままテキスト化したら文字化けしたので、.apparent_encodingエンコーディングしてます
テキスト化されたxmlを返します
requestsについては以下のとおり学びました

atc.hateblo.jp


a = get_xml()
# XMLから降水確率を取得
def get_rainfallchance():
    root = ET.fromstring(a) #パース
    area = root.findall(".//area[@id]")  #北部と南部
    south = area[1] #南部エリアのノード
    info = south.findall('.//info[@date]') #南部の7日分
    today = info[0] #南部の今日の分のノード
    period = today.findall('.//period[@hour]') #南部の今日の降水確率(6時間毎)
    return period

テキスト化したXMLを変数aに入れ、新たに定義した関数の中でaをパースして辞書型的なノリで使えるようにしてます
ここらへんのコードゴッチャゴチャですが、初めてなので仕方ない?
その後、公式ドキュメント20.5.2.2. サポートされている XPath 構文にある方法で、今日の降水確率にジリジリ迫ってきています
そして南部の今日の降水確率が格納された変数periodを返してます

#取得した降水確率をテキスト化してリストにappend
rain = get_rainfallchance()
ch = []
for i in rain:
    ch.append(i.text)

取得した今日の降水確率をテキスト化してリストに入れています

#通知センターに表示
os.system("osascript -e 'display notification\"{0[0]}%     {0[1]}%     {0[2]}%     {0[3]}%\" with title \"今日の降水確率\"subtitle\"00-06  07~12  13-18  19-24\"'".format(ch))

os.system()の引数にAppleScriptdisplay notificationコマンドを入れてmacOSの通知センターに通知を出してます
タイトルに今日の降水確率の文字列、サブタイトルに時間帯、メインに降水確率を出力しているのですが、いい具合に時間帯の下に降水確率が来るように調整していたらすきっ歯になりました
なお、os.system()については以下のとおり学んでます

atc.hateblo.jp

後は定期的に実行する

作成したpythonファイルを定期的に実行させたいと思い調べたところ、cronという言葉がでてきました

cronとは? Wikipedia

デーモン(Deamon)と呼ばれるUnix系OSにおいてバックグラウンドで動作するプロセスのうち、コマンドを定期実行するもの。crontabというファイルに定期実行するものの一覧を記録している。

何ができる?

コマンドやファイルなどを定期的に実行させることができる
使い方については以下を参考にしました

qiita.com

実践

  • 編集開始
$ crontab -e

まだ設定していないので何もなし
f:id:Atc:20180626185210p:plain

  • 設定方法は以下の通り
(分)(時)(日)(月)(曜日) 実行するファイルの絶対パス

毎朝7:30なので以下の通り設定
cronは実行される度/var/mail/にメールが溜まっていくらしいので、メールが来ないようにも設定

MAILTO=''
30 7 * * *  python3 /Users/○○○/python/weather_pop/weather_pop.py
  • ちょっと詰まったところ
    ターミナルで上記コマンドを打ち終えたところ、どうやって設定を保存するのかわからなくなってしまった...
    viエディタはaで入力モード、escでコマンドモードに移る
    コマンドモードで:wとすることで保存
    :qとすることでviエディタが終了となる
    以下を参考にしました

https://oxynotes.com/?p=3844

おわりに

毎朝降水確率を教えてくれるようになりました
これで安心して布団を干せます. はじめてイチから作ってみましたが、学ぶ度自分の目標に近づく感覚が楽しかったです
次は何作ろ


追記 crontabの設定 2018/06/27

本日7時30分wktkしていたのですが実行されなかったのでMAILTOの設定を消してもう一度7:35に実行させ、ログを取得したところ/bin/sh: python3: command not foundとでていました
パスが通っていないところにpython3があるっぽいので、crontabにフルパスでpython3の在り処を指定してあげたら実行されました ↓参考

ja.stackoverflow.com

30 7 * * * /Users/○○○/.pyenv/shims/python3 /Users/○○○/python/weather_pop/weather_pop.py

なお、python3の在り処は下記コマンドで取得可

$ which python3