#note #tech

自分史上最強の情報収集フローを紹介するぜ

情報収集どうしてますかという話

RSSやAtomが好きだ。

昔ながらの情報配信形式であるものの、受け手が能動的に配信された情報を取得しに行くことができる。当然開封率に関係があるので、サービスを作る側からすると一転、push通知やメルマガやらDMやらLINE通知やらを作りたくなるのが人の性ではあるのだが、消費者からすると興味のある情報を自分のタイミングでフィードで取得できる形式はとても理にかなっているし心地よい。

キュレーションサービスもいいのだけれど、ある一定量、自分で情報を取得していると、「それ知ってる〜」の飽和状態になり、偶発的な出会いとは遠ざかり、あまり意味をなさなくなった。SNSはドゥームスクローリングにならないように、少し距離を置くようにしている。

この世のすべての配信がRSSになればいいんじゃないか、とか、RSSの復興を強く願うくらいにはRSSに頼って生きている。実際マジ便利だと思うんで、あのフィード型の配信形式は確実に全メディア、全サービスに搭載されるべきだと思う。その辺みんなどう思う?

なぜなら同じような仕事の人はだいたい同じようなもんだと思うんだけれど、日常的にそこそこのメディアとかニュースとかに目を通しておきたいと思っているし、実際問題、通しておかなきゃ困ることが多くて、情報に溺れる毎日を過ごしているからだ。受け取る情報をコントロールしなければとてもじゃないが扱いきれない。

これ実際みんなどうしてるんだろうなといつも思う。人それぞれ使ってるツールややり方があると思うんだけれど、見ている記事などの共有はよくあるとしても、その情報収集がどうやってコレクションされているかはあまり聞かない。

そこで自分も色々試してきたなかで、いまのやり方にしてから一年ほど経って、一つの到達点になったとして、ここらで一つ自分のやり方を紹介をしたい。たぶんこっから先は生成AIに食わせて要約を作ってもらうとかになると思う。(もうそろそろそういうサービスがどっかにあるんだろうな)

咀嚼を段階的におこなう

そういうわけで、いま得ている情報のほとんどの入口はRSSやAtomなどのフィード配信である。以降はRSSやAtomなどで配信された情報を「フィード」としてまとめて扱う。この記事で語ることもほとんどがそれらのフィードの扱い方になる。

理想的には興味のあるメディア、記事のフィードすべてを精読し、頭の中に入れられると良いとは思う。でも、あるメディア、ニュース、ブログ、動画などで流される全情報を追っていくのはもう無理がある。もうマジで無理になってきた。ある一週間、後述の方法でフィードから受け取っている記事数は1500以上だった。そうなってくるとそのすべてに目を通すというよりは、重要性が高いものをどれだけ確度高くキャッチできるかという話になる。

なのでここ数年、どれだけザッピングの精度を高められるか、どれだけストレスフリーでできるかを念頭に情報収集を考えてきた。そうなるとやっぱりslack。もう間違いなくザッピング当世最強はご存じ皆のインフラ、お仕事においてもベストパートナー、安定のslack。フィードを流し読みするにはslackのプレビュー機能が最強という結論に至る。

なんだ、フィードをslackに流す話かというと、端的に言うとそうなのだけれど、それだけじゃなくて最高のslackでもいくつか苦手なものがある。例えばslackのフィードは登録しにくくて仕方がない。管理画面に行かなきゃならなかったり、スラッシュコマンド打たなきゃならなかったり、何より一覧性とか俯瞰性とかが低い。なのでそれはslackから切り離すことにした。

またslackのせいではないのだけれど、流し読みをするのと、1コンテンツを精読するのを同時にやろうとすると頭の切り替えが必要で時間もかかる。slackは流し読みは最高だが、精読には向かない。それも別の工程に分けることにした。

そういう自分なりの最適化で、できあがったのが下記のフローである。

  1. Feedly にフィードを登録し、すべてのフィードを IFTTTslack の #news チャンネルに投げる。
  2. #news チャンネルでタイトルと概要をプレビューで見ながら、気になるコンテンツに pocket の絵文字を付ける。
  3. pocket の絵文字が付けられた記事はslack workflowによってpocketに保存される。
  4. pocket でコンテンツを精読し、興味深いコンテンツにはスターをつける。
  5. pocketでスターがついた記事は Buffer 経由で X に投げられる。
  6. pocketでスターがついた記事は Raindropにアーカイブされ、Raycast で検索、開けるようになる。

