<?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>Wed, 28 Jul 2010 01:18:46 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.1</generator>
	<language>ja</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<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とかに比べると、関連付けのレベルは全然劣りますね。
動画とタグ
動画とタグの関連付けは、DBに入っているタグ名をループで回し、動画のタイトルとディスクリプションの中にそのタグ名が入っているかどうかを判定し、入っていればその動画とタグを関連付けています。
それ以外に、スクレイピングのところで述べたように、動画は、タグ名をURLのパラメータに追加して、該当する動画が見つかったらそれらの情報を取得しています。だから例えば『女教師』というタグ名で動画を検索して引っかかった動画に関しては、タイトルやディスクリプションに『女教師』という単語がなくても、『女教師』のタグとは関連付けるようにしてあります。
動画と女優
動画と女優は、処理自体は動画とタグの関連付けと同様です。動画のタイトルやディスクリプションに女優名があるかどうかを判定し、あればその動画と女優を結び付ける。さらには女優名をURLのパラメータに追加して、例えば『麻美ゆま』という女優名で引っかかった動画に関しては、タイトルやディスクリプションの内容に関係なく、『麻美ゆま』とは関連付けています。
最初は、動画に女優のIDを持たせて、一対多で結び付けていました。つまり『麻美ゆま』で検索して見つかった動画に関しては、その動画の中に他の女優が出ていても、関連する女優は麻美ゆま一人だけでした。ですが話し合いの結果、複数の女優が出演している動画にはそれら全ての女優を関連付けた方が良いだろうという結論に達し、急遽HABTMに切り替えました。
ただし関連付けの判定方法がタイトルやディスクリプションの中に女優名が入っているかどうかだけなので、タイトルにもディスクリプションにも名前のない女優は、動画の中に出てきたとしても関連付いてはいないです。
DMM商品とタグ
DMMの商品の詳細情報が載っているページには、その商品に関連するジャンルの情報が載っています。そのジャンルとこちらのDBに入っているタグ名を比較し、一致するものがあった場合にはそのタグを関連付けるようにしています。
一つの商品に対し関連付いているジャンルは複数ある場合がほとんどですが、今回はそれら一つ一つをループさせてタグ名と一致するかどうか比較するような処理は行わずに、複数あるジャンルを一つの文字列として連結させ、正規表現を使ってタグ名がその文字列の中に存在するかどうかを判定して関連付けを行うようにしました。クエリが減るからこっちの方が処理時間は速いです。
DMM商品と女優
特別な処理は何もしてないです。商品データの取得でも述べたように、商品は基本的に誰かしらの女優と結び付いているので、その女優さんのIDを商品テーブルに持たせているだけです。
サーバーの選定・設定
日本のサーバーは、アダルトコンテンツを許可していないところが多いみたいですね。なので今回はMEGAFACTORYという海外のサーバーを利用しました。
サーバーの複数台の利用
当初は一台のサーバーで運用を考えていました。しかしクライアント（弊社社長）の度重なる要求に応えているうちにどんどんと仕様が膨らみ、それに伴って裏側の処理（動画の取得など）の負荷がかなり大きくなってしまったため、一台では運用が厳しくなってしまいました。
そこで、サーバーを二台に分割することにしました。複数台の運用、そして二つのDBを連動させるという試みは自分にとっては初めてだったため、何をどう設定すれば良いのかさっぱりでしたが、づや先輩やとりよし先輩のお力を最大限に借りることで、無事に乗り切りました。借りたっていうか、任せたの方が正しい表現ですけど。
要は、スレーブとマスターの二つにサーバーを分け、スレーブの方はSELECT処理のみ接続し、INSERTやUPDATE、DELETE処理を行う時には、マスターの方に接続するように設定している……らしいです。裏側の処理はずっとマスターの方に接続していれば問題ないですが、表示側はその時々に応じて接続先を切り替えなければなりません。shecoolでは動画や女優にランキングをつけているため、表示側でもINSERTやUPDATEの処理が行われることはあります。そういう時だけ、接続先をマスターの方に切り替えるわけです。
これはsaveメソッドやupdateメソッドをオーバーライドすることで切り替えを実現しています。一時的にマスターに接続して処理を行い、終わったら接続先をスレーブに戻す、そんな処理を書いています。
Sennaの導入
SQLにおいて、LIKE検索は速度が遅い。だから特定の文字列を検索したい時にはLIKE検索よりも全文検索を利用するのが常套だと思うのですが、しかしMySQLの全文検索はマルチバイトに対応していない。FULLTEXTのインデックスを張るだけでは残念ながら日本語の全文検索は上手くできないみたいです。
対応策はいくつかあるようですが、今回はSennaという組み込み型の全文検索エンジンを利用して、日本語での全文検索を可能にしました。
SennaをMySQLに組み込むにはMySQLをコンパイルし直す必要があるらしく、そこがちょっと大変で、結構苦戦するみたいです。しかしここでも当然のように先輩方のお力を借りた私は、特に苦労はしていないです。使えない後輩を持つと先輩は大変なんだってのがこれでよく分かりますね。気をつけましょう。
表示側の動き
サイトの性質上、shecoolは画像や動画のデータが多いです。デザイナー編にある、どういうサイトにするかのところで、画像をあまりごちゃごちゃ使わないようにしたいとか何とか言っちゃおりますが、できるだけ使わないようにしたって、一度に結構な数の画像が表示されます。まあ、そういうサイトなんだから仕方ないわけなんですが、加えて、関連動画の表示や検索ワードに該当する動画データと関連するDMM商品の表示など、複雑なSQLクエリを発行する箇所が多々あり、ページ全体はかなり重いです。全体の読み込みが終わるまでにかなりの時間を要してしまう。
そういうわけで、少しでも速くするために、さまざまな試みが要求されました。Sennaの導入もその一つですね。
jQueryの利用
shecoolでは、表示の動的な変化にjQueryを多用しています。細かい動きがあるたびにページ全体を読み込んでいたら目的の動画にたどりつくまでに日がくれてしまいますからね。気持ちが萎えることこの上ないですね。萎えるのは気持ちだけではないと思います。
jQueryでなければならない理由は特にありませんが、Ajaxでスライドなどのアニメーション効果をつけたいと思った時に、個人的にはjQueryを利用するのが手軽に実現できて良いと思っています。HTTP通信でのページ読み込みも簡単に行えるし、自分はprototypeよりはjQueryを使うことが多いです。
クエリの最適化
本気で最適化をするとなったら、これから書くこと以外にもかなりいろいろとやらないといけませんが、とりあえずは明日のためのその１。技術レベルが素人以上素人未満の僕みたいな奴でもすぐに手を出せるような項目を、いくつか挙げてみます。

