<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>アストロデオブログ &#187; PHP</title>
	<atom:link href="http://astrodeo.com/blog/archives/category/php/feed" rel="self" type="application/rss+xml" />
	<link>http://astrodeo.com/blog</link>
	<description>Just another WordPress weblog</description>
	<lastBuildDate>Mon, 11 Jul 2011 01:38:11 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.1.2</generator>
		<item>
		<title>PHP5.3で昔のCakePHPをソースをいじったら</title>
		<link>http://astrodeo.com/blog/archives/1634</link>
		<comments>http://astrodeo.com/blog/archives/1634#comments</comments>
		<pubDate>Wed, 11 May 2011 13:50:00 +0000</pubDate>
		<dc:creator>マッチー</dc:creator>
				<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://astrodeo.com/blog/?p=1634</guid>
		<description><![CDATA[アストロデオのHPがリニューアルしました！ おめでとうございます！！　ありがとうございます！！ ま、僕は今回な～んもしてないです。みんなが頑張ってデザイン組んだりカメラ持って撮影してる横で笑ってただけです。ニコニコ動画で抱腹絶倒な動画を見て文字通り腹を抱えて笑っていただけです。 一人だけ何もしてないとか感じ悪いんで、今日のところは僕がブログを更新してリニューアル作業に花を添えたいと思います。 ここんとこ更新もサボリ気味でしたしね。またちょいちょい更新を頑張っていけたらいいんですか？ それに何といっても今日は5月11日で切れ目も良いですからね。心機一転するにはちょうど良いかもしれんです。 今日、5月11日は「技術の日」なんですよ。だから技術者の我々に取っちゃある意味一つの節目の日なんですよ。 まあ、どうして今日が技術の日なのかってのは、核実験に由来するらしいんで、IT的な技術に携わる僕らにはあまり関係ないんですけどねｗｗ とはいえ、最近は何か技術的な考察があったときにはここじゃなくて個人ブログの方に書くようにしてたので、とりわけ今ここで書くことってないんですよね……せっかく意気込んだところあれなんですけど。 だから、すっごい今さらなんだけどずっと書くのを忘れてたことをまた忘れないうちに書いとこうと思います。本当に今さらなんだけど、もしかしたらいつか誰かの役に立てるかもしれない。 以前、PHP5.3でEC-CUBEを動かしたら何やら大変なことが起きたぞってな記事を書きましたが、CakePHPでも同様なことが起こりました。 Deprecated: Assigning the return value of new by reference is deprecated in　～ ↑こんな感じのエラー。まあ原因も前回と同じで、PHPのバージョンが新しくなったのが原因です。昔に作ったサイトを手直ししようとしてローカル環境で見ると、こんなエラーが出ることがあるようです。 CakePHPの場合の解決方法は、cake/libsフォルダの中にあるconfigure.phpのwriteメソッドをいじればおｋです。 if (isset($config['debug'])) { if ($_this->debug) { error_reporting(E_ALL); //このif文を追加 if(error_reporting() > 6143) { error_reporting(E_ALL &#038; ~E_DEPRECATED); } if (function_exists('ini_set')) { ini_set('display_errors', 1); } if (!class_exists('Debugger')) { require LIBS . 'debugger.php'; } [...]]]></description>
			<content:encoded><![CDATA[<p>アストロデオのHPがリニューアルしました！</p>
<p>おめでとうございます！！　ありがとうございます！！</p>
<p>ま、僕は今回な～んもしてないです。みんなが頑張ってデザイン組んだりカメラ持って撮影してる横で笑ってただけです。ニコニコ動画で抱腹絶倒な動画を見て文字通り腹を抱えて笑っていただけです。</p>
<p>一人だけ何もしてないとか感じ悪いんで、今日のところは僕がブログを更新してリニューアル作業に花を添えたいと思います。</p>
<p>ここんとこ更新もサボリ気味でしたしね。またちょいちょい更新を頑張っていけたらいいんですか？</p>
<p>それに何といっても今日は5月11日で切れ目も良いですからね。心機一転するにはちょうど良いかもしれんです。</p>
<p>今日、5月11日は「技術の日」なんですよ。だから技術者の我々に取っちゃある意味一つの節目の日なんですよ。</p>
<p>まあ、どうして今日が技術の日なのかってのは、核実験に由来するらしいんで、IT的な技術に携わる僕らにはあまり関係ないんですけどねｗｗ</p>
<p>とはいえ、最近は何か技術的な考察があったときにはここじゃなくて個人ブログの方に書くようにしてたので、とりわけ今ここで書くことってないんですよね……せっかく意気込んだところあれなんですけど。</p>
<p>だから、すっごい今さらなんだけどずっと書くのを忘れてたことをまた忘れないうちに書いとこうと思います。本当に今さらなんだけど、もしかしたらいつか誰かの役に立てるかもしれない。</p>
<p>以前、<a href="http://astrodeo.com/blog/archives/1627">PHP5.3でEC-CUBEを動かしたら何やら大変なことが起きたぞ</a>ってな記事を書きましたが、CakePHPでも同様なことが起こりました。</p>
<p>Deprecated: Assigning the return value of new by reference is deprecated in　～</p>
<p>↑こんな感じのエラー。まあ原因も前回と同じで、PHPのバージョンが新しくなったのが原因です。昔に作ったサイトを手直ししようとしてローカル環境で見ると、こんなエラーが出ることがあるようです。</p>
<p>CakePHPの場合の解決方法は、cake/libsフォルダの中にあるconfigure.phpのwriteメソッドをいじればおｋです。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

if (isset($config['debug'])) {
    if ($_this->debug) {
        error_reporting(E_ALL);

        //このif文を追加
        if(error_reporting() > 6143) {
            error_reporting(E_ALL &#038; ~E_DEPRECATED);
        }

        if (function_exists('ini_set')) {
            ini_set('display_errors', 1);
        }

        if (!class_exists('Debugger')) {
            require LIBS . 'debugger.php';
        }

        if (!class_exists('CakeLog')) {
            require LIBS . 'cake_log.php';
        }

        Configure::write('log', LOG_NOTICE);
    } else {

～以下略～

</textarea>
<p>cakeフォルダの中身を直接いじるのはあまり良くないような気はするんですけど、まあこれくらいなら良いよね。たぶん。</p>
<p>もっとも、今回みたいにlibsの直下にあるファイルをapp側にコピーしていじるとかって、できるんですかね？　もしできるばあい、どこにコピーすればいいんだろう？　appの直下？</p>
]]></content:encoded>
			<wfw:commentRss>http://astrodeo.com/blog/archives/1634/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>PHP5.3でEC-CUBEを動かしたら</title>
		<link>http://astrodeo.com/blog/archives/1627</link>
		<comments>http://astrodeo.com/blog/archives/1627#comments</comments>
		<pubDate>Thu, 31 Mar 2011 09:12:37 +0000</pubDate>
		<dc:creator>マッチー</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://astrodeo.com/blog/?p=1627</guid>
		<description><![CDATA[今さら感全開な記事ではあるんですが、久しぶりにEC-CUBEの案件を担当したので、ちょうどいいかなと思いました。 とあるタイミングで、まあ随分前の話なんですが、一度xamppをインストールし直したんですね。し直したっていうか、パソコンを新しくしたので入れ直したって言った方が正しいと思うんですけど。 そしたら、EC-CUBEのプロジェクトを開いたときに何やらよく分からないエラーが大量に出たんですよ。 Deprecated: Assigning the return value of new by reference is deprecated in　～ みたいな感じのエラー。これはどうやらPHP5.3の環境だと出るらしくて、ようはPHPのバージョンが新しくなってんぞこの野郎的なエラーってことです。CakePHPでも同じようなエラーが出ました。昔のプロジェクトとかを開くと、もんのすごい量のエラーが出る。 対処法は簡単で、error_reportの設定をちょいちょいと書きかえれば大丈夫のようです。 data/classにあるSC_Initial.phpファイルの中でエラーレベルの設定を行っている部分があります。たぶん100行目あたり。そこを書き換えてあげればいいようです。 function setErrorReporting() { //こいつをコメントアウト // error_reporting(E_ALL &#038; ~E_NOTICE); //こっちに書き換え error_reporting(E_ERROR &#038; ~E_NOTICE &#038; ~E_PARSE); } 原因が分かってしまえば何てことないですけど、今まで動いてたのがいきなり大量のエラーを吐き出すようになったら、ちょっと焦るよね。 「うおいっ！　俺が何したよ！？」ってね。]]></description>
			<content:encoded><![CDATA[<p>今さら感全開な記事ではあるんですが、久しぶりにEC-CUBEの案件を担当したので、ちょうどいいかなと思いました。</p>
<p>とあるタイミングで、まあ随分前の話なんですが、一度xamppをインストールし直したんですね。し直したっていうか、パソコンを新しくしたので入れ直したって言った方が正しいと思うんですけど。</p>
<p>そしたら、EC-CUBEのプロジェクトを開いたときに何やらよく分からないエラーが大量に出たんですよ。</p>
<p>Deprecated: Assigning the return value of new by reference is deprecated in　～</p>
<p>みたいな感じのエラー。これはどうやらPHP5.3の環境だと出るらしくて、ようはPHPのバージョンが新しくなってんぞこの野郎的なエラーってことです。CakePHPでも同じようなエラーが出ました。昔のプロジェクトとかを開くと、もんのすごい量のエラーが出る。</p>
<p>対処法は簡単で、error_reportの設定をちょいちょいと書きかえれば大丈夫のようです。</p>
<p>data/classにあるSC_Initial.phpファイルの中でエラーレベルの設定を行っている部分があります。たぶん100行目あたり。そこを書き換えてあげればいいようです。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

function setErrorReporting() {
    //こいつをコメントアウト
//  error_reporting(E_ALL &#038; ~E_NOTICE);

    //こっちに書き換え
   error_reporting(E_ERROR &#038; ~E_NOTICE &#038; ~E_PARSE);
}

</textarea>
<p>原因が分かってしまえば何てことないですけど、今まで動いてたのがいきなり大量のエラーを吐き出すようになったら、ちょっと焦るよね。</p>
<p>「うおいっ！　俺が何したよ！？」ってね。</p>
]]></content:encoded>
			<wfw:commentRss>http://astrodeo.com/blog/archives/1627/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>PHPでUNIXコマンドを使う</title>
		<link>http://astrodeo.com/blog/archives/1623</link>
		<comments>http://astrodeo.com/blog/archives/1623#comments</comments>
		<pubDate>Tue, 08 Feb 2011 09:23:21 +0000</pubDate>
		<dc:creator>マッチー</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://astrodeo.com/blog/?p=1623</guid>
		<description><![CDATA[前に、忘れないようにブログに書いとこうと思っていたのをすっかり忘れていたことを唐突に思い出したので、また忘れてしまう前に書きとめとこうと思います。 コマンドを使って直接ファイルを操作したい。でもSSHの情報をもらってないからTeratermとかは使えない。 そんなときは、PHPからコマンドを使えば良いようです。 例えば、UNIXコマンドでディレクトリの中身を見る『ls』 これをPHPで使うなら、こう↓ 圧縮ファイル(たとえばzip)を作るなら、こう↓ //test_dirというディレクトリをzipに圧縮 作った圧縮ファイルを解凍するなら、こう↓ 要はこのsystem()って関数を使えば良いってことですね。引数に普段SSHなどで実行してるコマンドをそのまま書いてやれば動くと、そんな感じです。 先日、まさにファイルを圧縮して解凍したい状況に出会いまして、でもSSHの情報を先方さんからもらってないからコマンドを打つことができなくて、こんなときどうすんのかな～と思ってたら、横の人が「PHPから直接コマンド叩けばええやん」って言ったんですけど、ぶっちゃけ何言ってんだかさっぱり分かりませんでした。何のことか分からないから、素直にそんなことができるのかと聞き返したら「システム関数を使えばええんやで」って言われて、もっと分からなくなりました。 こういう関数があったんですね。 いやはや、まだまだ知らないことが多過ぎですわい。]]></description>
			<content:encoded><![CDATA[<p>前に、忘れないようにブログに書いとこうと思っていたのをすっかり忘れていたことを唐突に思い出したので、また忘れてしまう前に書きとめとこうと思います。</p>
<p>コマンドを使って直接ファイルを操作したい。でもSSHの情報をもらってないからTeratermとかは使えない。</p>
<p>そんなときは、PHPからコマンドを使えば良いようです。</p>
<p>例えば、UNIXコマンドでディレクトリの中身を見る『ls』</p>
<p>これをPHPで使うなら、こう↓</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

<?php system("ls"); ?>

</textarea>
<p>圧縮ファイル(たとえばzip)を作るなら、こう↓</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

//test_dirというディレクトリをzipに圧縮
<?php system("zip test.zip test_dir") ?>

</textarea>
<p>作った圧縮ファイルを解凍するなら、こう↓</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

<?php system("unzip test.zip") ?>

</textarea>
<p>要はこのsystem()って関数を使えば良いってことですね。引数に普段SSHなどで実行してるコマンドをそのまま書いてやれば動くと、そんな感じです。</p>
<p>先日、まさにファイルを圧縮して解凍したい状況に出会いまして、でもSSHの情報を先方さんからもらってないからコマンドを打つことができなくて、こんなときどうすんのかな～と思ってたら、横の人が「PHPから直接コマンド叩けばええやん」って言ったんですけど、ぶっちゃけ何言ってんだかさっぱり分かりませんでした。何のことか分からないから、素直にそんなことができるのかと聞き返したら「システム関数を使えばええんやで」って言われて、もっと分からなくなりました。</p>
<p>こういう関数があったんですね。</p>
<p>いやはや、まだまだ知らないことが多過ぎですわい。</p>
]]></content:encoded>
			<wfw:commentRss>http://astrodeo.com/blog/archives/1623/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>timezoneの問題なんて知らねーっすよ</title>
		<link>http://astrodeo.com/blog/archives/1593</link>
		<comments>http://astrodeo.com/blog/archives/1593#comments</comments>
		<pubDate>Fri, 10 Dec 2010 03:07:22 +0000</pubDate>
		<dc:creator>マッチー</dc:creator>
				<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://astrodeo.com/blog/?p=1593</guid>
		<description><![CDATA[今日、現在関わっている仕事のクライアントさんのテストサーバーにCakePHPの本体をアップロードしたんですけど、そしたら見たことないエラーが出ました。 Warning: date() [http://php.net/function.date]: It is not safe to rely on the system&#8217;s timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected &#8216;Asia/Tokyo&#8217; for &#8216;JST/9.0/no DST&#8217; [...]]]></description>
			<content:encoded><![CDATA[<p>今日、現在関わっている仕事のクライアントさんのテストサーバーにCakePHPの本体をアップロードしたんですけど、そしたら見たことないエラーが出ました。</p>
<p><strong>Warning: date() [http://php.net/function.date]: It is not safe to rely on the system&#8217;s timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected &#8216;Asia/Tokyo&#8217; for &#8216;JST/9.0/no DST&#8217;</strong></p>
<p>どうやらタイムゾーンが設定されていないせいでdate()関数が使えませんよ～ってなことらしいです。</p>
<p>PHP5.1以降は、デフォルトのタイムゾーンが設定されていないとこんなエラーが出るみたいですね。今まで見たことなかったってことは、今まで関わってきたサーバーはどれもちゃんとそのデフォルトのタイムゾーンが設定されていたってことなんですかね。</p>
<p>これを解決するには、php.iniの設定を書き換えるか、date_default_timezone_set()関数を使うと良いようです。</p>
<textarea name="code" class="php:nocontrols" cols="30" rows="5">

//php.ini
date.timezone = Asia/Tokyo

//date_default_timezone_set()を使う場合
date_default_timezone_set('Asia/Tokyo');

</textarea>
<p>こういうときはphp.iniを直接書き替えたいところなんですけど、すでに動いているサイトのphp.iniを書き換えるのはちょっとあれなので、てゆーか、どこにphp.iniファイルがあるか分からなかったので、date何ちゃら関数の方で対処しました。</p>
<p>bootstrap.phpにでも書いとけば大丈夫かなって思ったんですけど、どうやらbootstrap.phpが呼ばれる前にdate()関数が発動している部分があるようで、仕方ないのでindex.phpに書きました。</p>
<p>いいんかいな……？</p>
]]></content:encoded>
			<wfw:commentRss>http://astrodeo.com/blog/archives/1593/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>csvファイルでdbに登録時、日本語文字化け</title>
		<link>http://astrodeo.com/blog/archives/1387</link>
		<comments>http://astrodeo.com/blog/archives/1387#comments</comments>
		<pubDate>Thu, 04 Nov 2010 09:59:42 +0000</pubDate>
		<dc:creator>oc</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://astrodeo.com/blog/?p=1387</guid>
		<description><![CDATA[大体の処理は書けてたのですが文字化けが直らず調べてると $gyosei_cd = mb_convert_encoding($gyosei_cd, "UTF-8","Shift-JIS"); このmb_convert_encodingはもし文字コードがShift-JISだったらUTF8に置き換えてくれます。 これをしてなかったので文字化けが直りませんでした。失態。 で、今回insertするデータが多く処理途中でエラーが。 php.iniで設定されているset_time_limitを set_time_limit(0); と現在のファイルの先頭書いて解決。 set_time_limit→実行時間の最大値を制限するのを一時的に制限なしとしてみました。 どうでもいいですが原付でこけたら痛いですね。]]></description>
			<content:encoded><![CDATA[<p>大体の処理は書けてたのですが文字化けが直らず調べてると</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">
$gyosei_cd = mb_convert_encoding($gyosei_cd, "UTF-8","Shift-JIS");
</textarea>
<p>このmb_convert_encodingはもし文字コードがShift-JISだったらUTF8に置き換えてくれます。<br />
これをしてなかったので文字化けが直りませんでした。失態。</p>
<p>で、今回insertするデータが多く処理途中でエラーが。</p>
<p>php.iniで設定されているset_time_limitを</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">
set_time_limit(0);
</textarea>
<p>と現在のファイルの先頭書いて解決。</p>
<p>set_time_limit→実行時間の最大値を制限するのを一時的に制限なしとしてみました。</p>
<p>どうでもいいですが原付でこけたら痛いですね。</p>
]]></content:encoded>
			<wfw:commentRss>http://astrodeo.com/blog/archives/1387/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>headerでexitしてなかった……</title>
		<link>http://astrodeo.com/blog/archives/1140</link>
		<comments>http://astrodeo.com/blog/archives/1140#comments</comments>
		<pubDate>Fri, 08 Oct 2010 13:58:48 +0000</pubDate>
		<dc:creator>マッチー</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://astrodeo.com/blog/?p=1140</guid>
		<description><![CDATA[PHPでHTTPヘッダを送信する関数に『header』がありますよね。 僕は今まで、この関数はリダイレクト処理くらいにしか使ったことがなかったもんで、こいつはリダイレクト処理を行ってくれる、CakePHPでいうところの$this->redirect()と同じ関数だと思ってました。 いや、まあ実際に$this->redirect()も中身を見てみると最後にheaderをやってるんですが、これってheaderが読み込まれた時点でページ遷移が発生するわけじゃなかったんですね。 header('Location: test.php'); $a = $b = 1; $a + $b; みたいなことを書いておくと、$a + $bまでの処理を行ってから、test.phpに飛ぶみたいですね。ぶっちゃけ知りませんでした。 なので、headerでリダイレクト処理を行うときは、exitをつけるのを忘れないようにしないといけないみたいです。 header('Location: test.php'); exit; 今までたまたま上手く行っていたから気にしたことなかったんですけど、この前headerが上手く動かない変なバグに出会いまして……いろいろと原因を探ってみたら、このexitが足りないのが問題だったようです。これを加えたら、正常に動いた。 CakePHPの$this->redirect()も、見るとちゃんとexitしてました。うちの先輩プログラマにも「そんなの常識だよ？　誰もがやってる当り前の処理だよ。お前はマジでプログラマの風上にも風下にも風前にも風後にも風左にも風右にも片隅にもおけないな」って言われました。俺だけがやっていなかったようです。 今後は気をつけたいと思います。]]></description>
			<content:encoded><![CDATA[<p>PHPでHTTPヘッダを送信する関数に『header』がありますよね。</p>
<p>僕は今まで、この関数はリダイレクト処理くらいにしか使ったことがなかったもんで、こいつはリダイレクト処理を行ってくれる、CakePHPでいうところの$this->redirect()と同じ関数だと思ってました。</p>
<p>いや、まあ実際に$this->redirect()も中身を見てみると最後にheaderをやってるんですが、これってheaderが読み込まれた時点でページ遷移が発生するわけじゃなかったんですね。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

header('Location: test.php');

$a = $b = 1;

$a + $b;

</textarea>
<p>みたいなことを書いておくと、$a + $bまでの処理を行ってから、test.phpに飛ぶみたいですね。ぶっちゃけ知りませんでした。</p>
<p>なので、headerでリダイレクト処理を行うときは、exitをつけるのを忘れないようにしないといけないみたいです。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

header('Location: test.php');

exit;

</textarea>
<p>今までたまたま上手く行っていたから気にしたことなかったんですけど、この前headerが上手く動かない変なバグに出会いまして……いろいろと原因を探ってみたら、このexitが足りないのが問題だったようです。これを加えたら、正常に動いた。</p>
<p>CakePHPの$this->redirect()も、見るとちゃんとexitしてました。うちの先輩プログラマにも「そんなの常識だよ？　誰もがやってる当り前の処理だよ。お前はマジでプログラマの風上にも風下にも風前にも風後にも風左にも風右にも片隅にもおけないな」って言われました。俺だけがやっていなかったようです。</p>
<p>今後は気をつけたいと思います。</p>
]]></content:encoded>
			<wfw:commentRss>http://astrodeo.com/blog/archives/1140/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>phpでExcelファイルの中身を取得する</title>
		<link>http://astrodeo.com/blog/archives/686</link>
		<comments>http://astrodeo.com/blog/archives/686#comments</comments>
		<pubDate>Mon, 05 Jul 2010 08:11:19 +0000</pubDate>
		<dc:creator>マッチー</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://astrodeo.com/blog/?p=686</guid>
		<description><![CDATA[$file = fopen('test.csv', 'r'); $result = fgetcsv($file); みたいなことをやると、csvファイルの中身を配列形式で取って来ることができますが、同様のことがxlsファイル（Excelのファイル）でもできないのかなと思っていたら、それを可能にするライブラリが見つかったので、今回はPHP-ExcelRaderというのを使ってみました。 ダウンロードしてファイルを解凍したら、Excelというフォルダの中にこんな感じ（↓）のファイルが入っていると思いますので、それを適当なディレクトリに移します。まあ、Excelフォルダごと移動しちゃえばいいでしょう。 oleread.inc reader.php 場合によっては、reader.phpの中身をちょこっと書き換える必要があります。だいたい30行目あたりかな……？ require_once 'Spreadsheet/Excel/Reader/OLERead.php'; ↓ require_once 'Excel/oleread.inc'; 使い方は簡単です。巻きで説明すると、reader.phpを読み込んでクラスをnewしてエンコードを指定してデータをreadすればOK。お手軽ですね。チャーハンよりもお手軽だ。 require_once 'Excel/reader.php'; $excel = new Spreadsheet_Excel_Reader(); $excel->setUTFEncoder('mb'); $excel->setOutputEncoding('UTF-8'); $excel->read('test.xls'); $data = $excel->sheets[0]; ソースで書くとこんなんです。これで$dataにExcelファイルの中身がシート単位で配列に入っています。ファイルの中にシートが2つあるなら $excel->sheets[0] => 1番目のシートの中身 $excel->sheets[1] => 2番目のシートの中身 ってな具合になります。]]></description>
			<content:encoded><![CDATA[<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

$file = fopen('test.csv', 'r');

$result = fgetcsv($file);

</textarea>
<p>みたいなことをやると、csvファイルの中身を配列形式で取って来ることができますが、同様のことがxlsファイル（Excelのファイル）でもできないのかなと思っていたら、それを可能にするライブラリが見つかったので、今回は<a href="http://sourceforge.net/projects/phpexcelreader/" target="_blank">PHP-ExcelRader</a>というのを使ってみました。</p>
<p>ダウンロードしてファイルを解凍したら、Excelというフォルダの中にこんな感じ（↓）のファイルが入っていると思いますので、それを適当なディレクトリに移します。まあ、Excelフォルダごと移動しちゃえばいいでしょう。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

oleread.inc

reader.php

</textarea>
<p>場合によっては、reader.phpの中身をちょこっと書き換える必要があります。だいたい30行目あたりかな……？</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

require_once 'Spreadsheet/Excel/Reader/OLERead.php';
↓
require_once 'Excel/oleread.inc';

</textarea>
<p>使い方は簡単です。巻きで説明すると、reader.phpを読み込んでクラスをnewしてエンコードを指定してデータをreadすればOK。お手軽ですね。チャーハンよりもお手軽だ。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

require_once 'Excel/reader.php';

$excel = new Spreadsheet_Excel_Reader();

$excel->setUTFEncoder('mb');

$excel->setOutputEncoding('UTF-8');

$excel->read('test.xls');

$data = $excel->sheets[0];

</textarea>
<p>ソースで書くとこんなんです。これで$dataにExcelファイルの中身がシート単位で配列に入っています。ファイルの中にシートが2つあるなら</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

$excel->sheets[0] => 1番目のシートの中身

$excel->sheets[1] => 2番目のシートの中身

</textarea>
<p>ってな具合になります。</p>
]]></content:encoded>
			<wfw:commentRss>http://astrodeo.com/blog/archives/686/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>mailtoで初めから件名などを入れておく</title>
		<link>http://astrodeo.com/blog/archives/675</link>
		<comments>http://astrodeo.com/blog/archives/675#comments</comments>
		<pubDate>Mon, 28 Jun 2010 03:32:48 +0000</pubDate>
		<dc:creator>マッチー</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://astrodeo.com/blog/?p=675</guid>
		<description><![CDATA[お問い合わせはこちら みたいな感じで、リンクからメールを送信することができますよね。 リンクをクリックすれば何かしらのメーラーが起動すると思うのですが、このとき、何かしらの事情で件名や本文に何かしらの初期値を何かしら入れておきたい場合が何かしらあるかもしれません。何かしらって言い過ぎかしら？ で、そんなときはどうすれば良いかしら？　ってことなんですが、メールアドレスの後ろにURLでGETのパラメータを渡すみたいに、直接パラメータをくっつけちゃえば良いみたいです。 お問い合わせはこちら 携帯サイトを作ってて、お問い合わせフォームを作る代わりにいきなりメーラーを起動させちゃう場合なんかは、本文に名前や電話番号の入力項目名を入れたりする場合があるかもしれない。そういうときに使うことができると思います。]]></description>
			<content:encoded><![CDATA[<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

<a href="mailto:メールアドレス">お問い合わせはこちら</a>

</textarea>
<p>みたいな感じで、リンクからメールを送信することができますよね。</p>
<p>リンクをクリックすれば何かしらのメーラーが起動すると思うのですが、このとき、何かしらの事情で件名や本文に何かしらの初期値を何かしら入れておきたい場合が何かしらあるかもしれません。何かしらって言い過ぎかしら？</p>
<p>で、そんなときはどうすれば良いかしら？　ってことなんですが、メールアドレスの後ろにURLでGETのパラメータを渡すみたいに、直接パラメータをくっつけちゃえば良いみたいです。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

<a href="mailto:メールアドレス?subject=件名&#038;body=お問い合わせ内容を記述してください">お問い合わせはこちら</a>

</textarea>
<p>携帯サイトを作ってて、お問い合わせフォームを作る代わりにいきなりメーラーを起動させちゃう場合なんかは、本文に名前や電話番号の入力項目名を入れたりする場合があるかもしれない。そういうときに使うことができると思います。</p>
]]></content:encoded>
			<wfw:commentRss>http://astrodeo.com/blog/archives/675/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>strtotimeの+monthについて</title>
		<link>http://astrodeo.com/blog/archives/590</link>
		<comments>http://astrodeo.com/blog/archives/590#comments</comments>
		<pubDate>Thu, 06 May 2010 00:38:01 +0000</pubDate>
		<dc:creator>マッチー</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://astrodeo.com/blog/?p=590</guid>
		<description><![CDATA[どうもこんにちは。マッチーです。 GW？　何それ？　日本にはそんな連休があったんでしたっけ？ 大型連休を取っている人は、今度の土日まで連休が続く人もいますよね。まったくうらやましい話です。 僕はGWは一日も休みがありませんでした。今度の土日もおそらく仕事してます。まあ、30年近く生きていればそんな年もあるさ。 さて、phpを使ってカレンダーなんかを作っていて、来月の月を表示させたい、そんなときはdate関数を以下のように使えば楽勝。 echo date('m月', strtotime('+1 month')); ……と、思っていたのだけれど、案外そうでもなかった。 基本的には大丈夫なんだけど、例えば今日が3月31日の場合、上記の書き方だと、内部的には4月31日を出力しているらしい。でも4月には31日がないので、実際の出力は5月1日になる。だからechoされるのは5月になる。他の31日がない月も同様なので、つまり西を向いているお侍さんはさすがに武士だけあって、+1 monthなんていうハイカラな横文字を素直に受け入れる気はないらしいですね。 いや、ある意味素直に受け入れてるからこその出力結果とも言えるんだけど……まあそれはどうでもいいや。 $timestamp = strtotime(date('Y-m')); echo date('m月', strtotime('+1 month', $timestamp)); こうやると、$timestampには今月の1日のタイムスタンプが入るから、今日が何日であろうともちゃんと来月が出力される。 一応こんな感じで回避することもできるんですけど、ちょっとダサいかも？ あと、php4だとこの場合、$timestampは1970-01-01になってしまうようなので、date(&#8216;Y-m&#8217;)をdate(&#8216;Y-m-01&#8242;)とかにする必要があるっぽい。 あるいはmktimeを使うとか。 $timestamp = mktime(0,0,0,date('m'),1,date('Y'); echo date('m月', strtotime('+1 month', $timestamp)); ものすごく関係ないけど、 (&#8216;m&#8217;)　←顔文字っぽいよね。]]></description>
			<content:encoded><![CDATA[<p>どうもこんにちは。マッチーです。</p>
<p>GW？　何それ？　日本にはそんな連休があったんでしたっけ？</p>
<p>大型連休を取っている人は、今度の土日まで連休が続く人もいますよね。まったくうらやましい話です。</p>
<p>僕はGWは一日も休みがありませんでした。今度の土日もおそらく仕事してます。まあ、30年近く生きていればそんな年もあるさ。</p>
<p>さて、phpを使ってカレンダーなんかを作っていて、来月の月を表示させたい、そんなときはdate関数を以下のように使えば楽勝。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

echo date('m月', strtotime('+1 month'));

</textarea>
<p>……と、思っていたのだけれど、案外そうでもなかった。</p>
<p>基本的には大丈夫なんだけど、例えば今日が3月31日の場合、上記の書き方だと、内部的には4月31日を出力しているらしい。でも4月には31日がないので、実際の出力は5月1日になる。だからechoされるのは5月になる。他の31日がない月も同様なので、つまり西を向いているお侍さんはさすがに武士だけあって、+1 monthなんていうハイカラな横文字を素直に受け入れる気はないらしいですね。</p>
<p>いや、ある意味素直に受け入れてるからこその出力結果とも言えるんだけど……まあそれはどうでもいいや。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

$timestamp = strtotime(date('Y-m'));

echo date('m月', strtotime('+1 month', $timestamp));

</textarea>
<p>こうやると、$timestampには今月の1日のタイムスタンプが入るから、今日が何日であろうともちゃんと来月が出力される。</p>
<p>一応こんな感じで回避することもできるんですけど、ちょっとダサいかも？</p>
<p>あと、php4だとこの場合、$timestampは1970-01-01になってしまうようなので、date(&#8216;Y-m&#8217;)をdate(&#8216;Y-m-01&#8242;)とかにする必要があるっぽい。</p>
<p>あるいはmktimeを使うとか。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

$timestamp = mktime(0,0,0,date('m'),1,date('Y');

echo date('m月', strtotime('+1 month', $timestamp));

</textarea>
<p>ものすごく関係ないけど、</p>
<p>(&#8216;m&#8217;)　←顔文字っぽいよね。</p>
]]></content:encoded>
			<wfw:commentRss>http://astrodeo.com/blog/archives/590/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>真面目にエロサイトを作ってみた【プログラマ編】</title>
		<link>http://astrodeo.com/blog/archives/257</link>
		<comments>http://astrodeo.com/blog/archives/257#comments</comments>
		<pubDate>Mon, 01 Feb 2010 02:54:22 +0000</pubDate>
		<dc:creator>マッチー</dc:creator>
				<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[ウェブサービス]]></category>
		<category><![CDATA[サイト発表]]></category>

		<guid isPermaLink="false">http://astrodeo.com/blog/?p=257</guid>
		<description><![CDATA[真面目にエロサイトを作ってみた【デザイナー編】はコチラから。 エロサイトを作るにあたって 「スタイリッシュなアダルトサイト思いついたから作るぞ」 （たぶん）そんな感じの一言で始まったSheCoolのサイト制作。見た目がどれくらいスタイリッシュかってのはデザイナーの裁量次第なので、自分の担当はあくまでもシステム部分。発案者の意にできるだけ添えられるように、未熟ながらも自分の技術力を駆使してサイト制作に乗り出しました。 デザインの前に、とりあえず大まかな仕様を決めた時点で、今までやったことのない技術や、そこまで強く意識していなかったことがいくつも出てきました。主なところでは データのスクレイピング cronを利用したバッチ処理(DBの自動更新) SQLのチューニング 辺りが自分にとっての未開領域でした。特にスクレイピングなんて、正直なところそういう単語すら初めて聞くくらいのレベル。所詮は素人プログラマです。ついでに、性欲は人よりあるわりに普段はほとんどアダルト動画を見ないもんですから、どんなアダルト動画の配信サイトがあるのかというのもよく知りませんでした。所詮は素人アダルト動画ウォッチャーです。 しかし今回の制作は何よりもスクレイピングが重要な部分なので、とにかくやってみるしかないってことでやってみました。 開発はCakePHPで行いました。どうしてCakePHPなのかは、今まで開発は基本的にCakePHPを使ってやってきたので一番使い慣れてるっていう、まあそれだけの、水たまりより浅い理由ですよ、ええ。海より深い理由などありゃしません。 データベースはMySQLを使用、ストレージエンジンは全テーブルがMyISAMです。基本的に検索なので、MyISAMが一番早いしトランザクションの必要な処理もないってことなで、MyISAMにしてます。それから後でもまた述べますが、表示側の動的な部分については基本的にjQueryを用いています。 必要なデータのスクレイピング 何をおいてもまずはデータを取得することが必要。データがないと何も始まらない。ナニも始められない。まあ、俺ならデータがなくても妄想だけで始められｍ 最初はfile_get_contents関数を使ってページの情報を持って来て、必要な部分を正規表現で抜き取っていましたが、これが案外しんどい。意外と思い通りの形にデータを取れなかったりする。 そんなときに出会ったのがhtmlSQLというライブラリ。SQLを発行する感覚でスクレイピングが行える。しかもデータがHTMLタグ単位で返って来るので、欲しいところだけを抜き取りやすい。こいつは素晴らしい。中にはどうしてもhtmlSQLでは取れないデータがあったりもしたのですが、たいていのデータはhtmlSQLでいける。使い勝手が良いことは間違いないでしょう。 この解釈が正しいかはちょっと分かりませんが、タグの階層が深くなると、htmlSQLでは取れない場合があるような気がする。divタグの中にdivタグがあって、さらにその中にdivが…って感じでどんどん深くなると、divタグの中身を取って来ようと思った時に、一番深いところのdivの情報が上手く取れないようなケースがあるっぽい。でも取れてる場合もあるから、確かなことは言えないです。 女優データの取得 デザイナー編でもあったように、女優のデータは全てDMMから取得しています。DMMには女優の一覧ページと各女優の詳細情報のページがあるので、まず女優一覧のページから各女優の詳細情報ページへのURLを取得し、それぞれのURLにリクエストを投げて、情報を取得する。名前を複数持っている女優に関しては、名前を分割してDBにデータを入力しています。 一旦全女優のデータを取得した後は、新人の女優さんだけを定期的に見に行くようにして、その女優のデータがDBに入っていなければINSERT処理を行うようにしました。 それにしても、最近の女優さんはみんな可愛いですね。 DMMの商品データの取得 サーバー代、生ビール代をゲットするために必要なデータだから、ここはしっかり取得しなければならない。いえ、しっかり取得しなければならないのは他のデータも同じですけどね。 DMMは、各女優さんとその女優さんが出演しているDVDや動画がすでに関連づいているので、女優のデータを取得するときに一緒に商品データも取得するようにしています。女優ごとの商品の一覧ページへリクエストを投げて、その一覧ページから必要な情報を取得する。商品情報が複数ページにわたっている場合は、もちろんその全てのページのデータを取得する。 DMMではこの商品情報が人気順や価格順で見られるようになっているので、shecoolでもそこは連動させるようにしました。DMMを見ていると人気順はちょくちょく変化しているようなので、定期的に商品情報のページを見に行って、DBの方も更新するようにしています。人気順用のカラムを作っておいて、その中身を更新する感じ。毎回全商品の順位が変動するわけではないので、順位の変動があった商品だけを更新するようにしています。でないと、サーバーに無駄な負荷をかけてしまうからね。 DMMの方に追加された新しい商品情報の取得も、この人気順を入れ替える処理と同時に行っています。商品の一覧ページに載っている商品の情報がすでにDBに入っているかどうかを検証し、DBにデータがあった場合は、順位が変動していたら順位を入れ替えるUPDATE処理を行い、変動していなかったら何もしない、そしてDBにデータがなかった場合はINSERT処理を行うような分岐処理をしています。 動画データの取得 考え方としては、取得先のサイトの検索ボックスにキーワードを入力して、見つかった動画の詳細な情報を取得する感じ。スクレイピングの場合は直接検索ボックスにキーワードを入力するわけではないけど、フォームから送信されるデータは、特にそれが検索フォームのような場合は、多くがGETメソッドによってURLの後ろにパラメータを追加するように作られているので、最初からパラメータのついたURLに接続すれば情報を取得できる。 今回は、DBに入っている女優の名前とタグをそれぞれパラメータとしてURLに追加し、見つかった動画を取得しました。例えば『麻美ゆま』というパラメータをURLに追加して見つかった動画を取得するということは、検索ボックスに『麻美ゆま』というワードを入力して見つかった動画の情報を取得することに等しいことになります。 当然ながらサイトによってデータの取得の仕方が多少変わってきますが、htmlSQLが存分に活躍してくれたおかげで、何とかなりました。 データを取得させていただいた諸サイトさんには、この場を借りてお礼を申し上げます。 スクレイピングにおける注意点 スクレイピングは相手のサイトからデータを取ってくる作業なわけで、だからリクエストをたくさん投げ続ければそれだけサーバーにかかる負荷が大きくなります。自分側のサーバーはともかく、相手側のサーバーには極力迷惑をかけないようにしたいですね。連続してリクエストを投げすぎないようにする、取得する情報は必要最小限に抑える、余計なリクエストを投げない、その辺を意識しながらコーディングする必要があるかと思います。 DBへのデータ入力 データを取得したら当然次は入力です……が、しかし今回は入力する情報量が非常に多く、スクレイピングで必要な情報を取得する度にINSERT処理を行うと、1000件ほどのデータを入力するのに丸一日掛かってしまうようなこともありました。最初は数万件の動画情報を取得してDBに入れなければならなかったため、1000件の動画情報を入力するのに一日掛かっていたら時間がいくらあっても足りません。一日が40時間になってもきっと足りない。 更には、情報の取得だけならともかく、shecoolはタグや女優が動画やDMMの商品とHABTMで関連付けているため（関連付けに関しては後述）、アソシエーションを設定してCakePHPのsaveallメソッドでまとめて入力処理を行うと、一件辺りの動画情報の入力時間がもっと長くなる可能性も出てきます。というか、実際に長くなってました。 少しでも処理時間を短縮するためには、余計なインスタンスを生成しないようにするなど、考えられる点は多々あると思うのですが、今回は大幅な時間の短縮が求められたため、そんな焼け石にぬるま湯をかける程度の改善ではどうにもなりませんでした。かけるなら最低でも液体窒素くらいでないといけない。 そこで今回は、バッチ処理時にsaveメソッドやsaveallメソッドでのINSERT処理は行わずに、スクレイピングで必要な情報を持ってきたら、その情報を入力するINSERT文をテキストファイルに書き出す処理を行い、シェルから直接SQLを実行するようにしました。関連付けも、動画とタグ、動画同士、それぞれにINSERT用のテキストファイルを作成し、同様の処理を行いました。 shell_exec("mysql --user=ユーザー名 --password=パスワード DB名 &#60; ".ファイルパス); コードはこんな感じ。 CakePHP 1.2にはシェル機能があり、それを利用すればバッチ処理をサーバー側で実行できるみたいです。サーバーでcronの設定を行い、毎日決まった時間に自動で処理を実行するようにしておけば、DBへのアクセスが比較的少ないと思われる時間帯（早朝とか）などに処理を走らせることができる。この辺りの設定とかに関しては、先輩プログラマのお力を借りました。というかほとんどやってもらいました。 おかげで、入力処理の時間は大幅に短縮されました。液体ヘリウムをかけたかのようでした（？） 動画との関連付け shecoolでは、動画、タグ、女優がそれぞれ動画とHABTMで関連付いています（動画と動画は正確にはHABTMではないですが、多対多で結び付いているという意味で）。また、DMMの商品データとタグもHABTMで関連付いています。DMMの商品と女優は一対多。 動画と動画 動画同士は、タイトルがどれだけ類似しているかで関連付けを行っています。similar_text関数を使い、一定以上のパーセンテージ（今回は70%に設定している）が返って来たら関連していると判断し、バッチで処理を行うINSERT文をテキストファイルに書き出す。 処理自体はループ処理なので、ソースとしてはそんなに複雑にはなりません。ただ、10000件の動画があったら10000件の動画それぞれが他の9999件の動画と関連するのかどうかをチェックしなければならないため、処理自体はかなり重いです。動画の件数が増えれば増えるほど、処理はどんどん重くなる。なので、うっかりループの中で全動画データをSELECTするような処理を書くとメモリが大変なことになってしまうので、こういう時は、処理を行う前に一度全動画のデータを適当なメンバ変数などに持たせておくようにした方が良いと思います。処理時間を短くするには、とにかく必要以上にクエリは投げない。これが大事のようです。 本来なら、新規に動画データをDBに入力した時に同時に関連付けも行えば良いのですが、前述のデータ入力の項目にもあるように、それらを同時に行うのは負荷が大きいので、関連付けは関連付けで別個にINSERT処理を行っています。一度関連付けを行った動画データにもう一度同じ処理を行わせるのは無意味なので、今回は動画のテーブルに関連付けを行っていないかどうかの判定フラグ用のカラムを用意し、関連付けの処理を行うのはそのフラグが立っているデータのみ、つまりまだ関連付けが行われていない動画だけを抽出して処理を行うようにしました。 ただ、similar_text関数の検証という記事でも書いたように、マルチバイト文字の比較に関しては、そこまでの正確性はないように思えます。一つの動画を複数に分割してあるような動画（サンプル動画１、サンプル動画２、みたいな）を関連付けるのが一番の目的であり、それについては問題なく関連付いているので目的は達成できていますが、それ以外にも、およそ関連しているとは思えないような動画同士が関連している可能性は多分にあります。youtubeとかに比べると、関連付けのレベルは全然劣りますね。 [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://astrodeo.com/blog/archives/259">真面目にエロサイトを作ってみた【デザイナー編】</a>はコチラから。</p>
<h3>エロサイトを作るにあたって</h3>
<p><strong>「スタイリッシュなアダルトサイト思いついたから作るぞ」</strong></p>
<p>（たぶん）そんな感じの一言で始まった<a href="http://shecool.jp/">SheCool</a>のサイト制作。見た目がどれくらいスタイリッシュかってのはデザイナーの裁量次第なので、自分の担当はあくまでもシステム部分。発案者の意にできるだけ添えられるように、未熟ながらも自分の技術力を駆使してサイト制作に乗り出しました。</p>
<p>デザインの前に、とりあえず大まかな仕様を決めた時点で、今までやったことのない技術や、そこまで強く意識していなかったことがいくつも出てきました。主なところでは</p>
<ul>
<li>データのスクレイピング</li>
<li>cronを利用したバッチ処理(DBの自動更新)</li>
<li>SQLのチューニング</li>
</ul>
<p>辺りが自分にとっての未開領域でした。特にスクレイピングなんて、正直なところそういう単語すら初めて聞くくらいのレベル。所詮は素人プログラマです。ついでに、性欲は人よりあるわりに普段はほとんどアダルト動画を見ないもんですから、どんなアダルト動画の配信サイトがあるのかというのもよく知りませんでした。所詮は素人アダルト動画ウォッチャーです。</p>
<p>しかし今回の制作は何よりもスクレイピングが重要な部分なので、とにかくやってみるしかないってことでやってみました。</p>
<p>開発はCakePHPで行いました。どうしてCakePHPなのかは、今まで開発は基本的にCakePHPを使ってやってきたので一番使い慣れてるっていう、まあそれだけの、水たまりより浅い理由ですよ、ええ。海より深い理由などありゃしません。</p>
<p>データベースはMySQLを使用、ストレージエンジンは全テーブルがMyISAMです。基本的に検索なので、MyISAMが一番早いしトランザクションの必要な処理もないってことなで、MyISAMにしてます。それから後でもまた述べますが、表示側の動的な部分については基本的にjQueryを用いています。</p>
<h3>必要なデータのスクレイピング</h3>
<p>何をおいてもまずはデータを取得することが必要。データがないと何も始まらない。ナニも始められない。まあ、俺ならデータがなくても妄想だけで始められｍ</p>
<p>最初は<a href="http://php.net/manual/ja/function.file-get-contents.php" target="_blank">file_get_contents関数</a>を使ってページの情報を持って来て、必要な部分を正規表現で抜き取っていましたが、これが案外しんどい。意外と思い通りの形にデータを取れなかったりする。</p>
<p>そんなときに出会ったのが<a href="http://www.jonasjohn.de/lab/htmlsql.htm" target="_blank">htmlSQL</a>というライブラリ。SQLを発行する感覚でスクレイピングが行える。しかもデータがHTMLタグ単位で返って来るので、欲しいところだけを抜き取りやすい。こいつは素晴らしい。中にはどうしてもhtmlSQLでは取れないデータがあったりもしたのですが、たいていのデータはhtmlSQLでいける。使い勝手が良いことは間違いないでしょう。</p>
<p>この解釈が正しいかはちょっと分かりませんが、タグの階層が深くなると、htmlSQLでは取れない場合があるような気がする。divタグの中にdivタグがあって、さらにその中にdivが…って感じでどんどん深くなると、divタグの中身を取って来ようと思った時に、一番深いところのdivの情報が上手く取れないようなケースがあるっぽい。でも取れてる場合もあるから、確かなことは言えないです。</p>
<h4>女優データの取得</h4>
<p><a href="http://astrodeo.com/blog/archives/259">デザイナー編</a>でもあったように、女優のデータは全てDMMから取得しています。DMMには女優の一覧ページと各女優の詳細情報のページがあるので、まず女優一覧のページから各女優の詳細情報ページへのURLを取得し、それぞれのURLにリクエストを投げて、情報を取得する。名前を複数持っている女優に関しては、名前を分割してDBにデータを入力しています。</p>
<p>一旦全女優のデータを取得した後は、新人の女優さんだけを定期的に見に行くようにして、その女優のデータがDBに入っていなければINSERT処理を行うようにしました。</p>
<p>それにしても、最近の女優さんはみんな可愛いですね。</p>
<h4>DMMの商品データの取得</h4>
<p>サーバー代、生ビール代をゲットするために必要なデータだから、ここはしっかり取得しなければならない。いえ、しっかり取得しなければならないのは他のデータも同じですけどね。</p>
<p>DMMは、各女優さんとその女優さんが出演しているDVDや動画がすでに関連づいているので、女優のデータを取得するときに一緒に商品データも取得するようにしています。女優ごとの商品の一覧ページへリクエストを投げて、その一覧ページから必要な情報を取得する。商品情報が複数ページにわたっている場合は、もちろんその全てのページのデータを取得する。</p>
<p>DMMではこの商品情報が人気順や価格順で見られるようになっているので、shecoolでもそこは連動させるようにしました。DMMを見ていると人気順はちょくちょく変化しているようなので、定期的に商品情報のページを見に行って、DBの方も更新するようにしています。人気順用のカラムを作っておいて、その中身を更新する感じ。毎回全商品の順位が変動するわけではないので、順位の変動があった商品だけを更新するようにしています。でないと、サーバーに無駄な負荷をかけてしまうからね。</p>
<p>DMMの方に追加された新しい商品情報の取得も、この人気順を入れ替える処理と同時に行っています。商品の一覧ページに載っている商品の情報がすでにDBに入っているかどうかを検証し、DBにデータがあった場合は、順位が変動していたら順位を入れ替えるUPDATE処理を行い、変動していなかったら何もしない、そしてDBにデータがなかった場合はINSERT処理を行うような分岐処理をしています。</p>
<h4>動画データの取得</h4>
<p>考え方としては、取得先のサイトの検索ボックスにキーワードを入力して、見つかった動画の詳細な情報を取得する感じ。スクレイピングの場合は直接検索ボックスにキーワードを入力するわけではないけど、フォームから送信されるデータは、特にそれが検索フォームのような場合は、多くがGETメソッドによってURLの後ろにパラメータを追加するように作られているので、最初からパラメータのついたURLに接続すれば情報を取得できる。</p>
<p>今回は、DBに入っている女優の名前とタグをそれぞれパラメータとしてURLに追加し、見つかった動画を取得しました。例えば『麻美ゆま』というパラメータをURLに追加して見つかった動画を取得するということは、検索ボックスに『麻美ゆま』というワードを入力して見つかった動画の情報を取得することに等しいことになります。</p>
<p>当然ながらサイトによってデータの取得の仕方が多少変わってきますが、htmlSQLが存分に活躍してくれたおかげで、何とかなりました。</p>
<p>データを取得させていただいた諸サイトさんには、この場を借りてお礼を申し上げます。</p>
<h4>スクレイピングにおける注意点</h4>
<p>スクレイピングは相手のサイトからデータを取ってくる作業なわけで、だからリクエストをたくさん投げ続ければそれだけサーバーにかかる負荷が大きくなります。自分側のサーバーはともかく、相手側のサーバーには極力迷惑をかけないようにしたいですね。連続してリクエストを投げすぎないようにする、取得する情報は必要最小限に抑える、余計なリクエストを投げない、その辺を意識しながらコーディングする必要があるかと思います。</p>
<h3>DBへのデータ入力</h3>
<p>データを取得したら当然次は入力です……が、しかし今回は入力する情報量が非常に多く、スクレイピングで必要な情報を取得する度にINSERT処理を行うと、1000件ほどのデータを入力するのに丸一日掛かってしまうようなこともありました。最初は数万件の動画情報を取得してDBに入れなければならなかったため、1000件の動画情報を入力するのに一日掛かっていたら時間がいくらあっても足りません。一日が40時間になってもきっと足りない。</p>
<p>更には、情報の取得だけならともかく、shecoolはタグや女優が動画やDMMの商品とHABTMで関連付けているため（関連付けに関しては後述）、アソシエーションを設定してCakePHPのsaveallメソッドでまとめて入力処理を行うと、一件辺りの動画情報の入力時間がもっと長くなる可能性も出てきます。というか、実際に長くなってました。</p>
<p>少しでも処理時間を短縮するためには、余計なインスタンスを生成しないようにするなど、考えられる点は多々あると思うのですが、今回は大幅な時間の短縮が求められたため、そんな焼け石にぬるま湯をかける程度の改善ではどうにもなりませんでした。かけるなら最低でも液体窒素くらいでないといけない。</p>
<p>そこで今回は、バッチ処理時にsaveメソッドやsaveallメソッドでのINSERT処理は行わずに、スクレイピングで必要な情報を持ってきたら、その情報を入力するINSERT文をテキストファイルに書き出す処理を行い、シェルから直接SQLを実行するようにしました。関連付けも、動画とタグ、動画同士、それぞれにINSERT用のテキストファイルを作成し、同様の処理を行いました。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

shell_exec("mysql --user=ユーザー名 --password=パスワード DB名 &lt; ".ファイルパス);

</textarea>
<p>コードはこんな感じ。</p>
<p>CakePHP 1.2にはシェル機能があり、それを利用すればバッチ処理をサーバー側で実行できるみたいです。サーバーでcronの設定を行い、毎日決まった時間に自動で処理を実行するようにしておけば、DBへのアクセスが比較的少ないと思われる時間帯（早朝とか）などに処理を走らせることができる。この辺りの設定とかに関しては、先輩プログラマのお力を借りました。というかほとんどやってもらいました。</p>
<p>おかげで、入力処理の時間は大幅に短縮されました。液体ヘリウムをかけたかのようでした（？）</p>
<h3>動画との関連付け</h3>
<p>shecoolでは、動画、タグ、女優がそれぞれ動画とHABTMで関連付いています（動画と動画は正確にはHABTMではないですが、多対多で結び付いているという意味で）。また、DMMの商品データとタグもHABTMで関連付いています。DMMの商品と女優は一対多。</p>
<h4>動画と動画</h4>
<p>動画同士は、タイトルがどれだけ類似しているかで関連付けを行っています。<a href="http://jp.php.net/manual/ja/function.similar-text.php" target="_blank">similar_text関数</a>を使い、一定以上のパーセンテージ（今回は70%に設定している）が返って来たら関連していると判断し、バッチで処理を行うINSERT文をテキストファイルに書き出す。</p>
<p>処理自体はループ処理なので、ソースとしてはそんなに複雑にはなりません。ただ、10000件の動画があったら10000件の動画それぞれが他の9999件の動画と関連するのかどうかをチェックしなければならないため、処理自体はかなり重いです。動画の件数が増えれば増えるほど、処理はどんどん重くなる。なので、うっかりループの中で全動画データをSELECTするような処理を書くとメモリが大変なことになってしまうので、こういう時は、処理を行う前に一度全動画のデータを適当なメンバ変数などに持たせておくようにした方が良いと思います。処理時間を短くするには、とにかく必要以上にクエリは投げない。これが大事のようです。</p>
<p>本来なら、新規に動画データをDBに入力した時に同時に関連付けも行えば良いのですが、前述のデータ入力の項目にもあるように、それらを同時に行うのは負荷が大きいので、関連付けは関連付けで別個にINSERT処理を行っています。一度関連付けを行った動画データにもう一度同じ処理を行わせるのは無意味なので、今回は動画のテーブルに関連付けを行っていないかどうかの判定フラグ用のカラムを用意し、関連付けの処理を行うのはそのフラグが立っているデータのみ、つまりまだ関連付けが行われていない動画だけを抽出して処理を行うようにしました。</p>
<p>ただ、<a href="http://astrodeo.com/blog/archives/258">similar_text関数の検証</a>という記事でも書いたように、マルチバイト文字の比較に関しては、そこまでの正確性はないように思えます。一つの動画を複数に分割してあるような動画（サンプル動画１、サンプル動画２、みたいな）を関連付けるのが一番の目的であり、それについては問題なく関連付いているので目的は達成できていますが、それ以外にも、およそ関連しているとは思えないような動画同士が関連している可能性は多分にあります。youtubeとかに比べると、関連付けのレベルは全然劣りますね。</p>
<h4>動画とタグ</h4>
<p>動画とタグの関連付けは、DBに入っているタグ名をループで回し、動画のタイトルとディスクリプションの中にそのタグ名が入っているかどうかを判定し、入っていればその動画とタグを関連付けています。</p>
<p>それ以外に、スクレイピングのところで述べたように、動画は、タグ名をURLのパラメータに追加して、該当する動画が見つかったらそれらの情報を取得しています。だから例えば『女教師』というタグ名で動画を検索して引っかかった動画に関しては、タイトルやディスクリプションに『女教師』という単語がなくても、『女教師』のタグとは関連付けるようにしてあります。</p>
<h4>動画と女優</h4>
<p>動画と女優は、処理自体は動画とタグの関連付けと同様です。動画のタイトルやディスクリプションに女優名があるかどうかを判定し、あればその動画と女優を結び付ける。さらには女優名をURLのパラメータに追加して、例えば『麻美ゆま』という女優名で引っかかった動画に関しては、タイトルやディスクリプションの内容に関係なく、『麻美ゆま』とは関連付けています。</p>
<p>最初は、動画に女優のIDを持たせて、一対多で結び付けていました。つまり『麻美ゆま』で検索して見つかった動画に関しては、その動画の中に他の女優が出ていても、関連する女優は麻美ゆま一人だけでした。ですが話し合いの結果、複数の女優が出演している動画にはそれら全ての女優を関連付けた方が良いだろうという結論に達し、急遽HABTMに切り替えました。</p>
<p>ただし関連付けの判定方法がタイトルやディスクリプションの中に女優名が入っているかどうかだけなので、タイトルにもディスクリプションにも名前のない女優は、動画の中に出てきたとしても関連付いてはいないです。</p>
<h4>DMM商品とタグ</h4>
<p>DMMの商品の詳細情報が載っているページには、その商品に関連するジャンルの情報が載っています。そのジャンルとこちらのDBに入っているタグ名を比較し、一致するものがあった場合にはそのタグを関連付けるようにしています。</p>
<p>一つの商品に対し関連付いているジャンルは複数ある場合がほとんどですが、今回はそれら一つ一つをループさせてタグ名と一致するかどうか比較するような処理は行わずに、複数あるジャンルを一つの文字列として連結させ、正規表現を使ってタグ名がその文字列の中に存在するかどうかを判定して関連付けを行うようにしました。クエリが減るからこっちの方が処理時間は速いです。</p>
<h4>DMM商品と女優</h4>
<p>特別な処理は何もしてないです。商品データの取得でも述べたように、商品は基本的に誰かしらの女優と結び付いているので、その女優さんのIDを商品テーブルに持たせているだけです。</p>
<h3>サーバーの選定・設定</h3>
<p>日本のサーバーは、アダルトコンテンツを許可していないところが多いみたいですね。なので今回は<a href="http://www.megafactory.com/" target="_blank">MEGAFACTORY</a>という海外のサーバーを利用しました。</p>
<h4>サーバーの複数台の利用</h4>
<p>当初は一台のサーバーで運用を考えていました。しかしクライアント（弊社社長）の度重なる要求に応えているうちにどんどんと仕様が膨らみ、それに伴って裏側の処理（動画の取得など）の負荷がかなり大きくなってしまったため、一台では運用が厳しくなってしまいました。</p>
<p>そこで、サーバーを二台に分割することにしました。複数台の運用、そして二つのDBを連動させるという試みは自分にとっては初めてだったため、何をどう設定すれば良いのかさっぱりでしたが、づや先輩やとりよし先輩のお力を最大限に借りることで、無事に乗り切りました。借りたっていうか、任せたの方が正しい表現ですけど。</p>
<p>要は、スレーブとマスターの二つにサーバーを分け、スレーブの方はSELECT処理のみ接続し、INSERTやUPDATE、DELETE処理を行う時には、マスターの方に接続するように設定している……らしいです。裏側の処理はずっとマスターの方に接続していれば問題ないですが、表示側はその時々に応じて接続先を切り替えなければなりません。shecoolでは動画や女優にランキングをつけているため、表示側でもINSERTやUPDATEの処理が行われることはあります。そういう時だけ、接続先をマスターの方に切り替えるわけです。</p>
<p>これはsaveメソッドやupdateメソッドをオーバーライドすることで切り替えを実現しています。一時的にマスターに接続して処理を行い、終わったら接続先をスレーブに戻す、そんな処理を書いています。</p>
<h4>Sennaの導入</h4>
<p>SQLにおいて、LIKE検索は速度が遅い。だから特定の文字列を検索したい時にはLIKE検索よりも全文検索を利用するのが常套だと思うのですが、しかしMySQLの全文検索はマルチバイトに対応していない。FULLTEXTのインデックスを張るだけでは残念ながら日本語の全文検索は上手くできないみたいです。</p>
<p>対応策はいくつかあるようですが、今回は<a href="http://qwik.jp/senna/FrontPageJ.html" target="_blank">Senna</a>という組み込み型の全文検索エンジンを利用して、日本語での全文検索を可能にしました。</p>
<p>SennaをMySQLに組み込むにはMySQLをコンパイルし直す必要があるらしく、そこがちょっと大変で、結構苦戦するみたいです。しかしここでも当然のように先輩方のお力を借りた私は、特に苦労はしていないです。使えない後輩を持つと先輩は大変なんだってのがこれでよく分かりますね。気をつけましょう。</p>
<h3>表示側の動き</h3>
<p>サイトの性質上、shecoolは画像や動画のデータが多いです。デザイナー編にある、どういうサイトにするかのところで、画像をあまりごちゃごちゃ使わないようにしたいとか何とか言っちゃおりますが、できるだけ使わないようにしたって、一度に結構な数の画像が表示されます。まあ、そういうサイトなんだから仕方ないわけなんですが、加えて、関連動画の表示や検索ワードに該当する動画データと関連するDMM商品の表示など、複雑なSQLクエリを発行する箇所が多々あり、ページ全体はかなり重いです。全体の読み込みが終わるまでにかなりの時間を要してしまう。</p>
<p>そういうわけで、少しでも速くするために、さまざまな試みが要求されました。Sennaの導入もその一つですね。</p>
<h4>jQueryの利用</h4>
<p>shecoolでは、表示の動的な変化にjQueryを多用しています。細かい動きがあるたびにページ全体を読み込んでいたら目的の動画にたどりつくまでに日がくれてしまいますからね。気持ちが萎えることこの上ないですね。萎えるのは気持ちだけではないと思います。</p>
<p>jQueryでなければならない理由は特にありませんが、Ajaxでスライドなどのアニメーション効果をつけたいと思った時に、個人的にはjQueryを利用するのが手軽に実現できて良いと思っています。HTTP通信でのページ読み込みも簡単に行えるし、自分はprototypeよりはjQueryを使うことが多いです。</p>
<h4>クエリの最適化</h4>
<p>本気で最適化をするとなったら、これから書くこと以外にもかなりいろいろとやらないといけませんが、とりあえずは明日のためのその１。技術レベルが素人以上素人未満の僕みたいな奴でもすぐに手を出せるような項目を、いくつか挙げてみます。</p>
<ul>
<li>SELECT文を発行する際、取得するカラムは常に必要なものだけにする。ワイルドカード（*）は使わない方が良い。</li>
<li>COUNTの場合も、ワイルドカードは使わずにカラムに主キーなどを指定することで、実行速度は上がる。</li>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

SELECT COUNT(*) FROM `movies`
⇒SELECT COUNT(`id`) FROM `movies`

</textarea>
<li>WHERE句にORを使うようなときはINに置き換えた方が良い。</li>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

SELECT `title` FROM `movies` WHERE `id` = 1 OR `id` = 2
⇒SELECT `title` FROM `movies` WHERE `id` IN(1,2)

</textarea>
<li>インデックスをちゃんと張る。</li>
<p>DBのテーブルを見直して、ちゃんとインデックスが張られているかチェックしましょう。主キーだけでなく、特にWHERE句に関わってくるようなカラムは、インデックスを張っておいた方がSELECT文の場合には実行速度が速くなります。</ul>
<p>あとはCake側でアソシエーションの設定をしている場合は、必要なところ以外はunbindModelをしておくことも時間短縮の一つですね。joinしていると遅くなったりするから。</p>
<p>何にせよ、まずはどのクエリがボトルネックになっているかを洗い出すことが肝心です。基本的にはEXPLAINで地味にこつこつと見るのが良い。クエリを解析すればどのクエリが遅いのかは分かるので、そこに改善を施すわけです。今回は全文検索のインデックスがjoinの時にうまく効かなくて、構成から変えたところもあったりしました。</p>
<p>先輩プログラマ曰く、だいぶ頑張って実行時間を短縮したけどまだまだ遅い感じがする、だそうです。</p>
<h4>キャッシュを使う</h4>
<p>ページにキャッシュを利かせれば、表示は当然速くなります。でも当サイトではユーザーの動きがダイレクトに出るところがあるから（最近見た動画とか）、ページキャッシュを作ってもすぐに消される羽目になり、それではキャッシュを利かせるメリットがない。</p>
<p>ということで、今回はクエリの方にキャッシュを利かせました。検索のように、毎回同じクエリが投げられるとは限らないようなところはともかく、人気の動画や人気の女優を取得してくるようなところは常に同じクエリが投げられるので、クエリ自体をキャッシュ化しても特に問題はないわけです。ページ読み込みの際に、すでにクエリのキャッシュが存在している場合にはそちらを見に行くようにすれば、これまた時間短縮につながります。</p>
<h4>Ajaxで画像を読み込むようにする</h4>
<p>画像が多いと、どうしても全体が読み込み終わるまでにある程度の時間を要してしまいますね。そこで、画像の読み込みは後回しにしてしまっても良いような部分は、ページの読み込みが完了してからAjaxで読み込ませるようにする方法もあります。前述の通り、shecoolはjQueryを多用しているので、例えばフッター部など、スクロールしないと見えないような部分はonLoadでAjax通信を行って画像を読み込ませるというのも、有効な手段だと思います。読み込みが終わるまではloading用のgif画像でも出しておけば良いんじゃないでしょうか。</p>
<h3>そして公開へ</h3>
<p>こんな感じで、ことあるごとに追加される機能や、その都度発生するバグ、そしてデザインの修正、<strong>果てはリリースしてないのに大幅にリニューアルする</strong>という、これはもはやパワハラなんじゃねーのってくらいの仕様変更に耐えながら地道なチューニングを行い、ようやくリリースまでこぎつけたわけです。</p>
<p>分からないことだらけで大変ではありましたが、とても有意義な制作でした。特に、今までそんなに重いページを作ったことがなかったせいか、クエリの最適化とかは全然意識したことがなかったんですけど、これは今後も必ず役に立ってくるところだと思うので、とても勉強になりました。</p>
<p>それに何といっても、動作テストと称して<strong>毎日仕事中に堂々とアダルト動画をひたすら見続けること</strong>ができたのでとても有意義な制作でした。お気に入りの動画を探してマイリストに登録するところなんて、テストに見せかけてかなり真剣に選抜してました。とても有意義な制作でした。</p>
<p>ただ、大真面目にアダルト動画をチェックしてマイリストを作っている自分とか、取得するサイトの中でもどのサイトが優良な動画をたくさん持っているかを入念に吟味している自分とかを客観的に見たら、「<strong>ああ、こいつは絶対にモテないな。右手しか恋人候補がいねえ</strong>」って思いました。</p>
]]></content:encoded>
			<wfw:commentRss>http://astrodeo.com/blog/archives/257/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

