astamuse Lab

astamuse Labとは、アスタミューゼのエンジニアとデザイナーのブログです。アスタミューゼの事業・サービスを支えている知識と舞台裏の今を発信しています。

お手軽に英文文書にメタ情報を!!Pythonでgensimを使ったLDAに挑戦してみた。

f:id:astamuse:20171101221349j:plain

こんにちは。白木(@YojiShiraki)です。デザイナーです。
前回はpolyglotを用いて英文から名詞を抽出する処理を行いました。今回は、その延長でLDAという手法にチャレンジしたいと思います。

背景

当社ではぼちぼち大量の自然文章データを取り扱っています。通常、これらのデータを読み解いてクライアントへの提案に繋げているのですが、概観を把握する場合は、膨大なデータを一つ一つ丁寧に読んでいる余裕などありません

そうなると、できる限りメタ情報を付与して、対象データの中身を読まずにだいたい把握するニーズが高くなりますが、残念ながら最初からデータに豊かなメタ情報が付与されているケースは稀であり、あってもカテゴリが一つ与えられているくらいです。

そこで自分たちでメタ情報を付与できないか、ということでLDAをやってみたという流れです。

LDAとは?

ざっくり言うと、対象となる文書がどういったトピックによって構成されているかを推定する方法です。潜在的ディレクリ配分法と言われます。数学的な解説は他サイトに譲りまして、ここでは直感的にLDAがどういったものか解説します。

まず、ある文書があったときに、その文書のトピックとは何か、ということです。

f:id:astamuse:20171101220040j:plain
文書の主たるトピックを明らかにする

例えばある野球ニュースがあったしましょう、この時、その文書(ニュース)がクライマックスシリーズのニュースであればトピックは「DeNA」「ソフトバンク」「試合結果」「ホームラン」といったところでしょうか。こういった情報が把握できればニュースを全部読まなくてもだいたい何の話かわかりますよね。

注意点

「なんだ楽勝じゃん」と思われるかもしれませんが注意点が二つあります。

  1. トピックは母集団となる文書群から抽出される
  2. トピックは明確的には抽出できない

どういうこと?と思われるでしょう。以下に簡単に説明いたします。

1.トピックは母集団となる文書群から抽出される

文書の成分として推定されるトピックは対象となる文書群から抽出されます。 例えば以下の二つのケースを考えてみます。

  1. 日本のすべてのニュース(文書群)を対象としてトピックを推定した場合
  2. スポーツニュース(文書群)を対象としてトピック推定をした場合

1の場合、おそらく上がってくるトピックは「経済」「政治」「芸能」「スポーツ」などで、2の場合は「野球」「サッカー」「バスケット」「ラグビー」などでしょう。

1よりも2の方がスポーツに関して偏った文書群であり、自然と単語の出現頻度もより具体的な単語が支配的になるためです。

f:id:astamuse:20171101220249j:plain
トピックは母集団となる文書群によって変化する

2.トピックは明確的には抽出できない

ここまでで便宜上ではあったにせよトピックは「経済」「政治」「野球」「サッカー」など、明確に与えられるように書きました。しかし実際はトピックも単語の成分として与えられます(OMG)

イメージとしては以下のようなものです。

f:id:astamuse:20171101220414j:plain
トピックは単語の成分で与えられる

つまり処理手順はこういうイメージです

  1. 文書群から
  2. トピック成分を抽出し
  3. トピックを定義する
  4. そのうえで個別の文書を分析にかけ
  5. その文書のトピックの構成比率を明らかにする

実践

ということであとはコードでも書いていきましょう。前回ご紹介したPolyglotを利用してますので、まだご存じない方はこちら)をご覧ください。

なお、以下のコードは説明しやすいよう便宜的なデータセットで用意しています。実際はちゃんとDBを使ってデータの管理を行なっております。また本筋に関連の薄い処理は割愛しております。あらかじめご了承ください。

1. 名詞の抽出

polyglot)を利用します。

from polyglot.text import Text
from polyglot.detect import Detector

