同村チェッカーについて技術的な話
http://d.hatena.ne.jp/siro_xx/20111029/1319880204
概要とかは昔書いた記事を参照*1してもらうとして、
聞かれたのでちょっと技術的な部分に踏み込んで記事を書きます。
HTML解析部分
ここがわかれば他国用の同村チェッカーも作れると思うのでここから。
HTML解析のために僕が使っているのはPHPのSimple HTML DOM Parserです。
使い方は、えっと、ぐぐって!って言いたくなっちゃうくらい単純なのですが、実際のソースコードを交えつつ少し。
以下はエピローグのURLとサーバー名(DBの内部管理・検索条件用で、RP、とか、陰謀、とかそんな値が入ってます)を引数にしてHTML解析〜DB登録までを行なっている関数です。
原文ママです。ソースコード汚いとかコメントどういうことなのと言われても仕様なのでおさss
<?php // エピローグURLから挿入 function insert_vil_data($vil_url,$form_server){ $html = file_get_html($vil_url); // 変数宣言 $data; $edit_i = 0; // 村情報 // どうやらtitleからぶん取ってくるしかない様子。えー。 $title = trim_convert($html->find('title',0)->plaintext); $vil_no = preg_replace("/エピローグ \/ ([0-9]*) (.*)/s","\\1",$title,-1); $vil_name = preg_replace("/エピローグ \/ [0-9]* (.+) \- 人狼議事.*/s","\\1",$title,-1); // 鯖名の特定 $server_name = get_server_name($form_server); // プロローグ日付取得 $SS00000_time = get_vil_create_time($vil_url,$server_name); // キャラ情報 // 鯖によって若干処理が異なるので分岐 if(strcmp($form_server,"Wolf") == 0 || strcmp($form_server,"RP") == 0 ){ echo "標準orRP<br>"; // 標準 foreach($html->find('table.vindex tbody tr') as $element){ // 加工(edit_hoge)-------------------------------------- $edit_character = trim_convert($element->children(0)->plaintext); $edit_id = trim_convert($element->children(1)->plaintext); $data[] = array( 'number' => $edit_i, 'character' => $edit_character, 'id' => $edit_id); $edit_i++; } //先頭要素(列名)の削除 array_shift($data); }else if(strcmp($form_server,"RPAd") == 0 || strcmp($form_server,"ultimate") == 0){ echo "RPAd or ultimate<br>"; // RPAd foreach($html->find('table.vindex tbody tr') as $element){ // 加工(edit_hoge)-------------------------------------- $edit_string = trim_convert($element->children(0)->plaintext); $edit_string = preg_replace("/(.*)\r\n(.*)/","\\2,\\1",$edit_string,-1); $edit_string_array = explode(",",$edit_string); $edit_character = trim($edit_string_array[1]); $edit_id = trim($edit_string_array[0]); $data[] = array( 'number' => $edit_i, 'character' => $edit_character, 'id' => $edit_id); $edit_i++; } //先頭要素(列名)の削除 array_shift($data); }else{ echo "その他<br>"; foreach($html->find('table.vindex tr.i_active') as $element){ // 加工(edit_hoge)-------------------------------------- $edit_character = trim_convert($element->children(0)->plaintext); $edit_id = trim_convert($element->children(1)->plaintext); $data[] = array( 'number' => $edit_i, 'character' => $edit_character, 'id' => $edit_id); $edit_i++; } } /* echo "配列を出力する<br>"; foreach($data as $column){ var_dump($column); echo "<br>"; } */ // SQLの挿入 $db_conn = db_conn(); if($db_conn !== false){ // 村情報の登録 // 一度登録した村は登録しない $exists_vil = mysql_query("SELECT * FROM douson_vil WHERE vil_server = '" . $server_name . "' AND vil_no = '" . $vil_no . "'"); if(mysql_num_rows($exists_vil) > 0){ // 既にデータのある村なのでスキップ echo $vil_no . "already exists<br>"; return; } mysql_query("INSERT INTO douson_vil (vil_name,vil_url,vil_server,vil_no,vil_date) VALUES ('" . mysql_real_escape_string($vil_name) . "','" . $vil_url . "','" . $server_name . "','" . $vil_no . "','" . $SS00000_time . "')"); // idの保持 $vil_id = mysql_insert_id(); // PL情報の登録 foreach($data as $userdata){ mysql_query("INSERT INTO douson_user (user_id,vil_id,character_name) VALUES ('" . $userdata['id'] . "','" . $vil_id . "','" . $userdata['character'] ."')"); } } unset($html); unset($data); } ?>
もう丸1年近くコード書いてないので(仕事でも)2011年の僕はよくこんなの書けたなーって思いつつ、解説に参ります。
<?php $html = file_get_html($vil_url); ?>
Simple HTML DOM ParserのQuickStartの通りなのですが、ここでHTMLをがっと取得してきます。
一例として、このページを取得してみました。
ここで取得してきたjsonコードがこちら……と出したいところなんですが、ob_startで取ろうとしてもブラウザがタイムアウトorフリーズするというとんでもない量になってしまったので、
代わりに上記ページのHTMLソースを見ながら以下の説明を見ていただければ幸いです。
<?php // 村情報 // どうやらtitleからぶん取ってくるしかない様子。えー。 $title = trim_convert($html->find('title',0)->plaintext); $vil_no = preg_replace("/エピローグ \/ ([0-9]*) (.*)/s","\\1",$title,-1); $vil_name = preg_replace("/エピローグ \/ [0-9]* (.+) \- 人狼議事.*/s","\\1",$title,-1); ?>
村の名前を取得しています。
先程取得したHTML($html)から「title」という要素を含む0番目(0 basedなので、実質1番最初)のものからplaintext(素のテキスト)を取得してきます。
取得してきた中身がこちら。
エピローグ / 167 少年サナトリウム - 人狼議事 perjury rulez<br>- Role Play braid -
要するにtitleタグの中身をそのまま引っ張ってきただけです。
この文字列を正規表現を使ってごちゃごちゃいじくりまわして「少年サナトリウム」の部分だけを抽出しています。
ついでに村番号の「167」も取得しています。
<?php // 鯖名の特定 $server_name = get_server_name($form_server); ?>
あまり気にしないでください。サーバー名を日本語で処理するか英語で処理するか僕が血迷った結果、英語で取得したものを日本語表記にコンバートする処理が必要になっただけです。
<?php // プロローグ日付取得 $SS00000_time = get_vil_create_time($vil_url,$server_name); ?>
村建て日(一番最初のダミーの発言>>0:0の日)を取得しています。get_vil_create_time()の中身はこちら。
<?php // エピローグURLからプロローグ>>0:0を特定して日付の取得 // 既存URLに対しても実行できるように別関数化 // 返り値:日付(YYYY/MM/DD) function get_vil_create_time($vil_url,$server_name){ //エピローグURLからプロローグURLを生成する //↓エピローグURL例 //./sow.cgi?css=cinema800&vid=133&turn=6&mode=all&move=page&pageno=1 //turn=hogeをturn=0に置換 $vil_prg_url = preg_replace("/turn=[0-9]+/","turn=0",$vil_url,-1); echo "debug:vil_prg_url:" . $vil_prg_url . "<br>"; $html = file_get_html($vil_prg_url); // RPBrの例 多分これif分岐必要な予感 // <p class="mes_date" turn="0"> (0) 2012/06/01(Fri) 09時半頃</p> $SS00000 = $html->find('p.mes_date',0)->innertext; echo "debug:SS00000:" . $SS00000 . "<br>"; $SS00000_time = preg_replace("/.*(\d{4}\/\d{2}\/\d{2}).*/","\\1",$SS00000,-1); echo "debug:SS00000_time:" . $SS00000_time. "<br>"; return $SS00000_time; } ?>
この機能、追加で実装したのでエピローグのURLから今度はプロローグのURLを求めるという大変面倒くさいことをしています。
turn=0にするだけなんですけども。
あとif分岐必要な予感とか書いていてしてないのどういうことなんでしょうね2012年くらいの僕。
<?php $SS00000 = $html->find('p.mes_date',0)->innertext; ?>
これもHTMLソース見ていただければわかるのですが、
議事国の発言やタグにはclass名またはID名がちゃんとついています。これがついているから抽出ができるのでありがたやありがたや。
これはそのまま、pタグのmes_dateというclassを持つ0番目の要素(なんか日本語の順序が変ですが)のinnertextを抽出しています。
<table class="mes_nom"> <tbody> <tr class="say"> <td class="img"><img src="http://giji.check.jp/images/portrate//c05.jpg" width="90" height="130" alt=""> <td class="field"><DIV class="msg"> <h3 class="mesname">【人】 <a name="SS00000">病人 キャサリン</a></h3> <p class="mes_text">たいへん、たいへん、たいへん!</p> <p class="mes_date" turn="0"> (0) 2013/03/19(Tue) 00時半頃</p> </DIV></td> </tr></table>
キャサリンの発言一部抜粋。
要するに、
(0) 2013/03/19(Tue) 00時半頃
ここを抽出しています。
必要なのは日付だけなので、正規表現でYYYY/MM/DDの部分だけを切り取って、返しています。
……と、ここまで書いたところで既にかなり長いので続きはまた今度にしましょう!☆(ゝω・)vアデュ
*1:何も参考にはならないけど