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を使って自動化してみました
本記事ではその際に手こずった点をあげていこうと思います

Seleniumを使うには最低限のHTMLの知識を要するため、事前に簡単な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%にしてあげれば解決。 普段のブラウザ操作では頻繁に拡大表示するので高確率でエラーがでちゃいます。

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

  • エラー
要求された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システムで業務を行っていると、新規ウィンドウが立ち上がります。普段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に存在しない要素を指定して情報を取得しようとしても、エラーになってしまいます

  • 切り換え方法
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を書きました。 完成したイントラを職場内に公開する前に、総務省の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を使えばアクセシビリティの知識がなくても、検証結果の問題箇所を改善するだけで、手軽に利便性を高めることができると感じました。行政のみならず一個人にとっても、コンテンツをより多くの人に使ってもらえるチャンスが増える便利なツールなのではと思います。

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

こんにちは

毎朝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に入れ、パースします。
その後、公式ドキュメント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エディタが終了となる
    以下を参考にしました

Linuxの鬼門、viエディタの基本的な操作方法と実用的なコマンド | OXY NOTES

おわりに

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


追記 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

pythonからmacOSの通知センターを操る方法

pythonで出力する文字列をmacの通知で表示したいと思ってます
python mac 通知で検索した結果いくつか選択肢が出てきたので整理してみます
(逐次参考にさせて頂いたサイトを載せて行きます)

選択肢

  1. terminal-notifier github.com macOSの通知を操作できるコマンドラインツール

  2. Alerter dev.classmethod.jp macOSの通知を扱えるコマンドラインツール

  3. AppleScriptのdisplay notificationコマンドとosモジュール qiita.com

選んだのは、3.

コマンドラインツールをインストールすれば簡単に実現できそうですが、AppleScliptが色々できて面白そうなのでこれを機に調べてみたくなりました

さて、AppleScliptを用いて「pythonからmacOSの通知を操る方法」は、一通り調べた結果以下のような手順を踏めばできそうです

1. pyhonでos.system()の引数にosascriptコマンドを入力して実行
2. サブシェルでosascriptコマンドが実行される
3. AppleScliptのdisplay notificationが実行される
4. macOSの通知に文字列が表示される

ひとつずつ調べていこう

1. os.system(COMMAND)とは?

os --- 雑多なオペレーティングシステムインタフェース — Python 3.7.2 ドキュメント

os.systemはpythonに標準で入っているosモジュールで使える
公式ドキュメントに沿ってosモジュールをお勉強しようと思ったのですが、わからない単語の嵐で全然理解できなかったので、今回はos.system()についてのみ調べます。

以下公式ドキュメントから引用

サブシェル内でコマンド (文字列) を実行します。この関数は標準 C 関数 system() を使って実装されており、system() と同じ制限があります。sys.stdin などに対する変更を行っても、実行されるコマンドの環境には反映されません。command が何らかの出力を生成した場合、インタープリターの標準出力ストリームに送られます。


分からない単語を列挙して1つずつ

* サブシェル
sub shell ( csh )
コマンド:鐃緒申鐃瞬ワ申鐃緒申鐃緒申: UNIX/Linuxの部屋

シェル内でコマンドを()で囲って実行することで起動するシェル。これによって起動したプログラムを子プロセスと呼ぶ(起動した側は親プロセス)。子プロセスで別ディレクトリに移動しても、子プロセスが終了すれば親プロセス=つまり元のディレクトリに戻ってくる


* 標準C関数system()
シェルコマンドを実行できるC言語の関数


* sys.stdin
29.1. sys — システムパラメータと関数 — Python 3.6.5 ドキュメント,
algorithm.joho.info

pythonでキーボードからの標準入力ができる


* 標準出力ストリーム
nononochi.hatenablog.jp
標準ストリーム:Unix系OSでプロセスと端末間の接続としてはじめから接続されている入出力のチャネル(チャネル=データの通路)
標準入力(stdin) デフォはキーボード
標準出力(stdout) デフォはディスプレイ
標準エラー出力(stderr) デフォはディスプレイ
と3つの入出力がある

つまり?

pythonのos.system('COMMAND')は、引数に入力された文字列を子プロセスとして実行する。この関数はC言語のsystem()関数を使って実装されており当該関数と同様の制約がある。引数に入力した文字列により何かしらの出力が発生したら、それはインタプリタに出力される