# 文書群(実際にはこういった文書が100万以上あるイメージです)
documents = [
      {
          'desc' : 'A beach safe for all those who want a carefree beach holiday. The HBT BELSAFER the HBT brings a mobile vault on the market, which offers a premium value protection with smart additional features.'
      },
      {
          'desc' : 'The south curve must stay! The SOUTH CURVE is THE place of active FCC fans. Here come for generations the blue-yellow-white trailer together, showing brilliant choreography, are creative and unmistakable behind their team.'
      },
      {
          'desc' : 'Darling4me - Video Dating and dating service on the smartphone. Find your soul mate on the smartphone with just one click on your smartphone. Darling4me is the first truly Video Dating app for your smartphone (Iphone, Ipad and Android). Darling4me dating service does not require lengthy questionnaires and extensive information from you to find your soul mate. They are tired of seeing it on various dating sites on the Internet images of singles that look quite different in reality? They also do not believe that you will find his true love with a questionnaire? Then try just the new Video Dating Darling4me! It has never been easier to find love. Get your partner suggestions from your environment and see with just one click on your smartphone, the videos of the singles that could be suitable for you. Live and real!'
      }
]

for doc in documents:
    desc     = doc['desc']
    text     = Text(desc)
    nouns    = []

    for tag in text.pos_tags:
        word       = tag[0]
        word_class = tag[1]

        if word_class != 'NOUN':
            continue

        if len(word) < 3:
            continue

        nouns.append(word.lower())
    doc['nouns'] = nouns
    print(nouns)

こんな名詞データが取れます。実際には名詞データを格納する際にクレンジングなどを行う必要があります。今回はサンプルとして2文字以下の名詞は含めない処理を書いてあります。

['beach', 'beach', 'holiday', 'vault', 'market', 'premium', 'value', 'protection', 'features']
['south', 'curve', 'curve', 'place', 'fans', 'generations', 'blue', 'trailer', 'choreography', 'team']
['video', 'dating', 'service', 'smartphone', 'soul', 'mate', 'smartphone', 'smartphone', 'video', 'dating', 'app', 'smartphone', 'iphone', 'ipad', 'service', 'questionnaires', 'information', 'soul', 'mate', 'dating', 'sites', 'images', 'singles', 'reality', 'love', 'questionnaire', 'video', 'dating', 'love', 'partner', 'suggestions', 'environment', 'smartphone', 'videos', 'singles', 'live']

2.辞書の作成

先ほど抽出した名詞の配列から、単語と出願回数がペアになったデータを整備します。このタイミングで出現頻度の著しく高い単語や低い単語をフィルタリングしておきます。

import gensim
from gensim import corpora, models, similarities

words = [x['nouns'] for x in documents]

dictionary = corpora.Dictionary(words)

# 出現頻度の100回以下の単語は除外
# 5割の文書に出現している単語は除外
dictionary.filter_extremes(no_below=100, no_above=0.5)

# 必要であれば辞書データを保存しておいてください。
dictionary.save_as_text('dict.txt')

# 保存した辞書のロード
dictionary = corpora.Dictionary.load_from_text('dict.txt')

辞書の中はこのような形です。

99416
142 ability 1285
269 access  1190
112 accident    1059
175 account 1100
250 action  1077
164 activities  2936
48  activity    1239
369 addition    3438
61  adults  1203
208 advance 1527
85  age 2557
323 amount  4965
244 animals 1380
260 anyone  2604
94  anything    3794
362 area    3266
147 areas   1832
66  art 2847

左からID, 単語, 出現回数となっています。

3.辞書と単語配列データからコーパスデータを作成

ここでは単語とその出現回数をセットにしたデータ(BoW)のまとまりをコーパスと呼びます。

corpus = []
for word in words:
    bow = dictionary.doc2bow(word)
    corpus.append(bow)

print(len(corpus))
for c in corpus:
    print(c[0:5])

出力内容はこちら。

99416
[(226, 1), (348, 1)]
[(16, 1), (386, 1)]
[(22, 2), (73, 3), (107, 1), (213, 1), (256, 1)]
[(16, 1), (32, 1), (38, 1), (51, 2), (53, 5)]
[(13, 2), (27, 13), (44, 1), (50, 1), (53, 2)]
[(112, 1), (227, 1), (256, 2), (358, 1), (367, 1)]
[(120, 1), (130, 1), (222, 1), (404, 1)]
[(130, 2), (205, 1), (234, 1), (275, 1), (349, 1)]

