# Neo Inspiration > ctoになってしまった @jakkrokk のブログ sem,seo,自然言語処理,アプリなど ### 2024年お疲れ様でした 2024年は今日が(一応)最終日 明日と週明けの月曜が有給奨励なので、休み人多めといった形です。 今年は27から1月の6日まで休める人もいるので、まれにみる長連休です。 毎年こうして記事を書く際に、過去の見返してるんですが、 ・裁量労働制になってから6年 ・コンテンツも担当するようになって5年 ・フルリモートになって4年 ・オフラインも重視するようになって2年 ということで働き方はこの数年で大きく変わりましたが、今年は安定した年って感じでしょうか。 ※逆に言うと大きい変化はない年だった ということで、今年を振り返ります。 売上はピンチ 例年にないくらい数値は厳しかった。今も厳しい。 AIだけのせいとは思えないが、とにかくあんまりうまくいかない年だった。胃が痛い。 まあいい話も多いし、大手さんとのおつきあいは安定的にできているので、来季はそこを伸ばしたいところ。 メディア 会社のメディアが爆伸びしてくれたおかげで、数値はなんとかいけそう。 感謝しかないが、俺の手柄ではないので戒めねばならぬ。 次の柱を作る努力をする。 組織 一気に4人も離脱した。(退職2、産休1、業務委託化1) 自分のマネジメントのせい。来年は立て直す。 体調 45になり、いよいよ体調管理が大変に。 集中力も落ちるし、油断するとすぐ体調崩すし、何より血圧が高い。 運動はしないとあかん。 ちなみに今の時点で息子と俺以外がインフルになっている。 うつらないようにしないと、年末年始が割と休めない予定なのにマジでヤバイ。 個人の話 昨年から作っていたゲームは無事リリース。 200本しか売れなかったので、時給100円くらいにしかならないけど、それでもSteamで1本リリースできたのには満足。 2本目はまだ進捗芳しくないけど、ちゃんとリリースして、アラフィフでもチャレンジ精神を忘れないように。 キーボード 2年前くらいから、チャイナなレツプリを使っているわけですが、今年はそれをパワーアップさせて ・ホットスワップ ・タクタイルなスイッチ(Kailh Midnight Pro) のものに変えてみました。 こりこりする感じがまだ慣れないけど、ゴム感のあるoutemのsilent oceanよりは気に入っている。 ということで、今年もお疲れ様でした! ### XMLサイトマップからURLを生成するだけのツールをリリースしました XMLサイトマップからURLを生成するだけのツールをリリースしました。 https://neoinspire.net/sitemap.xml こういうやつを再帰的にたどってURLリスト作るイメージです。 業務で使うことがあったので、作っただけですが、意外に使う場面がある。 XMLサイトマップからURLリスト生成ツール ### JSONをパースするだけのツールを作りました いつも json parser で検索してたんですが、 ずっと使ってたツールがとうとう使用不能になってしまったので、自分で作りました。 Json Parser json parser こんな感じで、突っ込んだJSONを展開するだけのツールです。 地味にエンコードされたJSONの中身確認しなければならないタイミングがあるので、そういう時に使用します。 ### 2023年お疲れ様でした 家からのブログ更新 コロナの前までは、出社して大掃除の前にがーっと書ききるスタイルでしたが、 今はもう家で朝1番で更新するのが恒例になりました。 ということで今年もお疲れ様でした。 毎年書く前に過去数年間の振り返りをブログみつつしているんで 来年の今頃も今年どんなことがあったか、ふわっと思い出せるようにメモ書きしていこうと思います。 売上ピンチ 昨年が良すぎた関係で、今年は本当に前半戦厳しい戦いでした。 数値が全然いかないので、部署がなくなるかもみたいな話すら出た年。 結果的に今はある程度復活していますが、夏前くらいまでは本当に地獄だった。 捨てる神あれば拾う神ありというように、今は結構見えている+来季の数字のもとになりそうな案件も話が来ているのでありがたい限りですが、来年は今年のような感じにはしたくないなぁ。 会社全体 フルリモートワークから若干揺り戻してONのミーティングも強化した年 1-2週間に1回はONミーティングがあったので、それなりに大変だったけど有意義だったとは思う ONとOFFのバランスは重要 来年はONとOFFのバランスを考えていく年に。 エンジニアとして もうちょっとあきらめた。 やはり稼働は取れないw 一方でクローラー周りのトラブルも多かった年だったのによく耐えたという印象。 エースエンジニアさんに感謝。 会社メディア 会社でやっているメディアは、過去最高に順調にいっているきがする。 やはり優秀なメンバーにがっつり入ってもらうと動きが全然違うな。 来年はもっとグロースさせたい。 個人の話 昨年作り始めたゲームは体験版出してあとストーリー作ればリリースできるというところでSTEAMストップが入ってリリースがとん挫した。 AIとのすみわけが難しい一念だった。 一方で新作の作成を開始。 こちらは来年スマホ向けにリリースできればいいなと思っている。 いずれにせよゲーム制作は1年がかりだから個人でやってる限り儲かるものではないな。。。 サイト 個人サイトを一個閉じてその予算で新しいのを作り始めた。 コンセプトは月1万円でアルバイトに更新させる仕組みづくり うまくいけばぼんやり月1万払って月数万はいるようになるんじゃないかなと思ってたり。 仕組みづくりの練習も兼ねた研究。 今年のガジェット 自作キーボードが一番あつかった ずっとRealforceだったのからMistelのBarrocoに変えて分離キーボードしかだめになってしまったわけだけど それが進んで今年はErgoDashに手を出すことに。 昨年:Barroco+RealforceMouse 今年:Ergodash+SlimBlade ということで、今年も1年お疲れ様でした! ### 2023年4月 引き続きAIを使い倒す1か月 だんだん見えてきたのは、 ・構成案⇒作れる ・記事⇒面白みがないのなら作れる ・前後のスクリプトを書いて、半自動で**みたいなの⇒作れる ・そのスクリプトも書き出せる ・スクリプトの精度もGPT4ならかなり高い なので、コンテンツマーケティングも二極するのかな ・ローエンド:割りきってAIでコスパあげる ・ハイエンド:生活物そのものにAIを使うのは禁止(著作権とかのため) つまるとこ、中途半端な3万以下のコンテンツが死滅するのかもしれない うちは、需要が残るであろうハイエンドを目指すべきだろうし、 そのためにはみんなの成長も超重要 なので、AIも含めて教育の年にする ### 2023年3月 AIの進化 しかネタがない むしろそればっかりの感じ。 現在地点としては、 ・ChatGPTを使って開発生産性が爆上がり ーソースコード入れてバグ見つけてもらう ーひと様のソースコードにコメントつけてもらう ースクラッチでめんどくさい奴を作る場合のサポート(80%くらいは出力できる) ・GPT3,4を使ったツールが雨後の筍のようにでている ーWeb3で通った道 ・法律問題 ーこれもWeb3で通った道 ・SSMとしての対策は・・・ ー本件に関する専任をアサインする ー俺もコミットする ー社内の啓蒙(たぶん一番重要) ー社内の生産性を上げる ー使って見まくる とにかく今ついていかないとやばい感がすごい。 WEBの熱が、2000年代のWEB2.0に戻った感じがある ### 2023年2月 AI周りが一気に進んだ1-2月 相変わらずchat.gptはすごいものの、検索エンジンとマージしたbingが登場 1998年くらい?から維持されてきた検索エンジンのインターフェースが変わる時期 20年くらいたつので、頃合いでもあるのかも。 画像回りもMidjournyから始まるあれこれの画像系がさらに進化して、Controlnetとかが出始めている Colabで使ってみたが、たしかにこれはみんな使うわってかんじ。 たぶん色々ブレイクスルーが続くので、ずっと追っかけてないとおいてかれて、ただの利用者に成り下がるきがする 向こう3か月くらいで、google x openmind が出す検索エンジンが、今後10年くらいのベースになるんじゃないか? その後は小型化、音声マージ、脱スマホあたりがトリガーの予感 スマホもあと10年だと思うので、次が何になるのか…… ### 2022年お疲れさまでした 2022年も早いものでもうおしまいです。 毎年恒例のブログも、もう家からリリースするのが当たり前になっています。 そして、24からはずっと有給予定なので、今日が最終出社。 ※ここまで長いのは、10年くらい前までさかのぼらないとだめかも 今年もいろいろありましたが、43歳になって、仕事も順調、コロナもある程度落ち着いて、 ということで、色々考える年になりました。 正直、かなりいい年だったと思います。 やっぱり厄年開けたらいいことが多い。 会社 昨年の反省から、優秀なメンバーにちゃんと頼って物事を回していくように体制変更。 実案件にかかるコストをほとんどを1割くらいまで落として、その分周辺の組織回りとかメンバーとか、そういうのに工数を割いた1年。 メインだった部長職も別の人をアサインしたし、コンサル業も引き継いで裏方に回った。 それが全部引継ぎとしてはうまくいっていて、順調に進んでいるのはとても助かった。 業績的にもちゃんと育っていて、週3にするとか、そういうのの検討する余裕も出てきた。 うまくいっているあいだに、次を考える、なんていう余裕はこの会社で初めてかも。 体調の話 体調に関してはとてもBADな1年、というか下半期。 8月にマイコプラズマ、9月はコロナになり、10月は、普通になんもない日に高熱が出た。 1年で3回も39度でたのは、社会人生活というか、人生でも初めてかも。 年なのか。体調には気をつける。 個人として メディア メインで回したツイッターは、1500人突破 手ごたえはあまりないですが、とりあえず目標達成出来ました。よかったよかった。 一方で、このために作っていたメディアは頓挫、というかあきらめw 本業で、目の前でガチのサイトを作っていく瞬間を体験したけど、これは俺一人じゃ無理だと悟った。 Defi 才能がなかったんであきらめた。 でっかい損失はゲーム制作で取り返そう。 アプリ 8月に回収して、無事DAU下がっていますが、12月頭に少し修正して維持向上モードか? まああまり力は入れないで安定運用していく予定。 一方で、アプリをみて連絡してくれた高校生とミーティングという経験も。 学校の研究で匿名SNSを研究しているとのことだったので、 せっかくなので、しっかり資料も作ってあげて、MTGも2時間みっちり説明してあげた。 こういうときの大人の対応はとても重要(奥さん談)なので、授業感覚でやった。 うまく単位とれてるといいんだけど。 ゲーム制作 20代から考えていたものをついにスタート。 今のところ手ごたえあるけど、売れるかは別問題。 完成すればいいやくらいの勢いで来年10月までにリリースしようと思う。 BtoB系 構築はとても大変だったが、今年1年は安定運用中。 まあ少額でも、知り合いに喜ばれるのはうれしいので続ける。 あと、理容室の立ち上げのお手伝いもした。 こっちもWeb経路のお客さんもちゃんと生まれて、実際店舗に来たお客さんから褒められたりといいことが多かった。 GMB系の新しい情報も手に入ったので本業でも生かせそうだし、多少コストかかっても続けていく ということで、1年お疲れ様でした。 - ### 2022年6月のメモ BTCクラッシュ、STEPN大事故、本業は相変わらず忙しい 8月までは忍耐かなぁ・・・ 本業 新しい事業のほうに注力する予定が、メディアのほうは追い付いていない感じ プラットフォーム立ち上げはがんばる 思いのほか案件が大変なのと、システム回りがトラブりまくってる Twitter運用 前回から100増えてる・・・ やはりいいね効果はすごい ペースも上がってきているし来月1000いくかも アプリ ほぼ未着手 円安(130円オーバー)でAWSの価格が30%近く上昇 国内のサーバーにしてないのがここにきて絶大な裏目に 入れ替えるか・・・ Web3回り BTCクラッシュ2万ドルを割る ETHも890とか STEPNも大事故 こっちはかなりつらい月 8月まで耐える ### 2022年5月のメモ GWも終わりに近づく感じ 仕事もだいぶ落ち着いたのでメモしとく 本業 だいぶ引き継ぎ完了 新しい事業のほうに注力 プラットフォーム立ち上げとメディア立ち上げがメイン Twitter運用 1年ちょっとでここまで成長 リプライとかコメント付きRTとかガンガンすれば伸びると分かったけど、まあちょっとメンタルきびしいかなということで、 いいね+セグメント切ってちゃんとしたツイート(ロングツイート)のみで、コツコツ増やしてく戦略 年内に1000いきたい アプリ ほぼ未着手 Appceleratorが終わってしまったので、次のアプデは載せ替えが発生=1から組み立てる必要ありということで、腰が重い... 広告周りもエラーがではじめているのでそろそろちゃんとやらないと Web3回り NFTプロジェクト立ち上げたものの、Devチーム側の問題でぽしゃり 作りたいなーとは思うものの、まだ熱が入るアイディアがない BCGのAPI使って分析ツール作るとかはありだけど、まあまあお金がかかる $kuma $aaa あたりが爆発してくれれば色々と動かせるんだけど... ### 2021年お疲れさまでした このブログ、今の会社に入った初日にインストールしたWPなわけですが、それが来年は15年目 ずいぶん長くなってきましたね ということで、本日が2021年の仮の最終日です 今年も家からリモートワークでの〆です なお昨年同様、仮にしているのは、金曜日の今日でオシマイの人が多いから 来週27.28は有給奨励日になっています ということで、毎年恒例の振り返り 会社 激務でした… 今年ほど激務だった年はなかったなと思います。 リモートワークということもあって、朝の8時から夜の12時くらいまで、合間に昼、夜、風呂入れつつずっと座りっぱなしって感じでした。 成長もありましたが、正直再現性もないし、死ぬかもなーって思ったタイミングもあったので、来年は1日8時間労働を徹底する予定です 今年は、大きく、エンジニア、コンサル、コンテンツ、執行役員として の4つのミッションがありました エンジニアとして 特に後半は活動ほぼ0かな… うちのエンジニアメンバーが辞めていかないか心配でしょうがない コンサルとして アプリのコンサルを急遽今年からやることに 見様見真似でしゃべれはしますが、本気で向いてないなと思った一年でした 元々向いてない(コンサルは、モノづくりじゃなくて、モノづくり支援だから)と思っていましたが、まさかこんな駄目だとは思わなかった 人見知りが致命傷になったイメージ メンタル的にもきつかったし、ただでさえ200%稼働している中で30%でも持ってかれるのはイライラしまくる 会社的に数値が重要なのでやっていましたが、捨てていいよって言われたら今すぐ捨てたい感じ 幸い、年明けから引き継ぐので、全部引き継ぎ切って裏に回る これがだめなら、アプリのコンサルはごめんなさいの覚悟でやる コンテンツ こっちはどんなにハードでも頑張れる… って思ってましたが、まさかこんなハードな案件が来るとはwwww って感じでした 月160本構成案作るとか、月100本記事を作るとか。 毎週40本の構成案チェック、毎週20本の記事チェック、これは本当にハード 月-水の18時以降はMTG一切禁止で集中するって感じで1年乗り切りました まあでもコンテンツはまだまだ頑張れる ※絶賛今もトラブってるけどww 執行役員として 及第点。。。。は与えられないだろうなーという感じ ひたすらタスクに追われて、長期施策練っても実行力がほぼない感じで終わってしまった メンバーに任せて動かせるところは動かしたけど、、、 といった所 実際問題、4月から動いていたキャパシティの流動性問題は、想定を上振れしたということもあってあまり解決できない 来年タスクをはがしたら注力してビジネスロジックを作り直す予定 プラスだったところは、いい意味で自分の状況を見てできるところをサポートしてくれたメンバーの成長 とても助かった 個人として BtoBサービス 前半戦はサーバの実費だけでいいよって感じで重たい案件を受けてしまって、結構大変だったけど、勉強になったことは多かった サービスを片手間で運用するのは難しい SNSアプリ 毎年改修していたアプリは、今年は結局無理だった、DAUも30%くらい下がってしまっている感じ DeFi関連 あとは昨年末からはじめているDeFiとかが楽しい ⇒楽しいけど成果はイマイチ 来年はこっちもちゃんと向き合いたい メディア 結局記事アップが大変で進められていない ※セッション月100程度 しかしこれと併せて作成したツイッターはフォロワー400人越え 全部Webライター関連でアクティブなので反応は良好 コンテンツビジネスと親和性が高いアカウントなので、来年も運用して1000人超えたい ということで 今年も一年お疲れさまでした。 ### 11月のメモ 長かった厄年期間も残すところ後2ヶ月 しかしまあまだでかい山が残っていた・・・ 昨年もそうだけど、年末ってでかい案件が炎上して飛び込んでくる傾向にある 最近のめんどくさかった事項としては、 ・AWSのAuroraでAmazonn側が出している仕様と違う挙動が発生。サポートに聞いてみたものの直せないらしく、強制的にDB全移行という悲しみを背負った なんでこんなことになったのかわからないんだけど、Deleteして最適化かましたら、普通はデフラグされてDBが圧縮されるはずなのにそれが動作しないという感じ。 圧縮されないのでDBの利用コストが普通にそのままだし、なんかseekも遅い -> 色々とまずいので直すかっていう感じで重い腰を上げる とりあえず未解決で、今月中になんとかする予定 年末はゆっくりしたい・・・ 去年みたいになりたくないのでトラブルは起きないでほしい ### ツイッターをまじめに運用すると… 2021年、SNSとかメディアとかやろうと決めて運用していたわけだけど、 メディアは結局まだ全然(アップしないといけない記事がたくさん・・・) 一方で、SNSは割と毎日コツコツ運用できている そっちはフォロワー300人程度なんだけど、反応がすごい ※フォロワーこれしかいないのに ※いいねがいっぱいつく セグメント切って、そこのフォロワーしか集めないようにした結果がこれ この状態でフォロワー1000人くらいいければ、かなりアツイと思うんだけど、ちょっと心が入りきらないのがネック・・・w ### 2021年後半戦 今年も後3か月。そしてもう少しで会社は下期。 はやっ・・ 前回ブログ書いたのが6月で、そこから3か月はあんまり進捗なかった。 GMBアプリ開発 こちらはなんとかリリース完了。 但しこれから機能追加などもあるので、まだゆっくりはしていられない。 一旦喜ばれてよかった。 すでに3社くらいクライアントもいるみたいなので、何とか安定運用にもっていく。 メディア制作 ライターさんがストップしていたので、ほとんど更新せず・・・と思ったら、昨日どかっと6記事納品してきた。 これからチェックして順次アップ 年内15記事くらいかな? ツイッターは相変わらずコツコツ フォロワー200まで成長 家業のWebマーケ 最近放置 GMB更新しなくなったら、与件もこなくなってしまった。 ただ、あまりレッスンの余裕もないので、一旦このままの予定。 ゲーム制作 全然やっていない。 アプリ運用 8月にTVCM放映。 ちょっと高かったけど、かなり割引してくれたので、個人で発注した。 芸能人に紹介してもらうのはなんだか恥ずかしいんだけど CVRがあがった気がしている。 アフィリエイトの研究 個人でも本業でもメディアスタート。 個人のほうは即頓挫したが、同じ記事を本業のドメインに入れたら3位まで上昇して、成果もつくように。 今後はマネタイズの幅を広げていくための研究。 いずれにせよ、まずは自分の手で動かすことが大事。 向こう3か月 色々安定運用に向けてってのと、投資系を強めたい。 余裕が全然ないので、ゲームはちょっと作れなそうだなぁ・・ ### 2021年 - 半年の進捗 大型案件にうつつを抜かしていたらあっという間に半年ですね。 ブログを書いている今日、緊急事態宣言は解除されました。 少しは景気が変わるといいのだけれど… さてさてプロジェクトの進捗 GMBアプリ開発 一番厄介 引き受けたからにはやりきるんだけど、Google My Business APIは、整っているようで整っていない上に、やっている人がほぼ0なのかGitHubでもでてきやしない。 しかたないので、メソッドをたたいて中身を確認みたいなことをひたすらしている。何時間かかるのこれ… やはり知り合い以外からはシステム開発なんて引き受けてはいけないと肝に銘じる メディア制作 まじめなライターさんに依頼しなおしてなんとか進捗開始…といってもまだ10記事以下 SNSは引き続きコツコツやれているので、あと半年でなんとか形にしたい 持つべきは良いライター! 家業のWebマーケ こちらも引き続き月1件ペースが守れている 最近Webじゃなくて直で電話のケースが増えてきていて、意外にWebじゃないほうがいいって人もいるんだなと ゲーム制作 GMBアプリに全力リソース突っ込んでいるため、こちらは秋口スタートに変更 やりたいんだけど、やれない アフィリエイトの研究 本業でもきちんとメディアに向き合う必要が出てきたので、セッションではなくCVRに向き合うため研究 とりあえずページ作って運用 向こう3か月 まずはGMBをリリースして手離れしたい 土日が全部なくなってしまうのが痛すぎる 次は本業のためにもアフィリエイトの研究とメディアを進める 秋までに形にして、ゲーム作りに励みたいところ 今年から始めたこの備忘録、意外に忘れがちな部分も振り返れてよいと思っている ### GW終わりました GWは緊急事態宣言(神奈川はでていないけど)みたいな雰囲気だったので、結局旅行はキャンセル。しかたないので家でかぞくと過ごす感じでした。 また新しくプロジェクトはじめてしまったので、そっちが捗ったともいう。 以下プロジェクトの進捗 GMBアプリの開発 やっとα版ができていろいろ修正箇所を対応中あと2か月くらいかかるかな・・・でも目途は立ってきた とりあえずGMBのAPIはとても丁寧でかなり細かいんだけど、GMB自体がかなりのテンポでアップデートかかっているので、APIが全然追い付いていない感じが否めない。できないこと多すぎ。 メディア制作 毎日ツイッター運用しつつ記事を発注してチェックしてってやろうとおもったら、ライターさんが全然戻してくれない・・・  ここまででまだ5記事。単価が安いと人は動かないということがよくわかった。 家業のWebマーケ これはまともに全部稼働。半年で6CV、そのうち2本は受注というかお客さんに。1年で1-2人お客さんが捕まればいいやと思っていたので上々。 GMBをまじめに運用しているのが大きい。むしろGMBだけがっつり運用していれば、サイトいらなかった説ある。 友達のWebマーケコンサル(Ended) 一旦おしまい。一通り見てあげて運用設計とやること教えてあげたら、やることないな・・・ ゲーム制作(New!) 元々大学生のときはゲーム業界でディレクターやりたかったんだよなーって思い出して、今のスキルと知識があればちょっとはできるかもしれん、と思い立ちGWで制作を開始してみた。 Unity使ってスマホでタワーディフェンス作る予定。 すごい優秀なtoolkitとTerrainを購入したので、それを軸にコツコツいじっていく予定。案外2-3か月でできちゃう気がする。 向こう3ヶ月 タスク減らないどころか追加していまったけど、攻めの姿勢は忘れない 本業が激務なんだけど、不定なのか安定なのかで大きく違うので、後半戦はもっとがんばれるかな・・・ 1-3月で立ち上げ時期だったプロジェクトが3つくらいまとめて安定した激務って感じで着地したので、ぶん回しモード。 ### あっという間に2021年も2ヶ月・・・ 早いもんで気づいたらもう2ヶ月立っています。今年は少しずつでもログを残していく予定なので、着手しているメモしておきます。 GMBアプリの開発 今一番工数かけているやつです。Webベースですが、nodeで作っているのでapi化してアプリにもしてみようかなと。 簡単にいうとGMBのAPI使って、複数の店舗の一括管理みたいなよくあるやつです。ただ、よくあるとはいえ、今世の中にある奴は1,2万くらいは取られるので、2-3店舗程度の小さい会社さん向けに作って1000円/月とかで使える奴を作ってみようかなと。 メディア制作 これはアフィリエイトの前にそもそもセッションを産むサイトを作るロジックが作れるかを試してみたくて、1月ずっと考えて設計して、あとはお金払って回してもらうって感じでやってます。もうそろそろ記事ができはじめるので、ここからが勝負。 家業のWebマーケ 音楽教室やっているので、それを改めてちゃんとWebマーケしてみようみたいな。いい機会なのでGMBのアプリとセットでしっかり運用できるように。目標月3問い合わせくらいでいいので。それ以外にも、アプリ系のサイト作ってみるなど マッチングアプリなどのアイディアや開発のメモサイト 友達のWebマーケコンサル どうせ月1万くらいしか予算がないので、無報酬でやっていますが学ぶこと多いですね。広告の管理、GMBの管理、SNS運用指南、記事入れる方向性考えてあげたり、記事作る人WPの運用保守のメンバーの剪定を手伝ってあげたり。やること多いので、毎月ちょっとずつですが、半年くらいで結果出るといいなと。 ものづくりの人としてのプライドもあるので、直接物が作れないコンサルは基本NGなんですが、まあ今年は諦めてちょっとだけやる予定。 向こう3ヶ月 とりあえず今着手した物の成果を出すところ。本業が激務すぎるので、スピードが遅いのがネックなんだけど。ただ、こうやって幅を広げていく人がいないのでなんとか自分でやるという強い覚悟を持つ。   ### 2020年お疲れ様でした 実は予定通りなんですが、2020年は一回もブログを更新しない初めての年になりました。まあ今年は文字じゃなくソースコードを書くという目標を立てていて、時間を割り当てていたので、これでよかったりします。 ということで、2020年もお疲れさまでした。本日が仮の最終日になっています。※28,29は出勤日なのですが、有給推奨日となっていて、メンバーの多くは休むため しかも、この記事は初めて家から書いている年末記事です。今年を象徴する感じです。 今年はコロナというとてつもない壁があった年でした。毎年、激動でしたねといってきましたが、今年ほどこの言葉が似あう年はないかなと。 ということで1年を振り返ってみたいと思います。 フルリモートに 一番の出来事は、やっぱりこれです。元々自分の管轄内ではリモートワークを導入していたのですが、コロナが激化する前の2/17に社長に提案、無事承認されて、2/19から全社的にリモートワークになりました。 その後4/17には、当時のオフィスを引き払う選択を行い、それに伴って、恒久的にリモートワークに。本格的にリモートワークの環境を整える必要があったので、自宅の部屋を改装して仕事部屋にしました。あわせてイスを買ったりデスクを買ったりしています。それにしてもリモートワークは電気代とかの負担が増えますね・・ QOLが上がった 今になっては、出社する日は仕事が回らないのですごい困る、というレベルでリモートワークにフィットしています。うちのメンバーの多くも、もう出社は考えられないといっているので、そういう意味でも切り替えてよかったなと。 あとQOLという意味でもすごい上がっています。今年に入ってからの自分の生活は 起床 > 家族でご飯 > ジムで筋トレ > ジム併設のカフェで仕事 > 昼食(家でとったり外食したり) > 家で仕事 って感じで、とてもいいルーチンが組めています。 リモートワークの弊害は・・・ 一方で弊害は・・・結局あまりないですね。コミュニケーションロスは、時間使ってカバーする感じになりますが、出退勤の時間 > コミュニケーションの時間 なので、リモートワークにしたうえで、必要なコミュニケーションをとっていく、で十分カバー可能だと思っています。 メンバーが15>20人に リモートワークとかコロナの下りを書くと、あとはおまけみたいなもんなんですが、人数が20人になったことも一つイベントとして数えられるかなと。 追加したメンバーは半分が業務委託での契約になっていて、よりチームとして柔軟性が高い状態になっています。こうしたチームを組織して管理するのもだいぶ慣れてきていて、チームとしての完成度はかなり上がった印象があります。 去年一年間、それぞれが自分のポジションをもって仕事ができるように意識して配置してきたわけですが、それが実を結んでいて、**といえば## といった形のメンバーが増えています。自信もつくし、質も上がるし、管理も楽になるし、でとてもよいサイクルになっています。 今年は、自分のチームというくくりでいくと、だいぶうまくいったなーと思っています。 個人としては・・ ilkaのver2をリリースしました。半年かけてunityとC#を覚えたので、エンジニアとしては一つ成長です。Ver2自体は全然DLされなくてしょんぼりなんですが、どこかのタイミングでAppceleratorが使えなくなるんじゃないかっていう不安もあるので、リスクヘッジとして置いておこうと思います。ilka ver2 併せて、GameStockっていうゲームのまとめアプリみたいなのも作ってみました。こっちはレギュレーションの問題でiosの審査が通っていないのでAndroidだけですが、Unityでサクサクっと作れるようになったのは大きな成果です。 たぶん2週間くらいで作りました。まだVer1なので、ここからチューンしていく感じになりますが。GameStock - 自分だけのゲーム攻略まとめ作成ツール 来年は せっかくUnity覚えたので、BtoBのデスクトップアプリ作ってみようかなーと考えています。あとAR系。どっちもハードルが高いんですが、一つエンジニアとしては成長につながるなと。 その他 今年は、一応厄年だったはずなんですが、前厄がすごすぎたせいなのか、ほぼ狙い通り動けた年でした。年始に立てた目標も達成でき、全然厄年を感じない一年でほっとしています。 もしかしたら家族で川崎大師に行ったからかも?お払いの効果はあるのかもしれませんね・・・ ということで、今年も一年お疲れさまでした! ### 新しいサイトを試験的に作成中 お金がない サブディレクトリが良いときいたり、サブドメインが良いと聞いたり 色々ありますが、今回はレンタルサーバーのサブドメインを利用中を実験的に使用中 今回はお金がないをテーマに20記事を程をつっこんで、 順位とか状況を確認していく予定。 ちゃんとがっつりFPとかもまじえて作って役に立つようにしていきます ### 2019年お疲れ様でした まさか平成最後の更新の次が、2019年最後の更新になってしまうとは。 ということで今年もお疲れ様でした。今年は12/27が最終日となります。 厄年ということで、社内転職しないといけなくなる、祖母、おばが亡くなる、インフルエンザにかかる(息子も)ノロにかかる(息子が)ギランバレー症候群だといわれる(未遂)などなど、中々大変な一年でした。 例年になく激動の年だったので、色々振り返って見ます。 エンジニアからコンテンツマーケの人に転職 2018年末に当時の部長が抜けるため後釜としてコンテンツマーケ部の部長になったわけですが、 課題が山積み+激務ということで、ほぼエンジニア、CTOとしての職責は勤められませんでした。反省です。 ただ、事実上転職して、全く新しい職業についた感じなのでとても勉強になった一年でした。 本当に一からコンテンツマーケを勉強してみんなについていったのでとても新鮮ではありました。 校正校閲ができるCTOなんて中々いないかなと。 コンテンツマーケ部の部長として もちろん売上、粗利の管理、維持向上、 14人もチームメンバーがいるので有機的に機能させるためのマネジメント とにかく負荷が高く、残業が深夜に及ぶこともしばしば という状態で、ビジネスとしての継続性が低かったので、ビジネスモデルの改善 マンパワーが足らないので、コンテンツの1ディレクターとして案件もバシバシ回す フロントできるメンバーも少ないので、代理店に限ってはフロントも担当 って感じで、これだけで1.5人月分くらいの職務があって非常に大変でしたが、全部なんとかやりきったなーという感じです。 最終的には 粗利:目標達成 マネジメント:チーム力大幅強化。離脱者も1名のみで過去と比べて大成功(彼はもともと長期的にはやめて自分で会社がしたかったので問題なし) ディレクター部分:SEOを加味した構成案、順位、大手代理店でも一発で通る編集・校正・校閲能力、ここらへんの能力はつけることができて、自分の案件はきっちり回せた。 フロント:信頼されて、一部常駐依頼も 激務でしたが、なかなか達成感ある一年でした! 一方マネジメントみたいな視点で行くと、悲しいことも多くて、自分で組織を作ってみたいなという気持ちも芽生えた一年でした。一緒に働きたいなというやつは、やっぱり昔の自分と似ている感覚があって、それだけに今の組織は小さすぎる。 自分だったこうするな ということを、自分の生死をかけてやるっていうのは、もしかしたらすごく楽しいのかもしれない。そういう意味では、経営という部分はとても魅力的に見えます。 まあスキルが足らないんですけどね(主にやりたくないという意思) 来年はもっと一緒に働きたいと思われるような人間になって、足りない部分を補えるようにならないといけないなと自戒。 個人として 激務のため例年に比べて作業がはかどらず進捗がかなり悪い・・が! とてもうれしかったこととして、テレビ、それもNHKで取り上げられたことがあります。 これによりユーザも増えて、対応するために個人でaurora借りたりする暴挙にもでています。 これ以外に2回、テレビのご相談はあって(いずれも尺の問題でカットと通達されてしまった・・) ilka運営5年目にして大きな成果がでました。 あとは、コンテンツマーケ部の部長として、メディアの一つぐらい作って置かないと、と思って作ったメディアを作りました。 が、こっちは鳴かず飛ばず。ちょっと奇策で攻めすぎましたね。まあアフィリエイトとか狙うわけじゃないのでいいんですが、 こっちは来年もうちょっと工夫して、2ー3万PVくらいにはしたいなーと思っています。 来年の話 来年は、そうですねもう少しエンジニアというか、物作りに携わりたいなと思います。 個人もそうですが、会社としても部長の座はゆずって、0->1の部分に関わっていく必要があると感じているので、パワーをそこにかける。 個人としてもそこの能力を伸ばしてく。こんな感じの1年になるといいなと考えています。 ということで、今年も一年お疲れ様でした! ### 平成最終営業日 今年初めての投稿がこれでいいのか・・という疑問がありますが、記念なので 最近はCTO片手間にコンテンツマーケチームの建て直しとか組織作りとかをやっています。チームとしては去年から気をつけている心理的安全性なんかは、結構うまくいってきている印象です。 https://qiita.com/jakkrokk/items/341ac25c049ea0b5350f ※昨年書いたやつ。そろそろ結果かかないと・・ 昨年は会社に直訴してリモートワーク制度を作りましたが、なかなかよいですね。気分変えてタスクに集中できるというのは、業務する上でいい環境だと思います。 今年はそれをコンテンツマーケのディレクターでも展開するようになっています。おおむね問題なくいけてます。案外会社にいなければならない日数っていうのは少ないのかも。 あとなぜかライターもやってます。 コンテンツマーケティングの解説記事 こないだ書き上げたやつは1万文字もあってそれはまぁ大変でした。 令和になったらもう少しエンジニアになりたい! けどまずは令和一発目のアプリをGW中に進めます ### 2018年お疲れ様でした 今年も本日26日が最終日となっています。 全従業員3人+社長も経験した、弱小ベンチャーですが、自分の社歴も11年目突入し、人数もミッションも大きく変わった一年でした。 毎年、色々ありますが、変化で行ったら今年が一番大きかったかも。 まず執行役員CTO(エンジニア3名しかいませんが)という肩書きに、プロダクト部門長というのが増え、さらに10月からコンテンツマーケティング部長という肩書きも追加されました。 そのため自分の管轄が3人から20人近くに一気に増えています。 ということで、エンジニアとしてのお仕事は1,2割くらいで、それ以外はCTOとしての職責、プロダクト部門長、部長としてマネジメント、コンテンツマーケティングのディレクターまで増えて、まあなんというかカオスです。 4月からは、裁量労働制といっていうのが正しいのかわかりませんが、出社、退社などの勤怠にしばられなくなり、週3くらいで朝カフェで仕事、夜MTGがなければ、残りはカフェで仕事みたいな感じになっています。 こうでもしないとMTGですぐ埋まってしまうので、正直自衛手段に近いです。 MTGといえば、1on1の制度や、そもそもジャッジする管轄が広がってしまったということもあり、本当に一日中MTGばっかりで、もうエンジニア!と胸を張っては名乗れなくなったかな。。。というのが残念です。ソースコードの量は過去最低かな。 CTOとして、エンジニアとして 一応自分の中でメインと位置付けているCTOとしては、チームの働き方だったり、今までの縛りとかも変えていこうと努力した1年でした。 qiitaとかでも書きましたが、 「心理的安全性」について進めたり、 https://qiita.com/jakkrokk/items/341ac25c049ea0b5350f エンジニアのリモートワーク制度を承認してもらったり、 https://qiita.com/jakkrokk/items/4d546b836e019d17fc62 と、チームに変化を与える結果が出せたのは結構満足しています。 エンジニアとしての仕事もしてはいますが、渾身のプロダクトは売れずにぽしゃるという悲しみを背負ったのと、社内の整備が早急に必要ということで、あまり新しい技術みたいなところには触れられなかったなというのが振り返りです。 その分メンバーには面白そうな仕事をなるべく作ってあげるようにはしていて、そういう意味でも、多少なりともCTOっぽい動きはできたかなと思っています。 個人として こつこつ開発している匿名SNSアプリのilkaは横ばいでした。というかほぼなにもできなかったです。落ちなかっただけマシ。 毎年1000万PV目指しているのですが、今年は本当に開発リソースなかったですね。。 あとはコンテンツマーケティングも自分の領域にになるということで、年初に個人でメディア立ち上げてみたのですが、目標の10万PVどころか、このブログにも勝てていません。来年は10万いくといいんですが、それまでモチベーションが持つのかどうか・・・ 今年も文章よく書いた1年でもありました。(このブログは全然更新していませんが・・) qiita、note,なんかもそうですし、テストで作っているメディアでも謎のライターとして書いてみています。 まあメディアに関しては、来年はフルランサーズして、楽しようかなと思っています。 https://qiita.com/jakkrokk/items/341ac25c049ea0b5350f https://note.mu/jakkrokk/n/n4523dec259bb 来年の話 今担当しているコンテンツマーケティング部の立て直しと改革を断行(安倍政権みたいだ)するのと、エンジニアやプロフェッショナルの地位向上というか、存在意義を明確にして、会社として才能を使いきれるような、資産にもっていきたいですね。 あとは、そろそろ会社にうちはこれ!というようなプロダクトが作りたいなーと。まあそのためのインプット、あまりできていないので、そこからがんばらないとですが。 時代はAIですし、間違いなくそこは抑えたいんですが、いかんせん経営陣を説得できるようなものが自分にないんですよね。難しいところ。自分が社長だったら直感的に絶対やるってことでも、あたりまえですがきちんと説明しないといけないので、そこは自分のスキル不足を痛感しています。のびしろってことですかね。 ということで、2018年、今年もお疲れ様でした。 みなさま良いお年を! ### 共起語をカウントするツールを作りました。 これです https://neoinspire.net/cooccur/check.php だいぶ前から、要望はあって、コツコツ遊んでたんですが、こういうのはやりきらないとめんどくさくなってリリースしないで終わってしまうので、一旦作りきり。 細かいCSVで落とすとかは一旦後回しです。 出来上がったコンテンツと狙っている共起語を入れると、色つけてカウントしてくれます。 適当にwikiの安倍総理のやつでやるとこんなかんじ。 よかったら使ってみてください。 後、これはただの宣伝ですが、この手のツールを現在ガシガシ実装しているコンテンツマーケティングツール CACASEL が絶賛売り出し中なので、よかったら見てみてください。 https://www.sakurasaku-labo.jp/cacasel/ ### AI学会/人工知能学会全国大会に参加してきました 先週 6/5~6/7にかけて3名でAI学会に参加してきました。 スポンサー60社以上、論文数600以上らしくにぎわいはすごいものでした。 会場がかなり広かったからか、混雑っぷりは自然言語処理学会と同じくらいに見えましたが、参加人数はこっちのほうがだいぶ多かったかなと思います。 セッションはあらかじめ見るセッションを全部リストアップして、エンジニア2名で手分けしてみて行ったわけですが、あらかじめ論文の内容に軽く目を通して、ヒントになりそうなものを見ていくっていう感じで行ったせいか、自然言語処理学会の時と似たような内容が結構多めでした。 だいたいmecab(neologd)+word2vec とかでやっている感じで、自然言語の文脈では画期的な手法みたいなものはあまりない印象ですが、全然違うとこに着目していたりするものもあって、ヒントは多かったです。 面白いなーって思ったのは、思わずツイートまでしちゃいましたが、曰く「部分的退色表現」を使用して文章の理解を向上させる(可読性みたいなのとは別の話)ってやつで、確かにスライドにある表現は見やすくて、WEBコンテンツでも使えるんじゃないかなと思いました。今度このブログでも実装してみようと思います。 ※WEBで使えるかどうかっていう点は、人生初めての学会での質問をしようとしていたんですが、あまりに他の質問が多くて時間切れという。 それ以外では、結構な発表でクラウドソーシングを利用して~ という話がでてきていたのも印象的でした。 ※これは自然言語処理学会でも同じ うちの場合自前でコンテンツ特化のクラウドソーシングを運営しているため、このデータなら簡単集められそうとか、できそう みたいなものは結構あって、AIを作るという文脈もそうですが、AIに必要なデータを作るスキームと販売 みたいなものもできるんじゃないかなと思っています。 ⇒表現悪いですがそれをエサに共同研究とかもできないかなと思ってたり ということで、今絶賛開発中のコンテンツマーケティングツール:CACASELにガンガン実装するためのヒントがもらえた3日間でした。 余談ですが、社長直々にアテンドしてくれるので、贅沢にお昼休みに桜島までいってご飯食べてきたりしてます!(サクラサクだけに?) [caption id="attachment_850" align="aligncenter" width="225"] dav[/caption] ### AI学会/人工知能学会全国大会に参加してきます 来週火曜日〜木曜日で、鹿児島開催の2018年度 人工知能学会全国大会に参加してきます。 https://www.ai-gakkai.or.jp/jsai2018/ 今回は社長・エンジニア1名+自分の3名体制で参加です。 すでにタイムスケジュールはでているので、それを元に見る予定のものは全部洗い出しているんですが、 気になるものが多すぎて分身しないと全部みれない状況です・・・ とりあえずエンジニアと自分がバラバラに行動しつつ効率よく情報収集+共有って感じで、動く予定になっています。 学会に参加する他の会社さんはどういうスタンスなのかはわかりませんが、 弊社のような小さい会社の場合、なんでもかんでもプロダクトに結びつけていかないといけないので、 メインは自然言語処理+アルファの部分で、先行研究や現在進行形の情報などをキャッチアップ、できれば現在開発中の新しい共起語ツールに活用できたりしないかなど、 プロダクトベースで見ていく予定です。 ということで、現地でお会いできる方などいらっしゃいましたら、よろしくお願いします。 ### 自然言語処理学会に参加してきました タイトルの通り、自然言語処理学会に参加してきました。 http://www.anlp.jp/nlp2018/ この会社に入って10年、初の長距離出張で岡山でしたが、非常に勉強になる4日間でした。 背景としては、半年前くらいからAIだったり自然言語処理だったりをチームで取り組んでいる中で、 この4月リリースしたコンテンツマーケティングツール、「CACASEL(カカセル)」の強化に必要な自然言語処理の情報が少しでもキャッチアップできないかというところがあります。 学会を通してキャッチアップしていたのは、分散表現の部分で、例えば現状だと word2vecだったりが隆盛を極めているようですが、それ以外の方法でうまく分散表現できないか==文章のベクトル化の別方法はないか という点です。 現状社内ではword2vecなどを使えばある程度うまくいっているものがあるのですが、それを精度をあげたり、プロダクトに落とし込もうとすると、どうしても別のベクトル化の方法がほしく、社長と二人で参加した学会ですが、ほぼ関連しそう(と思われる)発表のみに絞ってスケジュールを詰め込んで聴講していました。 学会の参加は初なので、アカデミックな場からここからいきなりプロダクト化のヒントがもらえるとまでは想定していなかったのですが、全然そんなこともなく、ヒントやアイディアの塊だったなという感じで、わざわざ岡山までいったかいがありました。 次は鹿児島に人工知能学会に参加する予定です! ### 2017年お疲れ様でした 今年も本日26日が最終日となっています。 今年はサクラサクLABO - Advancedの大規模開発があったのが印象的でした。 毎年プロトタイプみたいなプロジェクトを大量に走らせる形の開発スタイルから一転、 半年かけてがっつりでかいものを作るというスタイルになったのは、非常に勉強になりました。 内部的には、Auroraをがっつりつかって、バッチサーバも2台フル稼働させているようなものになります。 後、ついにというかディープラーニングだったりにチームとして手を出し始めました。 まだまだ始まったばかりですが、自然言語の分野でtensorだったりchainerだったりを使ってコンテンツ解析を始めています。 来年からは学会にも顔を出したりして、よりコンテンツに特化したツールを作成予定です。 プライベートではアプリ3本、WEBサイト2本って感じで、そこそこ開発できた1年でした。 WEBサイトでいくと、去年あたりからちょこちょこ触っていたビットコインに関連して、 Googleの感情APIと組み合わせて変動を見るツールをリリースしました。 海外の人からよくツイッターでハートもらえています。 ビットコイン感情分析 ちなみにポジション取っているのはほとんどnemです。 こつこつ開発している匿名SNSアプリのilkaは30%ほど成長できました。 現在リアルタイムで新バージョンを申請していますが、これが通ると、初めて内部課金のコンテンツもリリースされます。 毎年のことながら来年こそ1000万PVと思って布石を打っています。 こんな感じでしょうか。 去年は自分じゃない部分でかなり使った印象でしたが、今年は割りと投資できたよい一年でした。 それでは皆様よいお年を! ### ランダムフォレストでASOとかSEOとかやってます 今年は割とデータサイエンス的なこともやっていて、ランダムフォレストという機械学習のアルゴリズムを使ってASOやSEOを分析しています。 特にASOなんかは外部リンクのようなとってこれないデータが少なめなので内部対策の重要度をデータから導き出せるので中々よい参考になります。SEOだと取りきれないデータも多いので、内部対策に絞ってやっていますが、これはこれで不要な内部対策を洗い出したりできます。 ランダムフォレストがわかりやすい感じの参考URL http://drilldripper.hatenablog.com/entry/2016/10/04/211245 https://www.slideshare.net/HitoshiHabe/ss-58784421 実際やってることはこんな感じで、週次で自動計算してデータを集めて、変動を見ながらアルゴリズムが動いたとかそういった検知をしています。 ランダムフォレストのいい所は、ディープラーニングと違って寄与変数を出せる所で、仮説一覧の中で相関度が高いものを出してくれるので、それがそのまま対策優先順位になるってとこでしょうか。 ※上にいくほど相関度が高い的なグラフ さらに、各項目について、詳細を出す事も出来て、例えばこれだとASOにおいて更新日が高いほどよくて、急カーブで落ちて行って、ドメインオーソリティみたいに少しづつ上がっていくみたいなデータも見れます。 ソースは裏はRで書いていて週次でデータを集計してRFかけて、データをまとめる所まで自動でやっています。RFのチューン自体は毎日データ見つつやっています。 データの量が多いので処理のサーバが中々に大変なんですよね。特にios11対応でリソースひっ迫してピンチだったりしますが、今の所安定して動いています。 会社が初めてEXPOなるものに出展したので、たまには宣伝みたいなことも書いてみようかと思ったので投稿しました。 ということで、テクニカルだったり、データ分析の側面からもSEOやっています。 ほとんどコンサル案件になっていますが、一度話を聞いてみたいと思った方や、単純いSEOなどのご相談はこちらからお願いします! # もしくは記事にコメントいただければこちらからご連絡いたします! https://www.sakurasaku-labo.jp/contact ### Wordpress のサブディレクトリ型マルチサイト対応で、ロードバランサーかましてると無限リダイレクトになる件 こんなのわかるわけがない・・ 気合で調べていたら、先人の知恵がありました。 掲題の件、この対応すれば解決します。 ※先人の知恵 Thanks! http://wasielewski.org/uncategorized/2014/01/28/wordpress-multisite-proxy-configuration.html ※以下、subfolder == PATH_CURRENT_SITE の設定で脳内置換してください 以上。 結局HTTP_HOSTがロードバランサーのIPになっちゃうので、それでおかしくなっていたようで、あわせてついてくる、HTTP_X_FORWARDED_HOSTで置き換えてしまえということらしい。 $base に設定入れるのはなぜなのかは不明。 先人の知恵には .htaccess の設定もかかれていましたが、 こちらは https とかの対応云々なので、必要必要な方はそちらもあわせて。 ※自分は今回使用せず。 ### Lets encrypt で SSL 自動更新 > Centos6 で certbot-auto を入れる SSLがデフォルトになりつつある世界で、無料のSSLがあったらそりゃみんな使いますよね。 ってことで、ドメイン認証に限りますが、無料のSSL Let's Encrypt を結構使っているのですが、昔から借りてるさくらのVPSはcentos6ってこともあり、yum 1発で入ってくれません。 ってことで、 こんな感じで叩けばいいという備忘録。 ちなみに、上記で ./certbot-auto すると、apacheだとコンフィグからあたっているドメインリストを作成して、そこからどれをSSLにするか選択するようなメニューになります。あんまりないと思いますが、confにドメインを書かない感じで設定しているとメニューにでてこなくなってハマるので注意。 最後に Let's Encrypt は3ヶ月で切れるので、crontabで更新設定しておきます。 ここにあるシェルとか参考に。 https://inaba-serverdesign.jp/blog/20160630/lets-encrypt_update_sslcert.html 余談ですが、 iosは今年からSSL必須みたいな雰囲気で、アプリから叩く自作APIとかはSSL対応迫られたわけですが、あれ、オレオレSSLだめなんですよね。ただこれなら通るのは確認したので、個人開発者さんはLet's Encrypt にしましょう。 ### XMLサイトマップ生成ツール公開 そういえばずいぶん前に作ったんですが、使う人いないだろってことでリンクすら貼っていなかったツールを今さら使う事になったので、改めて公開しておこうかなと思います。 XMLサイトマップ生成ツール http://neoinspire.net/xml_sitemap/ やっていることは単純で、エクセルでこんな感じの書式でまとめると、それをXMLサイトマップにしてくれますよっていう話です。 lastmod,changefreq,priorityなんかは任意にしているので、なければ空欄にしておけば自動的に削除します。 こんな感じ。 5,000 - 10,000URLくらいなら普通に処理できますので。 それ以上の場合、サーバ落ちるかも。 Wordpressならプラグインがあるし、むしろSEOの観点から見ても一般のサイトで必要とすることはほとんどないと思うので、マイナー中のマイナーだとは思うんですが、せっかく作ったので公開です。 ### いまさらながら php の array_merge が8466倍遅い件について 大量データのレポート出力処理を書いていたら、 線形じゃない形で出力時間が増えていくことになって、何かおかしいなって調査した結果です。 結果、 php の array_merge は使うんじゃない。 Centos7 php7.0.19 でこんな感じのソースで試す ※1から30000まで 5000刻みで配列追加を行う 結果 // use array_merge 5001 >> 0.40064096450806 s 10001 >> 2.418830871582 s 15001 >> 5.3260531425476 s 20001 >> 8.5192930698395 s 25001 >> 14.773620128632 s // insert with [] 5001 >> 0.00035500526428223 s 10001 >> 0.00072813034057617 s 15001 >> 0.0010349750518799 s 20001 >> 0.0013651847839355 s 25001 >> 0.0017449855804443 s array_merge だと25000行追加するのに14秒 $arr[] = ''; の形だと 0.001秒 14.773620128632/0.0017449855804443 = 8466倍! 使い方が間違っているんだろうか。 -- 追記 akiyanさんにコメントで教えていただきました。 @jakkrokk https://t.co/KBcM0cjMvJ ですが、https://t.co/6j4ReJHqsI は配列同士をマージする関数であって配列に追加していく関数ではないのでそもそもの用途が違うのではと思います。第二引数に配列以外を渡すと自動変換も挟んでそうです— akiyan (@akiyan) 2017年6月19日 用途が違うってことですね。 php10年近く触っているのにまだまだ知らないことが多い・・・ ### 行動指針と採用活動 買収などで規模が大きくなったということもあり、部署ごとに行動指針を作り始めています。うちのチームも3人とはいえ、立派に独立した事業部(エンジニアチーム)なので、CTOはがんばって行動指針を作ってねというお達しがきました。 調べてみるとエンジニアの行動指針って結構いい感じなのをみんな言っていて、パクりたかったんですが、いいものパクるだけの行動指針作っても誰にも刺さらないようなぁ・・・ と思い、苦手ではありますが、普段うちのメンバーに言ってきたことを明文化するっていう感じで作りました。 内容を簡単に言えば、特化した武器をもち、かつ残りの部分もそつなくこなし、メンバーをリスペクトして、仕事に誇りを持てということになります。 ちょっと贅沢言い過ぎた気がしますが、かっこいいエンジニア像にありがちな、「●●は超できるけど、あとは全然ダメ 」 みたいな人はうちではあまり活躍できないので、そういう意味でちゃんと書けたかなとは思っています。 以下原文。 エンジニア募集しています そういえばエンジニアをもう一人採用していいことになったので、よかったら一緒に仕事しましょう! 採用内容はこんな感じです。 ### php elseif と else if の違い phperの中では常識だったのでしょうか・・ たまたまベンチマークで array_flipがクソ重い的な調査をしていた時に目にしたのがこのページ Php/速度改善/おすすめコード置換 うそでしょうって思ったので試しにテストしてみたら本当でした。 上記のテストの結果は else if >> 27.6 s elseif >> 22.57 s とこんな感じで20%以上速度改善 なんでなの?ってことで調べてみたらどうも elseif > C言語の構文 else if > 構文でない という違いらしい。 else if の場合 if () { } else { if () { } } こういう感じでネストしているということらしい。 ちなみに弊社では else if で統一しようって言っていたんですが、これはダメですね。 ※記憶が正しければzendの仕様がそうだったはず? ### たまには"SEO"の話 アップデートの予想とか予感とか適当なこと 会社のブログで書くような内容ですが、基本的にエビデンスがないものはかけないので、こっちに雑記がてらメモります。 会社では検索エンジンの分析ツールなんかをたくさん作っているわけですが、その一つに検索エンジンを日々チェックして、差分値を抽出しグラフ化するっていうものがあります。 順位変動調査ツール こいつの裏側でいろいろなログが出てくるんですが、こいつがここ最近ある一定のアラートを出しています。 なんか大きなgoogleの仕込みがあるんじゃないか?って思っているのは、以前にも一回だけ同じようなことがあり、それがgoogleのイベントと重なったからで、なんとなくこれ怪しいね説が自分の中にあります。 とおもって、ドヤ顔でこんなことを呟いた! 完全に個人的なアレですが、ここ1,2週間の間にMFIの第一弾が・・ と予想してみる。— Yoshifumi Yamazaki (@jakkrokk) 2017年2月15日 ええ、二週間経ちますがなにもありません・・・ というかまだアラート出っぱなしなんですよね。 1年以上計測していて平常時と違うということはわかるので、何か絶対仕込んでると思うんだけどなぁ。 というかこれから大きいやつってMFIだから、それなんじゃないかなと思っているんですが。 とまあ噂話のメモでした。 ### 王佐の才 誕生日が同じ8/7の司馬遼太郎が大好きで、うちにずらっと本が並んでいるんですが、最近は司馬遼太郎が書いた小説だけじゃなくて、講演集とか、亡くなってから故人を偲んで論を語ったものとかまで手を出して読んでいます。 最近はこれを読んでいます。 リーダーを書くのが好きな司馬遼太郎は、どんなこと思って書いていたのか、みたいなことをいろんな人が語っているものです。 昔からこの手の本を読みながら自分の立ち位置とかセルフブランディングのこととかを考えていますが、司馬遼太郎と括ってあると、児玉源太郎とか、土方歳三とかのキレキレの人物が思い浮かんで非常に楽しいです。 本の中では王佐の才の話が出てきます。 王佐の才といえば荀彧ですが、君主をリーダーとかの位置付けで見てみると児玉源太郎とか、土方歳三も当てはまるのかな。いずれにせよ、司馬遼太郎の本に出てくる人たちには魅力的な王佐の才を持つ人物が多いと思います。 この本の中では豊臣秀長が挙げられていますね。この秀吉の異父弟は、正直地味というかあんまり歴史上名前がどんどん出てくる人でないように思いますが、実績というか秀吉における存在感はすごいものがあったようで、攻め、防御、調整役の全部で実績をあげています。 https://ja.wikipedia.org/wiki/ 弊社でも実力で勝ち取った確固たるNo2がいて、まさに秀長のような働きをしてる人がいるのでよくわかります。こういう人が一人いるかいないかは経営にとってとんでもないインパクトを与えるのは今も昔も変わらずといったところですね。あとのメンバーも会社で例えるなら最近の若手マネジャーは福島正則っぽいのと加藤清正っぽいので、豊臣で例えるのはなかなかハマっている気がします。 自分はというと、性格が悪く、野心も高くて、体力もしぶとさもないけど、仕事はそこそこできる みたいなめんどくさいタイプなので、秀吉の部下ではいなそうですね。 というか秀吉が上司だったらとっくに斬られてる気がします。 性格が悪いのと野心が高いところだけ黒田如水と同じなんですけどねぇ・・ ### 2017年あけましておめでとうございます 今年は1/5からの仕事でしたが、早速3連休でしたので、がっつり開発は今日からって感じになっています。 今年は10年目ということで、いろいろルール破っていこうと思っています。 このブログにしても、もともとエンジニアっぽいブログにしたいと思って、ソースコードのない記事は極力書かない方針でしたが、その禁を破りソースコードなしでも書いていこうと思っています。自分で決めたルールを破るのって難しいですが、それよりも難しいのは、「自分が決めたルールだ」って再認識することだと思います。 そういう意味で自分の身の回りのことや普段からやってることも改めて見つめ直してがんばっていこうと思います。 去年の末くらいからこんなことを考えていたんですが、ちょうど今年の会社のスローガンが「再インストール」と発表されたのでちょうどいいかなと。 ### 2016年お疲れ様でした 今年は本日26日が最終日となっています。 今年を振り返ると仕事的にはよくブログ書いたなーっていう年でした。 #ソースコードじゃないんかいとw ・会社でやってるブログ ・息子のブログ(仕事じゃないけど) ・このブログ ・某サイトに寄稿 と3つも更新+寄稿までしていて、しかも息子のブログに至っては半年で100記事以上書いています。もっともこれだけ書いていても文章力はそんなに上がらなかった気がしますね。 奥さんに無機質でつまらないと酷評されています。 エンジニアとしては、チームメンバーが倍になって、マネジメントコストが非常に増えた一年でした。会議も増え、まとめ資料とか、どうやって教育するかとか、誰に何をしてもらうかとか、そういうことを考える時間でかなり埋まるので、ソースコード書く暇ないです・・・ っていう状況が結構続いてつらかったんですが、今は色々諦めて、業務上はどんどんメンバーに書かせる形で折り合いつけられるようになってきました。エンジニアとして悔しい部分ではありますが、まあまったくかかないってわけじゃないですし。 また、今年後半から、ASOを始めて、ランダムフォレスト、word2vectorといったものも取り入れ始めてそこらへんの解析や実装なども始めています。こちらは来年会社的なアウトプットとしても使っていこうと思っていて、必死にキャッチアップしてるところです。エンジニアとしては今後こっち方面(データアナリシス)で成長していこうかなと思っています。 #その割に数学できないんですが・・・ プライベートでは、まず息子ができたってのと引っ越ししたってのが一番のイベントなんですが、それ以外だと、あまり動けなかった年です。やっぱり家関係で時間がなくなっちゃったといういいわけなんですが、なんとかかんとか折り合いつけて夏頃から大きいアプリを開発始めていて、来年1-3月中にリリースしたいと思っていたりします。 アプリでいうと、いるかは色々ありましたが、そこまで大きくはなっていません。(ぎりぎり維持くらい)ただ、DBサーバ2台ないと無理なくらいにはなっていて、サーバ関係の知見はすごくたまりました。1000万PVめざしたいと言い始めて4年目ですが、なかなか難しいですね・・・ 来年の5月でこのブログも10年目なんですよね。 節目の年ですし、このブログも少し強化しようと思っています。 月日は早いもので、もうアラフォーですが、35歳定年説なんて無視して、引き続きエンジニアとしてがんばっていきます。 それでは皆様よいお年を! ### ステータスコードチェッカーのスマホ版をリリースしました タイトル通り、ステータスコードチェッカーのスマホ版をリリースしました。 必要になったので、ちゃちゃっとユーザエージェントの設定変更しただけです。 スマホ:ステータスコードチェッカー ユーザエージェントは下記です。(iphone) 自分で言うのもなんですが、意外と便利なんですよね・・・ ### Titaniumでローディング画面をsvgで たまにはtitaniumネタでも。 相変わらずアプリは課金しているtitaniumでやっているわけですが、 ローディングのやつって毎回書いているなってきがしたので改めてメモ書き。 #基本的に ActivityIndicator ってものがあるので、普通はこっちでいいと思います。 http://docs.appcelerator.com/platform/latest/#!/api/Titanium.UI.ActivityIndicator まあ角ばったデザインとかで、デフォルトのローディングが合わない場合とかは使えないので、 svgのローディングファイルとかもってきて、それでやりたい場合の方法です。 自分は下記の感じのソースをよく書いています。 Titaniumではsvgはwebviewじゃないと表示できないので、webviewに直接svgソースをはっつけてそれを背景透過で表示するって形になります。 またAlloy使わない自作FW派なので、javascriptオンリーです。 最初にローディング画面を設定した上でopacity:0で表示しないようにしておいて、必要な時に opacityあげる感じです。 試していた感じ、作ってadd するよりこっちのほうが表示の気持ちがいい感じだったので、毎回これでやっています。 ちなみに毎回お世話になっているのは下記のサイト。 綺麗なローディング画像がsvgだけでなくcssやgifで落とせる素敵サイトです。 http://loading.io/ ### PWA(プログレッシブウェブアプリ)について書きました 書きましたっていうのは、会社のブログの方です。 https://blog.sakurasaku-labo.jp/pwa/ ちなみにサンプルも書きました。 https://blog.sakurasaku-labo.jp/sample/pwa/ 調べていてPWAは面白いなあと思ったんですが、あっちはルールがいろいろあって書けないので、ソースコード的な話と補足をこっちに書きたいと思います。 所感 触りごこちはChromeアプリ作っていた時と同じなので、service-worker周りはほとんど苦戦しませんでした。あと、プッシュ通知はandroidと同じ感じなので、こっちも触ったことある人なら、ってものになっています。 なので、androidとchromeアプリ両方触ったことある人ならすぐわかると思います。 まあテストサイトはほとんどgoogleのソースコードなんで、えらそうなこと言えませんが・・・ #JSだけでかけるので、そうじゃない人でもわかると思います。 他のブログでも書いてありましたが、既存の技術だけでできているというのは、触ったことがある人には大きなメリットですね。 自分はTitanium とかでガリガリアプリ書いている人ですが、WEBで作ってそのままの工数でいけるのなら価値は高いと感じました。 後、補足として実装周りの簡単な説明もしておきます。 アイコン / スプラッシュスクリーン アイコンはこんな感じで manifest.json に画像のパス渡すだけです。 スプラッシュスクリーンは manifest.json の background_color をベタ塗りして、中央にアイコン設置したようなものになります。 オフラインキャッシュ キャッシュの部分は service-worker の最初の方でobjectで渡すだけで、でこんな感じで簡単に設定可能です。 プッシュ通知 さすがにこっちはワンライナーでみたいな感じにはなりませんが、受け側を用意しておいて、リクエスト投げればいいというだけでそんなに複雑ではありません。 サンプルサイトでは、開いたらその場でプッシュしちゃってますのでちょっとありえない感じですが、一応ソースコードはこんな感じになっています。 まとめ わりと楽に実装できますし、見栄えというか雰囲気もいいんですが、本ブログでも書いたように safari未対応なのが痛いですね・・ 政治的にも WEBに持っていきたいgoogleと そうはさせないapple ってとこでしょうし、簡単には、なびかなそうですが、どうなんですかね。 ### ASOはじめました 今まではエンジニア兼SEOコンサルタントって感じだったのですが、ここの所ASOのシステムを作り、コンサルも手伝いという感じでがっつりASO周りに時間を割くようになっています。 ASOに関しては(ASOって言葉もあんまり好きではないんですが)まだ言葉の定義も定着していないような段階なので、なかなか何をやっていいのか難しいところです。 現状は、ひとまず「広告を打つことなくトータルでDLを増やす施策全般」という認識で、検索結果はもちろん、サジェストやスクリーンショット、タイトル、ディスクリプションなど、触れる所を全部解析しています。 幸いなことに、自分がSEOでその手の解析ツールは散々作ってきたので、ASOも同じ要領でガツガツ作れるので、データの取得はだいぶ落ち着いてきたんであとは解析をがんばる・・・って感じのフェーズです。 あと、アプリも3年前から作っていて、それなりにDLもされているので自分でテストができたり、データが取れたりできるのも大きいですね。自分がDL増やすためにやったことなども改めて体系化してみるのもなかなか面白かったです。 通常のSEOと違って、リスティングデータからの検索ボリュームがないため、キーワード探しも難しいですし、どうしても施策して、結果見てというトライアルをクライアントとともに繰り返す形にはなってしまいますが、その分やりがいもある仕事って思っています。 最近ブログ書くことなくなったんですが、その理由の一つが、ソースコードのない記事はかかないポリシー(例外あり)だったこともあり、 それをここからは破って、しばらくASO周りの分析系の話やデータのお話なども書いていこうと思います。 たまにはブログかかないとねということで。 ### crontab にゆらぎをもたせる 何を言ってるか意味が。。。 ってことで、タイトルの件について。 crontab を指定時間に実行したいわけですが、 それにゆらぎをもたせて、例えば 9時0分~9時30分の間に実行する みたいなことをめんどくさがってワンライナーでやろうとするとこうなります。 なお $RANDOM ってのがbashおよびzshってことなので、 使えない環境の方はごめんなさい。 数千台のサーバに一斉にrsyncとかさせる時に使うようです。 ### phpのmb_convert_encoding で「上」の文字が消える件 よく という書き方をするかと思いますが、今回はこれでハマりました。 ハマったのは下記のコード これを実行すると上という文字が消えます。 CSVのデータで単体で「上」と書いてあるセルをエンコードしようとした時に発生したもので、直感でエンコードだろうなとは思っていたのですが、案の定と言った所でした。 結論としては 下記で読み込めるようになるのですが、どうも気持ち悪い・・ 第3引数は前から順に処理しているようなので、SJISを先にチェックさせればうまく通るわけですが、これによって逆に別の問題とかおきるんじゃないかと心配です。 とりあえず今回はSJIS確定(EXECL)のデータだったため、これで問題ないと思いますが、別のデータを取得する際は注意が必要だと思います。 ### SEO用ツールサイトをリリースしました 弊社で社内用として開発していたSEOツールをまとめて世に送り出そう ということで、サクラサクLABOというものを企画してから早2ヶ月 やっとこさリリースまでこぎつけました。 https://www.sakurasaku-labo.jp/ うちのサイトにある共起語ツールやステータスコードチェッカーも毟り取ら(ry ではなく、惜しみなく提供して、 さらに複数ワードや複数URLまとめて出来るようにしたりと こまごま色々と変更を加えてあります。 もちろんそれ以外のツールもバンバン追加してあり、 さらに向こう数カ月分くらいの追加開発予定もあります。 特に共起語ツールのほうはヘビーユーザさんが多いので、 ラボにいけば5ワードまとめてドーンと出せますのでメリットが大きいかと思いますし 一度見てみてもらえるとうれしいです。 いつもならこういうリリースは全て自分一人でやるんですが、 今年は頼りになるチームメンバーがいますので、非常に楽出来ました。 設計も半分くらい譲ったので、開発技量と設計技量共に成長してもらえたし、 中々プロジェクトとしてはチーム的には成功したと思っています。 後は無事トラブルなく運用出来れば! ということでサクラサクLABOを宜しくお願いします。 サクラサクLABO ### 2015年お疲れ様でした 本日12/25 が弊社の最終営業日です。 ということで今年も軽く振りかえると、 大きいイベントはやっぱりエンジニアの部下が出来た事です。 会社にジョインしてから8年以上事実上1人エンジニアとしてやってきましたが、 会社の急成長に追いつかなくなる前に エンジニアを育てていこうという風潮になったのがきっかけで、 まとめて二人採用しました。 結果1人が残ってくれ立派にエンジニアリングしてくれており、 さらにもう一人も年末でジョインしたので 現在3名体制でがんばっています。 システムやインフラは大きく変更はありませんでしたが、 がっつりansibleで1年間運用してみて、すばらしいなの一言。 全く問題なく使用できています。 ※一般のウェブベンチャーと違った意味で酷使しているので ※ちょっと負荷のかけどころは違いますが。 個人としては 一昨年からの目標であった1000万PVはやっぱり未達成で終わりそうです。 広告のPVで300万/月が限界でした。 それでも個人としては十分がんばったかなという印象で、 アプリにはだいぶ自信がついたのも確かです。 また先のエントリーでも書きましたが、 出版される本にきちんと載せて頂く事が出来たのも大きいです。 あんまりウェブの域を出る事のないものなので、 出版された本が家に届くのが非常に楽しみです。 来年はチームメンバーともちろん自分を一流のエンジニアに出来るように 多方面でスキルを磨いていこうかなと思っています。 特にdocker関連と単体サービスの運営の部分で知見をためて よりフルスタックなエンジニアになれればと思います。 個人としてはやっぱりアプリで、 合間を縫ってなんとか3本はリリースしたいと思っています。 また現在運営中のilkaも継続してアップデートして 今度こそ1000万PVに到達できるように がんばっていきます。 それでは良いお年を!! ### 公開中のアプリが書籍に載ることになりました 長らく運営している匿名SNSのilkaが 来年1月に出版される本に掲載されることになりました。 わっしょい! ーーーーーーーーーーーーーーーーーーーーーーーーー 本のタイトル 「自律神経を整えるスマホアプリ100選」 出版社 技術評論社 アマゾンのURL(予約のやつ) http://www.amazon.co.jp/exec/obidos/ASIN/4774178489/machipon-22/ref=nosim ーーーーーーーーーーーーーーーーーーーーーーーーー 作ったものが本に載るのは何回かあるんですが 文章や紙面の確認とか画像の提供とか、できた本送付しますとか そういった「関係者っぽいやつ」(重要)は 初めてだったのでうきうきしています。 たぶん1/4ページくらいの大きさになるのかな?と勝手に思っていますが、 新年1発目のうれしいニュースになりそうです。 ### Ti.Paint でandroid 5.0以降strokeWidthの変更ができない件についての対応 PoiというアプリでTi.Paintをがっつり使用しているのですが、 Android 5.0以降でstrokeWidthを変更すると なにもかけなくなるという事象が発生しました。 initializeの時点ではかけているので、 setStrokeWidthがおかしいんだろうなと思っていたのですが、 いかんせんモジュール頼みだったのでおっくで後回しにしていましたが、 どうにも治らないので、初めてtitaniumでandroidモジュールをいじってみることに。 **  元のモジュールを素ビルドするだけで1週間かかりました。。 環境構築難しいですね・・・ ** さて結論ですが、 setStrokeWidthが悪さをしていました。 あまり詳しい理由はわからないのですが、 下記のソースをいじってビルドしなおせば治ります。 https://github.com/appcelerator-modules/ti.paint/blob/master/android/src/ti/modules/titanium/paint/UIPaintView.java ようは widthをfloatに直すだけでOK 引数の型はfloatのはずなのにどうしてこうなったかはわかりませんが、 とりあえずこれで動いたので良しとします。 結局環境構築1週間 バグの原因の特定と修正で30分という。。 ### Scheme.org を利用した google のリッチスニペットのテスト このエントリーはScheme.org を利用した google のリッチスニペットのテストです。 一般的なリッチスニペットで使用されているものはレーティング等で、 AggregateRating 等を利用しているわけですが、 これに子階層をネストさせて、(今回の場合例えばアプリ等) アプリのデータとレーティングのデータをまとめて記載した場合どうなるのか ※Googleがきちんと処理できるのか、書き方がこれで合っているのか等 をチェックするため下記テストでデータを挿入してみます。 挿入項目はこちら。 今回は拙作のアプリで試してみます。 http://schema.org/MobileApplication アプリ名(name):ilka 作成者(author):jakkrokk URL(url):http://ilka.jakkrokk.com/ 対象OS(operatingSystem):ios / android カテゴリ(applicationCategory):ソーシャルネットワーク http://schema.org/AggregateRating レーティング(ratingValue):4.0 レビュー数(reviewCount):604 挿入データが正しく機能すれば Serpsにレーティングとアプリの名前、作成者等が表示されるはずです。 ### Titaniumでlabelの文字間(letter-spacing)を調節する方法 タイトルの件、苦しんでる方多かったのではないでしょうか。 デザイナーさんにこれもう少し文字間空けてって気軽に言われても 対応できないむむむ。。CSSなら簡単なのに! っていう。 Attributed Stringsを使う 早速やり方なんですが、 Attributed Strings というものを使うことで実現が可能です。(4.1.2時点ではiphoneだけです) 実は3.2.0から使えるようになっていたらしいんですが、 全然知らなくて、今更知りました。 http://docs.appcelerator.com/platform/latest/#!/guide/Attributed_Strings-section-37538231_AttributedStrings-Font 書き方はこんな感じ。 var attr = Ti.UI.createAttributedString({ text: text, attributes: [ { type: Ti.UI. ATTRIBUTE_KERN, value: 5.0, range: [0,text.length] } ] }); var label = Ti.UI.createLabel({ attributedString: attr }); 注意点としては、対象のlabelにすでに色や文字列の設定がついている状態だと、 変になることがあるので、 設定する際は文字、色、フォントなどはlabel自体には設定せず、 AttributedStringでフォントや色なども一緒に設定したほうがいいです。 なので実際はこんな感じ。 var attr = Ti.UI.createAttributedString({ text: text, attributes: [ { type: Ti.UI.iOS.ATTRIBUTE_FONT, value:{fontSize:18,fontFamily:'HiraKakuProN-W3'}, range:[0,text.length], },{ type: Ti.UI.iOS.ATTRIBUTE_FOREGROUND_COLOR, value:'#ff0000', range:[0,text.length], },{ type:Ti.UI.iOS.ATTRIBUTE_KERN, value:5.0, range:[0,text.length], } ] }); var label = Ti.UI.createLabel({ attributedString: attr }); rangeの所を細かくいじれば 1文字目から10文字目まで赤くするとかもできるので、 今まで複数のlabelを使い分けていた人なんかにとっては めちゃくちゃうれしい機能だと思います。 Androidに期待 このAttribute stringsは徐々にandroidも対応してきているので、 (現在の所フォント、色、アンダーライン、リンクなど) androidでも文字間調節ができるようになることに期待です! ### Titanium Studio 改めAppcelerator Studioの有料化に伴い 前のエントリーでもしょんぼりしましたが、 改めて、メールを見逃していて永久不滅Indieの資格を失ってしまったため、 このまま使い続けるか非常に悩んでいます。  さすがに39ドルは高すぎて払えないので、 (お小遣いの1割以上はちょっと。。) リリース時のみ払って使うか、いっそrubymotion+javaに切り替えるか。。 と頭の中をぐるぐると。 手に馴染んでいるし、資産となるコードもたくさんあるので 本来は乗り換えたくないんですが、 titaniumも企業なのでしかたないとあきらめるしかないですかね。 この手のミドルウェア系は無料の時はよいですが、 方針によってはTitaniumのように有料化になることもあるわけで、 #SDKでやれってのはひとまず置いておいて そこまで含めて使う前に選定する努力は必要なんだと痛感しました。 それにしてもgithubみたく500円くらいのやつ作ってくれないだろうか。。。 arrowとかいらないんで、プロダクトリリースできるやつだけのプランを! ### サイトダウンしました Titaniumが有料になったのをすっかり忘れていて、 永久不滅indieの資格を失いました。 だいぶショックです・・・ それはそうと土日にかけてどこからともなく攻撃を受けダウンしました。 apacheのログを見た所、 http://www.google.com/bot.html に偽装しているクライアントで IP:185.11.146.125 から秒間10発くらいですが /xmlrpc.php にPOSTされていました。 さくらのvpsの一番下の今は販売されていないやつということもあり、 さすがに耐えれなくて、ブログと同じとこにあるツール系がダウン。 気づいたのはsearch consoleからの google bot can't access っていうメールでした。。 SSHもつながらなかったので、すぐに管理画面からリスタートかけて、 ログを見た所大量に同じIPからきていたという。 過去のwordpressにxmlrpc系の脆弱性があったはずなので、 それを突こうとしたのかもしれませんが、 いずれにせよなんでこんな大量にアクセスされているのか。。。 とりあえず復旧はしていますし、もう大丈夫だと思うんですが、 かれこれ10時間以上 .htaccess経由で403出しているにもかかわらず、止む気配なし。 なにかのうらみなのでしょうか。。 ### ❥¶™google 文字のテスト!❤ ❤ ✼ GoogleのSERPsに絵文字が対応したらしいです。 http://web-tan.forum.impressrd.jp/e/2015/05/08/19871/page/1 せっかくなのでテストで投稿してみます。 ここらへんからいくつかもってきてタイトルタグに埋め込んであります。 http://uda2.com/spmoji.php 成功すれば絵文字だらけになるはず! 余談ですがSerpsに変な文字を入れるのであればこんなのもあります。 Serpsで目立たせる! ### Titaniumでlocal reference table overflowが出た時の対処法 Titanium-androidで原因不明で突然落ちることがあって、 それも普段はうまくいっているのに、数が増えてくると落ちる みたいな現象を調査していたところぶち当たりました。 原因 「local reference table overflow」 よくみたらこんな文字がエラーログに載っていたので、検索してみると、どうもローカル参照がどうのこうのということでたくさんヒットします。結構昔のブログやTitanium界隈の有名なお方のツイートとかもあるので、結構メジャーなバグのようです。 #TitaniumというかAndroidの? 結論としてはどうも、下記のような感じで 現在画面に表示しているものプロパティにループで参照していると、 数が増えた時にアウトになってしまうようです。 :厳密には違うのかもしれませんが・・ for (var i in view.children) { var p = view.children[i].rect.height; } rect.height以外にもdisplayCapsなどでもダメなので、 あれはダメこれはダメと、きっとプロパティによって メモリの使い方が違うんでしょう。 対処法 これを思いつくまでハマりまくりましたが、思いつけば非常に簡単で、 直接表示しているものへの参照をはずせばいいだけなんです。 var children = view.children; for (var i in children) { var p = children[i].rect.height; } こんな感じでコピーして表示しているものを直接参照しなければうまく動きます。 なんかメモリもったいない気がしますが気にしない。 scrollviewの全子供viewの高さを知りたかった自分は 非常に苦戦しました。。 ### Titanium - androidでbackgroundColorのアニメーションがうまくいかない件 新作も変わらずTitaniumでアプリを作っておりますが、 今回はもう少しマテリアルデザインぽくしたいなーと思い、 細かいアニメーションもなるべく資料に寄せるようにしているんですが、 例によってiphoneで動くのにandroidでは動かないみたいなことが多すぎて辟易しています。。 #以下の現象はSDK 3.5.0GAにて検証しています。 一番はまっていたのが viewをタップしたら背景色を変更し、離したら背景色を元に戻すというものなんですが、 #いわゆるクリック感的なやつ これを単純にbackgroundColorの入れ替えのみ行う分にはいいのですが、 それだとなんかせわしない感じなので、少しアニメーションにしたく。 //これはOK var W = Ti.UI.createWindow(); W.open(); var view = Ti.UI.createView({height:200,width:200,top:50,left:50,backgroundColor:'#ffffff'}); view = changeColor(view,{start:'#00ffff',end:'#ffffff'}); W.add(view); function changeColor(view,CSS){ //Touch and change background color. view.addEventListener('touchstart',function(e){ e.source.backgroundColor = CSS.start; }); view.addEventListener('touchend',function(e){ e.source.animate({backgroundColor:CSS.end}); }); view.addEventListener('touchcancel',function(e){ e.source.animate({backgroundColor:CSS.end}); }); return view; }; こんな感じで、viewにtouchstartなどのリスナーを設定すればうまくいくわけですが これをカスタマイズしようとすると一気にバグり放題になります。 borderRadiusの罠 上記のviewにちょっとおしゃれにしようとか色気付いてborderRadiusを設定するといきなりバグります。 //これはダメ var view = Ti.UI.createView({height:200,width:200,top:50,left:50,backgroundColor:'#ffffff',borderRadius:10}); これを回避するにはborderRadiusを親のviewに設定してあげればOKです。 なのでこうなります。 //これはOK var parent = Ti.UI.createView({height:200,width:200,top:50,left:50,borderRadius:10}); var view = Ti.UI.createView({backgroundColor:'#ffffff'}); parent.add(view); borderColorの罠 ではborderRadiusuでなくborderColorを足してみたらどうでしょうか。 嫌な予感しかしません。 //これはNG var parent = Ti.UI.createView({height:200,width:200,top:50,left:50}); var view = Ti.UI.createView({backgroundColor:'#ffffff',borderWidth:1,borderColor:'#ff0000'}); parent.add(view); またバグりました。 同じようにborderColorも親のviewに渡しましょう //これはOK var parent = Ti.UI.createView({height:200,width:200,top:50,left:50,borderRadius:10,borderWidth:1,borderColor:'#ff0000'}); var view = Ti.UI.createView({backgroundColor:'#ffffff'}); parent.add(view); 動きました! 結論 結論、titanium-androidで背景色アニメーションをするときは、 border関係をすべて別のviewに切り出しましょう。 ちょっと古いですが、下記のサイトでも同様のバグっぽいのがでていました。 3.1.3ですが、ずっと続いているものなんですね。 http://umi-uyura.hatenablog.com/entry/2013/12/03/170517 ### MysqlでGot error 28 from storage engine が出た場合の対処 管理画面にログインできない! というアラートが上がりまして、 もう何年も使っているシステムの管理画面に確かにログイン出来ないと。 サーバが落ちているわけでもないしなんでなのかとログをあさったら Native Error: Got error 28 from storage engine] 見たことないエラーだ・・ ということで調べてみたらどうもHDDが足らないようで。 早速dfしてみると /dev/sda2 149308676 149308108 0 100% / となっていました。 適当にキャッシュ系を削除して対応完了。 DB系で見たことないエラーがでると焦りますね。 おすすめのレンタルサーバーを徹底比較! ### 広告でアプリ内レビュー誘導してみた 気づいたらilkaが10万DLしたのですが、 以前からレビューがかなり少ないことに悩んでいて、 施策前までの段階でもIos Google Play 合わせても500いかないレベル。 率にして0.5%・・ しかもこれ2年かけてなので。。 レビューが付かない原因 実は原因は明らかで、アプリ内でレビュー誘導を一度もした事がないんです。 ※よくあるゲーム終了後にポップアップで楽しかったらレビューお願いしますみたいな奴 もっともゲーム等のようにどこかのタイミングで操作が切れるタイプのアプリではないのと、 唯一それに近いタイミングはメッセージの投稿完了画面なんですが、 それも連投したい人が多い本アプリでは品質を下げるだけなので 設置したくても非常に悩ましい所。 で考えた結果レビューは広告にまぜることにしました。 広告でアプリ内レビュー誘導 用は、広告バナーの代わりによかったらレビューお願いします って感じの画像を表示させ、 飛び先アドレスを自アプリのレビューページにしただけです。 これなら普段から広告が表示されている所なので、 邪魔にもならないし、ちょこちょこ表示されてもうっとおしくないかと。 後、広告部分をタップするっていう心理的なハードルも低くなるかなーと(こっちは希望的観測) 結果 実装1.5日程でiosで7件、androidで15件増えました! ※iosはちょっと反映が遅いのでもっと増えるかも。 その間のDLは大体300前後です。 元からのユーザが投稿してくれた可能性が高いので、 有効な手段なのかわかりませんが、 やっぱりアプリ内でレビュー誘導する事は重要だと感じました。 特にツール系を作っていて、レビュー誘導のタイミングがあまりないアプリは 広告枠を使って、自分に自分でお金をちょっと払う感じでやるのも ありだなと感じました。 ### Excelで形式を選択して貼り付け>値 のショートカット どうでもいいことなんですが、 Excel使っていると 「右クリック>形式を選択して貼り付け>値」を良く使いませんか? 一時期結構探したんですが出てくるのがVBAで書いてあるやつばっかで かっこわるくて手でやってたんですが、 久しぶりに調べたらいい方法があったので。 やり方 ここに書いてありますんで! Excel: ショートカットで値として貼り付ける ようはそのままではショートカットキー設定とかはできませんが、 クイックアクセスに登録してしまって、 クイックアクセスのショートカットキーを使えばOKってことですね! これでALT+1でいつでも値張り出来ます! 幸せExeclライフ! ### バリュードメインのDDOSによる障害と今後の対策 昨日2/3 のバリュードメインのDDOS攻撃による障害の件 Twitterみてると影響有った方多いんじゃないんでしょうか。 ●バリュードメイン障害情報 昨年12月にも同じように日中攻撃がありその時もアナウンスが出たのですが、 その際DNSを新しいほうに切り替える対応をしていたおかげで 今回の攻撃での弊社へのドメインへの影響は最小限におさえられました。 バリュードメインはあんまり大々的に書かないので 多少なりとも拡散するつもりでエントリーにしておきます。 結論からいうと バリュードメインが用意している別のDNSに変えましょう。 ※下記に理由を書きますが、これで今回はOKでした。 ●切り替え方法 バリュードメインお知らせ 上記URLの 2013.05.31 の所に 「専用ネームサーバー(ns11-13.value-domain.com)を新設しました。」 とあります。 実はこれ、バリュードメインで取得したドメインなら誰でも使用でき、 ネームサーバの管理画面にもこっそり切り替え用のリンクが追記されています。 ※一部ダメな物もあるようです。.jpとかはダメ? 今回はこれに切り替えるやり方になります。 ※契約時デフォルトは ns1-5系です。 ※これを ns11-13系に切り替えることになります。 切り替え方法は簡単で、ドメイン一覧から ns をクリックし、 ネームサーバの変更画面から下記の赤いとこをクリックして保存するだけです。 DDOS原因 バリュードメインのツイッターにありましたが、 調査中でございますが、某政府に対する不利益なサイトを攻撃によって停止させる目的であると断定しております。言論・表現の自由を攻撃的な手法によって強引に封じ込める方法には断固反対します。ご利用の皆様にはご迷惑をお掛けし、大変申し訳ございません。— バリュードメイン (@VALUEDOMAIN) 2015, 2月 3 どうも使用しているどっかのサイト単体がDDOSの標的になっていたようです。 このサイトが使用しているDNSの設定がデフォルト(ns1-5)なので ns1-5が重くなるんじゃないかなと想定しています。 ※そもそもデフォルトなので大量にドメインが乗っているというのもあると思います ※Valudomainに問い合わせたところDNS水責め攻撃だそうです。 DNS水責め攻撃 まとめ 原因がアレのため、DNSをns11-13に切り替えたとしても落ちる場合があります。 ※上記の標的サイトが同じようにDNS切り替えを行った場合、 ※結局落ちてしまう可能性はあります。 しかしながら、一先ず一手間かけて デフォルトでないDNS=空いているDNSに切り替えることで バリュードメインにおける今後のDDOS対策には一応なると思います。 ※1-5と比べるとめちゃくちゃ空いているとのことでした。 ### お絵かきメールアプリをリリースしました。 年明け1発目はアプリのお話でも。 半年以上かけて作ってきたアプリがやっとリリースできました。 今回リリースしたアプリは「お絵かきメール - POI(ぽい)」 というものです。(海外版:Drawing Mail - POI) Itunes Google Play お絵描きメールのpoi 中身はアプリ名の通り、指ででお絵かきや落書きをして それをそのままメッセージとして送るアプリです。 画像を読み込んで追記したり、ちゃんと絵を描いたり ただの落書きをしたり、まあ色々と遊べると思います。 今回も全部Titaniumで作りましたが、中々に大変だったのがpush通知で、 特にandroidさんは言う事を聞くようになるまでに1週間以上かかりました。。 逆によかったのはituneのレビューは一発で通った事。 しかも初回リリースなのに1週間でinreviewからの30分でリリース。 複雑なアプリだけに5回くらいは覚悟していたんですが、あっさり通ってびっくりでした。 今年はこれを丁寧に育てていければと思っていますので 皆さん是非遊んでみてください! 紹介して頂けました! iPhoroid様 iPadiPhoneWire様 Kids-app.com様 お絵かきメール - POIがApplivに掲載されました!(iOS版) お絵かきメール - POIがApplivに掲載されました!(Android版) アプリヴ大辞典様 ありがとうございます! ※紹介サイト様を見つけ次第随時更新します ### 2014年お疲れ様でした 今年も本日12/26が最終営業日です。 今年は私生活が大変動して、家庭を持ったということもあって、 なんとなくfacebookの一年を振り返ろうはそればかりでしたが、 技術的にも色々と大きな変化があった一年でした。 なによりも大きかったのが弊社環境のインフラ関連を刷新したことで、 それに伴ってansibleだったりシェルを触っている時間が極端に増えました。 特にansibleは無かったらと思うとぞっとします。 またインフラの延長でhadoopや形態素解析の実装行う機会があり そちらの知識は来年一層重要になってくると思っています。 インフラ以外ではアプリを3本作れたのが大きかったです。 土日や家に帰ってから奥さんのいない時間等をうまく使って アプリをリリースできたことは自信になっていますし、 titaniumもだいぶ使いこなせてきてモジュールを作ってみたり android/iosをほぼ1ソースで作るコツも習得できたので、 非常に実りのある一年でした。 アウトプットとしてはアプリがほとんどで、 10万DLしたのが一本で、残りは1万いかない感じですが、 それでも使ってもらえるアプリが一つでも作れたことはうれしいです。 公開するようなWEBサービスは1つも作りませんでした。 これも世の習いってことでしょうか。 去年の目標であった1000万PVは未達成で終わりそうです。 3分の1くらいしかいかなかったのでまったく未達で残念なんですが、 現在製作中のアプリで来年こそは1000万の大台を目指したいと思います。 サーバ関連もだいぶ知識が増えてきて 35歳になりましたが、まだまだ勉強する余地はあるんだなと思います。 来年は年明け早々現在着手中のアプリをリリースして、 それを丁寧に運用していく形で、本数よりも1本を丁寧にやりたいと思っています。 それではまた来年! ### titaniumでモジュールを使ってadmobを表示 Titaniumの情報も古いのばっかりになってきましたね。。 1ソースコードでios,android同時対応にはコツが必要で結構難しいっていうのが見えてきて、 だったら~ みたいに思われているのかもしれません。 ※後、時代はunityっしょ!みたいな流れもあるかも。 自分は少しコツがつかめて、難しいアプリ作ってないってのもありますが、 だいぶ楽に同時展開できているので、まだまだtitaniumでがんばります。 で、タイトルの件ですが、 ずっとアプリはnendとi-mobileをwebviewで自分で配分決めてやっていたので、 admobは使った事なかったんですが、海外にアプリ公開するにあたって 必要だったので導入したところ、後から見ると一瞬で片付く問題で3日程ハマったので、 それのメモです。 ●admobのSDK的な奴 https://github.com/appcelerator/titanium_modules/tree/master/admob/mobile ・android https://github.com/appcelerator/titanium_modules/blob/master/admob/mobile/android/dist/ti.admob-android-2.1.1.zip?raw=true ・ios https://github.com/appcelerator/titanium_modules/blob/master/admob/mobile/ios/ti.admob-iphone-1.6.0.zip?raw=true ここにappceralator公式のモジュールがあるので、 それをDLして組み込みます。 ※titaniumのモジュール導入方法 >> Titaniumのmodule追加方法 それで後はこんなかんじで、viewを追加してfireEventすればいいんですが、ここでハマります。 Ti.App.addEventListener('showAds',function(){ var admob = require('ti.admob'); ad = admob.createView({bottom:0,height:50,adUnitId:'xxxxxxxx'}); window.add(ads); }); このままiphoneでテストするとうまく表示されるんですが、 androidにもっていくとクラッシュ! これでずっとはまっていました。 でどうすればいいかというと実は答えは簡単で、 //ios ad = admob.createView({bottom:0,height:50,adUnitId:'xxxxxxxx'}); //android ad = admob.createView({bottom:0,height:50,publisherId:'xxxxxxxx'}); とするだけです。 なぜかパラメータ名が違うんです。。 こういう小さい所がちょっとづつ違うのがtitaniumのイケてないとこなんですよね。 なんかphpみたいですね。 ### DeploygateでTitaniumで作ったandroidアプリがうまくインストールできない件 アプリのテストするならdeploygateでしょうーってことで iphoneでは使っていたんですが、 titaniumで作ったアプリがなぜかandroidだと動かない。。 というかgoogle playでもアップデートは出来てもそこからダウンロードの際に失敗する・・・ なんだこれは! ってことで調査を致しました。 原因 「SHA256withRSA がダメで、 SHA1withRSAでないとダメ」ってやつで、 普通にtitanium studioでパブリッシュしちゃうとSHA256withRSAでできちゃうので google play経由でインストールできない === deploygateでもできない ってことでした。 なので、これさえ解決すればgoogle playでもdeploygateでもうまくいく! 対処方法 対処は簡単で、 一旦普通にパブリッシュした後、 コマンドラインから 1: "【jarsignerへのパス】\jarsigner.exe" "-sigalg" "SHA1withRSA" "-digestalg" "SHA1" "-keystore" "【あなたのkeystoreへのパス】.pem" "-storepass" "キーストアのパスワード" "-signedjar" "さっきパブリッシュしたapk" "さっきパブリッシュしたapk" "keystoreのalias" 2: 【android-sdkへのパス】\tools\zipalign -v 4 "さっきパブリッシュしたapk" "作成するAPKファイルの名前" を順番に叩くだけです。 2を叩いてできるAPKでならdeploygate も google play でも通ります。 それにしても ググってもこれ余り出てこないんだけどなんでなんでしょうか。 特定のバージョンのみ発生するわけじゃないのに 結構クリティカルにめんどくさいと思うんですが詰まってる人少ないのかな。 参考にしたサイト Titanium Mobileで公開用Androidアプリを作成する手順まとめ Titanium Studio/SDK/CLI 3.2.0 で「パッケージファイルに正しく署名されていません」 ### ansibleでさくらvpsを設定しようのお話 故あってVPSをたくさん設定することになったので、 これは今流行りのchefとかpuppetとかでしょうとか思ったものの調べた結果 ansibleがベストだと思ったので、そのお話です。   ●要件 ・SSHが最初から起動してるVPSに一度もログインせずに全部まとめて設定したい ・できればめんどくさい勉強はしたくない   ●chef でなく ansible 調べた感じ巷では例えば chef とか puppet とかは情報が多かったんですが、 そもそもクライアントをインスコしないといけないという時点でアウトでした。 あれはキッティングするレベルの人達が最初から入れておこうねとかそういうやつであって、 借りてきたものをあーだこーだするレベルの俺みたいなエンジニアには不要でした。 (自分はどうせ httpd,sshd,squidとcronの設定くらいでよかったってのもありますが。) 監視はポーリングレベルなら自分ですればいいし、snmp使うならちゃんとツール入れたほうがいいかと。 ってことで、 クライアントがいらない、yamlで書ける、ansibleを選択しました。   ●インストール 基本的なインストールは yum でポイポイしてしまったので省略 sudo yum -y install ansible ansible -v が通ったらもう後は動かすだけです。   ●設定 ベストプラクティスとかありますが、参考にしつつちょっといじってます。 ・基本的な考え SEDとか使ってコンフィグ書き換えてるサンプルが多いですが、 サーバごとに違うもの以外の例えば、httpd.conf とか sshd_configとかは 正のファイルをsv1において、それをコピー配布してます。 どうしても書き換えが必要なネットワーク系とかのみSEDしてます。 (それすらも正のファイルをローカルにおいちゃう事も可能ですが。) 一応ここからはこんなイメージで進めます。 ・ansibleを動かすサーバ: server ・vpsのIP: 11.22.33.44 ・VPSはsshでログイン可能状態 設定する項目は ・apacheのインストールとhttpd.confの上書きと起動 ・任意のシェル実行 の2つ   server側の作業フォルダの構成 ansible/ ├── ansible.cfg ←設定ファイル ├── init.yml ←実行ファイル ├── hosts/ | ├── hosts ←vps1のIP書くとこ | ├── templates/ | ├── httpd.conf ←完璧にしておいたコンフィグファイル | ├── echo.sh ←実行したいシェル | ├── roles/ ├── httpd/ | ├── tasks/ | ├── main.yml ←init.ymlから呼び出して実行する ├── shells/ ├── tasks/ ├── main.yml ←init.ymlから呼び出して実行する 後はもう書き書きするだけです。     ./ansible.cfg [defaults] ansible_ssh_args="-o ForwardAgent=yes" host_key_checking=false remote_tmp=/tmp   ./init.yml - hosts: vps roles: - httpd - shells   ./hosts/hosts [vps] 11.22.33.44 ansible_connection=ssh ansible_ssh_user=ユーザ名 ansible_ssh_pass=パスワード ansible_ssh_port=ポート番号   ./roles/httpd/tasks/main.yml - name: Install httpd yum: name: httpd state: installed - name: Copy HTTPD Setting copy: src: templates/httpd.conf dest: /etc/httpd/conf/httpd.conf owner: root group: root mode: 0644 - name: Install php yum: name={{ item }} state=present with_items: - php - php-mysql - php-mbstring - php-gd - name: Restart httpd service: name=httpd state=restarted - name: Enable httpd service: name=httpd state=running enabled=yes さくらのvpsは最初ルートでSSHが通るので、 その設定でつなげるようにしています。 SSHを変える際はhttpdのやつを改良すればいいと思いますが、 その際ポートとかrootログインとか変更すると 当たり前ですがこの設定ではログインできなくなります。 また、init.ymlで sshd,httpd,みたいな順番にしちゃうと、 taskごとにsshコネクションを張りなおしているみたいで sshdで変更してhttpdの際にコネクションが張れない みたいな事になりますので、sshd変更するのなら最後にしたほうがいいと思います。 ### アプリUI変更に対するユーザの反応について考える 巷でよく言われていることですが、 割と長く使ったUIを変更すると盛大に炎上します。 というかしました。 自戒とどうするべきかを検討する脳味噌の中をメモがてらまとめます。 状況 リリース1年、DL数5万程度のアプリ。 スタート当初から使っていたUIはデザインセンスのない自分が一人で考えて作ったので アイコンもなく、薄い青に白字とかで見にくい、フォントも小さい、 投稿フォームもずれていたり等など おそらくプロのUI作成従事者からすればゴミクズのようなUIだったので がんばってもうすこしメインユーザである女性的なデザインに変更しようと考えました。 そこでランサーズを使ってそこらへんをメインに色々とお願いして、 新デザインを作りました。 (新デザインを作ってくれたランサーさんは  すばらしい仕事をしてくれたと思っています。) UI変更 UI改変した要素は 色合いを白と青オンリーから小豆色、クリーム色等のやさしい色にし、 青字に白等見にくい色合わせを極力なくすように変更 ⇒薄い青バックに白い文字とか確かに見にくかった ボックスの大きさを少し大きくし、文字のフォントを0.5上げて見やすく ⇒元々文字を読むアプリなのにフォントが小さすぎた ○投稿文字数を表示し、文字数制限をわかりやすく ⇒文字数オーバーで投稿できないものを解決しただけ ○ほぼ使われていなかったカテゴリ機能を廃止 (98%のユーザが利用していない) 逆に改変してない所 最大投稿文字数 1画面に表示する投稿の数(iphoneのみ) 投稿ボタンや閲覧ボタン等のメインのボタンの配置 ようは、 見た目はめちゃくちゃ変えたけど、機能はあんまり変わってない という感じです。 結果 ○今までのレビュー ★4,5が150/200 ○改変後 ★1が9割 ★5なし oh... 実際のレビューも 色合いが嫌とか○○機能はいらない、 文字数制限が邪魔とか、文字が見にくいとか投稿をぱっとみれなくなったとか カテゴリはあったほうがいいというのもたくさんあったりして、 なかなか心が痛くて折れそうに。 しかもレビューだけでなくSNSの投稿としてバシバシ飛んできてorz状態。 変更されていない部分とかまで書かれていて、 どういうことなのか理解するのにすごく時間がかかりました。 反省点と今後 機能を削ったり追加したり、さらにUIそのものも変えたり とかなりドラスティックにやったことがいけなかったのかもしれません。 最初の半年くらいは確かに機能の追加と削除のみを行っていたので、 クレーム祭りにはならなかったというのもあります。 機能を削ったのはアプリの方針があるため戻すことはないのですが、 デザインに関しては元の形に似せるようにするか考えています。 (実装機能は残すのでロールバックにならないのもつらいとこ) ただ戻したら戻したで混乱するだろうし、悩ましい。 しかも戻すことに意味が有るのかどうかもわからないので勉強中です。 まとめ ・ユーザにとってはUI変更そのものがストレス ⇒色々と一気に変えると変わっていない部分も変わったと 錯覚してより大きい変更に見える ・暗黙的なものをあえて明示的にするのはリスク ⇒今まで文字数制限ないと思っていたが、 文字カウントをするようになったらそれがストレスの引き金になった ・ファーストリリース時のUIが一番重要 ⇒UIを触った瞬間にイメージが固定化する=ファーストリリースが最強説 ・UI変更は小出しにするべき ⇒漸進的アプローチ おまけ 一応何を考えて変更したのかという点について、 書くところもないので最後にしれっと。 ・カテゴリ機能を削ったことについて 元々閲覧する人にとってカテゴリ制限しておけば不快なものが目に入らないだろう (特にアダルト) という観点で作ったのですが、 このアプリの目標は投稿を自己解析して自分と近しい人の投稿のみ表示する ということ(まだほとんど出来ていませんが) そもそもカテゴリを使っている人がほとんどいないので、カテゴライズする意味がない という観点から、機能が大きなウェイトを占める前に削ってしまおうという判断をしました。 ・色々な通知の削除 元々画面を開くと○○件共感されました みたいなメッセージがでていたのですが、 それを削りました。 そもそもこのアプリは、時間や人自分の投稿そのもの等、 通常のSNSで気にする全てからしばられないで使ってほしかったのですが、 ○○件共感されたやったー みたいな投稿も結構あって、 ようはこれではほかのSNSとかわらんじゃん?っていう思いでいたので 思い切って消しました。 最後に 余談ですが、俺が適当に作ったUIをほめられ、 お金を払ってデザインしたものが一時でも酷評されるのは複雑ですね・・・ ### 2013年お疲れ様でした 弊社は本日が仕事納めなので、 ブログ的にも2013年納めになります。 毎年Google履歴見てるんですが、 去年のどこからか家でも同じアカウント使うようになって (chromeの履歴とか共有できるっていうのもあって) そのせいでだいぶカオスになってしまったのでもうやりません。 今年は仕事的にも私生活的にも大変動があったわけですが、 技術的な意味では逆に落ち着いた感じで、 ある程度自分的には枯れてきたなと思う所もあって あまりアクティブに次の技術を追い求めるような感じではなかったかもしれません。 力を入れたのは去年から引き続きアプリ関係がメインだったですね。 TitaniumとPHPである一定のクオリティのものが思った速度で形にできる という所ではだいぶ安定して信頼ももらえるレベルになってきたと思います。 今年技術的に一番よかったなと思ったのは、 人生で初めて作ったアプリから万単位のお金を手にできたことです。 今までは行っても月数千円レベルで、 まあサーバ費用の元取れたねよかったねという感じで、 次のアクションを取るモチベーションが 中々上がってこないレベルのものだったんですが、 今年初めて全体でみれば小さいとはいえ 自分なりにヒットしたアプリを出せたというのは結構自信につながったと思います。 まあビジネスとしてみたらゴミですけど、 個人として100万PVオーバーの何かが作れたって率直にうれしいです。 来年はアプリの総PVを月間1000万くらいまでもっていきたいなと思っています。 そうすればそこそこ自慢できるものになるかなと。 まあこれは食べていくというよりも 技術者として一つまとまったアウトプットが出したいというほうの欲になります。 来年からは求められる業務の中で技術の占める割合がどんどん減ってく予定なので、 そろそろまとまったアウトプットが出したいんですよね。 もうこの業界入って7年になります。 このブログも来年7年になります。 やっぱプロダクトの名前を覚えてもらって アレを作った人 と言われたい夢はかなえたいと思っています。 ということで、今年1年お疲れ様でした。 当ブログをよく見に来て下さる方の来年が素晴らしいものになりますように! ### Titaniumでiphone/android両方同時に作るコツ 新しいアプリをリリースしました。 覚えやすく比較的クラックしにくいであろうパスワードを生成してくれるアプリなんですが、 画面が2つだけと簡単なこともあって、一発で何とかできないかなと思って 今までいくつか作ってきたTitaniumの経験値を元にトライしてみました。 ○Androidをベースにする ・とにかくTitaniumのAndroidは癖があります。なんともいえないんですが、 思うように動かないことが多いです。 なので、iphoneをとりあえず作ってそっから色々と考える。。。 みたいなことしてる人多いと思います。 逆にiphoneのほうは思ったような動きをしてくれるので、 androidで作ってiphoneで手直しする っていう形にしてみました。 ○使っちゃいけないやつ Ti.UI.iPhone みたいなアレとか、複雑なアニメーションとかでしょうか。 特に複雑なアニメーションはどれだけ android 実機で再現できてるのか謎すぎます。 なので、なるだけ簡単なフェードアウトとかスライドインとかそういうのだけで構成するようにしました。 画面サイズとかはなるだけ Ti.platform.displayCaps.displayWidth / height を使ってます。 ○tabgroup 使わない tabgroupって楽だし便利なんですが、androidとiphoneで見え方も違うしandroidだと挙動が変なので、 あえて使っていません。全部 window に直でビューを add/remove して画面を構成しています。 こんなことまでしないといけないとかtitanium使う意味が。。 とか言わないでください。 ○パスの書き方に注意 androidとiphoneでパスの処理の仕方が違います。 なので、 require('hoge/hage').**() みたいなのは require('/hoge/hage').**() 頭にスラッシュを入れて、 フォルダの中に全部まとめて書いています。 ちなみにフォルダ構成はこんな感じです。 Resources android settings controllers includes styles app.js で各データには require('/settings/controllers/test').execute() みたいな感じです。 こうかけばiphone側でもそのまま動きます。 ○個別でOSで切り替えは書かないようにする 基本CSS的なのは下のサンプルっぽく1番最初に書いて終わりにしてます。 createAlertDialogにtextfieldを追加する みたいなOSごとに処理が違うやつの場合は、 完全に自作しちゃってます。(ビューに貼りつけてフェードインするだけだと思えば簡単です。) (ちなみにjssは使っていません。) var osName = 'android'; if (Ti.Platform.osname == "android") { if (Ti.Platform.displayCaps.platformWidth > 899 || Ti.Platform.displayCaps.platformHeight >= 899) { osName = 'tablet'; } } else if ((Ti.Platform.osname == "iphone")||(Ti.Platform.osname == "ipad")) { osName = Ti.Platform.osname; } var css = require('/settings/styles/' + osName + '/main').css; ○結論 簡単なのアプリで土日で完成しましたが、iphone側の修正はマージンの調整くらいでほぼ手入れなしでリリースできました。 そういう意味ではかなり楽でしたが、 その代わりtextfieldつきのalertdialogが使えないとか(パスワード入力画面とかで。。) そういう制限もあるのがネックですね。 複雑なアプリをtitaniumで作るということでなければ一考の価値はあるとは思います。 ### 1万ダウンロード達成したTitanium製アプリの状況 ちょこちょことアップデートをかけて、 今月やっと合計で1万ダウンロード達成しました。 使って頂いている方に本当に感謝。 iphoneのほうが1万DLに対して、androidはまだ500もいってないです。 同じ仕掛けをしても、iphoneのほうが勝手に露出してくれる割合が大きいのかな? 以下、いくつかメモしていたデータを参考までに開示してみます。 ●1万DLまでにかかった時間 5/30に1.0.0をリリースしたので 正味4カ月。長かった。。。 もっとも9月までプロモーションとか何もやっていないので、そのせいもあると思います。 ●レビュー依頼 有名どころは全部送ってみましたが、 採用してくれたのはほんのすこしでした。 appbankさんの効果が見たかったんですが、不採用でした。。 逆に下のサイトはすぐ対応してくれました。感謝です! iPhone 研究室様 アプリヴ様 App Library様 それから依頼してないのに紹介していただけました! It Mama様 インファクトスタッフブログ様 追加で紹介していただけました! @jakkrokk 窓の杜編集部の長谷川と申します。貴アプリ「ilka」を窓の杜で紹介させていただきましたのでご報告いたします。 http://t.co/ncawwr2nOY ありがとうございました。— 窓の杜 (@madonomori) October 16, 2013 さらに紹介して頂けたサイト様 smapli(スマプリ)様 ●appstoreでのユーザのレビュー率 自分はレビューお願いします みたいなやつはうざいと思っているので、 あえて表示させていません。 そのためか1万DLにしてコメント50以下。。 率にすると0.5%といったところです。少なすぎますね。 やっぱり基本的に導線がないとあまりレビューはしてくれないようです。 ●ユーザレビューの内容 初期のやつ以外は致命的なバグはなかったためか、 的外れのものもほとんどなく、☆3~5に収まっています。 イニシャライズ中に回線がきれちゃうと、クライアント側での処理だけ走ってしまい、 そのせいで起こるバグとかはレビューできづいたりしたので、 マメに見て対応するのがいいと思います。 ちなみにandroid版は直したら☆もちゃんと戻してくれてありがとうのコメントもつきました。 ●ランキング iosのみですが、ソーシャルネットワークのカテゴリのランキングで300位くらいで 毎日40~70くらいのDLでした。 adwordsをちょっとしかけてみたら(CPC1円ww) ほんのすこしだけDLされるようになって、 そのタイミングでアプリをアップデートしたら一気にランキング100位まで上がりました。 100位まであがると一気に毎日150~200DLされるようになり、 今は安定して80-100の間を維持できています。 ●1日のユニークユーザ数 1000~くらいです。 1万DLで毎日1000以上ユニークユーザがいるって結構いいんではないでしょうか。 ちなみにPVにすると50~60万ほど。 もっとも広告の有効インプレッションは20%以下ですが。。 そんなかんじで引き続き宜しくお願いします。 本音SNSアプリ -ilka - ### Android版 独り言アプリをリリースしました 以前iphoneで出した ilkaという完全匿名の独り言アプリのAndroid版をリリースしました。 元々出すつもりで、Titaniumで作っていたのですが、 iphoneは何個か作っていたもののTitaniumでのandroidアプリは一つも作っていなかったので、 正直なめていた としかいいようがない結果に。。 iphone版はほとんど全てのUIをiphone側で処理し、 サーバからはjsonをgzipして送信するようにしていたのですが、 まず、android版はgzipで転送してもうまく受信できない。。 さらに、アニメーションがいちいちカックカクで、iphoneだとこの倍は処理できるような所も 余裕で処理が多すぎるから落とします 的なエラーで落ちてしまう。 そんなわけで、iphone版をそのまま持ってきて書き換えていくのは時間がかかり過ぎると踏んで、 android版は別に作ることにしました。 →この時点でなんでtitanium使ってるのって話ですが。。 で、元々いつかwebviewでレイアウトを処理するのもやってみたかったってのもあったので、 それで再構築を開始。 処理の大半をhtml+css+jqueryで書いて、 それをfireeventでアプリに渡して実行するっていうスタイルにしてみました。 おかげで、iphone版とまったく違うデザインになってしまい、 もう別にアプリになってしまっています。 そんなわけで、よかったらtitaniumで作った力作を見てやってください。 独り言アプリのilka ### iphoneアプリのレビュー(審査)の時間 6月にリリースしたツイッターライクな匿名アプリが思いのほか好評だったので 継続的にアップデートしている状態です。 一つのアプリでこんなに更新しているのは初めてなので、 審査の時間とかをまとめておけば何かの役に立つかと思うので、 タイムライン的なものをまとめておきます。 ○アプリのカテゴリ 1.ソーシャルネットワーキング 2.エンターテイメント ○デバイス ユニバーサル -------------------------------------------------------------- 05/02: v1.0.0アップロード 05/08: リジェクト 05/10: v1.0.0アップロード 05/14: リジェクト、即日再アップロード 05/17: リジェクト 05/20: v1.0.0アップロード 05/22: リジェクト 05/28: v1.0.0アップロード 06/01: インレビュー > v1.0.0リリース 06/09: v1.0.1アップロード 06/12: v1.0.1デベロッパーリジェクト、即日再アップロード 06/19: インレビュー > v1.0.1リリース、即日v1.1.0アップロード 06/21: インレビュー > v1.1.0リリース、即日v1.2.0アップロード 06/22: v1.2.0デベロッパーリジェクト 06/25: v1.2.0アップロード 07/01: インレビュー > v1.2.0リリース、即日v1.2.1アップロード 07/04: インレビュー > v1.2.1リリース、即日v1.3.0アップロード 07/12: インレビュー > v1.3.0リリース、即日v1.4.0アップロード 07/19: インレビュー > v1.4.0リリース、即日v2.0.0アップロード 07/24: インレビュー > v2.0.0リリース、即日v2.0.1アップロード -------------------------------------------------------------- 他のアプリだと、7月が遅い事が多いらしいですが、 このアプリに関しては毎回即日アップロードしてるせいか、 7月も劇的に遅い感じはしていません。 このアプリの平均的なレビュー日数は デベロッパーリジェクトを差し引くと平均4.75日といったところです。 最大8日、最小で2日。早いほうなんじゃないでしょうか。 初回の05/02のアップロードの時も6日でレビューが始まっているので、 初回のアップから早めだったのかもしれません。(普通2週間かかるといいますし) まあただのログですが、何かの参考になれば。 ### TitaniumでEULA関連で落とされた時の回避方法とか TitaniumでEULA関連で落とされた時の回避方法とか 前のエントリーでコメントに詳細がほしい的なことを頂いたので、 少しまとめてみます。 前のエントリーでは ・不適切な投稿の報告のボタンと仕組みを作成 ・アプリの初回起動時にEULAを表示して、それに同意しないとアプリが使えなくする ・各アプリに個別にIDを自動作成しそれを元に不適切な投稿をしたユーザを弾く仕組み とまとめたのですが、これで一先ずレビューも通ったので、 これに沿って書いていきます。 ○不適切な投稿の報告のボタンと仕組みを作成 これは単純に投稿を表示するアプリの場合、投稿ごとにボタンを用意するだけです。 ボタンを押した際に投稿が消えて、その投稿が再出しないようにするだけなので、 サーバ側でフラグを立てるボタンを用意する ってイメージです。 ○アプリの初回起動時にEULAを表示して、それに同意しないとアプリが使えなくする 初回にEULAを表示させるのは自分はテキストファイルを作ってしまいました。 同意ボタンが押されたらAGREEDとテキストファイルに書きこんでおいて、 それを元に続きの処理をする感じです。 ファイルがない場合 or 同意しないボタンを押された場合は続きの処理をせず、 エラーメッセージだして終わりとか。 コードはこんな感じです。 ○各アプリに個別にIDを自動作成しそれを元に不適切な投稿をしたユーザを弾く仕組み 最後にアプリからのアクセス自体を弾く仕組みですが、 これはレビュワーからはわからないものなので、実装する必要があったのかはよくわかりません。 ただ、最終的に必要な機能だったので自分は実装しました。 アカウント自体はユニークになる乱数で自動生成しているので、 それを投稿の度に送信して、サーバ側でチェックするようにしています。 ○まとめ ざっくり書きましたが、Titaniumで必要な作業はEULAの表示くらいで、 サーバ側での処理のほうが多いと思います ちなみに実装済みのアプリはこれです(宣伝) 匿名ツイッターライクアプリ ### ツイッターライクなアプリを公開しました 以前書いたアプリリジェクトの話の結果、無事レビューが通ったので、 先日リリースしました。 App Store サポートサイト ツイッターライクな投稿アプリですが、 フォローとか被フォローとかメンションとかその手の類を全部排除しました。 イメージ的には2chライクなツイッターってかんじなのかな。 とにかくただ投稿するのと見るのといいねボタン的なもの 2ch+いいね じゃないですけど そんなイメージのアプリにしたくて、作ってみました。 自分の作ったアプリの中では珍しくダウンロードが伸びているので、 よかったらインストールしてみてください。 ### Apple のアプリリジェクトのお話 現在製作中のアプリはツイッターライクなやつなんですが、 CGMというかユーザが投稿できるタイプのアプリにはEULAとか削除のポリシーとか 結構色々と要求されます。 で高確率で来るのがこのメッセージ - Use Moderators to flag and remove inappropriate content - Require that your users agree to terms (EULA) and these terms must be clear that there's no tolerance for objectionable content - Users need a way to flag or report objectionable content and/or users generating this content - Developer must act on objectionable content reports within 24 hours by removing the content and ejecting the user who provided the offending content - Developer needs a method for ejecting users who violate the terms of the EULA これを全部クリアしていないと審査は絶対に通らないので、 それの対応まとめです。 - Use Moderators to flag and remove inappropriate content - Users need a way to flag or report objectionable content and/or users generating this content ようは不適切な投稿に対してフラグをたて、削除する仕組みを作る必要があります。 具体的には投稿表示画面とかに「不適切な投稿の報告」的なよくあるアレを仕込む必要があります。 もちろん立てたフラグが本当に不適切な投稿な場合は削除する仕組みも必要です。 - Require that your users agree to terms (EULA) and these terms must be clear that there's no tolerance for objectionable content 投稿する前にEULA(使用許諾的な同意書)が必要で、不適切な投稿が一時的にでも表示されてしまうことを明記する必要があります。 - Developer must act on objectionable content reports within 24 hours by removing the content and ejecting the user who provided the offending content 不適切な投稿は報告されてから24時間以内に削除する必要があります。 また、不適切な投稿をしたユーザがアプリを利用できなくする必要もあります。 - Developer needs a method for ejecting users who violate the terms of the EULA EULAに同意しない人を弾く仕組みを作る必要があります。 ということで、自分がやったのは ・不適切な投稿の報告のボタンと仕組みを作成 ・アプリの初回起動時にEULAを表示して、それに同意しないとアプリが使えなくする ・各アプリに個別にIDを自動作成しそれを元に不適切な投稿をしたユーザを弾く仕組み の3つを実装しました。 これで通るといいな~と思って今書いています。 (まだ通っていません。) ちなみに、Resolution Center でこれが返ってきた時は これとこれはやってますが、何がダメですか?って聞けば 具体的にどの部分がダメなのかちゃんと教えてくれますので、 上記のメッセージが返ってきた時は一度質問してみるのをお勧めします。 #2013/6/3追記 上記の修正をやったところ無事レビュー通りました。 ### .in ドメインの問題とツールの移行 .in ドメインのリスクについては以下のページが詳しいですが、 Geekなページ 自分の管理している refeed.in も思いっきりこれに該当する形だったので、 まだ稼働中でしたが、中に入っているコンテンツをこちらのブログ配下に移してきました。 もっともツール専用のドメインとして取得したものの、 APIの死亡等に伴ってどんどんツールが減っていき、 今稼働しているのは一つしかなかったので、楽でしたが。 唯一稼働しているツールが共起語チェッカーでして、 こちらは意外にもこのブログ単体と月のPVが同じくらいあるという。。(1万程度ですが。) そんなわけで、ドメインが移行しましたが、中身はまるっきり同じですのでご心配なく 今後ともご利用お願い致します。 ### キーワードサジェストツール公開終了します 2007年にブログを初めて少しして作ったツールで、 個人的に初めて公開したツールだったので思い入れのあったSEOツールのキーワードサジェストツールですが、 YahooAPIの改定に伴い有料化するということで、今月で終了することにしました。 5年半という長い間ありがとうございました。 思い返すと当時はオーバーチュアのキーワードアドバイスツールが終了して、 何とかして検索回数を求めようとみんな躍起になっていた時期で、 あの時キーワードアドバイスツールからひたすら情報を集めておいて サービスを開始した所とかは割と成功してますね。 まあ今となってはGoogleも少しは情報を出すようになっていますし、 あまりリアルタイムで役に立つツールにはなりませんでしたが、 少しは公開した意味があったかな?と思っています。 TwitterといいYahooといい、こういうAPIビジネスが終焉していくのを見ると 当時の流行語だったWEB2.0的なアレの終焉を垣間見てしまいますね~ ### Titaniumでお小遣いを稼げるiphoneアプリをリリースしました ずっと更新していませんでしたが、 いくつかアプリをリリースしたりしていました。 WebViewを使った小さなアプリはちょこちょこ出したりしたんですが、 今年になってやっと大きなアプリがリリースできたのでその時のお話でも。 なおアプリは全部Titaniumで作成しています。 会社のアプリの宣伝ですw Repoアプリ お小遣い稼ぎ で検索すれば出てきます。 アプリはざっくりいえばお小遣いアプリでして、 記事を投稿したらそれに応じた報酬がもらえる仕組みになっています。 ただ、この投稿ってのが曲者でして、 クライアントによって細かい要望が一杯あるので、 それに臨機応変に対応するようにPC版では専用管理画面でフォームを自動作成して、 それをJSONに吐き出しています。 同じことをアプリでも求められていたので、 PC版と同じJSONを受け取ってアプリ上でフォームを作成していたんですが まさかのメモリが足らないという。。。 10文字から20文字の間の○○というワードが入った文章、 350文字以上の○○というワードが入っていて、かつ○○という文字が入っていない文章 などの複数のフォームがあり、 回線が弱いことの多いアプリでは都度POSTしてチェックするわけにもいかないので、 書き込み終了と同時にチェックをする形にしていました。 そのため、一つのフォーム必ずeventListenerがついていて、 しかもそれが割と色々とチェック(必須ワード、NGワード、文字数など)をするので フォームがたくさんあると旧型のiphoneとかでは描画がおかしくなります。 特に書き込み最中に(ソフトウェアキーボード表示中)に残りの画面をスクロールしたりした際には 結構な確率で描画がおかしくなりました。(ビヨンビヨンってなったのでビヨンビヨン現象とか言ってましたw) ここら辺はEventごとの処理をまとめてなるだけ短く綺麗に書くようにしたり、 一部はあきらめてPOSTした際にチェックするようにしたりしました。 それでもまだiphone4とかでは発生したりするんですが、 一応少し待ったり画面を切り替えたりすれば(ようはメモリにきちんとロードされれば) 表示できるので、一旦Ver1としてリリースしています。 またそれ以外に Ti.APP にカスタムイベントを追加して、それを後で着火する形で書いていたのですが、 いらなくなった時に手動で消す必要があるので、無駄なメモリを使いやすいので、 なるだけ作成した画面自体にカスタムイベントを追加するように書き換えたりしました。 また、案件一覧を表示する際も検索出来るように色々とTableRowにワードを追加していたんですが、 これがまた重い。。 案件タイトル、ディスクリプション、タグをくっつけたものをオブジェクトのプロパティに入れて それを検索対象にしていたんですが、 こいつのせいで、100件とかを超えたテーブルになるとスムーズにスクロールできない (表示されていない部分がメモリから落ちているかんじ) になってしまっていたので、 これも全部きっぱり排除しました。 っていうかまずは快適に使えるように検索機能自体も削除しちゃいました。 とまあ、今回はメモリ不足に悩まされまくったアプリですが、 Ver2ではこれにローカルDBをつけてキャッシュとかもしようと考えているので、 さらに重くなりそうで今から気が重いです。。 ### Android: ボタンをdrawable/xmlで作る 絵画センス0に自分にはフォトショやらイラレやらは操作はできても まともなボタンを作ることはできないので、 せめて単純な角丸グラデのボタンくらいXMLで作れないかということで。 結果こんなかんじになりました。 メインに配置するボタン #layout/main.xml <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/submitButton" style="@style/myButton" /> ボタンのスタイル設定 #values/style.xml <resources> <style name="myButton" parent="android:Widget.Button"> <item name="android:textColor">#444444</item> <item name="android:layout_margin">10dp</item> <item name="android:padding">10dip</item> <item name="android:background">@drawable/frame</item> </style> </resources> #drawable frame.xml <shape xmlns:android="http://schemas.android.com/apk/res/android"> <stroke android:width="1dp" android:color="#cccccc" /> <corners android:radius="5dp" /> <gradient android:angle="270" android:startColor="#ffffff" android:centerColor="#dddddd" android:endColor="#eeeeee" android:type="linear" android:centerX="0.5" android:centerY="0.5" /> </shape> これでiphoneっぽいアイコンになります! デザインができない人用に こんな感じのXMLのテンプレートみたいなの公開してるとこないのかなぁ。。 ### imageTTFText で縦書きにする方法 検索しても出てこなかったので、文字ごとにバラして 一つづつ書き出し>高さ調整 を繰り返してたんですが、 その場合、一つ一つの高さを足していって合計を出したりとか 結構めんどくさいので、なんとかならんもんかと思ってたんですが、 実は単純に改行挟めばいいだけでした。 なので、こんな感じに //文字列を1文字づつ配列にする。マルチバイトがないのであれば、str_splitでも可能 $l = mb_strlen($string,'UTF-8'); $chunked = array(); for ($i=0; $i ### 今年もお疲れさまでした 今年は飛躍の年でした! 技術的には半分PHP、半分FLEXってかんじでしたね。 まあFLEXで作ったSEOツール系はだんだんPHPに置き換わっていますが。 あと、諸事情でVPS系10台近く、専用サーバ、クラウドなどサーバ周りをめちゃくさ触った1年でした。 そういうの嫌いで前職やめたんですけどね。。 ってことで毎年恒例WEB履歴 去年:総検索回数:10888 今年:総検索回数:10410 1月:1032 2月:828 3月:735 4月:742 5月:1165 6月:1340 7月:970 8月:824 9月:727 10月:826 11月:724 12月:497 トップサイト livedocs.adobe.com とうとうトップ10にhatenaもphp.netも入らなくなった。。 前者は時代を感じるし、後者は成長を感じる! ということで、今年1年お疲れさまでした! ### ランキングチェックツールを自作してみたのでそのメモ FLEX、PHP、その他もろもろでランキングチェックツールを構築したわけですが、 最近やっと安定稼働になったので、そこらへんのメモ。 サービスの規模 ・現在のワード数:1500前後 →通常のツールと違って、全SERPSを取得してさらにキャプチャも取得するので、  google,yahooへのアクセス回数は大体普通のツールの7-8倍。  (なのでGRCとかフェレットとかでいったら12000キーワードくらいまわしてる計算) ・URLのチェック件数:1600前後 →同時に競合調査とかもできるようになっているので、ワード数よりちょっと多い。 この規模感です。大きいような小さいような。 まだ稼働して1カ月で個の規模は大きいというべきか。。。 ハードウェア的には VPS4台という笑えない状況w 大体400ワードを1VPSで裁いてる感じです。。 HDD的には キャプチャ含めて1日分のSerpsは大体3GB↑ 1か月分保存するだけで、100GB近くになる。 HDDはいくらあっても足りない状況。 現在は前月分以外はtar.gzにしてローカル保存。 スクリーンショットとかの関係で、 DBに入れず全部ファイルにしてたのがあだになったかも。。 CPU的には 余裕すぎ。というかほぼ回線とHDDのためにVPSを分散させてる状況。 回線的には 1ワード10件表示で、100位までをGとYでやるので、20アクセス。 それが1500ワードなので、3万アクセス それを4台で賄っているので、1台あたり7500アクセス。 こんなの普通にG,YにアクセスしたらDOS攻撃になっちゃうので、 それを24時間で分散してアクセスしてます。 ちなみに最低7.5秒は感覚あけないと、Googleはすぐに弾いてきますので、 24*60*60 / 7.5 = 11520ってことで、 MAX1万ちょっとくらいしか1サーバ(てかIPかな)からはアクセスできない感じです。 Yahooはたぶんそこまでチェックはしていなっぽいです。 その他 ・なぜかVPS経由のIE8相当のアクセスだと、  ロケーション取得が日によってかなりばらつきがあって、  ある日は渋谷、ある日は目黒、ある日は愛媛、ある日は鹿児島  みたいな感じで、ばっらばらになります。 ・HDDが一杯になると、VNCがつながるけど、画面が真っ黒なままなんだと初めて知りました。 ・改めてランキングチェックツールの大変さがよくわかりました。。。 ・とりあえず普通のランキングツールと違って全Serpsが手元にあるので、  分析し放題なのだけはメリットなのかな。 ### LZTD機能追加(GoogleCalenderに対応) Ver0.94 にアップしました。 変更点は ・GoogleCalenderへの登録を終日でしかできなかったのを時間指定でできるように EvernoteでTodo管理するアプリ Flexで時間指定まで含めたUIを考えるのはちょっと頭を使いました。 Flex Date and Time (datetime) Picker Control 結局ここを参考にNumericStepper2個を時、分に適応させてやりました。 本当はもっとスマートな形がいいんでしょうけど。。 ### php で session_regenerate_id(true) でセッションが切れる問題 わざわざセキュリティリスクを上げるプログラムを書くとかどうかしてると思うけど。。 セッションハイジャックのために session_regenerate_id(true)を書くと リンクをダブルクリックしたときに高確率でセッションが切れるようになります。 たぶんセッションを保存するI/Oのあたりなんじゃないかなぁと思いますが、 さしあたってこれでは色々と支障をきたすので、 こちら側で session_regenerate_idのタイミングをコントロールしなければなりません。 でこんなかんじ。 //ログインするときexpireにtimeをいれてく $_SESSION['expires'] = time(); //セッションのページを開くたびにこんなかんじ。 if ($_SESSION['expires'] < time() - 7) { session_regenerate_id(true); $_SESSION['expires'] = time(); } わざわざこんなこと書かないといけないなんて・・ なので、セッション使うなら、 ハイジャック対策は別の方法でやるほうがいいのかもしれないですね。 やり方わかりませんが。 ### LZTDバージョンちょいアップ Ver0.93 にアップ 変更点は ・地味なボタンをちょっと鮮やかなアイコンに変更 ・Google CalenderにTodoを送れるように ・あと細かいけど、クリックするとリザイズできる範囲をちょっと広げました EvernoteでTodo管理するアプリ 余談ですが、GoogleCalenderの設定はめちゃくちゃ苦労しました。。 ### FLEXで縦だけ、横だけにコンポーネントをドラッグしたいとき LZTDで今実装してるTODOのドラッグ機能ですが、 こんなかんじで実装してます。 public function mouseDown(e:MouseEvent):void { todo.startDrag(false,new Rectangle(this.x,this.y,0,this.height)); //ドラッグ処理開始 } public function mouseUp(e:MouseEvent):void { todo.stopDrag(); //ドラッグ処理終了 //ドラッグ対象の中点が整列対象の一番上より下にきたら、ドラッグ対象はそれより下とみなす todo.y = todo.y - todo.height / 2; lineup(); //整列処理 } 開始処理はspriteにある startDrag() メソッドでOK。 ただ、そのままだと、自由自在に動かせてしまうので、 今回みたいな縦一列の画面の場合は、3つめの引数(width)を0にして 横には動かせないようにしてあります。 これはクリックしたところから、どれだけの幅で動かせるか、みたいな意味ぽいので、 this.widthとかにすると、 クリックしたところからwidth分横に動かせる=クリック位置によっては画面外にもってける って感じになってしまうので。 横だけに動かしたいなら this.heightを0にして、横幅を指定すればOK。 あとなんか終了処理がややこしいですが、 1 2 3 4 5 ってあったとして、1をドラッグしたときに、 1の高さの中央点が、2の一番上よりも下にきたら 2 1 3 4 5 になるようにする処理を書いてます。 これ書かないと、1の一番上が、2の一番上よりも下に行かないと 上のように並ばないので。 ### Evernoteでtodo管理するツールLZTDを軽くバージョンアップしました 内部の処理をちょっと変えて、あと常に最前面に表示機能をつけました。 そのまま上書きインストールすれば、データロスなく使えます。 http://refeed.in/lztd ちなみに最前面に表示機能は チェックボックスにチェックを入れた時 this.alwaysInFront = true; こうなるように書いただけです。 ### 共起語検索ツールを作りました 共起語検索ツール http://refeed.in/collocation/ 今更感もありますが、SEO業界で割と最近ネタになっている共起語をピックアップするツールを作ってみました。 メインのワードを入れると、Googleで検索し、その結果のTOP10を引っ張ってきて 本文と思われるものなどポイントが高そうな文を計算して、 そこから引っこ抜くかんじです。 たとえばseoでやると SEO エンジン サイト 上位 ページ リンク Google Yahoo サーチ 登録 キーワード 最適化 場合 情報 ユーザー Web ディレクトリ 猿 ビジネス 猿・・・? まあおいておいて。 少なくともGoogle先生のTOP10なので、 Googleが高い評価をしているコンテンツに入っている共起語なのであれば それなりに意味があるのかなと。 共起語検索ツール http://refeed.in/collocation/ ### AS3でThriftを使う LZTDを作る際に必要になったので (というか一番のヤマだった) いくつか問題残ってますが、自分はこの方法でやりました。 1:適当にサーバを用意する(自分はVirtualBoxでやりました) 2:https://github.com/asogor/thrift-as3   ここからソースを落としてきてコンパイル 3:EvernoteのApiをダウンロードして、thiftのソースを thriftコマンドでgenerateする 自分のバージョンだと evernote-api-1.16\src\thrift に5個の.thriftファイルがあったので、それを全部 thrift -gen as3 **.thrift みたいにする。 このうちType.thriftとかはエラーで落ちるので、 (原因は64ビットを割り当てる型がAS3にないためらしい) それを検索して全部32ビットに置き換える ⇒割とめんどくさい&後で色々修正しないといけない。 4:3で出来たソースと、thrift-as3の /lib/as3/src/org/apache/thrift にある asファイルの塊を まとめてFLEXにインポートして使用する ただこのままだとi64で読み込むところは全部落ちるので、色々と修正しないといけない。 i64は evernoteでは timestamp型がほとんどなので、(たとえばNote の created,updatedなど) うまく修正しないと全部空っぽになったりするので注意。 (一応自分は http://code.google.com/p/as3crypto/source/browse/trunk/as3crypto/src/com/hurlant/crypto/rsa/BigInteger.as?r=3  ここのらBiginteger型を参考にして、64ビット分操作するような感じで動かしました) ### Evernoteでtodo管理するツールLZTDをリリースしました EvernoteのチェックボックスでのTodo管理がしっくりこなかったので、 Airでインターフェースを作ってみました。 まだβ版ってことですが、とりあえずリリースです。 http://refeed.in/lztd ダウンロードページに書いてありますが、以下概要など 概要 ○基本的にはただのTodo管理ツールです ○データの保存先がEvernoteになっています。 ○Evernoteのアカウントが必須です。 ○Adobe Airがインストールされている必要があります。 ○無料です 機能 ○todoをEvernoteに保存 ○タイトル、1行コメント、その他データをtodo形式で保存 ○todoの削除(終了フラグをつけるだけでデータ削除ではないです) ○重要度の設定 ○重要度でソート(昇順/降順) ○日付でソート(昇順/降順) ○ダブルクリック一時的に一番上にtodoをもってくる ○右クリックでタイトルをクリップボードへ ○タイトルと1行コメント以外のデータの個別閲覧 ○やりづらいですが、一応右と下のバーをドラッグすれば大きさも変更可能 その他個人的メモなど Adobe Air + FlexでThriftにアクセスする際、 as3にはない、i64型がうまくおさまってなくて、 一応Bigintを強引に扱う形で補完したけど、 送信がうまくいっていない データの編集機能がまだない インターフェースがかっこわるいorz フォントをメイリオで指定しているので、入ってないとしょぼいフォントになる なお機能追加要望にはお答えできませんが、 バグなどは以下コメント欄に頂ければ修正できる範囲でします。 ### Flex:Airで画面のスクリーンショットを取得 画面のスクリーンショットをとるAirツールがいっぱいありますが、 あれどうやってるだろう?って思ったら結構簡単だったので。 ソースはこんなかんじ。 private function saveScreenshot(myHtml:HTML):void { var myScreenshotData:BitmapData; myScreenshotData = new BitmapData(myHtml.htmlLoader.contentWidth,myHtml.htmlLoader.contentHeight); myScreenshotData.draw(myHtml); //引数のオブジェクトのスクリーンショットを作成 var pngEnc:PNGEncoder = new PNGEncoder(); var pngBta:ByteArray = pngEnc.encode(myScreenshotData); //取得したスクリーンショットをPNGに var saveFile:File = myUploadDir.resolvePath(String("test.png")); var fs:FileStream = new FileStream(); fs.open(saveFile,FileMode.WRITE); fs.writeBytes(pngBta,0,pngBta.length); //PNGファイルを書き込む fs.close(); } ためしに mx:HTML でやってみました。 mx:HTML にhtmlを読み込んで、それをBitmapData.draw() で変換し、 あとはそれをpngにエンコードして保存するだけ。 実際は mx:HTML をリサイズできるようにして、 それに合わせて取得できるようにすれば、あの枠だけ表示させて それをドラッグして大きさを決めてスクリーンショット ってやつができるはずです。 ちなみにサイズを指定が myScreenshotData = new BitmapData(myHtml.htmlLoader.contentWidth,myHtml.htmlLoader.contentHeight); となっていますが、これはスクロールして一番最後までいったとこまで取得するために、こうしてます。 htmlLoader.contentWidth と htmlLoader.contentHeight はHTMLの高さです。 これを普通に myHtml.width myHtml.height にすれば今表示されてる部分だけになります。 ちなみに contentHeight は2880 なので、それ以上の横幅、高さをもつHTMLの場合は 切れてしまいます。 Flexリファレンス:HTMLLoader ### ウェブ履歴でしめる1年 毎年恒例の1年のシメ的なものを。 おもしろい1年の振り返り方ということで ウェブ履歴を見て色々考え事をします。 ログインして ウェブ履歴にアクセスしてください。 *履歴ONにしてないとだめだったかも。 去年:総検索回数:13629 今年:総検索回数:10888 1月: 803 2月: 762 3月: 352 4月: 1107 5月: 719 6月: 774 7月: 1303 8月: 990 9月: 1021 10月:917 11月:1208 12月:932 去年は7月から検索回数が激減してましたが、 今年はさらに減ってる! 別アカウントに切替とかってわけじゃないんだけど 単純に、知ってることが増えて検索しないでも出来ることが増えたのかな。 ちなみに検索時間は 10-12時と 15-18時の 間が飛びぬけて多く、 仕事の集中時間的にもここらへんとマッチするかんじです。 クリックしたTOP3のドメインは 1位:ja.wikipedia.org 2位:d.hatena.ne.jp 3位:livedocs.adobe.com 今年はだいぶFlex触ったので、そのせいかな。 wiki がTOPっていうのはちょっと解せませんが。 ってことで来年はさらに検索回数を減らして、(4ケタにして!) 自分の脳みその中に知識をためていきたいと思います。 よいお年を! ### Air (Flex) で XML でコンフィグを書く Airでなんか作るときにローカルにXMLの設定ファイル的なものを 外だしして適宜変更したいみたいな時にFlexでは myRequest = new URLRequest(targetUrl); myLoader = new URLLoader(myRequest); myLoader.addEventListener(Event.COMPLETE,myXMLData); public function myXMLData(evt:Event):void { var myXML:XML = XML(evt.target.result); } みたいにしてたんですが、 もっといい方法がありました。 <mx:Model id="mySetting" source="setting.xml"/> MXMLにこれだけ追加すればOK。 これで mySetting.title とかいう風にXMLを直接オブジェクトで操作することができます。 ただリファレンスにも書いてあるように色々と注意点はありそうです。 http://livedocs.adobe.com/flex/3_jp/langref/mxml/model.html ここで、source はデータモデルの外部ソース(ファイルなど)を指定します。 外部ソースには、静的データおよびデータバインディング式を含めることができます。 source プロパティで参照されるファイルは、クライアントコンピュータではなくサーバーに存在します。 コンパイラはソース値を読み取り、ソースをアプリケーションにコンパイルします。 source 値は実行時には読み取られません。 たぶんコンパイルしたファイルと同じサーバに置いておく必要があり、かつ実行時に動的に変更かけるとおかしくなるのかな? ちょっとそこまでは試していませんが、注意はしないといけないかと。 ### FLEX->AMF->PHPの初歩 ほかのブログさんにも色々ソースはあったのですが、 結局自分で書かないと全然意味がわからなかったので 自分流のまとめってことで。 AMFの詳細はここら辺を参考にさせて頂きました。 http://framework.zend.com/manual/ja/zend.amf.server.html http://www.atmarkit.co.jp/fwcr/rensai2/flex4_05/01.html http://blog.asial.co.jp/323 http://andante0727.blog81.fc2.com/blog-entry-299.html 簡単にいえばFLASHからバイナリ通信でPHPのクラスを叩けますよってことですかね。 ただ割と規約というか制約も多いので、そこら辺を。 AMFとPHPの連携自体も何個かライブラリがあるのですが、 とりあえずZEND_AMFを使うことに (特に理由はありません。) FLASH側はFLEX BUILDER3です。 とりあえずZEND_AMFを落としてきてサーバ側ではパスを通しておいてください。 php_iniとかに書いておけばいいかと。 シンプルなソースはこんなかんじ。 php側 //http://hogehoge.com/amf.php include('/Zend/Amf/Server.php'); $Server = new Zend_Amf_Server(); $Server->setClass('Foo'); $return = $Server->handle(); //phpからFLEXにデータを返す echo $return; Class Foo { private $aho; public function bar($aho,$baka,$manuke) { $this->aho = $aho; return array($this->aho,$baka,$manuke); } public function buzz($baka,$manuke) { return array($baka,$manuke); } } ほぼソースそのままです。っていうかほぼおまじないみたいなもので、 これだけ書いておけばあとはFLEX側から書いたクラスをぶったたけます。 FLEXではこのクラス名とメソッド名が必要になってきます。 FLEX側MXML //test.mxml <?xml version="1.0" encoding="utf-8"?> <mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"> <mx:Script source="../libs/amf.as" /> //AMFのアクセス部分 <mx:RemoteObject id="amfAccess" source="Foo" endpoint="http://hogehoge.com/amf.php" destination="zend" fault="onFault1(event)"> <mx:method name="bar" result="onResult(event)" fault="onFault2(event)" /> <mx:method name="buzz" result="onResult(event)" fault="onFault3(event)" /> </mx:RemoteObject> //結果出力 <mx:VBox width="100%"> <mx:VBox width="100%"> <mx:Text id="php_result1"/> <mx:Text id="php_result2"/> <mx:Text id="php_result3"/> </mx:VBox> <mx:VBox width="100%"> <mx:Button label="てすと1" click="amfTest1()" /> <mx:Button label="てすと2" click="amfTest2()" /> </mx:VBox> </mx:VBox> FLEX側asファイル //amf.as import mx.controls.Alert; import mx.rpc.events.ResultEvent; public function amfTest1() :void{   //FLEXのid.phpのメソッド名(引数) にする amfAccess.bar("amf1","amf2","amf3"); } public function amfTest2() :void{   //FLEXのid.phpのメソッド名(引数) にする amfAccess.buzz("amf1","amf2","amf3"); } public function onResult(event:ResultEvent):void {  //phpからのリターンを表示 var spl:Array = event.result.toString().split(","); php_result1.text = spl[0]; php_result2.text = spl[1]; php_result3.text = spl[2]; } MXMLから説明すると、 mx:RemoteObject の source をPHPで作ったクラス名に、 endpoint はファイルのURIにします。 んで、mx:method の name をメソッド名にします。 (この mx:method の中に <arguments> としてタグでやる場合はその中のタグは  PHPのメソッドのほうで書いた引数と名前合わせないといけないのでなんかかっこ悪いです。) でAS側で単純に FLEXのid.メソッド名(引数,引数,引数)ってかけるわけです。 ちなみにリターンはarrayで送るとカンマくぎりだったので splitして表示させているだけです。 ただエラー関連はphpのエラーを返してくれるわけではないので、 PHP側でエラーがないか先にテスト通してからじゃないと FLEXのほうをゴリゴリってわけにはいかないのがめんどくさいですね。 (FLEXとPHPを同時並行で書くのは結構きつい) *追記 癖で tostring() してたので勘違いしてましたが、phpからくるデータは flexのarrayで受け取ってそのまま扱うことができました。 ### sakuraのマルチドメイン設定してるサーバでpostした時のサーバ情報 別にpostじゃなくてもいいんですが、 とにかくsakuraのマルチドメインサーバから他サバを叩いたときの話です。 何回かハマったのでメモってきます。 専用APIとか発行してもらって、 そこにアクセスする際、IPで許可とかしてもらうと思いますが、 IPとドメインのセットで許可してる場合の現象です。 以下PHPで取得した場合のデータです。 許可されたIP:192.168.0.1 許可されたドメイン:hogehoge.com 通常のサーバでアクセスした時 許可してる側からは print($_SERVER['REMOTE_ADDR']); ⇒192.168.0.1 print($_SERVER['REMOTE_HOST']); ⇒hogehoge.com ってなるわけです。 なので、これを両方みておけばいいでしょうってなるんですが、 sakuraのマルチドメイン設定だとこれが変わります。 sakuraのマルチドメインサーバでアクセスした時 許可してる側からは print($_SERVER['REMOTE_ADDR']); ⇒192.168.0.1 print($_SERVER['REMOTE_HOST']); ⇒111.101.10.1.static.www1a.sakura.ne.jp(例です。環境によって変わります。) ってなかんじで、REMOTE_HOSTがかわっちゃいます。 なので、IP+ドメインの両方で見ていると、 単純にAPIアクセス側がsakuraに移行しちゃうと セキュリティエラーではじかれちゃうという。 3回ハマって、ハマった瞬間思い出すという切ない感じです。 ### Google App Inventor を使ってみた 何週間前かもう覚えてませんが、とりあえず申請していたのがやっと通ったようで。 ってことで、AppInventor触ってみました。 http://appinventor.googlelabs.com/learn/ 初めに とりあえず動画は見ていたのでブラウザでいじれるんだろうって思ったら、 どうもアプリもインスコしないといけないらしい。 (別にアクセス許可が下りる前からインスコまではできるらしい) I downloaded the App Inventor Extras Installer and ran it. Can I start using App Inventor right now? No, App Inventor is a Web application that runs in a browser. You'll need to have access to the system before you can run it. Installing the App Inventor Extras software and setting up the phone is necessary preparation for using the system, but this doesn't in itself provide access to App Inventor. 設定して立ち上げてみる インスコ完了したら、今度はAndroid側の設定っておもったけど、 ざっとみると1.6のXperiaちゃんの名前はない(当たり前か。。w) とりあえずUSBつなぐ以外特にandroid側でやることはないようなので XperiaをUSB接続させた状態で立ち上げてみることに。 Appinventor自体はブラウザから立ち上げるので、 ページにアクセスするだけです。 (インストールしたのはたぶん実機テスト関係のアプリっぽいです) アプリを作ってみる プロジェクト作成 とりあえずappinventorのページにアクセスすると シンプルにメニューバーだけが表示されてるので、プロジェクトを作成してみる。 適当にアプリのビューを作る 作るとこんなかんじの画面になるので、適当に直感で左のメニューから ドラッグ&ドロップで動かしてみるとなんか入った! 入れたボタンのテキストとかのプロパティは全部右側で操作できます。 なおブラウザなので、ここでは日本語がちゃんと入りますので、 ボタンに日本語で名前を付けれます。 (後述のBlockEditorでは日本語が化けます) Block Editorでロジックの操作 ただ、これだとクリックしたときの動作とかを設定する画面がないなー とチュートリアルを見たら、上部にあるBlock Editorをクリックせよ と書いてあるので押してみる。 (またアプリダウンロード) ちょっとするとこんな画面が別ウィンドウで立ちあがる。 おーこれこれ。動画で出てたやつが立ち上がりました。 どうもすみ分け的に、ブラウザ側でViewのレイヤーを操作して、 ロジック的なことはBlockEditorでやる模様。 とりあえず適当にアプリのイニシャライズ時の設定とかしてみました。 が、titleに日本語ぶち込もうとしたら入らない。。設定がおかしいのかな? とりあえず文字化けしちゃうので、今回はローマ字でw 実機テスト ここまで出来たら、あとはエミュレータでもなんでも確認してみたいわけですが、 チュートリアルには上のバーにある 「restart phone app」をクリックするように書いてあるので、 とりあえずポチっとすると。。 10秒くらいでUSB接続したXperiaちゃんから画面が!! しかもこれ、リアルタイムなかんじで、ブラウザ側でアイテムを追加すると そのままXperiaの画面でも反映されます。なにこれすごい。 パブリッシュ 最後パブリッシュですが、これはブラウザ側でやるっぽいです。 ブラウザ側の右上にパッケージ化みたいなボタンがあるので 押してみたらapkファイルにまとめてダウンロードしてくれました。 まとめ Eclipseより早い。。。 はっきりいってほぼマウス操作でできるので、 プログラムを少しでも組んだことがあれば(if とかあそこらへんの概念がわかれば) アプリ作ることができちゃいます。これ。 ただ、blockEditorのほうが日本語対応してくれないと、色々ときついかなーとは思います。 ### Niftyクラウドのhttpの設定 新しい案件で、スモールスタートでniftyクラウドを使ってみることになったので それ関係のメモでも。 スモールなので miniのサーバプランを選択。 EC2じゃなくてniftyクラウドを選ぶってことは普通にサーバとして使いたいと思うので、 とりあえず ftp と http を動かしにかかります。 今回はまずhttpを起すことにします。 まずは iptablesの設定を確認 iptables -L --line-numbers とりあえず80番だけつけとけばって思ったんですが、 どうもそれだけじゃ駄目なようで、 ここを参考に Niftyクラウドでのiptablesの設定 iptables -I RH-Firewall-1-INPUT 6 -p tcp --dport 80 -j ACCEPT (自分の環境だと6でした) /etc/init.d/iptables save service iptables restart (FWがうんたらかんたらってでてOKでるはず) んでもって、 apacheは /etc/httpd にあるので まずは httpd.confを設定 cd /etc/httpd/conf あとは適当に編集(ServerSignature とかONになってます) んで最初起動してたか忘れたけど、 apachectl start か apachectl -k restart で再起動なり起動なり。 で、ドキュメントルートは /var/www/html なので、そこに cd /var/www/html vi index.php で とか書き込んで あとは http://サーバのIPアドレス/ でアクセス可能になります。 ### Flexで埋め込みフォントを使用しないtext fieldでのエフェクト Flexでは理由はよくわかりませんが、埋め込みフォントじゃないと text系はエフェクトかけれないそうで。 textfieldをフェードインさせたかったのでつまったので調べてみました。 とりあえずこれが一番いいようなのでこちらでも紹介します。 Alternatives to using the Fade effect with text ソース private function forTextEffect(obj:Object):void { var bf:BlurFilter = new BlurFilter(0,0,0); var myFilters:Array = new Array(); myFilters.push(bf); obj.filters = myFilters; } 向こうに書いてあるのがオブジェクト指定だったので、 多少いじりました。 とりあえず mx:Text とかにFadeInなどをかけるまえに forTextEffect(textObject); みたいに一枚かましておけばちゃんとfadeInが動作するようになります。 ただこの処理自体が多少時間がかかるようなので、もしかしたら forTextEffect(textObject); fadeIn.play([textObject]); みたいに連続して書くと確実にfadeInが動作するか担保できないかもしれません。 (未検証) ### seopanelをインストールしてみた *超長いです。 めずらしくMOONGIFTさんにSEOのネタがあったので 拾ってみました。 SEOに力を入れるなら使おう「Seo Panel」 別にローカルの環境でやってもよかったんですが、 一応さくらのサーバにぶちこんでみました。 ダウンロードとインストール ダウンロードはここから http://www.seopanel.in/download/ WGETで取得するなりダウンロードしたあと解凍してアップするなりします。 アップしたら以下の作業を行います。 ・configフォルダのsp-config.phpのパーミッションを666に ・tmpフォルダのパーミッションを777に ここまで終わったら、ブラウザから ***.com/seopanel/install/index.phpにアクセス (フォルダ名とか変えてるともちろんURLも変わります。) Wordpressとかインストールするときのように DB系のデータを入力してポチでインストール完了です。 (インストールが終わったら installフォルダを削除し、  congfigフォルダのsp-config.phpを644にもどします) コマンドラインだとこんな感じ wget http://seopanel.googlecode.com/files/seopanel.v.2.0.2.zip unzip seopanel.v.2.0.2.zip cd seopanel chmod 666 config/sp-config.php chmod 777 tmp //ブラウザで作業 chmod 644 config/sp-config.php rm -r install //削除するから気をつけて インストールが終わるとDBはこんな感じのが増えてます backlinkresults country directories dirsubmitinfo keywords languages rankresults saturationresults searchengines searchresultdetails searchresults seoplugins seotools settings skipdirectories users usertypes websites DBにプレフィックスがないから多重インストールとかはめんどくさそうです。 触ってみる ログインするとこんな感じで見た目ちょいいまいちですが、 所詮ツールなので問題なし。 全部英語ですがとりあえずなんとなくわかると思うので、 まずはspadminのパスとか変えておいてください。 ○ランキングチェック とりあえずcronは置いておいて、サイトを登録して調べてみます。 1:サイトを登録する  Admin Panel ⇒ Website Manager ⇒ New Website  でサイトを登録します。  日本語も大丈夫そうですが、Crawl Meta Dataボタンはutf8のサイトじゃないと  文字化けするっぽいので使わないほうがいいかも。  *この時Seo Panelのトップに行くとサマリーがでますが、   この時点ではバッチが走っていないので、全部空っぽです。   これは Admin Panel ⇒ Reports Manager ⇒ Report Generation Manager   でサイトを指定して Proceed を押せばすぐ更新できます。 2:サイトを登録したらそれにキーワードを設定します。  Seo Tools ⇒ Keyword Position Checker ⇒ Keywords Manager  でキーワードを設定していきます。  画面下の Create をおして、キーワード名、サイトなどを指定してください。  *Search EnginesはSHIFTを押しながら選択することで複数選択できます。 3:とりあえず一発走らせる  このままだとサイト登録時と同様にまだバッチが走っていないので、  とりあえず一回チェックさせます。  Seo Tools ⇒ Keyword Position Checker ⇒ Generate Keyword Reports  にて、とりあえず何も設定せずに Proceed を押してください  (なにも設定しないと全部 All 設定になります。) 4:ランキングのチェック  Seo Tools ⇒ Keyword Position Checker ⇒ Keyword Position Summary  にてレコードが表示されてるはずです。 ○ランキングチェックを自動にする 1:色々と機能があるので、とりあえず何を自動で回すかを確認します。  Admin Panel ⇒ Seo Tools Manager  でCronの項目がActivateになっているものが全部自動でチェックされる対象です。 2:Cronの設定  さくらの場合ですが、単純に cron.php を定期的に回すだけでOKなので、  さくらのコンパネから CRONの設定 ⇒ 新規追加で  以下みたいな設定にすればいいはずです。  月 *  日 *  時 0  分 0 (毎日0時にバッチスタート) おまけ 他にもちょこちょこ機能ありますが、長くなったので今回はとりあえずこんなとこで。 ただソース見た感じでは俺が作ってるやつと比べてgoogleの回数制限とかの対処が甘かったりするので、 1000件以上のキーワードは回せないかもしれません。(未検証) これで回せるのであればもしかしたら裏技っぽいことがあるのかなー。 ってことでソースの読み倒しはこの後の宿題。 おまけ ○細かいツール系 登録せずにささっとサイトのバックリンクを調べてみます。 1:メニューのSeoToolをクリック 2:Backlinks Checker⇒Quick Backlinks Checker 3:明らかにフォームがあるので、そこにURLをぶち込めばAjaxで順繰りに表示してくれます。 ### さくら 以前のサーバは7年以上前に契約したものを使っていた関係で、 Mysqlのバージョンが上がらない=Wordpressが2.9以降にできない ってことで、ぜい弱性的な意味でもそろそろなんとかしないとー ってときに、サーバ会社からプランが古くなっているので、乗り換えてください的なメールがきたので、 ほかのサービスでも使ってすでに契約中のさくらサーバでまとめて管理しようと思い、 ドメインを移管することに。 手順はたいしてなく、作業も短時間で意外と簡単でした。 1:今ドメインが登録されてる会社にドメインを移管する旨を伝えて  AuthCodeを発行してもらう。 2:さくらサーバの会員メニュー(契約とか見れるほうの)で  契約情報タブ⇒サイドバーからドメインメニュー⇒  ⇒ドメインの移管を選択 3:移管するドメイン名を入力して1でもらったAuthCodeを入力 4:ドメインの管理者あてにメールがくるので(これが自分になってる人もいそう)  自分の場合は前のサーバ会社だったので、そのメールからURLをクリックして承認  (たしか  Domain Transfer Request みたいなタイトル) 5:これでちょっとすると前のサーバ会社とさくらの両方から  作業完了しましたってメールがくるので、それを確認する。 6:さくらサーバの会員メニュー(契約とか見れるほうの)で  契約情報タブ⇒サイドバーからドメインメニュー⇒  ⇒一覧の中に移管したドメインがあるので、  表示⇒ネームサーバのところの変更で設定を変更する  (さくらでそのまま使うなら )  ネームサーバ1 ns1.dns.ne.jp  ネームサーバ2 ns2.dns.ne.jp 7:ここまで終わったらあとは普通に管理画面からドメインを割り振ればOKです。 とりあえず自分は移管した時点でDNSがさくらに自動的になると思って 2日むだにしました。。 ちなみに2.9のままですw ### 被リンクワードチェッカー公開 アンカーテキストバラエティチェッカー 必要に迫られていろいろ作っていたのツールの中の一つです。 今そのワードがリンクでちゃんと貼られているのか、と 貼られている場合なんのワードで貼られているのかをチェックするツールです。 特に今はおんなじワードで大量に貼られるのはよろしくないので、 アンカーテキストバラエティが重要なので、 こんな調査もしなきゃいけないんですよね。 基本的にyahooのapiから抽出するリンクリストを上からなめて リンクだけ取り出すようなそんなツールです。 被リンクワードチェッカー 最初Airで作ってたんだけど、色々と不都合があったのでWEBにきりかえてます。 ### phpのrangeの便利な使い方 自分は結構使う頻度が高いんですが、意外と地味なrange()について 基本 とりあえず普通に1~10の数値の配列 range(1,10); ⇒1 2 3 4 ・・・ もちろん逆もOK range(10,1); ⇒10 9 8 7 ・・・ アルファベットもOK range('a','z'); ⇒a b c d e ・・・ アルファベットも逆でいけます range('z','a'); ⇒z y x w v ・・・ 遊び 第3引数を使うともっと遊べます 奇数のみ range(1,10,2); ⇒1 3 5 7 9 偶数のみ range(2,10,2); ⇒2 4 6 8 10 アルファベットでもいけます 偶数のみ range('a','z',2); ⇒a c e g i ・・・・ まとめ こんなかんじで結構融通のきく関数なので、for(){} を使うような場合は結構な割合で foreach (range(1,10) as $key => $value) { } みたいな書き方をしたりしてます。range()を変数に格納したほうがいいですけど。 ### SEOとAndroid -- 先にお知らせ -- インフォキュービックのブログの中の人とかなり懇意にさせてもらっているんですが、 その人が新しいtwitterのアカウントを開いたので ここで書いておいてとキャバクラで念を押されてしまったので 書いておきますw http://twitter.com/sakurasaku_inc これでフォロワー増えたら叙々苑的なものが発生するヨカン! -- 終わり -- で Xperia Desire あと今日AUのも発表?らしいんですが、 Androidが一気に普及しそうですね! まあXperiaはバージョン1.6 Desireは2.1 画面サイズも4インチと3.7インチ 解像度もちょっと違う と今の携帯みたいなカオスっぷりがすでに想像できて かなりアプリ開発がめんどくさそうなのですが、 iphoneと違って開発環境の構築とかもかなり敷居が低く (mac必須とか実機テストにお金がかかるとかありえんw) すでに自分も開発環境は整えてあるので、 SEO,SEMのシステム開発してる自分としてはどうからめて製作していくか 日々検討しているかんじです。 ちなみに自分は、 Xperiaがマルチタッチに対応してないせいで、 しばらくはシングルタッチのアプリばっかりがでそうなのと (Xperiaはそういう意味でAndroidの成長をかなり阻害するともいえますが) Ver1.6環境にも対応しなければならないということもあるので、 テスト用の実機としてXperiaを購入予定です。 (普通に考えたらDesireのが圧倒的にいいと思いますが。。。) まあXperia予約したら予約待ちで入荷未定の状態なので Desireの発表でキャンセルがでて当日ゲットできたらうれしいな~とか考えてます。 ### ユーズドドメインチェッカー を更新 どうもクローラの調子がよくなくて、うまく取得できないパターンが多かったので、 取得していたデータを一旦クリアして書き換えてみました。 ついでにあまり意味のないリンク情報も合算で目安程度にしました。 それにしても結構落ちてるものですねー 前のクローラのときもページランク3みたいなのはかなりとれたんですが、 今回のはどうなんだろう。 まあいずれにせよページランク自体は残ったり残らなかったり Google先生の気分でかわっちゃうので、 用途はハネムーン回避、ファーストインデックス速度の向上 最初からちょっとリンクがついてる、といったところでしょうか。 アフィリエイトサイト作成くらいなら十分かとおもいます。 ユーズドドメインチェッカー ### SEO難易度調査ツールを作ってみました もっと時間がかかるけど精度が高いのはたぶんいらないと思うので、 精度と時間のバランスをとったヤツを作ってみました。 SEO分析ツール ビジネスセンスのある人はきっとここで、 調査結果にSEO会社の広告でものせるんだろうけどw このたぐいのものがどの程度ニーズあるのか的な調査もあるので とりあえず使ってもらう形にしました。 精度は100%じゃないですが、100%出せるツールは少なくともフリーじゃ出回ってないですしね。 ### .htaccess で RewriteCond の後方参照 $1 ではなく %1 のお話です。 今まで画像をキャッシュしたり生成したりというのは 全部プログラム側で書いてたわけですが、 if (file_exists(IMG_CACHE_DIR.$cacheFile)) { $image = imagecreatefromjpeg (IMG_CACHE_DIR.$cacheFile); } else { $image = imagecreatefromjpeg ($imgBack); //画像加工処理 imagejpeg($image,IMG_CACHE_DIR.$cacheFile); } header("content-type: image/jpg"); imagejpeg($image); imagedestroy($image); まあこんなかんじで、キャッシュヒットもプログラム側で書いてたわけで、 これがどうにも地味に遅いので、何とか回避するべく頭をひねったお話です。 でまあ結論から言うと .htaccess のほうで キャッシュヒットの処理を書いてしまうことに。 こんなかんじです。 //ファイルが画像の場合 RewriteCond %{REQUEST_URI} ^(.*\.(jpg|gif|png|jpeg))$ //cacheフォルダのキャッシュとヒットするか確認 RewriteCond %{DOCUMENT_ROOT}/cache/%1 -f //ヒットしたらリライトして表示させる RewriteRule .* /cache/%1 [L] ポイントは %1 を使ってRewriteCondでヒットさせた参照を使用することで、 http://a.com/a.gif がキャッシュファイルである http://a.com/cache/a.gif にヒットさせるってとこですかね。 今まで$1とかしか使ってなかったわけですが、%のが便利だなーと。 ### .htaccessでファイルがあったらリダイレクトしない処理 なんか今日3人から聞かれたので何でだろうって思ったら、 「.htaccessでファイルがあったらリダイレクトしない」 みたいなかんじで 検索しても出てこないんですね。 「RewriteCond」まで思いつけば割とすぐなんですが。 ということで、こんなかんじですよっと。 RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ index.php?url=$1 [QSA,L] ・!-f はファイルがあったら以下のRewriteRuleを適用しない ・!-d はファイルがあったら以下のRewriteRuleを適用しない これで「.htaccessでファイルがあったらリダイレクトしない」で検索して ヒットしてくれればいいんだけどw ### sakuraサーバで 2013:Lost connection to MySQL server エラー さくらで運用しているアプリを弄っていたら 突然こんなエラーが ERROR 2013: Lost connection to MySQL server at 'reading initial communication packet' Error 61 無論DBにアクセスできない状態になったので、なんなのかなーと探っていたら 10分ほどして勝手に直った。 直った?っていうか本当に直ったの?ってことで 一応作業を終えた後色々調べていたら どうもプロセスが死んでリスタートされてる間にアクセスすると こんなエラーになるらしい。 このエラーが出た間に裏側ででたmysqlのエラーログはこんなかんじ。 Server shutdown in progress Lost connection to MySQL server during query Can't connect to MySQL server on '***.db.sakura.ne.jp' てっきり作業でUPDATE文とかINSERT文とか1000行単位でぶちこんでいたから それのせいなのかと思ってただけによかったよかった。 なんかのタイミングでmysqlサーバが再起動してたぽいですね ということでまたおきた時の為にメモ。 ### set_error_handler と restore_error_handler シングルトン君を書いてる時にふと try-catchのとこをきれいにかけないかなと思って (今までいちいちtry文の中で throw new Exception() してたので) set_error_handlerを使って書き直してみました。 //今までこんなかんじでめんどくさい public function hogehoge() { try { if (TRUE === $error) { throw new Exception($e); } if (~~~) { throw new Exception($e); } } catch(Exception $e) { } } //set_error_handlerを使うとこうなる public function hogehoge() { set_error_handler("Class名::dbErrorHandler"); try { } catch(Exception $e) { } restore_error_handler(); } public static function dbErrorHandler($enNo,$eStr,$eFile,$eLine) { throw new Exception($eStr,$enNo); } おーやっとそれっぽくなった。 ちなみに set_error_handler() は staticで書いておけば set_error_handler("クラス名::dbErrorHandler"); set_error_handler("self::dbErrorHandler"); set_error_handler(array($this,'dbErrorHandler'); のどれでもいけます。 ### あけましておめでとうございます 今更感ですが、 あけましておめでとうございます。 ブログ開設から3回目の年明けですが (最近ついったーで満足してるという事実もありますが) これからもがんばって色々作っていきたいと思います。 とりあえず今年やろうと思ってるあたりは ・androidアプリ作成 ・もう少しましなWEBアプリの作成 ・もう少しましなデスクトップアプリの作成 ってとこでしょうか。 androidは今実機がありませんが、 nexusは買って見るつもりなのでそれにあわせて 開発しています。 ということで今年もよろしくお願いします。 ### 毎年恒例? WEB履歴で振り返る1年 もうこのブログも3回目の年末を迎えたわけですが、 毎年やってるWeb履歴で1年を振り返る作業でも。 やり方はこうです。 1:ログインしてGoogleにアクセスしてください。 2:右上に 「アカウント」ってあるとおもうのでそれをクリック 3:「ウェブ履歴」をクリック 4:サイドバーの 「統計」 をクリック 5:上部にある「昨年」をクリック(これが今年分らしい) 毎年思うですが、 仕事の時使ってるアカウントと、 家で遊んでる時使ってるアカウントが一緒なので 遊び系のネタが豊富すぎて仕事してないように見えますorz ということで今年の総計 総検索回数:7621 1月:582 2月:507 3月:391 4月:664 5月:622 6月:630 7月:656 8月:526 9月:842 10月:1025 11月:556 12月:620 去年の総検索回数が13629 今年の総検索回数が7621 は 半分になってる。。 まあ去年などはPHP関連でかなり検索した記憶がありますが、 今年はそういうとこで詰まるってことが少なかったからかもしれません。 成長したから。。。だと思っておきます。 ちなみに今年のトップクエリは yahoo api いっつも yahoo developers をこれで検索してるからですね。 ブックマークしとけよってお話。 2位は youtube dirpyというサイトを見つけて tubefire で落とせなかった mp3とかも落とせるようになったので、 それで結構使ってた記憶が。 それ以外はゲーム関連とか wiki とか。 あと今年のトップサイトは livedocs.adobe.com でした。 これはFLEX関連でかなり調査したからですね。 ということで、今年もお疲れ様でした。 ### 数字を大きくするjavascript デジタル時計を見てたらなんとなくjavascriptでつくってみたくなった。 あそび ┃の半分の高さの文字がわかんなくて微妙に 4,6,7が大きさ違うのがきにくわない。 ほんとは日付にするつもりだったんだけど スラッシュとかめんどくさいし 数字だけできたらもう満足なんてイエナイ ### さくらサーバで Client does not support authentication protocol ~ エラー 新しく契約したサーバでDB(mysql5)使おうとしたらこんなのがでた。 Client does not support authentication protocol requested by server; consider upgrading MySQL client phpのほうは同じなのにmysql4.1 > 5 の違いでエラーが出てるので なんでだろうとおもったら、パスワードのハッシュの長さがうんたらかんたらだった。 さくらサーバだとDBのアカウント作成する際に自動でやってくれるんだけど それが4.1用のやつなのかな。 mysql5をさくらで使うの初めてだから毎回こうなるのかはわからないんだけど。 で、まあこんなかんじで解決 UPDATE user-name SET password = OLD_PASSWORD('**********') ユーザネームとパスワードを上の構文に入れてあげるだけでした。 エラーの詳細はここら辺を参考にどうぞ パスワードハッシュ   https://neoinspire.net/rental-server-sakura ### FizzBuzz問題 最近朝頭の体操がてら今更ながら コードゴルフをやってたりするんですが、 (こんなの) 世の中の人はすげえなぁっと。 FizzBuzz問題っていうのは 検索すればいっぱい出てきますが、ようは 世界のナベアツをコードにすればいいだけの話。 (3の倍数はFizz 5の倍数はBuzz 15の倍数はFizzBuzzを出力) これだけなんだけど、どこまでコードを短くできるかっていうと めっちゃむずい。 結局ここでオレはくじけました。 <?for(;++$i ### PHPで非同期的なことをやってみる 非同期といっても、イベントを投げるだけなので、 非同期で行った結果を取得できないので、使いどころは少ないという。。 まあ一応こんなやり方もできましたってことで。 やり方としてはキックする側とキックされる側を少し弄ればOK キックする側 こんな感じの関数を書いてみました。 単純にURIにアクセスして、fgetせずに即コネクションを落とすというだけ。 function async_access($uri){ if ($parsed = parse_url($uri)) { $host = $parsed['host']; $path = ($parsed['query'])?$parsed['path'].'?'.$parsed['query']:$parsed['path']; if (isset($host) && isset($path)) { $fp = fsockopen($host,80,$errNumber,$errMessage,3); if ($fp) { $request = "GET {$path} HTTP/1.1\r\n"; $request .= "Host: {$host}\r\n"; $request .= "Connection: close\r\n\r\n"; fputs($fp,$request); fclose($fp); return true; } } } return false; } ただ普通にやるとこれだけだと、キックされる側によって、 PHPが最後まで実行される or されない というのが変わってしまうので、 キックされる側のPHPをチェックします。 キックされる側 ここらへんPHPの接続処理関係 をみればわかるように、PHPのステータスは3つあるわけですが、 今回のように即コネクションを閉じてしまうと基本的にはABORTEDになるわけです。 で、このABORTEDになったときにPHPの処理をとめるorとめないというのを制御してあげれば 上の関数でキックしたあと、きちんと処理をして終わってくれるというわけです。 で、調べた限りでPHPが最後まで実行してくれるようにするには2パターンありました。 ●1個目 PHP側で一切出力処理をしない。 ⇒単純にheader()とか echo,print などを削除&バッファ関連の ob_~ も削除する。 ●2個目 どうしてもprintなどの処理が必要な場合は ignore_user_abort(true)を最初にかき、 ob_startにてバッファ処理をさせて、 出力時は ob_flush() などで対応する。 これのどちらかをやれば問題なく動きました。 まあいずれにせよ、キックされた側のPHPの終了を読み取るには register_shutdown_function などを使って 再度こっちにキックしなおさないといけなかったりするので、 基本的には片道切符として使ったほうがいいと思います。 参考文献 ignore_user_abort(false)でPHPが処理を中断しない件 ignore_user_abort=falseなのにスクリプトが止まらない ignore_user_abort PHPの接続処理関係 ### Twitterのアカウント復活 記憶が正しければSkypeのコメント欄と同期させるのがどうしてもできなくて めんどくさくなってやらなくなってそのまま放置。。。 のついったー君のアカウントを復活させました。 せっかく jakk っていうアカウントとってたのになー 休眠アカウントになってわけのわからん wasjakk123 みたいなのに強制変更させられてました。 ⇒なのでアカウント名でログインできず四苦八苦。 結局メールアドレスでログインしたら こんなかんじのメールがきて復活しました。 ついったーのアカウントを復活させてくれて、ありがとう! ブラウザでこのリンクを開いてください: http://twitter.com/account/confirm_restore?email**** このURLにアクセスすることで、あなたのアカウントが復活します。 ついったーチームより あなたのついったー使用に対してのお知らせです。 サポート管理外のメールアドレスからのメッセージのため、このメールに返信しないでください。 ご質問やカスタマーサポートを希望される方はついったーカスタマーサポートまでご連絡ください。 まあ復活したとはいえ1時間に何回も更新されるようなものではないのでw とりあえずフォローは遠慮なくどうぞ Twitterアカウント: jakkrokk ついでに サイドバーにもちょろっと貼っておこうとか思ったんだけど いいのがなくて。 (ブログに合うやつがね) しょうがないので落ちてたjsのソースをちょっと書き換えて表示させてみました。 ### BINGのAPI 調べる必要があったので、ついでにメモ。 とりあえず、割と?情報は充実しているようだけど 英語オンリーっぽいので。 APPIDはここで取得 http://www.bing.com/developers/ ->サイドバーの 「Create an AppID」 だいたいの情報はここに http://msdn.microsoft.com/en-us/library/dd251056.aspx まあSOAPでもRESTでもOKで、 結果もJSONでもXMLでも取得できますよってとこです。 とりあえず一番使うであろうRESTでのクエリーはこれ http://api.bing.net/xml.aspx?AppId= YOUR_APPID &Version=2.2&Market=ja-JP&Query=検索クエリー&Sources=web&Web.Count=1 web検索系のオプション Web.*** っていうのがWeb(Sourcesで選んだもの)のオプションです。 今回の場合 Web.countだけですが。 Web.Count は取得件数のことで1-50の間の値のみOK。 これ以外のオプションとして http://msdn.microsoft.com/en-us/library/dd250876.aspx Web.FileType(ファイルタイプ) http://msdn.microsoft.com/en-us/library/dd250920.aspx Web.Offset(ページの開始位置) などがあります。 SourceType SourceType も結構色々合って、面白そうです。 http://msdn.microsoft.com/en-us/library/dd250895.aspx InstantAnswerとか面白そうなんだけど、 日本語じゃ上手く動いてないみたいですね。 やり方が違うのかな。 Translationはそのまま翻訳。 http://api.bing.net/xml.aspx?AppId=APPID&Version=2.2&Query=Translation&Sources=Translation&Translation.SourceLanguage=En&Translation.TargetLanguage=Ja こんなかんじでサクっと取得できます。 一応取得結果も 通常Web検索 http://api.bing.net/xml.aspx?AppId=APPID&Version=2.2&Market=ja-JP&Query=転職&Sources=web&Web.Count=1 <SearchResponse xmlns="http://schemas.microsoft.com/LiveSearch/2008/04/XML/element" Version="2.2"> <Query> <SearchTerms>転職</SearchTerms> </Query> <web:Web xmlns:web="http://schemas.microsoft.com/LiveSearch/2008/04/XML/web"> <web:Total>3620000</web:Total> <web:Offset>0</web:Offset> <web:Results> <web:WebResult> <web:Title>転職はエンジャパンの転職サイト [en]社会人の転職情報</web:Title> <web:Description>転職経験者が選んだ、総合評価No.1転職サイト。様々な職種・希望条件か...</web:Description> <web:Url>http://employment.en-japan.com/</web:Url> <web:CacheUrl>http://cc.bingj.com/cache.aspx?q=&d=76586192667331&mkt=ja-JP&w=f10d1e04,45e7a1ad</web:CacheUrl> <web:DisplayUrl>employment.en-japan.com</web:DisplayUrl><web:DateTime>2009-08-19T02:01:43Z</web:DateTime> </web:WebResult> </web:Results> </web:Web> </SearchResponse> 翻訳 http://api.bing.net/xml.aspx?AppId=APPID&Version=2.2&Query=Translation&Sources=Translation&Translation.SourceLanguage=En&Translation.TargetLanguage=Ja <SearchResponse xmlns="http://schemas.microsoft.com/LiveSearch/2008/04/XML/element" Version="2.2"> <Query> <SearchTerms>Translation</SearchTerms> </Query> <tra:Translation xmlns:tra="http://schemas.microsoft.com/LiveSearch/2008/04/XML/translation"> <tra:Results> <tra:TranslationResult> <tra:TranslatedTerm>翻訳</tra:TranslatedTerm> </tra:TranslationResult> </tra:Results> </tra:Translation> </SearchResponse> ### PHPのGDライブラリだけで角丸する imagick使ったほうが明らかに綺麗&楽にできるんだけど 勉強がてらGDオンリーでやってみた。 結果 やっぱ綺麗に抜けないw あとimagickだと3行くらいのソースが無駄に長くなるのがね。 実用的ではないです。 まあ一応GDで画像操作するコツみたいなのはわかったので 無駄ではないかな。。。 一応こんなかんじになりました。 //角の大きさ $cornerRad = 15; //元画像の読み込み $originalImage = imageCreateFromJPEG('images/test.png'); //角画像の読み込み $cornerPng = ImageCreateFromPNG('rounded_corner.png'); //角をリサンプリングするための画像 $cornerResized = imageCreateTrueColor($cornerRad,$cornerRad); //リサンプリング用の画像(真っ黒)に角画像をコピーしてついでに大きさを調整する($cornerRad) imageCopyResampled($cornerResized,$cornerPng,0,0,0,0,$cornerRad,$cornerRad,imageSX($cornerPng),imageSY($cornerPng)); //黒を透明にするため黒を定義 $black = imageColorAllocate($cornerResized,0,0,0); //リサンプル画像の黒を透明にする imageColorTransparent($cornerResized,$black); //元画像のデータ取得 $sizeX = imageSX($originalImage); $sizeY = imageSY($originalImage); //元データの左上の角を丸める imageCopyMerge($originalImage,$cornerResized,0,0,0,0,$cornerRad,$cornerRad,100); //imageRotateで角丸画像を回転させて、元データの右上の角を丸める $rotated = imageRotate($cornerResized,90,0); imageCopyMerge($originalImage,$rotated,0,$sizeY - $cornerRad,0,0,$cornerRad,$cornerRad,100); //imageRotateで角丸画像を回転させて、元データの左下の角を丸める $rotated = imageRotate($cornerResized,270,0); imageCopyMerge($originalImage,$rotated,$sizeX - $cornerRad,0,0,0,$cornerRad,$cornerRad,100); //imageRotateで角丸画像を回転させて、元データの右下の角を丸める $rotated = imageRotate($cornerResized,180,0); imageCopyMerge($originalImage,$rotated,$sizeX - $cornerRad,$sizeY - $cornerRad,0,0,$cornerRad,$cornerRad,100); //画像出力 header("Content-type: image/jpeg"); imagePNG($originalImage); imageDestroy($originalImage); imageDestroy($cornerPng); 左上の画像をPNGで作って こんなやつ (背景は黒じゃなくて透明でもOK:どっちにしろ黒バックの画像にリサンプルするから) それを真っ黒な画像にリサンプルして黒をimageColorTransparentする でそれを90度ずつ回転させながら角に当ててくってかんじです。 ### さくらレンタルサーバでメールを受信したらPHPを動かす https://neoinspire.net/rental-server-sakura PEARがPEARがって検索したら結構出てきたんですが、 PEARは使わないので自分で書いてみることに。 以前 OpenPNEをセットアップした時にやったので、さくっと .mailfilter を設定して (ここら辺参照 http://sumidagawa.sblo.jp/article/11226948.html ) メールデータを mail.php に飛ばす。 そうするとmail.phpに直で Return-Path: <aaa@a.com> Received: from ***.sakura.ne.jp (***.sakura.ne.jp [210.224.165.45]) by ***.sakura.ne.jp (8.14.3/8.14.3) with ESMTP id n6H7MnFd094136 for <php@phpdata>; Fri, 17 Jul 2009 16:22:49 +0900 (JST) (envelope-from aaa@a.com) Received: from a.com (a.com [***.***.***.***]) by ***.sakura.ne.jp (8.14.3/8.14.3) with ESMTP id n6H7Ml98094112 for <php@phpdata>; Fri, 17 Jul 2009 16:22:47 +0900 (JST) (envelope-from aaa@a.com) Received: by yxe11 with SMTP id 11so1156534yxe.3 for <php@phpdata>; Fri, 17 Jul 2009 00:22:46 -0700 (PDT) MIME-Version: 1.0 Received: by 10.150.146.14 with SMTP id t14mr1339668ybd.142.1247815366595; Fri, 17 Jul 2009 00:22:46 -0700 (PDT) Date: Fri, 17 Jul 2009 16:22:46 +0900 Message-ID: <710e6c850907170022u465e2601oe8fcdb18fad6bafa@a.com> Subject: =?ISO-2022-JP?B?GyRCJCIkJCQmJCgkKhsoQiBhaXVlbw==?= From: j <aaa@a.com> To: php@phpdata Content-Type: text/plain; charset=ISO-2022-JP Content-Transfer-Encoding: 7bit X-Anti-Virus: K-Prox Anti-Virus Powered by Kaspersky, bases: 17072009 #2246191, status: clean ここに本文が入りますよー ここに本文が入りますよー ここに本文が入りますよー こんな感じのデータが飛んでくるのでそれをばらして保存するだけ。 ってことで、こんな感じで書いてみた。 <?php //mail.php mb_language('Japanese'); mb_internal_encoding('UTF-8'); mb_detect_order("ASCII,JIS,EUC-JP,SJIS,UTF-8"); $mailData = array(); $inputedMail = new NoRewindIterator(new SplFileObject("php://stdin")); foreach ($inputedMail as $eachLine) { $matchCount = preg_match("/^(.*?): (.*?)$/",$eachLine,$line); switch ($line[1]) { case 'Date': $mailData['date'] = $line[2]; break; case 'To': $mailData['to'] = $line[2]; break; case 'From': $mailData['from'] = $line[2]; break; case 'Content-Type': $mailData['content-type'] = $line[2]; break; case 'Subject': $sub = explode('?',$line[2]); switch ($sub[2]) { case 'B': $mailData['subject'] = mb_convert_encoding(base64_decode($sub[3]),'UTF-8','ASCII,JIS,EUC-JP,SJIS,UTF-8'); break; case 'Q': $mailData['subject'] = mb_convert_encoding(quoted_printable_decode($sub[3]),'UTF-8','ASCII,JIS,EUC-JP,SJIS,UTF-8'); break; default : break; } break; default : if ($matchCount==0 && isset($mailData['content-type'])) { $mailData['body'] .= mb_convert_encoding($eachLine,'UTF-8','ASCII,JIS,EUC-JP,SJIS,UTF-8'); } break; } } //print_r($mailData); Array ( [date] => Fri, 17 Jul 2009 16:22:46 +0900 [subject] => あいうえお aiueo [from] => *** [to] => ***@***.*** [content-type] => text/plain; charset=ISO-2022-JP [body] => ここに本文が入りますよーここに本文が入りますよーここに本文が入りますよー ) ?> 添付ファイルの処理とか text/html の処理とかは別途書かないといけないけど、 基本的にこんな感じで動くので、 割と楽にメールベースの運用アカウント管理ツールとかが作れるという。 まあめんどくさいので、ペンディングですがw   https://neoinspire.net/rental-server ### ユーズドドメイン オールドドメイン とはチョット違いますが、 いわゆるユーズドドメイン(ドロップドドメイン?)ってやつを 検索するサイトを作ってみました。 Used-Domain Checker ドメインエイジは流行らないよとか昨今聞きますが、 基本的にユーズドドメインは最初からPRがついてるとか そんなんじゃなくて、以前違うサイトがあったというだけのものです。 なので、ちょっとだけリンクが付いてたりしますが、 基本的には すぐクローラーがくるということ以外には 大して効果があるわけではありません。 それでもサテライトサイト作る時とか、 SEOをちょっとでも意識するのであれば すぐクローラーが来るのは便利なので、 こういうのを使ってもいいかなとおもって作りました。 ユーズドドメインチェッカー ### Wordpressのパーマネントリンクを切り替えた時のリダイレクト設定 そういえば元もとこのブログはWordpressのデフォである http://dev.neoinspire.net/?p=1 みたいなURLだったんですが、 いつかGoogleのテストもかねて今の形に変えたんですよね。 そんときにぜんぜん切り替わってくれなかったのは覚えてるんですが、 ふと Google Webmaster Toolsを見ていたら旧URL経由で 404を返すページが結構あることに気づいて、 しょうがないので .htaccessでフォルダごと というか 正規表現マッチで全部移動させることに。 まあ一瞬だろ っておもってこんな感じで書く。 Redirect permanent /?p=(.*)$ http://dev.neoinspire.net/archives/$1 動かないww で10分ほど調べたところでポン。 RedirectMatch じゃんね。 ついつい RewriteRule の時のようにそのまま正規表現書いちゃうんだけど Redirectじゃ正規表現使えなくて RedirectMatchを使うんだった。。。 ってことでこんなかんじになりました。 RedirectMatch permanent /?p=(.*)$ http://dev.neoinspire.net/archives/$1 これで /?p=*** を全部今のURLに301で飛ばしてくれる! ### 天下一プログラマーのお題 天下一プログラマー 凡愚なので、すらすらとは解けないわけです。 というかビットがどうのなんて普段触らないので いまいちピンとこなかったわけですが、 なんとなしに packかなーとおもってやってみたらできた。 (これは10分でわかった!) echo mb_strlen(pack('H*','e4bba5e4b88be381aee69687e5ad97e58897e381af5554462d38e38292e69687e5ad97e382a8e383b3e382b3e383bce38387e382a3e383b3e382b0e5bda2e5bc8fe381a8e38199e3828b3136e980b2e695b0e381aee38390e382a4e38388e58897e381a7e38182e3828be38082'),'UTF-8'); //41 他にもやり方あるかなーとおもって 勉強ついでに手でpackしてみる (こっから1時間コース orz) $odai = "e4bba5e4b88be381aee69687e5ad97e58897e381af5554462d38e38292e69687e5ad97e382a8e383b3e382b3e383bce38387e382a3e383b3e382b0e5bda2e5bc8fe381a8e38199e3828b3136e980b2e695b0e381aee38390e382a4e38388e58897e381a7e38182e3828be38082"; //これで16進数文字を2進数に直して for ($i=0;$i<strlen($odai);$i++) $dec .= str_pad(base_convert($odai[$i],16,2),4,0,STR_PAD_LEFT); //正規表現でとってみる print(preg_match_all("/(0[0-1]{7})|(110[0-1]{13})|(1110[0-1]{20})/u",$dec,$match)); //41 //真面目にループしてみる $strCount = 0; for ($i=0;$i<strlen($dec);$i+=8) { if (substr($dec,$i,1) === '0') { $strCount += 1; } else if (substr($dec,$i,3) === '110') { $strCount += 1; $i +=8; } else if (substr($dec,$i,4) === '1110') { $strCount += 1; $i +=16; } } print($strCount); //41 意外とできるもんだ~ さて天下一プログラマーはどんな答えになるんだろう? --- ぐぐって偉い人の答えを見てると だいたい [89ab]. で直で正規表現でとってるようで。 UTF8の仕組みがよくわかってないとだめだなぁ。 ### 検索1発でGoogleをバグらす方法 たまたま見つけたのでw これでGoogleで検索してください。 allintext:"aiueo" 別に aiueo はなんでもいいんですが、 このコマンドをGoogleにぶちこむと自分のPCだと 100% 申し訳ありません ... コンピュータ ウィルスやスパイウェア アプリケーションの自動リクエストと類似したクエリが送信されました。 ユーザーを保護するため、このリクエストをすぐに処理することはできません。 の画面になります。 もちろんコロンはずせば allintext:aiueo ちゃんと機能するので別におかしいわけじゃないんだけど。 まあ理由もなんとなく想像つきますがw ### Papervision3D + SHADEができるのね 3Dかふーんとか思ってちょっと線つけるくらい遊んで終わってたネタですが、 ここらへんとか http://blog.livedoor.jp/techblog/archives/65135414.html ここらへんとか http://d.hatena.ne.jp/haru-komugi/20071123/1195829334 ここらへんとか http://www.libspark.org/wiki/rch850/Metasequoia みて革命がおきたw まさかメタセコや六角からインポートできるとか。。。 っていうかまさかDAEデータ食わせれるとは。。。 つかSHADEってDAEデータ作れたよな。。。 じゃあDAEファイルにすればFlashで読み込めるじゃんね!まじで! とまあ一人で超もりあがってました。 まあそのままやるわけにはいかなくて、 基本BMPで作ってるテクスチャ系は全部JPEGなりに当てなおさないといけないし (SHADE5(古)だとJPEGでテクスチャ当てれなかったきがする。。) 大学の頃ガンダムを3Dで作りまくって遊んでたので、 (こんなの) SHADE結構得意なので、 3Dに起こしてみようかなとおもった! が マダヤッテナイ。 だってSHADEデータのレンダリングに1発3日とかかかるから。。。 ### phpでcannot use string offset as an arrayエラー Yahooの商品検索Apiの php serialized を使っていたら 「cannot use string offset as an array」 とかいう見たこともないエラーが出たので対応メモ。 エラーが出るコードはこんなかんじ //めんどくさいので取得の部分はぶきますが //Yahoo APIで phpのserializedデータを取得 $unserializedData = unserialize(file_get_contents('http://shopping.yahooapis.jp/ShoppingWebService/V1/php/itemSearch?')); foreach ($unserializedData['ResultSet'][0]['Result'] as $k=>$v) { print($v['Image']['Medium']); //これがPHP5だとエラーになる4だとOK } 肝心のYahooの商品検索Apiのリターンはこんなかんじ(適当にはしょってます) [ResultSet] => Array ( [totalResultsAvailable] => Integer [totalResultsReturned] => Integer [firstResultPosition] => Integer [0] => Array ( [Result] => Array ( [Request] => Array ( [Query] => String ) [Modules] => String [0] => Array ( [Name] => String [Description] => String [Headline] => String [Url] => String [Availability] => String [Code] => String [PersonId] => String [ProductId] => String [Image] => Array ( [Id] => String [Small] => String [Medium] => String ) ) 原因 エラー内容はStringなのにオフセットに配列つかうんじゃねえ つまり文字列を対象に3次元配列としてアクセスしちゃうとPHP4ではなんもしないもに PHP5ではエラーになっちゃうってことですね。 ようは ResultSet->0->Result でループをぶん回すと (Array)Request とか (String)Modules とかがまざっちゃうわけですねハイ。 おそらくこのゴミも全部foreachかけてるのが原因ぽい。 一応ぐぐったらこんなのも出てきた。 https://www.zend.com/forums/index.php?t=msg&th=1556&S=c38b4b1a45a1dfba92db79dd967cc302 つまるとここういうこと $array['hoge'] = "nemu"; print($array['hoge'][1]); //e が表示される print($array['hoge'][1][1]); //PHP4だと何も表示されない //PHP5だとcannot use string offset as an array 対応策 issetなりis_arrayなりで$v['Image']がある配列だけ作業するようにするのが 一番無難かな。 $unserializedData = unserialize(file_get_contents('http://shopping.yahooapis.jp/ShoppingWebService/V1/php/itemSearch?')); foreach ($unserializedData['ResultSet'][0]['Result'] as $k=>$v) { if (is_array($v['Image'])) { print($v['Image']['Medium']); } } 結局 配列とハッシュをごちゃごちゃにまとめるんじゃねえってかんじかな。。 せめて [Result] => Array ( [Request] => Array ( [Query] => String ) [Modules] => String [resultData] => Array ( [0] => array [1] => array ) こうしてほしいな。 絶対みんなここをforeachすんだから無駄な情報は移動しておいてほしいわ。。。 おすすめのレンタルサーバーを徹底比較! ### php + mysql でランダムデータの抽出方法 DBからランダムデータを10個抽出するのに SELECT id,contents FROM table ORDER BY rand() LIMIT 10; みたいな書き方をしていたんですが、 最初のうちはよかったんですが、 テーブルが1万越えてきたあたりからやったら遅くなってきて、 Copying to tmp table連発で激重+コネクション圧迫が ひどくなってきたので改善することに。 とりあえず当該テーブルで explain するとこんなかんじ EXPLAIN SELECT id,contents FROM table ORDER BY rand() LIMIT 10 table table type ALL possible_keys NULL key NULL key_len NULL ref NULL rows 29302 Extra Using temporary; Using filesort rows :29302 なので全件走査(良く考えたら当たり前か。。) なので Using temporary してるわけで、 SQLでなんかできるかなというのは無理だったので、 phpで対応することに。 対応っていっても、簡単にこんな感じで直しただけで劇的に速度が上がりました。 //件数を取得して $count にいれて、 SELECT count(*) as count FROM table; //0 ~ $count までの配列を作ってそれをランダムに10個取り出して , で join してIN句に突っ込む "SELECT id,contents FROM table WHERE id IN (".join(',',array_rand(range(0,$count),10));" ### Wordpressのテーマを変えました。ついでに複数widget対応してみました。 ちょうど自作のWordpressに乗り換えてから丸二年たったこと、 また長く前のテンプレートを使ってたのと、 仕事でテンプレートの量産をすることになったので、 勉強がてら落ちてるテンプレを回収して とかやってました。 意外とめんどかった。。 今回手間取ったのが複数widget対応で 検索して出てくるブログがどれこれも間違ってるww google先生もそのソースが動くかどうかまでは検索結果に反映してくれないようです。 で、その動かないソースは大抵こんなかんじで書いてあって、 if ( function_exists('register_sidebar') ) { register_sidebars(2); register_sidebar(array(1, 'before_widget' => '', 'after_widget' =>'', 'before_title' => '', 'after_title' => '', )); register_sidebar(array(2, 'before_widget' => '', 'after_widget' =>'', 'before_title' => '', 'after_title' => '', )); } これじゃ動きません。 しかもこんな感じのソースが3つくらいブログにのってたなぁ。 コピペしただけで、使わずに満足でもしたんだろうか。。 で結局ソースを当たって、あとドキュメント読んだらこんなかんじでした。 http://codex.wordpress.org/WordPress_Widgets_Api/register_sidebars まずfunction名が register_sidebars です。 で次に register_sidebars(2); これはいらない。 そして register_sidebar(array(1, これは関数名も違うし引数の順番も違います。 正しくは第一引数がwidget番号、第二引数がハッシュで表示するwidgetのテンプレート部分です。 実際はこうなります。 if ( function_exists('register_sidebars') ) { register_sidebars(1,array( 'before_widget' => '', 'after_widget' =>'', 'before_title' => '', 'after_title' => '', )); register_sidebars(2,array( 'before_widget' => '', 'after_widget' =>'', 'before_title' => '', 'after_title' => '', )); } まあだけど 修正はめんどくさいけど、テンプレートはいいのがたくさん落ちてるし、 CMSはやっぱり楽でいいですね。 ### TinyTodoが窓の杜で紹介されました これはうれしい!!! 窓の杜とVectorっていうと 相当前から知っていて (Win3.1から95に乗り換えた時くらいじゃないか?) まあ 超有名ですよねーってことで。 seo-scouterのときも結構紹介していただいて、 メールももらったんですが、(Grevaさんやウィジェットタウンさんとか) 個別にしてたらキリないしとおもって、感謝のメールだけ返信してたんですががが (本当に有難うございます!) まあ これは記念てことでw http://www.forest.impress.co.jp/article/2009/05/26/tinytodo.html ネタがあればもっと作っていこう とモチベーションが上がった一日でした。 それにしても もっとヴィジュアル凝ればよかったとかいまさら。。。 ### Flexで phpでいうsleepみたいなことをしてhttp requestを大量に発行する イベント駆動のプログラミングがここまで違うとは思わなくて、 思わぬところではまったのでめも。 PHPならこう書きたい・・ ハマったのは、phpで言うところの foreach ($myRssUrlList as $key=>$url) { $html[] = file_get_contents($url); } みたいなことがしたかったんですが、 イベント駆動の考え方でこれがめんどくさくって。 Flex的には file_get_contents にイベントを設定して、 file_get_contentsが完了したら次にやる っていう感じに書き方をするわけで、 そのまま書くとこうなっちゃう。 <mx:HTTPService id="httpService" /> function httpReq():void { for each (var url:String in myRssUrlList) { httpService.url = url; httpService.addEventListener(ResultEvent.RESULT,function():void{}); httpService.send(); } } (myRssUrlListにはRSSのURLが10個くらい入ってるイメージ) が、 これだとうまくいかない。 どうなるかというと、 例えばmyRssUrlListにURLが10個入ってると、 最後のURLの結果が10回取得されて終わっちゃう。(自分の環境では) ほぼ同時に(一応順番どおりだけど)リクエストを全部走らせて、 終わったものから順にfuntionを走らせるので、 1個のオブジェクトの結果をひたすら上書きしていて、 イベントのfunctionが走るタイミングでは、指定したURLの結果かどうかが担保されていないから。 さらに全部ほぼ同時にrequestが走るので、10とかならまだしも10000とかURLリストがあると 即効落ちるw でしょうがないので、毎回オブジェクトを作ることにする。 function httpReq():void { for each (var url:String in myRssUrlList) { var httpService:HTTPService = new HTTPService(); httpService.url = url; httpService.addEventListener(ResultEvent.RESULT,function():void{}); httpService.send(); } } こうすると毎回別のオブジェクトなので、うまく取れるようになるわけだけど、 やっぱり問題は全部同時に走らせちゃうこと。 で、次に思ったのが、 foreach ($myRssUrlList as $key=>$url) { $html[] = file_get_contents($url); sleep(3); } こう書きたい。 ので、 flexでsleepを探してみる。 javascriptでも似たようなことで詰まった覚えがあるので、 あんまり期待しない。 Flex で Sleep的な~ で、 やっぱ sleepみたいなことはなかったけど、(探したりないだけかもしれないけど) 変わりに Timer();を使うといいとか書いてあったので、それを使ってみる。 //HTTPリクエストのタイマー private function httpQue():void { var timer:Timer = new Timer(2000,myRssUrlList.length); //2秒ごとにイベント発動 for each (var url:String in myRssUrlList) { //とにかくmyRssUrlListの数だけtimerイベントを発動させる timer.addEventListener(TimerEvent.TIMER,httpReq); } timer.start(); } //HTTPリクエストを発行するメソッド private function httpReq(evt:Event):void { var url:String = myRssUrlList.shift() as String; var httpService:HTTPService = new HTTPService(); httpService.url = url; httpService.addEventListener(ResultEvent.RESULT,function():void{}); httpService.send(); } これで2秒置きにHTTPリクエストが発行できるようになる。 とりあえず10000くらいは処理できたので、 あとはPCがんばれ! ちなみに タイマーのところで これを timer.addEventListener(TimerEvent.TIMER,httpReq); こうかいちゃうと timer.addEventListener(TimerEvent.TIMER,function(evt:TimerEvent):void{httpReq(url);}); functionの生成の順番のせいで、最後のURLが回数分だけアクセスされちゃうことになるので注意。 この生成の順番で超はまってしまった。。 最後 ながかった。。 ### FlexでAccordionHeaderにつけたボタンに押した感を出す FlexでAccordionHeaderにボタンを配置の続きで、 AccordionHeaderにつけたボタンに押した感を出すやり方。 やり方としては2段階にわけて、 まずはボタンの上にマウスが来たら指マークにして、 その後その部分をダブルクリックしたら、イベントをさらに発生させる ようにします。 こんなかんじ とりあえず前提としてAccodionにイベントを埋め込んでおく //Accordionはこうかいておいてイベントリッスンさせる 各イベント //マウスカーソルがボタンの上にきたら指マークに変更 private function dispatchMouseMove(e:MouseEvent):void { var accHeader:AccordionHeader = new AccordionHeader(); var button:Button = new Button(); //AccordionHeaderをすべてチェックする for (var i:int = 0;i < myAccordionHeader.numChildren;i++) { accHeader = myAccordionHeader.getHeaderAt(i) as AccordionHeader; button = accHeader.getChildAt(0) as Button; //ボタンの上にマウスがあるかをチェックする if (button.getRect(this).useHandCursor(e.stageX,e.stageY)) { //マウスがボタンの上にいたら useHandCursorをtrueにする accHeader.buttonMode = true; accHeader.useHandCursor = true; } else { accHeader.useHandCursor = false; } } } まあ経験が浅いので上記のやり方くらいしか思いつかなかったんですが、 AccordionHeaderのボタンの位置とマウスカーソルの位置をチェックして、 contains() でチェックしていくというやり方です。 指カーソルに変更するのは、別のやり方もあると思いますが、 とりあえずAccordionHeaderのプロパティを書き換えるだけでも上手くいきます。 で、次に指カーソル状態でダブルクリックをチェックします。 //ボタンの上でダブルクリックされたらイベント発動 private function dispatchDblClick(e:MouseEvent):void { var accHeader:AccordionHeader = new AccordionHeader(); var button:Button = new Button(); //AccordionHeaderをすべてチェックする for (var i:int = 0;i < myAccordionHeader.numChildren;i++) { accHeader = myAccordionHeader.getHeaderAt(i) as AccordionHeader; button = accHeader.getChildAt(0) as Button; if (button.getRect(this).contains(e.stageX,e.stageY)) { accHeader.addEventListener(MouseEvent.CLICK,myDoubleClickEvent); accHeader.dispatchEvent(new MouseEvent(MouseEvent.CLICK)); } } } こっちも同じようにAccordionHeaderのボタンの位置と マウスカーソルの位置をチェックして、 contains() でチェックしていくというやり方です。 一応実装してみた 一応このAccordionはTinyTodoというアプリで実装してみたので、 よかったら見てみてください。 Air製Todo管理ツール ### FlexでAccordionHeaderにボタンを配置 FlexでAccordionHeaderにボタンを配置 SeoScouterのブクマのタグがFlexになっていて、 いや。。これAjaxなんだよな。。。 と思いつつ、ついでにFlexも勉強するかなーってことで、始めてみたら・・ 面白い!!! ということで土日からがっつりやってます。 今ちょうど、Todo管理ツールが欲しかったので、 それを作成することに。 まあもうほとんどできたので、 あとはテストで自分で使ってみるだけなんですが。 こんなかんじ。 アコーディオン形式にしようとおもって、 アコーディオンで作り始めたら、このヘッダーの部分に クリッカブルなボタン入れたり、それをマウスオーバーで指ポインタにしたり とか そういうのが結構大変だったので、 随時メモっていきます。 ---- で、ボタンの配置は そのまま accordion.***=ボタン みたいにはかけなくて、(iconなら入れれますが) mxmlで配置したaccordionを ActionScriptでAccordionHeaderをextendしたクラスを書いて、 super()してaddchildする方法(AccordionHeaderが出力される際に必ず処理される)と、 一旦配置したaccodionのchildを accordion.getHeaderAt(i) as AccordionHeader みたいなかんじで、順番に取得して、AccordionHeaderとして そこに addchildする(こっちなら特定のだけボタンを配置できる) 方法の2個ができるのがわかって、マウスオーバーとかもしたかったので、 すぐ実装が理解できた下の方で実装しました。 こんなかんじ。 //アコーディオンを配置 <mx:Accordion id="accMain" openDuration="300" doubleClickEnabled="true" doubleClick="dispatchDblClick(event)" mouseMove="dispatchMouseMove(event)" /> <mx:Script> <![CDATA[ pricate function initAccordion():void { //アコーディオンのchildの数だけforをまわして、buttonをつけていく for ( var i:int = 0; i < accMain.numChildren; i++ ) { var button:Button = new Button(); button.width = 20; button.height = 20; button.styleName = "button"; (accMain.getHeaderAt(i) as AccordionHeader).addChild(button); } } ]]> </mx:Script> アコーディオンのパーツをどこかで書き出した後、initAccodion()で アコーディオンのchildをすべて取得して、それぞれにaddChildしています。 ただ、これだと、ヘッダーにかぶさって? かわかりませんが、ボタンが押した感じがしないので、 マウスオーバーで指さしマークにしたいとおもいますが、 長くなるので、わけます。 ### AirでGoogleにログインする Airで というよりも jqueryで というほうが正しいきもしますが、 seo-webmasterではRESTでGoogleにログインするわけですが、 実装はこんなかんじの雑なソースになっております。 var auth_url = "https://www.google.com/accounts/ClientLogin"; var auth_id = $("#auth_id").val(); //フォームのIDを取得 var auth_pass = $("#auth_pass").val(); //フォームのパスワードを取得 $.ajax({ url : auth_url, type : "POST", data : {"accountType":"GOOGLE","Email":auth_id,"Passwd":auth_pass,"service":"サービス名","source":"ソース名"}, error : function(XMLHttpRequest, textStatus, errorThrown){ //エラー処理 }, success: function(rs) { var token = rs.match(/Auth=([a-zA-Z0-9_\-]+)/); //ここでトークンを切り出して保存する $('#SITE-TOKEN').val(token[1]); } }); これはPOSTなので、いいのですが、次はサイトの認証。 こっちはPUTを使います。 var webmaster_url = "https://www.google.com/webmasters/tools/feeds/sites/" + encodeURIComponent(サイトID).replace(/\./g,""); var token = $('#SITE-TOKEN').val()//認証トークン; //送信するXMLを生成する var xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?><atom:entry xmlns:atom='http://www.w3.org/2005/Atom' xmlns:wt='http://schemas.google.com/webmasters/tools/2007'><atom:id>" + site_id + "</atom:id><atom:category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/webmasters/tools/2007#site-info'/><wt:verification-method type='htmlpage' in-use='true'/></atom:entry>"; $.ajax({ url : webmaster_url, type : "PUT", processData : false, //デフォルトがtrueなので必ずきってく dataType:"xml", //なくても動いたけど一応 受信コンテンツタイプの指定 contentType : "application/atom+xml; charset=UTF-8", //コンテンツタイプXMLにしないとデフォルトが urlencodedなのでエラーになる data: xml, //送信xml beforeSend : function(req) { //ログイン時の認証トークンを送る req.setRequestHeader('Authorization', 'GoogleLogin auth=' + token); }, error : function(XMLHttpRequest, textStatus, errorThrown){ //エラー処理 }, success: function(rs) { //成功時の処理 } }); ポイントとしては processData をきることと、 contentType をちゃんとatom+xmlにすること、 それからbeforeSendでsetRequestHeaderにトークンを埋め込むことかなー ここらへんのリファレンスみたいなのがあんまなくて(読みかたがわからないだけかも) 適当に大文字にしたり小文字にしたり半角スペース入れたり してたら通った感じなので、もしかしたら間違ってるかも。 AirでGoogle認証を使うならこんなかんじで試してみてください。 ### Air-Seo Webmasterを公開しました サイトマップの一括更新(一括でもないんだけど) を手早くやるために作ったツールです。 よかったら使ってみてください。 http://dev.neoinspire.net/air-seo-download ついでにAir-Seo用のページを作ってそっちに移動~ 余談ですが 結局サイトの管理はjqueryでPUTが上手くいかなくてパスw そのうちバージョンアップしたときにできればいいかなぁ。 それにしてもDELETEがうまくいくのになんでPUTがうまくいかんのだろう。。。 ### 楽にキャッシュしたいなぁ[php] PHPでキャッシュっていうと いちいち 最初にファイル名指定して、キャッシュがあるかみて、 なければ処理 -> あったら最後にキャッシュ書き出し処理を書くかんじが デフォっぽいんですが、 なんていうか イメージ的にはこう $Object->method();//キャッシュの処理をメソッド内に書いてある $Object->cache()->method(); みたいな感じで書くだけでいいようにしたいてーきーなー(ぱくり) ことが急激にしたくなったので、色々と考えてました。 まだ完成はしていないんだけど、 とりあえずこういう風にかけるんだと思ったのでメモ。 function hoge($className,$args) { $NewObject = new $className($args); } そういえば いつもLDRで見てるブログさんがエラーで残念なことになってた。 本日アクセスしたらこんな感じのエラーがでていました。 path丸わかりですし、 display_errors は忘れずにオフにしたほうがいいかとおもいます。(自戒的な意味もこめて) ### ある文字列を数値に変換 (php) いや 何をいってるかわからないとおもうが。。。(ry なんでこんなことしなきゃいけないのかわかりませんが、 文字列が必ず毎回同じ(int)数値になるようにしなきゃいけなくなりまして。 こんな感じでかいてみました。 $word2Int = (int)("1".substr(preg_replace("/[^0-9]+/","",md5($word)),1,9)); 一応コメントをつけるとすれば 文字列をmd5して、そこから0-9以外の数値を切り取って、 PHP_INT_MAXの値の桁数くらいまでに抑えるようにsubstrして 頭が0だと8進数になって8,9の数値がぶっ壊れるので適当に頭は1固定とかにして、 それを(string)から(int)キャストするかんじです。 こんなかんじ。 $word2Int[] = (int)("1".substr(preg_replace("/[^0-9]+/","",md5("ばかやろー")),1,9)); $word2Int[] = (int)("1".substr(preg_replace("/[^0-9]+/","",md5("あほやろー")),1,9)); $word2Int[] = (int)("1".substr(preg_replace("/[^0-9]+/","",md5("")),1,9)); var_dump($word2Int); array(3) { [0]=> int(1523827225) [1]=> int(1877443054) [2]=> int(1189800204) } われながら気持ち悪いなw 書くことないとはいえ。 どうでもいいけどこれやばい array_unique関数がPHP5.2.9から後方互換性を失いました - hnwの日記 だからPHPはカオスとか言われるんだよ。。。 ユニーク一つ満足に取れないとは なんてユニークな言語になってしまったんだ。 ### YahooのLink: ツールを作っていて、YahooのLink:を全部出すことになったんですが、 これが意外とやっかいで。 普通にやると1000でストップ。 もちろんページングを1000以上にしても1000のデータしかでないわけで、 じゃあAPIはどうだーっていってもAPIも同じ。 で、色々と最近知り合いになれたSEOの本物のプロの方に聞いたら 本来手作業でやってるやりかたを教えてくれて、 (自分でも近いやり方は思いついたんだけど、ちょっとずれてたなぁ) なんとか上手くとれました。 やっぱプロのSEO研究者は違う。 一応その方に見せたらほぼ誤差がないっておっしゃっていただけたので、 満足満足。 ---- まあ、その普段見ることのできないバックリンクとかみてると ほんと ダークな世界だな~とにやにやしちゃいますねw どんなにホワイトだ、ダークだいっても結局こうなんだなーと思いました。 バックリンクを1000件以上調査するなんて、 それこそプロユースというかプロオンリーのツールなので、 公開はしませんが、 まあほしいなら10万くらいで売りますかw イラナイナ。 GoogleはそもそもLink:で10万も20万も出ないから必要ないんだけど Yahooは普通に64万とか出てくるから 全調査するのならツールは必須だと思うんですが、 これあんまりできてる人がいないラシイ?と聞いたのが意外でした。 ### jquery で putできない airで google webmaster tool の一括操作をできるツールを作っていて、 ほぼできてはいるのですが、サイトの認証の部分だけどうしてもできなくて。 サイト認証には REST の PUT を使うんですが(唯一) そのPUTがおかしい? jquery を使ってRESTなAPIにXMLを送りつけてDELETEとか で書いたようにDELETEは動かしていたんですが、 PUTもできる と書きつつもどうもうまくいかないので。 $.ajax({ url : url, type : "DELETE", PUTも可(ブラウザに依存) contentType : ‘application/atom+xml; charset=UTF-8′, 設定しないとapplication/x-www-form-urlencodedになっちゃうので必須 processData :false, 設定しないとapplication/x-www-form-urlencodedなGETクエリに変換にしちゃうので必須 data: xml_document, ←XMLドキュメント本体 beforeSend : function(request) { request.setRequestHeader(’Authorization’, ‘GoogleLogin auth=’ + token); ←Googleだとこんなかんじ }, error : function(XMLHttpRequest, status, error){ ~~~ }, success: function(result) { ~~~ } }); こんな感じで書いてはいたのですが、 RESTクライアントで投げたらOkなものが このコード経由だとうまくいかない。 具体的には 400 Premature end of file エラーになる。 これは送信するXMLファイルにエラーがあったりするっぽいんだけど、 それはRESTクライアントで問題ないことは確認済み。 なんでなんだろうなぁと。 リサーチしてると application/x-www-form-urlencodedとかで躓いたりしてるひとはいたんだけど、 それはそれで別のエラーになるし Air経由でこっちがPUTしてるヘッダーの確認方法がないので、 完全にいきづまった/(^^)\ jqueryのPUTのソースもよんだけど 別に特別なことはなにもしてないしなぁ。 とりあえず今のところ登録、一括サイトマップ登録、削除とかができるから よしとしよう。。 じゃーだめなんだろうけどw まあ片手まで作ってるので、しばらく様子見かなぁ。。 ### Air のデバッグ 情報はここらへんとかに http://help.adobe.com/ja_JP/AIR/1.1/devappshtml/WS5b3ccc516d4fbf351e63e3d118666ade46-7ed2.html あとオライリーのくっくぶっくだとp8とか にありますが、 AirでFirebugみたいなinspectorがあるので、その使い方。 とりあえずまず、 air\frameworks\libs\air\AIRIntrospector.js を作業フォルダにコピる。 で、それをHTMLでインクルードする <script type="text/javascript" src="js/AIRIntrospector.js"></script> あとは、ADLしてからF12を押すと見慣れたような画面がでてきます。 Ajaxのヘッダーのトレースとかするなら割と便利・・なようで、 実際に投げたこっちのクエリが表示されないという。 まあ、でも便利便利。 ### jquery を使ってRESTなAPIにXMLを送りつけてDELETEとか GoogleのデータAPIを使いまくり&つまりまくりなんですが、 とりあえず基本となるAuth -> XMLを送りつけるところを jqueryで実装してるとこんな感じになるよというメモ。 $.ajax({ url : url, type : "DELETE", PUTも可(ブラウザに依存) contentType : 'application/atom+xml; charset=UTF-8', 設定しないとapplication/x-www-form-urlencodedになっちゃうので必須 processData :false, 設定しないとapplication/x-www-form-urlencodedなGETクエリに変換にしちゃうので必須 data: xml_document, ←XMLドキュメント本体 beforeSend : function(request) { request.setRequestHeader('Authorization', 'GoogleLogin auth=' + token); ←Googleだとこんなかんじ }, error : function(XMLHttpRequest, status, error){ ~~~ }, success: function(result) { ~~~ } }); } にしてもマイナーなAPI使うとドキュメントがなさすぎてきつい。。 ### GoogleWebmasterTool のApiリファレンスが・・ GoogleWebmasterTool を使った サイトマップ一括更新とかのツールを作ってるわけですが、 その中でAPIリファレンスが間違ってるせいで、いきづまってたわけで、 なんで通らないんだろうと・・・ カンでAPIを変えたら通った! リファレンス間違ってるっつーーーのwwww これで3時間くらい悩んでしまった。。。 間違ってる箇所はここ http://code.google.com/intl/ja/apis/webmastertools/docs/2.0/developers_guide_protocol.html Deleting Sitemaps You can delete a Sitemap for a specified site. The site and sitemap are identified by their URLs, encoded using URL encoding. For example, to delete the Sitemap http://www.example.com/sitemap.xml for the site http://www.example.com/, send an authenticated DELETE request to: https://www.google.com/webmasters/tools/feeds/httpwwwexamplecom/ sitemaps/httpwwwexamplecom/sitemaps/httpwwwexamplecomsitemapxml 肝心のAPIが間違ってます。 正しくはこう https://www.google.com/webmasters/tools/feeds/httpwwwexamplecom/ sitemaps/httpwwwexamplecomsitemapxml 直感でURLを直したらうまくいったからいいものの、 DELETE投げるのがjquery経由でミスってるのかとか 調べまくって3時間ほど無駄にしてしまった・・・ とりあえずメールってみました。 結論 Googleだから正しいと思わないこと! ### Air でのドラッグ&ドロップとか Air面白いわ~ ってことで、Seo Scouterでのドラッグ&ドロップの 実装の部分とか(jqueryをつかったAjaxでやってます) $("#main") .bind("dragenter dragover", function (e) { e.preventDefault(); }) .bind("drop", function (e) { droppedText = event.dataTransfer.getData("text/uri-list"); $("#url").val(droppedText); start(); }); ドラッグ&ドロップも基本的には 専用のクリップボードみたいなところを使ってデータを受け渡ししてるので、 前回のように getDataで欲しいデータ型を渡せばくれるはず 今回の場合text/uri-listというのがあるので、URLのバリデーションはそれで代用してみた あと最初のpreventDefaultのコールは ドラッグエンターとドラッグオーバーでデフォに設定されてるアクションを 消すっていうこと。(らしい) ここらへんとかもあわせて。 http://help.adobe.com/ja_JP/AIR/1.1/devappshtml/WS5b3ccc516d4fbf351e63e3d118666ade46-7e8a.html ### Adobeに登録されました! Air-Seo ScouterをAdobeのディレクトリに登録したら 登録できました! http://adobe-ria.jp/air/science_technology/ ---- 話かわってAirの技術的なメモというかなんというか ぐぐって出てくるサイトは大抵Flexとかで、 自分みたいにJavascriptで書いてる人はAir界隈では マイナー組みなのかなぁと思いつつ クリップボードの部分とかのお話。 ダブルクリックでクリップボードのデータをペーストする Seo Scouterでの実装はこんなかんじ $("#result") .dblclick(function () { var g = air.Clipboard.generalClipboard; if (g.hasFormat(air.ClipboardFormats.TEXT_FORMAT)) { $("#url").val(g.getData(air.ClipboardFormats.TEXT_FORMAT)); start(); } else { } }); やってることは簡単で、 air.Clipboard.generalClipboard オブジェクトに TEXT_FORMAT があるかどうかをチェックして あったらそれを #url のテキストボックスにいれて、 start() のfunctionを実行するというもの 実際にはテキストフォーマット以外にも http://help.adobe.com/ja_JP/AIR/1.5/jslr/index.html?flash/desktop/Clipboard.html&all-classes.html BITMAP_FORMAT: BitmapData オブジェクト(AIR のみ) FILE_LIST_FORMAT:File オブジェクトの配列(AIR のみ) HTML_FORMAT: HTML 形式のストリングデータ TEXT_FORMAT: ストリングデータ RICH_TEXT_FORMAT:リッチテキストフォーマットデータを含む ByteArray URL_FORMAT:URL ストリング(AIR のみ) といろいろあるんだけど、 AIRのみ っていうやつは おそらくAIR間でのやり取りにしか使えないデータっぽい ので、HTMLからとってくるデータにURL_FORMATを付き合わせても falseになってしまうので、 TEXT_FORMATで取得するしかない。 とりあえず理由は違うみたいだけど、URL_FORMATだとFalseになるので TEXT_FORMATでやってます。 でもって、それはあらゆるテキストフォーマットがOKってことなので、 それをURLかどうかをヴァリデートする必要があると。 結局 結局うだうだ説明かかれるより ソースみたほうが理解が早いなって 最近ほんとおもう。。 ### Air-Seo Scouter をバージョンアップしました http://dev.neoinspire.net/air-seo-download ←こちらに移動しました リリースしてたいしてたってないのに、 反響大きくてびっくりです。 使ってくれてる皆様に感謝です。 で まあ早速いくつかコメントとかもらったものに関して 追加してみたりしたので、 Ver1.1としてアップしてあります。 追加機能 ・ドメインエイジの取得 ・ヤフカテ登録のチェック の2点 ドメインエイジに関しては すげー綿密に作ることも可能だったんですが、 一般的に出回ってるほぼ全てと同じように web archive を元に抽出しているので、 実際に取得した日付よりも若干後(ようはweb archiveが初めてキャッシュした日付) から起算する形になっています。 あとドメインエイジに関しては結構重たいので、 ロードしっぱなしにしておいて どんどんURL突っ込んでもらって大丈夫です。 (Ajaxで処理ってるんで、時間がたてば全部ちゃんとロードします) バグフィックス ・直接フォームに書き込めなかった(致命的すぐるww) の1点です。 なぜか return false してたので クリックしても書き込めないという。 ということで ということでこちらからダウンロードどうぞ http://dev.neoinspire.net/air-seo/air_seo_ver1.1.air 以下初めての方の為に インストール方法 Adobe Airがインストールされていないと動きませんので、 まずはここからAdobe Airをインストールしてください。 (Flashプレイヤーみたいなもんです)  ⇒デスクトップガジェットとか入れてる方は既に入ってる可能性もあります。 http://get.adobe.com/jp/air/ 1,2分で終わりますので、 インストールが終わったら、これをダウンロード http://dev.neoinspire.net/air-seo/air_seo_ver1.1.air で、ダウンロードしたものをダブルクリックでオワリ。 ### Air-Seo Scouter を公開しました http://dev.neoinspire.net/air-seo-download ←こちらに移動しました Airが楽しくてしょうがないw ということで、昨年末からハマってるAirを使って SEOツールでも作ってみようと思って、 簡単に y,gのデータを取得するツール Air-Seo Scouterを作ってみました。 こんなかんじのUIでポンポンリンクをドラッグ&ドロップしていけば CSVでまとめてデータを取得できるというもの。 Air-Seo Scouter のツールの説明|無料動画、動画配信、フラッシュ動画ストリーミングはムービーキャスターへ インストール方法 Adobe Airがインストールされていないと動きませんので、 まずはここからAdobe Airをインストールしてください。 (Flashプレイヤーみたいなもんです)  ⇒デスクトップガジェットとか入れてる方は既に入ってる可能性もあります。 http://get.adobe.com/jp/air/ 1,2分で終わりますので、 インストールが終わったら、これをダウンロード http://dev.neoinspire.net/air-seo/air_seo.air で、ダウンロードしたものをダブルクリックでオワリ。 Air-Seo Scouterでできること ・対象URLのデータ取得(下記列挙) ・タイトルタグの中身 ・H1の中身 ・ページランク ・Yahoo の site: ・Yahoo の link: ・Google の site: ・Google の link: ・取得したデータの一括CSV化 ・取得したデータをページング処理して、閲覧可能 こんなとこです。 上の動画のように、リンクをドラッグ&ドロップでぽんぽん入れればいいだけなので、 結構らくだと思います。 それから、ドラッグ&ドロップ以外にも以下の操作が可能です ・フォームに直接入力 ・URLをコピーした状態で画面をダブルクリック(これオススメ!) その他 他にもSeo関連ツールで作れそうなものはあるので、 Air-Seo ~~ みたいなかんじでシリーズで出しちゃおぅ ### [ネタ]サブドメインを使って一瞬でページランク7にする 購読させてもらってるブログに書いてあったネタなんですが、 http://labs.cybozu.co.jp/blog/akky/archives/2009/01/hyves-google-penalty-check.html 元ネタ http://www.mediadonis.net/?p=378 http://www.prchecker.info/check_page_rank.php ここで適当に http://hyves.あなたのドメイン/ と入れてみる。 と 0,4,7の3パターンでしかPRが出力されないらしい。 hyves というサブドメインがテスト用なのかなんなのかは不明。 本サイトのコメント欄みてると結構見れましたとか スパムサイトが案の定4にみたいなのがずらずらでてるんですが、 その紹介されてるサイトを googleツールバーで見ても全部 0になってる。。 どうも 日本圏のツールバーが見てるサーバとは違うみたいです。 =日本のサーバにあるサイトには適応できないっぽいです 。 (厳密にはドメインをアメリカでとらないとだめっぽいです。) が、逆にいうとアメリカとかにあるサーバを契約してそこでドメインをとったサイトは上手く機能します。 http://hyves.uoh.jp/ ↑自分が管理してるゲームのコミュニティサイトなんですが、 1server とかいうやつをつかっていて ドメインもそこでとったやつなんですよね。 これを見るとPRが7にwwwwwwwwwwwwwww (グレーになるときは取得に失敗してるので、更新ボタンを押してください) ようは 0,4,7の3択しかないので、 スパム扱いされてない7が出てくるドメインの サブドメインを今取得すればwww という話です。 いつまで使えるのやら。 ### array_multisort で キー値を保持しつつソートする array_multisort で キー値を保持しつつソートする ArrayベースのDBを作って、使ってるわけですが、 そういう稀有な場合、array_multisort君がキー値を捨ててしまうのが 非常に邪魔なので、それをなんとか保持できないかという方法。 array_multisort //今回のテストデータ $mainData = array(); $mainData[] = array('data'=>'green33','main'=>'red33'); $mainData[] = array('data'=>'green40','main'=>'red40'); $mainData[] = array('data'=>'green31','main'=>'red31'); $mainData[] = array('data'=>'green10','main'=>'red10'); $mainData[] = array('data'=>'green20','main'=>'red20'); $mainData[] = array('data'=>'green30','main'=>'red30'); 普通にやるとこう $search = array(); foreach ($mainData as $v) $search[] = $v['main']; array_multisort($search,$mainData); print_r($mainData); Array ( [0] => Array ( [data] => green10 [main] => red10 ) [1] => Array ( [data] => green20 [main] => red20 ) [2] => Array ( [data] => green30 [main] => red30 ) -略- ) で どうするかというと、 array_multisort君が捨てるキーは数値添字のみなので、 数値添字をstringに変えてしまう(型キャストだけだと、自動変換でINTに戻っちゃう) ってことでこうする //キーを取得してすべて文字列にして元の配列に戻す $keyData = array_keys($mainData); array_walk($keyData,create_function('&$i,$k','$i .= "_";')); $keyFixedData = array_combine($keyData,$mainData); //それをarray_multisort $search = array(); foreach ($mainData as $v) $search[] = $v['main']; array_multisort($search,$keyFixedData); //またキーを全部取得して最初の添字に戻す $keyData = array_keys($keyFixedData); array_walk($keyData,create_function('&$i,$k','$t = split("_",$i);$i = $t[0];')); $result = array_combine($keyData,$keyFixedData); //中間データはこんな感じになる print_r($keyFixedData); Array ( [3_] => Array ( [data] => green10 [main] => red10 ) [4_] => Array ( [data] => green20 [main] => red20 ) [5_] => Array ( [data] => green30 [main] => red30 ) -略- ) //完成データ print_r($result); Array ( [3] => Array ( [data] => green10 [main] => red10 ) [4] => Array ( [data] => green20 [main] => red20 ) [5] => Array ( [data] => green30 [main] => red30 ) -略- ) ってなって最終的にキーを保持したarray_multisortが可能になるというわけです。 結論 arrayベースのDBはやめましょう ### Wordpressの投票プラグイン いい加減wordpressも卒業しないとなあと思いつつ ちょっとしたサイトだとwordpressを使って作ってしまうのが。。 プラグインが充実してるししょうがないのかな。 いまだにXoopsが繁盛してるのもそーいったところなのかも。 ---- まあ タイトルの件ですが、 新年1発目にしては地味ですが、 ちょうど探す機会があったので。 投票機能で割といいなと思った2つがこちら 1:http://blog.jalenack.com/archives/democracy/ 2:http://www.tevine.com/projects/voteitup/ コンテンツを作って投票 1のほうはサイドバーとかにおいたりして、 質問単位で管理するタイプの投票プラグイン。 プラグインフォルダにアップさえしてしまえば あとは管理画面の投稿メニューのところに画面が追加されるので、 そこで、ON、OFF、投票の修正とかができます。 ただ、これはページ単位では管理できないので、 記事の投票機能としては使えません。 逆にサイドバーにコレコレについてどうおもいますか みたいな質問をおいておく ものとしてはかなりいいモノです。 記事単位で投票 2のほうは1とは逆にコンテンツ単位で投票できるタイプ。 ようは記事がよかったらポチしてくださいねみたいな はてなスターみたいな感じの役割。 管理画面は設定のところに追加されます。(これ指針だして統一してほしいよなぁ) 記事単位で投票はできますが、 質問を作ってそれに対して投票みたいなのは 一工夫(wordpressの記事そのものを質問にしないといけない) が必要だったりするので、 そういう使い方をする場合は1を使ったほうがよいでしょう。 ちなみに今回のサイトでは両方使いました。 ということで 新年あけましておめでとうございました。 ### Google ウェブ履歴で1年のシメを 去年もやった1年のシメ的なもので、 おもしろい1年の振り返り方ということで。 1:ログインしてGoogleにアクセスしてください。 2:右上に 「アカウント」ってあるとおもうのでそれをクリック 3:「ウェブ履歴」をクリック 4:サイドバーの 「統計」 をクリック 5:上部にある「昨年」をクリック(これが今年分らしい) 去年もやったんですが、 一年の検索ワードで、その時期何考えてたかが結構わかったりしますね~ あーこのことはこれ良くわかってなくてしらべまくったなとか、 あのころマイブームはこれだったなみたいな。 個人的なまとめ 総検索回数:13629 1月:1474 2月:2278 3月:1991 4月:1481 5月:1666 6月:1310 7月:450 8月:521 9月:412 10月:456 11月:747 12月:843 ・・・なんでこんな7月から検索してないんだろう。 あとどうでもいいけどこれはひどすぎる 「関心のあるアイテム」 1:メリークリスマス 2:ダウン症 3:ちんこアプロダ 4:真木よう子 5:グリーティングカード 6:ジャックラッセルテリア 7:autopagerize 8:松野明美 9:むすめーかー 10:バナナダイエット 一度も検索したことないワードがずらずらと つか 真木よう子とか誰だよwwwww ということで今年一年お疲れ様でした。 ### Yahoo Google でリスティングとSEOで切り分ける リファラでさっくり簡単かなーって思ったけどそうでもなかった・・ Googleだと 検索結果URL: http://www.google.co.jp/search?hl=ja&rlz=***&q=***&btnG=***&lr= PPCリンクをクリック PPCのURL先: http://www.google.co.jp/aclk?sa=l&ai=*** そのときの  リファラ: http://www.google.co.jp/search?hl=ja&rlz=***&q=***&btnG=***&lr=  ステータスコード: HTTP/1.x 302 Found  転送先: http://www.googleadservices.com/pagead/aclk?sa=*** 転送されてきたURL:http://www.googleadservices.com/pagead/aclk?sa=L&ai=*** そのときの  リファラ: http://www.google.co.jp/search?hl=ja&rlz=***&q=***&btnG=***&lr=  ステータスコード: HTTP/1.x 302 Found  転送先: http://PPCの対象URL さらに転送されてきて:http://PPCの対象URL そのときの  リファラ: http://www.google.co.jp/search?hl=ja&rlz=***&q=***&btnG=***&lr= Yahooだと 検索結果URL: http://search.yahoo.co.jp/search?p=***&search.x=1&fr=top_ga1&tid=top_ga1&ei=UTF-8 PPCリンクをクリック ↓ PPCのURL先: http://wrs.search.yahoo.co.jp/S=***/K=***/v=2/SID=w/TID=***/D=E/OV=0/S*** そのときの  リファラ: http://search.yahoo.co.jp/search?p=***&search.x=1&fr=top_ga1&tid=top_ga1&ei=UTF-8  ステータスコード:HTTP/1.x 302 Found  転送先: http://rc20.overture.com/d/sr/?xargs=*** 転送されてきたURL:http://rc20.overture.com/d/sr/?xargs=*** そのときの  リファラ: http://search.yahoo.co.jp/search?p=***&search.x=1&fr=top_ga1&tid=top_ga1&ei=UTF-8  ステータスコード: HTTP/1.x 302 Found  転送先: http://PPCの対象URL さらに転送されてきて:http://PPCの対象URL そのときの  リファラ: http://search.yahoo.co.jp/search?p=***&search.x=1&fr=top_ga1&tid=top_ga1&ei=UTF-8 見ればわかるように 302で飛ばしながらくるので、 リファラは常に検索結果ページのURL つまり、リファラだけみてもPPCからきたのか、SEOなのかわからないという。 まあPPCでも検索ワードが拾えるわけだけど。 で結局送られくるデータだけだと どうしようもないので、ひと手間かけて、実現しました。 やり方としては Webmater Tool系 + PPCの機能 + PHP ってかんじ。 近いうちにツールでも公開しますかね。 ### Googleのインデックスのお話 こちらとかのエントリーを見て思ったことをつらつら コメントに書こうとして長すぎてエントリーに。。 http://www.algoritmi.jp/seo/20081204010749.html http://www.suzukikenichi.com/blog/googl-must-have-more-than-one-partitions/ (先にいってくと、私はGoogleもYahooも2つのインデックスは使っていないと思ってる人です) とりあえずインデックスってどんなものなのか イメージできていないとおもうので、 ここらへんを参考にしてみてください。 http://ja.wikipedia.org/wiki/ (転置インデックスの説明@wikipedia) まあこんなかんじで サイトがインデックスされると ほんとに索引に近いものができるわけです。 (実際には転置インデックスはつかってないとおもいますが  イメージとして) クロール > インデックス ここで簡単にクロールから検索結果までのフローを書くと ○クローラー 1:クロールする 2:クロールしたものがキャッシュするに値するかを判定  ⇒キャッシュしない場合終了 ○インデクサー 1:キャッシュされたリストを順にみていってインデックスに値するかを判定  ⇒インデックスしない場合終了 2:サイトを分析 3:(検索エンジンが独自に考えた理論で)インデックス 4:ついたインデックスに対してさらに独自の操作をしてポイントさらに操作 (ドメインエイジとかスパムみたいな文書解析とは別のレイヤーの作業) 5:検索結果を出すためのインデックス完成 このできたリストから検索結果を出すわけです Yとかはこのインデクサーの作業が追いついていなくて、 定期的に 更新しました! といって全サイトのインデックス作業をやるわけです。 (Yahooの場合このインデクサーの作業4だけはこまめにやってる感じ) が Googleはたぶん余裕で追いついているので、 随時やってるわけです。 だから随時変動。 ---- というのを踏まえて ・補足結果っていうのはたぶん インデクサーの作業の4でしてる作業の一つの集合 ・Matt Cuttさんの言及 -> 4の作業をしない状態で5にいってしまった ・ヨーヨー現象 -> インデクサーの処理が早くなっただけじゃないのかな? ・ghost data-set -> こんなのあったらあのスピードで検索結果は出せないと思う。。 ・site:とか -> Googleのインデックスは他のサイトからの影響を強く受けるわけだから         被リンク元のページにインデクサーが走った時、被リンク先のページの         評価が変わるのは自然だと思う。 というお話でした。 長ひ。。 結論 hyperestraierを使ってみよう(ぇ ### php cUrl でプロキシを通す方法 最近はcurlでファイルアクセスする方法が多いんですが、 その際プロキシ経由でアクセスするための方法のメモ //curlがバンドリングされていないと使えません $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); //プロキシ経由フラグ curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1); //プロキシアドレス設定(プロキシのアドレス:ポート名) curl_setopt($ch, CURLOPT_PROXY, 'http://xxx.xxx.xxx.xxx:8080'); //念のためプロキシのポートを指定 curl_setopt($ch, CURLOPT_PROXYPORT, '8080'); //プロキシのID,PASSの設定(ID:PASS) curl_setopt($ch, CURLOPT_PROXYUSERPWD, "anonymous:"); $ret = curl_exec($ch); print($ret); これでいけます。 まあ海外プロキシとか使うと たまに鬼重になってしまうわけで、使いどころが難しいんですが。 ### キーワード検索回数を出す ちょっと思うところがあって作ってみました。 ずいぶん前につくった Keyword Suggest Tool があったので、 それに Plus とかつけて公開しました。 http://refeed.in/keywords/ どこまで動いてくれるかはわからないけど、 とりあえず、近しいデータが出せるはずです。 色々とデータは工夫して近しい数値にしてるんですが、 このくらいの精度以上になったら、現物データとってくるしかないですねw まあそれは それこそGとYがマネタイズできちゃう部分なので 無料じゃ100%のものは無理でしょうw ### SEM用ツールがGoogleから登場 Search-based Keyword Tool サイト+メインのワードを与えるとGoogleの検索回数とサイトの適応率をかんがみて お勧めワード的なものを出してくれる らしい。 サイト自体は英語だけど、日本語も通る。 Adwordsのキーワードツールに似てるけど、 サイトベースで使えるのと 何よりRESTでいじれそうなのが最高すぎるので (まあどうせ自動クエリは投げれないけど) これはいいツールだと思った。(REST的な意味が非常に大きいw) いまだにferretの検索回数ベースで話してくるSEO業者も多いし、 みんなそれだけ困ってるってことなんだろうし、 これを利用する人は増えそうだなー ### googleの裏ワザ http://www.haywirehacking.com/main/2008/10/11/hacking-google-turning-a-search-engine-into-an-mp3-downloade.html こ これわ・・・ ---- 検索式を使ってMP3をダウンロードする方法デス。 ダークサイドな検索ってことで。 -inurl:(htm|html|php) intitle:"index of" +"last modified" +"parent directory" +description +size +(wma|mp3) "バンド名や曲名" 検索、特にgoogleって正規表現みたいな書き方ができるんですよね。 そういう特殊な書き方をミックスさせたウラワザかな。 軽く説明するとこの条件式は3つ構成で、 1:-inurl:(htm|html|php) 2:intitle:"index of" 3:"last modified" +"parent directory" +description +size +(wma|mp3) 1:URLの中に htm,html,php という文字が入っていない 2:タイトルに "index of" というフレーズが入っている 3:あとは通常の and検索 これで何を検索するかというと apacheのディレクトリ表示画面を出すわけです。 デフォルトのディレクトリ表示画面には Last modified,Size,Descriptionという文字があって、 タイトルはIndex of(フォルダ名)になってるわけで、 それを狙い撃ちするわけです。 たとえばメタリカとかでいくと -inurl:(htm|html|php) intitle:"index of" +"last modified" +"parent directory" +description +size +(wma|mp3) "metallica" こんなかんじで、 出てくるページはこんなかんじ。 ってURL載せようと思ったけどかわいそうだからヤメタ。 いまだと上から3個目とかかな。 . 日本語でもできるかな -inurl:(htm|html|php) intitle:"index of" +"last modified" +"parent directory" +description +size +(wma|mp3) "ハルヒ" 日本語ファイルつけてる人もいるのね。 . というわけです。 はいはい自己責任自己責任。 結論 . まあ index.htmlは絶対おいておこうね。というお話です。 ### mysql4.0系でfulltextを利用したマルチバイト全文検索2 前回の続き とりあえず強引に mysql4.0系でマルチバイト対応全文検索してみるだけ。 簡単に言えばトークンに切り出して、それをmd5して別テーブルに格納するという力ワザ。 どうやってトークンを切りだすか 以前流行った正規表現ベースのトークンとn-gramで速度比較 100文字ほどのデータだと %php -f morph.php ←正規表現ベース 0.0232629776001sec % %php -f ngram.php ←tri-gram 0.029877967834sec % 10万文字ほどのデータだと %php -f morph.php ←正規表現ベース 8.76749396324sec% % %php -f ngram.php ←tri-gram 9.08151698112sec% あんまりかわらないのでワンライナーな正規表現ベースでやってみる。 こんなかんじの正規表現。 $r = preg_match_all('/[一-龠々〆ヵヶ]+|[ぁ-ん]+|[ァ-ヴ]+|[a-zA-Z0-9]+|[a-zA-Z0-9]+/u', $str, $m);   この正規表現で各レコードの本文を割って 出てきたトークンを一つづつハッシュにしてjoinする $tempToken = array(); foreach ($m as $v) { $hashedToken[] = md5($v); } $joinedToken = join(' ',$hashedToken);   でこれをトークン専用カラムを作成してそこに突っ込む。 ここまでできたら 検索クエリも上の正規表現でトークンにしてそれをmd5にしてから検索。 テストしてみる 全文中から 86deb27a32903da70a7b2348fcf36bc3(あいうえおのmd5) を検索 SELECT id,MATCH (test_fulltext) AGAINST ('86deb27a32903da70a7b2348fcf36bc3') FROM **table WHERE MATCH (test_fulltext) AGAINST ('86deb27a32903da70a7b2348fcf36bc3') ORDER BY MATCH (test_fulltext) AGAINST ('86deb27a32903da70a7b2348fcf36bc3') ASC クエリの実行時間 0.0065 秒   おーあたったあたった。 しかもこれなら mysqld のft_min_word_lenを4からいじれないっていう件も 強制32文字になるわけだから解決! ちょいと手間だし、トークンの数 * 32バイト分1レコードのデータが膨れ上がりますが (まあ全文字md5にしないで、マルチバイトのとこだけ切り出してるわけだから、 そこだけエンコードしてしまえば結構減るかも) こういう方法で一応できるということで。 っていうか pack() とか使えばもっと短くなるかな。 ### mysql4.0系でfulltextを利用したマルチバイト全文検索1 とりあえずさくらのサーバに 全文検索も実装しようかなーと思ってsennaとか調べてたんですが、 色々と制約にヒットしてめんどくさくなったんで、 既存の範囲で全文検索ができないかと考えてメモ ここらへんを参考に MySQL 全文検索 MySQL FULLTEXT + Ngram : LIKE検索より数十倍高速な、お手軽 日本語全文検索 について mysql の fulltext型のインデックスを使って実装するのが一般的らしい。 で このfulltext型ってのが何をやってるかといえば MySQL では、非常に単純なパーサを使用してテキストをワード(語)に分割します。``ワード'' とは、文字、数字、‘'’、‘_’ で構成される文字列です。 だそうで。 ただ、 MySQL 4.1.1 以降はマルチバイト文字も対応。 ただし Unicode は utf8 のみ対応 (ucs2はNG)。 えーっと さくらのDBは 4.0.27なわけですが・・・ さらに 4.1だと文字揺らぎの修正(半角<>全角とか) +QUERY EXPANSION MODE という付加モードも使用可能。。 ということで、あきらかに4.1を使ったほうがいいんですが、 4.0でも最低限動かせないかなということで試行錯誤開始。 とりあえずやってみる とりあえずそこらへんは無視して 既存にあるテーブル(レコード数6000)の コンテンツテーブル(text型、1つのレコードにつきマルチバイトで1000文字くらい)のものでやってみる。 (DBはUTF-8統一) ALTER TABLE **table ADD FULLTEXT(contents); 6000行もあるから結構かかるのかとおもったら5秒ほどで終了。 え これほんとにできてんのかな・・ とりあえずテスト 全文中から Screenshot っていう言葉を検索 SELECT id,MATCH (contents) AGAINST ('Screenshot') FROM **table WHERE MATCH (contents) AGAINST ('Screenshot') ORDER BY MATCH (contents) AGAINST ('Screenshot') ASC クエリの実行時間 0.0054 秒 おお早い早い。 ちゃんと結果も100件ほど出てきた。 LIKEにしてみる SELECT * FROM **table WHERE contents LIKE '%Screenshot%' クエリの実行時間 0.2419 秒 やっぱこっちは実用的じゃない。 しかも結果は160件ほど。 じゃあ日本語はどうかというと。。 全文中から ミュージック っていう言葉を検索 SELECT id,MATCH (contents) AGAINST ('ミュージック') FROM **table WHERE MATCH (contents) AGAINST ('ミュージック') ORDER BY MATCH (contents) AGAINST ('ミュージック') ASC クエリの実行時間 0.0057 秒 検索結果0。手動で入れてみたんだけどやっぱり0。 やっぱ機能していない。 でもまあ今更UTF8からEUCにDB変えるとかナンセンスすぎるので どうしようかなーと。 やり方を考える 日本語トークンを切り出してハッシュにでもする? できそうな気がしないでもないので、やってみる。 続き ### phpでマルチスレッド またまた自作フレームワーク用に組み込みたかった機能の話 特にfile_get_contentsなどの相手側の応答いかんで処理時間がかわるような場合 同時にだーって流しておきたいなーとはおもったんですが、 考えていたやりかたはモジュール版だと使えないということが発覚・・ (pcntl_forkって関数) で今回みっけたのは curl_multi PHP curl_multi example of parallel GET requests 注意点としては、各ハンドラが独自にキャッシュしてるから ハンドルを変更せず再度 curl_multi_exec しても トライせずにキャッシュを返すことかなぁ (その場合一旦ハンドラを解除して再セットすればOKらしい) なので、こんな感じでクラスを作って localhostのphpファイルを呼び出せば 簡単にマルチスレッドでプログラムが動かせると。 まあよっぽどのことがないと使わないとおもいますが。 というかこれ、どこまでいけるんだろう。 Class Parallel { public $result; public function __construct() { } public function execute($urlList = array()) { if (count($urlList) > 0) { $channel = array(); $uniqueUrls = array(); $uniqueUrls = array_unique($urlList); //マルチプロセスハンドラ $multiHandle = curl_multi_init(); //URLに一つづつチャンネルを割り当てる foreach ($uniqueUrls as $k=>$v) { $channel[$k] = curl_init($v); curl_setopt($channel[$k], CURLOPT_RETURNTRANSFER, 1); curl_multi_add_handle($multiHandle,$channel[$k]); } while (CURLM_CALL_MULTI_PERFORM == ($execReturn = curl_multi_exec($multiHandle,$isActive))) { } while ($isActive && $execReturn == CURLM_OK) { if (curl_multi_select($multiHandle) != -1) { while (CURLM_CALL_MULTI_PERFORM == ($execReturn = curl_multi_exec($multiHandle,$isActive))) { } } } if ($execReturn != CURLM_OK) { return false; } $resultData = array(); foreach ($uniqueUrls as $k=>$v) { $curlError = curl_error($channel[$k]); if ($curlError == "") { $resultData[$k] = curl_multi_getcontent($channel[$k]); } else { $resultData[$k] = 'error'; } curl_multi_remove_handle($multiHandle,$channel[$k]); curl_close($channel[$k]); } curl_multi_close($multiHandle); $this->result = $resultData; return true; } else { return false; } } } ### XML DB 10万くらいのRSSをうまく管理するのはどうすっかなーと 新しいシステムについてつらつら考えていたのだけれど、 ふとXMLベースのDBってどうなんだろうと思い出して調査開始。 キャッシュがきかない、トランザクションがきかない とかI/O周りの負荷が高すぎて 結局具合のいいパーサかいてやるか 一旦mysqlに格納しちまったほうがよさそうだなー と思いつつとりあえず使ってみた。 Xprioriってのでテスト Xpriori ここらへんを参考にXquery(SQLの代わりに投げるDB用クエリ)をいじってみる Xquery ・・・ スキーマレスDBっていうのは RSSのようにスキーマがガンガン変わるようなものを 一括で管理するにはいいとおもったんだけど、 どうも勝手がちがったw JOINとかは1ファイル1テーブルで、JOINはファイルを結合してくイメージだから・・ あかんw10万ファイルをJOIN(実質MAX1000くらいなんだろうけど) とか気が遠くなるこれw 10万ファイルをDBに格納してRSSを生成しなおすのと(こっちは格納コストが高い) ファイルとして保持して、Xqueryで生成するのと・・(こっちは生成コストが高い) うーん ちょっと簡単にはコスト計算できそうもないので、要検証。 パーサ書くほうが楽しいしとりあえずやめよっかなーとか。 余談だけどXqueryに関するこの記事をよんで めっちゃ面白そうだとはおもった http://d.hatena.ne.jp/stemy/20060708/1152385620 ### DB分割とか 大きい開発がはじまったので、 作りためた自作フレームワークを完成させよう~ と思ったんだけど、肝心のDB分割の部分がうまく整理できていないので、 そこらへんのメモ。 キーで分割するか、テーブルで分割するか ○テーブル分割 ・IDテーブルはこのDBサーバ、blogテーブルはこのサーバみたいな。  さらに細かくblogテーブルのこのカラムはこのサーバみたいなやり方もあり。 ・パーティション・マップを用意してテーブル<>サーバのマッピングをする。 ・色々JOINとかが超めんどい (そもそもその規模だとJOIN使うな的な話も多い) ・分割するなら例えばBlogテーブルのテキストカラムだけ 外だしするとかクエリベースで切り貼りするイメージ。 ・シームレスなラッパーは書くのがきつそう ○キーで分割 ・単純にID1~100はこのサーバ 200~300はこのサーバみたいな。 ・キーの決め方がハードコーディングになると汎用性がさがるけどリソースをうまく使いきれそう ・キーの決め方がアルゴリかましてるとID100を別サーバに移動みたいなことがきつい。  だけどサーバ増設が伴っても比較的ラクに対応できそう。 ・マッピングを使う どっかにID1はこのサーバって書いてく。  このマッピングが書いてあるDBの負荷は大丈夫なん? ・シームレスなラッパーは書くのが楽そう ○タイムスタンプベース ・そこまで必要なサイトってDbいくつ必要なんだというw ---- テーブルで分割の場合よりも、キーで分割のほうが サーバの拡張のイメージが付きやすいし、管理コストも楽そうだから こっちを採用予定。 とりあえずログインサーバとメインDB1,メインDB2でわけようかな。 キーで分割して、マッピング使用、ログインサーバでマッピングさせてしまう予定 (ログインサーバの負荷がきになるけど) あ あと自作フレームワークは neo という名前に! (そういうとこだけ早いというw 参考サイト http://www.itmedia.co.jp/enterprise/articles/0808/28/news013_1.html http://itpro.nikkeibp.co.jp/article/NEWS/20060330/233820/ ### Captchaを自作してみた そういえばあれはどうやって作るんだろうっておもって 作ってみました。 意外と簡単だった。 こんなかんじ。 日本語を出すにはフォントいれとかないとだめだというのに ちょっとはまったくらいかな。 PHPで画像いじるの意外と面白いかもw 一応ソースはこんなかんじ。 header("Content-type: image/png"); //設定 $string = 'きゃぷちゃテストね'; $width = 250; $height = 100; $fontSize = 20; $distortion = true; //フォントのパス設定 putenv('GDFONTPATH='.realpath('.')); $font = 'fontname'; //元画像を使わないから1から作成 $img = imagecreatetruecolor($width,$height); //captchaで使う色を設定 $black = imagecolorallocate($img,22,22,22); $darkGray = imagecolorallocate($img,55,55,55); $gray = imagecolorallocate($img,200,200,200); $white = imagecolorallocate($img,255,255,255); //画像を背景色でfill imagefill($img,0,0,$white); //センタリング処理用 $wd = (imagesx($img) - $fontSize * 0.35 * strlen($string)) / 2; $ht = $height / 2 + $fontSize * 0.35; //各文字を表示 for ($i=0;$i < mb_strlen($string);$i++) { $putString = mb_substr($string,$i,1,'UTF-8'); imagettftext($img,$fontSize + rand(-5,5),rand(-5,5),$wd + $fontSize * $i,$ht + rand(-10,10),$darkGray,$font,$putString); } //ディストーション処理 if ($distortion) { imagesetthickness($img,1); //ラインディストーション $i = 0; while ($i ### 36進数 36進数を新しいサービスで使おうと思って 作ってたわけです。 いや 思いついたら面白くなってしまったというオチなんですが。 こんなかんじで。 function decodeDec36($decimal='') { if (preg_match('/^[0-9a-z]+$/',$decimal)) { $stack = array('0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'); $dec35 = 0; $len = strlen($decimal); for ($i=1; $i$v) { $enc35 .= $stack[$v]; } return $enc35; } else { return false; } } 反省はしてない。 と勢いで作っていたらふと javascript の toString(36) を思い出した。 あれってPHPでねえのとおもったらあった・・・ $encoded = base_convert($number,10,36); //10進数の$numberを36進数に変換 無知って罪だ。 やっぱり反省します。 ### 10月になりまして@SEO雑感 そういえば去年の今頃SEOのことがやっと理解できてきたなーと思い出して。 SEOは昔はやったテーブルトークゲームみたいなもので、 ゲームマスターがいて、こっちが行動した結果だけが返ってきて、それを元に色々考える まあ そんなとこですよね。 業界的には1年たってどんどん Google>>Yahoo 化が進んでますねー (それでも日本はまだまだYahoo贔屓ですがw) 個人的には検索エンジンの路線はYahooのほうが好きというか スマートだと思っていて、 他人の評価が基準のGoogleではなく そのページそのものの本質を調査するYahooにがんばってほしいものです。 まあ今の解析アルゴリズムじゃ中々、 本文からその文章の情報量を抽出できないんですが、 人間ならそのページがいい情報を書いているか目で判別できるんだし そのうちできるようになるはずです。 Yahooガンバレ まあ自分のSEOの知識も無駄に増えましたが、 やっぱり 「確証がない」 ことをえらそうに語るのがどうも苦手ですw 営業上必要なことも多いんですがねぇ。。 ----余談---- SEO塾さん SEO塾さんのエントリーでコメントついてたのをみて。 (コメント書く気にはなれなかったので) このキャッシュが複数あるっていうのはなんなのでしょうね? (2,3世代前を元にインデックス処理するのはまずないとおもいます) もしきちんと残すのであれば それこそGoogleのもつサーバをはるかに超える インフラをもたないといけないし、 意味がない(Web Archiveでやってるしそれなりのビューも作らないといけない)気がします。 2,3世代データをもってる可能性はあると思いますが、 それをキャッシュのリンクに意図的に使う理由はないと思います。 あとキャッシュを削除するというのは Yahooのアルゴリズムの中に時系列を見る部分があることを考慮すると、 まずないんじゃないでしょうか。 (↑ここが2,3世代もってる可能性があるかもしれない理由でもあります) 1個前のデータと比較するだけならあれですが、 2,3こ前のデータと比較検証するとしたら、必要ですよね。 まあそういう意味では、DBのレプリケーション時差なんでしょうかね。 (簡単に言えば参照しているDBサーバが違う) まぁそれでも2世代ならまだしも3世代残ってる理由は不明です。 超巨大DBのレプリケーションの時間が何日単位で、 その間に平行してどんどんレプリケーションが走るのであれば 3世代、4世代前が残っていてもおかしくないですが、 さすがにそんなこともないんじゃないのかな。。。 ⇒こればっかりは大規模DB運用してみないとさっぱり見当もつかないなぁ。。 まぁ 結局 「こっちが行動した結果だけが返ってきて、それを元に色々考える」 だけで終わります。 正解はありませんw ・・・余談のが長くなってしまった。。 ### スモールワードチェッカー スモールワードチェッカー 社内で話していて、そういえばこんなツールなかったよねー ということだったので、ちくちくいじったら 1時間くらいででけたので公開。 スモールワードチェッカー まあやってることは単純で、関連検索のYahoo Apiをたたいて、 メインワードを基点に関連ワードをならべてから、 それを順番に、同じくYahooApiの検索を使ってかけて、 でURLがあったら何位 みたいな処理してるだけです。 問題は50000クエリというAPIの制限。 1時間の制作時間の10分くらいはここをどうするかで悩んでました。 どうやっても検索回数は減らせない=一回に100クエリ投げることになる ということで、 そもそもこのツール1日500アクセス以上できないという謎なものに。 まあIPかえればぜんぜんできるから、 いくらでもやろうと思えばやれるんだけど、 こんなツールのためにそこまでやりたくなひ・・・ ということで、現在のところ1日500アクセスまで! なぜかめっちゃアクセスが増えるようならまた考えます。 スモールワードチェッカー ### 有名人の誕生日検索ができるサイトを作ってみました Jakk's Toy Box Wikiのデータがスクレイプしやすかったので、 そっから有名人のDBを作ってみたらうまくいったので ついでに検索できるようにしてみた(・・ おんなじようなサービスはあるけど、 まあせっかくなんで公開w ついでなので、GoogleのAPIとWikipedia APIも使ってそれっぽく。 今までSEOのツールをサブフォルダにおいていたので 外だしして、おもちゃ箱を作った(完全に気分の問題w 画像はflickrとフォト蔵もためしてみたんだけど、 やっぱり古い人とかは出てこない(あたりまえ) なので、Google先生に頼りました。 かなり画像の精度が高いのはさすがGoogle先生 Jakk's Toy Box ### はてなのSEOはうらやましい http://d.hatena.ne.jp/hatenadiary/20080924/1222246664 nofollow 属性とは、Googleなどの検索エンジンがこの属性を発見した際に そのリンク先を追わないということに利用されています。 はてなで盛り上がってるキーワード専用ページの話ですが、 やっぱりこういう認識してる人多いんですかねー。 どこかの偉い人もGoogleは言ってることと(この場合言ってることともあってないですが) やってることが違うといったように、 今nofollowが意味ないっていうのは結構普通の認識だと思ってました。 (SEOに漬かりすぎかな。。) たぶん今回の件って 内部リンクが多すぎて、外部リンクほぼ0でもスモールワードだとガンガン上がってしまう ⇒じゃあ内部リンクを0にしておこう ⇒そうすればGoogleで上位にこないだろう 見たいな考えだと思うんですけど、 そうじゃなくて、コメントにあるように存在しないキーワードページに noindexタグを貼った方が効果あると思いますね。 (SEO考慮すれば最低ですが まあそもそも自分がnoindexなんてためしたことないので、 noindexが実装されてるかわかりませんが。 後、一応自分がゆるく調査したデータの範囲でいうと nofollow先のページは Googleで ・キャッシュされる (これはまあ単純にnoindexじゃないからですね) ・おそらく被リンク価値はあんまりない (0とはいえないけど) ・最初nofollowにしていたリンクは後からnofollowにしても意味がない (nofollowなしで1万リンクほど貼っていたサイトをすべてnofollowにしても何もかわらなかった というかLink:でも普通に出てくるw) Yahooで ・キャッシュされる (同上) ・おそらく被リンク価値はある (一つのサイトからすべてnofollowで5000リンクほど張っただけで明らかに上がりました) ・最初nofollowにしていたリンクは後からnofollowにしても意味がない (同上) まあ、だからnofollowとかまだ全部実装されてないんでしょうね。 もしくは実装されているけど解析するのが遅いのかな。 301リダイレクトとか一緒かも。 っまあ内部リンクだけでスモールワード対策できちゃうはてなはすごいなぁ。。 ### WebSiteAnalyzer を更新しました WebSiteAnalyzer 一日100アクセスくらいあって割とユーザさんがいたので、 仕事の合間に改良しつづけたクラスを使ってリンク解析プログラムを作ってみました。 解析するといっても単純に、 リンク元のURLにアクセスして、 ・アンカーテキストの文字列(Googleと同じく最初の1個のみ) ・アンカーテキストの前後にある文字列 を取得してくるだけです。 外部->内部リンク数の部分が表示されたら、 「バックリンク分析」というリンクを押してみてください。 それでも結構処理が遅いので、まあゆっくり使ってみてください。 なんでこんな遅いかっていうと 処理する部分じゃなくて、 相手のWEBサーバにアクセスする部分がボトルネックなんですよね。 fsockopenにタイムアウト2秒、さらにヘッダが決まったバイト数以上きたらカット などなど仕込んでるのにそれでもやっぱり遅いです。 もうちょいここはチューニングしないとなぁ。。 http://dev.neoinspire.net/seotools/ ### MYSQL で IF(CASE)文が便利 これはめちゃくちゃ便利なので、メモメモ。 例えば tableA に 性別カラム (clmA) (1=男,2=女,3=その他)があって 男のレコードの数 女のレコードの数 その他のレコードの数 を1発のSQLで出すことができます。 SELECT count( CASE WHEN clmA=1 THEN 1 ELSE null END ) as "Men" count( CASE WHEN clmA=2 THEN 1 ELSE null END ) as "Women" count( CASE WHEN clmA=3 THEN 1 ELSE null END ) as "Others" FROM tableA ほかにも CASE WHEN 1>0 THEN "true" ELSE "false" みたいな書き方も可能だし、 もちろんネストも可能。 しかも意外と処理も早いので、 わりとヘビーなDBじゃない限り実用に耐えうるきがする。 他にも色々と使えそうな演算子があったので、 一度きちんと勉強したほうがよさそうだなー おすすめのレンタルサーバーを徹底比較! ### Google Chrome を使ってみたら超微妙なバグ?があった。 めちゃめちゃいい!! 早いし、軽いし。 Firebugと一対一対応のものがでたら完全に乗り換えたい。。 Chrome にもそれっぽいのがあるんですが、 background系とか 特定のプロパティしか書きかえれないみたいで 微妙に使えない。 それにしてもこんなに使えるとは思わなかった。。 若干Flashサイトが普通より重く感じますが、それ以外はむしろ全部早い。 完成度めちゃめちゃ高いです。 で、さっそく微妙なSEO?的なバグを教えてもらったので載せときます。 アドレスバーの右にあるWebページみたいなマークをクリック メニューが出てくるので上から4個目? のアプリケーションのショートカットを作成を押す すると ショートカットができるんですが、 この時 タイトルタグとディスクリプションタグを取ってくるはずななんですが、 微妙にバグ?があります。 これはとってこれる <meta name="description" content="メタテストです ここは全部小文字だと読みます。" /> これはとってこれない <meta name="Description" content="メタテストです ここは頭だけ大文字だと読みます。" /> <meta name="DESCRIPTION" content="メタテストです ここは全部大文字だと読みます。" /> このページを開いてやってみてください。 ダメなほうのメタ うまくいくメタ あちこち meta とかで検索して出てくるHTMLのサイトでも 頭を大文字にしてるサイトがあるので、コピペしてると普通に大文字になってたりしますね。 あとHTMLタグもそうですが、厳密に大文字、小文字のどちらかみたいな記載はなさそうなので、 表記方法として問題ないとは思うんですけどね。 というわけで、これをやる人は相当限られた人ですが、 そんなときのためにもメタタグは小文字で書けってことですかねw ### IEとFirefoxのURIの長さの処理の違い FireFoxとIEでURLの長さによる挙動がちょっと違うんですよね。 IEの場合 2083文字で Firefoxの場合200万以上つっこんでも大丈夫らしい。 これだけだとあれなんですが、 エラーになった場合の処理が違うかんじ。 例えばフォームに5000文字くらいつっこんでSubmitボタンを押すと、 ◎Firefoxの場合 ちゃんとCGIに渡そうとデータを送信して、 サーバでURIが長すぎるよというエラーになる。 ◎IEの場合 そもそもデータを送信すらしない ⇒つまり画面も遷移しないので、ボタンを押しても画面がかわらない。 という感じ。 ----- サーバの観点からみるとIEのが親切な気がするけど、 HTTP/1.1では、URLの長さの規定はないんだし、 そっちにあわせてほしいなぁ。 処理するところできちんとそういうのは処理するんだし。 途中で勝手にやられるとそれはそれで色々と不都合が。。。 ### 謎のユーザエージェント FunWebProducts 運営サイトのユーザからの問いあわせの時、 切り分けのため、送信元のユーザエージェントをログにしてるんですが、 今日はじめて?みたような謎なのがあって。 Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; FunWebProducts; SV1; .NET CLR 1.1.4322) Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; YPC 3.2.0; FunWebProducts; MSDigitalLocker; .NET CLR 1.1.4322; InfoPath.1) Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; FunWebProducts; .NET CLR 1.0.3705; .NET CLR 1.1.4322; IEMB3) Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; FunWebProducts-AskJeevesJapan; SV1) こんなやつですが、 FunWebProductsでぐぐってみるといっぱいでてきた。。 このFunWebProductsってのはどうもアドウェアらしくて グレーゾーン。必要ないような、そんなものなのか。 その人の問い合わせが謎の広告がうんたら みたいなことだったので これが原因なんじゃないかなーと思いつつ、 どう説明するかで非常に悩む今日この頃。。 ### ランキングチェックツールが使えなくなる? 海外SEO情報ブログさんでコメントを書いてて長くなったので ついでにエントリーにしておこうかな。。 8月になってまだ書いてないし(ぇ http://www.suzukikenichi.com/blog/google-blocks-ranking-check-tool/ SEO業者さんだったりうちみたいに真似事をしているような会社では やっぱりクライアントのランキングとかを常時おっかける必要があったりするわけで、 そういうときにこういったツールがすげー便利になるわけですが そういうのを使ってると「特に最近」 google で capcha がでてきませんか? (うちの場合はじめてyahooでも出ました) 実はこれらの機能が物足りなくて自作して動かしてた時期があったのですが、 そのときも同じような現象があってすぐにピンときたわけです。 判断軸は難しいのですが 同一IP、検索ワード以外同一クエリ、ログインしていない などの条件で検索クエリを連射するとすぐGoogleははじくようになっているので、 それにひっかかるんですよね。 うちで使ってるのはGRCってやつなのですが、 以前は大丈夫だったんですが、特にここ数日よく通常業務に支障をきたすほどエラーになります。 (当たり前ですが、その状態ではランキングデータも取得できないので、その日のデータが欠落したりします) まあそういうエラーを差し引いてもめちゃ便利なので愛用しているのですが。。。 たぶんエラーにする閾値を上げた or 精度をあげたのかもしれませんね。 まあ元々検索エンジン側でやるな と言われているものなので、 使えなくなったらしょうがないといえばそうなのですが、 これがないとめっちゃ不便ですねぇ。。 どうせ人の手を使ってもやらないといけないことなので、 そういう情報は公開してもらいたいものですが。。 ### レーベンシュタイン距離をPHPで Googleのもしかして** みたいなのってどうやって出してるんだろうな~ とかうにゃうにゃ調べてたら レーベンシュタイン距離なるものを発見。 Wiki 言葉でアルゴリかかれてもよくわからないけど ソースコード見るとよくわかるな。。 というわけでチョット遊んでみようと思ったら。。。 http://jp2.php.net/levenshtein PHPの標準関数の中にありました。mjd ということで適当にPHP関数検索っぽいものを作ってみました。 反省はしてない。 PHP関数さーち ### form だけで firefox を判別する方法 ど どうでもいいけど、こんなことができてしまったのでメモ。 <form method="GET"> <input type="image" src="1.gif" name="aaa" value="aiueo" /> </form> これをやると firefox (つかgecko?)の時だけ aaa=aiueo が入る。 というそれだけ。。 POSTでももちろん飛ぶ。 なぜか 本来 type=image にvalue属性はないのですが、 なぜか firefox (gecko) だけはvalue属性もみて GET/POSTしてくれるわけです。 それを悪用wしただけです。 こういう違いを探していけば携帯のブラウザ判別ができる! というそれだけ。。(A`) ### Live Searchのクローラ バグで自分がいじってるサイトのTOPページ以外のすべてのコンテンツが404かえしてる (404のヘッダーだけ出してコンテンツ表示してる) という恐ろしいことになっていたんですが、 当たり前ですが、 yahooもgoogleもキャッシュは1(TOPページだけ) なのに Live Search は・・・ すごいな 404返しててもコンテンツがあるとキャッシュしてくなんて・・・ ### 携帯サイトとして認識させる@google 前にやっていた実験の続きをざっくり。 一度PCサイトと認識されたドメインはモバイルサイトになれない? 追加施策 ・PCサイトからのリンクをはってみた ・モバイルサイトからのリンクをはってみた 結果 1:ウェブマスターズツールにモバイル登録してるだけではだめらしい。  ⇒登録2日後にモバイルクローラがきて、1~2週間モバイルクローラが定期的にきていたが、  3週間たって突然通常クローラに戻った。  (キャッシュが進まなかったので、コンテンツ量を増やしたからかも?)  それ以降はGoogleモバイル検索でもPCサイト として表示された。 2:PCサイトとして運用したドメインを焼きなおすのはだめらしい。  ⇒PCリンクもあるせいか、1ヶ月たってもモバイルクローラはこなかった。 3:携帯サイトからのリンクは有効  ⇒携帯サイト として認識されているサイトからのリンクはSEOというよりは   モバイルクローラをひっぱってくるのに結構有効だった  ⇒PCサイトだったドメインもこれで、モバイルクローラがくるようになった。  以降Googleモバイル検索で携帯サイト として表示されるようになった。 結論 ・モバイルサイトとして認識させるにはサイトマップ登録だけじゃ不十分。 ・モバイルサイトとしての適切なコンテンツ量みたいなのがあるのかもしれない ⇒ 要調査 ・モバイルサイトからのリンクはかなり有効というか確実にやるなら必須っぽい どうやってモバイルサイトと判断してるかというのは Googleらしくリンク重視なのかなーと。 あと感覚的には デフォルトPCサイト。なんかのフラグで携帯サイトに変更みたい。 ### Google Webmaster Tools のApiが公開 Google Webmaster Tools のApiが公開 Google Webmaster Tools API Get Cooking with the Webmaster Tools API Your client application can use the Google Webmaster Tools Data API to view sites in your account, add and remove sites, verify site ownership, and submit and delete Sitemaps. たいしたことできないみたいで、 サイトの追加、認証、削除とサイトマップの更新、追加、削除だけ。 サテライトサイト量産するときに使うくらいしか思いつかないんだけど。。 10000サイトのくらいの管理が若干楽になるツールとか。 Wordpressの管理画面からサイトマップ更新かけれるとか、そんなもんかなぁ。 飛んでくるデータで使えそうなのを探してみる ・wt:indexed(インデックスされたかどうか) ・ wt:last-crawled(最終クロール日付)  ⇒これウェブマスターズツールの画面にある最終クロール日ってやつ  でもログみりゃわかるけど、クロール日じゃなくて インデックス日だとおもう。 ・sitemap-last-downloaded(サイトマップ取得日付)  ⇒これも実際にアクセスしてきた日付とずいぶん離れてる。 このくらいかな。。すくなっ つまるとこ 肝心の情報がないじゃんー 被リンクとか発リンクとか。 ってことがトラックバックに入ってた記事にかかれまくってて笑ったw やっぱりみんなそろって 「リンクデータもきっと開示してくれるでしょう!」って書いてありますねw まあ APIが拡張されたらツールでも作ってみましょうか。 ### httpステータスチェッカー 海外SEO情報ブログ・メルマガさんで面白いことが書かれていて、 robot.txtをまず見に行くgoogleにとって robot.txtで404が返ってこないで 変なとこにリダイレクトされたりすると、そのサイトをクロールしなくなるらしいです。 あとで、ほんとかどうかやってみたいとおもいます(といっても遊びとはいえキャッシュ消されるとか痛い。。) ついでにステータスチェッカーがどうのっていう下りがあったため、ザクっとつくってみた。 ※URLがかわったので更新(2024/04/08) ステータスチェッカー なんという手抜きw ### Wordpressのログ取り バージョンあげる前はずっと firestats とかいうのを使っていたのですが、 結構重いのと、DBがたくさんできて美しくなかったので、 今は、単純にテキストファイルにアクセスログを吐き出すプログラムを作ってまわしています。 まあ一応それを公開しますってことで。 wp-logger 別にwordpress以外でも使えるんだけど。 なんとなく wp ってかいてみただけですw 使い方 1:ZIPファイルを解凍すると wp-logger.php なるものができるので、  それをwordpressのフォルダ(wp-config.phpとかがあるフォルダ)にアップする 2:wordpressの管理画面から テーマエディタを使って footer.php (フッター)にこのタグを貼り付けてください。 <?php require_once('wp-logger.php'); ?> 以上! *うまくいかなかったら カキコミ権限とかいじってみてくださいまし。 これで 勝手に200806.log とかいう感じで月ごとのアクセスログがポンポン書き出されます。 こんな感じのログ(カンマくぎり)です。 日付,アクセスされたURI,IP,リモートホスト,ユーザーエージェント 2008/06/18 12:03:22,/,192.168.0.1,****.ocn.ne.jp,Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.8.1.14) Gecko/20080404 Firefox/2.0.0.14, ### simplexml_load_string で CDATAもとってくる 最初できないと思ってたんですが、 optionつければできるようですね。 こんなとこに書いてありました。 PHP5’s SimpleXML Now Passes CDATA Content PHP要件とか ただこんな要件が http://phpspot.net/ 導入 以下の関数/定数は、PHP 5.1.0 以降、 DOM, SimpleXML および XSLT のような libxml に基く エクステンションのどれかをコンパイルしている場合に利用可能です。 要件 このエクステンションは、libxml >= 2.6.0を必要とします。 こうやる これで $xml = simplexml_load_string($rssUrl, 'SimpleXMLElement', LIBXML_NOCDATA); echo $xml->item[0]->description; みたいな。 さくらで動かしたら動いた~ ### js を使わずに PHPだけで、loading メッセージを出して終わったら消す どうでもいい php tips こんなことがあるので 管理画面を作っていて 長い処理とかでブラウザのタイムアウトとかを防ぐ為に php だと echo "Now Loading..."; 処理やってる間1ループごとに echo "."; 処理が終わってから echo "End."; とか echo したりすると思うんですが、 それがお客にも見せるようなものだったりする特殊な場合 (ユーザがタイムアウトしそうな処理全部かもしれないけど) echo した Now Loading... が画面にずっと残っちゃってうざいね ってことで、 それを js を使わずに消してしまおうと。 魔法を使って解決します 魔法でも なんでもないんだけどw こうする。 echo "<div id='loading'>Now Loading..."; 処理やってる間1ループごとに echo "."; 処理が終わってから echo "End.</div>"; とPHPで書いておいて CSS で #loading { display:none; } こう書けばOK。 これで、処理中は .... がどんどん増えていって End が表示されて、 その後PHPの残りのプログラムが処理されて、 最後に html を生成して、display:none が当たって見えなくなるわけです。 HTMLの読み込み時間差を利用するかんじ。 応用するとこんなことが PHPとCSSだけで可能です。 <?php ob_start(); echo " <div id='loading' style='width:100%;margin:0;padding:0;height:100%;text-align:center;'> <div style='position:absolute;top:50%;left:50%;margin-left:-7.5em;margin-top:-1em;'> <div style='width:500px;'> <ul id='loading' style='list-style-type: none;'> "; for ($i=0;$i <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>ローディングテスト <style> #loading { display:none; } </style> </head> <body> 全部完了したらこれだけが表示されます。 </body> </html> おすすめのレンタルサーバーを徹底比較! ### 一度PCサイトと認識されたドメインはモバイルサイトになれない? モバイルサイトの実験をしてる過程でこんな状況になったのでメモ。 仕込み 携帯用CMSフレームワークを作ったので、 それを ・テストサイト1:既存にあるドメイン(被リンク自前サイト100本くらい&PCのGoogleでキャッシュされてる) ・テストサイト2:新規取得ドメイン(被リンク0) にのせて、コンテンツボリュームもほぼ同じに調整した状態で、 Google Web Masters Tool に ケータイ/cHTMLで登録。 2週間での経過 テストサイト1のクロール状況 ⇒モバイルクロールすらされず。  もちろんキャッシュもされず。 ものの見事に初日にきた通常BOTしかきていなくて、 かつクロールしていったのにPC版Googleでみてもキャッシュが変更されていない (これはモバイルコンテンツだからコンテンツボリュームが少ないから?) で以下履歴 Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html),66.249.70.214,crawl-66-249-70-214.googlebot.com Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html),66.249.70.214,crawl-66-249-70-214.googlebot.com Google-Sitemaps/1.0,66.249.85.88,ff-in-f88.google.com Google-Sitemaps/1.0,66.249.85.88,ff-in-f88.google.com Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html),66.249.70.214,crawl-66-249-70-214.googlebot.com Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html),66.249.67.236,crawl-66-249-67-236.googlebot.com ・ ・ ・ 以下ずっとこれ。 テストサイト2のクロール状況 ⇒モバイルクロールされた。それもすぐに。  キャッシュは1ページだけだけどこれはコンテンツボリュームの問題かな。 サイトマップ突っ込んで2日でにいきなりモバイルクロール開始。 初日は通常のクローラ、3日後にモバイルクローラがきてページをガンガンクロールしていく。 (ちなみにGoogleのサイトマップ しか ないのになぜかこんなとこからもアクセスがくるという219.118.188.122) Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html),66.249.73.20,crawl-66-249-73-20.googlebot.com Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html),66.249.70.214,crawl-66-249-70-214.googlebot.com Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html),66.249.67.236,crawl-66-249-67-236.googlebot.com Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html),66.249.67.236,crawl-66-249-67-236.googlebot.com Nokia6820/2.0 (4.83) Profile/MIDP-1.0 Configuration/CLDC-1.0 (compatible; Googlebot-Mobile/2.1; +http://www.google.com/bot.html),66.249.67.236,crawl-66-249-67-236.googlebot.com Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html),66.249.67.236,crawl-66-249-67-236.googlebot.com Nokia6820/2.0 (4.83) Profile/MIDP-1.0 Configuration/CLDC-1.0 (compatible; Googlebot-Mobile/2.1; +http://www.google.com/bot.html),66.249.67.236,crawl-66-249-67-236.googlebot.com ・ こっから携帯BOTで全クロール ・ ・ Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html),66.249.67.236,crawl-66-249-67-236.googlebot.com Google-Sitemaps/1.0,66.249.85.88,ff-in-f88.google.com Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html),66.249.67.236,crawl-66-249-67-236.googlebot.com 結論 ってことで2週間たっても音沙汰ないあたり、 一度PCサイトと認識されたドメインはモバイルサイトとして 認識されるのにすごい時間がかかるってことですかね。 つかまだ認識すらされてないので、 どのくらいで認識されるのかもおいおい検証ってとこですね。 ### MySQL server has gone awayで吹いた 出社してサイトチェックしようとしたら繋がらない。 ちょうどタイミングよくメールでDBのパス(同じサーバの別DB) を変更したとか言うアナウンスがきたので それ関係するのかなと調査開始。 原因がまずわからん パスはどうも大丈夫なよう(当たり前だけど) で 繋がらないのはなぜか。。DNS? いやでもDB接続しないただのテキストファイルとか開けるし。。 と 30分くらいうだうだ悩みつつF5押しまくってたら 突然繋がった。 が。。 真っ白。 管理画面にアクセスしてみると 「MySQL server has gone away」 の文字が。 どっかいったってどういうことだwwwwwwwww DBを直すか。。 とりあえずgoogle先生に聞こうってことで 聞いてみると どうやらDBがぶっ壊れてるかアクセス過多か クエリーがでかすぎだと出るらしい。 ・アクセス過多はない(ローンチして1週間くらいのサイトだし) ・クエリーも最適化はある程度してある(と信じたい) ・まさかぶっ壊れた?? ってことで直でDBにつないで SELECTしてみる。 ⇒普通にOK CHECK TABLE ぽぃ。 ⇒「Table is already up to date」しかでない。 #「Table is Table is marked as crashed」って出れば話ははやかったのに。 #SELECTもUPDATEもできるのに Table is marked as crashed なんてでないか。。 だがサイトにアクセスしてみるとやっぱDbに繋がらない。 ええいめんどくせー ってことで repair table 発動。 。 。 。 直った。 結局 「Table is marked as crashed」 以外でもぶっ壊れてるってことがあるんですね。 ### Wordpressをバージョンアップしてみた Me だったので いい加減と思って 最新版2.5にアップしました。 アップといってもDB構成からなにから全部違ったので 全部消して再インストール。。 まあおかげで、2002年から使ってたDBに イオナズンならぬ drop database かますことができて <-人生初 ちょっとすっきり。 はまったのは カテゴリーとタグのところ。 何気なくエントリーのインポートしてたら、 「カテゴリーからタグへの変換」 とか書いてあるから、お これはいいね。 ってことでポチしたら。。 カテゴリーが表示されなくなりました orc 削除というよりは厳密には カテゴリーのカラムがタグ に全部書き換わって (なぜか2こづつでてきてましたが。)いるようなので。 なおせるには直せるがめんどくせーー とりあえず SQLでちまちま直しました ついでにタグも作ってみたけど 1から記事に割り振るのがめんどくさくてやめやめ。 とりあえず管理画面はかなりよくて 特にウィジェットがはんぱなく使いやすいです。 アップデートしてよかった~ ### ex_file_get_contents を作ったので。。 前のエントリーで書いてた fsockopen のソースがひどいやつだったので ちゃんと動くように修正したので、一応。 最初strippos使ってたんだけど、php4じゃ動かないから書き直したりして 結局php4でも動くように。 ちなみに ヘッダーからゴソっともってきてるので、 html部分に出すのはワンクッションおいてください。 https とか ssl はめんどくさくて非対応w /** * ex_file_get_contents:file_get_contentsの代替バージョン。 * * @author jakk * @copyright 2008/04/25 neoinspirations * @param string $server 形式のURL * @param int $timeout タイムアウト秒数 * @return 成功した場合 $urlのhtmlデータをstringで返す 失敗した場合 false Ex. $html = ex_file_get_contents($url); */ function ex_file_get_contents($uri='',$timeout=10){ if (!empty($uri)) { $uri = strtr($uri,array("http://"=>"")); $domain = strpos(strtolower($uri),strtolower('/'),3); if ($domain !== false) { $server = substr($uri, 0, $domain); $file = substr($uri, $domain); } else { $server = $uri; $file = "/"; } $fp = @fsockopen($server, 80,$errno, $errstr, $timeout); if ($fp) { $out = "GET " . $file . " HTTP/1.1\r\n"; $out .= "Host: " . $server . "\r\n"; $out .= "Connection: Close\r\n\r\n"; @fwrite($fp, $out); $data = ''; while ($eofCheck=@fgets($fp,1024)) { $data .= $eofCheck; } @fclose($fp); } else { return false; } } else { return false; } return $data; } ### php の file_get_contents で true も false も返ってこない 特定のURLでfile_get_contents で true も false も返ってこない現象があって 困っていたのでその対策メモ 現象 単純に $html = file_get_contents($url); ってやるとタイムアウトすることがあって、 それは ini_set('default_socket_timeout',3); ってやってもダメなパターン。 問題はタイムアウトじゃない? file_get_contentsがだめでもこれ以上切り分けできないので、 処理を1個づつかくために fsockopen 使ってみたけどやっぱりだめ。 fsockopen がダメな時のソースはこんなかんじ。 $fp = fsockopen($server,80,$errno,$errstr,$timeout); if($fp){   while(!feof($fp)){     $data .= fread($fp, 1024);   } } echo $data; で こっから1こづつ切り分けていくと どうも while(!feof($fp)){   $data .= fread($fp,1024); } ここが無限ループになってるらしい。 file_get_contentsのCソースまでは追いきれないので (Cソース探したけどどこにあるかわからなくて) カンですが、file_get_contents も同じ感じの処理をしていて、 feof のとこで詰まってるんじゃないかと。 だとしたら、ソケットの問題じゃないので、default_socket_timeout を指定しても意味なかったってわけです。 こうやって解決 問題が無限ループだったので php feof で検索したら・・・ 関連検索: php feof 無限ループ, php feof fgets 同じ問題で詰まった人がいっぱいいるw! で見ていくと http://jp.php.net/feof 無効なファイルポインタを渡した場合、無限ループに陥ることがあります。 なぜなら EOF が TRUE を返すことができないからです。 ということらしい。 つまり feof が true を返さないので、feof で判定するのはダメなわけだ。 おそらく何らかの形でEOFが受信できない場合も trueが返らないんだろう。。 なので feof じゃなくて fread が成功したかで判定するように変更 $fp = fsockopen($server,80,$errno,$errstr,$timeout); if($fp){ while($eof_check= fread($fp,1024)){   $data .= $eof_check; } } echo $data; したら うまくいった! ってことで 結局こんな感じの関数作って対処しました。 function ex_file_get_contents($server='',$timeout=10){   $data = '';   if(isset($server)){     $fp = @fsockopen($server, 80,$errno, $errstr, $timeout);     if($fp){       while($eof_check=@fread($fp, 1024)){         $data .= $eof_check;       }       @fclose($fp);     }else{       return false;     }   }else{     return false;   }   return $data; } ### Ngramを利用したページ類似度チェッカー+Ngram計算のphpクラス Ngramを利用したページ類似度チェッカー+Ngram計算のphpクラス Ngram まあ何がやりたかったって スパムチェック的な類似度チェッカーです。 こんなかんじ。 類似度チェッカー 全く違うURLだと5~10%くらいになって ちょっと関係がある(リンク紹介してたり)すると20%くらいかな 同じブログの違う記事とかだと40%くらいになって 60%超えたらかなり似てるページってかんじのラインです。 例えば wikiの アイスランドのページとアイルランドのページとかでやると 重複度77%とかになります。 Googleは言語にとらわれない解析方法を使っているとのことなので、 高確率で、どっかにNgramは使ってると思うので、 デュプリケイトコンテンツとかの参考に使えるかなーと。 一応ソース公開 たいしたものじゃないんですが、たいしたものじゃないだけに ソースくらい出しとけというお話です。 アルゴリズム的には単純に ・URLからタグを引っぺがす ・出てきたテキストのNgram(3)を抽出 ・2個URLの共通項を出す(php の array_instersect 使っただけ) ・(共通項の数 / Ngram(3)の個数) % まあなんか array_intersect とarray_diff がま逆の動きをしないので あやしいんですが。。 ソースを追うのもめんどいので後で検証。 使い方 Newして(引数でNgram2,3とかコントロール) RunメソッドにURL突っ込むだけです。 こんなかんじです。 (以下見やすくする為にインデントを全角スペースに変換してあります。) $Ngram = new CreateNgram(); if($Ngram->run("http://dev.neoinspire.net/archives/92","http://dev.neoinspire.net/archives/90")){   print($Ngram->result);   echo"<pre>";   print_r($Ngram->mainNgram);   print_r($Ngram->checkNgram);   echo"</pre>"; }else{   echo "対象のURLを解析できませんでした。"; } 以下ソースです。コピペしてUTF8で保存してください。 <?php /* *2つのURLのNgramを比較して類似度を出すクラス *引数を一つで出した場合単純に対象URLのNgramを抽出する */ Class CreateNgram{   var $mainNgram; //第一引数URLのNgram   var $checkNgram; //第二引数URLのNgram   var $ngram; //Ngramをいくつでやるか   var $result; //類似度   function CreateNgram($ngram=3){     $this->ngram = $ngram;   }   function run($mainUrl=null,$checkUrl=null){     if(!empty($mainUrl) && !empty($checkUrl)){       if($mainHtml = mb_convert_encoding(@file_get_contents($mainUrl), 'UTF-8', 'auto'))         $this->mainNgram = $this->_ngram($this->_solid($mainHtml));       if($checkHtml = mb_convert_encoding(@file_get_contents($checkUrl), 'UTF-8', 'auto'))         $this->checkNgram = $this->_ngram($this->_solid($checkHtml));       if(!empty($this->mainNgram) && !empty($this->checkNgram)){         $mainResult = @array_intersect($this->mainNgram,$this->checkNgram);         $checkResult = @array_intersect($this->checkNgram,$this->mainNgram);         $result1 = count($mainResult);         $result2 = count($checkResult);         $cnt1 = count($this->mainNgram);         $cnt2 = count($this->checkNgram);         $this->result = @round((($result1 + $result2) / ($cnt1 + $cnt2)),3);         return true;       }else{         return false;       }     }else{       return false;     }   }   function _ngram($query=null){     $query = trim($query);     if(!empty($query)){       $len = mb_strlen($query,'UTF-8');       for($i=0; $i< $len; $i++){         $buf = trim(mb_substr($query, $i, $this->ngram,'UTF-8'));         if(isset($buf))$ngrams[] = $buf;       }     }else{       $ngrams = "";     }     return $ngrams;   }   function _solid($html=null){     if($html){       $pat[]="/<script.+?\/script>/is";       $pat[]="/<style.+?\/style>/is";       $pat[]="/<\!\-\-.+?\-\->/is";       $pat[]="/<.*?>/is";       $pat[]="/\r|\n|\s/is";       $rep="";       return preg_replace($pat,$rep,$html);     }else{       return "";     }   } } ?> ### ウェブサイト オプティマイザーを使ってみた LPOとか言われて久しいですが、 Googleがまたこんなものを開発した模様です。 ウェブサイト オプティマイザー コンバージョンをはかりたいページを1個選んで それを色々カスタマイズしたバージョンをたくさんつくって (例えばサイドバーが右と左とかボタンが上にあるとか下にあるとか) それの効果検証ができるようです。 アフィリサイトとかもってないので適当に 自作のツールページからこのブログに飛んでくるのがコンバージョンとして設定してみました。 レポートは1日必要なんでどんな感じなのかまだわからないですが。 やってみたので軽く説明 とりあえずここでアカウントを登録 https://www.google.com/analytics/siteopt/splash?et=reset&hl=ja 1:登録したら左上のほうにある「テストを作成」リンクをクリック 2:とりあえず「A/B テスト」ってことで 「作成」ボタンをクリック 3:チェックボックスにチェックいれて続行 4:テストの名前を決めて、オリジナルのページ(比較元になるランディングページ)   その次に比べるページ(カスタマイズしたランディングページ)をいくつか登録   し最後にコンバージョンページを指定 5:つらつらとJSタグが出てくるので全部に設定して終わり アフィリエイトとかはかるのであれば、クッションページ作ってそこにタグはらないとだめですね。 まあほかにもadsenceの貼る位置のチェックとか 会員登録ボタンの設置位置とか そういうのもはかれそうです。 googleはこえぇ にしてもgoogleの発想というのは、 ほしいデータはユーザに使わせて取れ ということなんですかね。 analyticsとか。 まさに うぃずだむおぶくらうど(笑) ### FirefoxとIEでヤフカテのリンクが違う件 こんなエントリーがあったので YahooカテゴリのリンクがIEとfirefoxでは違う 調べてみた。 状況確認 IEでみると <a href="http://www.searchengineoptimization.jp/" onmousedown="return catrd(this,'&l=SIT&sid=2077584930&ssid=2076957840&loc=1&sig=1c4257cfbecbbc422d0d69bb26b0eac8&rd=','','site');">SEO検索エンジン最適化</a> Firefoxでみると <a onmousedown="" href="http://dir.yahoo.co.jp/proc/?m=redir&l=SIT&sid=2077584930&ssid=2076957840&loc=1&sig=1c4257cfbecbbc422d0d69bb26b0eac8&rd=httpwww.searchengineoptimization.jp">SEO検索エンジン最適化</a> おお確かに違う でもそういや似たようなこと俺もやったな。。 onmousedown って確か Javascriptの挙動の違いがあったきがしたなぁ。。 と思い出したんで、ちょっとテストしてみました。 このページでIEとFirefoxで 右クリックしてみてください。 テストページ あれれ? そう Firefoxだと右クリで javascript が反応してないですね。; ようはブラウザによってjavascript の挙動が違うので正確なクリックカウントができないじゃんということなんでしょうね。。 たぶん本当なら統一したほうがいいので、 firefoxバージョンでやるのがいいんだけど SEOの効果がないとヤフカテが売りあげに影響がある・・ なんてことだったりしたら面白いですね! というわけで、意図的にプログラム側で書き分けているっていうのが結論です。 おまけ ちなみに 選択した部分のソースを表示ってやるのと、CTRL+U押すのとで、 結果が変わるのは、Firefoxの仕様で 取得したHTMLをDOMツリーに展開した後でその部分のソースを見てるからだそうです。 (CTRL+Uを押した時はDOMツリーに展開前のソースを表示させているということですね) ### yahooのクローラ ljとrzだけのお話 またすげーマニアックな話を・・ yahooのBOTはたくさんあるっぽいわけですが、 以前のエントリー「スパイダーとクローラが別物なわけ」で知った話も踏まえて実験をしていて、 気づいたことをまとめておきます。 元々はスパイダーとクローラーは別みたいな話だったので、 LJ系のクローラ「だけ」見えないリンクを貼って そのページがyahooにキャッシュされるかとか見てました。 実験 何をやったかというと、3月21日から キーワード適当に 「みれすふぃお」で1ページからリンクをはって そのページに LJがきたら lj.php それ以外は no.php に飛ばすリンクを出すように仕込んでおく で lj.php と no.php にはBOT名を出すように仕込んでおく という簡単なものです。 ということでこんなかんじ http://seo.vertical-search.jp/seo/index.php 結論からいえば lj系でキャッシュしてる。<-「現在の」クローラはLJのデータを元にしている lj系でリンクをたどっている<-「現在の」スパイダーもLJのデータを元にしている rz系ではキャッシュはしてない感じ or rz系のキャッシュは現在反映されていない rz系でもリンクをたどっている<-スパイダーが2個ある? とったログはこんな感じ (日付、ファイル名、BOT名、BOTIP) 2008/03/27 12:45:38`index`crawl-66-249-70-134.googlebot.com`66.249.70.134 2008/03/27 02:17:59`no`crawl-66-249-70-134.googlebot.com`66.249.70.134 2008/04/02 04:50:23`index`crawl-66-249-73-26.googlebot.com`66.249.73.26 2008/04/02 10:27:17`no`crawl-66-249-73-26.googlebot.com`66.249.73.26 2008/04/02 10:39:45`index`lj511500.crawl.yahoo.net`74.6.25.154 2008/04/02 10:42:09`lj`lj512649.crawl.yahoo.net`74.6.29.109 2008/04/02 10:42:52`index`lj512709.crawl.yahoo.net`74.6.27.163 2008/04/05 12:49:33`index`crawl-66-249-70-186.googlebot.com`66.249.70.186 2008/04/06 02:37:05`index`rz311270.crawl.yahoo.net`67.195.51.227 2008/04/06 02:40:12`index`rz311341.crawl.yahoo.net`67.195.52.124 2008/04/06 02:43:56`no`rz311327.crawl.yahoo.net`67.195.52.113 2008/04/06 07:51:45`no`crawl-66-249-70-186.googlebot.com`66.249.70.186 2008/04/09 10:40:48`index`lj511500.crawl.yahoo.net`74.6.25.154 2008/04/09 10:44:07`index`lj512709.crawl.yahoo.net`74.6.27.163 2008/04/09 10:44:16`lj`lj512649.crawl.yahoo.net`74.6.29.109 最初にlj系が 2008/04/02 きて index.phpをキャッシュした後 リンク先の lj.php を見つけてそれを見に行っています。 つまり LJがリンクを自分で見つけたということです。 ついでにいうとYahooに出てくるキャッシュファイルから LJクローラがキャッシュしたことも確認しました。 Yahooでのキャッシュ BOTの名前とふづけがぴったり一致したので初回にきた時にキャッシュしてます。 さて、それとは別に rz系クローラが 2008/04/06 にきています。 こいつが謎なのですが、indexを見た後に no.php(LJ系ではアクセスできないページ)を見に行っています。 つまり rz系はそれはそれで別にリンクを見つけてたどっているということになります。 しかしながら、キャッシュやらURLやらを色々チェックしたんですが、 現在のYahooにはどうやら反映されていないようです。 or そもそもぜんぜん別の行動をとっているのかもしれない? ということで 次は rz系の研究ってことになるのかな。 どこから考えればいいのか検討つきませんが。。w ### Googleの技術書 仕事の関係でSEOに関してだいぶ動向が把握できるようになり、エントリーもするようになったんですが、 その際質問だったり、メールやコメント等を頂くようになり(めちゃくちゃうれしいですが!)、 また色々なSEOの方のブログを拝見していて気づいたことは、 意外とSEO専門家は技術に詳しくない(別に私もぶっとんで詳しいわけではないですが。。。) ということ。 ということで、この本 技術書なんですが、SEOやっている方にも進めてみたいと思い。 Googleを支える技術 ~巨大システムの内側の世界 ↑アフィリリンクなので踏むのが嫌なかたは 「西田圭介 / Googleを支える技術 - 巨大システムの内側の世界」 これでぐぐってください。 *Googleのパテントとか見てる人にはいらないと思います。 技術書だとおもうので、純粋にSEOとしては必要のない部分(ハードウェア管理系の話とか)が多いですが、 map reduce(hadoopが実装してるアルゴリズム)の話とか 1章は検索の話もあります。 googleがすごいのは検索力よりも(検索力もすごいですが、そのすごい検索力を支えている) インフラの部分であるということがわかる本だと思います。 アマゾンアフィリエイトとか始めてやったからうまく動かなかったりして。 ### 便利なphp foreach のオプション? 答えだけ言うと単に型キャストですがw foreachをまわす時 引数が配列じゃないとこんなメッセージが Warning: Invalid argument supplied for foreach() in ******** on line 10 がでるかと思うんですが、これを出さないための対応方法。 今までこんな感じに処理してました。 if(is_array($def)){ foreach( $def as $k=>$v){ print $v; } } が、実はこんな foreach の書き方がありました。。 foreach( (array) $def as $k=>$v){ print $v; } なるほど。。こういう型キャストの使い方があったのか。。 動作の説明 (array)$def で $def を array に変換してるんですね。(ちなみにこれを type casting といいます。) なので foreach のエラー(これは array じゃねーよ!)というエラーがでなくなると。 これでソースが2行も減りました。 ちなみに、 array だけじゃなく object や int もあります。 型の相互変換 でもさすがにこれはひどいな。。 $binary = b"binary string"; 最後 こんな芸当が可能に。 var_dump((object)(int)(string)true); //string(1) "1" が表示 必殺 チェインタイプキャスティングw ### array_merge を使った初期値設定 Wordpressの内部なんかで使われてる手法なんですが、(いまさら?) クラスとかのコンストラクタで引数のないものだけデフォルト値をセットするみたいな時に。 普通にやったらまあこんなかんじ $org = array('a' => 'good' , 'b' => 'bad'); func_r($org); function func_r($org=''){ //キーがなければデフォルト値、あったらそのデータを格納 $a = (array_key_exists('a', $org))?$org['a']:'default_a'; $b = (array_key_exists('b', $org))?$org['b']:'default_b'; $c = (array_key_exists('c', $org))?$org['c']:'default_c'; } //$a=good,$b=bad,$c=default_c が格納される これを array_merge を使うと $array = array('a' => 'good' , 'b' => 'bad'); func_s($array); function func_s($org=''){ $def = array('a' => 'default_a', 'b' => 'default_b', 'c' => 'default_c'); $res = array_merge($def, $org); extract($res, EXTR_SKIP); } //$a=good,$b=bad,$c=default_c が格納される 行数は一緒ですが、array のデフォルト値の管理がわかりやすい! あとなんとなく冗長じゃなくて見やすい。 動作としては、第一引数と第二引数をみくらべて 同じキー文字列があれば、そのキーに関する第二引数の値が、 第一引数の値を上書きします。 (文字通りマージする) ⇒ちなみにキーが数値の場合は追記になっちゃいますので注意 ($org[1]と$def[1]をマージすると$res[1]と$res[2]に入っちゃいます) ### Googleの流出文書?を翻訳してみた(ちょっとだけ) 本物っぽいですねぇこれ。無論誰も確証なんてだせませんが。 以下ちょー長文です。 ためしにこのURLをひっぱたくと https://www.google.com/evaluation/search/rating/home Forbidden The user ******@gmail.com is not a member of EWOQ. Please ask your EWOQ contact for access. とでるので、本物っぽい(このURLを知ってる時点で)ですねぇ。 まあぜんぜん確信できる証拠じゃないですが。 (やめた人がネタで書いてる可能性もあるしね~) まあ本物だったらおもしろいなーってことで、部分的に意訳してみます。 とりあえず内容としては Webサイト評価の方法と そのツールであるEWOQの使い方 あとスパム判定系のお話ってところですね。 全部で43ページもあって長いので 面白そうなスパム系のとこだけ ------------ここから------------ P32からのスパムサイトについて Webサイトの評価はクエリーと共にするものです。 しかしスパムサイト評価はクエリーに依存しません。 怪しい技術を使ったスパムサイトはどんなクエリーに対してもスパムサイトです。 #ページ match 検索クエリー という考え スパマーが報酬を得る方法 ・PPC ・アフィリエイト しかし、これらのサイトは付加価値がある場合はスパムサイトとはなりません。 (ようはオリジナルコンテンツとか役に立つコンテンツがあることかな) スパムサイトじゃない例はこんなかんじ。 ・価格比較サイト ・商品のレビュー ・レシピ ・歌詞や引用文 ・連絡先のあるページ(特に住所とかが書いてあるページなど) ・クーポンやディスカウント系の情報 以下スパムサイトの例 PPC ・PPCのみのページ ・フェイクディレクトリ型検索エンジン(検索結果が全部PPCみたいな) ・フェイクブログ(PPCをクリックさせるためだけにあるブログみたいな) ・フェイク掲示板(書き込みがPPC広告みたいな) ・コピーコンテンツ+PPC #ようはコピー記事を全部とっぱらったら広告しかないサイトはダメですよ 見分け方 ・文章をそのまま検索窓につっこんで検索 ・Site:で検索して文章テンプレートとかを見てみる 確保してあるドメインの利用 確保したドメインを利用してこんなコンテンツを載せている ・スポンサードリンクのリスト ・どこにでもありそうなカテゴリリスト ・関連カテゴリのリスト こんなサイトがいい例です www.dasonet.com/todahfezkdk.htm #これよくみますよね またこれらのドメインを売買する #ドメインの売買もNGっぽい雰囲気 #スパムドメインじゃない場合はどうするんだろ 見分け方 ・インターネットアーカイブを見てみる #まじだw アフィリエイト ・PPCとほぼ同じ アフィリエイトだけのサイトとか。 オリジナルコンテンツが重要 PPCと同じくアフィリエイト+オリジナルな解説とかはOK 見分け方 ・住所、電話番号があるかどうか ・フォーラムがあるかどうか ・ログイン機能があるかどうか ・アフィリエイトを展開(やるんじゃなくて広告主のほう)してる ・ショッピングカートのリンクが同じドメイン 隠しテキスト、隠しリンク ユーザには見えない(見えにくい)がロボットには見えるたぐいのものすべて #もちろん例外はあるみたい 見分け方 ・CTRL+Aを押して全部みてみる ・ブラウザのJSをオフにして見てみる ・ブラウザのCSSをはずして見てみる JSでリダイレクト ユーザとロボットで表示を切り替えるようなやつのことかな 見分け方 ・googleキャッシュと今みてるページとの差分をチェックする キーワードスタッフィング とにかくキーワード詰め込むやつかな 見分け方 ・目で見れる場合はそのまま ・JSを使って隠してる場合はJSを切ってみよう URLにキーワードスタッフィング #サブドメインを切り出してページでも生成してるんだとおもう ⇒does not make sense だってw 100%フレームページ ・ページフルにフレーム使ってる場合 見分け方 ・右クリしてフレームのURLをみて今のURLと違うことを確認 せこいリダイレクト ようは一旦検索エンジン用のページを開いてその直後にリダイレクトして 他のドメインにあったりするページを見せるってことかな 見分け方 ・ドメインが違う場合、whoisとか使ってドメイン所有者を見てみる #これってURLが変わりましたってやつで5秒後にリダイレクトとかのあれが #別のレンサバ(ドメインをレンサバ名義にしてるやつ) #にしちゃうとNGってことですね ------------ここまで------------ 雑感ですが、意外とGoogleですらできないことって多いんですね。 例えば アクセスしたURLと今表示してるURLの違いとか FireBugがやってるようなこととか(レンダリングエンジンをそのまま積み込むわけにはいかないのか) でもここまでやるから検索性能を維持できているんだろうなぁ。 まあ実装はしていても、人間の作ったものをプログラムで見分けるわけだから 100%じゃなくって、その100%じゃない部分の補完として使ってる可能性もありますが。 とりあえず spammy site という表現が気に入ったw 最後 信じるも信じないもあなた次第!(ぱくり ### スパイダーとクローラが別物なわけ またまたSEOのお話 最近SEO BookMark なるものがあって 登録してみたんですが、見てるだけで色々新しい発見あって面白いです。 (SEOのナレッジのために自分はSEO Vertical Blog Searchを作ったんですが、 明らかにこっちのがクオリティも量も新規性も維持できますね~ カシコイ!もうこれやめよっかな。) それはそうとまた面白い記事を見つけたのでちょっとエントリー 検索エンジンの5つのプログラム - スパイダーとクローラって別物? in SEOブログ from SEO塾 よくよく考えてみると当たり前なんですが、 確かに言及するにいたってないですね。気づくことって重要ですね。 こういう時海外の情報をすばやく取得できる人がすごいです。 *ちなみにSEO Vertical Blog Searchでもスパイダーとクローラは別になっています。 ⇒が独立モジュールではありません。 ということで、どうしてこうなるかというお話。 スパイダーとクローラが別である理由 (スパイダーとかクローラとか名前はどうでもいいんですが、ようは URLリストを作るプログラムと キャッシュするプログラムってことです。) 開発者っぽい視点で検索エンジンを考えてみるとこうなります。 ●ほしいもの 検索エンジンを作るためのDB作成 ブレイクダウン:WEB全体のページデータがほしい(できるだけ新鮮なのがいい)        :ページを取得するためにURLリストがほしい(新鮮なページのために新鮮なURLがいい)        :ページ全体を分析したい        :分析したやつを超早く出力したい ⇒という要望なので、こんなかんじにしましょう 1:WEBのURLリストが最速でほしい(スパイダー) 2:URLリストを順に見て行ってデータをひとまず保存したい(クローラ*キャッシュ) 3:WEBページを色々分析したい(インデクサ) 4:分析結果をすばやく検索結果として出力したい(アウトプット) #なぜURLリストが最速で欲しいかというと、 #そのURLリストがそのままデータの鮮度に直結するからです。 #このリスト作成サイクルが早くなればなるほど、情報の鮮度がよくなります。 じゃあどうするかってことで、 スパイダーとクローラが同じ場合 プログラム1:URLリストDBを元にURLにアクセスして「以前と変更があったらキャッシュDBに格納」し、        そのキャッシュにあるURLを抽出して、「既存にあるURLと重複がなければURLリストDBに格納」する プログラム2:キャッシュDBのデータを色々分析 プログラム3:検索結果を出力 ->この場合単純に1プログラムの負荷が高すぎるというのもありますが、  URLリストの作成とキャッシュを作成してURLリストを作成するのが同時なので、  一回のプログラムの実行時間が長くなってしまい、URLリストの鮮度が保てません。  *しかもこの場合、キャッシュしたページのURLを優先するのか、リストを優先するのかとか、   いろいろクローラー<>スパイダー間の関係が複雑になります。  さらにプログラムのチューンアップとかも考えると、別モジュールにしたほうがよいです。 *よくあるアルゴリズムを更新しました とかのときに、   URLリストから生成しなくてはならないのも問題ですね。 スパイダーとクローラをわける場合 プログラム1:キャッシュDBを順番に見ていって「既存にあるURLと重複がなければURLリストDBに格納」する プログラム2:URLリストDBからURLにアクセスして「以前と変更があったらキャッシュDBに格納」する プログラム3:キャッシュDBのデータを色々分析 プログラム4:検索結果を出力 ->プログラムが増えますが、URLリストを作りつつ別軸でキャッシュもできるので、  優先的にキャッシュしたいURLなんかを設定したとしても、URLリストの作成に影響がでません。  URLリストの優先順位とかキャッシュするしないの閾値とか、そういうチューンアップも  別々に行えます。 おまけでインデクサとスパイダーをくっつける場合 プログラム1:URLリストDBからURLにアクセスして「以前と変更があったらキャッシュDBに格納」する プログラム2:キャッシュDBのデータを色々分析する中で必要なURLを見つけてばURLリストDBに格納する」 プログラム3:検索結果を出力 ->これもありっておもってます。  メリットとしては必要なURLだけアクセスすることで、  サーバの負荷を減らせること。  デメリットはURLのリストが小さくなってしまうこと。  あとチューンアップとか速度とかの問題。  ちなみに SEO Vertical Blog Search はこの形態です。 *補足1:プログラムとして他人のURLにアクセスすること が一番負荷が高く 自分のDBにアクセスしてURLをチェックすることと比べるとコストは何倍にもなります。 (俺の感覚ですがw) *補足2: 3:WEBページを色々分析したい(インデクサ) というのは極端な話、クロール最中にしてしまってもいいわけです。 =URLにアクセスしてURLリストを作りつつインデックスしてもいいわけです。 (つまり インデクサ+クローラー+スパイダーの1プログラム) が これは一番効率が悪いので分割してると思われるわけです。 そういう意味で、キャッシュ というのは中間生成物なんですよね。(アーカイブとは違うわけです) それを単にユーザにとって便利かもしれないということで、 YahooもGoogleも開示してるっていう。 プログラムが5こ? 当該のエントリーの引用元では5個のプログラムとなっていますが、 まあそれはないのかなーと思うので書きます。 上記の通りDBだけでも、 スパイダーが作ったURLリストDB クロールしてキャッシュしたキャッシュDB インデックスした後のインデックスDB は必要なので、 DBもプログラムというなら5個以上になりますね。 ちなみにインデックスも仕方があって(これは長くなるので別エントリーにします) ページ主体でインデックスする場合とワード主体でインデックスする場合で DBの格納の仕方がかわるので、さらに増える可能性もありますね。 というかアメリカの人はDBをアプリというのか。。 おまけ 改めて気づいて考えてみると なかなかおくが深いというかなんと言うか。 実際にGoogleが100%こうなっているとは思えませんが、 近いイメージなんではないでしょうか。 そうそう当たり前ですが、規模が小さい場合クローラとスパイダーを一緒にしてる場合が結構あります。 そういうのは結構出回っているので、自分で使ってみるのもいいかもしれませんね。 しかし、めちゃくちゃでかい(WEB全体とか)をやろうとおもったら、 分離しておくのが普通の発想なのかもしれません。 (というか普通のサーバじゃWEB全体をクロールとかむりだw) ちなみに検索したらsouceforge にスパイダー+クローラーのPHPクラスがおいてありました。 http://sourceforge.net/projects/phpcrawl/ 挙動は確認してません。 ってういか・・・ エントリー長すぎだしまとめるのが不能になった・・・ ### 検索エンジンの日本語解析力 最近SEOのエントリーばっかりですが。。 こちらのエントリーをみて気になったので。 http://e-club3.hyperposition.com/seoblog/terminology/20080308083139.html まあ気になったのは 「肝心の検索エンジンの日本語キーワードの解析力はどうなのだろうか?」 というところです。 yahooでは形態素解析をつかって 日本語を分解して文脈で理解しているのは APIを公開してるとこからみてほぼ間違いないんだけど、 Googleはそれはやっていません。(それっていうのは日本語を「解析」するということ) 一応ソース Google の鵜飼文敏さんによる講演会の内容 http://nanto.asablo.jp/blog/2008/01/25/2578762 国ごとに検索をローカライズしていく=その言語の研究をめちゃくちゃしなければいけない というのは スマートではないわけで、 Googleが考えそうなことというのは、言語に依存しないページからのナレッジの抽出技術 みたいなものだったりするのかなと。 言語に依存せずに文脈を理解するっていうのはすごいなー。 まあただそれは Googleのスケーラブルで巨大なインフラがなければ無理な話だとおもいますが。 まあそういう意味で、Googleは検索ワードがすごい重要なんだなと思います。 (n-gramモデルを公開したことには実はすごい意味があるのかもしれませんね) あと以下おまけで下記疑問に答えてみます その分母であるべき総単語数から違っている。 以下簡単なHTMLでためしてみた結果です a ALT読み込み strip_tags してる b ALTは読み込んで(というより日本語だけ抽出してるかんじ) strip_tags してる javascript も文字数としてカウントしている c - d - e ALTよまない strip_tags してる 文字数のカウントには カウントする対象を決める必要があって、 そのカウントする対象によって文字数がかわってしまうということですね。 bのjavascriptをカウントしてるのはさすがに問題だと思うけど。。。 「SEOウェブセミナー」は、「SEO + ウェブセミナー」となるケースが多く、「セミナー」を抽出できてないツールが多い。 通常、形態素解析には段階があって、 一番簡単にできるものでは ウェブセミナー を ウェブと セミナー にわけることができません。 それは単純にスクリプトでばらしているからであって これはまあ簡単にいえば 形態素ばらし ってとこなんですよね。 なので、逆にいえば 最低限 ウェブセミナー をばらせないと 形態素解析できるということにはならないんですが。 ちなみにYahooApiを使えば簡単にウェブセミナーをばらすことは可能です。 ### Googleの初インデックス日を出す方法は100%じゃないっぽい 面白いことが書いてあって、 試して見ようとおもったのと、 ちょうど関連したことで調査をすることがあったのでやってみたんですが。。 Googleに初めてインデックスされた日付を知る方法 これ100%正しいデータではないっぽいですね。 自分が2002年から管理してるサイトがあるんですが、(今は休止中) それを検索してみると 2005年6月21日 とでるわけです。 少なくとも2003年の時点で、Googleでタイトルで検索して1位表示されてるサイトでしたので 最初にキャッシュされた日付は2003年~になるはずです。 同様にそのサブドメインのサイトを検索してみると 2005年6月19日 ちなみにこのサイトは2004年に作成してやっぱり今は休止しています。 ⇒こっちも2004年の段階ではGoogleで検索結果に出ているサイトです。 ・サブドメインのが先にキャッシュされてるとかおかしいし。 ん~なんだこりゃw ちなみにわかる範囲でこの日付「2005年6月21日」はいつなのかというと ・そのサイトを休止して、休止メッセージのページだけにした日のあたり ・その時タイトルタグを消した ・その日からサイトはほったらかし ・しかしその配下にフォルダを作って、そこで公開XOOPSを運用中 ・そのXOOPSのサイトの初めてキャッシュされた日はだいたい合ってるような日付 ・XOOPSのサイトがあるせいで、GoogleBOTは結構ドメインTOPにもきているっぽい ・サブドメインのサイトは消したりせず、ページそのままで放置 わかったようなわからないような感じですが、 今流行のドメインの売買とかでこれを使おうとしても あんまり役に立たないのではないかと思いました。 ### WebSiteAnalyzerを公開してみました。 WebSiteAnalyzer Serpを作る際に内部リンクと外部リンクの数や wide&shallow narrow&deep の判別とかのために 内部リンク、外部リンク抽出プログラムを組んでいたので、 ついでにいろいろ追加してしまいましたw いまのとこできる項目は ・ページランク取得 ・HTMLの構造取得(title,h1,h2) ・yahooカテゴリに登録されているか ・cssレイアウトなのかtableレイアウトなのか ・内部->内部リンク数+URLリスト ・内部->外部リンク数+URLリスト ・外部->外部リンク数+URLリスト ・外部へのリンクの最大重複数  ←ここら辺がミソだった ・内部リンクの最大重複数  ←ここら辺がミソだった ・頻出ワード となっています。 面白そうなのがあったら随時追加予定w 最近SEOツール? ばっかり作ってる気がするなぁ。 WebSiteAnalyzer ### Google Browser Sync は便利! いまさらなんですが、 自分はPC環境がたくさんあって かつsynergyとか動かせないような環境なので(廃アプリ使ってるせいで) FireFoxがあちこちに入っています。 しかも ブックマークとかも はてぶとfirefoxのブックマークで用途が違うので、 ばらばらの環境だとちょっとづつ作業効率が落ちるんですよね。 あれどこいったっけ みたいな。 それを解決するアイテムってことで、 Firefoxのセッティングを同期させるのがこのGoogle Browser Syncです。 Google Browser Sync ブックマークや履歴、タブ構成以外にパスワードやクッキーとかをそのまま保存してくれます。 保存する設定は個別にカスタマイズ可能で、 その際、やっぱり個別に暗号化設定も可能です。 ちなみに自分はcookie とpassword をはずして 残りはON で全部暗号化設定にしています。 ブラウザが点在してる人にお勧めです ### TechnoratiのAPIを使ってキーワードを元に関連ブログのURLをリストアップ TechnoratiのAPIを使ってるので精度悪いですが。 仕事で開発していたものの中間物的なものをちょいと加工して作ってみました。 URLリストアッパー 前からTechnoratiのAPIは結構使用していたのですが、 ここはクエリ数が500が上限のため、 枯渇しやすいんですよね。 幸いなことにAPIキーの取得が楽なので、 いくつか取得して、使い分けたりして回避しています。 まあ単純にこんなかんじで switch (rand()%2){   case 0:     $api_key = "";     break;   case 1:     $api_key = "";     break; } なおAPIは authority:a4でとってきています。 URLリストアッパー ### 日本語の解析の仕方とかで悩む サーチエンジンっぽいものを自作していると どういう形でインデックスするかとか どういう形でヒットにしてSerpにだすかを考えていくわけですが googleでは http://nanto.asablo.jp/blog/2008/01/25/2578762 日本語の知識の利用は皆無。品詞情報なども使っていない。 テキスト処理は言語非依存。 らしいです。 一瞬目を疑いましたがw でも 良く考えると現時点での自分のSerpもそういう形で出してるわけで 突き詰めれば品詞情報とかはなくても 文脈がとれるってことなんでしょう。 でも 日本語の品詞情報があれば精度は高まるはずなので、 API公開してるくらいのYahooなら こと日本語にかぎっていえば Googleより精度が高くていいはずなのになぁ。 とりあえず当面はブログに限って、日本語に限って ある程度文脈を理解して解析できるようなプログラムを組めるように 研究したいとおもっています。 最終的にはやっぱり http://nanto.asablo.jp/blog/2008/01/25/2578762 Web を文脈つきの辞書とみなす。 こういう形にしたいなぁ。 ### seo vertical blog search をリリースしました seo vertical blog search をリリースしました 長ったらしい名前ですが。。 seo vertical blog search 個人としてSEOのブログの縦断検索がしてみたいなーということで 前々から色々と本文抽出やら形態素解析やらのPHPを作っていたのですが 全部こういうことがしたかったためです。 まあ 使い道はあんまりない気もしますが、 一応作ったんだし公開とします。 自分が見ているようなブログは大抵突っ込んだんですが、 自分の知らないブログもいっぱいあるので ユーザ参加型w というかURL教えてくださいってことで クローラ投稿フォーム設置してあります。 インデックスの仕方とかSERPの作り方とか 作ってみると色々悩むもので googleはすげーなぁ と改めて実感しました。 いや、何1000億のページから検索かけて0.1秒で結果返すとか キャッシュ使っててもきついってば。。。 seo vertical blog search ### PHPのコーディング規約?メモ? PHPがメインのうちの人たち用に PHPのコーディング規約?メモ? 全て網羅されてるか というと自分でもよくわからないですが、 最低限ここは抑えよう という指標としてみて欲しいです。 以下すべて引用 http://slashdot.jp/askslashdot/comments.pl?sid=389009&cid=1290697 * コーディング規約を守る。組織にコーディング規約がないなら、Zend Framework PHP標準コーディング規約 [zend.com]を使う。オレ流コーディングスタイルは禁止。 * 内部コードにはEUC-JPかUTF-8を使う。入出力もできるだけShift JISを避ける。Shift JISを使う場合には2byte目に0x5Cを含む文字の動作を忘れずに確認する。 * 開発環境の警告レベルをE_STRICTにする。本番環境ではdisplay_errorsをオフにする。 * register_globals、magic_quotesはオフにする。 * type hintingを積極的に使う。 * スコープの長い配列をクラスでラップする。 * プレゼンテーションとロジックを分割すること。プレゼンテーションには変数または関数の出力とループ以外のロジックを残さないこと。ロジックの中で直接出力をしないこと。ロジックはSimpleTestを使って単体テストすること。 * SQL文に変数を埋め込むときにはプレースホルダを使う(PDOのprepareとbindParams)。 * リクエストから取得したパラメータを出力するときはhtmlspecialcharsを使う。 * 関数が長くなったら分割する(composite methodパターンを使おう)。 * グローバル変数を使うのは避ける。 * 変数を定数として使わない。定数はdefineで定義するか、const宣言する。 * 常に最新バージョンでテストできる環境を作り、サービスイン後もバージョンアップに対応するための予算をとっておく(サービスイン後にお金をかけたくないなら、PHPは避けるべき)。 * 継承を使うなら、クラス図くらいは書いておく(単にコードを再利用したいだけなら、移譲を検討すること)。 * 各URLにおけるパラメータとその閾値を文書化すること。パラメータの閾値の境界線についてソースレビューし、テストすること。 * 既存のフレームワークを学習することに投資すること(学習コストを嫌って自己流のフレームワークを作ることは、バグを作りこむことになるのでかえってコスト高になる)。 * PHPしか使えない作業者の成果物はこまめにレビューすること。 * 設計を重視すること。 * コピー&ペーストプログラミングの匂いを発見したら、そのコードを書いた人にプログラミングの基礎を教えること。 * リファクタリングの時間をスケジュールに入れること。 * require/includeを条件文や関数宣言の中に記述しないこと。 量があるように見えて、 最低でもこのくらいチェックできないとダメなんですよね。 自分でもテスト環境のこととかちゃんと管理しないとなー。 おまけ 1:htmspecialcharsは ENT_QUOTESつけてやろう。   htmspecialchars($data, ENT_QUOTES); 2:ZENDの PHP標準コーディング規約 http://framework.zend.com/manual/ja/coding-standard.html ### Arc を触ってみた 前々からlisp系の書き方もやってみたいなーと思っていたので Arcをいれてみました。 (名前がカッコヨシ) インストール 1: http://download.plt-scheme.org/mzscheme/v352.html こっから Ver352の MzSchemeを落としてくる。 (新しい372とかはだめらしい) 2: http://ycombinator.com/arc/arc0.tar こっからarcの本体を落としてくる。 3: mzscheme を引数つけて実行する そのまんまだとパスが通ってないので http://www.arclanguage.org/install に書いてある 「mzscheme -m -f as.scm」 がエラーになるので インストールしたフォルダ ***/Mzscheme/ から MzScheme -m -d ./arc0/as.scm で Use (quit) to quit, (tl) to return here after an interrupt. arc> ってでるのでロードしてるらしい。 一応できたっぽいけど、あってるんかな。 あそぶ LISPの本からコピって打ってくださいw とりあえず arc> "hello world" "hello world" arc> (+ 1 2) 3 arc> (* 1 2 3 4 5) 120 arc> まだ複雑なことがぜんぜんできないので 最低でも普通にロジックが組めるようになりたいと思います。 こんなところを参考にしました。 http://blog.livedoor.jp/dankogai/archives/50994188.html http://www.geocities.jp/m_hiroi/xyzzy_lisp/abclisp01.html http://www.paulgraham.com/arc.html http://d.hatena.ne.jp/sirocco634/20080130#1201690137 ### javascript で style.display したらフォーカスが消えた javascript で style.display したらフォーカスが消えました。 こんな感じのJSを作っていて function clickable(){     document.getElementById('hidden').style.display = "none";     document.getElementById('visible').style.display = "block"; } こんな感じのinput <input type=text id=clickable onBlur=clickable() /> <input type=text id=hidden /> <input type=text id=visible /> みたいな設定になったときに id=clickableでTABキーおすとフォーカスが消えます。 どこをクリックしてもフォーカスしません。(実際はしてるんだけどカーソルが見えない) こんな感じ:テストHTML たぶんhidden(描画しない設定)になったとこにフォーカスが当たってそっからでなくなる? ような気がします。 描画しない display を 単に表示しない visiblity にすれば問題は解決するけど、 レイアウトの問題もあるので、display のままでなんとかならないかなーと試行錯誤したら Tabを押したときに強制的にどっかをフォーカスするといいみたいです。 でもどっかフォーカスといって document.getElementById('visible').focus(); ではだめみたいです。 処理の順番の関係かな。 この場合は document.getElementById('clickable').focus(); を付け加えればうまくいきます。 ### HTMLからタグの構造だけ抜き出すPHP HTMLからタグの構造だけ抜き出すPHP HTMLをArrayにばらして 中身を書き換えて再度フィックスさせるみたいなことがしたくて チマチマ作っていた中間物です。 (完成してない!) HTML構造だけ http://dev.neoinspire.net/class/html_structure.php?url= のurl= の後に適当にURLを入れると タグ構造だけぬきとってくれるはずです。 タグがぶっ壊れてるとおかしくなるきがします。 こんなかんじ <!DOCTYPE> <html> <body> <div> <div> <div> <div> <div> <h1> <a> <span> </span> </a> </h1> <div> <li> <h2> </h2> </li> <li> <form> <div> </div> </form> </li> <ul> <li> <h2> </h2> </li> <li> <ul> <li> <a> </a> </li> <li> <a> </a> </li> <li> <a> </a> </li> <li> <a> </a> </li> <li> <a> </a> </li> <li> <a> </a> </li> <li> <a> </a> <ul> <li> <a> </a> </li> <li> <a> </a> <ul> <li> <a> </a> </li> <li> <a> </a> ~~~~~~~~~~~~~~~ <div> <h3> </h3> <pre> </pre> </div> </body> </html> いやなんに使うとか突っ込みはなしで。。 ### GoogleSitemapを作成するPHP Cakephpでも~ 自動で全部だしたかったんですが、できなそうだったので せめてってことで、 URLリストを渡したらそれをsitemapにするものをつくっていました。 まあ必要があって作成していたので、一応公開しておきます。 たいしたものじゃないですが。 Class.Sitemap.zip 一応googleのSitemapの説明のとか書いた前の記事 http://dev.neoinspire.net/archives/category/seo/page/4 使い方 ・前準備 .htaccessで RewriteRule sitemap.xml$ sitemap.php として /sitemap.xml でsitemap.phpが呼ばれるようにする。 ・PHP作成 sitemap.phpを作って require(Class.Sitemap.php); //URLデータ作成 $array[0]['url'] = "http://www.a.com"; //url $array[0]['lastmod'] = "2008-01-01"; //LastModified Date $array[0]['changefreq'] = "daily"; //Change Frequency $array[0]['priority'] = "0.5"; //Priority //以下必要なだけArrayに突っ込む $array[1]['url'] = "http://www.a.com"; $array[1]['lastmod'] = "2008-01-02"; $array[1]['changefreq'] = "monthly"; $array[1]['priority'] = "0.5"; //クラスを呼び出してGO $Sitemap = new Sitemap(); $Sitemap->create($array); $Sitemap->output(); こんな感じの中身を書く。 これで Arrayをバラしてsitemapっぽいxmlを吐き出します。 ちなみに中では headerコマンド使ってますので、一切のprintとかができません。 CakePHPで使うには 1: /app/webroot/files/ にClass.Sitemap.phpをおく 2: /app/webroot/.htaccessに RewriteRule sitemap.xml$ sitemap.php を追加する。 3: /app/webroot/sitemap.phpを作る sietmap.phpは require(/files/Class.Sitemap.php); $array[0]['url'] = "http://www.a.com"; //url $array[0]['lastmod'] = "2008-01-01"; //LastModified Date $array[0]['changefreq'] = "daily"; //Change Frequency $array[0]['priority'] = "0.5"; //Priority //以下必要なだけArrayに突っ込む $array[1]['url'] = "http://www.a.com"; $array[1]['lastmod'] = "2008-01-02"; $array[1]['changefreq'] = "monthly"; $array[1]['priority'] = "0.5"; $Sitemap = new Sitemap(); $Sitemap->create($array); $Sitemap->output(); こんなかんじ! これで http://www.a.com/sitemap.xml でsitemapがみれるはずです。 Class.Sitemap.zip ### これはすごい!写真から顔を抽出するAPI http://kaolabo.com/webapi 28日になったら使い倒そう。。 絶対面白いこれ! ### CakePHP 1.2で トップページにキャッシュを入れる あちこちみてたんですが、やっぱりコアに手を入れるしかなさそう。 1.1だとこれかな。 http://raminasser.com/category/cakephp/ >>一応対策は書きましたが、 コアをいじらないでできる方法があったら教えて欲しいです。 問題は2点 1個目はCache読み込み部分 /cake/dispatcher.php の Line638 $filename = CACHE . 'views' . DS . convertSlash($url) . '.php'; ようは $_SERVER['REQUEST_URI'] を convertSlash したものがキャッシュ名になるので、 http://www.a.com/ の場合の REQUEST_URI = / を convertSlash した null がキャッシュファイル名になってしまうわけです。 なのでエラーになってキャッシュが読み込めない。 2個目はCacheロード部分 /cake/view/view.php の Line808 $cache->here = $this->here; ようは dispatcherで作成された $this->here を持ちまわしてそのままファイル名にするわけです。 なので、キャッシュを書き出すだけ(読み込まない)でよいのなら controllerに $this->here = "/コントローラ名/"; をつけたすだけでOKです。 まあ これじゃあ役に立たないので・・・ ちゃんとキャッシュが動くようにすると こうなります。 /cake/dispatcher.php の Line638の $filename = CACHE . 'views' . DS . convertSlash($url) . '.php'; この文の上にこれを付け足す //例は toppages_controller を / にしている場合 if($_SERVER['REQUEST_URI']=="/"){ $url = "toppages"; $this->here = "/toppages/"; } コアライブラリにハードコーディングですが、これで / もキャッシュできるようになります。 ### CakePHPのモデルがエンティティとして扱える! あけましておめでとうございます(遅 すでにCakeを使った開発第2弾も佳境なんですが、 ここにきてやりたかったことが書いてあって 動揺していたりw 実装したいけど、もう時間がない! CakePHPのモデルをエンティティとして扱う http://wiki.cre8system.jp/index.php?PHPCakePHPCakePHP あちこちで言われていますが、Cakeのモデルはエンティティともなんともいえない微妙な状態(エンティティではないけど)なので $this->idは使えても $this->postname みたいなことはできない みたいな。 それを可能にしてくれるってことです。 Cake(というかPHPが?)元々ハッシュ地獄に近いソースになりがちなので、 こういう書き方ができるようになると Cakeを使う利点が増えるようなきがするなー しないかも。 次のプロジェクトのときは実装してみよう。 の前に検証だw ### yahooディレクトリの直リンのやり方 ちょっとまえに直リンに変わって騒いでいたので どうやってるのか気になったので見てみました。 これが実際のHTMLです。 <a href="****" onmousedown="return catrd(this,'&l=SIT&sid=2075187116&ssid=2076442857&loc=1&sig=0101cb926a7020f1c40d20a1169d8935&rd=','','site');">***** でこれがjsのソース function catrd(o,r,f,t){ var pat=/:/; var href=o.href; var rbase='http://srd.yahoo.co.jp/CAT'; var cbase='http://dir.yahoo.co.jp/proc/?m=redir'; var tourl=href.replace(pat,''); if(f!=""){ f='?frc='+f } if(t!="site"){ url=rbase+r+"*-"+tourl+f }else{ var enc=encodeURIComponent?encodeURIComponent:escape; url=cbase+r+enc(href) } o.href=url; o.onmousedown=""; return true } 適当にコメントつけてみると function catrd(o,r,f,t){ var pat=/:/; //正規表現のパターン代入 var href=o.href; リンクのhref属性を代入 var rbase='http://srd.yahoo.co.jp/CAT'; //URL代入1 var cbase='http://dir.yahoo.co.jp/proc/?m=redir'; //URL代入2 var tourl=href.replace(pat,''); //正規表現で:をに置き換え if(f!=""){//fがブランク以外の例がないのでよくわからんけど置き換えてる f='?frc='+f } if(t!="site"){//内部リンク系だったら url=rbase+r+"*-"+tourl+f }else{//外部リンク系だったら var enc=encodeURIComponent?encodeURIComponent:escape; //encodeURIComponentが使えたら使う、使えなければescape url=cbase+r+enc(href) //URIエンコードしてURL作成 } o.href=url; //Aタグのhref属性を書き換え o.onmousedown=""; //onmouse属性を空っぽに return true } こんなかんじかな。 ようはクリックしたら動的にURLを変えてカウントしてるだけですが。 これをちょいいじれば //引数o:this r:クリックカウントのURIに渡したい引数 function test(o,r){ var u='http://***'; //クリックカウントURI var e=encodeURIComponent?encodeURIComponent:escape; //encodeURIComponentが使えたら使う、使えなければescape h=u+r+e(o.href); //URIエンコードしてURL作成 o.href=h; //Aタグのhref属性を書き換え o.onmousedown=""; //onmouse属性を空っぽに return true } こんなかんじにして クリックカウントのファイルからURLに飛ばせれば自分で使えますね。 テストしてないのでエラーでるかも ### ライフハック?googleで1年を振り返ると。。 一年のシメ的な雰囲気になってるとおもうんですが、 おもしろい1年の振り返り方ということで。 1:ログインしてGoogleにアクセスしてください。 2:右上に ウェブ履歴ってあるとおもうのでそれをクリック 3:サイドバーの 「統計」 をクリック ・・・・あー おれ遊んでばっかりだなーとかおもうわけですがw とりあえず晒すと 検索回数が一番多い時間:24時 検索回数が一番少ない時間:12時 昼ねしたりしてるからかなぁ。。 24時は一番遊んでネットしてる時間だし納得 検索回数が一番多い月:10月 検索回数が一番少ない月:3月 3月はニート君だったのでしかたないか。 10月はトラブル対応が多くて検索しまくってたかも 検索回数が一番多い週:水曜日 検索回数が一番少ない週:土曜日 土曜日は日中ほとんどネットしないからかな。 水曜日が一番だけど月、火、水は全部同じくらいだし納得。 木曜日、金曜日は会議とかルーチンワークが多くて少ないのも納得。 トップサイト 1. jbbs.livedoor.jp 2. cakephp.jp 3. ja.wikipedia.org 4. d.hatena.ne.jp 5. homepage2.nifty.com 1位と5位がわからないけど 後はだいたい納得w トップ クエリは見せれないほどゲーム関連でうまってるorz 皆さんも1年の振り返りにどうぞ! ### CakePHP hasAndBelongsToMany でページング(SQL LIMIT)とかを設定する ひさびさCake触ると いろいろ新しい発見があるものです。。 Cakeでは仕様上 hasOne > LEFT JOIN belongsTo > LEFT JOIN hasMany > IDを元に該当テーブルをSelect hasAndBelongsToMany > IDを元に接続テーブルをSelect という形なので Modelのアソシエーション単位で、条件の書き方が変わってくるので そのメモです。 例えば Post,Profile,Tag というModelで Post->hasOne->Profile Post->hasAndBelongsToMany->Tag という形だと Post Modelでこういう書き方が可能ですが Post Model $data = $this->findAll( $conditions = "Profile.delFlg'del'" ); これはできません。 Post Model $data = $this->findAll( $conditions = "Tag.delFlg'del'" ); なぜなら hasOne はLEFT JOIN しているので Profile というテーブル名が使えるのですが hasAndBelongsToMany はID切り出しでSelectかけるため PostモデルのFindAllからはテーブル名が使えないためです。 でこの場合はこうする Post Model $this->hasAndBelongsToMany['Tag']['conditions'] = "delFlg'del'"; $data = $this->findAll( $conditions = "", ); hasManyの場合も同じ。 ようはアソシエーションを書き換えるわけですね。 なのでこれを利用して Post Model $this->hasAndBelongsToMany['Tag']['limit'] = "$page,10"; $data = $this->findAll( $conditions = "", ); とこう書けば、引数 $page を元にアソシエーション先のページングができるわけです。 ### PHPでブログのキーワードを抽出するクラス 前のエントリで作ったブログから本文抽出するやつを使って 本文からキーワードを抽出するクラスを書いてみました。 以下参考にしたサイト http://ifs.seesaa.net/article/26300967.html http://phpspot.org/blog/archives/2005/11/php_17.html http://ablog.seesaa.net/article/20969848.html ダウンロードはこれ ●Class.Morpheme.zipリンク切れ中 本文取得はこっち ●http://dev.neoinspire.net/archives/63リンク切れ中 #2016/11/30追記 Yahoo APIを使用したものをgithub に上げました。 https://github.com/jakkrokk/blog-resources/blob/master/php/Morph.Class.php 下記2つをインクルードして使って下さい。 https://github.com/jakkrokk/blog-resources/blob/master/php/YahooApi.Class.php https://github.com/jakkrokk/blog-resources/blob/master/php/GetTitleAndBody.Class.php まあ内容は単純で、本文から正規表現で名詞っぽいのを取り出して それのdiffを取っていって出現頻度別にArrayに格納しています。 参考サイトを元に $r = preg_match_all('/[一-龠々〆ヵヶ]+|[ぁ-ん]+|[ァ-ヴー]+|[a-zA-Z0-9]+|[a-zA-Z0-9]+|[、。!!??()()「」『』]+/u', $str, $match); こんな感じの正規表現を書いて $v = preg_replace('/([する|でなければ|について|ならば|までを|までの|くらい|なのか|として|とは|なら|から|まで|して|だけ|より|ほど|など|って|では|は|で|を|の|が|に|へ|と|て])/u', "", $v); こんな感じに助詞とか消して $uniq_lv1 = array_unique($match_lv1); $match_lv1 = array_diff_assoc($match_lv1,$uniq_lv1); $this->word[$i] = array_unique($match_lv1); こんな感じでUniqueのDiffとってるだけです。 適当にGigazineさんでやってみるとこんなかんじです。 Array ( [0] => Array ( [5] => 高校生 [21] => 違法化 [24] => 運営者 [26] => 週刊 [31] => 送信 [51] => 著作権 [59] => 私的録音録画小委員会 [73] => 生活 [79] => 爆弾 [85] => 漫画 [95] => 検索 [111] => 映画 [117] => 日本語 [119] => 日本 [151] => 家電製品 [160] => 女性 [172] => 国際 [175] => 回以上 [181] => 名乗 [196] => 初音 [225] => 世界初 [228] => 世界 [253] => ユーザー [256] => モーニング [259] => モバイル [262] => メール [264] => メモ [274] => ミク [281] => マイコミジャーナル [286] => ボーナス [289] => ボードゲーム [293] => ホビー [295] => ペルソナ [297] => ヘッドラインニュース [303] => プロバイダ [305] => プレーヤー [307] => ブログ [312] => ファミ [319] => バグ [323] => ネットサービス [326] => ネット [329] => ネタ [331] => ニュース [338] => トラフィック [343] => テレビ [345] => ダウンロード [350] => ソフト [355] => スポーツ [365] => サービス [376] => サイト [382] => コーラス [385] => コルク [388] => ゲーム [411] => オンライン [416] => エンジン [419] => ウィルコム [426] => イタチ [428] => アート [434] => アニメ [453] => ちゃんね [455] => ため [457] => たち [459] => たこあ [467] => きた [486] => WIRED [489] => VISION [493] => News [508] => ITmedia [524] => AFPBB [529] => 2007 ) [1] => Array ( [32] => 送信 [96] => 検索 [197] => 初音 [260] => モバイル [265] => メモ [275] => ミク [287] => ボーナス [298] => ヘッドラインニュース [308] => ブログ [324] => ネットサービス [327] => ネット [332] => ニュース [351] => ソフト [377] => サイト [389] => ゲーム [435] => アニメ [494] => News [509] => ITmedia [530] => 2007 ) [2] => Array ( [266] => メモ [299] => ヘッドラインニュース [309] => ブログ [333] => ニュース [378] => サイト [390] => ゲーム [436] => アニメ [495] => News [510] => ITmedia [531] => 2007 ) [3] => Array ( [391] => ゲーム [496] => News [511] => ITmedia [532] => 2007 ) ) Arrayの番号が1回以上のワードがarray[0]に入ってます 以下2回以上はarray[1]、3回以上はarray[2]・・・ きりがないので、ワードが5個未満になったら終了にしています。 ### ブログから本文を取得するPHP 私用で前に作ったんですが、 今回別のアプリで流用するのでクラスにしたので ついでに公開します。 //2015/02/18 リンク修正 ダウンロードはこれ Class.GetTitleAndBody.zip CSSレイアウトのDIVかテーブルレイアウトのTDで分割して 一番リンク率が低い 設定値よりリンク率が低い 設定値よりタグ率が低い 画像率が一番高い 文字数が規定以上 一番大きいパラグラフ ここらへんにポイントを割り振ってはじき出してます なので、ITMEDIAのようにサイドバーがえらく充実してるとことか <div class="brclear">なんてひどいことしてるとことかは うまくいきませんね。 使い方 こんなかんじです。 $Html = new GetTitleAndBody(); $Html->GetHtml("http://dev.neoinspire.net"); if(empty($Html->error)){   echo $Html->title; //タイトルっぽいもの   echo $Html->body; //本文っぽいもの   echo $Html->body_length; //文字数 }else{   echo $Html->error; //エラー出力 } 本文はbrやa、li,strong など残せそうなのは一応残してあるので、 純粋にテキストにしたいなら strip_tags($Html->body) で。 パラメータとか コンストラクタのパラメータはこんなかんじです 1:$_body_len=有効にする記事の文字数の下限値(400ならマルチバイトで400文字以上じゃないと無効) 2:$_link_per=有効にするリンクの出現率の上限値(0.1ならリンクが1割以下じゃないと無効) 3:$_tag_per=有効にするHTMLタグの出現率の上限値(0.1ならタグが1割以下じゃないと無効) 4:$_html_point;=この点数を超えないと対象にならない(デフォルト8点) 5:$_weight=設定値の変更(Array)   $_array['キー']=評価点 の形式     キーリスト     max_link_weight;    //リンク率が一番小さい(デフォルト2点)     link_weight;      //リンクが規定値より低い(デフォルト3点)     tag_weight;      //タグ率が一番小さい(デフォルト1点)     max_img_weight;    //画像率が高い(デフォルト1点)     body_weight;      //文字数規定値より多い(デフォルト1点)     body_length_weight;   //文字数が全DIVの中で一番多い(デフォルト2点) 第5パラメータは配列で $array['body_weight']=5 とかすれば 「文字数規定値より多い」という項目のポイントが5になる ってういかんじです。 最後 パラメータ変えなくても割と取れます! //2015/02/18 リンク修正 ダウンロードはこれ Class.GetTitleAndBody.zip ### いきなりつまづいたakelos まさかインストールでつまづくとはw Lamp構成で特にいじってないサーバで入れてみたんですが、 FTPのとこでつまづく。 なんでだろうとあちこちみてたら、 これデフォルトでfile_get_contentとか使うみたい。 fopenで失敗エラーってことだし、 ということは ベーシック認証してるだけでアウトか。。 とりあえず回避方法がわからなかったので、 ベーシック認証してないサーバにいれてみたらすんなりOK。 回避方法を探してみたけど特に見当たらないので、 自分でトライ&エラーするしかないなぁ。 ということで、自分の宿題。。 ### Akelos はじめました。 Akelos はじめました。 CakePHPと乗り換えというよりは併用なんですが。 http://www.akelos.org/ 次の案件が近々走ることになり、 CakePHPを使っても良かったんですが、 前回Cakeで作った直後から気になっていたAkelosを導入しようかなと。 ソースをみて使いたいと思ったのと、 単に新しいものが使いたかった 新人に特異なオブジェクト指向やMVCを触らせたくなかった というあたりでしょうか。 まあ決めては下記の文をでした。 *パフォーマンス向上の取り組みです。 http://blog.takeda-soft.jp/blog/show/223 ということで、早速検証中です。 といってもまずはチュートリアルから~かな。 http://www.akelos.org/docs/tutorials/booklink-ja ### 有料リンクに関して思うところを。。 ちまたですごい盛り上がってますねw いろいろコメントしたいところがあったんですが自粛してます。 自分はSEO(自身でやるのは内部対策メインですが)もやってるので Googleの有料リンクに対するペナルティの事実は激震だったりするわけです。 特に nofollow をつけようとかいうのは誰がやるんでしょうか。 (お金の無駄使いってことですからね) nofollow がついてるリンクがいいリンクだ ということもあるか。。 さてはてこれをGoogleの気持ちになって考えてみると ・有料リンクがどうやっても排除できない ・かといって手動で全部なんてやってらんない というところなのかな。 苦肉の策なのか、もしかして狙ってるんじゃないか とか思っちゃうわけですが。 ちなみに実際に有料これをどうやって検知するか・・ ・リンクパターン ・手動でチェック ここらへんしか知りません。 このうちリンクパターンでの検知は 知ってる限りほぼ機能してないっぽいですね。 (自分でもアルゴリ考えてみたこと有りますが 精度高めるのってすごいむずいきがします。) たとえばPayPerPostとか海外のマイナーな有料ディレクトリ系エンジンとかはBANされてないですし。 (これが規約違反かはおいておいて) というかそんなこといったらクロスレコメンドとか全部やっちゃいけないしw まあそんなわけで今回のGoogleの件わりとインパクトだけ狙ったような気がしないでもない という実感です。 むしろあわててるのはSEOに詳しくない人たちばかりな気がします。 (自分もですが) nofollowの外部リンクが増えれば自分のブログの価値を若干なりとも下げるわけですしね。 (適正値とかあるんでしょうが) googleが怖いから、とりあえずリンクに nofollow をつける という風習が広まって一番困るのは検索エンジンなわけで、 実は遠まわしにYahooつぶしなんじゃないか!と思ってたり。 そう遠くない将来リンクを基準にした検索結果になるわけなんだし リンクの価値をなくしてしまうという手段もあるのかなと思いました。 ### PHP Arrayを簡単にグラフに amchartsの続きですがw http://dev.neoinspire.net/archives/58 で紹介した amcharts を結構使いまくっていて、 ついでにと簡単に使えるクラスを作ったので上げときます。 Class.CreateGraph.php 追記:200711/07 改良してる方がいらっしゃったので一緒に載せておきます http://d.hatena.ne.jp/pirokyun/20071104/1194161217#c 使い方 *PHP部分 1:amchartsをダウンロードしてアップする。 2:Class.CreateGraph.phpをインクルードする。 3:グラフ用の配列を作る  ->キー値がそのまま属性に、データがそのままデータになります。  例:$array['japan']="5"    $array['america']="5"   なら japan が5(50%) america が5(50%)でグラフを作ります。 4:new して終わり。 以下例: $ar['test1'] = 1; $ar['test2'] = 2; $ar['test3'] = 5; $ar['test4'] = 10; $graph = new CreateGraph($ar); //test1=5.56% test2=11.11% test3=27.78% test4=55.56% となります。 *HTML部分 amcharts をインクルード <script type='text/javascript' src='./ampie/swfobject.js'></script> 以上 その他 必須な引数は第一引数のグラフ作成用・データのみです。 後はこんなかんじ var $filename; //データXMLのファイル名を指定 var $foldername; //データXMLの入ってるフォルダ名を指定 var $width; //グラフの幅を指定 var $height; //グラフの高さを指定 var $setting; //セッティングファイルの名前を指定 全部やるとこんなかんじ。 $graph = new CreateGraph($array,"./ampie/","array.xml","400","300","./ampie/ampie_settings.xml"); あと注意点としては元のflashファイルがUTF-8らしくて UTF-8以外では動きません。 なのでPHPファイルもUTF-8で作ってください。 ファイルはこちら Class.CreateGraph.php もしくは以下コピペしてUTF-8で保存(全角スペースになってるので注意) Class CreateGraph{    var $array;   //Graph Data  var $filename;  //Xml File Name  var $foldername; //XML Folder Name  var $width;   //Graph Width  var $height;  //Graph height  var $setting;  //Graph Setting File  /*   Construct  */  function CreateGraph($array,$foldername='./ampie/',$filename='array.xml',$width="400",$height="300",$setting="./ampie/ampie_settings.xml"){   $this->array = $array;   $this->filename = $filename;   $this->foldername = $foldername;   $this->width = $width;   $this->height = $height;   $this->setting = $setting;      $this->_ViewGraph();   $this->_PrintOut();  }    /*   Create Xml Data  */  function _ViewGraph(){   $xml = '';    foreach($this->array as $key=>$value){     $xml.= ''.$value.'';    }   $xml .= '';   $write = $this->_file_put_contents($this->foldername.$this->filename,$xml);  }    /*   Print html&javascript codes  */  function _PrintOut(){   print "    <script type='text/javascript'>    window.onload=function(){     var so = new SWFObject('".$this->foldername."ampie.swf', 'ampie', '".$this->width."', '".$this->height."', '8');     so.addVariable('path', 'ampie/');     so.addVariable('settings_file', escape('".$this->setting."'));     so.addVariable('data_file', escape('".$this->foldername.$this->filename."'));     so.write('".$this->filename."');    }    </script>    <div id='".$this->filename."'>     <strong>You need to upgrade your Flash Player</strong>    </div>   ";  }  /*   Create Xml Files  */  function _file_put_contents($fileName, $data) {   $res = fopen($fileName, 'w+b');   if($res){    $write = fwrite($res, $data);    if($write === false){     return false;    }else{     return $write;    }   }  } } ### 簡単に円グラフを作成するamcharts Pie & Donut chart というやつです。 (追記:11/2:簡単に円グラフを作成するamcharts) レポート出力に円グラフがほしくて、 円グラフって作りにくいなーと思っていたので調べていたら見つけました。 amCharts かなりいい感じです。 導入も楽です。 とりあえず今回は円グラフだけですが。 使い方 とりあえずダウンロードして解凍。 ampieを適当にアップロードする。 <script type="text/javascript" src="ampie/swfobject.js"></script> <script type="text/javascript"> window.onload=function(){  var so = new SWFObject("ampie/ampie.swf", "ampie", "465", "380", "8", "#FFFFFF");  so.addVariable("path", "ampie/");  so.addVariable("settings_file", escape("ampie/ampie_settings.xml"));  so.addVariable("preloader_color", "#999999");   so.addVariable("data_file", escape("ampie/ampie_data.xml"));   so.write("graph"); } </script> これを<head> 部分に貼り付けて <body>部分に //好きなところに <div id='graph'></div> これを貼り付けるだけです。 scriptタグで動的にJSをロードしておいて、onloadでグラフオブジェクト作成>表示 という感じです。 適当に解説 遊べる部分はここらへんですね。 ・ampie_settings.xml ・ampie_data.xml ampie_settings.xmlは文字通りセッティング関連のXMLです。 グラフの角度とか文字の大きさ、色、透明度とかの設定。 別にデフォルトでも特に問題ないですがw <pie>っていうところが円グラフのステータスです。 //円グラフの厚み <height>8</height> //円グラフの角度 <angle>30</angle> ここら辺をいじれば見栄えがします。 heightを高くしすぎると やったら表示に時間がかかるので注意です。 ampie_data.xmlが円グラフの元データです。 型が決まっているので、その形式で出力すればそれだけでOKです。 <pie> <slice title="20~24歳" pull_out="true">5</slice> <slice title="25~29歳">10</slice> <slice title="30~34歳">7</slice> </pie> こんな感じですね。 自分はUTF8でやってますが、文字コードがおかしいと データがありませんとかいうエラーになるので注意してください。 ### Yahoo API を使うクラス? for php 最近Yahoo  や Technorati のAPIを使いまくってます。 それようにクラスを作ったので、公開してみます。 (一番下に張っておきます。) 使い方 とりあえず パラメータを作成します。 といっても引数名のキーで引数の値を入れた配列をいれればOKです。 Technorati でも Yahoo でも使えますが、 とりあえずYahooだと   $params['appid'] = "id";   $params['query'] = $_POST['keywords'];   $params['results'] = "100";   $params['start'] = "1"; こんなかんじです。 appid(Yahooで必要なんで)が id で query(YahooSearchのクエリー)が $_POST['keyword']で 結果数は100開始行は1 という設定です。 Techonrati なら   $params['language'] = 'en';   $params['query'] = $_POST['keywords'];   $params['limit'] = 100;   $params['authority'] = 'a7'; こんなかんじです。 で   $UseApi = new UseApi($params,'http://api.search.yahoo.co.jp/AssistSearchService/V1/webunitSearch?');   print_r $UseApi->apiData; 作った配列とAPIのURLを引数にクラスを生成すると $UseApi->apiData にデータ(Array)がくるので それを上手くつかってください。 Yahoo Technorati にかぎらずRESTなAPIなら 大抵上手くいくとおもいます。 なお本クラスは PHPテクメモさんのものを使わせてもらっています。 *問題があったら消しますので。。 以下丸コピしてはっつけてください。 もしくはこちらからダウンロード UseApi.zip /* Using API Class ex.   $params['appid'] = "id";   $params['query'] = $_POST['keywords'];   $params['results'] = "100";   $params['start'] = "1";   $UseYahooApi = new UseYahooApi($params,'http://api.search.yahoo.co.jp/AssistSearchService/V1/webunitSearch?');   print_r $UseYahooApi->apiData; */ Class UseApi{   var $requestUrl;   var $params;   var $apiData;    /* Inputs   1:Request Parameter Array   2:Request Service Main URL Outputs   return true   return data in $apiData    */   function UseApi($params=null,$url=''){     foreach($params as $key => $value){       $this->params .= $key."=".rawurlencode($value)."&";     }     $this->requestUrl = $url.$this->params;          $xmlData = file_get_contents($this->requestUrl);     $XmlToArray = new XmlToArray($xmlData);     $this->apiData = $XmlToArray->createArray();     return true;   } } /* Xml Exchanges to Array */ Class XmlToArray {   var $xml='';   function XmlToArray($xml)   {    $this->xml = $xml;   }   function _struct_to_array($values, &$i)   {     $child = array();     if (isset($values[$i]['value'])) array_push($child, $values[$i]['value']);          while ($i++ < count($values)) {       switch ($values[$i]['type']) {         case 'cdata':            array_push($child, $values[$i]['value']);           break;         case 'complete':           $name = $values[$i]['tag'];           if(!empty($name)){             $child[$name]= ($values[$i]['value'])?($values[$i]['value']):'';             if(isset($values[$i]['attributes'])) {               $child[$name] = $values[$i]['attributes'];             }           }           break;                  case 'open':           $name = $values[$i]['tag'];           $size = isset($child[$name]) ? sizeof($child[$name]) : 0;           $child[$name][$size] = $this->_struct_to_array($values, $i);           break;         case 'close':           return $child;           break;       }     }     return $child;   }   function createArray($targetEncoding='',$sourceEncoding='',$caseFolding=0,$skipWhite=1,$skipTagStart=0)   {     $xml = $this->xml;     $values = array();     $index = array();     $created= array();     $parser = xml_parser_create($sourceEncoding);     if($targetEncoding && $sourceEncoding!=$targetEncoding) xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $targetEncoding);     xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, $caseFolding);     xml_parser_set_option($parser, XML_OPTION_SKIP_TAGSTART, $skipTagStart);     xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, $skipWhite);     xml_parse_into_struct($parser, $xml, $values, $index);     xml_parser_free($parser);     return $values;   } } ### Mysql がクラッシュ? いきなり朝DBが落ちて 何事かと思ったら どうもMYSQLがぶっ壊れたらしい。。。 myisamchk を走らせて myisamchk /var/lib/mysql/***** Checking MyISAM file: /var/lib/mysql/***** Data records: 1996837 Deleted blocks: 0 myisamchk: warning: Table is marked as crashed myisamchk: warning: 1 client is using or hasn't closed the table properly - check file-size - check record delete-chain - check key delete-chain - check index reference - check data record references index: 1 myisamchk: error: Key in wrong position at page 16121856 - check record links myisamchk: error: Found wrong record at 147693548 MyISAM-table '/var/lib/mysql/***' is corrupted Fix it using switch "-r" or "-o" こんな感じででてきた。 結果から言えば インデックス張りなおしで終わったのだけど Table is marked as crashed とかでるとあせりますw どうもプログラム上でテーブルをつかみっぱなしだったっぽいですね。 アクセステーブルだけに秒単位でクエリが投げられてるわけですが こんなことは初めて。 原因はこれから調査することになるわけですが、 プログラムで回避できるものなのだろうか。。 ### 正規表現のツール 最近異常に複雑な正規表現ばっかり使っていて このツールがなければやっていけません。 http://www.rexv.org/ 簡単に使い方を。 PHPだと PCREのタブで操作。 Regular Expression: ここに正規表現を打ち込む */ / ではさむ必要はなし Target: ここに本文を入れる *ブログから本文とタイトル取得 とかなら  実際のHTMLコードとか入れておくといいですね。 Matched Groups: こんなかんじで返ってくるよ という結果部分 *赤字だったらエラーでとれてないよってことです。 めっちゃ便利です。 ### 検索回数の取得方法 検索回数の取得方法 気になって色々調査してみたんでまとめておきます。 ちなみに結論からいうと キーワードアドバイスツールは偉大!w yahoo まず yahoo のデータですが、APIとして取得できるのは 関連検索語のみです。 これだけなら無料。 検索回数に関しては 最新のデータというのは 提携している overture のツールを使うしかなく しかもそれは google adwords と同じく5段階評価 ちなみに有料です。 公開されているのはこれだけなので yahoo の検索回数の実数は取得は無理だと思います。 実数じゃなく予測値はどうかというと ・2007年4月のデータを下に なんらかの式で現在値っぽく取得(大体どこもそう) ・検索回数も取得できる検索エンジンのデータにシェアの割合をかける(どっかでやってる) このくらいですかね。 google google のデータの場合 APIとして取得できるのは 関連検索語、検索ボリュームです。 検索ボリュームはご存知の通り5段階評価のあれで、 まあ ないよりマシ なのかなぁ。 ただ、登録制で、大体40クエリごとに0.25ドル30円くらい? が課金されるので、ただじゃないです。 まあAPI自体は良く整備されているので構築は楽ですが、 世の中にはこのツールを作って販売してる人もいるみたいです。 公開されているのはこれだけなので google の検索回数の実数は取得は無理だと思います。 実数じゃなく予測値はどうかというと ・2007年4月のデータを下に google trend のグラフでみる(keyword suggest toolがやってること) ・google trend を使ってあーだこーだ(google に禁止されているので公開はしません) あとはyahooと同じことをgoogleでも可能かな。 結論 キーワードアドバイスツールは偉大!w yahooクラスの検索エンジンで実数がわかるのが 2007年4月のデータのみなので それを基準に出すパターンか 小さい検索エンジンの実数を元に シェア率で掛けていくパターンか どっちが精度が高いのかな。 ### PHPのArrayをJsonに する必要があって、正規表現の練習ついでに作ってみたので ついでに公開してみます。 function arrayToJson($string,$callback){   $preg = "/(a|s|i|[^}]*?):(\d+)(?:[:](\{|(?:\\\";|.)*?\";)|[;])|(\})/";   if(!preg_match_all($preg,$string,$matches)){return false;}   $i=0;   foreach($matches[3] as $key=>$val){     if(($i%2)==1){       $matches[3][$key] = preg_replace('[;$]',':',$val);     }else{       $matches[3][$key] = preg_replace('[;$]',',',$val);     }     if(empty($val)){       $matches[3][$key - 1] = preg_replace('[,$]','',$matches[3][$key - 1]);       $matches[3][$key] = "},";     }else{       $i++;     }   }   array_unshift($matches[3],$callback.'(');   array_pop($matches[3]);   array_push($matches[3],'})');   return implode($matches[3]); } 使い方 はインクルードかなんかしてから   $array[] = "いろいろ":   $string = serialize($array);  //arrayをserializeして   print(arrayToJson($string,"jsonp"));  //functionに突っ込むだけ   第一引数:変換する array   第二引数:callback関数名 とするだけです。 出力結果は $array1['name9'] = "aaa1"; $array1['name6'] = "aaa2"; $array2['name1'] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1"; $array2['name2'] = "aaa2"; $array2['name3'] = $array1; $array2['name4'] = "aaa3"; が jsonp (   {     "name1":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1",     "name2":"aaa2",     "name3":{"name9":"aaa1","name6":"aaa2"},     "name4":"aaa3"   } ) となります。 逆に json を php に はまだ考えてませんw 普通に吐き出すのめんどいなーと思っていたら (pearとかいれてないので) phpのserlializeが結構近しいデータだったのを思い出して 流用してみたかんじです。 なので、1から作るっぽい逆側はめんどくさそうだなー ### キーワードサジェストツール更新 してみました。 どうやれば検索回数が曲がりなりにも出せるのかなと考えた結果 google trendを使うことを思いついて そのリンクを張ってみました。 具体的には 以前にキーワードアドバイスツールで検索回数がキリのよかった キーワードを一緒にならべて、 「グラフのこのあたりで1万回だから 今はだいたい4万回だね」 みたいなサジェストもできるようになりました。 というか意外とアクセスが多くてびっくりで まじめに手直ししたのが本音だったりしますw ### URLを変更すると。。 たいした話ではないんですが、 以前はURLがそのまま記事の名前になっていました。 たとえば 「これが今日のタイトル!」 というタイトルならば 「http://dev.neoinspire.net/これが今日のタイトル!」 なわけです。 で これはもちろん日本語なので、 5みたいな数字に変換されてしまうわけで。 URLにもSEOが効くという話もあるので、 SEO的にもよくないし、なにより見た目が美しくないので 変更しました。 本当は MTのように近しい英語に置き換える仕組みでも作ろうかと思ったんですが だるくなったので(略 単にIDに変えました。 短くなってすっきり。 で前のページは全部404にし、 その後どのくらいでGoogleのキャッシュとかが移り変わるのかなーとみていたんですが。 まだ変わらない!(土日含めて5日くらい) 404じゃなく301を返さないと なくなっただけと判断して キャッシュとか消さないんですかね。 まあただのブログなんで、いくらでも実験していいんですが、 こうなってくると、新URLでキャッシュされるとデュプリケイトだと認識されたりするのかな? ### キーワードサジェストツールを公開してみました キーワードアドバイスツールが 使えなくなりました というのはSEOやってる人にとって 実はかなりのダメージなんじゃないかと。。 で、昨今はGoogleが日本でも勢いをまして、情報によれば ・Yahoo:47.4% ・Google:35% となってるそうなので、 GoogleのAdwordsの3とか5で出てくるアレを今後は利用するのでしょうか。 まあ結局のことろ 検索回数っていうのは DBもってる側しか知りえない情報なのであれなのですが、 検索ワードのリストだけは出せるように Yahooはしてくれているので、 そのAPIを使って簡単に キーワードサジェストツール なるものを作ってみましたw キーワードサジェストツール キーワードアドバイスツールから検索回数がなくなったバージョンです。 が 一応ソートは検索回数の多い順っぽい(Yahooデベロッパーネットワーク見てると)ので それなりに役に立つのかな。 まあ SEOを効かせるワードの「サジェスト」用ですが。 一応公開しておきます。 ### XMLをパースしてArrayに というソースコードを公開されていらっしゃるので 便乗しちゃっておきますw http://php.atseason.com/?eid=620142 XMLのURLを突っ込めばArrayにしてくれるクラスです。 使い方はこんなかんじ $xml_data = file_get_contents('XMLのURL'); $xmlObj = new XmlToArray($xml_data); $arrayData = $xmlObj->createArray('0','UTF-8');  print("<pre>");  print_r($arrayData); //つらつらとArrayになったXMLデータ  print("</pre>"); 引数は ・XML_OPTION_CASE_FOLDING ・XMLソースのエンコード(UTF-8とか) ・XML_OPTION_SKIP_WHITE ・XML_OPTION_SKIP_TAGSTART ・呼び出し元ソースのエンコード 呼び出し元ソースのエンコードは xml_parser_create() で使うところなので、 ISO-8859-1、UTF-8、US-ASCIIしかサポートされていません。 まあブランクにしておけばEUCって自動判別してくれるはず。 CAKEでも・・ ちなみにこのクラス ファイル名の Class. を消して「XmlToArray.php」 という名前にして 「/cake/vendors/」 に突っ込めば vendor('XmlToArray'); とすることで、cakePHPで使用可能です。 ### cakePHP と DbDesigner ブームになりつつあるようですね Dbdesigner は実はうちの開発にcakeと同時に取り入れたものだったりします。 かなり便利なので、普通にDB作る時の設計でも使えます。 http://dbdesigner.iimp.jp/ Cakeforgeに cake と接続するツールが落ちてます http://cakeforge.org/frs/?group_id=162 ↑これはまだ未使用です。いずれ使いますが! 実際使うとどんなかんじか 左のバーとダブルクリックだけでほとんどできます。 以下デフォルトのツールバーでの説明です 1:テーブルを作る   ⇒左バーの上から10個目のボタンを押して 画面をどこかクリック   ⇒空のテーブルができるので、ダブルクリックして中身を入れていきます。 2:テーブル同士のリレーション   ⇒リレーションIDとかは cakePHP のままに自動で貼ってくれます。   ⇒左バーの11個目から13個目がそれです。    *11個目が 1対多リレーション hasMany    *12個目が 1対1リレーション hasOne    *13個目が N対Mリレーション hasAndBelongsToMany      ⇒ボタンをクリックして、リレーションを貼りたいテーブルをクリックするだけです。 3:デフォルトInsert文とかの設定   ⇒テーブルをダブルクリックして出てくる編集画面の一番下の部分   ⇒Standard Insert をクリック   ⇒フォームで右クリックして Paste SQL Insert でSQLがでてくるのでそれをフィルする。 4:SQLにする   ⇒テーブル名を右クリックして一番下の3つです    *Copy Table SQL Create が Create Table 文をクリップボードにコピー    *Copy Table SQL Drop が Drop Table 文をクリップボードにコピー すごいのがリレーションのとこで、 hasAndBelongsToMany とかを テーブル2個作って つなぐだけで自動でテーブル作ってくれます。 テーブル名さえ修正すればいいので(rubyっぽい名前になってるかな)楽です。 ほかにも Edit メニューから copy select object すると テーブルのXMLができたり AS IMAGE で画像で取得できたり とこまごまかなりよいものです。 ### 最近はまってるもの 1:google sitemap http://www.google.co.jp/webmasters Webサイト構築するとき気にするのがSEOだと思うんですが、 それの補助的な役割になるであろうサイトマップです。 Pythonでできた専用アプリで一発で作れるといえば作れるんですが、 たぶん cakePHP 入れてると無理だろうなーと思って 自作してました。 といってもごりごりですが。 とりあえずこんな感じで作れます ○仕様 ※参考サイト https://www.google.com/webmasters/tools/docs/ja/protocol.html 形式はXMLで、これにあわせましょう。 <?xml version="1.0" encoding="UTF-8"?> <urlset xmlns=http://www.google.com/schemas/sitemap/0.84>  <url>   <loc>サイトの各URL</loc>   <lastmod>2007-07-23</lastmod>   <changefreq>daily</changefreq>   <priority>0.5</priority>  </url>  <url>   <loc>サイトの各URL</loc>   <lastmod>2007-07-23</lastmod>   <changefreq>daily</changefreq>   <priority>0.5</priority>  </url> <urlset> ・urlset:大本のくくりです。これで決めうちでOkみたいです。 ・url:URLのタグです。普通のXMLっぽくいえば item タグみたいなかんじ ・loc:URLの部分です末尾は / で終わることと http:// から始めたほうがいいみたいです    (自分は末尾 / じゃなかったですが エラーにはなりませんでした。) ・lastmod:最終更新日付 ・changefreq:更新頻度です。Googleはこれ参考(程度)にクロールするそうです。   * always   * hourly   * daily   * weekly   * monthly   * yearly   * never  これから選んで入れておきます。 ・priority:サイト全体から見た、各URLのプライオリティです。      0.5が基準なんで、全部の平均が0.5になるようにするといいかもしれません。 ○cakePHPでの設置注意 ルートに置かないといけません。 なので /app/webroot/***.xml という感じになります。 ○ウラワザっぽいこと 普通はxmlじゃないと無理ですが、(googleでエラーになる) xmlファイルじゃなくて、動的に出したphpファイルでもOKです。 どうやるかというとポイントは2個。 ○1個目 この一行を冒頭に加えます header("Content-Type: application/xml"); そう、吐き出しをxmlですとに強引にします。 ○2個目 /app/webroot/ にある.htaccess ファイルに この行を追加します RewriteRule sitemap.xml$ sitemap.php これで ドメイン名前/sitemap.xml と叩くと ドメイン名前/sitemap.php が呼ばれてXMLを吐き出すわけです。 いろんなサイトのサイトマップ作るサイトマップ職人になってましたw 2:ポケッター http://pocketer.jp/ 紹介っていうか自分で作って楽しかったので やってみましたというだけですが。 大きさが縦=名刺の3分の1  横が3分の2くらい 厚みが名刺の2倍くらい さわり心地が いいかんじw 名刺=パブリック ポケッター=プライベート そんな使い方してます。 ちなみに英語のつづりのままに読むと ポッケターなのは気にしないほうがいいかな。 どこぞではやってるみたいなので紹介しておきます。 ### timeplot.js を使ってみた これです。 timeplot.js http://simile.mit.edu/timeplot/ 用はCSVとかテキストのデータを自動でグラフにしてくれる というJSライブラリです。 URL先のTOPに出てくるグラフとかすごすぎる。 使い方 1:とりあえずインクルード <script src="http://static.simile.mit.edu/timeplot/api/1.0/timeplot-api.js" type="text/javascript"></script> ライブラリを落とすんじゃなくて、外部JSをたたいてくださいというタイプです。 まあこのJS->画像作成処理->Ajaxでアウトプット というスタイルなので当たり前といえば当たり前ですが。 ちなみに若干重いです。 あと自分のサーバに置けないのはちょっと不安かな。 2:HTML部分を書こう <body onload="onLoad();" onresize="onResize();"> <div id="my-timeplot" style="height: 150px;"></div> </body> こんだけです。 もちろんonloadはイベントリスナーで対応してもOK。 3:メインのJS部分 <script type='text/javascript'> function onLoad() { var plotInfo = [ Timeplot.createPlotInfo({ id: "plot1" }) ]; timeplot = Timeplot.create(document.getElementById("my-timeplot"), plotInfo); //ここでデータファイルのパスを書く timeplot.loadText("a.txt", " ", eventSource); } var resizeTimerID = null; function onResize() { if (resizeTimerID == null) { resizeTimerID = window.setTimeout(function() { resizeTimerID = null; timeplot.repaint(); }, 100); } </script> とりあえずこれでまずは動きます。(データ突っ込めば) しょぼいけどw 最後の timeplot.loadText("a.txt", ",", eventSource); これがデータロード部分で、 第一引数がファイル名 第二引数が区切り文字 です。 区切り文字はデータテキストの区切り文字です(そのまんま) これで、自分のサーバ上のデータを取得して、グラフにして、HTMLにAjaxで吐き出す という設定が終わりました。 すげー簡単! ちなみに上の場合はカラムが2個の場合(日付,データ)とかのデータに対応します。 4:遊ぼう! もちろんかっこよく仕上げるいじり方が色々書いてあるので、 ドキュメントみてもらうのが一番ですが、 とりあえずこんな感じでやれば簡単にそれっぽくなります。 以下 var plotInfo の中身を書き換える遊び var plotInfo = [ Timeplot.createPlotInfo({ id: "plot1", dataSource: new Timeplot.ColumnSource(eventSource,1), timeGeometry: timeGeometry, valueGeometry: valueGeometry, lineColor: "#0000ff", fillColor: "#00ff00", showValues: true }) ]; これでマウスオーバーで日付とデータがでます。 データが(日付、データ1、データ2..)となる場合は var plotInfo = [ Timeplot.createPlotInfo({ id: "plot1", dataSource: new Timeplot.ColumnSource(eventSource,1), timeGeometry: timeGeometry, valueGeometry: valueGeometry, lineColor: "#ff0000", fillColor: "#cc8080", showValues: true }), Timeplot.createPlotInfo({ id: "plot2", dataSource: new Timeplot.ColumnSource(eventSource,3), timeGeometry: timeGeometry, valueGeometry: valueGeometry, lineColor: "#D0A825", showValues: true }) こんな感じでJS部分をコピー追加していけばOKです。 ちなみに id -> データファイルのカラム番号になっています。 最後 BSDライセンスだそうです。 でも十分遊べますねこれ! ### やまざきメソッドジェネレータ! 面白いものを発見しました。 その名も「タイトル通り!」 http://labs.creazy.net/yamazaki/ なにこれ? 進捗管理>報告 のためのツール に使えるかんじ。 字体とかがゆるいので、 雰囲気読んで使わないと上司に殴られそうです。 でもネタ好きにはたまらないかも。 ちゃんと有効利用できそうなにおいがするのがイイ。 とりあえずやろう! ためしに作ってみました。 こんなかんじ。 ぶ ブログの進捗管理とか無理ですが。 感想かかなきゃ 今どこかまで簡単にかけるから楽です。 あとIdメモっておけは続きかけるような。 いや続きかけないと意味ないか。 直近で 新しい開発始まったらパーツで使ってみようw ### cakePHP はまりそう?な$this->Model->save() 自分がはまっただけですが。 cakePHP の model を使って、 「なんも考えずに」 ループ内で insert を繰り返すとうまく insert されないっていう事象です。 ループで insert ループでデータを insert していきたいときに 直感でPHPを書くとこんなかんじで 実はこれが上手くいきません。 while(!empty($i)){ $insertData['Model']['id'] = $i; $this->Model->save($insertData['Model'],false); $i++; } 結果はどうなるかというと、ループの最後のデータだけが insert されます。 キャッシュ?とかいろいろ思ったんですが 実は理由は簡単でした。 とりあえず解決方法 これで連続 insert が可能に! while(!empty($i)){ $insertData['Model']['id'] = $i; $this->Model->save($insertData['Model'],false); //これを追記 $this->Model->create(); $i++; } 理由は簡単 $this->Model->create() の中身はこう。 function create() { $this->id = false; unset ($this->data); $this->data = $this->validationErrors = array(); return true; } 単純にモデルに insert する際のIDを false にしてるだけです。 どういうことかというと、insert するか update するかは、 保存データ(この場合 $insertData['Model'] ) に id データがあるかどうかで判別するわけですが、 その id が一番最初に insert した時にセットしたものが ずっとループの間使われてしまうわけです。 なので ループ1回目 ->新規IDをセットして insert ループ2回目以降 ->一回目のIDを使用して、そのIDのレコードを update となってしまい、最後のレコードでデータを上書きして終わりとなってしまうわけです。 ### javascrpit で ヴァリデート(いまさら) 今やってる作業は基本のヴァリデートを全部JSでやってみようという 勝手な企画をしていてそこでできたメモ。 数字のチェック(数字以外が入ってたら false) //形式があってたら true function isNumeric(str){ return /~[0-9]+$/.test(str); } これだけでできた! ついでにメールアドレスのチェック //形式があってたら true function isMailAddress(str){ return /^[0-9A-Za-z._]+@[0-9A-Za-z.]+[.]+[0-9A-Za-z.]+$/.test(str); } 正規表現はすばらしいですね~ Perlあんまり触ったこと無いので 正規表現に疎かったのですがこれからはジャンジャン使おう。 ### cakePHP でURLにSEOを効かせよう セグメントベースURLの場合どうしても 天下の google には正しくキャッシュされなくなるパターンがあって、 それはいわゆる「SEO的によくない」状態なので、 それを直しましょうというお話です。 といっても結局力ワザなんですが。 どんなとき使うか パターンとしてはこーいう場合があります。 大カテゴリのコントローラがあります。 小カテゴリのコントローラがあります。 その下にデータのコントローラがあります。 というときに普通にやると ドメイン名/大カテゴリ ドメイン名/小カテゴリ ドメイン名/データ となってしまうわけです。 (1個のコントローラで全部書いてしまえば問題ないですが 普通わけるとおもいます。) で google 的には ドメイン名/大カテゴリ名/小カテゴリ名/データ となるのが理想的なキャッシュです。 なので 上記のURLでたたけるようにするやり方です。 dispatcher.php とか cake本体には手をいれません。 というかくじけましたorz なので力ワザします 自前でURLディスパッチャを書いちゃいましょう。 まずディスパッチャ用のコントローラを作ります。 名前は適当で。 それで、 ドメイン名/大カテゴリ名/小カテゴリ名/データ このURLを叩いたときに $_SERVER に入ってくるデータを取得します。 $_SERVER['REQUEST_URI'] これでとっちゃいます。 でこれをバラして組みなおす $requestUrl = explode('小カテゴリ名', $_SERVER['REQUEST_URI']); で最後 好きなコントローラにぶち込む $this->set('result',$this->requestAction($requestUrl[0],array('return'))); こんなコントローラを書いて Viewは {$result} とだけ書いておく。 最後に route.php でこの処理をしたいURLだけこのコントローラに飛ばせばOKです。 まとめっぽく 用は route.php(cakePHP のディスパッチャ) -> 自前ディスパッチャ -> アクション と冗長だけど1個かましてあげればいいですねというお話です。 処理的にもコントローラ上で requestAction してるので気になるほど重くはなりません。 ### cakePHPのコーディング規約 最近 cake で作ったアプリがリリ-スしたので、 リファクタリングというか ちまちまとチューニングとかしています。 ついでに、必死こいてたせいで、目を通し忘れていた コーディング規約とかに目がいったので、 そこも直してたりしますw なんとなく翻訳気味に下記書いていきます。 *以下ここを参照しました。 cakePHP:コーディング規約 https://trac.cakephp.org/wiki/Developement/CodingStandards インデント One tab will be used for indentation. 1インデント=1タブってことですね。 制御文 In the control structures there should be 1 (one) space before the first parenthisis and 1 (one) space between the last parenthisis and the opening bracket. Always use curly brackets in control structures, even if they are not needed. They increase the readability of the code, and they give you fewer logical errors. Opening curly brackets should be placed on the same line as the control structure. Closing curly brackets should be placed on new lines, and they should have same indentation level as the control structure. The statement included in curly brackets should begin on a new line, and code contained within it should gain a new level of indentation. 制御文は半角スペース1個で区切っていきましょう。 あと大括弧は必ずつけましょうってことですね。 if (***) { } ってことです。 自分は半角スペース入れない人だったので、 修正が大変だったり。。。 メソッドコール Functions should be called without space between function's name and starting bracket. There should be one space between every parameter of a function call. メソッドは メソッド名と() の間に半角スペースを入れないでコールしましょうってことですね。 As you can see above there should be one space on both sides of equals sign (=). To increase the redability of the code you can add spaces (or tabs) before the equals sign, but only in the case of a multiple function call presented below 代入とかで使う = の前後は半角スペースいれましょう。 メソッドの定義 Parameters with a default value, should be placed last in function definition. Try to make your functions return something, at least true or false - so it can be determined whether the function call was successful. 引数にデフォルト値があるやつは最後に入れましょう & メソッドは必ず return でなんかかえしましょうってことですね。 引数の話は意味あるのかなぁ。見易さだけかな。 コメント All comments should be written in English, and should in a clear way describe the commented block of code. 全てのコメントは英語で書きましょう。。(ロ)◎◎ インクルード When including files with classes or libraries, use only and always the require_once function. 全部 require_once を使いましょう。 PHPタグ Always use long tags: 全て <?php ?> を使いましょう ここらへんはさすがに問題ないかな。 命名規則 ・Function names should be written in camelBack ・Class names should be written in CamelCase メソッドとクラスは camelBack で書きましょう ・Variable names should be as descriptive as possible, but also as short as possible. Normal variables should start with a lowercase letter, and should be written in camelBack? in case of multiple words. Variables containing objects should start with a capital letter, and in some way associate to the class the variable is an object of. 変数名はわかりやすくかつできるだけ短く。最初の文字は小文字で、かつ camelBack で。 オブジェクトの場合は最初の文字を大文字に。 なんか普通にやってるとここら辺は抑えてそう。 ・A protected method or variable name start with a single underscore ("_"). ・A private method or variable name start with double underscore ("__") protected method はアンダースコア1個で private method はアンダースコア2個で始めましょう。 private と protected の違いってなんだろう。。 ファイル系 File names should be created with lower case. If a file name consist of multiple words, they should be divided by an underscore character ファイルは全部小文字で、あと区切り文字はアンダースコアで。 まあ apps_controller.php とか こんなんでいいってことですね。 定数 ・Constants should be defined in capital letters ・If a constant name consists of multiple words, they should be separated by an underscore character 定数は全部大文字で。 あと複数文字がある場合はアンダースコアで区切りましょう。 ざっと見て、ほとんど大丈夫なんですが、 地味ーに半角スペースがとか微妙に camelback じゃないとか。 中途半端がいやなのでチマチマ修正してます。 ### CSSで il,ol をテーブルっぽくやるとき 別にいまさらなんだけど SEOを気にしたらこうなりますっていうメモってことで。 全体を指定して ul{ width:100%; } 割合を指定するして横にならべる li{ width:33%; float:left; } で 使うときはこんな感じ。 <ul> <li>ライン1のカラム1</li> <li>ライン1のカラム2</li> <br /> //行を変える <li>ライン2のカラム1</li> <li>ライン2のカラム2</li> <br /> //行を変える <li>ライン3のカラム1</li> <li>ライン3のカラム2</li> </ul> table タグ使って中に li する人が増えてるけど やっぱやるなら全部 list タグでってことで。 ### cakePHP 一通り使い終わって やっとこさ開発がひと段落して、 リリースできたので、 初心者 cakePHPer が一通り使った後の思うところを書いてみます。 遅い? フレームワーク使って処理する以上、絶対に普通にやるより遅くなるわけですが、 キャッシュ使えばそれなりに実用に耐えうる速度になると思います。 大体描画まで0.2秒くらいにはなります。(キャッシュヒット時) twitter とか異常に遅いと思ってる人なので、 体感はややきびしめですが、一般的に使える と判断できると思います。 開発効率あがった? あがりました。一人で開発していても、時間がたってもソースの可読性は高いし(MVCモデルだから) 修正も楽です。 中規模10人くらいの開発とかで、 MVCそれぞれに分業みたいなことをしても、かなりわかりやすい感じです。 それ以上だ自分はやったことないので、とちょっと想像はつきません。 まあこれは cake というよりMVCフレームワークというべきかもw ROR とくらべてどう? 本気で ROR 使ってサービス1個立ち上げたことはないのですが、 正直 cake はもっと評価が高くてもいいと思います。 ROR は巷でぶっちぎりの人気ですが、 それに匹敵するものをもってる(ようなきがw)します。 あと PHPer の独特の感覚かもしれませんが、 フレームワークそのものを見て、オーバーライドして修正かけたりとか そういうのも割りと楽にできるのがいいです。 フレームワークのソースの可読性が高いってことなのかな。 でも まあ結局良くも悪くもPHPです。 ごり押しもできるし、フレームワーク無視した使い方も可能です。 まあそういうところ含めて自由度は高いです。 総じて 使ってて楽しい&開発が楽しいのでお勧めですw ### cakephp find findall read の違い すっかりだまされていました。 最小単位である read が一番早いと思っていたんですが。。 ソースがこんなになってました。 model_php4.php function findAll($conditions = null, $fields = null, $order = null, $limit = null, $page = 1, $recursive = null) {  $db =& ConnectionManager::getDataSource($this->useDbConfig);  $this->id = $this->getID();  $offset = null;  if ($page > 1 && $limit != null) {   $offset = ($page - 1) * $limit;  }  if ($order == null) {   $order = array();  } else {   $order = array($order);  }  $queryData = array('conditions' => $conditions,             'fields' => $fields,             'joins' => array(),             'limit' => $limit,             'offset' => $offset,             'order' => $order  );  $ret = $this->beforeFind($queryData);  if (is_array($ret)) {   $queryData = $ret;  } elseif ($ret === false) {  return null;  }  $return = $this->afterFind($db->read($this, $queryData, $recursive));  if (!empty($this->__backAssociation)) {   $this->__resetAssociations();  }  return $return; } function find($conditions = null, $fields = null, $order = null, $recursive = null) {  $data = $this->findAll($conditions, $fields, $order, 1, null, $recursive);  if (empty($data[0])) {   return false;  }  return $data[0]; } function read($fields = null, $id = null) {  $this->validationErrors = array();  if ($id != null) {   $this->id = $id;  }  $id = $this->id;  if (is_array($this->id)) {   $id = $this->id[0];  }  if ($this->id !== null && $this->id !== false) {     $db =& ConnectionManager::getDataSource($this->useDbConfig);     $field = $db->name($this->name) . '.' . $db->name($this->primaryKey);     return $this->find($field . ' = ' . $db->value($id, $this->getColumnType($this->primaryKey)), $fields);  } else {     return false;  } } そう 実はおおもとのDBアクセスは findAll() なんです。。。 read にいたっては read() にきたものをバラして find() に投げて find() はそれをさらに解析して findall に投げるわけです。 いや だったら使い分けないで全部 findAll で細かく指定するよ。。。 ちなみに findcount() も findAll() してます。 findAllBy とか findBy 系はどうなんだろう。 他のとこで camelclass をばらしてるのでチョット動き違うかもしれません。 ### リファラとかアクセスログを取得 本当は $this->log() で書きたかったんだけど これ、error か debug しか今使えないんですよね。 object.php のここ参照 switch($type) { case LOG_DEBUG: return $this->_log->write('debug', $msg); break; default: return $this->_log->write('error', $msg); break; } なので、書き出しのとこだけ頂いて自分で作りました。 以下コピって保存 以下のクラスを accesslogs.php として /app/vendors/ に保存 class AccessLogs{ function write_log() { $msg = date('Y-m-d H:i:s').",".env('REQUEST_URI').",".env('HTTP_REFERER').",".env('HTTP_USER_AGENT').",".env('REMOTE_ADDR')."\n"; $filename = LOGS.'accesslogs/'.date('Ymd').'.log'; $log = new File($filename); $log->append($msg); return true; } } app_controller に書き足し /app/ になければ cake フォルダからコピってこれを書き足して保存でOK vendor('accesslogs'); class AppController extends Controller{ $this->access = new AccessLogs(); $this->access->write_log(); } ログフォルダ作成 ログを格納するフォルダを作るだけです。 cake/app/tmp/logs/ ここに accesslogs というフォルダを作って書き込み権限を与える。 おしまい これでどの controller にアクセスされても、日時と、 REQUEST_URI,HTTP_REFERER,HTTP_USER_AGENT,REMOTE_ADDR を cake/app/tmp/logs/accesslogs 書き出してくれます。 ### Smarty と cakePHP でキャッシュを効かす方法 を誰か教えてくださいorz 自分なりの対策をした結果だけ書いておきますが、 正しいやり方ではないと思っていることだけは明記しておきます。 cake のキャッシュを使う のはできません。 なぜなら cake のキャッシュは cake の Viewの最後に renderCache して 書き出してるわけで、 そもそも smarty を使うと controller.php から view.php じゃなくて smarty.php (ファイル名は人によって違うけど)に飛ぶので 読み込むことすらしません。 Smarty のキャッシュを使う と、エラーになります。。。 具体的には missing_controller しかもなぜか 「image コントローラがありません」 といわれます。 core.write_cache_file みたいな smarty のコアファイルまで触らないといけなかったので くじけましたが、 おそらく 画像のキャッシュを取るときにあーだこーだやってこのエラーになる気がします。 ちなみに render のトレースをすると compornent ごとにキャッシュを作っていて、 (index.tpl だけじゃなく、中の compornent ごとにキャッシュファイルを作る) この部分もいちいち missing_controller になるようです。 さらに、極めつけは持ってくるのは ***.tpl のキャッシュなので URLによって変化するとか そんなこともないし、 image のとこだけ対応できても無駄っぽいですし。 じゃあどうするか。。。 cake/basic.php なんてコアライブラリに function cache なんてものが入っていたので、 なんだこりゃとみてみたら、どうも controller でキャッシュを取れるようになっているみたいです。 ということで使ってみます。 ・・・・ Arrayは使えませんか ってよくみたら $data = join('', $data); とかなってるし! グルーなしですか。。。 仕方ないので basic.php を書き直す orz function file_put_contents の $data = join('', $data); を $data = serialize($data); に書き換えて function file_get_contents の return の前に $data = unserialize($data); を書き足してみました。 ・・・動いた!(クララが立った以上の感動;;) 画面キャッシュじゃなくて 変数キャッシュになっちゃいましたが、 レスポンスが圧倒的に早くなりました。 というのも、DBアクセスの ORマッパーがボトルネックだったのは 目に見えていたから。 丁寧に unbind していてもやっぱり ビタのSQL直書きにはかなわないですからねぇ。。 ちなみにやっぱりコアライブラリをいじるのは気持ち悪いので外だししておきました。 (一番下に出しておきます。) 使い方 controller の頭で vendor('cachecreater'); を書いておいて $url = ""; //キャッシュファイルの名前 $this->cache = new CacheCreater();  $chk = $this->cache->cache_check($url); //キャッシュがあったらキャッシュデータを、なければnull $this->cache->cache_create($url,$data); //$url というキャッシュファイルに $data の変数(配列でOK)を格納 まあ実用的には $url = "引数_引数_引数.txt"; //キャッシュファイルの名前(引数全部を入れておけばIdごとにキャッシュできる) $this->cache = new CacheCreater();  $chk = $this->cache->cache_check($url); if(!isset($chk)){ ちゃんと処理する 最後にキャッシュに書き込む $this->cache->cache_create($url,$data); $this->set('cache',$data); }else{ $this->set('cache',$chk); } こんなかんじで。 ちなみに動的に model が更新されたらキャッシュを消すとかは 今リアルタイムで検討中です。。。 <?php class CacheCreater { var $expires = '+1 day';  //これでキャッシュ保持は1日 var $target = 'cache';   //WEBROOT か tmp/cache にするか。 var $p = "orgcache/"; //tmp/cache/の下のどのフォルダに詰め込むか。 function cache_check($req=null){ $path = $this->p.$a.".txt"; return $this->_cache($path,null,$this->expires,$this->target); } function cache_create($req=null,$data){ $path = $this->p.$a.".txt"; $this->_cache($path,$data,$this->expires,$this->target); return true; } function _file_get_contents($fileName, $useIncludePath = false){ $res=fopen($fileName, 'rb', $useIncludePath); if ($res === false) { trigger_error('file_get_contents() failed to open stream: No such file or directory', E_USER_WARNING); return false; } clearstatcache(); if ($fileSize = @filesize($fileName)) { $data = fread($res, $fileSize); } else { $data = ''; while(!feof($res)) { $data .= fread($res, 8192); } } $data = unserialize($data); return $data; } function _file_put_contents($fileName, $data) { $data = serialize($data); $res = @fopen($fileName, 'w+b'); if ($res) { $write = @fwrite($res, $data); if($write === false) { return false; } else { return $write; } } } function _cache($path, $data = null, $expires = '+1 day', $target = 'cache') { $now = time(); if (!is_numeric($expires)) { $expires = strtotime($expires, $now); } switch(strtolower($target)) { case 'cache': $filename = CACHE . $path; break; case 'public': $filename = WWW_ROOT . $path; break; } $timediff = $expires - $now; $filetime = false; if(file_exists($filename)) { $filetime = @filemtime($filename); } if ($data === null) { if (file_exists($filename) && $filetime !== false) { if ($filetime + $timediff < $now) { @unlink($filename); } else { $data = $this->file_get_contents($filename); } } } else { $this->file_put_contents($filename, $data); } return $data; } } ?> ### dragable-boxes.js を使ってみた iGoogle みたいな動かし放題、作りたい放題のRssフィード表示画面を 簡単に実装できるライブラリです。prototype.js 非依存。 ダウンロードはここから http://www.dhtmlgoodies.com/index.html?whichScript=dragable-boxes インストール フォルダを解凍してアップするだけ。 アップしたら インストールルート/dragable-boxes/dragable-boxes をひっぱたいてみましょう。 いきなり変なのがでるはず。 これ良く見るといきなり動かせるんですよね。 はいインストール完了! まあ実際に必要なのはこれだけです おきまりのインクルード <script type="text/javascript" src="js/ajax.js"></script> <script type="text/javascript" src="js/dragable-boxes.js"></script> 表示させる div の配置 <div id="floatingBoxParentContainer"></div> おまけでデフォルトに戻すボタン <a href="#" onclick="resetDragableBoxes();return false">Reset back to default </a> カスタマイズ カスタマイズポイントは結構分かれています。 まず表示数とかのパラメータ dragable-boxes.js の 21行目~の部分 /* USER VARIABLES */ var numberOfColumns = 3; //横に何分割するか var columnParentBoxId = 'floatingBoxParentContainer'; // 表示DIVのID名 var src_rightImage = 'images/arrow_right.gif'; //右矢印画像 var src_downImage = 'images/arrow_down.gif'; //下矢印画像 var src_refreshSource = 'images/refresh.gif'; //更新ボタン画像 var src_smallRightArrow = 'images/small_arrow.gif'; //小右矢印画像 var transparencyWhenDragging = false; //ドラッグして重ねたときの動き true なら場所を入れ替える var txt_editLink = 'Edit'; //エディット画面へのリンク文字 var txt_editLink_stop = 'End edit'; //エディット画面から抜けるときのリンク文字 var autoScrollSpeed = 4; //自動スクロール速度 var dragObjectBorderWidth = 1; //ドラッグした時の枠線の表示の大きさ(1は等倍) var useCookiesToRememberRSSSources = true; //クッキー使用可否 var nameOfCookie = 'dragable_rss_boxes'; // クッキーを使うときのクッキー名 /* END USER VARIABLES */ デフォルト表示とRSS以外の表示系 dragable-boxes.js の 966行目~の部分 /* You customize this function */ function createDefaultBoxes() { if(cookieCounter==0){ //ここにデフォルト表示させるBOXを書いていく //設定は 名前,表示番号,BOXの高さ,RSSの内容の表示数,リロードまでの時間 //高さは指定すると固定になる、falseにしておけばグリグリ動かせる //Js良くわからん人は1行コピーしてURLかえるだけでいいかな。 createARSSBox('http://rss.cnn.com/rss/cnn_topstories.rss',2,false,5,1); } /* Create static boxes */ //RSSじゃなくてHTMLとかの表示部分(これはJSに決めうちしないとだめみたい) //とりあえず上の2行だけ変えたセットをコピーすれば作れるはず。 //ここから! var htmlContentOfNewBox = ''; //BOXの中身(HTML) Ajaxでとって来て表示するには処理かかないとだめかな。 var titleOfNewBox = 'This is a static box'; //BOXのタイトル if(!staticObjectArray['staticObject1']){ var newIndex = createABox(1,100,false,'staticObject1'); document.getElementById('dragableBoxContent' + newIndex).innerHTML = htmlContentOfNewBox; document.getElementById('dragableBoxHeader_txt' + newIndex).innerHTML = titleOfNewBox; }else{ // Box is stored in cookie - all we have to do is to move content into it. document.getElementById('dragableBoxContent' + staticObjectArray['staticObject1']).innerHTML = htmlContentOfNewBox; document.getElementById('dragableBoxHeader_txt' + staticObjectArray['staticObject1']).innerHTML = titleOfNewBox; } hideHeaderOptionsForStaticBoxes(staticObjectArray['staticObject1']); //ここまで! setTimeout('saveCookies()',100); } おしいなー RSSだけじゃなくてhtmlもURL指定で書き込めたら最高だったのに。 まあちょっといじるだけでできそうだけど。 にしてもこんなものがフリーではいるなんて凄い時代だ。 ### cakephp の beforeFilter() の挙動とか session とか 個人でサイトを作成中なんですが、 ログイン機能のところで詰まった部分です。 ログイン機能っていうと簡易的には ・ID,パスをソースコードに決めうち ・DBから引っ張ってきて照合して~ とかあるわけですが、beforeFilter では model にアクセスできない! (あたりまえか。controller 呼び出す前に動けーっていう指令だし) なので、beforeFilter でDBとの照合ってできない。。。 のじゃないでしょうか。 そもそも beforeFilter で model にアクセスとかわかってない証拠なんじゃないかと思えてきた。 結局 index とかの function の頭に自分でフィルタかけるしかないのかな。 ちなみにログイン関係調べてて $_SESSION 系がわかったのでメモ。 宣言 var $components = array('Session'); 保存 $this->Session->write('名前', '値') 読み出し $this->Session->read('名前') ぶっ壊し $this->Session->destroy() あと $this->Session->valid() とか $this->Session->del('名前') とかも書いてありますね。 session 系はこのファイルかな。 /cake/libs/session.php ### cakephp で一つの controller からコンテンツを量産する方法 うまいタイトルが思いつかなかった。。 どういうことかというと 全体レイアウトに Element を使っていたりして Element に「同じ」動的データを渡したくて、かつ中身はファイルごとに替えたい場合 毎回 controller を作って view を作るのはだるい!!! ということです。 結果から書くとこう //引数はファイル名に class RyousansController extends AppController {     function index($name=null){          //Element へのデータ          $this->set("","");          ・・・・適当に共通処理・・・・          //ここで吐き出す view を指定          $this->render($name);     } } これでどうなるかというと http://www.hogehoge.com/ryousan/index/omake/ でアクセスすると /views/ryousans/omake.tpl を読みに行くわけです。 view だけ量産すれば 一切 controller をいじらなくても omake1.tpl -> http://www.hogehoge.com/ryousan/index/omake1/ omake2.tpl -> http://www.hogehoge.com/ryousan/index/omake2/ omake3.tpl -> http://www.hogehoge.com/ryousan/index/omake3/ omake4.tpl -> http://www.hogehoge.com/ryousan/index/omake4/ omake5.tpl -> http://www.hogehoge.com/ryousan/index/omake5/ と view にファイルを突っ込むだけでURLが自動生成されます。 DEBUGモードが0になっていれば view フォルダにファイルがないURLは404で返されます。 こうしておいて例えばヘッダフッタとかが共通のヘルプページやサイトマップとかは いちいち controller を作らずに help.tpl,sitemap.tpl を作って ryousans/ の中に入れてごりごり書いちゃえばいいだけになります。 一つの controller で view を量産!! ### cake のエラー処理関連 いくらなんでもユーザがミッシングURLをたたいたときに Missing Controller の画面がでるのはひどいな。。。 (Debugモードを0にすると今度はデフォルト真っ白だし!) ということで、エラー画面の修正方法を調査 とりあえず /cake/libs/error.php がデフォで行うエラー処理らしい。 エラー処理も ・単にViewを変えるパターン ・エラー処理そのものを変えるパターン の2種類あるので、まあ、両方調べてみました。 今ある View を変えるだけパターン render()でviewに渡してる名前を全部リストアップして、 その view を /app/views/errors/ の中にファイルとして作成する。 以下14ファイル(これで全部だと 思う・・) error404.thtml missing_action.thtml missing_component_class.thtml missing_connection.thtml missing_controller.thtml missing_scaffolddb.thtml missing_fomponent_file.thtml missing_helper_class.thtml missing_helper_file.thtml missing_layout.thtml missing_model.thtml missing_table.thtml missing_view.thtml private_action.thtml *Smarty 使ってる人は tpl ですね。 それぞれ適当な内容をかけばOK たぶんですがDEBUG=0 にしておくと if (Configure::read() > 0 || $method == 'error') {   call_user_func_array(array(&$this, $method), $messages); } else {   call_user_func_array(array(&$this, 'error404'), $messages); } これで全部 error404.thtml にすっとばしてくれるっぽいので、 これだけでもていねいに作った方がいいかな。 (未確認です) そもそもエラー処理を変えるパターン /app/error.php を作成する でこんな感じに適当に処理をかけばOK class AppError extends ErrorHandler {   function error($params)   {     //DEBUGモードが0かそれ以外かを判別     if(Configure::read() !== 0){       return parent::error($params);  //通常通りの処理     }else{       $this->controller =& new AppController();  //appcontroller を使えるように       $this->controller->redirect( "/", 301 );  //ルートにリダイレクト     }   } } 一応注意としては、 ErrorHandler は Exit してないそうなので リダイレクトとかならいいですが、 ほかの処理するなら最後に exit しないと うまく動きません。(error.php を見るとわかりますが、全部メソッド内で exit かけてます) ここに教えてもらいました: CakePHP cakeError()は値を返さない | Shin x blog とりあえず自分は 404に全部飛ばそうかな。 ### jquery と pager を触ってみた。 といっても突っ込んでみた感想だけです(・w・ ちまたで話題の jquery も時間あるうちに少しは触ってみようと思ったので、 ちょっと入れてみました。 コーディングするのがめんどくさいからライブラリ使ってみるなんて。。。 ちなみに pager は任意のタグで区切った文章(div とか)を js で区切って ページングさせるライブラリです。 決してページング処理してボタンおしたらAjaxで飛ぶとか そんなんじゃありませんw jquery:http://jquery.com/ pager:http://rikrikrik.com/jquery/pager/scripts/ とりあえず インクルード <script src="./js/jquery.js" type="text/javascript"> <script src="./js/jquery.pager.js" type="text/javascript"> 次 ページングする記事を書く <div id='page'>   <div>1ページ目ですよ~   <div>2ページ目ですよ~   <div>もうネタがないよ~ </div> 画面表示の時は1ページ目ですよ~だけが display:block されて、ほかは none されます 最後 js の設定 $(function(){   $("#page").pager("div"); }); 全部デフォルト設定にするとこれだけ。 jquery っぽいのが $() のとこだけだなこりゃw とりあえずこれだけで動きます。 細かい設定はこんなかんじ。 navId: 'nav',  //1ページに複数作るときは個別にIDを振る navClass: 'nav',  //CSSのクラス navAttach: 'append',  //ページングの表示部分(before,after,prepend,append) highlightClass: 'highlight',  //現在のページのリンク用CSS   prevText: '«',  //前のページリンクの表示文字 "«" は ≪ になる nextText: '»',  //次のページリンクの表示文字 "&rlaquo" は ≫ になる linkText: null,  //ページリンクの表示文字(画像でもOK) linkWrap: null,  //divタグとかでリンク文字をラップできる height: null  //コンテンツ自体の高さの設定 ちなみに linkText はこんなかんじでかけますね linkText : ["<img src='./gazo/1.gif' />",     "<img src='./gazo/2.gif' />",     "<img src='./gazo/3.gif' />"     ] SEOの観点からいうとこれアウトだと思います。 あと個人的に、1ページ目にも「前のページ」 最終ページにも「次のページ」が表示されるのがいやだなw まあブログとかに乗っけるとちょっとスタイリッシュになったりするかも? Wordpressに乗っけるかは未定ですが! (prototype.js と干渉するから対処策(リファレンスに書いてあるけど)しないといけないしね) ### unbindmodel と bindmodel の問題をライブラリで解決 なんというタイムリー 狙ったかのように凄い情報が 「CakePHPのおいしい食べ方」さんに乗っていました。 http://www.thinkingphp.org/2007/05/13/bringing-the-cold-war-to-cakephp-12-the-containable-behavior/ cake で動的な bind unbind を一歩進めて、bindしたテーブルのほしいとこだけとってくる というライブラリです。 作者のコメントにもあるように、前回のエントリの http://bakery.cakephp.org/articles/view/keeping-bindmodel-and-unbindmodel-out-of-your-controllers を意識して、より使いやすく、$recusive に頼らない設計に~ みたいです。 ざっと見た感じでは使い方が良くわからないのですが、 明らかに cake を便利にするものだと思うので、すごい期待ですね(・w・ いい素材がなくて、いきなり突っ込んで動かすことができないのが残念ですが、 時間があったら確認してみたいです。 *どうも作者は数学と歴史のテストが終わって開発にやる気みたいだしw どうでもいいけどこの「アソシーエション テロ」っていう表現が気に入った!w I hope this will become a powerful weapon in your very own war on terrorizing associations you don't want in your resultsets ### unbindModelとbindModel unbindmodel と bindmodel と cakephpを触ってると 意味解らないくらいアソシエーションがあって こらーー! と思いたくなり。。ました。 実体験です。 最初思っていたのは 「ありったけアソシーエション張って、いらんものは毎回はずす」 というスタンスだったのですが、 毎回Modelみて、これもいらん、これもいらん 見たいな事してるのはだるいなと。 で そうなると毎回必要なものだけ bind するほうがいいのではと考えています。 きれいにやる方法があるならですが。。。 作ってるものにもよりますが、毎回関連するレコード全部必要 なんてこと あんまりないんじゃないかなあ。。 しかもアソシーエション先のModelが3つとかになると、そこは全取得以外できない(と思う)し。 と まあ初級cake流コーディングで悩んでいたわけですが、 こんなところに一つの解決方法がありました。 http://bakery.cakephp.org/articles/view/keeping-bindmodel-and-unbindmodel-out-of-your-controllers コピペで恐縮ですが、 こんな感じでやるとこうですということで まずモデルの親クラスの AppModel に class AppModel extends Model {   var $assocs = array();      function expects($array) {     foreach ($array as $assoc) {       $this->bindModel(         array($this->assocs[$assoc]['type'] =>         array($assoc => $this->assocs[$assoc])));     }   } ... } ようは expects というメソッドを作って動的な bind の補助をしてくれるということみたいです。 で Model ではこう宣言 class Title extends AppModel {   var $assocs = array(       'Book' => array(         'type' => 'belongsTo',         'className' => 'Book',         'foreignKey' => 'collection_id',       ),       'Post' => array(         'type' => 'hasMany',         'className' => 'Post',         'order' => 'Post.id DESC',       )   ); } $assocs にアソシーエションを突っ込んでいきます。 type のところにアソシエーション部類を突っ込んで、後はクラスや外部キーなど通常のアソシエーションのあれを書く感じ。 最後 Controller で TitlesController extends AppController {   function list($id) {     //必要なだけアソシーエションを expects する     $this->Title->expects(array('Story', 'Post'));     $this->Title->Post->expects(array('User'));     $this->Title->recursive = 2;     $results = $this->Title->read(null, $id);   } } こうやって必要な分だけ簡単に bind することができます。というお話です。 これを俺は求めていた!! でも今さら変更とか 変更量が多すぎてミリ。。orz ----追記 http://bakery.cakephp.org/articles/view/an-improvement-to-unbindmodel-on-model-side こっちにはもうちょっと凝ったやつが乗っています。 違いは ・Cakeでのコーディングの仕方が変わらないようにしている ・デフォルトは全アソシエーションで、必要なバインドを1個でもつければ全部外れる ということっぽいです。 ようは、 基本全bind たまにイランのがある ->追記のURL 基本bindなし たまにいろいろバインドする ->一番上のURL かな。 でも基本全bind なら普通に unbindemodel 使えばいーじゃんという話もありますが。。 まあ検証してるわけじゃないので、それはまた別途。 ### WordPressのプラグイン:コメントのリアルタイムプレビュー いや 別にたいしたものじゃないんですがorz 別件でプレビュー画面を作ってたので ついでに勉強もかねてプラグインにしてみたというそれだけです。 URL:Comment_Previewer ダウンロードしてプラグインフォルダに突っ込んで有効化するだけです。 概要 このHPのコメント欄クリックしてもらえばわかると思います。 フォーム以外をクリックすれば消えます。 こんなサイトを想定してます ・1ページにコメント欄1個(これがネックw) ・コメント欄のフォームの name属性が comment になっている。 (適当にWordpressのサイトみたけど相当カスタマイズしてなければ comment になってるような) 微妙なカスタマイズ ・画面左に表示したいなら /css/preview.css の right:0px; を left:0px; に書き換えればOkです。 というかサイトにあわせてCSSは書き換えてください>< とりあえず何も期待しないこと。 ### cake でのデータの insert と update 悩んでうーんといって 適当にやったら いってしまった ついでにググったらすぐでてきてダブルパンチorz まあ一応メモしておこう。 基本はこう $data['モデル名']['カラム名'] という形でデータを突っ込んであげて $this->モデル名->save($data,true) これでOK 第二引数は varidate するかどうかのチェックなので、falseにすれば varidate をしません。 ここまではマニュアルに書いてあったんだけど これじゃあ insert だけじゃんか! と思って・・・ で実は内部で cake 自動判別していて モデルのID列が配列に ・入っていなければ -> insert ・入っていれば   -> Update(そのID列) となっているようです。 つまりUpdateしたければこう $data['モデル名']['id'] = 更新するレコードのID; というのを足せばOk ちなみにそのID列もarray()で複数与えてあげれば IN句みたいに複数 update してる ッポイ(未確認) まだまだ一人前に使いこなせないですな。。。 ### これはすごい!@ID、パス管理サイト こんなサイトがあるそうです。 https://www.clipperz.com/beta/ これはすごい! 複数のGmailのアカウントとかいちいちログアウト>パスワード入力しないでもOK 実は自作しようと思っていたところだけに非常にありがたくて 飛びつきましたw 使った感じは ・フル Ajax な感じ。 ・でもちょっと重め? ・アドレスに Beta ってついてるのが気になるw ・地味なところで、パスワードのフレーズチェックもしてくれるのがいいな できればずっとひらきっぱなしで使いたいから もう少し軽いとうれしいな。 ちなみにコードは Google Code に乗っているそうです。 自分はそこまでしませんが、セキュリティが気になる方はチェック。 ### php -> js のプロジェクト@google code MoonGiftさん http://www.moongift.jp/2007/06/p2js/ ここにのってました。 ざっと眺めたけどおー と思う感じが php ユーザにはあると思います。 isset() とか好きで自分でも function isset(data){     return Boolean(data); }; こんなコード書いてたので、 余計興味がわいてさらさらと見てました。 php 嫌いな人が見るとわざわざ実装せんでも js でできるがな っていいたくなることあると思いますがw Google code のプロジェクト http://code.google.com/p/p2js/ ### cakePHP で GROUP BY の壮絶バッドノウハウ いろいろなところで紹介されているように 普通に $condition の中にぶちこめばいけるんですが、 こんなかんじ $this->モデル->findAll(         $conditions = "GROUP BY title",         $limit = "" ); 例えばINとか、LIKEとか使ってごりごりやっていくときって Array で $condition つっこむのでそれのやり方のお話 普通にやると $this->モデル->findAll(         $conditions = array('Table.uid' => Array , 'GROUP BY Table.title'),         $limit = "" ); こんなかんじで、Arrayの一つとして GROUP BY を渡せばいいと思うんですが、 これじゃうまくいきません。 なぜなら Array の要素はすべて AND でつなげてしまうから。 つまりこれをSQLにすると WHERE Table.uid=1 AND GROUP BY Table.title なんていうSQLを投げてしまうわけです。 もちろんエラーに。 で これをどう解決するかというと こういう風にできます $this->モデル->findAll(         $conditions = array('Table.uid' => Array , 'Table.a<>"" GROUP BY Table.title'),         $limit = "" ); *2008/03/03追記 コメントをいただいたので修正してみました。 $this->モデル->findAll(         $conditions = array('Table.uid' => Array , '1=1 GROUP BY Table.title'),         $limit = "" ); わかるかな。。。 *Table.a<>"" という条件はほしいデータの抽出にまったく影響しないダミー条件です(なんでもいいんだけど) *2008/03/03追記 自前の1100万件のレコードのあるテーブルで上の二つをExplainしてみると table type possible_keys key key_len ref rows Extra Table_Name ALL NULL NULL NULL NULL 11006100 Using where Table_Name ALL NULL NULL NULL NULL 11006108 となります。 ようは Table.a"" というのはWHERE句として解析して その後で出力するんですが、 WHERE 1=1 はWHERE句の処理をせずに、無視してSQLを解析するので、 早くなります。 従って処理速度は table.a"" を使った時より圧倒的に早くなります。 ちなみに WHERE 1 でもOKだったので文字数を減らすならこうなります。 *2008/03/03追記 $this->モデル->findAll(         $conditions = array('Table.uid' => Array , '1 GROUP BY Table.title'),         $limit = "" ); Table.a<>"" をGROUP BY に書き加えただけなんですが、 これでうまくいきます。 ようは WHERE Table.uid=1 AND Table.a<>"" GROUP BY Table.title というSQLの正しい構文にするためにダミーの条件を GROUP BY が入ってるArrayのとこに書き加えてあるんです。 なんという力技。。。 ダミー条件とはいえ若干だろうけど負荷も大きくなるし、 もちろん激しく無駄w そのままSQLかけという話もありますが こういう方法もありますということで。 ### cakePHP + Smarty で定数とか取ってくる方法 凄いはまってしまった>< Viewでパスがほしいとき、URLがほしいときなどあるときとか cake の固有のグローバル定数を触りたい時どうするか。 <?php echo $WWW_ROOT ?> とかは tpl ファイルじゃできないので どうすればいいのかな~ (単に tpl ファイルに<?php?> を書きたくないだけですがw) ということで。 tplファイルではこれでいけます。 {$smarty.const.CAKEPHPの定数} 例えば cake の定数:WEBROOT_DIR:アプリケーションの webroot ディレクトリ名 ならこう {$smarty.const.WEBROOT_DIR} あとリファレンスに乗ってません(と思う。。)が、 cakeのインストールディレクトリの外からのURLは (http://ドメイン名/test/cake/ がインストールなら /test/cake/ が入る) {$html->base} こうです。 thtml でやるなら <?php echo $html->base ?> こうかな? ちなみに $this->webroot ってのもできました。 こーいう隠しデータがたくさんありそうだ! ### 画像をかっこよく魅せる?ライブラリ 3DCGやってたからよくわかるんですが、 CGってアップして単に見せるのって、なんかチョット。。。 と思いがちなんですよね。 別にふとしたことからこんなライブラリを見つけたのですが、 ひさしぶりに3DCGやってみたくなったんでついでに書いてみよう。 Smoothy Gallery http://smoothgallery.jondesign.net/what/ GPLライセンスです。 使い方 1:とりあえず落としてきてください 2:全部解凍して 適当なところに全部アップする。 3:表示したいHTML部分にこんなかんじで記載 ・まずはインクルード <head></head>のとこに下記を追加 <script src="scripts/mootools.js" type="text/javascript"></script> <script src="scripts/jd.gallery.js" type="text/javascript"></script> <link rel="stylesheet" href="css/jd.gallery.css" type="text/css" media="screen" /> パスはインストールフォルダにあわせてください。 ・画像部分   <div id="myGallery">     <div class="imageElement">       <h3>画像のタイトル</h3>         <p>画像の説明</p>         <a href="URL" title="open image" class="open"></a>  //クリックしたとき表示する画像(HTMLでも)のリンク         <img src="images/brugges2006/1.jpg" class="full" />  //画像本体のURL         <img src="" class="thumbnail" />  //サムネイルがあるならそれを使う(なくてもブランクのまま書いておく)     </div>     //ここから     <div class="imageElement">       <h3>画像のタイトル2</h3>         <p>画像の説明</p>         <a href="URL" title="open image" class="open"></a>  //クリックしたとき表示する画像(HTMLでも)のリンク         <img src="images/brugges2006/1.jpg" class="full" />  //画像本体のURL         <img src="" class="thumbnail" />  //サムネイルがあるならそれを使う(なくてもブランクのまま書いておく)     </div>     //ここまで       ・以下 ここから~ここまでの間をコピーして画像をペタペタ   </div> ・画像制御部分   <head></head>のとこに下記を追加   <script type="text/javascript">      function startGallery() {         var myGallery = new gallery($('myGallery'), {                      timed: true,                      showArrows: true,                      showCarousel: false,                      embedLinks: false         });      }   window.onload=function(){      startGallery();   }   </script> 以下説明 (true か false で設定する)   timed: true  //trueの場合時間で自動的に画像がつぎにいくようになる   showArrows: true  //trueの場合画像の横にマウスを宛てると矢印がでて、次の画像に進むボタンが表示される   showCarousel: false  //trueの場合画面上に一覧表示バーが表示される   embedLinks: false  //trueの場合画像をクリックすると指定のURLに飛ぶ これでできるはずです。 ### Modelの設計 Controller を作り始めると気づくのが、 アソシエーションしまくった Model へのアクセスには凄く無駄が多いこと。 そんなときの為にそのときだけアソシエーションをはずす機能が付いてる。 $this->モデル名->unbindModel(array('belongsTo' => array('**'))); 逆にこのときだけくっつけることも可能 $this->モデル名->bindModel(array('belongsTo' => array('**'))); テーブルもレコードも山のようにある我が案件では この機能はすばらしい。。。 と思ったけど、良く考えると ・モデルに設定しないで、全部適宜コントローラでやる ・全部モデルに設定して、後で必要な分だけはずす これってどっちも労力がかわりませんね。 どっちにしろいちいち確認しないとだめだし。 きっとcake的には全部アソシエーションした状態で、いらんものを外すのがお勧めなんだと思いますが。 そんなことをちょっと考え込んでしまった。今日この頃。 結局全部アソシエーションした状態で1個づつ外してます。 ### 3ヶ月スパンの予定変更 リンキンの新譜が予想外に俺の耳にフィットしないため 3ヶ月はヘビーローテーション入りが確定していたはずのアルバムがなくなってしまいました。。 すかさずフォローをしたいところなのですが、 ほしいアルバムがない。 レッチリも聞き終えたし、dreamtheaterの新譜くらいかなぁ。 まだジャケを見てすらいませんが。 とりあえず昨日からサポート要因で入ったメンバーは 「もってけセーラー服」 「J.E.N.O.V.A」 「再臨:片翼の天使」 「RealFace」 わかる人にしかわからない超マニアック構成。。 なんだろこのサポート。。。 とりあえず アヴリルかってきました。 ### requestAction() じゃなくてcompornentを使う どんなサイトでもたいていの場合 同じことが繰り返し表示される部分があります そのソースを毎回書くのじゃなく、どっかに書いてそれを再利用しましょう ということを考えるわけですが、 それのcakePHPでの解決方法を考えて見ます。(浅い知識で) cakePHPでそれを解決する方法が ぱっと2個あります。 (AppContoller に書くなんてこともあるので3つか。。) 1:requestAction() を使う 簡単に言えば別のコントローラのfunctionにタッチできますよということらしいです。 でもこれ、private functionはできない( _ で始まる名前のfunction)とのこと。 これって要は 外からURLぶったたいた結果を持ってきますよーってことみたいです。 なんとなく強引なイメージがありますね。 そもそも画面の一部分だけを 外でたたけちゃうのはいやだし。 2:compornent を使う それで他にないのかなーと探してました。 できればパーツ専用のコントローラを作ってそれを $this->** みたいに読み出すような形にしたかった。 *けど Appcontoroller には書きたくないし。。。 と思ってたら compornent というものを発見しました。 「目標は再利用性(reusability)です。」 URL:http://cakephp.jp/doc/components.html おおお KOREDA! ということで以下使い方メモ (といっても簡単です。) 構成的に ・TOP画面 (top_contoroller.php) ・TOPの中にプロフィールを常に表示するパーツ(parts.php) という感じのイメージです。 1:まず適当にコントローラを作る(top_contoroller.php) 適当に Class TopController {      //DB接続      var $uses = array('ここで使うモデルたくさん','パーツで使うモデルたくさん');      //Compornent接続      var $components = array('Parts','パーツPHPのクラス名');      function index()      {           //適当にset           $this->set('title',"とっぷぺーじ");           //Compornentからのデータをセット           $this->set("profile",$this->Parts->パーツのメソッド());      } } とします。 ここで注意点 ・この時点で $uses にパーツで使うのに必要なモデルも呼び出しておくこと ・パーツへのアクセスは      var $components = array('Parts','パーツPHPのクラス名'); ここでやります。 これは /app/controller/compornent/ にある parts.php を見に行って Partsクラスを呼び出します。 で最後そのメソッドを使用するには      $this->Parts->パーツのメソッド(); こんなかんじです。 2:コンポーネントを作る(/app/controller/compornent/parts.php) /app/controller/compornent/ に parts.phpを作って ごりごり書けばOK。 といってもとりあえず書き方にちょっとこつがあったので、書いておきます。 これもしかしたら普通にAppControllerを継承すればいいだけかも? Class PartsComponent extends Object {      var $Controller;      function startup(&$controller)      {           $this->Controller =& $controller;      }      function profile()      {           //プロフィール出力処理           return $this->Controller->モデル名->read();みたいな      } ・・以下ごりごり まずファイル名に対応するクラスを作ります(他のコントローラみたいに) で語尾がCompornent。 次に appcontrollerを継承するのじゃなくなぜか、Objectを継承します。 理由とか不明w(時間あったら調べます。。。) で、最初の function startup(&$controller) {      $this->Controller =& $controller; } ここで呼び出し元controllerを参照してそのモデルとかを持ってきます。 要はコントローラ<>コンポーネントでの中継部分かな。 これでcompornentファイルでcontrollerと同じようにごりごりかける設定が終了。 あとは書くだけですが、一応モデルへのアクセスは      //これじゃだめ      $this->モデル名->read();      //こう書く      $this->Controller->モデル名->read(); これでパーツだけ別ファイルに切り出して管理も楽だし、 追加とかも楽 なハズ? 他にもやり方あったら教えてください! ### Arrayにつまづくの巻 ORマッピングになれてないってのがあって 思いっきりつまづいたのでメモ。 いや メモというかグチ? 中身ほとんどないZE 今作成してるサイトのメインDBは何万レコード*30カラムとかいう とんでもないものなわけです。 それを ずっと FindAll してました orz という落ちでした。 しかもそれに hasMany とかアソシエーションかけてました。 ほんとごめんなさい。 ちなみにどうなるかというと。 cakePHP か php 本体で処理がストップします。 ==エラーメッセージも何も無く出力終了。 ==原因が特定できねえええええ というハマりでした。 でかすぎて落ちるならエラーくらいだせと。。。 俺の3時間をかえせ!KUSOGA! ということでORマッピングでの基本はLIMITを入れるということです。ハイ。 ### $this->log() よりもっと便利なのが! 自分の記事()がフォーラムに乗っていて しかも知りたいことが乗っていたのでそれの追記も含めて。(またメモメモw *$this->log() の正しい使い方(応用編) $this->log() はログをとる為のものなわけですが、 そのままだと同じファイルに延々と書き足していくだけなので、 簡単にログローテーションする方法 URL:http://cakephp.jp/modules/newbb/viewtopic.php?topic_id=562&forum=3 以下補足っぽく $filename = LOGS . date('ymd') . '_' . $type . '.log'; LOGS の部分が cake で定義されているパス /app/tmp/logs/ それに date() 関数でymd形式でつないで、ローテーションさせています。 月単位でローテーションするなら date('ym') でOK。 LOGSのとこ:http://cakephp.jp/doc/ch11s03.html $type が$this->log() の第2パラメータのLOG_DEBUG とかによって変わる部分です。 LOG_DEBUGしかサポートしてないっぽいので、 適当な言葉をいれると /app/tmp/logs/日付_error.log になります。 LOG_DEBUG を設定すると(単に第2パラメータに入れればよいだけ) /app/tmp/logs/日付_debug.log になります。 レスの部分が100倍勉強になりました! で タイトルの $this->log() よりもっと便利なのが!!! ですが、なんと pr() でOKでした。。。orz 補足すると cake に入ってるグローバル関数 pr() は view を無視して <pre> print_r() </pre> を書き出すのだそうです。 つまり コントローラを書いてるときに変数を見たければ pr('変数名'); と書くだけでOKでした。。。 これは神!(謎 ### Model系のメモ@cakePHP リファレンスがかなりわかりづらい(俺がgdgdなのかもしれん・・・) Controller 使うに際してのメモをしておきます。 なんかメモばっか。 //findAll $this->モデル名->findAll( $conditions="id=$id とか", $fields="取得したいフィールド名を書く", $order="ORDER BY *** DESC とか", $limit="そのまま数字だけでOK", $page="page数*リミットのとこだけ取ってくる", $recursive="アソシエーションの階層をどこまでとるか。" ); $condition= みたいなとこは省略できるっぽいけど わかりづらくなるから全部書いておこうと思う。。。 //findAllBy** $this->モデル名->findAllByIDAndPass(id="",pass="" みたいな); **CamelCaseで付け足せば何個でもいけるっぽい? //read $this->モデル名->read('フィールド名','レコードID') //save $this->モデル名->save('フィールド名','インサートデータ') //query *最終手段w $this->モデル名->query("SELECT id FROM table WHERE id = 1"); *これはやらんほうがいいねw リファレンスは例が少なすぎてわかりづらいきがしたなぁ みんなで作ってこうってことなのか! ### Smarty と cakePHP 適当メモ インストール方法はおいておいて(ぉぃ とりあえず使い方とかだけ若干はまったのでメモしておく *Controller系 ○既存のSmartyだと //変数のアサインはこう $smarty->assign('変数名', '出力データ'); //表示はこう $smarty->display('対象のテンプレパス'); ○cakePHPに入ると    //変数のアサインはこう $this->set('変数名','出力データ'); //表示は$this->set() で同時に行うから意識しないでOK *View系    //表示は既存のSmartyになる {$変数名} の形式 cakePHP部分での拡張も存在 ・htmlヘルパーが使用可能     -> htmlヘルパーについてはリファレンスを参照 http://cakephp.jp/doc/helpers.html#id4808750 なお<?php $html->*** ?> という使い方ではなく {$html->**} というSmarty流の書き方になります。 ・Elementが使用可能     -> Elementの使いかたもりふぁれん(ry http://cakephp.jp/doc/views.html#id4808257 これも同様に<?php $this->renderElement('**') ?> という使い方ではなく {$this->renderElement('**')} というSmarty流の書き方になります。 *Element ではコントローラでセットした値がそのまま使えます こんなかんじ。 //Contoroller class TestsController extends AppController { var $uses = "DB"; function index(){ $this->set('element_a','エレメントAにはこれが表示'); $this->set('element_b','エレメントAにはこれが表示'); $this->set('index_a','インデックスにはこれが表示'); } } //View //index.tpl <body> //エレメントAを表示 {$this->renderElement('element_a')} {'index_a'} //インデックスにはこれが表示 //エレメントBを表示 {$this->renderElement('element_b')} </body> //element_a.tpl <body> <hr> <b>ここがエレメントA</b> {'element_a'} //エレメントAにはこれが表示 <hr> </body> //element_b.tpl <body> <hr> <b>ここがエレメントB</b> {'element_b'} //エレメントBにはこれが表示 <hr> </body> Smarty流のアサインをしないとヘルパーが使えないのに若干ハマッタのでメモ どうでもいいけど wordpress でインデントの方法教えてください。。。 ### cakePHP の $this->log() が素敵すぎる controller と view に分かれていると Arrayに何が入ってるとかのデバッグが非常にめんどくさくて ちゃっと print_r したいときとかあると思いますが。 俺はこれ使ってます。 $this->log(Array,LOG_DEBUG); これやると /app/tmp/logs/debug.log ってのが出来てそこに 変数をがーーっと print_r したのが展開されてます。 ただのテキストファイルなんでそのまま見れるし。 もちろんアソシエーションしてあるとそれも全部表示されてます。 Array ( [0] => Array ( [users_table] => Array ( [id] => 1 [name] => お名前 ) [profile_table] => Array ( [id] => 1 [password] => パスワード ) ) ) こんなかんじ。 なので 愛用しまくってます。 使いかた間違ってる気がしますがw **おまけ デバッグをそのまま表示させる Dump 機能もあります。 /app/confi/core.php 52行目あたり。 define('DEBUG', ’数字’); 数字の部分を3とかにすれば画面更新すると その画面内に変数が全展開されて表示されますね。 ### アソシエーションの限界? Modelを作ってるとき 泥沼にはまってしまったのでメモメモ。 若干DB規約に沿っていないものを使っているということで、 これのせいかなと勘違いして何度もpostsテーブルとusersテーブルを作ってしまったぜ...orz 結論からいうと 複合キーは使えない ということらしい。 参照はこちら http://blog.tmty.jp/archives/51603147.html 非常に勉強になりました。 ※人生初トラックバック! スミマセン(・・ というか複合キ-の意味が良くわかってなくて、 文献にはたどり着いても、自分に適合できると知らず、ずっとハマってた俺乙。。。 無知は悲しいデスネ ちなみにずっとはまってたのは 記事テーブル<>タグテーブル の hasAndBelongsToMany のアソシーエションです。 記事テーブルからは 有効なんですが、 逆側から掛けようとすると画面が真っ白になる(エラー出力すらなし) なんでだろう ウーン で 気づいたわけですが、タグテーブルは処理の都合上別のリレーションIDも振ってあって 実はそれが原因だったわけです。 要は 記事_id と 処理_id の2個が入ってるってことですね。 たぶん複合キーってこういう状態だと思います。  ので、 複合キーは使えない ということです。ハイ。 ### テーマがたくさんあるサイトとか WordPressはテーマが豊富! ということで早速変えました。 めちゃかっこいいZEEEEEE でもオレほとんどなんもいじってないな。。(遠い目) 達成感0 満足度100 これがネットワークの時代(謎 とりあえずここで落としてきました。 ソートしてダウンロード順とかで出すと結構アメリカンな好みがわかりますなw http://themes.wordpress.net/ ### Scaffoldにtablekitをぶちこんでみる ってまあたいしたことしたわけじゃないですが。 しかもeditableは未実装\(゜ロ゜)/ やり方はこんなかんじ 1:/app/webroot/ に js というフォルダを作成(変更するならhtmlも変更が必要) 2:/app/webroot/ にjs.zipを解凍して全部アップする。 /app/webroot/js/tablekit.js になるようにする。 3:/app/views/ に scaffold というフォルダを作る。 4:/app/views/scaffold に scaffold.zip を解凍して全部アップする。 /app/views/scaffold/scaffold.index.thtml になるようにする。 これで $scaffold を宣言すると 微妙にAjaxテイストな scaffold のできあがり。 テーブルのリサイズとソートが自動で付加されているはず。 にしても cakePHPのリファレンスガイドは間違ってるので微妙にはまったのでメモ。 http://cakephp.jp/doc/ch05s02.htm 上記URLではindex.scaffold.thtml に という記述になっていますが、 やり方のとこで書いたとおり 正しくは scaffold.****.thtml です。 scaffold.php を見たら インストールURL/app/views/scaffold/scaffold.****.thtml となっていました。 時間できたら editable も実装してみるかな。 上で使ったファイルはこちら >> scaffold.zip >> js.zip ***2007・06・04追記 ファイル修正しました<scaffold.zip ### ソート機能とかを勝手にテーブルにつける なんつー とんでもないJSライブラリがありました。 http://www.millstream.com.au/upload/code/tablekit/ 使い方はめちゃ簡単で 1:まずインクルード <script type="text/javascript" src="scriptaculous/lib/prototype.js"></script> <script type="text/javascript" src="fastinit.js"></script> <script type="text/javascript" src="tablekit.js"></script> こんなかんじ。パスは適当に通して。 ソートしたいなら <table class="sortable"> リサイズ機能をつけるなら <table class="resizable"> Ajax使って更新かけたいテーブルなら <table class="editable"> で全部まとめてなら <table class="sortable resizable editable"> ってかんじ にクラスを設定してあげればあとは自動でやってくれます。 Ajaxは通信先とかは TableKit.options.editAjaxURI = 'パスを書く'; ってかんじでパス通せばそこにデータ飛ばしてonCompleteもちゃんと拾ってくれます。 ちゃっとソースみたかんじ ./tableKit.js の 774行目のAjax処理のところを書き換えればクリックしたら別の処理とかもすぐ作れそう $scaffold に適用できないかなこれ。 いやはや。 ### cakePHP 触り始め 入社してすぐいきなり 導入をPushしてしまった。 しかもすぐOKがでたので、追加サービスからはcakePHPを使用する予定。 ということで いろいろ調べてます。 とりあえず ruby の migration みたいなのが見当たらない(・・ というのはおいておいて、 新サービスは 旧サービスのDBも使うわけで もちろんcakeの厳しいDB規約に乗ってるわけがないから そこだけは上手くひっぱってくるしかないんだけど そもそもDB切り替えはどこでやるんだろう。 どうもModelでやるなら /app/config/database.php に設定を書き込んでおいて $useDbConfig = "使うDB名"; って感じでいけるらしい。 ちなみに $useDbConfig = "default"; はなんもしなくてもデフォで入るから普段は明示的に書かないだけ。 コントローラで動的に管理するなら $dbconection = ConnectionManager::getDataSource("使うDB名"); ってかんじらしい。 モデルで管理するほうがスマートな気がするけど、 DB規約に則っていない旧DBはモデルが使えない(?)ような気がするので これでごり押しするしか方法がないのかな。 **コメントいただきました。規約に則っていなくてもModelで柔軟に対応できるようです。(未検証) メモメモ ### Wordpressに移行してみた 立派な物を使ってみようということで 日本以外では スタンダードになっている WordPress に乗り換えてみました。 割といい感じです。 つかインストールがめっさ早かったw