fc2ブログ

ぽよメモ

2015年09月

DATE

  • このページのトップへ

rsyncでファイルを同期する


botを作るにあたり、だいたいはThinkPadに入れたUbuntuで開発を行っていたのですが、結局動作させるのはRasPiなわけで、動かしてみてうまく行かなかった時細かい修正はRasPiにsshして行っていました。

するとRasPi側と開発環境側で齟齬が発生するようになってしまい、ちょっとややこしいことに。
そこでスクリプトが入っているフォルダごと同期してしまおうということになりました。

Linuxにはこういうとき超便利なrsyncというコマンドが用意されています。最高。
RasPiへのアクセスにはsshを用いてログインしrsyncを使うので、まずはsshの設定をします(省略)

-eを使うなど古い書き方になってしまいますがこれが普通です。
rsync -au -e "ssh -p 22 -i ~/.ssh/id_rsa" pi@192.168.10.50:/home/pi/bot ~/


-aは「元のパーミッションやグループなどを保持したまま同期(-rlptgoDと同義)」、-uは「追加されたファイルだけをコピー」、-eは「リモートシェルの指定(最近はsshがデフォらしいので指定しないで良い?)」です。
しかしシェルスクリプト内での rsync に ssh 鍵を指定するに書いてあるように、シェルスクリプト内では”ssh (以下略)”の部分は読み取ってもらえません。
つまりcronで定期的に回すなどの実装にはこのままでは不適だということです。

なので.bashrcにRSYNC_RSHという環境変数を設定します。
RSYNC_RSH="ssh -p 22 -i $HOME/.ssh/id_rsa"
あとはsource ~/.bashrcして、普通にrsyncします。
rsync -au pi@192.168.10.50:/home/pi/bot ~/

また、sshに公開鍵暗号を使う今回のような場合、cronを使って定期的に回したければ公開鍵と秘密鍵を作る際にパスをかけないようにしておく必要があります。

あとはこれを適当なファイルに書いて「〜〜.sh」といった感じで書き、適当にcronに投げればオッケーです。
今回はリモートフォルダからローカルに保存するように書きましたが逆も可能です。また、リモート↔ローカルだけでなく、ローカル↔ローカルやリモート↔リモートも可能です。

このとき注意するのはソースとなるディレクトリのパスの最後に/を入れるかどうかです。
#ディレクトリごと同期
rsync -au /path/to/remote /path/to/local

#ディレクトリ以下の内容のみ同期
rsync -au /path/to/remote/ /path/to/local
つまり最後に「/」をつけなければリモートにある「A」というディレクトリをローカルにある「B」を指定してrsyncしたとき、Bの中にAというディレクトリが出来ます。「/」をつけると、「A」というディレクトリの中身が「B」に展開されます。
これにさえ注意を払っておけば問題ないと思います。

その他のオプションについてはググればすぐに出てくると思いますので省略。場合によって--excludeや--deleteを上手く使えばかなり高度なファイル同期/バックアップとして使えると思います。

スポンサーサイト



カテゴリー:ぷよぐやみんぐ


tweepyを使って指定条件のツイートを削除


この前はrequestsとrequests-oauthlibを使ってpythonでツイートをしました。ツイートするだけなら楽勝なんですが、さすがにTLを取得してゴニョゴニョとかはなんかもう闇っぽかったのでtweepyを使ってみることにしました。
pip install tweepy
準備は完了です。
import tweepy

CK = 'xxxxxxxxxxxxxxxx'
CS = 'xxxxxxxxxxxxxxxxxxxxxxxxx'
AT = 'xxxxxxxxxxxxxxxxx'
AS = 'xxxxxxxxxxxxxxxxxxxxxxxxx'

auth = tweepy.OAuthHandler(CK, CS)
auth.set_access_token(AT, AS)

api = tweepy.API(auth)

#アカウントの情報を取得
myinfo = api.me()
#100ツイートほどTLを取得
tweets = tweepy.Cursor(api.user_timeline, id = myinfo.id).items(100)
TLを取得までいけました。このままprint tweetsすればそれはもう意味不明な文字列がたくさん表示されることでしょう。