先ほどの辞書のIDの単語が各ドキュメントで何回出現しているのかが見て取れます。

4.トピックの抽出

さて全体での単語の出現頻度や、個別文書での単語の出現頻度のデータが整備できました。これによって出現確率が計算できるようになります。コード上でもいよいよトピックの抽出を行います。

topic_N = 5 # トピックの数(これは恣意的に設定できる。対象文書群が大きいなら数字は10にするなど)

lda = gensim.models.ldamodel.LdaModel(
          corpus     = corpus
        , num_topics = topic_N
        , id2word    = dictionary
      )

# モデルを保存する
lda.save('cf_lda.model')

# 見やすく出力
for i in range(topic_N):
    print("\n")
    print("="*80)
    print("TOPIC {0}\n".format(i))
    topic = lda.show_topic(i)
    for t in topic:
        print("{0:20s}{1}".format(t[0], t[1]))

出力はこんな感じです。

================================================================================
TOPIC 0

children            0.04091190129015311
community           0.03130555261697799
people              0.029479935059319066
school              0.02772734591535905
trip                0.024166451030072957
families            0.02161773848009083
food                0.021610873859255263
students            0.01979379324892966
funds               0.017129066919807687
donations           0.016645475281216793


================================================================================
TOPIC 1

family              0.05142340959037838
time                0.02913106744366217
life                0.027162997294828555
years               0.024956546073125124
home                0.01966655399836955
year                0.018690935949895755
bills               0.018176062282802562
cancer              0.017468175859926792
kids                0.016755068548548788
surgery             0.016417772028110674


================================================================================
TOPIC 2

cause               0.3400466352795431
progress            0.3288091973047845
supporter           0.326162187172668
name                0.002616388814580179
list                0.0007183396591630143
style               0.0001032523653516189
size                5.034349340074679e-05
time                1.8874650176037916e-05
business            1.802981647432903e-05
text                1.8023864292905654e-05


================================================================================
TOPIC 3

name                0.04493116909991208
dream               0.03237160912917436
team                0.026269755943711323
music               0.024893502207153875
time                0.01889772053109854
equipment           0.018581267347826023
http                0.017372070771627068
support             0.017230217254967435
girls               0.016372972121079955
year                0.016264355823729516


================================================================================
TOPIC 4

people              0.03610631244471521
business            0.0258750156278881
world               0.023791209653958246
life                0.02243440793115274
time                0.018039148524328114
way                 0.015300038103529194
project             0.014480208478364773
years               0.01395798245176747
campaign            0.012818022865819834
work                0.011415802285745767

面白いですね。ところどころでnamehttpなどのノイズがあるので、これは調整が必要そうです。ストップワード作ってフィルタしてしまうのもありだと思います。 さて、母集団である文書群を考え各トピックの意味を仮に以下のように定めます。

TOPIC0: 福祉・教育
TOPIC1: 健康
TOPIC2: ビジネス
TOPIC3: 夢の実現
TOPIC4: 人

4.個別のドキュメントの分析

最後に、文書を指定してその文書のトピックがどのようになっているか分析しましょう。 対象となっている文書のコーパスデータをldaインスタンスに渡すだけです。

topic_label = [
    "福祉・教育",
    "健康",
    "ビジネス",
    "夢の実現",
    "人"
]

target_record = 5 # 分析対象のドキュメントインデックス

for topics_per_document in lda[corpus[target_record]]:
   print("{0:30s}{1}".format(topic_label[topics_per_document[0]], topics_per_document[1]))

こんな結果になります。この文書はビジネスの性質が強く出ている、というのがわかります。あとは閾値などを設けて実際にメタ情報として活用できます。

福祉・教育                    0.025503321280086557
健康                          0.025001172222024352
ビジネス                      0.7468590388122892
夢の実現                      0.02545997949638024
人                            0.17717648818921963

終わりに

いかがでしょうか?膨大な文書に対して全体像にフィットしたカテゴリ情報・メタ情報を付与したいときにはなかなか使えそうな方法ではないでしょうか。

