2chトリップを変換するfilterKey2Trip

2ちゃんねるのトリップを変換する関数filterKey2Tripをまとめました。phpです。

UTF-8、EUC-JP、Shift_JISで確認済みです。EUC-JPの場合のみPHP versions 5.2.1以上が必要です。

マルチバイト対応 str_replaceのmb_str_replaceを
mb_str_replace (mb_replace) – fetus
http://fetus.k-hsu.net/document/programming/php/mb_str_replace.html
から持ってくる必要があります。

/**
 * filterKey2Trip
 *
 * 「name#key」を「name ◆trip」に変換するフィルタです
 * 同時に◆(\x81\x9f)を◇(\x81\x9e)に変換します
 * PHP versions >= 5.2.1
 *
 * Copyright (c) 2008 sanemat
 *
 * @param   string   $beforeFilter  変換前文字列
 * @param   string   $useEncoding   変換前文字列の文字エンコーディング 省略時はUTF-8
 *                                  UTF-8なら'UTF-8', EUC-JPなら'cp51932',
Shift-JISなら'SJIS-win'
 * @param   string   $tripEncoding  トリップ変換時の文字エンコーディング 省略時はSJIS-win(通常SJIS-win)
 * @return  string   $afterFilter   変換後文字列
 * @license http://www.opensource.org/licenses/mit-license.php The MIT License
 * @link    http://sane.justblog.jp/blog/2008/01/2chfilterkey2tr.html
 *
 *
 * mb_str_replace
 * マルチバイト対応 str_replace
 * @version     Release 2
 * @author      HiNa (hina@bouhime.com)
 * @copyright   Copyright (C) 2006-2007 by HiNa(hina@bouhime.com).
 * @link
http://fetus.k-hsu.net/document/programming/php/mb_str_replace.html
 *
 * 参考
 * ◆ 全サーバトリップ統一作戦
 * http://qb3.2ch.net/test/read.cgi/operate/1067245837/
 * XOOPS Cube日本サイト - BluesBBにトリップ機能を!
 * http://xoopscube.jp/modules/xhnewbb/viewtopic.php?topic_id=246&forum=2
 **/
function filterKey2Trip($beforeFilter, $useEncoding = 'UTF-8',
$tripEncoding = 'SJIS-win')
{
       $afterFilter = '';
       mb_convert_variables($tripEncoding, $useEncoding, &$beforeFilter);
       $beforeFilter = mb_str_replace("\x81\x9f", "\x81\x9e",
$beforeFilter, $tripEncoding);
       if(preg_match('/([^#]*)#(.+)/', $beforeFilter, $match)) {
               $salt = substr($match[2]."H.", 1, 2);
               $salt = preg_replace("/[^\.-z]/", ".", $salt);
               $salt =
strtr($salt,"\x3A-\x40\x5B-\x60\x00-\x2D\x7B-\xFF","A-Ga-f.");
               $trip = substr(crypt($match[2], $salt),-10);
               $afterFilter = $match[1]."\x20\x81\x9f".$trip;
       }else{
               $afterFilter = $beforeFilter;
       }
       mb_convert_variables($useEncoding, $tripEncoding, &$afterFilter);
       return $afterFilter;
}

関連
◆ 全サーバトリップ統一作戦
http://qb3.2ch.net/test/read.cgi/operate/1067245837/
XOOPS Cube日本サイト – BluesBBにトリップ機能を!
http://xoopscube.jp/modules/xhnewbb/viewtopic.php?topic_id=246&forum=2
2chトリップを吐き出す関数 – ?D of K
http://d.hatena.ne.jp/ofk/20070303/1172911262
fixed-area :: [PHP] 2ちゃんねるトリップ算出関数
http://www.fixed-area.com/blog.php?id=8

広告

2chのスレッドタイトルをクラウド化 threadTitleCloud b0.1.0

Yahooの日本語形態素解析Webサービスを利用して、2chのスレタイをタグクラウドに投げ込んでみました。