結局のところ、餅は餅屋、というサービスの使い分けをしているだけなのかもしれない。各工程での詳細を以下に続ける。

各工程でやっていること

Feedly(IFTTT) -> slack

とにかくフィードを集める。

フィードの扱いには、その名にFeedを冠したFeedlyがやはり一番使い勝手が良いと感じている。一覧性の高いフィードの管理方法に柔軟なインテグレーションを備えている。時々UIが変わって設定する箇所を見失ったりもするが全然許せる。

一つのメディアのフィードを登録すると、同じようにそれを登録している人数も分かって信頼度も測れるし、関連度が高いメディアも教えてくれるのも何気に情報収集が捗って嬉しい。ただまぁこの辺は Inoreader とかでも同じことはできるのかもしれない。

ちなみにFeedlyはそもそも「後で読む」機能も、読みやすさも兼ね備えている素晴らしいサービスだが、なぜそこで完結していないかというと、逆に読まなければならないという圧が強い(超わがまま)のと、このFeedly以外からの情報も扱いたいため。例えばAndroid上で気になった記事とかをFeedlyに送ることはできないが、pocketになら送れて、まとめて後で読めるから。

自分はFeedlyに届いたすべてのフィードをIFTTTを使ってslackのチャンネル(#news)に投げている。投げているのはURLだけ。どうせslackがプレビューで情報を開いてくれるので、蛇足な情報を一緒にポストすると認知の負荷が高まるため。なので全てのメディアはOGPなどの情報をきっちり載せてくれると非常にありがたい。

slack -> pocket

フィードを流し読みして仕分ける。

するするとスクロールだけで情報を見ていける体験が非常に良い。またFeedlyからのフィードはslackの #news チャンネルにすべて届くようになっているが、ここには「読まなくてもいいけど読んだほうがいいもの」も流れてくるようにしてあって、例えば毎日の体重の記録、習慣のロギングなどが何でもかんでも流れてくるようになっている。それを片っ端から目に入れて吟味する。

で、詳細を把握したいコンテンツについてはpocketの絵文字をつけ、「後で見る」を最小の動作で仕分けていく。pocketの絵文字が付けられたものがどうなるかというと、slack workflowによって自動的にpocketに保存されるようにしてある。これがこのフロー全体のキモ。この方法でなら読むスピードを落とさず、読みたいか読みたくないかの判断をすることができる。

slack workflowのQuick Startは下記。

あとこれだけじゃ全く役に立たないが一部のコードを下記に載せる。:pocket: の絵文字を作っておいて、下記のワークフローが正しく動くと、 :pocket: がつけられたメッセージ内のURLを自動的にpocketに保存する。主にChatGPTに作ってもらった。めちゃくちゃ楽。

main.js
// -------------------------
// ワークフロー定義
// -------------------------
import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts";

export const workflow = DefineWorkflow({
  callback_id: "slack-to-pocket",
  title: "Slack to pocket",
  input_parameters: {
    properties: {
      channel_id: { type: Schema.slack.types.channel_id },
      user_id: { type: Schema.slack.types.user_id },
      message_ts: { type: Schema.types.string },
      reaction: { type: Schema.types.string },
    },
    required: ["channel_id", "user_id", "message_ts", "reaction"],
  },
});

// -------------------------
// トリガー定義
// -------------------------
import { Trigger } from "deno-slack-api/types.ts";

const trigger: Trigger<typeof workflow.definition> = {
  type: "event",
  name: "Trigger the pocket",
  workflow: `#/workflows/${workflow.definition.callback_id}`,
  event: {
    event_type: "slack#/events/reaction_added",
    channel_ids: ["****"],
    // 特定のリアクションのときだけワークフローを実行するよう条件を指定
    filter: {
      version: 1,
      root: { statement: "{{data.reaction}} == pocket" },
    },
  },
  inputs: {
    channel_id: { value: "{{data.channel_id}}" },
    user_id: { value: "{{data.user_id}}" },
    message_ts: { value: "{{data.message_ts}}" },
    reaction: { value: "{{data.reaction}}" },
  },
};

export default trigger;

import { putPocket } from "./put_pocket.ts";
workflow.addStep(putPocket, {
  channel_id: workflow.inputs.channel_id,
  message_ts:
    workflow.inputs.message_ts,
});
put_pocket.ts
import { DefineFunction, Schema, SlackFunction } from "deno-slack-sdk/mod.ts";

// DefineFunction でファンクションのメタデータを定義
export const putPocket = DefineFunction({
	callback_id: "put-pocket",
	title: "Put pocket",
	// プロジェクトのルートディレクトリからのパスを指定する必要がある
	// functions/ 配下に配置するときは functions/foo.ts のようになる
	source_file: "put_pocket.ts",
	// inputs の名前と型を一覧で定義、必須のものは required の配列に列挙
	// ファンクションのコードの中で inputs.channel_id のように型安全にアクセスできるようになる
	input_parameters: {
		properties: {
			channel_id: { type: Schema.slack.types.channel_id },
			message_ts: { type: Schema.types.string },
		},
		required: ["channel_id", "message_ts"],
	},
	// このファンクションが返すべき outputs を定義
	// required のものがファンクションコードから返されていない場合
	// コンパイルがエラーとなる
	output_parameters: {
		properties: {},
		required: [],
	},
});

import { SlackAPI } from "deno-slack-api/mod.ts";

// SlackFunction の定義を export default することで
// このファンクションのハンドラーを有効化し、それをワークフロー側で import できるようにする
export default SlackFunction(putPocket, async ({
	// これらがサポートされているすべての引数
	inputs, // input_parameters に定義されている値
	env, // slack env コマンドで事前に登録された値(SLACK_API_URL は組み込みの値)
	team_id, // ワークスペースの ID(必ず存在する)
	enterprise_id, // Enterprise Grid の場合の OrG の ID(Grid でない場合は空文字となる)
	token, // Slack API 呼び出しに使える bot token
}) => {
	const consumerKey = env.POCKET_CONSUMER_KEY;
	const accessToken = env.POCKET_ACCESS_TOKEN;

	// 異常終了として error のみを返してワークフローの実行を終了する
	if (!consumerKey || !accessToken) {
		return { outputs: { } };
	}

	const text = await getSlackText(token, inputs.channel_id, inputs.message_ts);

	// URLを抽出し、pocketへpostする
	await extractUrls(text).map((url) => {
		postPocket(consumerKey, accessToken, url);
	});

	return { outputs: { } };
});

const getSlackText = async (token: string, channel_id: string, ts: string): Promise<string> => {
	const client = SlackAPI(token);
	const postedMessageResponse = await client.conversations.history({
		channel: channel_id,
		latest: ts,
		inclusive: true,
		limit: 1
	});

	const text = postedMessageResponse.messages.flatMap((message: any) => {

		// テキストが存在する場合はテキストから
		// 主に人がそのまま送信したメッセージ
		let text = message.text;

		if (!text) {
			text = message.attachments.flatMap((attachment: any) => {
				if (attachment.original_url) {
					const url = new URL(attachment.original_url);
					// Twitterの場合はツイートから取得し、
					// それ以外のメッセージの場合はオリジナルURLを取得する
					if (url.host === "twitter.com") {
						return attachment.text;
					} else {
						return attachment.original_url;
					}
				}
			}).join(" ");
		}
		return text;
	}).join(" ");

	return text;
}

const extractUrls = (text: string): string[] => {
	// URLを認識するための正規表現パターン
	const urlPattern = /(https?|ftp):\/\/[-_.!~*\'()a-zA-Z0-9;\/?:\@&=+\$,%#\u3001-\u30FE\u4E00-\u9FA0\uFF01-\uFFE3]+/g;

	// URLの配列を抽出し、それを返す
	const urls = text.match(urlPattern);

	// match()メソッドは一致する文字列がない場合にnullを返すので、その場合は空配列を返します。
	return urls ? urls : [];
}

const postPocket = async (consumerKey: string, accessToken: string, url: string) => {
	const apiUrl = "https://getpocket.com/v3/add"
	const body = new URLSearchParams();
	body.append("consumer_key", consumerKey);
	body.append("access_token", accessToken);
	body.append("url", url);
	const requestHeaders = {
		"content-type": "application/x-www-form-urlencoded;charset=utf-8",
	};

	const apiResponse = await fetch(apiUrl, {
		method: "POST",
		headers: requestHeaders,
		body,
	});
}

おすすめは下記のように絵文字のスロットに :pocket: を入れておくこと。こうすることでワンクリックで pocket に記事を送ることができる。

pocket(IFTTT) -> Buffer -> X

気になる情報を精読する。

pocketに届いているコンテンツを上から精読していく。だいたい毎日夜に細かく一度、週末にはまとめて見るようにはしている。把握し終わったらアーカイブするようにし、いわゆるインボックスゼロを目指している。

基本的にバックナンバー等でなければYoutube等のメディアも自分はpocket上で見ている。時々pocket上では見れない記事であったり、ログインが必要な記事があったりするが、「読む」と決めているものなので気持ち的にもハードルにはならない。あとこれは少し残念ながら、pocketに入れたからといって、コンテンツがとても読みやすくなるわけではない。

また精読、視聴した結果、興味深かった情報にはpocketでスターを付ける。スターがつけられたものはIFTTTによってBufferに送られるようになっていて、Bufferは5分に一度、X(@masakuni_ito_ps)に投げるようにしている。なぜBufferを通すかというと、XのAPI制限をコントロールするため。

pocket(IFTTT) -> Raindrop <- Raycast

気になる情報を保存する。

一度pocketでスターを付けたコンテンツについては、Buffer経由でXに送るのと同時に、IFTTTを通してRaindropに送るようにしている。RaindropはURLの整理と、検索などがやりやすいブックマークサービスだ。

自分はブックマークをRaindropに頼っているため、pocketと似たようなサービスではあるが使い分けている。さらにランチャーソフトの Raycastと連携することによって Raycast上から過去気になった記事を検索することができるようになるのも最高だ。

Collectionsはあまり分けるとRaycastと相性が悪かったのであまり分けてない。


このルーティーンを順次段階的に毎日している。このフローのいいところは、時間をかけたくない仕分けと、時間をかけたい精読を分離できることにあるのだが、他にも下記のようなメリットを感じている。

  • Chromeのエクステンションからslackに投げといたり、pocketに投げといたり、自由に情報の差し込みや拡張ができること。
  • 動画は週末に後で見ようなどと選択できるくらいの数に絞れること。
  • 興味深い情報だけをシェアすることで自己開示の濃度が上がること。
  • 興味深い情報だけをアーカイブすることで検索精度が上がること。

デメリットは下記だ。

  • 少なくともIFTTT、slackの料金はかかる。
  • 流れてくるフィードが重複することはままある。

各サービスのフィード対応状況

ブログだったりするとデフォルトでRSSやAtomを作ってくれたりするので、かなりの確率でフィードを取得できるのだが、サービスになるとその方針は各社異なる。ここに自分が確認できただけのサービスのフィード配信状況を載せておく。間違いがあれば指摘してもらいたいし、追加の情報があれば欲しい。各サービスはもっとフィード配信してくれると嬉しい。

フィードがなさそうで?あるもの

Youtube

https://www.youtube.com/feeds/videos.xml?channel_id={チャンネルID}

チャンネルIDは文字通りそのチャンネルのIDなのだが、画面上からは確認しにくいことがある。そんなときはチャンネルのページを開き、説明の「...さらに表示>チャンネルを共有>チャンネルIDをコピー」で取得できる。

最近の癒やし、「ひみつ基地。」のチャンネルID。マジ木漏れ日のような幸せがずっと継続しているチャンネルなのでみんな見て欲しい。

ちなみに、Feedlyの場合、このチャンネルのURL(ひみつ基地。の場合は https://www.youtube.com/@Himitsu-Kichi )を登録すれば自動的にチャンネルIDまで取得してくれるので、この手間は不要となる。これだけでもslackのRSS登録から離れる価値がある。

Bluesky

https://bsky.app/profile/{フルハンドル}/rss

フルハンドルというのは、その人のプロフィールページのURLで確認できる。自分なら masakuni-ito.bsky.social となる。

ちなみに自分はBlueskyは全く動かしてない。結局なー、SNSってみんなが一気に移らないと意味がないんだよなー。ただXもThreadsもフィードは取得できないので、その観点から言えば扱いやすい。さらにちなみにFeedlyはこのプロフィールページのURLを持っていくだけでRSS登録ができる。

note

https://note.com/{note ID}/rss

note IDはその人のプロフィールページのURLで確認できる。自分なら omoide_testament となる。

ちなみにFeedlyなら……そう、このプロフィールのURLを登録するだけで良い。 /rss を書くかかかないかの違いだが、大きな違いだ。どうだ、すごいだろう?

しずかなインターネット

https://sizu.me/{表示名}/RSS

まだ1記事も書いてないが、味のあるサービスしずかなインターネットもRSSで取得できる。表示名というのはやはりプロフィールのURLで取得できる。ちなみにFeedlyなら……そう、その通り。

はてなブックマーク

https://b.hatena.ne.jp/hotentry/{カテゴリID}.rss

e.g.
https://b.hatena.ne.jp/hotentry/all.rss

カテゴリIDというのは正式には何というのかは知らない。例えば 統合 であれば all だし、 テクノロジー であれば it となる。世の中で人気のあるものが上がってくるので、ホッテントリを追っていけばある程度その日どんな記事が流行ったかを把握することができる。

昔はブクマ数とかのしきい値でフィルタリングする機能も提供していたように思うが、いつの間にかなくなってしまった。このため、人気が高騰しているものでなくても流れてくる。もしそれが嫌なのであれば、下記を使わせてもらえばフィードをさらにフィルタリングしてくれる。

Qiita

https://qiita.com/popular-items/feed
https://qiita.com/{ユーザー名}/feed

Qiitaは トレンド を定期的にフィードしてくれている。これを追っていくと技術トレンドにそれなりについていけて、全くの未知が減る。ユーザー名はまぁどこでも見れるけど、プロフィールから確認できる。

Zenn

https://zenn.dev/feed
https://zenn.dev/{ユーザー名}/feed

Zennでもトレンドをフィードしてくれている。Qiitaと比較して腰を据えて読むような記事が流れてくる。他にもユーザーごとのFeedもQiitaと同じようにできて、詳しくは下記に書かれている。

SpeakerDeck

https://speakerdeck.com/{*}/{カテゴリID}.atom

e.g.
https://speakerdeck.com/p/featured.atom
https://speakerdeck.com/c/technology.atom
https://speakerdeck.com/c/programming.atom
https://speakerdeck.com/c/programming.atom?lang=ja

カテゴリIDと言っているものはこれも自分が勝手に言っているだけで、* 部分も含めて この ページから確認できる。単純にこのURLに .atom をつければフィードを取得できる。個人のスライドに関しては .rss をつける と取得できるようだ。

Github

https://github.com/{ユーザー名}/{リポジトリ名}/releases.atom

上記のURLで特定リポジトリのリリース情報をキャッチできる。他はあまり使ったことはないが、Githubからは色んなフィードが取得できるようだ。

ちなみに初めて試したのだが、FeedlyにリポジトリのURLを与えただけでいくつか選択できることは確認した。便利。

少年ジャンプ+

https://shonenjumpplus.com/rss/series/{シリーズID}

シリーズIDが何なのか知らないが、各漫画のページの上部でフィードのリンクを取得することができる。例えば SPY×FAMILY を開いてみると分かる。

フィードがありそうでないもの

X

Xにはフィードがない。少なくとも現時点では知らない。かつてはあった気もするし、自由度の高いAPIだったので自分で作成することができた。そういったサービスも色々あったが、軒並みなくなっている。

昔はリストに入っているユーザーのツイートをRSS化したりしてたので非常に便利に情報だったり日常を集めることができた。今はフィードではないけれど、少ない特定ユーザーのポストをIFTTTを使ってslackに投げるに留まっている。

Instagram

InstagramもAPIの利用制限強化を受けて、フィードが作りにくくなりなくなっていった。画像が投稿されるSNSとslackの相性が良かったので残念。プラットフォームとしての繁栄を考えるにいた仕方なし。

Threads

Threadsもフィードはない。少なくとも自分は知らない。中身はほぼFacebookであり、Instagramなので、そのあたりは当然のポリシー上として作られないのかもしれない。ユーザーが増えていると聞くので残念。

Netflix

Netflixで新作があったときにフィードしてくれると大変助かるのだけれど、見つけられていない。たぶんこれもない。

まとめ

今のところ、この方法が自分史上最強だな――と思っている。人それぞれだと思うので、何かの参考になれば嬉しいし、もっと冴えた方法があれば教えて欲しい。

ただ、最強だな――と思っているものの、割とすぐ変えそうな気がする。次は間違いなくAIの要約が入ってくると思うし、もうその方法を探し始めている。Notionに保存していくのも良いかなーとか。

それからそういった転換点であるがゆえか、メディアの広告運用が辛くなってきたという話も聞く。pocketで見てるとは言ったが有意義だった記事は開くしシェアもする。最近は広告が嫌ではないのだ。

各サービスやプラットフォームやメディアやブログの考え方やビジネスモデルによって、各コンテンツを「読ませていただいている」のか「読んでやってる」のか「読まされている」のか、受け取り手としては姿勢が実際的に変わるのかもしれないが、どんな記事にせよ「読ませていただいた」と思いつつ、共存共栄で出会うべきコンテンツに出会えるといいなと思う。

どうかフィードに栄光あれ。