勿論、いざやってみると、データのクレンジング、ストップワード処理やステミングなど前処理の手間がそれなりにかかりますし、パラメータの調整もそれなりの難しさがあります。 またトピックの善し悪しの判断も必要です(トピックはできれば距離が離れているほうが望ましい)。こういったことを踏まえると到底「お手軽」とは言えませんが一度試してみるのは良いかと思います。

では、本日も最後までお読みいただきありがとうございました。

例によって当社では一緒に開発してくれるメンバーを募集しております。カジュアル面談も随時行っておりますので、「ちょっと話聞きたい」という方は、このブログのサイドバー下にあるアドレスか@YojiShirakiあたりにDMいただければと思います。採用サイトもありますので↓↓↓のバナーから是非どうぞ!ちなみに次回はデザインの話に戻ります。

技術的負債と戦う指標の取り方と改善の仕方、そして諦め方

どうも、えいやです。

今回もお鉢が、というかぬいぐるみが回ってきたので、ブログを書きます。

今回は、技術的負債の計測と返済についてです。こんな方向で適当にやってるというだけの話とポエムなので、真面目に話を聞きたい人は、品質管理の専門家に相談してください。

なお、今回もJavaで開発を行っている想定での話です。あと、最後はやっぱり暗い気持ちで締めることになるので、誰か手伝いに来てください。

SonarQubeの紹介

僕がメインでメンテナンスしているサービスでは、CIに組み込んだSonarQubeを用いて静的解析ツールや動的テスト結果の解析ツールを実行し、CodeSmellなどのメトリクスを集計、可視化しています。

技術的負債とは、それらのメトリクスの個々の理由うちコード修正が必要なものについて、修正に要する日数を算定し、コストとして積み上げたものです。

www.sonarqube.org

SonarQubeについての詳しい内容は、公式のサイトを見てください。多様なプログラミング言語について指標を出せますが、解析ツールが洗練されているJavaについての指標がもっともきちんと提示できるようです。

Astamuse.comの場合

弊社の主要なサービスでは、Astamuse.com、AstaID.comについて取得しています。また、それらを構成するライブラリについても、自製のものについては取得しています。

例えば、コードベースが大きいAstamuse.comでは、Java言語で実装されている部分について、現在のメトリクスは以下の数値となっています。

         Lines of Code   Bugs    Vulnerabilities Code Smells Coverage    Duplications
main-java   42k             49      20              1.4k        30.4%       4.9%

これらの指標に基いて算出された技術的負債は62日となっています。これが多いのか少ないのかは、プロジェクトによって異なると思いますので言及しません。

ここ2年の技術的負債の推移を見てみると以下のようになります。

f:id:astamuse:20171023180246p:plain
技術的負債推移グラフ

新機能の追加で負債が増加することもあり、二年前の時点の167日を完済とは行きませんが、メインコードについては負債は減少傾向にあります。

借金で例えると「新規の借り入れ」はありつつも「元本」を返せている感じがしますね。まぁ頑張っている方だと思います。

数値の改善に取り組む

さて、この返済をどう行ってきたのかについてです。

もちろん、基本的には、個別の指摘事項を潰していくことで返済していますが、指摘されたことを機械的にやっていたのでは埒があきません。

以下では一気に改善してみせる方法を取り上げます。といっても、どれも当たり前な改善方法ですが。

抽象設計を行う

まず、負債が大きいコードでは設計がなっていない事が多いです。

コードをみて、オブジェクト指向の基礎にしたがった設計をしっかりと行います。

また、既存のコードが増える度に、抽象化して共通化すべき概念は増えていくはずです。エリック・エヴァンスが好きならそれらの一部はドメインと言い換えても良いでしょう。

手続きとしての共通化ではなく、抽象度の高い設計を行い、コードを共通化しましょう。

抽象設計が正しく行えると、コードの重複が少なくなりますので、わりと劇的に指標が改善します。

とくに、Java8以降では、ラムダ式やdefaultメソッドが使えるので、Java7以前から存在しているコードについては見直せば大きな効果が期待できるでしょう。

テストで使用されているMockの見直し

単体テストが設計時のままの場合、テストの対象となっているクラスと連携する他のクラスでモックを使用しているケースが多いかもしれません。