作りながら妄想してたほどには面白くなかったです。うーん。HTML/TagCloudの使い方をもう少し勉強するなり、閾値上げてみるなりして、視覚的面白さを優先した方がいいのかも。プルダウンで閾値弄れるといいのかも。まあとりあえずベータ0.1.0ってことで。

参考
Yahoo!デベロッパーネットワーク – テキスト解析 – 日本語形態素解析 http://developer.yahoo.co.jp/jlp/MAService/V1/parse.html
ヤフー、文章を解析できるAPI「日本語形態素解析Webサービス」を公開:ニュース – CNET Japan
http://japan.cnet.com/news/media/story/0,2000056023,20351038,00.htm
Do You PHP はてな – Yahoo!の日本語形態素解析Webサービスを使ってTwitterで流行っているキーワードをクラウド化
http://d.hatena.ne.jp/shimooka/20070618/1182163865

コード

ライセンスはApache License, Version 2.0です。licence.txt

<?php
/**
* threadTitleCloud b0.1.0
* Copyright 2007 sanemat
*
* 参考
* Do You PHP はてな - Yahoo!の日本語形態素解析Webサービスを使ってTwitterで流行っているキーワードをクラウド化
* http://d.hatena.ne.jp/shimooka/20070618/1182163865
*
*/
/* 準備 */
date_default_timezone_set('Asia/Tokyo');
mb_internal_encoding('UTF-8');
require_once('HTTP/Client.php');
require_once('HTML/TagCloud.php');
$target = '[対象の板]';
$applicationID = '[Yahoo! DEVELOPER NETWORKのアプリケーションID]';
$ua = 'Monazilla/1.00 (threadTitleCloud b0.1.0)';
$urlYahooAPI = 'http://api.jlp.yahoo.co.jp/MAService/V1/parse';
$title = 'threadTitleCloud';
$poweredby = 'threadTitleCloud b0.1.0';
$description = 'boardName '.date('r');
/* 関数 */
//mb_str_replace - fetus
//http://fetus.k-hsu.net/document/programming/php/mb_str_replace.html
/**
* マルチバイト対応 str_replace
*
* @version     Release 2
* @author      HiNa (hina@bouhime.com)
* @copyright   Copyright (C) 2006-2007 by HiNa(hina@bouhime.com).
*/
/* 省略 */
/* メインルーチン */
//subject.txtを取ってくる
$urlSubjectTxt = $target.'subject.txt';
$cli = new HTTP_Client();
$cli -> setDefaultHeader(
  array(
    'user-agent' => $ua
  )
);
try {
  $cli -> get($urlSubjectTxt);
} catch (PEAR_Error $e){
  header("$_SERVER[SERVER_PROTOCOL] 400 Bad Request");
  header('Content-Type: text/plain;charset=UTF-8');
  exit($e->getMessage());
}
//Yahoo!APIが食べやすいように整形
$response = $cli -> currentResponse();
$html = $response['body'];
mb_convert_variables('UTF-8','SJIS-win',&$html);
$arraySubjectTxt = explode("\n", $html);
$pattern = '`^.*<>(.*)[ ]\(.*\)$`';
$text = array();
$i = $j = 0;
foreach($arraySubjectTxt as $value){
  $i++;
  if($i % 400 === 0){$j++;}
  preg_match($pattern, $value, $match);
  $temp = mb_convert_kana($match[1], 'KVas', 'UTF-8');
  $temp =  mb_str_replace(' ', '', $temp, 'UTF-8').' ';
  $temp = preg_replace('`[0-9]`u','',$temp);
  $text[$j] .= $temp;
}
//Yahoo!APIをコール
$call = new HTTP_Client();
foreach($text as $value){
 
  $param = array(
      'appid'    => $applicationID,
      'sentence' => $value,
      'results'  => 'uniq',
      'filter'   => '9'
    );
 
 
  try {
    $call -> post($urlYahooAPI, $param);
  } catch (PEAR_Error $e){
    header("$_SERVER[SERVER_PROTOCOL] 400 Bad Request");
    header('Content-Type: text/plain;charset=UTF-8');
    exit($e->getMessage());
  }
 
  $response = $call -> currentResponse();
  $xml[] = new SimpleXMLElement($response['body']);
}
$arrayWord = array();
foreach($xml as $value){
  foreach ($value->uniq_result->word_list->word as $wd) {
    if(array_key_exists((string)$wd->surface, $arrayWord)){
      $arrayWord[(string)$wd->surface] += (int)$wd->count;
      continue 1;
    }
    $arrayWord[(string)$wd->surface] = (int)$wd->count;
  }
}
//HTML_TagCloudにタグと回数を食べさせる
$max_count = false;
$cloud = new HTML_TagCloud();
foreach ($arrayWord as $key => $value) {
    if ($max_count === false) {
        $max_count = $value;
    }
    $cloud->addElement($key, null, $value * (10000 / $max_count));
}
echo $cloud->buildHTML();