SELECT文を発行する際、取得するカラムは常に必要なものだけにする。ワイルドカード（*）は使わない方が良い。
COUNTの場合も、ワイルドカードは使わずにカラムに主キーなどを指定することで、実行速度は上がる。


SELECT COUNT(*) FROM `movies`
⇒SELECT COUNT(`id`) FROM `movies`


WHERE句にORを使うようなときはINに置き換えた方が良い。


SELECT `title` FROM `movies` WHERE `id` = 1 OR `id` = 2
⇒SELECT `title` FROM `movies` WHERE `id` IN(1,2)


インデックスをちゃんと張る。
DBのテーブルを見直して、ちゃんとインデックスが張られているかチェックしましょう。主キーだけでなく、特にWHERE句に関わってくるようなカラムは、インデックスを張っておいた方がSELECT文の場合には実行速度が速くなります。
あとはCake側でアソシエーションの設定をしている場合は、必要なところ以外はunbindModelをしておくことも時間短縮の一つですね。joinしていると遅くなったりするから。
何にせよ、まずはどのクエリがボトルネックになっているかを洗い出すことが肝心です。基本的にはEXPLAINで地味にこつこつと見るのが良い。クエリを解析すればどのクエリが遅いのかは分かるので、そこに改善を施すわけです。今回は全文検索のインデックスがjoinの時にうまく効かなくて、構成から変えたところもあったりしました。
先輩プログラマ曰く、だいぶ頑張って実行時間を短縮したけどまだまだ遅い感じがする、だそうです。
キャッシュを使う
ページにキャッシュを利かせれば、表示は当然速くなります。でも当サイトではユーザーの動きがダイレクトに出るところがあるから（最近見た動画とか）、ページキャッシュを作ってもすぐに消される羽目になり、それではキャッシュを利かせるメリットがない。
ということで、今回はクエリの方にキャッシュを利かせました。検索のように、毎回同じクエリが投げられるとは限らないようなところはともかく、人気の動画や人気の女優を取得してくるようなところは常に同じクエリが投げられるので、クエリ自体をキャッシュ化しても特に問題はないわけです。ページ読み込みの際に、すでにクエリのキャッシュが存在している場合にはそちらを見に行くようにすれば、これまた時間短縮につながります。
Ajaxで画像を読み込むようにする
画像が多いと、どうしても全体が読み込み終わるまでにある程度の時間を要してしまいますね。そこで、画像の読み込みは後回しにしてしまっても良いような部分は、ページの読み込みが完了してからAjaxで読み込ませるようにする方法もあります。前述の通り、shecoolはjQueryを多用しているので、例えばフッター部など、スクロールしないと見えないような部分はonLoadでAjax通信を行って画像を読み込ませるというのも、有効な手段だと思います。読み込みが終わるまではloading用のgif画像でも出しておけば良いんじゃないでしょうか。
そして公開へ
こんな感じで、ことあるごとに追加される機能や、その都度発生するバグ、そしてデザインの修正、果てはリリースしてないのに大幅にリニューアルするという、これはもはやパワハラなんじゃねーのってくらいの仕様変更に耐えながら地道なチューニングを行い、ようやくリリースまでこぎつけたわけです。
分からないことだらけで大変ではありましたが、とても有意義な制作でした。特に、今までそんなに重いページを作ったことがなかったせいか、クエリの最適化とかは全然意識したことがなかったんですけど、これは今後も必ず役に立ってくるところだと思うので、とても勉強になりました。
それに何といっても、動作テストと称して毎日仕事中に堂々とアダルト動画をひたすら見続けることができたのでとても有意義な制作でした。お気に入りの動画を探してマイリストに登録するところなんて、テストに見せかけてかなり真剣に選抜してました。とても有意義な制作でした。
ただ、大真面目にアダルト動画をチェックしてマイリストを作っている自分とか、取得するサイトの中でもどのサイトが優良な動画をたくさん持っているかを入念に吟味している自分とかを客観的に見たら、「ああ、こいつは絶対にモテないな。右手しか恋人候補がいねえ」って思いました。
]]></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>2</slash:comments>
		</item>
		<item>
		<title>similar_text関数の検証</title>
		<link>http://astrodeo.com/blog/archives/258</link>
		<comments>http://astrodeo.com/blog/archives/258#comments</comments>
		<pubDate>Tue, 05 Jan 2010 05:02:11 +0000</pubDate>
		<dc:creator>マッチー</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://astrodeo.com/blog/archives/258</guid>
		<description><![CDATA[以前（この辺の記事）、similar_text関数が便利なんじゃね？　みたいなことを書きました。
あれから何度か使ってはいるのですが、しかし仕方のないこととはいえ、本当にそんな高確率で類似してるのかよと言いたくなるような結果が返ってくることもあります。
例えば、以下のような比較をするとどうなるか。


$str1 = 'あああ';

$str2 = 'いいい';

$result = similar_test($str1, $str2, $percent);


$reusltには一致した文字数、$percentにはパーセンテージが入ります。
実際にやってみれば分かりますが、これを実行すると、一致する文字数は6、パーセンテージは66.666…%となります。正直、この比較で66%が返ってくるのは、あまりよろしくはない…ような気がする。
何でこんな結果が返ってくるのかなと思って、自分なりに検証してみたんですけど、やっぱりというべきか、日本語は基本的にマルチバイト文字なので、2バイトで1文字です。UTF-8の場合は3バイトになったりもする。
で、上の二つの文字列ですが、それぞれ文字コード（UTF-8）で見ると


あああ =&#62; E3 81 82 E3 81 82 E3 81 82

いいい =&#62; E3 81 83 E3 81 83 E3 81 83


16進表記でこのようになっています。見れば分かるように、非常に似ている。
今回の比較結果は、一致した文字数が6と出ましたから、これはたぶん、6バイト分の文字が一致しているという意味なのでしょう。E3と81がそれぞれ3回ずつかぶっているので、合計で6が出たと、そんな感じだと思います。
つまり、日本語で見れば違う文字であっても、文字コードが近ければ、それ相応の一致率が出てしまうってことですね。早い話、「あ」と「ぱ」を比較したって上記と同じ結果が出る。「ぱ」の文字コードは「E3 81 B1」なので、やはりE3と81が一致する。ぶっちゃけ、全角スペースだって文字コードでは「E3 80 80」なので、「あ」と比較すると33%くらいの結果が返ってくる。
このsimilar_text関数、ソース的にたったの1行で比較ができるという点では確かに便利なんですけど、しかしそれなりに場合を選んで使いこなさないと、プログラム的に期待した結果は出せないかもしれないですね。
]]></description>
			<content:encoded><![CDATA[<p>以前（<a href="http://astrodeo.com/blog/archives/236">この辺の記事</a>）、similar_text関数が便利なんじゃね？　みたいなことを書きました。</p>
<p>あれから何度か使ってはいるのですが、しかし仕方のないこととはいえ、本当にそんな高確率で類似してるのかよと言いたくなるような結果が返ってくることもあります。</p>
<p>例えば、以下のような比較をするとどうなるか。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

$str1 = 'あああ';

$str2 = 'いいい';

$result = similar_test($str1, $str2, $percent);

</textarea>
<p>$reusltには一致した文字数、$percentにはパーセンテージが入ります。</p>
<p>実際にやってみれば分かりますが、これを実行すると、一致する文字数は6、パーセンテージは66.666…%となります。正直、この比較で66%が返ってくるのは、あまりよろしくはない…ような気がする。</p>
<p>何でこんな結果が返ってくるのかなと思って、自分なりに検証してみたんですけど、やっぱりというべきか、日本語は基本的にマルチバイト文字なので、2バイトで1文字です。UTF-8の場合は3バイトになったりもする。</p>
<p>で、上の二つの文字列ですが、それぞれ文字コード（UTF-8）で見ると</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

あああ =&gt; E3 81 82 E3 81 82 E3 81 82

いいい =&gt; E3 81 83 E3 81 83 E3 81 83

</textarea>
<p>16進表記でこのようになっています。見れば分かるように、非常に似ている。</p>
<p>今回の比較結果は、一致した文字数が6と出ましたから、これはたぶん、6バイト分の文字が一致しているという意味なのでしょう。E3と81がそれぞれ3回ずつかぶっているので、合計で6が出たと、そんな感じだと思います。</p>
<p>つまり、日本語で見れば違う文字であっても、文字コードが近ければ、それ相応の一致率が出てしまうってことですね。早い話、「あ」と「ぱ」を比較したって上記と同じ結果が出る。「ぱ」の文字コードは「E3 81 B1」なので、やはりE3と81が一致する。ぶっちゃけ、全角スペースだって文字コードでは「E3 80 80」なので、「あ」と比較すると33%くらいの結果が返ってくる。</p>
<p>このsimilar_text関数、ソース的にたったの1行で比較ができるという点では確かに便利なんですけど、しかしそれなりに場合を選んで使いこなさないと、プログラム的に期待した結果は出せないかもしれないですね。</p>
]]></content:encoded>
			<wfw:commentRss>http://astrodeo.com/blog/archives/258/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>phpで画像をダウンロードする</title>
		<link>http://astrodeo.com/blog/archives/229</link>
		<comments>http://astrodeo.com/blog/archives/229#comments</comments>
		<pubDate>Thu, 26 Nov 2009 00:19:54 +0000</pubDate>
		<dc:creator>マッチー</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://astrodeo.com/blog/archives/229</guid>
		<description><![CDATA[ウェブサイトを閲覧していて画像が欲しくなったときは、右クリックで画像に名前をつけて保存すれば保存できますが、それをphpでやれないかな～と思っていたら、やっぱりというべきか、できるんですね。
たとえば、このブログのページの頭にある地球の画像を適当なフォルダにダウンロードしたいとしましょう。


//画像のパス
$image_path = "http://astrodeo.com/images/logo.jpg";

//保存するファイル名
$file_name = 'logo.jpg';

$image = file_get_contents($image_path);

$save_path = IMAGES_PATH.$file_name;

file_put_contents($save_path, $image);


IMAGES_PATHには何か適当なフォルダへのパスを入れればOKです。CakePHPだと、IMAGESという定数にimgフォルダまでのパスが入ってますね。
これで自分のパソコンに地球の画像がダウンロードできる。
参考サイトは　⇒　http://www.hideblog.net/nikkis/show/82
]]></description>
			<content:encoded><![CDATA[<p>ウェブサイトを閲覧していて画像が欲しくなったときは、右クリックで画像に名前をつけて保存すれば保存できますが、それをphpでやれないかな～と思っていたら、やっぱりというべきか、できるんですね。</p>
<p>たとえば、このブログのページの頭にある地球の画像を適当なフォルダにダウンロードしたいとしましょう。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

//画像のパス
$image_path = "http://astrodeo.com/images/logo.jpg";

//保存するファイル名
$file_name = 'logo.jpg';

$image = file_get_contents($image_path);

$save_path = IMAGES_PATH.$file_name;

file_put_contents($save_path, $image);

</textarea>
<p>IMAGES_PATHには何か適当なフォルダへのパスを入れればOKです。CakePHPだと、IMAGESという定数にimgフォルダまでのパスが入ってますね。</p>
<p>これで自分のパソコンに地球の画像がダウンロードできる。</p>
<p>参考サイトは　⇒　<a href="http://www.hideblog.net/nikkis/show/82">http://www.hideblog.net/nikkis/show/82</a></p>
]]></content:encoded>
			<wfw:commentRss>http://astrodeo.com/blog/archives/229/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>mb_convert_kanaを使って濁点や半濁点を除去してみる</title>
		<link>http://astrodeo.com/blog/archives/240</link>
		<comments>http://astrodeo.com/blog/archives/240#comments</comments>
		<pubDate>Thu, 19 Nov 2009 06:22:50 +0000</pubDate>
		<dc:creator>マッチー</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://astrodeo.com/blog/archives/240</guid>
		<description><![CDATA[どれくらい使う機会があるのかは分からないですけど、ここ最近の自分はそれなりにこのやり方を多用していたので、忘れないうちにメモ。
mb_convert_kanaを使うと全角文字を半角にできたり半角文字を全角にできたり急に異性にモテるようになったりします。嘘です。異性と接することもなく平日も土日もひたすらコーディングばっかりしてたってモテるはずがない。
mb_convert_kanaで全角ひらがなを半角にすると、濁点も1文字として変換される。だからそれをもう一回全角に変換してやれば濁点や半濁点のない全角文字が取得できる。ある文字列があ行なのかか行なのか、そういうのを判別するときに使えると思う。
では、文字列がか行かどうかを判定してみましょう。


//判定用の配列
$array = array('か', 'き', 'く', 'け', 'こ');

$str = 'ゴジラ';

//全角カタカナを半角カタカナに変換
$str = mb_convert_kana($str, 'k', 'UTF-8');

//半角カタカナを全角ひらがなに変換
$str = mb_convert_kana($str, 'H', 'UTF-8');

//先頭の１文字を取得
$str = mb_substr($str1, 0, 1);

//配列の中に該当する文字があるかどうかを判定
if(in_array($str, $array) == true) {

    echo $str.'は「か行」です';

}


8行目の時点で$strの中身は「ｺﾞｼﾞﾗ」になる。このとき、「ｺ」や「ｼ」と「゛」はそれぞれが1文字として分けられている。そして11行目でそれを全角ひらがなに変換すると、$strは「こ゛し゛ら」になるので、その文字列の先頭の1文字だけを取得すれば、$strの中身は「こ」になる。in_array関数は対象の配列の中に該当する文字（今回の場合は「こ」）があるかどうかを判定し、あったらtrueを返す。今回は配列の中に「こ」があるので、結果はtrueになる。
こんな感じで、各行なんかの判定をすれば良いのではないかな・・・と思う。いや、もちろん配列の中に濁点なんかも全部書いたって良いんだけどね。↓みたいな感じで。


$array1 = array('か', 'き', 'く', 'け', 'こ', 'が', 'ぎ', 'ぐ', 'げ', 'ご');


でも何かちょっとあれじゃん？
]]></description>
			<content:encoded><![CDATA[<p>どれくらい使う機会があるのかは分からないですけど、ここ最近の自分はそれなりにこのやり方を多用していたので、忘れないうちにメモ。</p>
<p>mb_convert_kanaを使うと全角文字を半角にできたり半角文字を全角にできたり急に異性にモテるようになったりします。嘘です。異性と接することもなく平日も土日もひたすらコーディングばっかりしてたってモテるはずがない。</p>
<p>mb_convert_kanaで全角ひらがなを半角にすると、濁点も1文字として変換される。だからそれをもう一回全角に変換してやれば濁点や半濁点のない全角文字が取得できる。ある文字列があ行なのかか行なのか、そういうのを判別するときに使えると思う。</p>
<p>では、文字列がか行かどうかを判定してみましょう。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

//判定用の配列
$array = array('か', 'き', 'く', 'け', 'こ');

$str = 'ゴジラ';

//全角カタカナを半角カタカナに変換
$str = mb_convert_kana($str, 'k', 'UTF-8');

//半角カタカナを全角ひらがなに変換
$str = mb_convert_kana($str, 'H', 'UTF-8');

//先頭の１文字を取得
$str = mb_substr($str1, 0, 1);

//配列の中に該当する文字があるかどうかを判定
if(in_array($str, $array) == true) {

    echo $str.'は「か行」です';

}

</textarea>
<p>8行目の時点で$strの中身は「ｺﾞｼﾞﾗ」になる。このとき、「ｺ」や「ｼ」と「゛」はそれぞれが1文字として分けられている。そして11行目でそれを全角ひらがなに変換すると、$strは「こ゛し゛ら」になるので、その文字列の先頭の1文字だけを取得すれば、$strの中身は「こ」になる。in_array関数は対象の配列の中に該当する文字（今回の場合は「こ」）があるかどうかを判定し、あったらtrueを返す。今回は配列の中に「こ」があるので、結果はtrueになる。</p>
<p>こんな感じで、各行なんかの判定をすれば良いのではないかな・・・と思う。いや、もちろん配列の中に濁点なんかも全部書いたって良いんだけどね。↓みたいな感じで。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

$array1 = array('か', 'き', 'く', 'け', 'こ', 'が', 'ぎ', 'ぐ', 'げ', 'ご');

</textarea>
<p>でも何かちょっとあれじゃん？</p>
]]></content:encoded>
			<wfw:commentRss>http://astrodeo.com/blog/archives/240/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>mb_convert_encodingで変換できない文字の対処</title>
		<link>http://astrodeo.com/blog/archives/233</link>
		<comments>http://astrodeo.com/blog/archives/233#comments</comments>
		<pubDate>Fri, 13 Nov 2009 00:24:44 +0000</pubDate>
		<dc:creator>マッチー</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://astrodeo.com/blog/archives/233</guid>
		<description><![CDATA[EUC-JPで書かれている文字列をUTF-8に直したいと思ったら、この記事のタイトルにも書いてあるmb_convert_encodingを使えばエンコードできる。
ただし、ちょっと気をつけておかなければならないのは、中には変換できない文字もあるということ。
たとえば、①とかがそう。これをEUC-JPからUTF-8に変換すると、?になってしまう。
この手の文字が文字列に入っている場合は、↓こんな感じにすれば変換は可能っぽい。


$str = 'ムダヅモなき改革①';

$str = mb_convert_encoding($str, 'EUCJP-win', 'EUC-JP');

$str = mb_convert_encoding($str, 'UTF-8', 'EUCJP-win');


いったんEUCJP-winに変換してからUTF-8に変換するわけですな。
とりあえず僕はこんな感じで対処してますけど、しかしソースとしてはちょっとかっこわるいような・・・？
何かもっとうまい（かっこいい）方法はないものでしょうか・・・何か1発でできるような。
あと、確認はしてないけど、この変換だとMacで見た場合はどうなるのかしら。
]]></description>
			<content:encoded><![CDATA[<p>EUC-JPで書かれている文字列をUTF-8に直したいと思ったら、この記事のタイトルにも書いてあるmb_convert_encodingを使えばエンコードできる。</p>
<p>ただし、ちょっと気をつけておかなければならないのは、中には変換できない文字もあるということ。</p>
<p>たとえば、①とかがそう。これをEUC-JPからUTF-8に変換すると、?になってしまう。</p>
<p>この手の文字が文字列に入っている場合は、↓こんな感じにすれば変換は可能っぽい。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

$str = 'ムダヅモなき改革①';

$str = mb_convert_encoding($str, 'EUCJP-win', 'EUC-JP');

$str = mb_convert_encoding($str, 'UTF-8', 'EUCJP-win');

</textarea>
<p>いったんEUCJP-winに変換してからUTF-8に変換するわけですな。</p>
<p>とりあえず僕はこんな感じで対処してますけど、しかしソースとしてはちょっとかっこわるいような・・・？</p>
<p>何かもっとうまい（かっこいい）方法はないものでしょうか・・・何か1発でできるような。</p>
<p>あと、確認はしてないけど、この変換だとMacで見た場合はどうなるのかしら。</p>
]]></content:encoded>
			<wfw:commentRss>http://astrodeo.com/blog/archives/233/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>similar_text関数が意外と便利・・・？</title>
		<link>http://astrodeo.com/blog/archives/236</link>
		<comments>http://astrodeo.com/blog/archives/236#comments</comments>
		<pubDate>Mon, 09 Nov 2009 02:24:16 +0000</pubDate>
		<dc:creator>マッチー</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://astrodeo.com/blog/archives/236</guid>
		<description><![CDATA[二つの文字列の比較をするときに、前から何文字が一致しているかとかではなくて、全体の何％くらいの文字が一致しているのかを返してくれる関数とかないのかな～と思っていたら、これがあったんですな。
similar_text()という関数がそれ。使い方はこんな感じ。


$str1 = "astrodeo";

$str2 = "macchi";

$result = similar_text($str1, $str2, $percent);


$resultには一致する文字数が返ってきます。第3引数には一致するパーセンテージが返ってきます。このパーセンテージは（返り値*2）/（２つの文字列の長さの合計）みたいな感じで算出しているそうで（こちらのサイトを参考にさせていただきました）・・・どうしてそういう計算式になるのかはよく分かりやせん。
ちなみに上のソースだと、$resultは1、$percentは14.2857142857%になるようです。
あ、循環小数じゃん、これ（果てしなくどうでもいい）。
]]></description>
			<content:encoded><![CDATA[<p>二つの文字列の比較をするときに、前から何文字が一致しているかとかではなくて、全体の何％くらいの文字が一致しているのかを返してくれる関数とかないのかな～と思っていたら、これがあったんですな。</p>
<p>similar_text()という関数がそれ。使い方はこんな感じ。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">

$str1 = "astrodeo";

$str2 = "macchi";

$result = similar_text($str1, $str2, $percent);

</textarea>
<p>$resultには一致する文字数が返ってきます。第3引数には一致するパーセンテージが返ってきます。このパーセンテージは（返り値*2）/（２つの文字列の長さの合計）みたいな感じで算出しているそうで（<a href="http://www.programming-magic.com/20071210005711/">こちら</a>のサイトを参考にさせていただきました）・・・どうしてそういう計算式になるのかはよく分かりやせん。</p>
<p>ちなみに上のソースだと、$resultは1、$percentは14.2857142857%になるようです。</p>
<p>あ、循環小数じゃん、これ（果てしなくどうでもいい）。</p>
]]></content:encoded>
			<wfw:commentRss>http://astrodeo.com/blog/archives/236/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>preg_matchで複数行の検索を行う</title>
		<link>http://astrodeo.com/blog/archives/228</link>
		<comments>http://astrodeo.com/blog/archives/228#comments</comments>
		<pubDate>Tue, 27 Oct 2009 02:42:45 +0000</pubDate>
		<dc:creator>マッチー</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://astrodeo.com/blog/archives/228</guid>
		<description><![CDATA[正規表現で文字列検索を行いたいときにpreg_matchを使うことがある。

$str = 'アストロデオのブログ';
preg_match('/ブログ/', $str, $result);

//$resultの中身
array([0] =&#62; ブログ)

このpreg_match、検索対象の文字列が1行なら特に問題ないのだけど、複数行にまたがるようなときは、上記の書き方だと上手く結果を返してくれない。
file_get_contentsなどでファイルの情報を取得し、さらにその中の一部の情報を取得したいようなとき、1行だけしか検索できないと、都合が悪いことがある。

&#60;div&#62;
    &#60;p&#62;アストロデオのブログ&#60;/p&#62;
&#60;/div&#62;

例えばsample.htmlに上のようなコードがあり、そこを丸々取得したいとする。

$data = file_get_contents('sample.html');
preg_match('/&#60;div&#62;(.*)&#60;\/div&#62;/', $data, $result);

//$resultの中身
array([0] =&#62; )

上記の書き方だと、こんな結果が返ってきてしまう。
そういうときは、デリミタの後ろにsをつけると良い。

preg_match('/&#60;div&#62;(.*)&#60;\/div&#62;/s', $data, $result);

//$resultの中身
array([0] =&#62;
    &#60;div&#62;
        &#60;p&#62;アストロデオのブログです&#60;/p&#62;
    &#60;/div&#62;
)

このsは、改行を無視して1行とみなして検索しますよという意味を持つらしい。つまり&#60;div&#62;～&#60;/div&#62;を1行とみなしてくれるから、ちゃんと検索結果が返ってくる。
]]></description>
			<content:encoded><![CDATA[<p>正規表現で文字列検索を行いたいときにpreg_matchを使うことがある。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">
$str = 'アストロデオのブログ';
preg_match('/ブログ/', $str, $result);

//$resultの中身
array([0] =&gt; ブログ)
</textarea>
<p>このpreg_match、検索対象の文字列が1行なら特に問題ないのだけど、複数行にまたがるようなときは、上記の書き方だと上手く結果を返してくれない。</p>
<p>file_get_contentsなどでファイルの情報を取得し、さらにその中の一部の情報を取得したいようなとき、1行だけしか検索できないと、都合が悪いことがある。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">
&lt;div&gt;
    &lt;p&gt;アストロデオのブログ&lt;/p&gt;
&lt;/div&gt;
</textarea>
<p>例えばsample.htmlに上のようなコードがあり、そこを丸々取得したいとする。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">
$data = file_get_contents('sample.html');
preg_match('/&lt;div&gt;(.*)&lt;\/div&gt;/', $data, $result);

//$resultの中身
array([0] =&gt; )
</textarea>
<p>上記の書き方だと、こんな結果が返ってきてしまう。</p>
<p>そういうときは、デリミタの後ろにsをつけると良い。</p>
<textarea name="code" class="PHP:nocontrols" cols="30" rows="5">
preg_match('/&lt;div&gt;(.*)&lt;\/div&gt;/s', $data, $result);

//$resultの中身
array([0] =&gt;
    &lt;div&gt;
        &lt;p&gt;アストロデオのブログです&lt;/p&gt;
    &lt;/div&gt;
)
</textarea>
<p>このsは、改行を無視して1行とみなして検索しますよという意味を持つらしい。つまり&lt;div&gt;～&lt;/div&gt;を1行とみなしてくれるから、ちゃんと検索結果が返ってくる。</p>
]]></content:encoded>
			<wfw:commentRss>http://astrodeo.com/blog/archives/228/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