それらがインスタンス化が出来るなら、インスタンス化してしまいましょう。無理ならスパイを使っても良いでしょう。

単純にカバレッジが改善することもあれば、実際にはどうやったって通ることのない不要なコードが見つかって、それを削除できたりします。

例えば、Nullになり得ないシーンでのNullチェックや、@NonNullアノテーションをつけられるシンボルの発見などです。

ライブラリへの切り出しでよく見せる

指標を改善するだけなら最もよく見せる方法がこれです。

じつは、グラフ中に現れる大きな減少は、メインから別のライブラリへコードを切り出した結果としてメインコードから負債が消えたという部分があります。

むろん、その場合では、ライブラリの負債も合計すれば負債総額は変わらないです。いわば(メインからは)視えない化ですね。

それでも、メインコードの負債が減るのは短期的には良い傾向だと捉えられます。

なぜなら、一般にライブラリへのコードの追加・更新はメインコードへの追加更新よりも頻度が低いからです。このことをまた借金で例えるなら、返済期限が長く、新規の借り入れをあまり必要としない返済方法への乗り換えと捉える事ができます。

また、ライブラリ化によってコードの独立性が高まることで、そのライブラリに関する業務を分離することが出来ます。メンバーが増えたときにその業務を担当させやすくなるでしょう。借金の肩代わりをさせるようなものといえます。

なお、ライブラリ化できる構造に変更すること自体が、設計を正しくし、指標の改善となる傾向があります。

もちろん、機能をライブラリとして切り出すべきかどうか、きちんと考えた上で行う必要がありますので、なんでも分割すればいいというわけではないことには注意しましょう。メイン部分と独立している、改修の頻度が高くない機能が切り出す対象として向いています。

そして、切り出した後のライブラリを知らんぷりしていると、多重債務者状態になって立ち行かなくなることも心に留めておきましょう。

視えない指標にも対抗する。。。けど心構えは必要

上記までは、基本的に視えている技術的負債に対抗する方法です。

技術的に負債には、見えない部分があって、それらについても対抗しておかないと困ったことなります。

陳腐化に対抗する。。。のにも限界はある

さて、視えない部分で大きな部分を占めるのが陳腐化による内在的な負債です。

陳腐化とは、時間経過により使用する基盤技術やライブラリが古くなってしまい、刷新を行わなければならなくなることを指します。もちろんコストがかかります。

いつライブラリや環境が新バージョンをリリースするか、旧バージョンのメンテナンス期限が切れるかなどは、その全てを自動で把握することが難しいため、SonarQubeでも指標として取りにくいケースが多いです。

セキュリティ上の問題が発覚したりなどは、ある日突然に起こる場合もあります。

使用している技術が突如として使用不能になったり、採用した技術の新しいバージョンへの移行が極端に難しいことが判明してしまう様子は、借金に例えるなら、貸し剥がしや貸し渋りといったところでしょうか。

可能な限り最新のライブラリ環境に寄せ続けることでリスクを減らすことは出来ますが、なかなかに難しいことが多いでしょう。

対抗策として考えられるのは、設計段階で腐敗防止層を設けたり、ファサードのような実装が可換なパターンを用いていたりなどですが、それらはライブラリの変更には耐えられても、言語のアップグレードなど基盤技術の変更には耐えられません。

どんなシステムでも、陳腐化の原因については対策は後手に回る以外にないため、「陳腐化速度>メンテナンス速度」が宿命づけられており、いずれ陳腐化による借金が返済不能になると思っています。

何処かでしっかりと諦めて、システムの一からの作り直しをするか、もう止めてしまう覚悟が必要だと思います。

ちなみに、今のAstamuse.comはJava製ですが、かつて黎明期にあったScalaを用いて、同じく初期頃のバージョンのLiftで組まれていた時期があります。乗り換えの理由は、総合的な理由であり、陳腐化のみではなかったかと思います。昔のScalaは、今よりも問題が多かったことは確かです。

前提条件の変更もある。。。流れに乗らざるを得ない

陳腐化と似ているのですが、時代の変化により前提の条件が変更されるということもあります。時間というより、もっと大局的な時代でおきる、いわゆる時代の要請ですね。