動作確認では狼板を対象にしました。あと上のコードに加えてCache/Lite使ってます。

追記(2007.06.26 08:13)
コードをこっそり修正。foreachの中で間違えてbreakしてたのをcontinueに。

裏get2ch

2chのdat落ちスレッドの中で自分がどうしても読みたいスレッドは●なりモリタポなりを使って取得すべきです。が、そこまではしたくない、でも最後までよみたい、程度のスレッドも多々あります。これを解決するためにGet2chを作りました。

作る上でネックになるのは次の三点です。

  • datの保存
  • 巡回動作
  • datのビュアー

datの保存

datは容量食いです。しかも現在進行形でガンガン増えていきます。逐一貯めていくよりも、必要な人が探してdatを回収できるだけの手段を提供、に機能を限定します。

巡回動作

2chからdatを持ってくる時に、登録されたスレッドURLに対してHEADしてGETして差分を取得、という流れはスレ登録数が増えたときに破綻します。クローリング用のミラーサーバがある板はまだいいですが、cronでスクリプトを動かして〜という一連の動作の中に収めるには2chに対する連続アタックになってしまいます。

datのビュアー

サーバにdatを引っ張ってくる場合には自前のnonyuで読み込ませればいいです。しかし、nonyuは本当に必要最低限の機能しかないので、あまり便利ではありません。(そして、datを引っ張ってこない かつ ビュアーは自前 の場合、直近の何スレッドかをキャッシュすることになりますが、その辺イマイチよく理解できていません。ビュアーで読むたびに毎回相手にアクセスしに行くのが不経済なことはわかります。)

選択肢とフリーライド

自前のサーバにdatを保存する一番オーソドックスなやり方は保存・巡回の二点から却下です。そうすると、他者のサービスにフリーライドする選択肢か諦めるしかなくなります。選択肢は二つ。snapshot2chとp2.2ch.netです。

snapshotはurlをGETで投げるとその時点での中身を保存してくれます。ビュアーも用意してあるので、保存・ビュアーの二点は解決します。しかし、巡回動作が難しいです。仮に登録スレが50あるとしたら、結局毎時50回それも短時間にGETすることになってしまいます。

p2.2ch.netはスレッドを読み込んだらそのスレッドを保存してくれます。この点はsnapshotと似ています。しかし、各スレを読み込む以外に、お気にスレに登録したスレッドはワンクリックでまとめて巡回することが可能です。この機能を利用します。ただし、dat落ち後にp2.2ch.netで読み込み済みのdatの取得は可能ですが、p2.2ch.netのビュアーで見ることは出来ません。

よって、p2.2ch.netの巡回でdatを取得しておいて、別のビュアーで見るのが現実解です。

ビュアーについても同様です。前述の理由からこちらも他者のサービスにフリーライドします。com-nika.osask.jpでは2chのスレッドを表示することが可能です。また、該当スレッドがdat落ちしている かつ p2.2ch.netにdatがある場合にはp2.2ch.netから保管して表示してくれます。

さらに、●・モリタポ以外で取得したdatは短期間のうちにp2.2ch.netから削除されます。ですので、datは冷めないうちに取りにいかなくてはいけません。これにはmirrorhenkanが役立ちます。

まとめ

以上が他人のふんどしサービスget2chの説明になります。大きなサービスより小さなサービスという方向性は間違ってないはずですが、しんどいところ全部フリーライドなのは問題問題。作るのは勝手だけど公開するのはやめてよ、って言われそう。