→ということは、os.system()を使えばpythonbashのコマンドであるlsやcdが使えるし、cdでディレクトリを移動してもpythonのプログラムが終了すれば元のディレクトリに戻るのでしょうか。

2. osascriptコマンドとは?

コマンド/osascript - MacWiki

ターミナルでAppleScliptを実行するためのコマンド
osascript <ファイル>コマンドラインからスクリプトファイルを呼びだして実行できたり、osascript -e 'コード'でコードを入力することでも実行できる。


3. AppleScliptのdisplay notificationとは?

alvinalexander.com

通知センターに通知できるAppleScliptのコマンド

まとめ

os.system('COMMAND')の引数にosascript -e 'display notification "出力する文字列"'と入力することでpythonから通知センターの通知を操れるのだ

試しにAppleScliptで遊んでみる

pythonからdisplay notificationする前にちょっとAppleScliptで遊んでみたいと思います。
このサイトを参考にしました

鳶嶋工房 / AppleScript / 入門 / AppleScriptってなに?

当該サイトではスクリプトエディタからファイルを新規作成してますが、めんどくさかったのでターミナルからosascriptコマンドで実行してみます

環境

Mac book pro (13-inch, 2017, Two Thunderbolt 3 ports)
macOS High Sierra ver10.13.4

  • ビープ音
$ osascript -e 'beep 2'  #2回鳴る
$ osascript -e 'beep 4'  #4回鳴る
  • ダイアログ
$ osascript -e 'display dialog "Hello World"'
button returned:OK

f:id:Atc:20180625184648p:plain
画像でけぇ...

  • 通知
osascript -e 'display notification "こんにちは世界"'

f:id:Atc:20180625185149p:plain


pythonから実行してみる

>>> import os
>>> a = 'Hello'
>>> os.system("osascript -e 'display notification\" {}\"'".format(a))
0

↓↓
f:id:Atc:20180625185836p:plain
やったぜ〜〜!!
ちなみに出力結果の「0」はコマンドが成功した際に返されるそうです

pythonからwebにアクセスして情報を取得するために_requests

はじめに

WEBから降水確率を取得して毎朝デスクトップ上に通知させたいと思ってます
YahooAPIを取得してGithubリポジトリを登録するまで気合いMAXで行いましたが、 YahooAPIでは60分後までの降水確率しか取得できないと判明...

天気予報は洗濯物のために使ってる私は1日分欲しいので色々調べた結果、
気象庁が公開している気象情報をXMLにしてくれている方がいました。
これなら6時間毎の降水確率がわかりそう

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

ということでこちらのページにアクセスして情報を取得したいと思います。
まずpython web xml 取得で検索して色々調べた結果urllibとかrequestsとかいうモジュールを使えば良さそう
これらのモジュールは何ができるのか、何を得られるのかを調べてみます

WEBへのアクセスについての知識

インターネットへのアクセスは、
1. 私たちが使ってるWEBブラウザからWEBページが存在するWEBサーバにリクエストを送る
2. WEBサーバはリクエストに対してレスポンスを返す
3. そのレスポンスを元にWEBブラウザはページを開くことができる
という流れで行われているらしい

WEBでリクエストとレスポンスを行う為に、HTTPという通信プロトコル(約束事)が決められてる。そのためHTTPリクエスト、HTTPレスポンスとも呼ぶ

HTTPリクエス

HTTPリクエストは「リクエストライン」「リクエストヘッダ」「リクエストメッセージボディ」から構成される

  • ライン
    HTTPリクエストの1行目に書かれている
    [メソッド] [URL] [HTTPバージョン]で構成される
    メソッドにはPOST(値がボディにつく)やGET(値がURLにつく)があるらしい
    requestモジュールかなにかのリファレンスでPOSTかGETか指定できるメソッドがあったので実際使う場面が出てきたら覚えたい

  • ヘッダ
    HTTPリクエストの際のお願い事とかが書いてある

  • メッセージボディ
    HTTPリクエストの際のメモ書

HTTPレスポンス

