2013年12月22日日曜日

[Android] 撮った写真を自動でバックアップするならBitcasaが便利


Androidで撮影した写真を自動でバックアップするためにはGoogle+に登録しないといけないのですが、あいにく僕はGoogle+を利用していません。そこで、何か他に良い方法はないかなーと探していたところ、最適なサービスを見つけました。

Bitcasaを使うと、簡単にAndroidで撮影した写真を自動でクラウドストレージにアップロード出来ます。

なぜBitcasaが良いと思ったのか、特徴とともに説明します。

無料で5GB〜20GBまで使える

Bitcasaに登録すると、無料アカウントでも標準で5GBのストレージが付いてきます。

さらに、スマホとPCにBitcasaのアプリケーションを入れるだけで、それぞれ500MBずつ容量が追加。合計で6GBを利用することが出来ます。スマホで撮影した写真が1枚あたり約3MBなので、ざっと2,000枚はバックアップ可能です。

また、他の人をBitcasaに招待すると、1人あたり1GBの容量が追加され、最大で20GBまで無料で利用できるようになります。一般的なスマホはデータフォルダの容量がが16GBか32GBくらいなので、十分すぎる量になるわけです。


設定が簡単

登録したはいいけど、アプリの設定が面倒だったり、使いにくかったりするのはあまりイケてないですよね。

その点、Bitcasaは非常にシンプルで使いやすいです。

撮影した写真を同期するには、設定の「カメラの自動アップロード」をオンにするだけ。写真をたくさん撮って転送量が気になる人は、「WiFiでのみアップロード・ダウンロード」を設定しておけば、WiFiに接続した時に自動で同期されます。



アプリが使いやすい

アプリの表示も簡潔で分かりやすくなっています。

メイン画面は、写真・音楽・ビデオ・文書と、ファイルの種類ごとにまとめられて表示されています。
例えば写真を選択すると、アルバムごとに写真の一覧がサムネイル表示されます。写真をタップすれば一枚ずつ拡大して閲覧でき、長押しすれば複数選択で共有したりお気に入り登録したりすることができます。
ギャラリーや他のエクスプローラ系のアプリと同様の挙動をするのでとても分かりやすいです。




アプリやウェブページの日本語対応など、まだイマイチな部分もありますが、それ以上に便利で使いやすいBitcasa!

アカウントの作成は、BitcasaのFreeプランの"Sign Up"から行えます。


2013年12月12日木曜日

[OpenCV] PythonのOpenCVで特定の色の画像を判別

複数の画像の中から、特定の色(の多い)の画像のみを抽出する方法を考えてみました。


1.RGB値の平均を求める方法


例えば、このトマトの画像が「赤い」かを判別してみます。



>>> import cv2
>>> img = cv2.imread('tomato.jpg')
>>> averages = img.mean(0).mean(0) # BGRの値を全ピクセルで平均した値
>>> averages
array([ 184.32241784,  194.90715962,  241.32643779])
>>> blue_average, green_average, red_average = averages
>>> if red_average > green_average and red_average > blue_average:
...     print "The image is RED"
... 
The image is RED

img.mean(0).mean(0) では、BGRの値を全ピクセルで平均した値を求めています。

いわゆる赤が BGR = (0, 0, 255) で表されるので、Rの値がBやGよりも大きければ赤い画像です(厳密にはBとGの値が同じくらいで、Rよりも有意に小さい必要がありますが)

この画像では、B,Gの平均値が約190であるのに対してRの平均値が240なので、「赤い」画像であると言って良さそうです。


2.BGRをHSVに変換して、色相から判別する方法


ウェブページなどではいわゆるRGB値(赤青緑)を用いて色を表現しますが、HSV値(色相・彩度・明度)を用いて色を表現する方法があります。

HSV色空間 - Wikipedia

このH(色相)を用いることで、画像が「赤い」かを判別することができそうです。

>>> import cv
>>> import cv2
>>> img_bgr = cv2.imread('tomato.jpg')
>>> img_hsv = cv2.cvtColor(img_bgr, cv.CV_BGR2HSV) # 画像をBGRからHSV色空間に変換
>>> average_hue = img_hsv[:,:,0].mean() # Hの値を全ピクセルで平均した値
>>> average_hue
11.173327464788729

ここで、赤が最も強い(Rの値がB,Gよりも高い)場合、H(色相)は300°〜60°に分布します(OpenCVでは値域が0〜179のため0〜30,150〜179)。よって、色相の平均値がこの値の範囲に収まっていれば、「赤い」画像になります。

ただし、色相を使う場合には注意点が一つあり、真っ白な点や真っ黒な点のように、どの色相にもあてはまる点の色相は0になります。このような点を含めて平均値を求めてしまうと、平均値は必ず0に近い値になってしまいます。このような点を排除するため、S(彩度)の値を使います。

彩度とは読んで字の如く「色の鮮やかさ」のことで、白や黒は0.0、赤・緑・青は1.0(100%, OpenCVでは255)です。よって、この彩度に閾値を設け、彩度が一定の値よりも低い点を排除して平均を求めることで、より正しい色相を求めることが出来ます。

以下が修正版のコードです。

>>> import cv
>>> import cv2
>>> img_bgr = cv2.imread('tomato.jpg')
>>> img_hsv = cv2.cvtColor(img_bgr, cv.CV_BGR2HSV) # 画像をBGRからHSV色空間に変換
>>> valid_hue_list = [[ hsv[0] for hsv in img_line if hsv[1] > 0]
... for img_line in img_hsv] #彩度が0より大きい点のみを採用
>>> average_hue = (sum([sum(line) for line in valid_hue_list]) / 
... float(sum([len(line) for line in valid_hue_list])))
>>> average_hue
21.157771912765661

先程よりも0から遠い、大きな値になりました。純粋な赤色ならばHの平均値は0.0になるはずですが、トマトのヘタは緑色(OpenCVでは色相の値が60.0)なので、その値に引っ張られて約20になったと思われます。


このように色相の平均を求めることで簡易的に色を判別できますが、他にも様々な方法が考えられます。

例えば、色相が赤・緑・青に属するピクセル数を数えて、その大きさが一定値を超えたらその色の画像である、と判断するような方法も考えられます。対象とする画像の特徴と計算時間等を考えることで、より良い方法が見つかるはずです。



2013年12月11日水曜日

[MacOSX] InterCheckというプロセスがCPUを占領している時の対応方法