各所に怒られたらサービスは終了です。もちろん怒られない程度のアクセス頻度・上限数に設定してますが、こればっかりはどうにも。文句言われるほど利用者来るかな。来ないだろうな。

get2chってCE用の専ブラがある

にゃんと!出来上がる前に検索しておけばよかった。ありきたりすぎた。

サブドメイン取っちゃったし(no-ipだけど)、テンプレートうまく作れなくてソースのあちこちに書き込んじゃったし、まあいっかあ。次から気をつけよう。

validate2chThread – validate 2ch thread

/** validate2chThread - validate 2ch thread
  *
  * Description
  *   bool validate2chThread ( string $URL [, string $mode ] )
  *   Validate 2ch Thread from URL.
  *
  * Parameters
  *   URL
  *     The input string.
  *   mode
  *     Let you define What type of 2ch URL you want to validate.
  *     "2" matches 2ch, "b" matches bbspink, "m" matches machiBBS,
  *     and its default is "2bm".
  *       ex) mode="2b" matches 2ch and bbspink.
  *
  * Return Values
  *   Returns TRUE if valid 2ch thread, FALSE if not.
  **/
function validate2chThread($URL,$mode ="2bm")
{
  $domain = array();
  $domains ="";
  $pattern = "";
 
  if( strpos($mode, "2" ) !== false){ $domain[] = "2ch\.net"; }
  if( strpos($mode, "b" ) !== false){ $domain[] = "bbspink\.com"; }
  if( !empty($domain) ){
    $domains = implode("|", $domain);
    $pattern = "^http://[0-9a-zA-Z]+\.({$domains})/test/read\.cgi/[0-9a-zA-Z]+/[0-9]{9,10}(/?|/[0-9a-zA-Z_\-/]+)$";
  }
 
  if( strpos($mode, "m") !== false ){
    $pattern = ( empty($pattern) ) ? "^http://[0-9a-zA-Z]+\.machi\.to/bbs/read\.pl\?BBS=[0-9a-zA-Z]+&KEY=[0-9]{9,10}$" : "({$pattern}|^http://[0-9a-zA-Z]+\.machi\.to/bbs/read\.pl\?BBS=[0-9a-zA-Z]+&KEY=[0-9]{9,10}$)";
  }
 
  return preg_match("`".$pattern."`", $URL) ? true : false;
}

datのcrawlerを共用鯖に置く非

利 「設置が容易」「コストが安価」
非 「共用鯖ではスクリプトに強制時間制限があることが多い」「共用鯖からの複数アクセスは相手にとって区別が付かない」「IPが変わらない」

自己中心で考えていくとこれ以上ない利なわけですが、使い方によっては各所に迷惑を掛けてしまいます。以下その非について触れます。
ちなみにここでは共用鯖、専用鯖、グローバルIPが割り当てられてる鯖(専用鯖、自宅鯖)ぐらいに分けて考えています。

クローラはクロールされる側から見れば、比較的短時間に同じIPから繰り返し呼び出す対象です。このとき、独自ドメインを取っていようと同じサーバの別のアカウントだろうと、同じIPからのアクセスは同一の相手からのアクセスにしか見えません。もちろんUserAgentやリファラ等で区別することが可能な場合もありますが、「どこからのアクセス」を見る際にそんなところはチェック対象にしないので大差はないです。これはアクセスに対してIPで対応しようがホストを引こうが同じことです。

要するに、自分が節度を持ってアクセスしていても同じIPから(同じサーバじゃなくても)無茶するやつがいればまとめて規制を受けます。逆もまた然りです。

次に、共用鯖のスクリプトは大量のアクセス・重い処理を一気に処理することは想定していても、ぽつぽつと定期的に(数秒〜数十秒間隔)軽い処理をすることは想定していません。だいたいどのサーバでも10秒から30秒程度で強制的にストップされてしまいます。settimelimit等の設定しだいですが、これを弄れないサーバも多いです。

制限時間内に巡回を終えなければ、という方向にばかり意識が向かってしまうと、毎秒n回の連打アクセスなんてことになってしまいます。