上で示したように、myinfoというオブジェクトにはたくさんのデータが格納されています。同様に取得した情報にもたくさん情報が入っており、tweepyのAPIリファレンスを見てもさっぱり書いてないのでつらいんですが、Twitter DeveloppersのAPIリファレンスを読むと多少はわかるかと。例えば
#リストで返ってくるTLをforループにかける
for tweet in tweets:
#本文を取得
sentence = tweet.text #Twitter始めました

#ツイートのidを取得
id = tweet.id #145872304857203445452345(適当な数字です

#fav数、RT数を取得
rt_ct = tweet.retweet_count #0
fav_ct = tweet.favorite_count #1

#ツイートした人の名前及びID
name = tweet.user.name #ぷりん
sc_name = tweet.user.screen_name #pudding_info

#公式アカウントかどうか
kousiki = tweet.user.verified #False

#ツイート日時
date = tweet.created_at #2015-09-17 16:55:59

#via名
via = tweet.source #Twitter for iPhone
print type()すると各要素がどういう値かわかると思いますが、nameやtextはunicode、verifiedはboolean、日時はdatetime.datetime、RT数やfav数はintで返ってきます。
公式のリファレンスを読むとRT数はretweet_countなのにfav数はfavorites_countだとか、いやなんでやねんみたいなとこがたまにあって面白いです(全然読みたいとは思いませんが

休講通知botには、毎朝その日の休講情報をダイジェストでツイートする機能を実装したかったのですが、ただそれを実装するだけではどんどんダイジェストツイートが溜まっていってウザいなぁと思ったので、毎回それと一緒に前の日のダイジェストツイートを削除してやろうと言うわけで、TLを取得までした後、以下の様な実装を加えました。
ダイジェストツイートは「2015/9/28 本日の休講はありません」といった感じの仕様にしてあります。
import re
from datetime import datetime, timedelta

#前日の日付を取得
d = datetime.now() + timedelta(days = -1)
yesterday = '%s/%s/%s' % (d.year, d.month, d.day)

#本文に一致するものを探し、削除
for tweet in tweets:
r = re.compile(yesterday)
sentence = tweet.text.encode('utf-8')
m = re.match(r, sentence)
if m == None:
pass
else:
api.destroy_status(tweet.id)
実装してから正規表現である必要なくねとか思いましたがもう良いんです。

そのうちリプライに反応して休講情報とか持ってこれるようにしたいなぁ……

カテゴリー:ぷよぐやみんぐ


Pythonでツイートする


実は休講通知botは完成しました((超雑なコードで
結局本当にこれまでやって来たことを一つにまとめただけなのですが、一応動いてはいます()

今回はpythonでツイートする方法をまとめておきます。

twitterにツイートするまでにはまず「apps.twitter.com」にアクセスし、ツイートするクライアント(アプリケーション)を作成する必要があります。
これまでならすべてのアカウントで作成できたのですが、最近Twitter社のクソな仕様粋な図らいにより電話番号がひも付けされたアカウントでしかアプリケーションが作成できなくなりました。またツイート時に必要なアクセストークンはアプリケーションの作成画面から見ることができるのは、そのアプリケーションを作成したアカウントの分だけです。

当然電話番号は一つしか持っていません(大体の人はそうだと思います
メインのアカウントにひも付けされています(大体の人はそうだと思います

ど う す ん の

というわけでこういうサイトを発見しました→Twitter BOT作るときOAuthのAccess token取得するのがめんどいから簡単に取得できるwebサービスつくった
先人は偉大です。ありがたく使わせていただきました。

番号を紐付けしたアカウントでapps.twitter.comにログインしアプリケーションを作成

ログアウト後botで使うアカウントでログイン

先程のサイトを使ってアクセストークンを取得

これで使うアカウントの「Access Token(以下AT)」「Access token Secret(以下AS)」、そして作成したアプリケーションの「Consumer Key(以下CK)」「Consumer Secret(以下CS)」が揃いました。
ようやくpythonの方に入っていきます。
今回はTwitterのAPIを叩くためにOAuth認証というモノを使います。

これにはすでにライブラリが用意されており、
pip install requests
pip install requests-oauthlib
pipでインストールすることができます。

# coding: utf-8
from requests_oauthlib import OAuth1Session

CK = "xxxxxxxxxxxxxxxxxx"
CS = "xxxxxxxxxxxxxxxxxx"
AT = "xxxxxxxxxxxxxxxxxxx"
AS = "xxxxxxxxxxxxxxxxxxx"

# ツイート投稿のURL
url = "https://api.twitter.com/1.1/statuses/update.json"

def tweet(text):
# ツイート内容を以下に記述
params = {"status": text}

# OAuth認証して投稿
twitter = OAuth1Session(CK, CS, AT, AS)
req = twitter.post(url, params = params)

# 返ってくるコードを確認
if req.status_code == 200:
print ("ツイート完了")
else:
print ("ツイートできませんでした: %d" % req.status_code)

if __name__ == '__main__':
print tweet("はじめまして") #確認


提供されているAPI一覧はこのあたりを参照してください。

APIは一定時間に指定回数以上叩くとアプリケーションごと規制されてしまいます。連投は避けたほうが無難です。
本当は連投時に遅延処理を仕込んだほうが良いんですが、昨日はそれ以外の部分の実装で忙しかったので…(言い訳
そのうちやりますそのうち


カテゴリー:ぷよぐやみんぐ


SQLAlchemyを使ってみる


だいぶ前に学務課のwebページをスクレイピングしてデータを取り出す記事をかきました。これをいちいち通知していたら毎回何百という通知を見ることになってしまいます。

そこで、SQLiteを用いてこれらの情報をデータベースで管理することで、新規で追加されたもののみを通知するようにしていきたいと思います。

が、いかんせんSQLiteなんてさっぱりわかりません。SQL構文とかポカーンですよポカーン。
そこで、O/Rマッパー(以下ORM)というものを用います。こいつはデータベースとPythonの橋渡し役、コミュニケーションを取ってくれるやつです。

これを用いるメリットとしては「Pythonのプログラム内からSQL構文を排除できる」ということ(でいいのかな

様々なORMがあり、また自作する人も多くいますが、いまのところ最も用いられていそうなSQLAlchemyを使っていこうと思います。対応するデータベースが豊富で、かつ日本語での解説サイトも結構あるのでこいつが良いのかなと。

まずはモジュールのインストールから。
pip install sqlalchemy
完了です。

SQLAlchemyでは、Tableクラスとユーザ定義のマッピング対象クラスを作成し、mapper()を用いてこれらをマッピングするのが従来のやり方のようですが、正直難しいのでdeclalativeを用いて簡単な設定を書きます。もちろん従来のやり方にもメリットは有り、ORMとしての要素を可能なかぎり分離することができます(つまりdeclalativeではごっちゃになる可能性がある

from sqlalchemy import create engine, Column, Integer, String
from sqlalchemy.ext.declalative import declalative_base
from sqlalchemy.orm import sessionmaker

#DBへの接続(echoはログ機能の有無)
engine = create_engine('sqlite:///:memory:', echo=True)

Base = declalative_base()

class Qkou(Base):
__tablename__ = 'kyukou'

id = Column(Interger, primary_key=True)
subject = Column(String) #教科名
teacher = Column(String) #担当教員
week = Column(String) #曜日
period = Column(String) #時限
abstract = Column(String) #概要
_property = Column(String) #詳細
update = Column(String) #更新日

def __init__(self, subject, teacher, week, period, abstract, _property, update):
self.subject = subject
self.teacher = teacher
self.week = week
self.period = period
self.abstract = abstract
self._property = _property
self.update = update

def __repr__(self):
return "" % (self.subject, self.teacher, self.week, self.period, self.abstract, self._property, self.update)

#Tableの作成
Base.metadata.create_all(engine)
36行目無視してください。なんでこいつが入るのかさっぱりわかりません。(自分では何も書いてないのに勝手に挿入されてる)
あとはDBに対して値を渡します。このとき「セッション」を作成します。こいつは、ORMがDBを操作する「ハンドル」に当たり、こいつを頻繁に使うことになります。
#Sessionにengineを結びつける
Session = sessionmaker(bind=engine)
session = Session()
この時コネクションはまだ開いていません。コネクションは、セッションを初めて利用した際にengineの管理するコネクションプールから取り出されて、「変更をすべてcommitする」 or 「セッションを閉じるまで保持される」(らしい。詳しくは分かんない

なにはともあれオブジェクトを新規追加します。
addinfo = Qkou('Twitter学概論', 'Pudding', '日曜日', '7限', '休講', '遊びに行くのでお休み', '9/16')
session.add(addinfo)
このときこれはまだ処理待ち(pending)状態で、DBには保存されていません。これを保存する必要ができた時点でフラッシュ(flush)され、SQLが実行されるらしいです。
変更を保存するには
session.commit()

また、クエリを実行してDBから目的のものだけを取り出すこともできます。
#条件に一致するものの中で最初のレコードを抽出。
#すべて取り出すには.first()ではなく.all()を用いる。
existinfo = session.quwry(Qkou).filter_by(teacher='Pudding').first()
addinfo is existinfo "True

#AND検索
from sqlalchemy import and_
filter(and_(Qkou.subject == '睡眠学', Qkou.teacher == 'Pudding'))
#こういう書き方も可
filter(Qkou.subject == '睡眠学').filter(Qkou.teacher == 'Pudding')

#OR検索
from sqlalchemy import or_
filter(or_(Qkou.subject == '睡眠学', Qkou.subject == 'ビール入門'))

これを用いて既存のレコードに同一のものがあるかどうか判定して、新規情報だけを入れることができる…?かな…?

まだまだ全然わかってないことが多すぎてなんかもう…(トランザクションって何?アイデンティティマップ?リレーション?ん???)

カテゴリー:ぷよぐやみんぐ


アクティブなウィンドウを別な仮想デスクトップに飛ばすアプリを起動できなかった話


ぐらばくさん(@Gracacr07)が開発されたアクティブなウィンドウを別な仮想デスクトップに飛ばすアプリが便利そうだったのでダウンロードしてみたのですが、以下の画像のようなエラーが出て動かず困っていました。


なんとなくWindowsでアプリが上手く動作しない時は.NetframeworkかVisual C++ ランタイムだと思っているのでとりあえずぶち込んでみたんですがさっぱり変わらず…

いろいろいじくり回してようやくVisual C++ 2015 再頒布パッケージがリリースされていることに気づきました。2013までしか知らない情報に疎い情弱でごめんなさいといった感じ…
こいつを入れたところ、何のエラーもなくすんなり動き、きちんと動作しました…便利です…重宝させてもらってます…

相変わらずなんでMSがWinにVisual C++ Redistributableをデフォルトで入れてくれないのか謎ですが、もし動かないという人がいればこいつを入れれば良いと思います。

カテゴリー:Other


Xonar D2/PMをWin10で使う


ついにデスクトップをWin10にクリーンインストールしなおしました。
SSDもNTT-Xで安売りされていたPlextor M6S 256GBモデルを購入しました。

だいたいのハードウェアは普通に最新のドライバをインストールすると使用可能になったのですが、Xonar D2/PMだけは最新のドライバでは「このプラットフォームには対応していません」というエラーメッセージが出てインストーラが起動しませんでした。
互換モードで起動してもダメで、さすがにそろそろこのクソ古いサウンドカードもご臨終かなと…

なんとなくでWin7用のドライバを導入したところ、ちょっと引くくらいすんなりいったので報告しておきます。

手持ちの機材は全部動いたのでよかったですね。

カテゴリー:Other


pyenvでpythonの管理


python3.xも随分と普及してきたのでそれを標準にしてもいい気はするのですが、解説サイトとかチュートリアルとかをやる上で2.x系も多いので両方手軽にやるためにpyenvを導入しました。

まず足りないライブラリがある可能性があるので、いくつか放り込みます。僕の環境ではlibbz2-dev、libreadline-dev、libsqlite3-devが足りませんでした。以下に参考にしたサイトに書いてあったライブラリ等を記しておきます。
sudo apt-get install git gcc make openssl libssl-dev libbz2-dev libreadline-dev libsqlite3-dev


ホームディレクトリに.pyenvというフォルダを作成し、git cloneします。
mkdir ~/.pyenv
git clone git://github.com/yyuu/pyenv.git ~/.pyenv


.bashrcに以下を追記
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"

追加したら
source ~/.bashrc
しておきます。

そして
pyenv install --list
とするとインストール可能なpythonのバージョンが表示されます。とりあえず2.7..10と3.4.3をインストールしておくこととします。
pyenv install 2.7.10
pyenv install 3.4.3
割と時間がかかります。また、足りないライブラリがあった場合ここでエラーが出ます。

デフォルトに設定するpythonのバージョンはglobalコマンドで設定できます。
pyenv global 2.7.10
これで2.7.10が標準で使われます。

また、特定のディレクトリにおいてバージョンを変更するときはlocalコマンドを使います。カレントディレクトリに.python-versionが作成されバージョンを決めるようです。
mkdir hoge #適当にディレクトリを作る
cd hoge
pyenv local 3.4.3
pyenv local --unset #localの設定を削除
localコマンドで宣言すると、globalが2.7.10になっていてもhogeディレクトリでは3.4.3が使われます。優先度がこちらのほうが高いのです。

pyenv versions
とすると今現在どのpythonインタプリタが標準になっているかがわかります。今回の場合ならsystem、2.7.10、3.4.3が表示され、2.7.10にアスタリスクが付いているはずです。

こんなことやってる前にさっさとガリガリ書けって話なんですが眠いので許して…(言い訳になっていない

カテゴリー:ぷよぐやみんぐ


dotfilesをgithubに投げる


最近ずっと.vimrcとかをいじってたんですがどうにも色々な機器でいちいち手動設定するの面倒だったので、githubにまとめて投げて今度からはgit cloneして終わりにさせたいと思いました。

というわけで、まずはgithubのアカウントを作ります。まぁSign upするだけですが…次にdotfilesというリポジトリを作ります。名前はなんでもいいです。

ターミナルに戻り、gitアカウントを登録します。
git config --global user.name "登録したユーザ名"
git config --global user.mail "登録したメールアドレス"
この操作は一回だけしておけば後は自動的にこの情報が使われます。

ターミナルに戻り、ローカルでの操作を行います。
cd ~/
mkdir dotfiles
mv .vimrc dotfiles
mv .bashrc dotfiles
mv .vim dotfiles
こんな感じで流用したい環境設定ファイルを放り込んでいきます。
※注意 この時mvを使っているので、ファイルは移動し元あった場所から消えます。つまりvimなどの設定が飛びます。その後がやりづらくなるのでcpなどを使ったほうが良い気がします。ディレクトリの場合、シンボリックリンクに強制置き換えできないためOperation not permittedが出るようです。ファイルの場合は問題無いですが、ディレクトリの場合はmvで移動する or 名前を変えてバックアップにするほうが良いようです

次にシンボリックリンクを張ります。dotfilesLink.shとか適当な名前付けて、
#!/bin/sh
ln -sf ~/dotfiles/.vimrc ~/.vimrc
ln -sf ~/dotfiles/.bashrc ~/.bashrc
ln -sf ~/dotfiles/.vim ~/.vim
こんな感じにして保存します。
export PATH=$PATH:/home/ユーザ名/dotfiles #PATHの追加
chmod a+x dotfilesLink.sh #実行権限の付与
dotfilesLink.sh
実行するとシンボリックリンクが張られます。

ここまで終わったら後はgitにpushするだけです。
cd ~/dotfiles
git init #必要な.gitフォルダの作成
git add . #.を使うとディレクトリ以下のすべてのファイルを選択
git commit -m 'first commit'
git remote add https://github.com/ユーザ名/dotfiles.git
git push origin master #ユーザ名とパスワードの入力が要求されます
これでローカルのdotfilesがgithubにアップロードされました。

dotfilesの中身を更新したらまたpushする必要があります。
#新規ファイルが有るとき
git add .
git commit -m "anything"
git push origin master

#新規ファイルが無いとき
git commit -a -m "anything"
git push origin master
こんな感じで。

後は他のコンピュータでgit cloneすれば良いです。更新した情報はgit pullで取得できます。詳しいことはcommitとpushしかできない人のためのgithubの使い方まとめを読んでください。

カテゴリー:ぷよぐやみんぐ