条件の変更の例を言えば、CPUの進化や、メモリ容量の向上、クラウドの普及、スマートフォンユーザの増加、HTTPSデフォルトの一般化、などです。

それらを理由に、システムの根本的な作りや、データの取り扱い方、必須とされる機能の要件など、様々なところで変更が強いられます。

つまり、かつての時代の常識が、今の時代の非常識になったため、対応を取らなければならないことですが、Webサービスで言えば、FlashからHTML5への刷新などですね。

こうした内容は、陳腐化と同様にSonarQubeでは指標化出来ないこともありますので、他の方法で気にかけられるようにしておくべきでしょう。たとえばモバイルフレンドリー調査ツールやアクセス解析など、要件ごとの解析ツールなどです。

Astamuse.comでも、こうしたツールを用いて、細かな修正で対応可能な範囲においては、時代の要請にあった改修を続けています。

もちろん、小さな変更では済まないことも多くあります。例として出したFlashの廃止も、手法によってはそのうちの一つとなるでしょう。

Astamuse.comにおいてそのような部分の一つに、コンポーネント化への対応というのがあります。

Astamuse.comの基本的機能は、かつてGoogleの検索ボットが動的ページのインデックスをうまく行えなかった時代に作られています。それゆえ、Astamuse.comは、データを元に静的なHTMLを生成できるCMSのような実装が得意なFWを用いて、サーバサイドでHTMLをレンダリングするシステムとして組まれています。

ですが、ご存知の通り、今ではGoogleのBotは賢く、フロントエンドで描画されるインタラクティブなページも難なく解析し、検索インデックスに加えることが出来ます。

一般に、サーバサイドレンダリングは、サーバサイドへの負荷が高く、クライアントでも重複するHTMLデータを何度もダウンロードをしなければならないため、非効率とされています。

なので、いつとは言えませんがAstamuse.comもそのようなサーバサイドレンダリングからフロントエンドレンダリングに実装を切り替えていくことになるでしょう。

こうした改修は、コストが大きいので、既存のシステムを「諦める」可能性についても考える必要があります。

今のAstamuse.comのコードベース全てを諦めるということはないと思いますが、ミドルウェアやフレームワークの選択を含め今後考えていくことになるでしょう。割りと好き勝手に。

まとめ

  • 指標を見えるようにしよう。
  • 指標を改善する設計を行おう。
  • 一度書いたテストも放っておかずにメンテしよう。
  • メインコードから切り出すべき機能を探してライブラリにしておこう。
  • 見えない部分にも気を配ろう。
  • 諦めるときは諦めよう。
  • 諦めたら割りと好き勝手出来ると思う。

P.S で、Java9もう出てるよね

先日、待望のJava9が発表され、モジュール化機能の検証をせざるを得ないなぁと思っているところにその後のJavaのリリース計画も半年ごとに云々とか、、、

新しいことが出来るようになっていくのはとてもいいことなんだけど、抱え込んでるものの大きさにもよるよなぁ。

全くもって、人手が足りません。

Vue.jsとYQLでお手軽RSSフィード実装

先日、弊社採用サイトに本ブログのRSSを表示させる機能を追加したのですが、その実装がとてもお手軽だったので共有します。

YQL API Consoleのざっくりトリセツ

昨年から定番として使われてきたgoogleのAPIが廃止になったので、代替としてYQL APIを使います。 RSSの取得、確認にはYQL API Consoleが便利です。 左サイドバーの各リンクをクリックすると、それぞれ対応したサンプルのステートメントが表示されるので、ここではData→RSSをクリック。

select * from rss where url='http://lab.astamuse.co.jp/rss/category/'

'url'の箇所に本ブログのURLをいれてtestすると下記のようなjsonが返ってきます。

