Asopitech With · あとで読む
Instapaper with Alopex DB
Instapaperは、Webの記事を保存して、あとでオフラインでも読めるサービスです。Alopex DBで同じものを作るとしたら…?
更新: 2026/10/12
どんなサービスか
Instapaperは、Webで見つけた記事を保存して、あとで読むためのサービスです。保存した記事は端末どうしで同期され、オフラインでも読めます。記事の文章にハイライトとメモを付けられます。扱うデータは、記事の本文、ハイライト、メモ、フォルダです。
データの置き場所
- SQLテーブル:記事、フォルダ、ハイライト、メモをテーブルに持ちます。読みかけの位置も記事の行に持ちます。
- 全文検索:本文とハイライトに全文検索の索引を張ります。
- ベクトル:記事の埋め込みをベクトル列に持ち、HNSW索引で、いま読んでいる記事に近い記事を出します。
記事の取り込みと埋め込みの計算は、アプリが行います。
配置の形
Alopex DBを、端末のアプリに組み込みます。記事は端末の中の1つのファイルに入るので、電車の中でも読めます。端末どうしの同期は、Alopex DBのサーバーが受けます。
Alopex DBが合う理由
あとで読む記事は、たまるほど探しにくくなります。Alopex DBは、本文の全文検索と、似た記事の近似探索を同じSQLで行い、たまった記事を読みたい記事に変えます。
コードで見る
記事を本文・埋め込み・ハイライトで持ち、いま読んでいる記事に近い記事を出します。たまった記事をさばく中心だけを取り出したコードです。
import alopex
import numpy as np
db = alopex.Database.open("instapaper.alopex")
# 記事(本文+埋め込み)とハイライトを持つ
db.execute_sql("""
CREATE TABLE IF NOT EXISTS articles (
id INTEGER PRIMARY KEY,
title TEXT,
body TEXT,
embedding VECTOR(4)
)
""")
db.execute_sql("""
CREATE TABLE IF NOT EXISTS highlights (
article_id INTEGER,
quote TEXT
)
""")
articles = [
(1, "Rustの非同期入門", "async/awaitの基礎", [0.9, 0.1, 0.0, 0.0]),
(2, "確定申告の基礎", "控除のまとめ", [0.0, 0.0, 0.9, 0.1]),
(3, "Tokioの仕組み", "ランタイムの話", [0.85, 0.15, 0.0, 0.0]),
]
for aid, title, body, emb in articles:
db.execute_sql(
"INSERT INTO articles (id, title, body, embedding) VALUES (?, ?, ?, ?)",
[aid, title, body, np.array(emb, dtype=np.float32)],
)
db.execute_sql("INSERT INTO highlights (article_id, quote) VALUES (?, ?)", [1, "awaitは評価を止めない"])
# いま読んでいる記事(Rustの非同期入門)の埋め込みに近い、ほかの記事を出す
reading = np.array([0.9, 0.1, 0.0, 0.0], dtype=np.float32)
rows = db.execute_sql(
"""
SELECT title, vector_similarity(embedding, ?, 'cosine') AS score
FROM articles
WHERE id <> ?
ORDER BY score DESC
LIMIT 2
""",
[reading, 1],
)
for row in rows:
print(round(row["score"], 3), row["title"])近似探索で、関連する記事が次に続きます。全文検索と組み合わせれば、言葉と意味の両方で探せます。
端末に組み込めて、HNSW索引で似た記事も出せるAlopex DBで、あとで読むアプリを始めてみませんか。クイックスタートから試せます。