そして、こと2chそれも読み込みに限った規制の話をします。とかげの尻尾切り、バーボンハウス、海外規制、htaccessで手動、知っているのはこれぐらいです。2chwikiを見れば大体のことは把握できます。それ以上詳しい情報は各自調べてください。
2ちゃんねるWiki – いきいき Wiki ( http://info.2ch.net/wiki/ )

一番食らいやすいのはとかげの尻尾切り規制です。これは、K秒・分単位に、L回アクセスがあった場合、M時間アクセスできなくなる、ものです。これに引っかかると規制リスト入りしてバーボンハウスとなります。巡回プログラムをパラメータ調整しながら作っていたときに私も実際に何度か食らいました。これは回線切ってIP変えられる場合は簡単に対応できます。自分が繋いでいたIPを割り当てられた人はドンマイですが。しかし、固定IPの場合そうも行きません。ただしこの規制は、ほっとくと直ります。

連続書き込み、クローラで地引、なんて場合に引っかかるのがバーボンハウス規制です。トカゲの尻尾きり→規制リスト入りでバーボンハウスの場合は前述の通りほっとけば直ります。しかし、それ以外のルートでバーボンハウスの場合はほっといても直りません。クロール専用鯖( http://liveb1.2ch.net/ )が用意されている場合、そこは地引いてもバーボンハウスにはならないそうです。しかし、クロール鯖が用意されていない板も多々あるのでその場合は気を付ける必要があります。

海外規制は書き込みのみ規制、読み込みも規制を含めて何段階もありそうです。ここは推測にすぎません。具体的には、海外無料鯖からつないだ時に2chから接続を拒否されるものがありました。個別に規制されていたのか、IPで包括的に規制されているのか、その辺がわかる術は私にありません。

最後に手動規制。以前自分が悪さをしたり、あるいはそのIPから誰かが悪さをした場合、手動で永久規制になっているものがあります。その場合解除の見込みはほぼ無いです。

以上の規制はブラックリスト型ですが、逆にホワイトリストも存在します。みみずん、肉ちゃんねる、p2.2ch.netクラスになるとそっちに入れてもらえるらしいですが、そこまではなかなか。

「共用鯖ではスクリプトに強制時間制限があることが多い」「共用鯖からの複数アクセスは相手にとって区別が付かない」「IPが変わらない」

これらの理由から共用鯖にcrawlerを置くことは非なのです。回線負荷、ハードウェア負荷が小さくても、です。

2chのp2スレのテンプレ「※レンタル鯖にp2設置は2ch運営とp2ユーザーに迷惑なのでやめましょう。 」はこの辺も含めて言ってるんだと思います。

でもさあ、グローバルIPもらえる専用鯖や自宅鯖って技術的にも金銭的にもハードル高い…

2chのdat仕様 その4

色付きキャップなんてのもあるのね。
datの名前欄に<font color=green>なんてのや<font color=red>なんてのが出てくるそうな。

というわけで改訂版
名前欄 <a> <b> (ただしdat中には</b><b>の順番) <font>
メール欄 なし
ID等 <a>
本文 <br> <a>

2chのdat仕様 その3

名前欄、メール欄、本文ともに

& → そのまま
< → &lt;
> → &gt;
" → &quot;
‘ → そのまま

現在はこの仕様なんだけど過去もこれなのかな?

他の実体参照を直書きできるようにってことで&が書けるようになってるんだろうけど、これhtmlにする時そのまま表示するとダメだよな。と思ったら2chのread.cgiもそのまま表示してんのねー

いちいち読む時にdatが不正じゃないか確認するよりもdatを取り込むときにvalidかどうか確認すればいいのか

というかdatのvalidator作ったほうがいいのね。

でもこれ互換板のログなんての考えていくと頭痛くなりそうだなあ。ていうか2chのログだって過去から仕様変わってるしそれ追うのだけでもめんどくさそう。

表示の時にvalidか確かめるよりvalidator通すほうが一見楽だけど、整合性調べてdat書き換えるよりはエスケープするだけなら表示の時にやるほうが楽なのかな?グルグル。