{
  "query": {
    "count": 30,
    "created": "2017-10-18T02:38:53Z",
    "lang": "en-US",
    "diagnostics": {
      "publiclyCallable": "true",
      "url": {
        "execution-start-time": "1",
        "execution-stop-time": "1112",
        "execution-time": "1111",
        "content": "http://lab.astamuse.co.jp/rss/category/"
      },
      "user-time": "1130",
      "service-time": "1111",
      "build-version": "2.0.187"
    },
    "results": {
      "item": [
        {
          "title": "データクレンジングとかクォリティチェックとかの話",
          "link": "http://lab.astamuse.co.jp/entry/2017/10/11/114500",
          "description": "<p>いつもご覧いただき誠にありがとうございます。",
          "pubDate": "Wed, 11 Oct 2017 11:45:00 +0900",
          "guid": {
            "isPermalink": "false",
            "content": "hatenablog://entry/8599973812305032728"
          },
          "category": [
            "Big Data",
            "データ開発エンジニア",
            "データ分析",
            "データクレンジング",
            "Data preparation"
          ],
          "enclosure": {
            "length": "0",
            "type": "image/png",
            "url": "https://cdn-ak.f.st-hatena.com/images/fotolife/a/astamuse/20171006/20171006150137.png"
          }
        }
      ]
    }
  }
}

パパっと整形

今回フィードリストで表示させたいのは

  • タイトル
  • 日付
  • 記事へのリンク
  • 記事のサムネイル画像

なので 、

select * from rss where url='http://lab.astamuse.co.jp/rss/category/'

こちらのワイルドカードになっている箇所に

title,link,pubDate,enclosure.url

をいれて、さらに、最新の4件だけにしたいので、rssの箇所をrss(4)に変更し再度テストすると下記のようなデータが返ってくる。

{
 "query": {
  "count": 4,
  "created": "2017-10-18T03:17:40Z",
  "lang": "en-US",
  "diagnostics": {
   "publiclyCallable": "true",
   "url": {
    "execution-start-time": "1",
    "execution-stop-time": "995",
    "execution-time": "994",
    "content": "http://lab.astamuse.co.jp/rss/category/"
   },
   "user-time": "1010",
   "service-time": "994",
   "build-version": "2.0.187"
  },
  "results": {
   "item": [
    {
     "title": "データクレンジングとかクォリティチェックとかの話",
     "link": "http://lab.astamuse.co.jp/entry/2017/10/11/114500",
     "pubDate": "Wed, 11 Oct 2017 11:45:00 +0900",
     "enclosure": {
      "url": "https://cdn-ak.f.st-hatena.com/images/fotolife/a/astamuse/20171006/20171006150137.png"
     }
    },
    {
     "title": "Spark3分クッキング HBaseで作る100万通りの文書分類器",
     "link": "http://lab.astamuse.co.jp/entry/2017/10/04/114500",
     "pubDate": "Wed, 04 Oct 2017 11:45:00 +0900",
     "enclosure": {
      "url": "https://cdn-ak.f.st-hatena.com/images/fotolife/a/astamuse/20171004/20171004104538.jpg"
     }
    },
    {
     "title": "データドリブンな企業とは何か~アスタミューゼ流宴会術~",
     "link": "http://lab.astamuse.co.jp/entry/data-driven-company",
     "pubDate": "Wed, 27 Sep 2017 11:50:00 +0900",
     "enclosure": {
      "url": "https://cdn-ak.f.st-hatena.com/images/fotolife/a/astamuse/20170925/20170925115807.png"
     }
    },
    {
     "title": "デザインの良し悪しを語るのに必要な「分解」について【書体編】",
     "link": "http://lab.astamuse.co.jp/entry/2017/09/20/120000",
     "pubDate": "Wed, 20 Sep 2017 12:00:00 +0900",
     "enclosure": {
      "url": "https://cdn-ak.f.st-hatena.com/images/fotolife/a/astamuse/20170920/20170920112706.png"
     }
    }
   ]
  }
 }
}

これでYQLのほうの準備は完了。URLはページ下にある「THE REST QUERY」の箇所に生成されます。


https://query.yahooapis.com/v1/public/yql?q=select%20title%2C%20pubDate%2C%20link%2C%20enclosure.url%20from%20rss(4)%20where%20%0A%20%20url%3D'http%3A%2F%2Flab.astamuse.co.jp%2Frss%2Fcategory%2F'&format=json&diagnostics=true&callback=

 Vue.jsで実装

今回Vueでajaxを扱うためのライブラリはvue-resoueceを使用しています。

