mecabで名詞を数える

トレンドとなっている語を探したくて、mecabを使って名詞を数えるスクリプトを書いた。

単に名詞だけを数えると、「山田太郎」を「山田」と「太郎」を別々に数えてしまう。

mecabに「山田太郎」で辞書登録すれば一語にできるがそれもめんどくさい。

そこで、「姓と名が連続して登場したらひとつの名詞とみなす」という風にしたのだが、どうもスッキリ書けず、下記のようになってしまった。

一応これで用は足せているのだが、もっと簡単にかけないかなあ・・・



foreach (@array){
    for (my $n = $m->parseToNode ($_); $n ; $n = $n->{next}) {
        my $surf=decode('utf8',$n->{surface});
        my $feature=decode('utf8',$n->{feature});
        my @features = split(/,/, $feature);

        if($features[0]=~ /名詞/) {
            if($features[3] eq "名"){
                if($saveword){ $surf = $saveword.$surf };
                    &check_word_hash($surf,\%hash);
                    $saveword = undef;
                }elsif($features[3] eq "姓") {
                    if($saveword){
                        &check_word_hash($saveword,\%hash);
                    }
                    $saveword = $surf;
                }else{
                    &check_word_hash($surf,\%hash);
                }
         }elsif($saveword){
             &check_word_hash($saveword,\%hash);
             $saveword = undef;
         }else{
             if($saveword){
                 &check_word_hash($saveword,\%hash);
                 $saveword = undef;
             }
         }
     }
}



sedによる一括置換

cgiでrequireしているファイル名を mylib.pl から util.plに変えた。

sedで全部置換する。

sed -i s/mylib/util/ *.cgi


-i をつけるとファイルを直接書き換える。

viのtips

カッコにカーソルを置いて % で対応カッコに移動

:!でコマンド実行

hashの値の降順でソート





foreach my $name (sort { $hash{$b} <=> $hash{$a} } keys %hash){
print "$name,$hash{$name}\n";
}

「数えてハッシュに登録し件数が多い順に表示」というのを最近おぼえてよく使うようになったのだが、


ハッシュの値でのソート方法をいつも検索して某所からコピーしていたのだがメンドクサイのでここに書いておく。





tweetを大量に削除

わけあって、自分の過去のtweetを大量にしかし全部ではなく削除する必要があった。

1個ずつWEBで削除していきながら、「これはAPIを使ってやるべきだな・・・」と思ったものの、やり方がわからず結局全部「手動で」消した。

調べたらごく簡単だったのでメモしておく。

tweetを削除するにはそのidを知る必要がある。まずはそれを知る方法。


#!/usr/bin/perl
use strict;
use warnings;
use YAML::Tiny;
use Net::Twitter::Lite;
use Encode;

my $config = ( YAML::Tiny->read('/hoge/config.yml') )->[0];
my $twit = Net::Twitter::Lite->new(
legacy_lists_api => 0,
consumer_key => $config->{'cs_key'}, consumer_secret => $config->{'cs_secret'} );

$twit->access_token( $config->{'ac_token'} );
$twit->access_token_secret( $config->{'ac_secret'} );


eval {
my $statuses = $twit->friends_timeline({ since_id => 1000, count => 10 });
for my $status ( @$statuses ) {
print encode('utf8',"$status->{created_at} <$status->{user}{screen_name}> $status->{text} $status->{id}\n");
}
};
warn "$@\n" if $@;


これは、Net::Twitter::Liteの、cpanのページの最初のサンプルほとんどそのままである。

tweet(status) は日本語なのでencodeしている。encodeしなくても表示できるが警告がうるさいのでencodeした。

use warning をはずせばいいのか・・・まあいいや。

since_id というのは名前からして、表示する最小のidであろう。ちなみにゼロにしたらエラーになった(省略できる)。

$status->{id}が、tweet(status)のidである。これはおそらく全tweetでユニークなidだ。

statusは新しいものからcountの数だけ取得される。

idがわかったら、以下のようにidを指定して destroy_statusを実行する。


eval {
my $statuses = $twit->destroy_status(3047606842882xxxxx);
};
warn "$@\n" if $@;


eval, warnとかの書き方はよくわからないがサンプルのままである。

あとは、tweetとidを表示し、消したいidを選んでdestroyすればよい。

「一括削除アプリ」みたいなものもあったのだが、遅い上に削除もできなかった。

注意すべきなのはあまり大量にいっぺんに表示・削除するとAPIの利用制限に引っかかるおそれがあることだ。

