fuba の日記

*1294834805*新しい MacBook Air がほしいですね [[MacBook Air 11インチ欲しい！]]

金がないので MacBook という名前になった最初の白いやつを長年使ってるんですが、一度ファンを交換したにもかかわらずまたファンの音がおかしくなってきています。もう一度交換すればいいんじゃないのという感じもするけど、 http://gameport.ocnk.net/product/134 を見ると6000円。5年間使ったマシンのパームレストが割れるリスクと6000円を投じて静音化とかちょっと考えたくないです。

あと電池も交換しました。これは非純正にもかかわらず明らかに金型が同じものを円高に物を言わせて <a href="http://www.dealextreme.com/details.dx/sku.15972">DealExtreme.com で $48 程度で購入しました</a>。DealExtreme.com は発送が遅い以外は大変安く送料もだいたい無料だし PayPal も使えて安心便利なので、ゴミを購入するときにはぜひ使うといいと思います。ただこの電池、普通につかってるだけで突然電源が落ちたりするし、微妙につくりがわるくそのうち外れそうです。8ヶ月で壊れたという残念なレビューもありましたし、今みると<a href="http://www.dealextreme.com/details.dx/sku.45379">ちょっと容量のでかいやつが $38.90 で売ってる</a>し大変ファックですね。

ここまでして今の MacBook を使ってるのは金のこともありますが、tumblr ステッカーがもったいないというのもあるし新しい tumblr ステッカーもほしいですね。[[Tumblr ステッカー欲しい！]]

*1247915732*移転しました http://give-me-money.g.hatena.ne.jp/fuba/

*1237529234*fuba_recorderについて

<img src="http://gyazo.com/0f042ae62e1d4fb59d011767e9579d1a.png" />

<img src="http://gyazo.com/83a0bdcc25588674861b0c5afacf3203.png" />

**経緯 -2008年秋　friioを使った録画ツール -2009年1月　twitterを使って録画予約したい！と思い制作 -無茶振りを採用しまくって片っ端から機能追加

**開発ポリシー -アンチ人工知能 --要求の分類はすべてパターンマッチで --会話はステートレスに --用意された発言しかしない -高速開発 --タイムラインをみて要求パターンどんどん採用 --新機能は忘れられないうちに実装 --テストせずに即運用

**キャラ付けポリシー -信頼できる相方として、基本的に想定の範囲内の返答を返す --話しかけられないと話さない -たまに頭がおかしくなる --検索してきた情報の意外性 --text converter --バグ --2分以内に要望に対応して機能追加

**fuba_recorderの基本動作 -1つの要求に対して、1つの回答を返す --ボクサー --木之本桜 --おやすみ --レシピ提案 --HDD残量通知 --録画予約 ---番組録画 ---録画要約 --番組提案 -自動follow

**ひとつの要求が処理されるまでの流れ -タイムラインのクロール -tweetを各要求タイプに分類 -要求タイプに応じて返信を生成 -返信のpost --text converter

**タイムラインのクロール

-Perl Net::Twitterモジュール -2分に1回、cronで。 -friend_timelineとtimelineから、自分への要求であろうtweetに対して返信する。

>|perl| my @msgs = reverse (@{$twit->replies}, @{$twit->friends_timeline}); ||<

***自分への要求かどうかを判別