採用サイトでは各ページに対応した職種(カテゴリー)のブログ記事を表示させたかったので、カスタムコンポーネントのpropsにカテゴリー名を入れて各カテゴリ一覧ページを読みにいかせるようにしました。

設置例)

<v-gethatena category="webデザイナー"></v-gethatena>

webデザイナーのカテゴリ一覧ページ

ソース例

templates

<div>  
  <ul>
    <li v-for="item in items">
      <a :href="item.link" target="_blank">
        <div class="feed-thumbnail"><img :src="item.enclosure.url"></div>
        <div class="feed-header">
          <span class="feed-date">{{item.pubDate}}</span>
          <h3 class="feed-title">{{item.title}}</h3>
        </div>
      </a>
    </li>
  </ul>
</div>

js


data() {
  return {
    items: [],
    isLoading: true 
  };
},
props:{
  category : { required: true }
},
created: function(){
  let yql = "https://query.yahooapis.com/v1/public/yql?q=select%20title%2Clink%2Cdescription%2CpubDate%2Cenclosure.url%20from%20rss(4)%20where%20url%3D";
  let lab = "http://lab.astamuse.co.jp/rss/category/";
  let requestURL = yql + "'" + lab + this.category+ "'" + "&format=json&diagnostics=true&callback="

  this.$http.get(requestURL).then(response => {
    for (var i = 0; i < response.body.query.results.item.length; i++) {
      this.items = response.body.query.results.item;
    }
  }, response => {
    // error callback
  });
}

これだけでとりあえずRSSフィードは実装完了です! (採用サイトで実装している実際のソースは最後に書いておきます。違いはローディングやデータフォーマットくらいですが。。。)

ホントお手軽にRSSフィード実装できちゃうので、まだ触ったことない人はぜひVue.jsとYQL触ってみてください~ (ただ、YQLはこの前2,3週間ほどサービスダウンしてたので、大事な箇所に利用するのはちょっと怖いw)


Vue.component('v-gethatena', {
  template: `
  <div>  
    <div v-if="isLoading">
      <slot></slot>
    </div>
    <div v-else>
      <transition-group name="v-gethatena" tag="ul" class="mod-list-feed" appear
      @before-enter="beforeEnter"
      @after-enter="afterEnter"
      @enter-cancelled="afterEnter">
        <li v-for="(item, index) in items" :key="index" :data-index="index">
          <a :href="item.link" target="_blank">
            <div class="feed-thumbnail"><img :src="item.enclosure.url"></div>
            <div class="feed-header">
              <span class="feed-date">{{item.pubDate}}</span>
              <h3 class="feed-title">{{item.title}}</h3>
            </div>
          </a>
        </li>
      </transition-group>
   </div>
  </div>
  `, 
  data() {
    return {
      items: [],
      isLoading: true 
    };
  },
  props:{
    category : { required: true }
  },
  created: function(){
    let yql = "https://query.yahooapis.com/v1/public/yql?q=select%20title%2Clink%2Cdescription%2CpubDate%2Cenclosure.url%20from%20rss(4)%20where%20url%3D";
    let lab = "http://lab.astamuse.co.jp/rss/category/";
    let requestURL = yql + "'" + lab + this.category+ "'" + "&format=json&diagnostics=true&callback="

    var 
    this.$http.get(requestURL).then(response => {
      for (var i = 0; i < response.body.query.results.item.length; i++) {
        this.items = response.body.query.results.item;
        this.items[i].pubDate = this.dateFormat(this.items[i].pubDate);
      }
      setTimeout(function() {
        this.isLoading = false;
      }.bind(this), 1500);

    }, response => {
      // error callback
    });

  },
  methods: {
    beforeEnter: function(el) {
      el.style.transitionDelay = 250 * el.dataset.index + 'ms'
    },
    afterEnter: function(el) {
      el.style.transitionDelay = ''
    },
    dateFormat: function(str){
      var my_date = new Date(str);
      var year = my_date.getFullYear();
      var month = my_date.getMonth() + 1;
      var date = my_date.getDate();
      var format_date = year + "/" + month + "/" + date;
      return format_date;
    };
  }  
});

new Vue({
    el: '#v-root'
});

Copyright © astamuse company, ltd. all rights reserved.