destroy_statusはAPIを1回呼び出すごとに1個ずつ消すから多分すぐアウトになるんじゃないか?

上記のやり方では1個だけ消せることを確認した。

大量に削除する際は要注意だ。




RSSフィードを出力するcgi

ずっと前にやったRSSフィードを出力するcgiを見直す。

Jcodeを使っているが、古いのでやめる。

が、どうやって使うのかを忘れた。

ブラウザで実行するとxmlのソースが表示される。

これでいいんだっけ?

最近はRSSはあまり使わないけど、一応できるようにはしておきたい。

さて、やってみたら何の問題もなくrssが書けた。

多分、以前苦労したのは日本語のエンコードが良くわかっていなかったからだと思う。

rssフィードを出力するcgiを書いて、実行すると、ブラウザでxmlのソースが表示される。

そのurlをgoogleリーダーに登録する。フィードが表示される。

cgiを書き直して、フィードを更新してみる。googleリーダーを更新する。フィードが更新されない。

更新間隔が短すぎるからだろうと思って、いったんフィードの登録を削除し再登録すると新しいフィードが表示される。

さて、これをどうやって使うのだろうか?

たとえばブログの新しい記事を書いてそれを通知するときには、rssフィードを追加していくのか?

今日記事を書いて、フィードを出力する。翌日書いてまたフィードを出力する。この時に昨日のフィードを上書きしたら、

昨日のフィードを読まなかった人は読めなくなる。

だが、googleリーダーに登録してずっと読んでいなくても、後でまとめて読むことができる・・・

フィードを追加していくとすると、今度はずっとたまってしまう・・・




logwatchを停める

logwatchは有益な情報が少ないので停めることにした。

どうやってインストールしたかもよく覚えていないが、

googleで検索し、/etc/cron.dailyにあるスクリプトを消す。

cron.dailyなるディレクトリの存在を初めて知る。

cron.hourlyなどもある。

それらの中のスクリプトを置いたおぼえはない。

毎日実行するようなものはここへ置くべきなのか。

たとえば /etc/cron.daily には logrotate というスクリプトがある。

毎日動くのはいいとして、何時に動くんだろう?午前零時?

話は変わるが、teratermでどこかに接続しているときに、途中でウィンドウのサイズを変えると切れる。

これはバグだね。

今再現できなかったのだがすぐつながってしまうからだ。

時々時間がかかることがあって、その時に起こる。

twitterのbot

以前twitterのボットを作った。今も動いている。まあ、作ったというほどでもない、簡単なものであるが。

botというと、名言をつぶやくものが多いが、私は非公開にして自分だけfollowし、

覚える必要があることをtweetさせている。

今はTOEICの準備をしているので、模擬試験ででてきた未知の単語やフレーズなどをtweetさせる。

twitterは病気みたいにいつも見ているので、起きた時とか、外出中のちょっとあいた時間などに読むと記憶がリフレッシュされてよいような気がする。

だがこれも、あまり頻度が高すぎると読まなくなってしまう。

これは普通のbotやtweetと同じだ。




mecabの辞書登録

登録する単語を記述したcsvファイルをIPA辞書の配下に置く。
mecabをインストールするときに辞書をutf-8にしたはずだが、
ここに置くときはeucにしないとダメだった。

$ cp mydic.csv mecab-ipadic-2.7.0-20070801/
$ cd mecab-ipadic-2.7.0-20070801


初めて登録するときは、configureとmakeをする

$ ./configure --with-charset=utf8
$ make
$ sudo make install


2回目以降はmake cleanとmakeをする

$ make clean
$ make
$ sudo make install


(参考)

http://www.mwsoft.jp/programming/nlp/mecab_dictionary_customize.html

crontabの書き方

twitterのbotをcronで動かしていて、つぶやく時間をひとつずつcrontabに書いていたのだが、便利な書き方を知った。

カンマで区切って複数時間を指定できる。

ハイフンでつないで範囲を指定できる。

8-20/3 は、8時から20時までの間で3時間置き、という意味である。

13  10  *  *  * root /hoge/hoge/hoge1
00,20,40  9-21  *  *  * root /hoge/hoge/hoge2
05  8-20/3  *  *  * root /hoge/hoge/hoge3


hogeの部分はperlで書いたスクリプトだが、スクリプト内で指定しているカレントディレクトリを認識しないようなので、絶対パス指定に書き換えた。




セッション管理の必要性

さて、ログインの仕組みを作ってみたいと考えている。