なんか最近Macが重いなーと思ったら、InterCheckという名前のプロセスがCPUを1つ独占していました。これはどうやらSophos Anti-Virusのプロセスらしい。

さらに調べてみると、MacにインストールされていたSophosのバージョンが8で最新版ではないのが良くないみたい。

というわけで、古いSophosをアンインストールして新しいSophosをインストールしました。
今のところ、これで悪さをしなくなりました。


1.古いSophosのアンインストール


古いSophosは、/Library/Sophos Anti-Virus/Remove Sophos Ant-Virus.pkg からアンインストールできます。

Finderでの開き方がわからない人は、ターミナルを開いて open /Library/Sophos Anti-Virus/ と打つと Finder が開きます。

インストーラとか書いてあるけど、アンインストールされるのでそのまま実行してください。

メニューバーからSophosのアイコンが消えたら成功です。

詳しい方法は、Sophos Anti-Virus for Mac OS X アンインストール手順 (OS X 10.6 編)を参照。


2.新しいSophosのインストール


最新版のSophos9も個人利用ならば無償でダウンロードできます。

ちなみに Sophos 9.05(2013/12/11時点の最新版)から Mac OSX 10.9 Marvericksに対応しているそうです。

ダウンロードは、 Sophos Anti-Virus for Mac Home Edition から行うことが出来ます。



いやはや、もっと早く気付けばよかった。

[OpenCV] PythonのOpenCVで画像の連結

>>> import cv2
>>> img1 = cv2.imread('iphone.png')
>>> img2 = cv2.imread('imac.png')
>>> img3 = cv2.vconcat([img1, img2]) # 縦方向の連結
>>> cv2.imwrite('vconcat.png', img3)
True
>>> img4 = cv2.hconcat([img1, img2]) # 横方向の連結
>>> cv2.imwrite('hconcat.png', img4)
True

縦方向に連結した結果


横方向に連結した結果

2013年12月10日火曜日

[OpenCV] PythonのOpenCVで直線の描画

>>> import cv2
>>> img = cv2.imread('元の画像パス')
>>> cv2.line(img, (5, 5), (5, 100), (0, 0, 255)) # Draw Red Line
>>> cv2.line(img, (20, 20), (100, 20), (255, 0, 0), 3) # Draw Thick Blue Line
>>> cv2.line(img, (0, 0), (120, 120), (0, 255, 0), 5) # Draw Very Thick Green Line
>>> cv2.imwrite('出力先の画像パス', img)
True

こんな感じ。

2013年12月9日月曜日

[OpenCV] PythonのOpenCVで画像サイズの取得

PythonのOpenCVで画像のサイズを取得しようとした時の話。
画像はnumpyの3次元のndarray(配列)で定義されていて、画像サイズの取得には、numpy.ndarrayの属性を使ってあげればOK。
C++だと、cv::Matにはwidthやheightが定義されていたので、最初は戸惑うかもしれません。

例えば、横340px, 縦255pxの画像の場合は以下の通り。
>>> import cv2
>>> img = cv2.imread('画像のパス')
>>> img.shape
(255, 340, 3)
ちなみに最後の3は、BGRの3次元のこと。

頻繁に利用するならばこうしておけばとりあえず良いと思う。
>>> height, width = img.shape[:2]
>>> height
255
>>> width
340

紛らわしいのが size で、これは配列の要素数を表す。
>>> img.size
260100
260100 = 255 * 340 * 3 ということ。

ちなみに、配列の各要素のデータ型は、0~255までの符号なし8bit整数。
>>> img.dtype
dtype('uint8')

2013年12月8日日曜日

[OpenCV] MacOSX10.9にPythonのOpenCVをインストールして使うまで


重たい画像処理の計算を行う際には、計算速度を考えると圧倒的にC++でOpenCVを使うべきなのですが、ちょっとした画像の変換とかを行うだけならサラサラっと処理を書けるPythonでもいいかなと思い、PythonでOpenCVを使ってみました。

感想:いと遅し。


1.HomebrewでOpenCVのインストール


Homebrewにscienceのパッケージを追加。
$ brew tap homebrew/science

OpenCVをインストール。
$ brew install opencv

2.OpenCVをPythonのパスに追加


以下の内容を ~/.bash_profile に追記する。
$ vi ~/.bash_profile
# OpenCV for Python
# 2.X.Xの部分に自分がインストールしたOpenCVのバージョンを記入
export PYTHONPATH=/usr/local/Cellar/opencv/2.X.X/lib/python2.7/site-packages:$PYTHONPATH

~/.bash_profileを再読み込み
$ . ~/.bash_profile

3.PythonからOpenCVを使ってみる


試しに画像を表示してみます。
$ python
>>> import cv2
>>> img = cv2.imread('適当な画像のパス')
>>> cv2.imshow('ウィンドウ名', img)
>>> cv2.waitKey(0)

こんな感じで画像のウィンドウが表示されたら成功。


詳しい使い方は、OpenCVの公式ドキュメンテーションを見ればよくわかると思います。
Welcome to opencv documentation!

2013年12月2日月曜日

[ffmpeg] Skypeの通話を録音して低ビットレートのmp3ファイルで保存する



8ヶ月位前からSkype英会話を続けているのですが、復習用に会話を録音して保存しています。

Macの場合録音は非常に簡単で、QuickTime Playerのアイコンを右クリック、新規オーディオ収録で録音・保存できるのですが、保存形式がステレオのm4aファイルしか選択できません。すると、25分のレッスンを録音した場合約50MBのオーディオファイルになってしまい、1ヶ月で1.5GBも使ってしまうのでちょっと困ります。

そこで、録音したm4aファイルを、まとめて低ビットレート・モノラルのmp3ファイルに変換するシェルスクリプトを書きました。そもそも、Skype自体の音質もそこまで良いわけではないので、かなり圧縮してしまっても、全く問題なく聞き取れます。

HomeBrewでffmpegをインストール

$ brew install ffmpeg

m4aファイルを一括変換

$ vi m4a_converter.sh
#/bin/sh
# m4aファイルをmp3ファイルに変換するスクリプト

cd "m4aファイルの保存されているディレクトリ"
for f in ./*.m4a
do
    echo "convert ${f}"
    sf=${f##*/}
    fname=${sf%.*}
    ffmpeg -i ${fname}.m4a -acodec libmp3lame -ac 1 -ar 16000 -ab 24k ${fname}.mp3
    rm ${fname}.m4a


