2014年1月アーカイブ

uehatsu (2014年1月11日 22:22)

先のエントリーでData APIのGA PVをとるところではまっていると書きました。今回はそれの原因が特定できたのでまとめてみます。

まずは自分が前回書いた内容は全部ハズレだったという事。いやぁ勘がにぶったなぁ。

戻り値のJSONをもうちょっと良く見てみたら、なんと同じURLを持ったitemが2つあるものが。一つはtitleが正しくセットされているもの。もう一つはtitleが(not set)となっているもの。

もうちょっと調べてみるとGA APIを叩いた時に既にtitleが正しいものと(not set)の物があるみたい。これはGAのjava scriptがページ閲覧時に読み込まれた時に通常は正しくtitleがセットされるのに、何かしらの理由でセットされない状況があるのかな。そうすると、同じURL(path)に対して2つのitemが戻ってきてしまう。

MT::DataAPI::Endpoint::Statsの中ではURL(path)はユニークなものとして設計されており、ハッシュのキーとしても用いられています。データのitemのポインタをURL(path)を使って指定しているため、上位から順番に$data->{items}のオブジェクトを指定していくと、titleが正しくセットされたitemではなく、(not set)となっているitemがそのURL(path)のオブジェクトとして$items{$path}に指定されてしまいます。

そうすると、archiveTypeやentry_idなどはtitleが正しくセットされている上位のitemオブジェクトではなく、正しくない(not set)となっている下位のオブジェクトに代入されます。これにより、上位にarchiveTypeがundefになっているオブジェクトが散見されるようになるという事です。

limitを200から30にして一見状況が改善されたように見受けられたのは、30位以内にtitleが(not set)となっているitemがなかったため。limitを増やせばそれがだんだん増える訳で、またPVが多ければそれだけ(not set)となる確立が高くなるため上位にundefになる物が増えていくという状況の説明にもピッタリと当てはまります。

(そしたらPerlデバッガで直接叩いても状況は変わらないはずだけど、、、見間違い??、、、)

根本的な解決としては、titleが(not set)となっているitemは、正しくセットされている物にその分のPVを加算し、$data->{items}から削除するのが良いのかなと思うのですが、ちょっとヘビー。そこで次点としてtitleが(not set)となっている物は無視するという風にコードをいじってみました。(80行目前後)

-$items{$path} = $i;
+$items{$path} = $i unless $i->{title} eq '(not set)';

これでOK。limitを200件にしても上位itemのarchiveTypeがundefになる事はなくなりました。

この方法だと(not set)となっているPV数が多くなるとランキングに若干の差が出てしまうのですが、そこまで厳密なランキングではないので、このような変更でとりあえず良しとしました。

方法は問いませんが、コアでも修正される事を願っています(^^)

追記)コアでも修正されました。私が修正したのよりも美しく修正されていますw。
https://github.com/movabletype/movabletype/commit/3dd6d275e98894afa973e3311fda2b14fc179a8a

Movable Type 6 本格活用ガイドブック (Web Designing BOOKS)
Movable Type 6 本格活用ガイドブック (Web Designing BOOKS) 藤本 壱 柳谷 真志 奥脇 知宏 シックス・アパート株式会社

マイナビ 2013-11-30
売り上げランキング : 80852


Amazonで詳しく見る
by G-Tools
uehatsu (2014年1月10日 15:21)

昨日の晩Skypeで「ランキングスクリプトに最新記事が反映されていないんだけど様子見て」と言われさっそく調査。

GAのPVを取ってくるためにpageviewsForPathを叩いていますが、この返ってくるJSONの内容がおかしい事がすぐ分かりました。

具体的にはこんな感じでarchiveTypeやentryなどの情報が全部undefで返ってきてる。

