pyqueryとBeautifulSoupの比較
lambdaにてpyqueryを使ったものをアップロードして利用しようとしたところ、エラーが発生してしまいました。pyquery中のetreeというパッケージが存在しないとのこと。おそらくetreeのデータをsite-packagesの中に組み込めば動くようになると考えられるのですが、普通の行動してエラー吐かれた事実に面倒くささを感じ、スクレイピングを行うapiをBeautifulSoupに変更することにしました。その時に発生した変更分などから、どんな違いがあるのか簡単まとめてみました。
スクレイピングの準備
pyquery
from pyquery import PyQuery url = 'https://…' query = PyQuery(url, parser='html')
BeautifulSoup
from urllib.request import urlopen from bs4 import BeautifulSoup url = 'https://…' soup = BeautifulSoup(urlopen(url), 'html.parser')
タグによる指定
pyquery
query(‘header’)
BeautifulSoup
soup.find_all('header')
idによる指定
pyquery
query('#a')
BeautifulSoup
soup.find_all(id='a')
class名による指定
pyquery
query('.clazz')
BeautifulSoup
soup.find_all(class_='clazz')
属性による指定
pyquery
query('[href=\'https://….\']')
BeautifulSoup
soup.find_all(attrs={'href':'https://….'}) soup.find_all(href='https://….')
中身を取得する
pyquery
element.attr('href') element.attr('class')
BeautifulSoup
element.get('href') element.get('class')
要素の中の文章を取得する
pyquery
element.text()
BeautifulSoup
element.text
少しコメント
pyqueryの指定方法は、cssセレクタのようです。一方、BeautifulSoupは使えないのかというとそういうわけではありません。selectというmethodを使えば、cssセレクタによる指定も可能です。
まとめ
YOLPを使って1時間以内の雨予報を取得する
休日なので外に洗濯物干してひきこもってたのですが、知らないうちにあめがふってたぽい?なことがありました。 そこで1日の天気予報とかではなく、もっと1時間とかの近い時間帯について雨の情報をおしらせしてくれるものがほしいなと思いました。 まずは、お手軽に小さい単位での気象情報を取得できるAPIがないか調べてみました。すると、Yahoo! Open Local Platform (YOLP)に気象情報APIというAPIを発見!2時間前から1時間後までの降水量を10分間隔で取得できるらしいことがわかったので、こちらを利用させてもらうことにしました。 developer.yahoo.co.jp
YOLPとは
地図・地域情報などのAPIやSDKです。地図を扱ったサービスがメインなようですが、今回利用した気象情報やその他にも郵便番号検索や経路探索などもできたりと幅は広そうです。
YOLPを利用するために
まずはapplication id を取得します。下記のサイトから手順を追えば、取得できます。
https://developer.yahoo.co.jp/webapi/map/openlocalplatform/v1/weather.html
1時間以内の雨情報を取得してみた
parameters
key | value | 必須 |
---|---|---|
appid | さっき取得したapplication id | * |
coordinates | 経度、緯度のリスト | * |
output | 出力方法 json or xml(デフォルト) | |
past | 1時間前の降水量を取得する デフォルトは0になってる。2時間前まで指定できる | |
interval | 10分毎 or 5分毎で指定できる |
コード
下記が取得するためのコードです。
from urllib.request import urlopen from urllib.parse import urlencode import json class WeatherClient: BASE_URL = "http://weather.olp.yahooapis.jp/v1/place" def __init__(self, app_id, coordinates): """ :param app_id: your YOLP app_id :param coordinates: tuple(longitude, latitude) :type app_id: str :type coordinates: tuple[float] """ self.app_id = app_id self.coordinates = coordinates def get(self): url = "{}?{}".format(self.BASE_URL, urlencode({ "appid": self.app_id, "coordinates": self.coordinates, "output": "json", "past": 1 })) with urlopen(url) as res: return json.loads(res.read().decode('utf-8')) if __name__ == '__main__': print(WeatherClient("xxxxxxxxxxxxxxxxxxxxxxxxxx", (139.767125, 35.681236)).get())
取得できたjson
{ "ResultInfo": { "Latency": 0.006118, "Total": 1, "Status": 200, "Description": "", "Count": 1, "Copyright": "(C) Yahoo Japan Corporation.", "Start": 1 }, "Feature": [ { "Name": "\u5730\u70b9(139.76712,35.681236)\u306e2018\u5e7412\u670822\u65e5 20\u664230\u5206\u304b\u3089120\u5206\u9593\u306e\u5929\u6c17\u60c5\u5831", "Geometry": { "Coordinates": "139.76712,35.681236", "Type": "point" }, "Id": "201812222030_139.76712_35.681236", "Property": { "WeatherList": { "Weather": [ { "Date": "201812222030", "Type": "observation", "Rainfall": 0.0 }, { "Date": "201812222040", "Type": "observation", "Rainfall": 0.0 }, { "Date": "201812222050", "Type": "observation", "Rainfall": 0.0 }, { "Date": "201812222100", "Type": "observation", "Rainfall": 0.0 }, { "Date": "201812222110", "Type": "observation", "Rainfall": 0.0 }, { "Date": "201812222120", "Type": "observation", "Rainfall": 0.0 }, { "Date": "201812222130", "Type": "observation", "Rainfall": 0.0 }, { "Date": "201812222140", "Type": "forecast", "Rainfall": 0.0 }, { "Date": "201812222150", "Type": "forecast", "Rainfall": 0.0 }, { "Date": "201812222200", "Type": "forecast", "Rainfall": 0.0 }, { "Date": "201812222210", "Type": "forecast", "Rainfall": 0.0 }, { "Date": "201812222220", "Type": "forecast", "Rainfall": 0.0 }, { "Date": "201812222230", "Type": "forecast", "Rainfall": 0.0 } ] }, "WeatherAreaCode": 4410 } } ] }
今回取得したいのは降水量のところなので、response["Feature"][0]["Property"]["WeatherList"]["Weather"]で取れそうです。
今回の制作物
雨情報が取得できたら、slackに投げるものをつくってみました。
まとめ
記事書いててはっとしたんですが、もしかすると、降水量は実測値しか入ってないかもなので予報のところはすべて0.0で入って返ってきてるかもです。5分以内の情報ならギリ使える。。頻度多めにapiを叩かないとですね。
pythonの参照型をデフォルト引数にすることとは
javaをメインにお仕事してたので、なかなかお目にかかることのなかったデフォルト引数 (javaにはない) 。最近pythonを書いていて、たまたまデフォルト引数にlistをいれてみようかなと思って書いてみたら、intelliJさんに黄色くされたので気になって調べてみることに。
デフォルト引数について思うこと
そもそもデフォルト引数とは引数の初期値。
javaだと引数減らした同じ名前のmethodをもう一つ作ってoverloadというかたちをとったり、場合によってはnull判定をしてnullだったらこれ入れるみたいなことをしてたのが、デフォルト引数で全て片付くのでコードがすっきりするので存在に感謝している。
デフォルト引数は使い回す。
print_sampleの引数としてもらったsのidを出力するというコードを書きました。
def print_sample(s=1): print(id(s)) if __name__ == '__main__': print_sample() #1 print_sample(2) #2 print_sample() #3
出力
4353093824 #1 4353093856 #2 4353093824 #3
出力を見ていただけるとわかるようにデフォルト引数を使うことになった#1,#3でidが同じことがわかります。このことから、デフォルト引数が毎度生成されているわけではないということが言えそうです。
デフォルト引数にlistを入れるということは。。。
デフォルト引数にlistをいれるなんて、なんて恐ろしいことなんだと気がつきました。
さきほどデフォルト引数が毎度生成されていないということがわかったので、つまるはなし参照型のものをデフォルト引数にしてしまうとlistに要素を追加するなどのデータが書き換えがあった場合に、次にデフォルト引数を使うときには中身が想定のもとは異なってしまうということです。
またこのmethodを実際に使うことを考えると、以前にどんな呼び出され方をしたのか意識して使わなければいけないということです。ああ、なんておそろしいんだ。。
恐ろしさを実感するコードが以下になります。
def print_list(n, l=[]): print(l) l.append(n) if __name__ == '__main__': print_list(2) print_list(3) print_list(0)
出力
[] [2] [2, 3]
デフォルト引数のlistに要素が増えていることがわかります。
まとめ
intelliJのwarningは聞いておくものだなとしみじみと感じました。これからはもうちょっとintelliJさんの声に耳を傾けようと思います。
macでcron的なことをする
macで定期実行はcronではなく、launchdがおすすめ
macでもcronは使えるらしいが、launchdを利用することが推奨されているらしいので、launchdで書きます。
今回動かすコマンド
$ /bin/sh path/to/test1.sh
のコマンドを動かしてもらいます。
念のためfull pathで/bin/sh
指定しました。
コード
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>test1</string> <key>ProgramArguments</key> <array> <string>/bin/sh</string> <string>/path/to/test1.sh</string> </array> <key>StartCalendarInterval</key> <dict> <key>Hour</key> <integer>7</integer> <key>Minute</key> <integer>10</integer> </dict> <key>StandardOutPath</key> <string>/path/to/out.log</string> <key>StandardErrorPath</key> <string>/path/to/err.log</string> </dict> </plist>
ファイルの置き場所
用途 | Directory |
---|---|
ユーザーごとの設定 | ~/Library/LaunchAgents/<Label>.plist |
全てのユーザーの設定 | /Library/LaunchAgents/<Label>.plist |
システム共通設定 | /Library/LaunchDaemons/<Label>.plist |
- LaunchAgents : ログインしているときに動くもの
- LaunchDaemons : ログインしてなくても動くもの
ファイル名の<Label>とファイル中の<Label>を一致させる。今回の場合はtest1
。
コマンドの指定
今回動かしてもらおうと思っていたコマンドを空白区切で配列としてProgramArgumentsをkeyにして記入する。
<key>ProgramArguments</key> <array> <string>/bin/sh</string> <string>/path/to/test1.sh</string> </array>
イベントのタイミング
今回は毎日同じ時間にプログラムを動かして欲しいので、時間指定しました。
<key>StartCalendarInterval</key> <dict> <key>Hour</key> <integer>7</integer> <key>Minute</key> <integer>10</integer> </dict>
数秒ごとにプログラム実行も可能。その場合はStartCalendarInterval
の代わりにStartInterval
を利用する。この時の単位は秒です。
<key>StartInterval</key> <integer>300</integer>
その他にもファイル監視をすることもできるようです。 下記urlに詳しく書いてあります。
Creating Launch Daemons and Agents
登録と解除
ユーザーがログインしている時に動かしたいので、pathは~/Library/LaunchAgents/test1.plist
にしました。
登録する
$ launchctl load ~/Library/LaunchAgents/test1.plist
解除する
$ launchctl unload ~/Library/LaunchAgents/test1.plist
golangでファイルパスを扱う際に気をつけたいこと
きっかけ
golangの標準ライブラリを使って、ファイルパスを扱うコードを書きました。自分はMacを使っていたので問題はなかったのですが、同じコードをWindowsに持っていったら、ファイルパスがうまく取れない事態が発生。あまりにもびっくりだったので、ここでまとめます。
path (os.path)のファイルセパレーターは/
だった
以下はpath.Splitのコードです。
func Split(path string) (dir, file string) { i := strings.LastIndex(path, "/") return path[:i+1], path[i+1:] }
まさかのファイルセパレーターが/
で指定されていました。Windowsの場合、ファイルセパレーターは\
なのでここで見つからずに.
になってしまっていました。
path.filepathを使おう
goの標準ライブラリにはpathで実装されているmethodを新たに実装してるものがあります。path.filepath
です。
さきほど例に挙げたSplitはfilepathでは以下のようになっています。
func Split(path string) (dir, file string) { vol := VolumeName(path) i := len(path) - 1 for i >= len(vol) && !os.IsPathSeparator(path[i]) { i-- } return path[:i+1], path[i+1:] }
こちらはセパレーターはosによって変化するようになっています。ファイルを扱う際にはこちらを使いましょう。
ちなみに
pathはもしや、urlの処理に使えるのではと頭によぎったので、入れてみました。
func main(){ url := path.Join("http://localhost:8080","app") fmt.Println(url) // http:/localhost:8080/app }
まさかのhttp://
からスラッシュが1つ削られて使えなくなりした。
path.Cleanというmethodによって不要なセパレーターは排除されてしまうようです。
まとめ
まさか標準ライブラリに翻弄されるとは思いませんでした。
path - The Go Programming Languageを見に行ってみるとurlで記述するようなスラッシュを使っていて、Windowsには対応してません。
と書いてありました。Macのファイル操作にしか使わないものにしか使えなさそうですね。
.gitignoreを変更せずに、ファイルを無視したい
動機
この度、intelliJのUltimate版を購入したのがきっかけで、.gitignoreファイルに.ideaを追記したくなったのが事の発端。 しかし、自分の事情だけでファイルを変更するのは、あまり好ましくありません。 ということで、他の方法で対象から外す方法を調査しました。
あった
git cloneや、git initをしたときにできる .git ディレクトリの中にありました。
.git/info/exclude
ファイル(repositoryからの相対path) です。ここに、.gitignoreと同じように記述することで、そのファイルらを対象から外すことができました。
before
$ git status On branch master Untracked files: (use "git add <file>..." to include in what will be committed) .idea/ my_test.iml nothing added to commit but untracked files present (use "git add" to track)
さっそく追記します
vimで開いて編集しました。
# git ls-files --others --exclude-from=.git/info/exclude # Lines that start with '#' are comments. # For a project mostly in C, the following would be a good set of # exclude patterns (uncomment them if you want to use them): # *.[oa] # *~ .idea #追記 *.iml #追記
after
$ git status On branch master nothing to commit, working directory clean
みごと、gitから.idea/と.imlファイルが無視されるようになりました。
参考させていただいたページ
pythonのプロジェクトごとにパッケージを管理する
動機
Pythonをお仕事でも使うことになり、自分の環境だけでなく、他の環境でもすぐ動くようにする必要が出てきたので、pyenv-vertualenvを利用して環境構築をしました。その際の覚え書きです。
まずはインストール(Mac)
Homebrewを使って、pyenvとpyenv-virtualenvをインストールします。
$ brew install pyenv $ brew install pyenv-virtualenv
必要なpythonのバージョンをインストール
まずはpyenvでインストールできるPythonのバージョンを確認してみます。
$ pyenv install --list
先ほどの一覧からインストールしたいバージョンを選びインストールを行います。今回は3.6.5をインストールすることにしました。
$ pyenv install 3.6.5
インストールができたら、実際にpyenvに入ったかどうか確認します。
$ pyenv versions
ディレクトリにpythonの仮想環境を作る
これからpythonの開発を行おうとしているディレクトリに移動し、そのディレクトリで利用するpythonのversionを指定します。
$ cd projectのディレクトリ $ pyenv local 3.6.5 #指定できたことの確認 $ python --version Python 3.6.5
pythonの環境を現在のディレクトリ配下のvenv(2つ目のvenvがディレクトリ指定になっています)に作成します。
$ python -m venv venv --copies
現在のディレクトリにて先ほど作成した環境を適用する。
$ source venv/bin/activate # 上記コマンド実行の結果、下記のようにvenvが現れれば成功です。 (venv) $
パッケージの管理
パッケージ管理に必要なツールのインストールを行います。
$ pip install pip-tools
requirements.inに利用しようと思っているパッケージを記入します。
Django
requirements.inに書かれたパッケージと依存関係のあるパッケージをrequirements.txtに書き出します。
(venv) $ pip-compile requirements.in
# # This file is autogenerated by pip-compile # To update, run: # # pip-compile --output-file requirements.txt requirements.in # django==2.1.2 pytz==2018.5 # via django
djangoと依存関係があるらしいpytzも記入してくれました。
最後に書き出したパッケージのインストールを行います。
(venv) $ pip-sync
思ったこと
自分のPCでプロジェクトディレクトリにpythonコピーして、最終的にpip-syncをしたら、Uninstallが恐ろしいほど出てきました。今までこれだけのパッケージを統一的に扱っていたと思うと恐怖でしかありません。。