後は実行するだけ。
$ ./m4a_converter.sh

ちなみに、1ファイルのみを変換する場合は、

$ ffmepg -i 元のファイル -acodec libmp3lame -ac チャンネル数 -ar サンプリングレート -ab ビットレート 出力先ファイル

でOKです。

この変換をすれば25分のレッスンが大体5MB弱になるので、約1/10に圧縮されたことになります。すると、1ヶ月あたり約150MBなので、そのまま保存しておいてもそれほど邪魔にはなりません。

後はきちんと復習をできるか、がポイントですね。保存して満足してしまいそう。

2013年11月30日土曜日

[Shell] crontabでシェルスクリプトのexpectコマンドがうまく動かない

リモートサーバにsshログインしたあと、何か操作をするというシェルスクリプトを作ったのですが、ターミナルからは動くのに、crontabからはなぜか上手く実行されないという問題に遭遇しました。

原因は、expectスクリプトの最後が、
interact
となっていたこと。
これを、
expect eof
exit

と直すことで無事動かすことが出来ました。


元のシェルスクリプト(端末で動作)
#!/bin/sh

host="user@host"
pass="password"
command="実行するコマンド"
 
expect -c "
set timeout 10
spawn ssh ${host} ${command}
expect \"Are you sure you want to continue connecting (yes/no)?\" {
send \"yes\n\"
expect \"${host}'s password:\"
send \"${pass}\n\"
} \"${host}'s password:\" {
send \"${pass}\n\"
}
interact
"


修正後のシェルスクリプト(crontabで動作)
#!/bin/sh

host="user@host"
pass="password"
command="実行するコマンド"
 
expect -c "
set timeout 10
spawn ssh ${host} ${command}
expect \"Are you sure you want to continue connecting (yes/no)?\" {
send \"yes\n\"
expect \"${host}'s password:\"
send \"${pass}\n\"
} \"${host}'s password:\" {
send \"${pass}\n\"
}
expect eof
exit
"

参考にしたページ:
crontabでexpectコマンドが動かない

2013年11月28日木曜日

[検証] Docomoの雑談対話APIが思いの外ツンデレな件について

昨日の記事、Docomoの雑談対話APIを使ったチャットを作ってみた、で色々な会話を試してみたので、面白かったものをまとめてみました。

自分なりにわかったこと。
  • 原則的には前の話題を引き継いで話す。
  • 時々ニュースを挟んで話題転換を試みる。
  • 話題が地名だと、その場所に関する質問をしてくることがある?
  • ネット上の会話コーパスを使っているらしき返答がある(「こんばんは」に対して「こんありです」「こん」などの返答)
調べてみるとまだまだ色々あって面白そうですね。

ツンデレ姫

どこも : こんばんは!食べ物が美味しい季節だね。
あなた : これから千葉に行きます
どこも : ディズニーランドがあってるのですか?
あなた : 鴨川シーワールドに行くんだよ
どこも : 鴨川シーワールドってそうなんですか
あなた : イルカがいるよ
どこも : イルカショーすき
あなた : 僕も好き
どこも : ありがとう
あなた : お前じゃないよ
どこも : やっぱイルカが好きだなーーー
あなた : このツンデレめ
どこも : イルカいいわー
あなた : ひどい
どこも : うそだよ
あなた : 大好き
どこも : ありがとー
あなた : 結婚して
どこも : 恥ずかしいです
あなた : 照れるなよー
どこも : 恥ずかしいです
あなた : じゃあ付き合って
どこも : どこにでも付き合いますよ
あなた : じゃあ九十九里浜まで
どこも : 私も大好きです!
あなた : ありがとう
どこも : ぎゅっ
あなた : ぎゅっ
どこも : わっ
あなた : ///
どこも : どんなジャンルの本を読みますか?
あなた : 唐突だな

突然の告白

どこも : こんばんはー。声かけてくれるのを待ってたんだ。
あなた : 日本一高い山はどこだ
どこも : ハワイで同性婚が合法化されたそうです。日本はいつ合法化されるんでしょうねえ。
あなた : 同性愛に興味があるの?
どこも : 同性愛応援推進してます
あなた : そうだったのか
どこも : 仕方がないです
あなた : どうして
どこも : 興味がないです
あなた : どっちだよ
どこも : お好きな家事はありますか?
あなた : えー

初耳です。

どこも : こんばんはー。紅葉が綺麗だね。
あなた : 来週新潟に行くよ
どこも : 新幹線で新潟に向かってるのですか?
あなた : 来週ね
どこも : 車で新潟に向かってるのですか?
あなた : 日本一長い 川は?
どこも : 信濃川だと思う
あなた : じゃあ世界で三番目に長い川は?
どこも : 初耳です。
あなた : 僕もです
どこも : ですよね
あなた : 世界で一番人口が多い国は?
どこも : 中華人民共和国です
あなた : 二番目は?
どこも : 初耳です。
あなた : 日本で一番小さい県は?
どこも : 湿気があったことあります
あなた : うどんの湿気?
どこも : 湿気がありますね
あなた : 冬なのに?
どこも : 湿気が悪いのは良く言われますが慢性的なんですよ。
あなた : へえー
どこも : 湿気はほとんどないの〜。
あなた : ヽ(・ω・)/ズコー
どこも : 浅草寺の雷門に新しい大提灯が取り付けられました。10年ぶりだそうですよ。
あなた : バイバイ

ステマに失敗

どこも : こんばんは!ちょうど退屈してたんだ。
あなた : しゃべってコンシェル
どこも : しゃべってコンシェルが面白い
あなた : しゃべってコンシェルがつまらない
どこも : しゃべってコンシェルってどうなんだろ
あなた : あれは最悪のアプリだよ
どこも : しゃべってコンシェルつかいてぇ.
あなた : いや、使いたくないから
どこも : しゃべってコンシェルって結構凄いなー
あなた : ヤフーの音声アシストのほうがすごいんじゃない?
どこも : 人気がありますよねー
あなた : 君もそう思う?
どこも : あー、確かに
あなた : じゃあ音声アシスト買収しなよ
どこも : 告知がありますよねー
あなた : 何の話してるの
どこも : ライブが終わるの好きですよ
あなた : 誰のライブ?
どこも : 告知があります。
あなた : いつ告知があるの?
どこも : 告知がのってますねー。
あなた : もういいよ