>|perl| for my $nickname (@nicknames) { if (($text =~ s/^\@$nickname//) || ( ($msg->{in_reply_to_screen_name}) && ($msg->{in_reply_to_screen_name} eq $nickname) )) { return $text; } } ||<

>|perl| my @nicknames = qw/ fuba＿recorder fuba_recoder fuba＿recoder fuba_ピカァァッフバコレ ﾌﾊﾞｺﾚ フバレコ ﾌﾊﾞﾚｺ プパペポフバレコたん フバレコちゃんフコレバ ﾌｺﾚﾊﾞ フコバレ ﾌｺﾊﾞﾚ フレコバ ﾌﾚｺﾊﾞ フレバコ ﾌﾚﾊﾞｺ フヴァレコ ﾌｳﾞｧﾚｺ ふゔゃれこ ふびゃれきょフバ様のマシーントウモロコシ小麦粉レコーダーフバリコーダー fuba_recorder /; ||<

**要求タイプの分類

-全部パターンマッチで

***reply、かつ録画の要求の場合 >|perl| if ($text =~ /^(.*?)(?:毎回|全部)(?:録画|予約|(?:録|と)っといて)/) { $keyword = $1; $is_repeat = 1; } elsif ($text =~ /^(.*?)(?:(?:、(?:なんとか)お願い)|(?:の|が|に)?(?:関する|出てる|録画|予約|(?:(?:で|出)る)?番組|(?:録|と)っといて))+/) { $keyword = $1; } elsif (($text !~ /(飲|の)みたい/) && ($text =~ /^(.*?)(?:いつやるの|(?:が|を)?(?:見|み|観)たい)/)) { $is_qa = 1; $keyword = $1; } ||<

***reply、かつ録画関連の要求でない場合

-レシピ推薦 >|perl| if ($m =~ /(?:(?:なに|なん|何)か)?(.*)(?:が|を)?(?:(?:(?:飲|の)(?:み|ま)|(?:た|食)べさ?|(?:喰|食|く)(?:い|わ))(?:た(?:い|く)|せ(?:ろ|て)))/) { if (my $str = $1) { if (server_avail($avail_dir, 'cookpad')) { my $recipe = retrieve_keyword_cookpad($str); if ($recipe) { $new_message = join('、', @{$recipe->{ingredients}}).'とか買ってきて'.$recipe->{title}.'を作れ '.$recipe->{url}; } else { $new_message = '何か違うもの食べたほうがいいですよ'; } } else { $new_message = '検索しすぎ'; } } } ||<

-reply版ボクシング >|perl| elsif (my $boxing = text_boxing( message => $m, ngwords => \@ngwords )) { $new_message = $boxing->{dump_message}; } ||<

-おやすみ >|perl| elsif ($m =~ /ペプ|行動を?開始|ねる|ねむ|ねて|眠|おやすみ|寝|バタリ|スヤ|ネルソン/) { $new_message = $goodnights[floor(rand(scalar(@goodnights)))]; } ||<

-黙る >|perl| elsif ($m =~ /黙れ/) { $new_message = '...'; } ||<

-ぜったいだいじょうぶだよ >|perl| elsif ($m =~ /血が.*出る|失業|原君|痛い|進まない|ハァ|＼\(＾o＾\)／|着る服が無い|あー|ねむい|鬱|ヘルプ|へるぷ|help|諦め|苦しい|つらい|希望がない|だめ|ダメ|駄目|死|しぬ|しにたい|自殺|たすけて|助けて|働きたくない|やだ|むり|無理|やめたい/) { $new_message = $cheerups[floor(rand(scalar(@cheerups)))]; } ||<

-謙遜 >|perl| elsif ($m =~ /おめでと|やればできる|でかした|(?:えら|偉)い|お(?:疲|つか)れ|すごい|いい(?:です)?ね|ありがと|thx|サンクス|サンキュー|thank/) { $new_message = $yourwelcomes[floor(rand(scalar(@yourwelcomes)))]; } ||<

-reply回数制限の確認 >|perl| elsif ($m =~ /回|制限/) { my $avail = server_avail($avail_dir, 'twitter_'.$req->{user}); $new_message = ($avail - 1); } ||<

-HDD残量あいまいに確認 >|perl| if (!$new_message) { if (int(rand(6))) { my $disk = disk_rest(); if ($disk < 10) { # 10GB未満 $new_message = $dies[floor(rand(scalar(@dies)))]; } elsif ($disk < 40) { # 40GB未満 $new_message = $noes[floor(rand(scalar(@noes)))]; } else { $new_message = $yeses[floor(rand(scalar(@yeses)))]; } } }

$req->{dump_message} = $new_message; return $req; } ||<

***replyじゃない場合

-非reply版ボクシング -just_do_quizのクイズにこたえる >|perl| if ($req->{message} =~ /^\[quiz\]/) { if ($req->{message} =~ /\[\s+\]|次の選択肢/) { $req->{is_quiz} = 1; return $req; } } ||<

**返信の生成

***録画関連 >|perl| if ($req->{is_quiz}) { post('@'.$req->{user}.' '.answer_quiz(map {$_->{message}} @quiz[0..1], $ua), $mid); } if ($req->{too_many}) { post('@'.$req->{user}.' 検索結果多すぎ、もうちょっと絞ってください', $mid); } elsif ($req->{is_forbidden}) { post('@'.$req->{user}.' そのiepg偽物っぽい', $mid); } elsif ($req->{has_collision}) { post('@'.$req->{user}.' '.$req->{collision_title}.'とかぶるのでむり', $mid); } elsif ($req->{is_expired}) { post('@'.$req->{user}.' そのiepg古い', $mid); } elsif ($req->{is_qa}) { if ($req->{url}) { post('@'.$req->{user}.' '.$req->{keyword}.'、これはどうですか '.$req->{url}, $mid); } else { post('@'.$req->{user}.' '.$req->{keyword}.'むり', $mid); } } elsif ($req->{is_reserved}) { if ($req->{keyword}) { post('@'.$req->{user}.' '.$req->{keyword}.'、もう予約してる', $mid); } else { post('@'.$req->{user}.' もう予約してる', $mid); } } elsif ($req->{is_repeat}) { if (grep {$req->{user} eq $_} @superusers) { my $date = DateTime->from_epoch(epoch => $req->{repeat_expire}); my $hdate = $date->ymd('-'); post('@'.$req->{user}.' '.$req->{keyword}.'、'.$hdate.'まで全部録画します', $mid); $refresh_repeat_flag = 1; } else { post('@'.$req->{user}.' 金くれ', $mid); } } elsif ($req->{default_iepg}) { if ($req->{has_collision}) { post('@'.$req->{user}.' '.$req->{collision_title}.'とかぶるからむり', $mid); } else { post('@'.$req->{user}.' 予約した', $mid); } } elsif ($req->{iepg}) { my $justified_flag = ($req->{retrieve_result}->{justify}) ? 'とりあえず' : ''; post('@'.$req->{user}.' '.$req->{keyword}.'、'.$justified_flag.'これ予約した '.$req->{url}, $mid); } elsif ($req->{keyword}) { if ($req->{search_result_num}) { post('@'.$req->{user}.' '.$req->{keyword}.'むり、番組表にはあったけどなんかとかぶってる', $mid); } else { post('@'.$req->{user}.' '.$req->{keyword}.'むり、検索にひっかかんない', $mid); } } elsif ($req->{dump_message}) { if ($req->{is_reply}) { if (grep {$req->{user} eq $_} @uzaiuser) { post('@'.$req->{user}.' '.$req->{dump_message}, $mid) if (int(rand(3))); } else { post('@'.$req->{user}.' '.$req->{dump_message}, $mid); } } if ($req->{is_boxer}) { # ボクサーの確率調整 post($req->{dump_message}, $mid) if (grep {$req->{user} eq $_} @boxeruser);

if (grep {$req->{user} eq $_} @boxeruser_light) { post($req->{dump_message}, $mid) if (int(rand(3))); next; }

next if (grep {$req->{user} eq $_} @nguser);

if (grep {$req->{user} eq $_} @nguser_light) { post($req->{dump_message}, $mid) unless (int(rand(20))); next; }

if (grep {$req->{user} eq $_} @nguser_strong) { post($req->{dump_message}, $mid) unless (int(rand(50000))); next; }

post($req->{dump_message}, $mid) unless (int(rand(3))); } } ||<

**録画以外の定型文 >|perl| my @goodnights = qw/とっとと寝ろや 寝るな ぼくもねます 6時起きな/; my $goodmorning_notice = ( ( (localtime(time))[2] + 1 + 3 + int(rand(7)) ) % 12 ).'時起きな'; push @goodnights, $goodmorning_notice, $goodmorning_notice, $goodmorning_notice; my @yeses = qw/はい はいはい 了解しました そうですね/; my @noes = qw/むり めんどくせえ... ハァ～ らめぇ はぁん はぁ？ だるい/; my @dies = qw/死ぬ 助けて マジ無理 涅槃きれい… 川渡ってる/; my @cheerups = qw/ ぜんっぜん気持ち伝わってこない！もう1回！そんなんじゃ聞こえないよ！全っ然気持ちが伝わってこない！引きずらない！切り替えていこう！がんばれがんばれできるできる絶対できるがんばれもっとやれるって！！ぜったいだいじょうぶだよ！なんとかなるよ！ぜったいだいじょうぶだよ！ぜったいなんとかなるよ！だいじょうぶだよ！ぜったいなんとかなるよ！ /; my @yourwelcomes = qw/ どういたしましてはいはいいえいえ /; ||<

***ボクサー生成 >|perl| sub text_boxing { my %opt = @_; my $m = $opt{message}; my $ngwords = $opt{ngwords};

return if (grep {my $ngword = $_; $m =~ /$ngword/} @$ngwords);

$m =~ s/^(\@[^\s]+\s+)+//; my @arms = qw/= ≡ - - - - -=≡ ≡=- ～/; push @arms, ''; my $arm = $arms[int(rand(scalar(@arms)))];

my $lnp = $arm.'o'; my @lpunches = ($lnp, $lnp, $lnp, $lnp, $lnp, '9', '９', 'ノ⧉'); my $lpunch = $lpunches[int(rand(scalar(@lpunches)))];

my $rnp = 'o'.$arm; my @rpunches = ($rnp, $rnp, $rnp, $rnp, $rnp, '6', '６', '⧉ヽ'); my $rpunch = $rpunches[int(rand(scalar(@rpunches)))];

my @lfaces = map {"(o'-')"} (0..6); push @lfaces, "o'-')"; push @lfaces, "ｲｪｲ! o'-')"; my $lface = $lfaces[int(rand(scalar(@lfaces)))];

my @rfaces = map {"('-'o)"} (0..6); push @rfaces, "('-'o"; push @rfaces, "('-'o ｲｪｲ!"; my $rface = $rfaces[int(rand(scalar(@rfaces)))];

my $result = { is_boxer => 0, dump_message => '', };

if ($m =~ s/^[^(（\{]*[(（\{]\s*\*?\s*(?:(?:´[・･])|(?:`[・･])|(?:கு)|[☉\'´ு｀`◕´◕ﾟ^＾・。◠著])/${lface}${lpunch})ﾟ/) { $result->{dump_message} = $m; $result->{is_boxer} = 1; return $result; } if ($m =~ s/(?:(?:[・･]｀)|(?:[・･]´)|(?:கு)|[\'｀ு`◕ﾟ☣\＾◠=☉?権≦])\s*\*?\s*[\})）][^)）\}]*$/ﾟ(${rpunch}${rface}/) { $result->{dump_message} = $m; $result->{is_boxer} = 1; return $result; } return; } ||<

***返信のpost

Gearmanを使い、 -録画後の要約画像生成、および報告 -通常の返信用に、2つのworkerを動作。

>|perl| sub post_twit { my %opt = @_;

delete $opt{twit}; delete $opt{ua};

my @workers = ($opt{worker}) ? ($opt{worker}) : ('localhost');

my $client = Gearman::Client->new; $client->job_servers(@workers); my $args = freeze(\%opt); my $result_ref; $result_ref = $client->dispatch_background("post_twit", \$args, {});

return $result_ref; } ||<

**worker_post_twitter.pl 投稿につかうGearman用のworker

>|perl| use Gearman::Worker; use Storable qw(thaw);

(snip)

my $worker = Gearman::Worker->new; $worker->job_servers(@hosts); $worker->register_function( post_twit => sub { my $job = shift; my %opt = %{thaw($job->arg)};

my $text = $opt{message}; my $reply_id = $opt{reply_id}; my $tcss = $opt{tcss};

if ($opt{video_path}) { return 0 unless (-e $opt{video_path}); my $thumb_url = upload_thumbnail( $opt{video_path}, ($opt{tag} || ''), ); $text .= ' '.$thumb_url; }

if (1 == int(rand(3))) { $text = convert($text, $tcss, $ua); } warn $text; my $args = { status => $text, }; $args->{in_reply_to_status_id} = $reply_id if (defined $reply_id); return ($twit->update($args)) ? 1 : 0; } );

$worker->work while 1; ||<

** text converter

<img src="http://gyazo.com/0d01c24ccfc28962300529da1a0dcfb6.png">

WedataのText Conversion Servicesに登録されてるものをつかいます。

<img src="http://gyazo.com/1d318eac88aa66246a027e26e8d7744c.png">

>|perl| sub convert { my ($text, $tcss_ref, $ua) = @_;

use HTTP::Status qw/:is/;

my @tcss = @{$tcss_ref}; srand(time);

if (@tcss) { my $id = ''; if ($text =~ s/^(\@\w+\s)//) { $id = $1 || ''; }

my $url = '';

if ($text =~ s/(\shttp\:\/\/.*)$//) { $url = $1 || ''; }

my $new_text; my $response; do { my $service = $tcss[floor(rand(scalar(@tcss)))]; my $surl = $service->{url}; warn $surl;

my $text_esc = uri_escape( encode( ($service->{enc} || 'utf-8'), $text ) ); $surl =~ s/\%s/$text_esc/;

if ($service->{xpath}) { $new_text = get_text_by_xpath($surl, $service->{xpath}, $ua, $service->{enc}); } else { $ua ||= LWP::UserAgent->new(); my $resp_local = $ua->get($surl); if (is_success($resp_local->code)) { $new_text = decode($service->{enc}, $ua->get($surl)->content); } } } while (!$new_text);

$new_text =~ s/^\@/＠/;

$text = $id.$new_text.$url; warn $text; }

return $text; }

||<

**Webサービスのつかいかた

-基本的にscraping --APIがそもそもなかった（goo番組表、cookpad） --XPathの書きすぎでもうろうとしていてAPIを使うという発想がなかった（yahoo）

**番組表の検索 -goo番組表で検索 -みつからなかったら、yahooで検索して最初のタイトルを取得 -goo番組表で再検索 -なかったらあきらめる

-番組検索 >|perl| sub retrieve_keyword { my ($keyword, $ua, $repeat) = @_;

return unless $keyword;

my $search_url = 'http://tv.goo.ne.jp/search/result.php?genres%5B%5D=&category=VU&key=' . uri_escape(encode('EUC-JP', $keyword));

my $tree = HTML::TreeBuilder::XPath::Remote->new_from_uri($search_url, $ua); my $number; my @number_nodes = $tree->findnodes('id("incontents")/p[@class="fs16"][1]'); if (@number_nodes) { my $text = $number_nodes[0]->as_text; $text =~ /\((\d+)件\)/; $number = $1 || 0; } return unless $number;

my $xpath = '//table[@class="t01"]//a[contains(@href, "/contents/program")]'; $xpath .= '[count(./img) < 1]' if ($repeat); my @urls = $tree->findnodes($xpath); my @url_list; if (scalar(@urls)) { @url_list = map { $_->attr('href') } @urls; return { number => $number, list => \@url_list, }; }

return; } ||<

-もしかして >|perl| sub justify_keyword { my ($keyword, $ua) = @_;

my $yahoo_search_url = 'http://search.yahoo.co.jp/search?ei=UTF-8&p=' . uri_escape(encode('utf-8', $keyword)); my $tree = HTML::TreeBuilder::XPath::Remote->new_from_uri($yahoo_search_url, $ua); my $text = ''; if (my @nodes = $tree->findnodes('id("web")/ol/li/a')) { $text = $nodes[0]->as_text; } $text =~ s/\[[^\]]+\]$//;

return "$text"; } ||<

**cookpadで検索 -キーワードをちょっと分解して、ふつうに検索 -結果から材料だけ抽出して、「材料買ってこい」

>|perl| sub retrieve_keyword_cookpad { my ($keyword, $ua) = @_;

return unless $keyword;

if (my @keywords = split(/と|の|で作る/, $keyword)) { $keyword = join ' ', @keywords; }

my $search_url = 'http://cookpad.com/%E3%83%AC%E3%82%B7%E3%83%94/' . uri_escape(encode('utf-8', $keyword));

my $tree = HTML::TreeBuilder::XPath::Remote->new_from_uri($search_url, $ua);

my $xpath = '//div[@class="recipe-preview"]//span[contains(@class, "title")]/a'; my $xpath_ing = '//div[@class="recipe-preview"]//div[contains(@class, "material")]'; my @urls = $tree->findnodes($xpath); my @ings_nodes = $tree->findnodes($xpath_ing); if (scalar(@urls)) { my $index = floor(rand(scalar(@urls))); my $url = $urls[$index]; my $ing = $ings_nodes[$index]->as_text;

$ing =~ s/(\s|　)+/ /g; $ing =~ s/^\s*材料：//; my @ings = map {s/\s.*$//;$_} split /、/, $ing; @ings = (@ings > 3) ? @ings[0..2] : @ings;

return { title => $url->as_text, url => $url->attr('href'), ingredients => \@ings, }; }

return; } ||<

**HDDレコーダー機能 -録画予約要求からiEPGファイルを取得、録画予約リストに追加 --毎回録画機能：日に1回1週間分の番組を検索し、マッチしたものをすべて録画予約リストに追加 -30秒ごとに録画リストを走査、録画開始フラグの立ったiEPGをつかって録画 --録画開始前にtwitterに通知 --録画終了後に要約画像といっしょにtwitterに通知

***要約画像生成

-ffmpegでスクリーンショット

<img src="http://gyazo.com/0863789ad32ca0a79a15a5083783b5c1.png">

-スクリーンショットをいろいろ処理

<img src="http://gyazo.com/253e7bdef77e7d59e2712ef2142e1cc1.png">

<img src="http://gyazo.com/6a2d9d6261aaff1bc6a40d27f8f862e7.png">

<img src="http://farm4.static.flickr.com/3422/3367200427_756a21870c_b.jpg">

-Flickr APIでアップロード

**使用したWebサービス - <a href="http://twitter.com/">Twitter</a>をプラットフォームとして - <a href="http://flickr.com/">Flickr</a>を映像要約画像の保存先として

それぞれ使用しています。また、

- <a href="http://tv.goo.ne.jp/index.html">goo番組表</a>を番組表の検索に - <a href="http://cookpad.com/">クックパッド</a>をレシピの検索に - <a href="http://www.yahoo.co.jp/">Yahoo! ウェブ検索</a>を検索時のもしかしてデータベースとして

それぞれ勝手に使用しています、すみません…

**謝辞

-このような発表の場を用意してくださったチームラボ様 -flickrのproアカウントをくださった有料会員のisbsh様 -その他いろいろアイディアを頂いたりキャラクタをパクらせてくださったtwitterでのfollowing/followers各位

ありがとうございました。これからもfuba_recorderをよろしくお願いいたします。

*1221824120*Twitterのfavorites数を表示するGreasemonkey User Script GETだとAPI制限になることが判明したのでuserscripts.orgから引き上げたけど、とりあえずソースだけ貼っとく。<del datetime="2009-05-13T13:28:37+09:00">いつものようにPOSTでAPI制限は無視できるので、使いたい人はそこいじって使えばいいとおもいます。</del><ins datetime="2009-05-13T13:28:37+09:00">もうPOSTでAPI制限は無視できなくなったので使わない方がいいですよ。</ins> >|javascript| // ==UserScript== // @name Twitter favorites counter // @namespace http://fuba.moaningnerds.org/ // @include http://twitter.com/* // ==/UserScript==

$X('//link[@rel="alternate"]')[0].href.match(/(\d+)/); var id = RegExp.$1;

GM_xmlhttpRequest({ method: "GET", url: "http://twitter.com/users/show/" + id + ".xml", onload: function(x){ if (x.responseText.match(/<favourites_count>(\d+)<\/favourites_count>/)) { var span = document.createElement('span'); span.className = 'stat_count'; span.appendChild(document.createTextNode(RegExp.$1));

var link = $X('id("favorites_tab")')[0]; link.insertBefore(span, link.childNodes[0]); } } });

// simple version of $X // $X(exp); // $X(exp, context); // @source http://gist.github.com/3242.txt function $X (exp, context) { context || (context = document); var expr = (context.ownerDocument || context).createExpression(exp, function (prefix) { return document.createNSResolver(context)(prefix) || (document.contentType == "application/xhtml+xml") ? "http://www.w3.org/1999/xhtml" : ""; });

var result = expr.evaluate(context, XPathResult.ANY_TYPE, null); switch (result.resultType) { case XPathResult.STRING_TYPE : return result.stringValue; case XPathResult.NUMBER_TYPE : return result.numberValue; case XPathResult.BOOLEAN_TYPE: return result.booleanValue; case XPathResult.UNORDERED_NODE_ITERATOR_TYPE: // not ensure the order. var ret = [], i = null; while (i = result.iterateNext()) ret.push(i); return ret; } return null; }

||<

*1220822817*[NLP]パピョプパパピポポポパポMeCabポポピピペポンパプププ((パ行ばかりの言葉をMeCabを用いて翻訳する)) >> 他消火が伊露露モナコMeCab５５二時へホンがツクる << <ins datetime="2008-09-09T06:23:26+09:00">ピャじゃなかった！なおした</ins> **ニーズ -[http://twitter.com/phji/statuses/910847263:title="ヨッピーさんがしゃべってる言葉の変換系はまだできないんですか"] -[http://twitter.com/negipo/statuses/912778119:title="youpyさんのtwitterをフィードで読んでイラッとして自動翻訳書いてたけど10分ぐらいでアホかと思ってやめた"] **実装最新版の[http://mecab.sourceforge.net/:title=MeCab]がピプピョプです。 >|| % wget http://nchc.dl.sourceforge.net/sourceforge/mecab/mecab-ipadic-2.7.0-20070801.tar.gz % tar vxzf mecab-ipadic-2.7.0-20070801.tar.gz % cd mecab-ipadic-2.7.0-20070801 % wget http://fuba.moaningnerds.org/src/papyopu/conv.pl % perl conv.pl % wget http://fuba.moaningnerds.org/src/papyopu/decode.pl % exthtml.pl -x '//*[contains(@class, "entry-content")]' -p '$v=~s/^\s+//;$v' \ -n '//a[@rel="prev"]/@href' -d 2 http://twitter.com/youpy | perl decode.pl new ポプペプ 国鉄ポパポパペープ 露サモアセールパンパポパ、プーパーペパッペピペペプペパ 南ア炉は、スーパーへ勝手に寝てくれたポンペンポポポピパ ホンレンゾ炉をしたプポップパプペンポペピポプパーペンパプピピュプポプポプポDVD プポピポパッパ ストップ安兼露米国バーゲンが無二十号棟のDVDクロキを待ったパプポパプペプッパ 他クロマツへ打ったペピポポプポプペンプポンパ 米露露雨のフェンス飲んたピピポパパーポポプパポポピポピ シシコワカート＆熊本市の日パポプパピペ + パンポピープーパイ アコウダイへ+＆ピープーパイポピパ ５二課パパピープ バカチーム Handling パッパ Handlingだった Handing ペピパ http://tinyurl.com/69slhd Handingへ二課http://tinyurl.com/69slhd パペプピパ 加セルビアペッププピパピ セックス二課に Gnip + twitter、URL ピパパピパパペッポプAPI パパパパピャピペパピ、パンパポ Gnip+twitter、URLに払いさが結構APIがマハラジャ妃へ去り、神田炉ペポン 世論 -196 ペポプ -196ゼロ区パプパププピポペパパピペパ ガスマスクにこれから云えば Jピープパペパピ Jチームが粘りポプペプ 国鉄パパペッパ 他が結果ポペパプパペペペーポ 露ベラウが寝てメートポプパペパパペピパ 露グアテマラケニア @jazzanova プンポピパ？ @jazzanova群の二課？パンペーパポン サンケーがぽんポピポプンパペッペピパポパ トリノ郡が決定がの他ポーピー ローリープポパピッパ 角田一茶プパパッパ クラかったパッピュポペポプププ ラッシュ炉へ動物ポペペーポ おめぇーも @fub プポピ @fub強いパンパポパピピパペパ 南ア露ハイチが寝たポパペププププポパピ ５課へグズグズ止まりパパペッパ 他が結果 emobile ピプペンペペピプポパパ emobileに無レンゲへ行くのだがペパーピー ケラーリーポーピーパッペププ コーヒーだって打つピュッピャ 出射ペ えポプペプ 国鉄パパパーポパポピパペププパペプパペプパプ 他アワードが呼び掛ける無差別が寝るあるプピプピー スミスリーポペンパパピ ごめんなさいパイプップパプピ、パピプパパプポポパンパポパペンパピピペパ パイプップパプピ、加ビルマ加雨のロダンラボラ￥が二二ケタピープ チームパプポンパン 各ソン・サンピパプ http://tinyurl.com/575vb3 にあるhttp://tinyurl.com/575vb3 ポプペプ 国鉄 @bulkneets ププピプポパピンピポピ @bulkneets無スミスホアキン氏の日 http://api.satoru.net/text2... http://api.satoru.net/text2... プーポーピプポプパピンプポポピパポペポプ ウーホーピストルがピンクを呑みさを寝ようピパプ にあるプペペ グレてポンプポー 本部Ρ パパペッペピパ バカ決定だプパい 歩がいパポパピプパピポ サモアビルマ妃＆ポパパイパイ ポパパイパイ ||< **何をやってるかというとまず、[http://fuba.moaningnerds.org/src/papyopu/conv.pl:title=conv.pl]で学習済みipadicの見出し語を最近はやってるパ行に変換しています。そのあと[http://fuba.moaningnerds.org/src/papyopu/decode.pl:title=decode.pl]の中でMeCabを使って形態素解析を行い、素性列：元見出し語の変換を行うことでまともな日本語に変換しています。decode.plに突っ込むテキストは[http://fuba.jottit.com/exthtml:title=exthtml.pl]で[http://twitter.com/youpy:title=Twitterのポプピーさん]の発言からぶっこ抜いてきています。 **ぱっと見た感じぜんぜんだめですね。まともだったころのポプピーさんの発言を大量に収集して、それを形態素解析＆[http://fuba.moaningnerds.org/src/papyopu/encode.pl:title=パ行に変換]したものを学習用コーパスとして使用すると多少パピになるかもピペパペン。 <ins datetime="2008-09-08T15:15:11+09:00">ピペパペンと投げるのもアレだし、簡単なのならできるかなーと取れる範囲のログ全部とってやってみたものの、文脈無視して漢字の羅列になる始末でぜんぜんだめでした。形態素解析の学習データとしては何桁か文数が足らなかったのか、それともやりかたを間違えたのかよくわかんないけど、もうちょっと考える必要がありパプペ。</ins> **参考 -[http://mecab.sourceforge.net/learn.html:title] -[http://mecab.sourceforge.net/dic-detail.html:title]

*1214852634*極悪ぶっこ抜きツールの解説ページをjottitにつくった twitterにもかいたけど。 http://fuba.jottit.com/exthtml

*1214481800*URLとXPathを渡すと値を返すツールをもっと極悪ぶっこ抜きができるツールに改造してる <ins datetime="2008-12-26T18:21:02+09:00">[http://fuba.jottit.com/exthtml:title=jottitのほうに公式ページをつくりました。]</ins>

[http://d.hatena.ne.jp/fuba/20080625/1214332962:title=値がとれるだけ]では退屈、ページ辿りたいし、その先も辿りたいので機能追加。ずいぶんたくましくなりました。あとはキャッシュができればいいですねー。 **追加したオプション :n:次のページ（別に次じゃなくてもいいけど）のURLを返してくれるXPath、いっぱいマッチさせても最初の1つしかたどりません :d:ページを辿る回数、デフォルト1 :s:HTML::Elementがマッチしてる場合にHTMLソースを吐く :f:処理しているURLも出力し、結果の値の前にはタブがついているフォーマット(いまひとつなのでなんとかしたい) :URLのかわりに-:標準入力から改行区切りでURLをうけとる **ためす twitterのfollowingリストを0回ページ継ぎ足しして、そこから抽出したユーザリストのfavoritesをさらに確認、彼らのfavってるユーザ名を集計する。 >|| % alias exthtml="perl /Users/ec/bin/exthtml.pl -a Mozilla -c ~/Library/Cookies/Cookies.plist" % exthtml -X '//strong/a[@rel="contact"]/@href' -n "//a[contains(@rel, 'next')]/@href" \ -d 0 http://twitter.com/friends | perl -ne 'chomp;print "${_}/favourites\n"' | \ exthtml -X '//*[@class="content"]/strong/a/text()' -s - | sort | uniq -c 5 33 1 4316 1 8am 1 941 1 AirReader 2 Hamachiya2 1 IronNine 1 Johnny_S (snip) 1 yoppy 6 youpy 1 yugui 2 yuiseki 3 yukichi 1 yusukebe 2 yuyarin 1 yuzupepper 1 zammersonic ||< もちろん前のほうのexthtmlの-dをふやせばfollowing全員たどれるし、後のほうのexthtmlに-n、-dをつければどんどん深く辿れてべんり。両方-d 10000ぐらいにしたら過去のfavoritesぜんぶとれます。だけどtwitterのサーバは貧弱なのであんまり叩くと死にそうでこわいですね。

*1214332962*URLとXPathを渡すと値のリストを返してくれるツールを書いた <ins datetime="2008-12-26T18:21:28+09:00">[http://fuba.jottit.com/exthtml:title=jottitのほうに公式ページをつくりました。]</ins>

画像一覧からXPathでぶっこぬいてダウンローダにわたす、といった用途から簡単なマイニングまでべんりにつかう予定。 **つかいかた >|| % ./exthtml.pl [ -a [AGENT] -e [REFERER] -c [COOKIE_JAR] ] -x [XPATH] [URL] % perl exthtml.pl -a Mozilla -c ~/Library/Cookies/Cookies.plist -X "//img[@class='image']/@src" http://www.tumblr.com/dashboard http://media.tumblr.com/XEVQcg9Zzaj7driiTV8baCRJ_400.gif http://media.tumblr.com/G6je7UvklaklnqmgypCVpgBg_100.jpg http://media.tumblr.com/G6je7UvklaklnqmgypCVpgBg_400.jpg http://media.tumblr.com/Bcb4YCrxh3s3i9vsNiAe7H0C_100.jpg http://media.tumblr.com/Bcb4YCrxh3s3i9vsNiAe7H0C_400.jpg http://media.tumblr.com/cqRYWOkYRambcyqr1PmewR2r_100.jpg http://media.tumblr.com/cqRYWOkYRambcyqr1PmewR2r_400.jpg http://media.tumblr.com/vj8toQRftakozppfgR7H0j40_400.jpg http://media.tumblr.com/51bAnBJJUamcm3iu9QVSYb2t_100.png http://media.tumblr.com/51bAnBJJUamcm3iu9QVSYb2t_400.png % perl exthtml.pl -a Mozilla -c ~/Library/Cookies/Cookies.plist -X "//table[@id='timeline']//img[@class='photo fn']/@alt" http://twitter.com/ | sort | uniq -c 1 Tadaki Osawa 1 Vol.2% 2 Yusuke Yanbe 1 hanemimi 2 log_070702 1 oklahomamixer 2 totoounk皇子 2 ☆ギンギンギラギラギンギンのもりひろ☆ 2 オリハタ 3 ヤマタケ 1 小池 陸 1 岸田渉 1 星村 ||< **code >|perl| #!/usr/bin/perl use strict; use warnings;

use Encode; use Getopt::Long;

use LWP::UserAgent; use HTTP::Cookies::Guess;

my $url = pop @ARGV;

my ($xpath, $referer, $cookie, $agent); my $result = GetOptions( "x|xpath=s" => \$xpath, "e|referer=s" => \$referer, "c|cookie-jar=s" => \$cookie, "a|agent=s" => \$agent, );

die "usage: ./exthtml.pl [ -a [AGENT] -e [REFERER] -c [COOKIE_JAR] ] -x [XPATH] [URL]" unless ($url && $xpath);

$xpath = decode('utf-8', $xpath);

my $ua = LWP::UserAgent->new; $ua->cookie_jar(HTTP::Cookies::Guess->create(file => $cookie)) if ($cookie); $ua->agent($agent) if ($agent);

my $tree = HTML::TreeBuilder::XPath::Remote->new_from_uri($url, $ua, $referer); for my $node ($tree->findnodes($xpath)) { print encode('utf-8', $node->getValue."\n"); }

package HTML::TreeBuilder::XPath::Remote; use strict; use warnings;

use List::Util qw( first );

use Encode; require Encode::Detect;

use HTML::TreeBuilder::XPath; use HTML::ResolveLink;

use LWP::UserAgent; use HTTP::Request; use HTTP::Response::Encoding;

sub new_from_uri { my ($pkg, $uri, $ua, $referer) = @_;

my $resolver = HTML::ResolveLink->new( base => $uri, );

return HTML::TreeBuilder::XPath->new_from_content( $resolver->resolve( $pkg->get($uri, $ua, $referer) ) ); }

sub get { my ($self, $uri, $ua, $referer) = @_;

my $html;

$ua ||= LWP::UserAgent->new(); my $req = HTTP::Request->new('GET', $uri); $req->header(referer => $referer) if ($referer);

my $res = $ua->request($req);

# this detection is based on Web::Scraper. if ($res->is_success) { my @encoding = ( $res->encoding, ($res->header('Content-Type') =~ /charset=([\w\-]+)/g), "Detect", "shift-jis" ); my $encoding = first { defined $_ && Encode::find_encoding($_) } @encoding;

$html = Encode::decode($encoding, $res->content); return $html; } return; } ||<

*1212111519*WedataにあるLDR Full Feedのsiteinfoを使ってフィードを全文入りにupgradeするPlagger::Plugin::Filter::EntryFullText::LDRFullFeed **[http://fuba.moaningnerds.org/src/EntryFullText/LDRFullFeed.pm:title=lib/Plagger/Plugin/Filter/EntryFullText/LDRFullFeed.pm] http://b.hatena.ne.jp/otsune/20080530#bookmark-8776502 >> SITEINFOはwedata版に書き換えたほうがよさそう << とのことなので書き換えた。[http://d.hatena.ne.jp/fuba/20080426/1209142095:title=HTML::Featureを使ってもっと便利にしたかった]のだけど、UAがうまく置き換えられない問題とかが残ってるのであいかわらずEntryFullTextを継承。[http://d.hatena.ne.jp/fuba/20080224/1203783772:title=なのでPPF::EFT::SiteInfoのEFTのyamlがうまく一緒に使えない問題]が残ってます。あとPPF::EFT::SiteInfoも野良siteinfoがあればまだ使えるのでこっちは名前かえました。<del datetime="2008-06-04T18:49:40+09:00">[http://d.hatena.ne.jp/koyachi/20080420/1208627319:title=Webservice::Wedataはkoyachiさんのところからとってきてください]</del>[http://search.cpan.org/dist/WebService-Wedata/:title=WebService::WedataもCPANからインストールできるようになりました]、とてもよかったですね。 **config.yaml >|yaml| plugins: - module: Subscription::Config config: feed: - url: http://kichiku.oq.la/rss - url: http://kawamurayukie.cocolog-nifty.com/blog/

- module: Filter::EntryFullText::LDRFullFeed config: force_upgrade: 1 # impersonate: 1 ||<

*1211559580*[Plagger][EFT]pixiv.yaml >|| # upgrade http://pixiv.net/ author: fuba custom_feed_handle: http://www\.pixiv\.net/ custom_feed_follow_link: member_illust\.php\? handle: http://www\.pixiv\.net/member_illust\.php\? extract_xpath: body: //div[@id="content2"] author: //div[@id="profile"]/div/text() ||< **config.yaml ブラウザのcookieを使う。なにかがexpiredだとmypage.phpにリダイレクトされるっぽいので一度www無しのedit.phpに捨てアクセス。custom_feed_handleはwww入りになってるのでEFTは動かないで、2つ目以降のURLだけ有効になる。 >|| global: user_agent: cookies: /Users/ec/Library/Cookies/Cookies.plist timezone: Asia/Tokyo

plugins: - module: Subscription::Config config: feed: - url: http://pixiv.net/edit.php - url: http://www.pixiv.net/ranking_r18.php - module: Filter::EntryFullText ||<