HTTPレスポンスは「ステータスライン」「レスポンスヘッダ」「レスポンスボディ」から構成される

  • ライン
    HTTPレスポンスの1行目に書かれている [HTTPバージョン] [ステータスコード=結果] [ステータスコードの詳細]で構成される
    ステータコードはwikipediaに乗っている → wiki
    とりあえず200なら成功してるってことみたい

  • ヘッダ
    ファイルの情報を示す情報()とかが書いてあるところ

  • ボディ
    HTMLの中身が入ってる



    さて、webにアクセスする際の知識を得たところで、pythonに戻る
    webにアクセスできるurllibrequestはHTTP通信を行うためのモジュール
    HTTPレスポンスを得ることができるのかな?

urllibはpythonに最初から入っているがurllibの公式リファレンスによるとrequestのほうが使い勝手がいいみたい
requestを使ってみることにした

requestsモジュールの概要

Requests: 人間のためのHTTP — requests-docs-ja 1.0.4 documentation

ここからは公式リファレンスのクイックスタートガイドに沿ってrequestsを試してみる

requestsはurllibよりも人間にわかりやすいらしい
まずはインストールしてから呼び出す

$ pip install requests
import requests

URLを指定してURLレスポンスを取得

r = requests.get('https://www.drk7.jp/weather/xml/08.xml')
print(r)

取得したレスポンスをそのまま出力したらステータスコードがでた↓

<Response [200]>

取得したHTTPレスポンスで何ができるのか

  • レスポンスの内容 requestsはサーバから得た内容を勝手に復元(デコード)してくれる。 HTTPリクエストを作成したら、requestsはリクエストヘッダに入ってる情報に基づいて円コーディングする。エンコディングしたデータは変数.textで得ることができる
r.text

結果↓

<?xml version="1.0" encoding="UTF-8"?>
<weatherforecast>
<title>weather forecast xml</title>
<link>http://www.drk7.jp/weather/xml/08.xml</link>
<description>æ°è±¡åºã®å¤©æ°äºå ±æå ±ã XML ã§éä¿¡ã1æ¥1å AM 6:00 ããæ´æ°ã</description>
<pubDate>Thu, 21 Jun 2018 18:00:02 +0900</pubDate> 
<author>æ°è±¡åº</author>
<managingEditor>drk7.jp</managingEditor>
<pref id="è¨åç">

    <area id="åé¨">
    <geo>
        <long>140.4021</long>
.........

文字化けしてる!
ググって以下記事を拝見

kanji.hatenablog.jp

以下のとおり記入することで解決(詳しい原因は調べてない)

r.encoding = r.apparent_encoding
print(r.text)

↓↓

<?xml version="1.0" encoding="UTF-8"?>
<weatherforecast>
<title>weather forecast xml</title>
<link>http://www.drk7.jp/weather/xml/08.xml</link>
<description>気象庁の天気予報情報を XML で配信。11回 AM 6:00 ごろ更新。</description>
<pubDate>Thu, 21 Jun 2018 18:00:02 +0900</pubDate> 
<author>気象庁</author>
<managingEditor>drk7.jp</managingEditor>
<pref id="茨城県">

    <area id="北部">
    <geo>
        <long>140.4021</long>
        <lat>36.3980</lat>

OK~!


エンコーディング変数.eoncodingで調べることができる

r.encoding

↓↓

ISO-8859-1  #encodingにapparent_encodingを指定する前
utf-8 #指定した後


レスポンスのステータスコードを知ることもできる
ステータスコードの意味は先程書いたようにWikipediaにある

r.status_code

↓↓

200


レスポンスヘッダーをみる

r.headers

↓↓

{'Content-Type': 'text/xml', 'Accept-Ranges': 'bytes', 'ETag': '"3863184871"', 'Last-Modified': 'Thu, 21 Jun 2018 09:00:11 GMT', 'Content-Length': '8839', 'Date': 'Thu, 21 Jun 2018 10:03:43 GMT', 'Server': 'lighttpd/1.4.33'}

辞書で取得できるためキーを指定して値を取得できるようだが、大文字でも小文字でもOKみたい
特に今は使わなそうなのでハショリます

URLにパラメータを渡す

APIを使うときにパラメータを指定させることで特定の条件の商品などを取得できるみたい
今回は余裕がないのでまたあとで...
参考:http://bty.sakura.ne.jp/wp/archives/1157


とりあえず今回はxmlをテキスト化することができたので以上。
次はxmlの中の値を取得します