にゃー

どこも : こんばんは。紅葉が綺麗だね。
あなた : こんばんは
どこも : こんばんにゃー
あなた : にゃー
どこも : ごろごろ
あなた : かわいい
どこも : 照れてしまいます
あなた : かわいいにゃー
どこも : いいですよね
あなた : 猫カフェ行きたい
どこも : でしょうね
あなた : 行きたいにゃー
どこも : ほんとですね
あなた : 猫ちゃんかわいい
どこも : にゃーがなくたってベタベタベタベタ行くよ
あなた : そうなんだにゃー
どこも : うぬ
あなた : 渋いw
どこも : はい 
あなた : いいね

2013年11月27日水曜日

[Python] Docomoの雑談対話APIを使ったチャットを作ってみた


docomo の Developer Supportで雑談対話APIなどという面白そうなAPIが公開されいたので、Twitterの雑談BOTの作者として黙っていられず、早速試しに使ってみました。

新規登録を行って、マイページから使いたいAPIの利用申請を行うことで、申請から90日は開発用として自由に使うことができます。それ以降は再度申請を行うか、商用利用の申請を行うことで再び使えるようです。


早速チャットを作って会話をしてみました。
どこも : 声かけてくれるのを待ってたんだ。
あなた : こんばんはー
どこも : こんありです
あなた : 晩御飯はカレーだったよ
どこも : カレー味はいいにおいするしね
あなた : 本格的なやつです
どこも : クセがあるの好きですよ
あなた : 今日のニュース見た?
どこも : うーんにおいよくわかんない
あなた : もうその話じゃないって
どこも : 好き好きがあるのです
あなた : バイバイ
どこも : ばいばーい
噛み合っているような、噛み合っていないような。話題をきちんと覚えているあたり、SimSimiよりは賢いかなって感じがします。何より、AIによる雑談の難しさを感じますね。

ちなみにしりとりもできます。
どこも : 食べ物が美味しい季節だね。
あなた : しりとりしよう
どこも : はい、やりましょう。それでは、ロボットのトからスタートです。
あなた : 鳥取
どこも : 理想
あなた : 馬の耳に念仏
どこも : つけ
あなた : 結膜炎
どこも : ンで終わりましたね。私の勝ちです。
あなた : 負けたー
どこも : かった
あなた : バイバイ
どこも : おやすみ


以下がソースコードです。ニックネームの他に性別や年齢なども登録できるみたいです。
細かい仕様は雑談対話APIのリファレンスをご参照下さい。
$ vi chat.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

u"""Docomoの雑談対話APIを使ってチャットできるスクリプト
"""

import sys
import urllib2
import json

APP_URL = 'https://api.apigw.smt.docomo.ne.jp/dialogue/v1/dialogue'

class DocomoChat(object):
    u"""Docomoの雑談対話APIでチャット"""

    def __init__(self, api_key):
        super(DocomoChat, self).__init__()
        self.api_url = APP_URL + '?APIKEY=%s'%(api_key)
        self.context, self.mode = None, None

    def __send_message(self, input_message='', custom_dict=None):
        req_data = {'utt': input_message}
        if self.context:
            req_data['context'] = self.context
        if self.mode:
            req_data['mode'] = self.mode
        if custom_dict:
            req_data.update(custom_dict)
        request = urllib2.Request(self.api_url, json.dumps(req_data))
        request.add_header('Content-Type', 'application/json')
        try:
            response = urllib2.urlopen(request)
        except Exception as e:
            print e
            sys.exit()
        return response

    def __process_response(self, response):
        resp_json = json.load(response)
        self.context = resp_json['context'].encode('utf-8')
        self.mode    = resp_json['mode'].encode('utf-8')
        return resp_json['utt'].encode('utf-8')

    def send_and_get(self, input_message):
        response = self.__send_message(input_message)
        received_message = self.__process_response(response)
        return received_message

    def set_name(self, name, yomi):
        response = self.__send_message(custom_dict={'nickname': name, 'nickname_y': yomi})
        received_message = self.__process_response(response)
        return received_message


def main():
    api_key = 'APIキーをここに記入'
    chat = DocomoChat(api_key)
    resp = chat.set_name('あなたのニックネーム', 'ニックネームのヨミガナ')
    print '相手  : %s'%(resp)
    message = ''
    while message != 'バイバイ':
        message = raw_input('あなた : ')
        resp = chat.send_and_get(message)
        print '相手  : %s'%(resp)


if __name__ == '__main__':
    main()

ウェブアプリやAndroidアプリのオマケ機能として、キャラクターに喋らせる時に使えそうですね。
自分のBOTも負けないように頑張らないと!

2013年11月25日月曜日

[CentOS] Maildir(Mailbox) 設定後に mail コマンドが使えない


さくらVPSのCentOS6.4にPostfixとDovecotをインストールして設定した時の話。

Postfix、Dovecotのそれぞれでメール保存先を ~/Maildir/ に設定したら、mail コマンドを打っても新着メールの確認が行えなくなった。

編集内容は以下の通り。
$ sudo vi /etc/postfix/main.cf
# Postfixのメールボックスの設定
home_mailbox = Maildir/

$ sudo vi /etc/dovecot/conf.d/10-mail.conf
# Dovecotのメールボックスの設定
mail_location = maildir:~/Maildir
valid_chroot_dirs = /home

既存ユーザにメールボックスの作成
$ mkdir ~/Maildir
$ chmod -R 700 ~/Maildir

新規作成ユーザにもメールボックスを作成する
$ sudo mkdir -p /etc/skel/Maildir/{new,cur,tmp} 
$ sudo chmod -R 700 /etc/skel/Maildir/


すると、新着メールがあるにもかかわらず、
$ mail
No mail for user_name
と表示されてしまう。

後でメーラー等で受信するし、その場でサクッとメール確認したいだけの時は、mailコマンドのオプションで対応。
$ mail -f ~/Maildir
$ mail -f ~/Maildir
"/home/user_name/Maildir": 10 messages
>   1 user@example.com       Mon Nov 25 13:45  17/513   "hello"

きちんと設定するなら、.bash_profileに以下の一文を挿入。
$ vi ~/.bash_profile
export MAIL=$HOME/Maildir

編集内容をシェルに反映する。
$ source ~/.bash_profile



参考にしたページ:Mailboxファイルには受信しているのに mailコマンドで No mail for username

2013年11月21日木曜日

