diagram

-

PROBLEM

  • フィードリーダーで記事を読んだ後にはてなブックマーク(ブクマ)するとフィード消化するのに時間がかかる
    • フィードをそのままブクマしていると下記の問題がでてくる
      • あとで確認することができない
      • 読みたくない記事をブクマしてしまう
      • 適切でないURLでブクマしてしまう

-

SOLUTION

というわけで、下記の方針でブクマすることにした。設置方法の詳細はGitHubレポジトリを参照。

方針

  • フィードごとにタグづけする
  • ブクマ対象になる記事をリンクとタイトルで除外判定する
  • ブクマ対象になる記事をリンクから校正すべきものかリダイレクトすべきものか判定する
  • 上記設定はYAMLファイルで簡単に管理できるようにする
  • フィード読込とブクマを非同期処理できるようElixirで実装する

ブクマの管理方法

下記5つのYAMLファイルで管理している、構造はマップとリストのみ。記事を読みすすめる中で気になるキーワードが出てきたら都度 feed.yaml を更新する。また、記事にノイズが多いようだったら傾向を分析して除外ファイル feed_excluded_link.yaml feed_excluded_title.yaml を更新してる。

item description
feed.yaml フィードグループ名に対するリンク、タグのマップ
feed_excluded_link.yaml 除外すべきフィードリンクのリスト
feed_excluded_title.yaml 除外すべきフィードタイトルのリスト
feed_corrected_link.yaml フィードリンクに対するトリミングすべきパラメータのマップ
feed_redirected_link.yaml フィードリンクに対するリダイレクト先リンクのマップ
# feed.yaml
nabinno/sports/feed_group_name:
  tags:
    - ski
  links:
    - http://rss.example.com/ski_feed.rss
    - http://rss.example.com/snowboard_feed.rss
    - http://ski-status.example.com/rss

# feed_excluded_link.yaml
- anti-ski.example.com
- awesome-snowboard.example.com

# feed_excluded_title.yaml
- queer
- two-planker
- beaver-tail

# feed_corrected_link.yaml
amazon.com:
  - ref
  - ie

# feed_redirected_link.yaml
ski-status.example.com:
  - Floki.find(fst, ".post__body a")

Elixirによる非同期処理

監視機構 Supervisor

Elixirには監視機構があり、それが各ワーカーを子プロセスとして管理している。ここではフィード読込とブクマは別々のワーカーで処理しているが、キャッシュが暖気処理を別ワーカーで行っているため再起動戦略は「失敗したイベントの中にあるすべての子プロセスを再起動」( one_for_all )にしてある。

下記のように Supervisor.start_linkKeshikimi2.Application.start に適用すると、アプリケーション開始( mix run )した時点で監視機構が起動される。

Supervisor.start_link(
  [
    :hackney_pool.child_spec(:hatena_bookmark_pool, timeout: 15_000, max_connections: 100),
    # @todo 当該ワーカーで暖気処理を行っていないので `one_for_one` にした場合、再起動時にほかに影響する
    supervisor(Cachex, [:feed, []]),
    supervisor(Keshikimi2Feed.Registry, [prefix]),

    # フィード読込処理 (PubSub)
    supervisor(Keshikimi2Feed.Subscriber, [prefix]),
    worker(Keshikimi2Feed.Worker, [prefix]),
    worker(Keshikimi2Feed.Publisher, [[prefix: prefix, poll_interval: 3_000]]),

    # ブクマ処理
    worker(Keshikimi2.HatenaBookmark.AddEntry, [
      [prefix: prefix, poll_interval: 3_000]
    ])
  ],
  strategy: :one_for_all,
  name: name(prefix)
)

非同期処理 Task.async_stream

配列を引き回すリクエスト処理は Task.async_stream がうってつけである。下記ではキャッシュからブクマ対象になるフィードリンクを取り出し、除外処理、校正処理を加えて、ブクマのリクエストを出すという流れを組んでいる。Elixirでは、流れをひとまとめにして視覚的にわかりやすく非同期処理してくことができる。

Cachex.keys!(:feed)
|> Enum.reject(fn key ->
  key in [
    "excluded_links",
    "excluded_titles",
    "corrected_links",
    "redirected_links",
    "feed_group",
    "archived_links"
  ]
end)
|> Task.async_stream(
  fn item_link ->
    with {:ok, [item_title, feed_tags]} <- Cachex.get(:feed, item_link),
         :ok <- validate_all(item_link, item_title),
         corrected_link <- correct_all(item_link),
         {:ok, payload} <-
           FormData.create(
             %{
               url: corrected_link,
               comment: feed_tags |> Enum.map_join(fn tag -> "[#{tag}]" end),
               rks: System.get_env("HATENA_BOOKMARK_RKS"),
               private: 0,
               keep_original_url: 1,
               with_status_op: 1,
               from: "inplace",
               post_twitter: 0,
               post_evernote: 0
             },
             :url_encoded,
             get: false
           ) do
      do_add_entries_to_hb(payload)
      Logger.info("add entry: #{item_link}")
    end

    archive_link(item_link)
  end,
  timeout: 15_000
)
|> Stream.run()

-

WRAPUP

これからのはてなブックマークの使い方

  • 手動でブクマ: 気になった記事があるごとに
  • ブクマの確認: 気になるタグごとにまとめて確認
    • たとえば、CIでデプロイしている間に最近のGitHubの動向を確認したい場合は「nabinno/github」をみる、という感じ。

-

以上 :droplet: