コード解説:Gunosyは多くのユーザに同じ記事を配信しているのか検証してみた

Gunosyは多くのユーザに同じ記事を配信しているのか検証してみた

はじめに

昨日、Gunosyは多くのユーザに同じ記事を配信しているのか検証してみたという記事を書いた。

ランダムにピックアップしたGunosyユーザの特定の日の配信記事リストから、記事URLを使って重複をカウントし、本当に重複記事数が著しく多かったり、ユーザ間の重複率が批判の通りなのかを検証したものだ。

こちらでは、技術ブログなので検証に使ったコードを公開して簡単に解説しよう。

技術的な方針

今回は、素早く分析するためにフレームワークなどは使わず、捨てる前提のスクリプトを以下の方針で書いた。

  1. Google Custom Search APIによりGunosyの公開ユーザ名を取得
  2. (1)で取得したユーザに特定の日(今回は5/5)に配信された記事のURLとタイトルを取得
  3. ユーザ間の重複数を記事毎に数えてファイルに書き出す

事前準備

Google Custom Search APIにより、gunosy.com内の公開ユーザ名をリストアップする。
ここはコードにしても良かったが、Google APIの制約上、上位100件までしか取得できないことが分かっていたため、ブラウザからAPIにパラメータを変えて10回アクセスし、それぞれの結果を1.jsonから10.jsonとしてローカルに保存した。(これを後述のimport.rbから読み込む)

Google Custom Search APIの使い方は、Getting Started with the API – Custom Search — Google Developersに書いてある順番通りに作業すれば使えるようになると思うので試してみてほしい。

以下のINSERT-YOUR-KEYの部分を自身のKEYに変えてリクエストすると試せると思う。
なお、2ページ目以降を取得する際に変更するパラメータはstartだ。

https://www.googleapis.com/customsearch/v1?key=INSERT-YOUR-KEY&cx=016434714500670441010:kxhgwdwqsza&q=site:gunosy.com&start=1

今回は、1.jsonから10.jsonを含むファイル群を後述のRubyファイルと共にGithubに上げてあるので、試す人はそちらを使うと良いと思う。

noriaki/validate-gunosy at 20130506 · GitHub

モデル (ORM)

データベースには、MongoDBを利用した。ローカルにMongoDBがインストールされていて、ポート27017(デフォルト設定)で起動していることを前提にしている。

user.rb

class User
  include Mongoid::Document
  field :name

  has_many :recommendeds, dependent: :destroy

  def url(date=Date.yesterday)
    "http://gunosy.com/#{name}/#{date.strftime("%Y/%m/%d")}"
  end
end

ユーザ名をnameカラムとして保持する。
また、urlメソッドにより、特定の日にユーザへ配信されたGunosy上の記事一覧ページURLを作っている。

article.rb

class Article
  include Mongoid::Document
  field :url, type: String
  field :title, type: String

  has_many :recommendeds, dependent: :destroy
  has_many :pickings, dependent: :destroy
end

こちらもUserモデルと同様の形式で、配信された記事URLとタイトルを保持している。

recommended.rb

これは、UserモデルとArticleモデルを多対多でつなぐためのモデルで、両モデルのIDだけを保持している。本来のKVSの使い方とは違うかもしれないが、自分としては分かりやすいので良くこういった書き方をする。

class Recommended
  include Mongoid::Document
  field :user_id
  field :article_id

  belongs_to :user
  belongs_to :article
end

事前準備したデータを読み込む (Import)

import.rb

このファイルは以下のように単体で実行すると、既存DBをクリアして事前準備したデータを読み込む。

% ruby import.rb
require 'json'
require 'open-uri'
require 'nokogiri'
require 'mongoid'
require 'csv'
require 'kconv'

$LOAD_PATH.push '.'
require 'user'
require 'article'
require 'recommended'

Mongoid.configure do |conf|
  conf.master = Mongo::Connection.new('localhost', 27017).db('validate_gunosy')
end

if __FILE__ == $0
  Article.destroy_all
  User.destroy_all

  search_results = (1..10).map{ |i|
    JSON.parse(File.open("g/#{i}.json").read)['items'].map do |t|
      t['link']
   end
  }.flatten
  search_results.each do |l|
    name = l[/[^g]\/([^\/]*?)\/?$/,1]
    if name && !%w(gunosy.com signup login iphone).include?(name)
      User.create name: name
    end
  end

  links = User.all.each do |user|
    Nokogiri(
      (open(user.url(Date.parse('2013/5/5'))) rescue StringIO.new).read
      ).css('article h1').each do |e|
      article = Article.find_or_initialize_by url: e.parent['href']
      article.title = e.text.strip
      article.save
      Recommended.create user_id: user.id, article_id: article.id
    end
  end

end