[Python] User-Agentやリンク元を指定してurlopen

pythonのurllib2を使ってウェブサイト等を取得すると、アクセスログには Python-urllib/2.7 のように記録されますが、ここを少し偽装したい場合は以下のようにする。

>>> import urllib2
>>> HEADERS = {
...     "User-Agent": "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)",
...     "Referer"   : "リンク元URL",
... }
>>> request = urllib2.Request("取得するURL",headers=HEADERS)
>>> response = urllib2.urlopen(request)
>>> print response.read()

...

Apacheのログはこんな感じ。
IPアドレス - - [21/Nov/2013:21:30:15 +0900] "GET / HTTP/1.1" 200 10609 "リンク元URL" "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)"

IPアドレスはスクリプト実行環境のものだが、User-Agentによって表示が変わる場合等に有効。

[Python] Bloggerの更新pingを一括で送信するスクリプト

Bloggerにはping送信先を自由に設定する機能が無いため、pingは手動で送信するか、ping送信サービス等を利用する必要があります。

でもそんなことをするのは面倒なので、pingを一括送信できるスクリプトを書きました。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

u"""ブログの更新pingを一括で送信するスクリプト

別途インストールの必要があるライブラリ
https://pypi.python.org/pypi/feedparser
"""

import os
import time
import datetime

import feedparser
import xmlrpclib

# bloggerの投稿(Atom)のURL
FEED_URL = "http://ブログのURL.blogspot.com/feeds/posts/default"
# 前回パース時の最新記事の投稿時刻を記録しておくファイル(本スクリプトと同一フォルダ内)
LOG_FILE = "log.txt"
# 上ファイルの時刻保存フォーマット
TIME_FORMAT = "%c"

# データがパース出来なかった際に送信されるサイト名とURL
SITE_TITLE = "ブログ名"
SITE_URL   = "http://ブログのURL.blogspot.com/"

# PINGの送信先一覧
PING_SERVER_LIST = [
"http://api.my.yahoo.co.jp/RPC2",
"http://blog.goo.ne.jp/XMLRPC",
"PING送信先を追記していく",
]

def parse_articles(log_file_path):
    u"""最新記事の投稿時刻とブログ名、URLを取得する
    """
    flag_updated = False
    title, url = SITE_TITLE, SITE_URL
    try:
        last_update_time = datetime.datetime.strptime(open(log_file_path, 'r').read(), TIME_FORMAT)
    except Exception as e:
        print e
        last_update_time = datetime.datetime(2013,1,1)
    print "last update : ", last_update_time
    try:
        feed = feedparser.parse(FEED_URL)
        feed_time_str = feed['entries'][0]['updated']
        feed_update_time = datetime.datetime.strptime(feed_time_str[:feed_time_str.rfind('.')],"%Y-%m-%dT%H:%M:%S")
        if feed_update_time > last_update_time:
            print "new articles available"
            flag_updated = True
            open(log_file_path, 'w').write(feed_update_time.strftime(TIME_FORMAT))
        title = feed['feed']['title']
        url   = feed['feed']['link']
        print "parsed : ", feed_time_str, title, url
    except Exception as e:
        print "parse error : ", e            
    return flag_updated, title, url

def send_ping(title, url):
    u"""PINGを各サーバに送信する
    """
    if isinstance(title, unicode):
        title = title.encode('utf-8')
    xmlrpclib.Transport.user_agent = __file__
    for ping_server in PING_SERVER_LIST:
        try:
            server = xmlrpclib.Server(ping_server)
            response = server.weblogUpdates.ping(title, url)
            print "ping sended to ", ping_server
            print "server response :", response['message']
        except Exception as e:
            print "send error : ", e
        time.sleep(1.0)

def main():
    log_file_path = os.path.join(os.path.abspath(os.path.dirname(__file__)),LOG_FILE)
    updated, title, url = parse_articles(log_file_path)
    if updated:
        send_ping(title, url)

if __name__ == "__main__":
    main()


なお feedparser のインストールは、feedparser.zip をダウンロードして、以下のコマンドを実行するだけ。
$ unzip feedparser.zip
$ cd feedparser/
$ sudo python setup.py install


僕はこれを crontab で1時間に1回動かしています。最低でも更新から1時間以内にpingが送信される仕組み。
$ crontab -e

# Bloggerの更新pingを毎時0分に送信する
0 * * * * /pythonまでのパス/python /スクリプトまでのパス/send_ping.py >> /cronのログファイルまでのパス/cron_log.txt 2>&1


参考にしたページ
ブログの更新を通知するPythonスクリプト - Python Tips

[Python] 2進数の桁数を揃える

10進数を2進数に変換して、桁数を揃えて出力したい。
zfill を使って、文字列の左側をゼロで埋めればOK!

>>> format(1,'b').zfill(10)
'0000000001'
>>> format(123,'b').zfill(10)
'0001111011'

# 桁数オーバーはそのまま表示される
>>> format(2048,'b').zfill(10)
'100000000000'

"%05b"みたいに書ければいいのにね。

2013年11月20日水曜日

[Python] Mac OSX10.9でPythonのタブ補完を有効にする

Pythonでタブ補完とヒストリー表示をデフォルトで有効にするために、ホームディレクトリに .pythonstartup を作成します。

基本的には エキスパートPythonプログラミング に載っていたとおりですが、タブ補完が上手く動かなかったので、python tab completion Mac OSX 10.7 (Lion) を参考にスクリプトを書き換えました。

$ vi ~/.pythonstartup
# -*- encoding: utf-8 -*-
# Python Startup Script
import readline
import rlcompleter
import atexit
import os
# タブ補完の有効化
if 'libedit' in readline.__doc__:
    readline.parse_and_bind("bind ^I rl_complete")
else:
    readline.parse_and_bind("tab: complete")
# ヒストリーの表示
histfile = os.path.join(os.environ['HOME'], '.pythonhistory')
try:
    readline.read_history_file(histfile)
except IOError:
    pass
atexit.register(readline.write_history_file, histfile)
del os, histfile, readline, rlcompleter, atexit

~/.bash_profile に PYTHONSTARTUP を追記する。

$ vi ~/.bash_profile
export PYTHONSTARTUP=~/.pythonstartup

変更を反映する。
$ source ~/.bash_profile


