On Blahfe

Elixirではてなブックマーク

blog/backendelixirhatena-bookmark
thumbnail

紆余曲折合ってはてなブックマークの運用を見直す必要が出てきました。人の興味というのは尽きないもので知りたいことが次々出てきます。にも拘わらず人の時間は有限でそれにあがなうための手段を考えたわけです。

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 フィードリンクに対するリダイレクト先リンクのマップ
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による非同期処理

Elixirで非同期処理を行っているのですが、大きく分けて監視機構のSupervisorと非同期処理のTask.async_streamを使っています。

監視機構 Supervisor

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

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

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

ex
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

Elixirの非同期処理を使うことではてなブックマークの運用がとても快適になりました。はてなブックマークとの今後の付き合い方は下記のように考えています。

  • 手動でブクマ: 気になった記事があるごとに
  • ブクマの確認: 気になるタグごとにまとめて確認

ブクマの確認については、例えば、CIでデプロイしている間に最近のGitHubの動向を確認したい場合は「nabinno/github」をみる、という感じの運用です。

融通が利かない点で途中運用が難しくなる気もしますが、しばらく回してみます。

nabinno
Emacsianでアート好き、ランニング好きな@nabinnoが書いています
GitHub / X / LinkedIn / ネクイノ
blog/market

今後の成長分野:新たなテクノロジーの展望

テクノロジーの進化は、絶え間ない変化の中で私たちの日常を塗り替えてきました。時には経済的な危機が、新たな可能性を切り拓く契機となることもあります。そこで、過去のリセッション期に生まれたテクノロジーの足

market-trendrecession
blog/organization

ATKerneyの課題解決パターンの魅力的な探求

ATKerneyの課題解決パターン は、課題の本質を見極め、効果的な戦略的構造化を通じて解決策を導き出す手法にフォーカスしています。この冒険の旅は、解決者と協力者たちが心を一つにし、課題に立ち向かう様

problem-solvingatkerney
blog/market

就職氷河期とは何だったのか

私はいわゆる就職氷河期世代です。周囲から時折漏れ聞こえる不平のような言葉がありますが、それを単なる不平として片付けるのはもったいない気がします。できれば、その中に新しい視点を見つけ、次のチャンスへ繋げ

labor-economicsrecessionemployment-ice-age