そんなに複雑なことはやっていないが、GitHubにも上げた先述の1~10.jsonを順番に読み込み、ユーザ名を抽出して、それぞれの5/5に配信された記事一覧をスクレイピングして取得している。

irb -r”./import”

最後にirbを以下のようにimport.rbを読み込んで起動し、各種検証用に数字を確認した。

% irb -r"./import"

TSVファイルへ結果を書き出す

上記の通り起動したirb上で以下のコードを実行すると、articles.tsvにデータが出力される。Excel以外で見るときなどは、.tosjis部分を削除すれば文字コードがUTF-8になるはずだ。

CSV.open('articles.tsv', 'w', col_sep: "\t") do |row|
  Article.all.each do |a|
    row << [a.url,a.title.tosjis,a.recommendeds.count]
  end
end

なお、最後になったがRubyのバージョンは以下である。

% ruby -v
ruby 1.9.3p392 (2013-02-22 revision 39386) [x86_64-linux]

さて、はてブのホットエントリーデータも入手できたことなので、次回はさらにはてブとGunosyが似ているという点についても調べてみよう。

スポンサード

名刺管理サービス「Eight」から連絡先情報をエクスポートする

名刺管理サービス「Eight」

はじめに

日々増え続ける名刺の管理には、もうずいぶん前からEightを利用している。
データも正確で検索もし易く相手も利用していれば名刺情報の更新が届くなど非常に便利に使っているが、唯一あったら良いなと思っているのがデータのエクスポート機能だ。

自分が退職するときにも、関わりのあった皆に退職報告をしようと名刺情報をダウンロードしようと思ったが見つからなかった。
登録した名刺情報をエクスポートしたいという要望は有るようだが、まだ機能は実装されていないようだ。

そこで、無ければ自分で何とかするということで、自分の管理している名刺情報をエクスポートするためのJavascriptを書いた。ログインしている画面 (Google Chrome) の Console から実行する簡易コードだ。

2014/03/23追記:Eightサービスがリニューアルして現在は動かなくなっています。対応したものを名刺情報をiPhone連絡先へインポートする方法(またはEightデータをvCard形式でエクスポートするブックマークレットを作りました) – noriaki blog はてな出張所としてエントリ書きました。こちらをご覧ください。

2014/03/23追記:また、コメント欄でMauriceさんからEight / 8card エクスポート Chromeエクステンションを作ったとご連絡いただきました。

ご注意点

  • あくまでも自分の管理している名刺情報(名前、会社、電話番号、メールアドレス)が取得できるだけ
  • 使い方をよく読み、Javascriptコードの内容が理解できる方のみ実行することをオススメする
  • 動作は無保証であり何らかの問題が起こっても責任は取れない
  • Eightサービス側の仕様変更等により使えなくなることもある
  • 利用規約はざっと読んで問題無いと思われるがEightサービスから怒られたら謝る

使い方

  1. EightへGoogle Chromeを使ってログイン
  2. Ctrl + Shift + JなどでConsole画面を開く
  3. 下記のJavascriptコード1を実行する
  4. 100件ずつ名刺情報を取得するので全件取得完了したらJavascriptコード2を実行する
  5. 結果として表示された部分をコピーしてExcel等にペーストする

Javascriptコード1

var res = [];
function recursive_get_data(res, page) {
    $.ajax({
        type: "GET",
        url: request_url({ page: page }),
        dataType: "json",
        success: function(data) {
            var data_array = data.result;
            $.each(data_array, function(i,obj) {
                $.each(obj, function(month,cards) {
                    if(cards.length === 0) { return }
                    $.each(cards, function(j, card) {
                        var person = card.person;
                        var info = person.personal_cards[0].eight_card || person.personal_cards[0].friend_card;
                        var r = [person.id];
                        r.push(info.front_company_name === "" ? info.back_company_name : info.front_company_name);
                        r.push(info.front_full_name === "" ? info.back_full_name : info.front_full_name);
                        r.push(info.front_full_name_reading === "" ? info.back_full_name_reading : info.front_full_name_reading);
                        r.push(info.front_tel1 === "" ? info.back_tel1 : info.front_tel1);
                        r.push(info.front_email === "" ? info.back_email : info.front_email);
                        r.push(month);
                        res.push(r);
                    });
                });
            });
            console.log(data.context, page);
            if(page < data.context.total_pages) {
                recursive_get_data(res, page + 1);
            }
        }
    });
}
recursive_get_data(res, 1);

function request_url(option) {
    return "https://8card.net/people/display_personal_cards.json?0.13670155755244195&_method=get&sort=5&transcribing=0&per_page=100&web=1&use_paginate=1&tags=&index=&keyword=&page=" + option.page;
}

Javascriptコード2

console.log($.map(res, function(array, i) { return array.join("\t"); }).join("\n"));

最後に

Eightさん、いつもありがたく使わせてもらってます。有料機能でも良いのでエクスポート機能を早く提供してください。

スポンサード