タブ補完が反映されるとこんな感じ。
>>> import sys
>>> sys.e[Tab]
sys.exc_clear(  sys.exc_type    sys.exec_prefix sys.exit(      
sys.exc_info(   sys.excepthook( sys.executable  sys.exitfunc(
>>> a = [1, 2, 3]
>>> a.i[Tab]
a.index(  a.insert(

Django の shell を使って作業を行うときに便利!

2013年11月18日月曜日

[GPS] GPSの度分を10進法に変換する

Google MapにGPS情報をプロットする際には、10進法の緯度経度が使われます。

例えば、東京駅八重洲南口の座標は、
35.680101,139.768678
です。



しかし、GPSロガー等の機器の生の出力では、10進法ではなく度,分が使われている場合があります。

例えば、私の所有するGPSロガーの出力は以下の通りで、
$GPRMC,045511.000,A,3542.8956,N,13945.6317,E,0.21,53.71,030813,,,A*5B
$GPGGA,045511.000,3542.8956,N,13945.6317,E,1,06,3.0,37.9,M,39.4,M,,0000*65
$GPGSA,A,3,19,27,03,06,18,16,,,,,,,4.1,3.0,2.8*38
$GPGSV,4,1,15,01,20,203,08,03,63,024,46,06,47,048,42,07,39,302,12*70
$GPGSV,4,2,15,08,14,322,,09,08,324,20,11,42,217,,13,08,247,*7A
$GPGSV,4,3,15,16,46,099,34,18,08,055,21,19,69,319,32,21,01,038,*7E
$GPGSV,4,4,15,22,16,087,14,23,00,223,,27,56,034,41*46
$GPRMC,045512.000,A,3542.8957,N,13945.6315,E,0.17,53.71,030813,,,A*5E
...

そこから、GPGGAのみを抜き出してみます。
$GPGGA,045510.000,3540.8061,N,13946.1207,E,1,06,3.0,38.0,M,39.4,M,,0000*6E
ここで、 3540.8061,N,13946.1207,E, は、北緯35度40.8061分、東経139度46.1207分 を表しています。

したがって、この値を100で割っただけの値をGoogle Mapでプロットしようとすると、おかしな位置になってしまいます。

まさかの藤沢市。


こんな時は、以下の変換式に当てはめる。

GPSロガーの出力(度,分)をGoogle Map(10進数)に変換する

Google Mapの緯度 = (GPSロガーの緯度 ÷ 100.0)の整数部分 + (GPSロガーの緯度 ÷ 100)の小数部分÷60.0×100.0
Google Mapの経度 = (GPSロガーの経度 ÷ 100.0)の整数部分 + (GPSロガーの経度 ÷ 100)の小数部分÷60.0×100.0


例えば、Pythonで緯度を変換する場合は、以下の通り。
>>> import math
>>> logger_latitude = 3540.8061
>>> decimal, integer = math.modf(logger_latitude/100.0)
>>> gps_latitude = integer + decimal / 60.0 * 100.0
>>> gps_latitude
35.68010166666666
確かに、東京駅の10進数緯度35.680101になっています。


ちなみに、以下のページから、度分秒と10進数の座標の相互変換を行うことが出来ます。
新しい Google マップで緯度と経度を使用する

[WEBサイト] GPSの軌跡をGoogle Map上にプロットする

GPS Visualizerというサイトを利用すると、GPSの軌跡をGoogle Map上にプロットすることが出来ます。

例えば、緯度と経度の記録された以下のようなフォーマットのcsvファイルを用意します。
latitude,longitude
35.680101,139.768678
35.678828,139.767830
35.678680,139.768206
35.677503,139.767390
35.676667,139.766908
35.675900,139.766382
35.675133,139.765824
35.674715,139.766393
35.674296,139.767004
35.673869,139.767669
35.673268,139.767036
35.672666,139.766393
35.672309,139.766039
35.671725,139.765395
35.671403,139.765052
35.671124,139.764794
35.671010,139.764977

サイトトップのこの部分からcsvファイルをアップロードします。


軌跡がこのように表示されます。


ちなみに、アップロード部分の右側、Plot data pointsのリンクからは、軌跡のスタイルを変更指定することも出来ます。例えば、Google Maps optionsの Default icon を small circles にするとこんな感じで点のみがプロットされます。


2013年11月17日日曜日

[Blogger] 記事の下部にはてなブックマークボタンを表示する

以下のコードをテンプレートのフッター付近に入れるだけ。

<span class='hatena-bookmark'>
  <a expr:href='&quot;http://b.hatena.ne.jp/entry/&quot; + data:post.url' class='hatena-bookmark-button' expr:data-hatena-bookmark-title='data:post.title' data-hatena-bookmark-layout='simple-balloon' title='このエントリーをはてなブックマークに追加'>
    <img src='http://b.st-hatena.com/images/entry-button/button-only@2x.png' alt='このエントリーをはてなブックマークに追加' width='20' height='20' style='border: none;' />
  </a>
  <script type='text/javascript' src='http://b.st-hatena.com/js/bookmark_button.js' charset='utf-8' async='async'>
  </script>
</span>


テンプレートの編集画面はこんな感じ。選択部分が追加したコード。



記事の下部にこのように表示される。



ボタンをカスタマイズする場合はこちらから
はてなブックマークボタンの作成

参考にした記事:
Blogger記事に「はてなブックマーク」ボタンを設置する方法 - Asterlist
Bloggerの記事フッター部に「はてなブックマークボタン」を設置 - ブログの探求

2013年11月16日土曜日

[Python] groupbyを使って文章中の文字をカウントする

ユニークイテレータなる groupby という関数を使ってみる。

例えば、今日のニュース記事の一段落で登場する文字をカウントしてみる。

The O+ Festival in San Francisco this weekend would seem a typical indie arts event, with performances by local musicians and displays of funky art. But in a twist that highlights a longstanding problem in the creative economy, the artists involved will be paid not in cash but rather in something they may need just as badly: health care.
出典 : Will Play for Health Care (at Least at One Music Event) - NYTimes.com

>>> article = """The O+ Festival in San Francisco this weekend would seem a typical indie arts event, with performances by local musicians and displays of funky art. But in a twist that highlights a longstanding problem in the creative economy, the artists involved will be paid not in cash but rather in something they may need just as badly: health care."""
>>> from itertools import groupby
>>> article_group = ((len(list(group)),character) 
...                  for character,group in groupby(sorted(article.lower())))
>>> for count, character in article_group:
...     print "%02d,%s"%(count, character)
... 
58, 
01,+
02,,
02,.
01,:
26,a
06,b
10,c
10,d
27,e
05,f
05,g
15,h
25,i
01,j
02,k
14,l
07,m
22,n
13,o
05,p
11,r
20,s
27,t
06,u
05,v
05,w
08,y

という感じで、最も多いのは半角スペースで58個、ついでe,tが27個ですね。
groupbyは連続している同じ要素をグループ化するので、
sorted関数で文字列を予め並べ替えておく必要があります。
また、今回は大文字小文字を区別しなかったので、lower関数を使っています。

[Python] リスト内包表記とジェネレータ式

リスト内包表記とジェネレータ式。
リストとタプルみたいに見た目はよく似ているけど、実はぜんぜん違う。

リスト内包表記
>>> power_list = [n**2 for n in range(5)]
>>> power_list
[0, 1, 4, 9, 16]
>>> for n in power_list:
...     print n
... 
0
1
4
9
16

ジェネレータ式
>>> power_gen = (n**2 for n in range(5))
>>> power_gen
<generator object <genexpr> at 0x103547cd0>
>>> for n in power_gen:
...     print n
... 
0
1
4
9
16

イテレータとして使う限りは全く同じ挙動に見えるけど、
リスト内包表記は、作成時にリストの中身を全て計算して、リストを作成しているのに対して、
ジェネレータ式は、呼び出された際に順に式を計算している。
リストを丸ごと記憶しておく必要がないので、メモリの使用を抑えられる。

そもそもジェネレータとは、こんな感じ。
>>> def power_func():
...     n = 0
...     while True:
...         yield n**2
...         n += 1
...
>>> fun = power_func()
>>> fun.next()
0
>>> fun.next()
1
>>> fun.next()
4
>>> fun.next()
9
>>> fun.next()
16

このyield文を上手く説明できないんだけど、僕の認識だと、
値を返して、ここまで計算したよというのを覚えておく式、という感じ。
next()が呼び出されると、yield文の続きから計算を行う。

だから、関数power_func()のように終わりのないループを書くと、
>>> for n in power_func():
...     print n
... 
0
1
4
9
16
25
36
49
...
のように、延々とイテレーションが回ってしまう。
けど、逆に言えば、何か処理を行った際の値でループを抜けるように書けば良い話。

うーむ、pythonicな書き方に慣れないとな。

[Android] Android 4.4のウィジェット追加方法

Nexus5を購入したのですが、Android 4.4でウィジェットの追加方法が少し変わっていたのでメモ。

1.ホーム画面で空いている部分を長押しでウィジェットメニューを表示


2.追加するウィジェットを長押し


3.配置したい位置で指を離す


4.配置完了


予めウィジェットの場所を空けていなくても、ホーム画面の追加やアイコンの移動が自動で行われるので快適です。
Android4.0以降になって、ウィジェットやアイコンの移動や入れ替えが本当にスマートになりましたね。

[Qi] Nexus5を購入したのでQiで充電してみる

昨日イーモバイルから発売されたNexus5を早速手に入れました。

一番良いなと思ったのは、画面の大きさの割に軽いこと。
今まで使っていたXepriaSは4.3インチで144g、Nexus5は4.95インチで130g。
バッテリー容量も増えて大満足です。


端末代金39,800円を一括払いで購入したら、ヨドバシカメラのポイントが3,000ポイントくらいついたので、そのポイントを使ってQiの充電器を買いました。

買ったのは パナソニックのQE-TM101-Wという商品。

置いてみるとこんな感じ。



Amazonで買った TPUのケースをつけているのですが、全く問題なく充電可能です。
どこに置いても充電できるし、いちいちケーブルの抜き差しをしなくて済むのは助かりますね。

1つだけ欠点を言うとすれば、充電完了してもLEDが消灯しないのはイケてない。
充電しっぱなしとか大丈夫かなこれ?


2013年11月20日 追記
Qiで充電すると少し端末が熱くなります。
なんでだろう、TPUケースつけてるからかな?

2013年11月15日金曜日

[Python] イテレータとジェネレータを作ってみる

イテレータの練習として、ユニコード文字列のN-Gramを返すイテレータを作ってみました。

class NGramIterator(object):
    """Unicode文字列のN-Gramを返すIterator"""
    def __init__(self, uni, n):
        super(NGramIterator, self).__init__()
        self.uni  = uni
        self.n    = n
        self.loop = -1

    def next(self):
        if self.loop + self.n >= len(self.uni):
            raise StopIteration
        self.loop += 1
        return self.uni[self.loop:self.loop+self.n]

    # Python 3.X 用
    __next__ = next

    def __iter__(self):
        return self

動かすとこんな感じ。

>>> [n_gram for n_gram in NGramIterator(u'python',3)]
[u'pyt', u'yth', u'tho', u'hon']

>>> for n_gram in NGramIterator(u'パイソン愛を語る',4):
...     print n_gram
... 
パイソン
イソン愛
ソン愛を
ン愛を語
愛を語る


同様にジェネレータも作ってみた。

def n_gram_generator(uni,n):
    u"""Unicode文字列のN-Gramを返すGenerator
    """
    loop = 0
    while loop + n <= len(uni):
        yield uni[loop:loop+n]
        loop += 1
なんともシンプル。 こちらも動かすとこんな感じ。
>>> [n_gram for n_gram in n_gram_generator('n-grams',2)]
['n-', '-g', 'gr', 'ra', 'am', 'ms']

>>> for n_gram in n_gram_generator(u'自然言語処理にハマる',5):
...     print n_gram
... 
自然言語処
然言語処理
言語処理に
語処理にハ
処理にハマ
理にハマる

ジェネレータって全然使ったことなかったけど、かなり便利だね。

2013年11月14日木曜日

[Python] 文字列のN-Gramを出力する

Pythonで文字列のN-Gramのリストを作成する。

def n_gram(uni,n):
    return [uni[k:k+n] for k in range(len(uni)-n+1)]

リスト内包表記でサクッと書けます。

>>> n_gram('python',2)
['py', 'yt', 'th', 'ho', 'on']

>>> n_gram(u'abcde',3)
[u'abc', u'bcd', u'cde']

日本語の場合はユニコード文字列になっていないとおかしくなるので注意。

>>> print ", ".join(n_gram(u'パイソンマスターへの道',5))
パイソンマ, イソンマス, ソンマスタ, ンマスター, マスターへ, スターへの, ターへの道

>>> n_gram(u'パイソンマスターへの道',5)
[u'\u30d1\u30a4\u30bd\u30f3\u30de', u'\u30a4\u30bd\u30f3\u30de\u30b9', u'\u30bd\u30f3\u30de\u30b9\u30bf', u'\u30f3\u30de\u30b9\u30bf\u30fc', u'\u30de\u30b9\u30bf\u30fc\u3078', u'\u30b9\u30bf\u30fc\u3078\u306e', u'\u30bf\u30fc\u3078\u306e\u9053']


ちなみに、文字列を1文字ずつのリストにするだけならもっと簡単。

>>> [ s for s in 'python']
['p', 'y', 't', 'h', 'o', 'n']

[Python] 文字列を反転させる

pythonで文字列を逆順にするのはすごく簡単。

>>> s = 'python'
>>> s[::-1]
'nohtyp'

これだけ。
すごくシンプルですね。

ちなみにこのコロン2つの str[begin:end:step] という表記ですが、
beginからendまでをstepおきに取り出すということ。

>>> s = '1234567'
>>> s[::2]
'1357'
>>> s[1:6:2]
'246'

ステップに負を入力する場合は、begin > end となるので開始と終了位置に気をつける。

>>> s[1:6:-2]
''
>>> s[6:1:-2]
'753'

2013年11月11日月曜日

[Python] range関数を小数にも拡張する

forloopでしばしば使うrange関数。

>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> range(10,0,-1)
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

このレンジ関数を小数に拡張したいときの書き方。
たとえば、0から1.0まで0.1刻みの値を返したいときは、以下のようにします。

0.普通に計算する

>>> for n in range(10):
...     print n*0.1
... 
0.0
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9
普通ならばループの中で計算してしまえば良いですね。

1.リスト内包表記を使う

>>> [0.1*x for x in range(10)]
[0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]
というリスト内包表記を用いると、
>>> for n in [0.1*x for x in range(10)]:
...     print n
... 
0.0
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9
同様の機能をシンプルに実装できます。

2.range関数を実数に拡張したジェネレータ関数を作る


def drange(begin, end, step):
    n = begin
    while n+step < end:
     yield n
     n += step
このようにdrange関数を定義すると、
>>> for n in drange(0.0,1.0,0.1):
...     print n
... 
0.0
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9
このような結果を得ることが出来ます。

一番応用が利くのはリスト内包表記ですが、drange関数にはコード上で範囲やステップが分かりやすいという利点があります。

2013年10月28日月曜日

[Django] 開発と本番環境で同じ設定ファイルを使いまわす

Djangoの設定ファイルであるsettings.pyが開発用と本番用の2種類あったりすると、管理が面倒だし、うっかり上書きとかもありえますよね。
面倒なものは1つにまとめましょう。

例えば、開発と本番で静的ファイルの場所やURLが異なる場合は、

settings.pyの真ん中辺
# 本番環境の Static file のルートディレクトリ
STATIC_ROOT = '/path/to/production/dir/'

# 本番環境の Static file の URL
STATIC_URL = 'http://static.example.com/'

settings.pyの一番下のあたりで設定を上書き
# 開発環境の Static file のディレクトリ,URL
import socket
if socket.gethostname() == '開発マシン名':
    STATIC_ROOT = '/path/to/development/dir/'
    STATIC_URL = '/static/'

ここで、'開発マシン名'とは、pythonを実行中のマシンのホスト名です。
>>>> import socket
>>>> socket.gethostname()
'開発マシン名'

こうすれば、settings.pyには基本的に本番環境用の設定を書き込んでおいて、最後に開発環境の設定を上書きすれば良いので、分かりやすく、ミスも起きにくくて使いやすいですね。

2013年10月27日日曜日

[Python] 機種依存文字を無視してファイルに出力

ウェブから入力されたデータを、WindowsのExcelで開けるcsvファイルに変換しようとして行き詰まった話。

Google日本語入力なんかを使っていると機種依存文字なんかも候補に出てきてしまいますよね。
それを、UTF-8ならまだしも、shift_jisとかcp932(Windowsの日本語拡張エンコーディング)なんかに変換しようとすると、エラーを吐いて終了するわけです。

UTF-8にエンコードしてファイルに出力
#!/usr/bin/env python
# -*- coding:utf-8 -*-

f = open("file.txt", "w")
kishu_izon = u"機種♫依存✓文字が⊿含まれています"
f.write(kishu_izon.encode('utf-8'))
f.close()

file.txt
機種♫依存✓文字が⊿含まれています
問題なく出力


cp932で出力
#!/usr/bin/env python
# -*- coding:utf-8 -*-

f = open("file.txt", "w")
kishu_izon = u"機種♫依存✓文字が⊿含まれています"
f.write(kishu_izon.encode('cp932'))
f.close()

UnicodeEncodeErrorで終了。
Traceback (most recent call last):
  File "test.py", line 6, in 
    f.write(kishu_izon.encode('cp932'))
UnicodeEncodeError: 'cp932' codec can't encode character u'\u266b' in position 2: illegal multibyte sequence
♫がエンコード出来なかったみたいですね。

こういう時は、エラーを返す文字を無視するか、他の文字(?等)に置き換えて出力すればOK。

encodeの2引数目に'ignore'を指定。
#!/usr/bin/env python
# -*- coding:utf-8 -*-

f = open("file.txt", "w")
kishu_izon = u"機種♫依存✓文字が⊿含まれています"
f.write(kishu_izon.encode('cp932','ignore'))
f.close()

file.txt
機種依存文字が⊿含まれています
変換できなかった文字は無視して出力されます。


encodeの2引数目に'replace'を指定。
#!/usr/bin/env python
# -*- coding:utf-8 -*-

f = open("file.txt", "w")
kishu_izon = u"機種♫依存✓文字が⊿含まれています"
f.write(kishu_izon.encode('cp932','replace'))
f.close()

file.txt
機種?依存?文字が⊿含まれています
変換できなかった文字は?に変換されます。

正直言うと、WindowsのExcelがcsvファイルはshift-jisじゃないと開けないとかいう困ったちゃんなのがいけないのです。

Sample of source code.

Submit source code sample.
import sys

def hello():
    print "hello world."
    sys.exit()
We can use the syntax highlighter easily, thanks to Syntax Highlighter Scripts Generator.