$VAR1 = {
'totalResults' => 886,
'items' => [
{
'entry' => undef,
'pageviews' => '311',
'author' => undef,
'category' => undef,
'archiveType' => undef,
'title' => "\x{30aa}\x{30fc}\x{30c8}\x{30b5}\x{30ed}\x{30f3}2014\x{3001}\x{4eca}\x{5e74}\x{3082}\x{5343}\x{8449}\x{770c}\x{8b66}\x{304c}\x{4e00}\x{6589}\x{53d6}\x{7de0}\x{3092}\x{5b9f}\x{65bd}\x{3010}\x{30ef}\x{30f3}\x{30c0}\x{30fc}\x{30c9}\x{30e9}\x{30a4}\x{30d3}\x{30f3}\x{30b0}\x{3011}",
'path' => '/archives/2014/01/2014-autosalon-police.html'
},
(後略)

本当ならここはentryにentry_idが、archiveTypeにIndividualが入っていないと駄目なはず。これが入っていないのでスクリプトでは個別アーカイブとして認識出来ず、当該のエントリーを抜かしていたという訳。これはDataAPIの挙動に問題がありそう。

pageviewsForPath Endpointは、個別ページだけでなく、インデックスやカテゴリーアーカイブが入るため実際にランキングで取る数より多く件数を取っています。これがいけないのかと思い多めにしていたlimitの数を30前後まで減らしてみました。そうするとデータが取れるようになった。

$VAR1 = {
'totalResults' => 887,
'items' => [
{
'entry' => {
'id' => '1430'
},
'pageviews' => '312',
'author' => undef,
'category' => undef,
'archiveType' => 'Individual',
'title' => "\x{30aa}\x{30fc}\x{30c8}\x{30b5}\x{30ed}\x{30f3}2014\x{3001}\x{4eca}\x{5e74}\x{3082}\x{5343}\x{8449}\x{770c}\x{8b66}\x{304c}\x{4e00}\x{6589}\x{53d6}\x{7de0}\x{3092}\x{5b9f}\x{65bd}\x{3010}\x{30ef}\x{30f3}\x{30c0}\x{30fc}\x{30c9}\x{30e9}\x{30a4}\x{30d3}\x{30f3}\x{30b0}\x{3011}",
'path' => '/archives/2014/01/2014-autosalon-police.html'
},

元々は200件取得して、30件の個別ページを一覧で出していたのですが、仕方が無いので30件取得して20件の個別ページを一覧で出すようにスクリプトを修正してとりあえずはクリアしました。

しかしこれだけじゃ、何のナレッジもたまらない。そこでさらっと仕組みを調査してみる事に。

pageviewsForPathが呼ばれると実際に叩かれるのは MT::DataAPI::Endpoint::Stats::pageviews_for_path()

この中でMT::DataAPI::Endpoint::Stats::fill_in_archive_info() を呼んでいます。

fill_in_archive_info() の中身を覗いてみるとそのものズバリ、ArchiveTypeなどをセットしている場所でした。この中でGA APIから返ってきている情報をarchive_infoとしてmt_fileinfoの中身と突き合わせをしています。

archiveTypeをセットしているのは、$fi->archiveTypeがdefinedの時。つまりこの場所を通っていてarchiveTypeが設定されていないという事はmt_fileinfoのデータの中にarchiveTypeが設定されていない物があるという事。

そこでデータベースを覗いてみると、archiveTypeがnullになっている行は一切なく、逆説的にarchiveTypeがundefになっているarchive_infoが出力されているという事は、mt_fileinfoのイテレータ $iterの中に当該のエントリーの情報が入っていない、MT->model('fileinfo')->load_iter()の条件式が誤っているという事になります。

内部処理で使われているURL一覧の行列 @in_pathが多くなりすぎてD::ODが処理しきれていないのか、別の問題か、、、

で、ここでタイムアップ。これ以上は僕の仕事ではないや:-P

以前もあったlimitを多くしすぎると尻切れとんぼのJSONが返ってくる問題や、今回の仕様と違った形のJSONが返ってくる所など、一人のユーザーとして調査しきれる内容のものではないので、一日もはやく修正される事をのぞんでおります。

p.s. ちなみにPerlデバッガーから直接MT::DataAPI::Endpoint::Stats::pageviews_for_path() を叩くのを試してみたところ、limitを200にしようが300にしようが正しい形のデータが取得出来ました。これってApache CGIとして動かした時に問題が発生するという事でしょうか。だとしたら、もっと根が深いなぁ、、、

Movable Type 6 本格活用ガイドブック (Web Designing BOOKS)
Movable Type 6 本格活用ガイドブック (Web Designing BOOKS) 藤本 壱 柳谷 真志 奥脇 知宏 シックス・アパート株式会社

マイナビ 2013-11-30
売り上げランキング : 80852


Amazonで詳しく見る
by G-Tools
uehatsu (2014年1月 6日 15:59)

年が明けて数日が立ちました。暦上は皆さん今日から仕事はじめでしょうか?本年もよろしくお願いします。

さて、年末年始関係なくData APIのプラグインやスクリプトを書きまくっている上野ですが、年始に実家に寄ろうと移動中、実家近所の駅近のタリーズでMT開発チームの元同僚と偶然会うなど、年明け早々Movable Type漬けになりそうな雰囲気でございます(笑)

さて、今日のエントリーもData APIネタ。Movable TypeのData APIで本当に作る必要があったのか微妙ですが、うちのブログメディアは基本flickrに上がっている画像を引っ張ってHTMLに貼付けています。その中のこれという画像を個別にアセットとしてアップロードし、メイン画像としてカスタムフィールドに放り込むとMTMLで色々と画像が処理される仕組みになっています。

この仕組み、今からアップするエントリーは個別にその作業をすれば良いのですが、今まで書いて来た数百件にのぼるエントリーに改めてこの作業をするのは骨が折れます。そこでData APIの出番。スクリプトでがりっとやってしまおうというのが今回のお題です。

まず作ったのは今までのスクリプトの派生としてData APIを使って全エントリーを取得するだけのスクリプト。さすがに数百件のエントリーをいっぺんに取得するのはメモリーの無駄遣いなので、offsetとlimitを使って適当な数で区切って読み込むようにしました。読み込む内容は、'id,permalink,body,more,assets,customFields'をfieldsにセットしてやります。これで、エントリーID、エントリーURL(permalink)、エントリーtext、エントリーmore、アセット情報、カスタムフィールド情報、が取得出来ます。

最初、エントリーtextを取得するのに fields => 'text' なんてやっていたものだから取得出来ず、悩んだ挙げ句リファレンスを読んだら fields => 'body' なんだとか。ちょっとビックリ。

これで数十件まとめて読んだところ、MT::Util::from_jsonがエラーを吐いた。取得したjsonを見てみると途中で切れてる。取得するjsonに容量制限ってあったのかしら?仕方がないので、エラーを出さない範囲(余分を見て5件)毎にしました。

bodyとmoreを連結して、HTML::TagParserで<img>タグを取得。そのsrcを見て、flickr.comが含まれていたら外部サイトにある画像だと認識。

画像ファイルを一旦 /tmp/ 配下に保存してData APIのアセットアップロード機能を使ってアップロード。ここで HTTP::Request のPOSTでファイルをアップロードするのにちょっとしたテクニックが必要でした(詳細は別の詳しいサイトにまかせます)。戻り値としてアセット情報が返ってくるので、このアセットIDを保存しておきます。

今度はアセットをエントリーに紐づける必要があるのですが、Data APIにはその機能がありません。そこで、アセットのREST APIにentry_idを放り込むと紐づけてくれるエンドポイントをプラグインとして追加しました。具体的には内部で MT::ObjectAsset->get_by_key() を呼んでやり、エントリーにアセットを紐づけます。

通常であればここまでで良いのですが今回はその情報をカスタムフィールドに設定しなければなりません。そこでData APIのエントリーアップデート機能を使ってカスタムフィールドを更新します。画像カスタムフィールドは内部で画像以外の値(<form>タグなど)を持っているので、それにあわせて整形しアセットのURLとIDをセットしました。

このエントリーのアップデートで困ったのが Can't call method "errtrans" without a package or object reference at lib/MT/CMS/Entry.pm line 2266. というエラーが頻発した事。処理自体は最後まで行っているのでリビルドの際に何か問題が起きているのかもしれませんが、深くは追いませんでした。時間があれば調べてみます。今回はスクリプトの再実行で済ませました。

こんな風にした事で、過去のエントリーで使った画像もサムネールとして表示されるようになりました。ここまでやるのであれば別にData APIではなくてMT::Appから派生させれば良いのですがまあ良しとします。おかげでData APIの内部構造もかなり分かってきましたし、MT::Appから作成するよりも画像のアップロードなどは簡単にできますし、他にも色々な面で深く考える必要がないので短時間で実装が可能でした。

あとはスクリプトの内部にID, PASSWORDをもたないといけない点だけをクリアしたいです。accessTokenを生成するための内部コードを書けばよさそうですね。また今度考えてみます。

Movable Type 6 本格活用ガイドブック (Web Designing BOOKS)
Movable Type 6 本格活用ガイドブック (Web Designing BOOKS) 藤本 壱 柳谷 真志 奥脇 知宏 シックス・アパート株式会社

マイナビ 2013-11-30
売り上げランキング : 80852


Amazonで詳しく見る
by G-Tools

Who is uehatsu?

uehatsu

アーカイブ

Facebook page