パスワードを画面上で隠す方法はわかった。

次は、それを送信する時に暗号化すればいいのだろうと考えた。

最初はperlのcrypt関数を使おうとしたが、8文字までしかチェックできないので、Digest::MD5を使うことにした。

かんたんなテストプログラムで動作を確認してさあCGIで使おうと思ったところで、止まった。

postというのは、入力した値を処理するスクリプトを指定するだけであり、

入力された値を加工してから、つまり暗号化してから、渡すことができない。

ブラウザ上では見えないが、パケットキャプチャをすると当然平文パスワードが丸見えである。

このパスワードを認証するcgiに渡してからmd5ハッシュを作ってもしょうがない。

だがどう考えても、通信自体が暗号化されていなければパスワードを暗号化して渡すことはできない。

さらっと検索しても少なくともperlでcgiを書くだけではできないようだ。

だからみんなhttpsを使っているというわけなのか。

ただ私はほんのお遊びのサイトなので、簡易的なパスワードでかまわない。

どうせ見えるのなら、平文でやりとりするかな。

apacheのベーシック認証で、アクセス制限することはできる。

が、そもそもやりたいのはアクセス制限ではなく、カスタマイズされたページの表示である。

今はどのサイトでもログインしてそのユーザ用のページを表示する。amazon, twitter, google, yahoo... なんでもそうだ。

そのためにはセッション管理が必要である。

パスワード入力フィールドをマスクするhtmlタグ

パスワード入力欄、つまり入力した文字を表示せずに*でマスクするフィールドはhtmlで指定できることを知った。

<input type="password" name="password" ....>


CGIモジュールだと、

password_field("password")


CGIモジュール

あんまり使わないが、すっきり書けそうなので使ってみる。

charsetを指定しないと iso-xxxxとかになるので、utf-8を指定する。

日本語を使わないなら、 print header() でよい。


#!/usr/bin/perl
use strict;
use warnings;
use CGI qw(:standard);

print header(-charset=>'utf-8'),
start_html(-title=>"Login"),

h2("ログインしてください"),
hr(), start_form(-method=>'post',-action=>'./post.cgi'),
p("ID : ", textfield("id")),
p("password : ", textfield("password")),
submit(-name=>'login',-value=>'ログイン'),
end_form(), hr(), end_html();


ついでに、入力された値を表示するcgi。


#!/usr/bin/perl
use strict;
use warnings;
use CGI qw(:standard);

my $id = param('id');
my $password = param('password');

print header(-charset=>'utf-8'),
start_html(-title=>"Login"),
h2("ようこそ"),
hr(),
p($id),
p($password),
hr(), end_html();


CGIモジュールは使ったことがあったのだが、以下のようにnewして ->でメソッド(?)を呼び出していた。


$obj = new CGI;
print $obj->header;


今回はPerlクックブックなどのサンプルを見たのであるが、そこではnewしないで使っている。

そして、qw(:standard) を書かないとnewしないと使えないことがわかった。

どうしてそうなるのかは、わからない。




一文字ずつパラパラと表示する

Perlクックブックより。
一文字ずつパラパラと表示する方法が紹介されていた。
まずは紹介されているものをそのまま書いて動くことを確認する。
split(//) で、一文字ずつに分解されることを知る。
ということは、分解した一文字ずつをprintし、sleepをはさんでいけばいいのではないかと思って、
while(<>){
   for(split(//)){
        print;
        sleep 1;
    }
}
とやってみたが、うまくいかない。
元のソースを見てみると、以下のような謎の1行があった。
$|=1;
そしてこれを書くと、想定どおりの動きをした。
$| とは何だろう?
調べてみたら、この値を0以外にすると出力のバッファリングをしないとのことであった。
通常はバッファリングするのでまとめて表示されるのだ。
1文字ずつパラパラと表示させるには、これを無効にする必要があったのだ。

Perlクックブック

前から、それも数年前から欲しいと思っていた本をついに入手した。

本屋にいくたびにもしあったら買おうと思うのだがいつもない。

amazonを見てみたら中古しかない。ということはいわゆる絶版か。

そして中古価格は定価の1/10程度だった。

2001年の初版モノである。外観は多少薄汚れてはいるものの、

中身は一度でも読んだかどうかというくらいきれいだ。

こういうものはどうやって入手するのだろう?

買ったはいいが読まずに本棚においたままだったとか、店で売れずにいたものを買い取るとか。

この「クックブック」は最近(といってももう数年前)に2分冊になったようであるが、その前のものである。