webサイトからAnkiノートを一括作成する


javascript/typescript, HTML, css を使用して、web サイトから Anki ノートを一括作成する方法を紹介します。

まず公式サイトのマニュアルを一読することをおすすめします。これも Immersion の一環です。

カードとノートの違い

Anki のノートとカードの概念がややこしいのでまとめました。

  • ノートはフィールドとカード(s)を定義する
  • カードはあるノートの表示方法をHTML, cssなどによって定義する

web サイトからノートを作る

例えば以下のスクリプトで語源まとめサイトから各語源のリンクとタイトルを取得できます。

javascript
// in browser console
document
.querySelectorAll('.entry-content h3 + ul li a')
.forEach((a) => console.log(a.href, a.textContent));

リストなどの形式で保存しておきます。

src/targets.ts
typescript
export const targets = [
{
href: 'https://shinuwakaeng.com/a-an-gogen-matome',
name: 'a, an (= without 否定/ない)',
},
// ...
];

Anki からノートを定義します。名前は例えばword_root、フィールドは以下のように定義しました。

fields for word_root

定義したフィールドに合わせてタイプを定義します。

src/types.ts
typescript
export type Note = {
Expression: string;
Definition: string;
Example: string;
Image: string;
URL: string;
};

先程用意したリストから csv ファイルを出力します。以下の例では Deno を使用しています。Anki は HTML によってカードを表示するので、web サイトからinnerHTMLなどのプロパティでそのまま HTML コードを取得すると後で便利です。

src/main.ts
typescript
import { DOMParser } from 'https://deno.land/x/deno_dom@v0.1.33-alpha/deno-dom-wasm.ts';
import { writeCSVObjects } from 'https://deno.land/x/csv@v0.7.5/mod.ts';
const notes: Note[] = [];
const parser = new DOMParser();
for (const { href, name } of targets) {
try {
await new Promise((resolve) => setTimeout(resolve, 500));
const response = await fetch(href);
const document = parser.parseFromString(await response.text(), 'text/html');
if (!document) throw new Error('document not found');
// ... get content from document ...
const note: Note = {
Expression: name.replace(/(.*)/, '') ?? '',
Definition: definition ?? '',
Example: example ?? '',
Image: image ?? '',
URL: href,
};
notes.push(note);
} catch (err) {
console.log(err);
}
}
const f = await Deno.open('./notes.csv', { write: true, create: true, truncate: true });
const header = Object.keys(notes[0]);
await writeCSVObjects(f, notes, { header });
f.close();

以下のコマンドで実行します。

zsh
deno run src/main.ts

Anki から csv ファイルをインポートすると以下の画面が表示されます。作成したノートとデッキを選択します。フィールドと各項目を合わせてインポートします。一括作成する場合、同じ単語のカードが既に存在していることがよくあります。その時にインポートしないこともここで設定できます。今回は重複があってもインポートする設定にしています。フィールドにHTMLを使用している場合、Allow HTML in fieldsをチェックします。

import csv

あとはカードのテンプレートを作成して完成です。

html
<!-- Front Template -->
<div class="front">{{Expression}}</div>
<script>
// ...
</script>
html
<!-- Back Template -->
<div class="front">{{FrontSide}}</div>
<hr class="separate" />
<div class="panels">
<div class="half">
<div class="photos" id="image">{{Image}}</div>
</div>
<div class="half">
<div id="definition">{{Definition}}</div>
<div id="example">{{Example}}</div>
<div id="URL"><a href="{{URL}}">{{URL}}</a></div>
<!-- ... -->
</div>
</div>
<script>
// ...
</script>

cssは直接カードに定義するか、linkタグでHTMLから読み込み出来ます。

css
/* style.css */
.front {
font-size: 2rem !important;
text-align: center;
}
#definition,
#example,
#URL {
margin: 0.5rem !important;
border: 1px solid;
border-color: var(--border-color);
border-radius: 5px;
padding: 1rem;
}
ul {
padding-left: 1rem;
}
strong {
text-decoration: underline;
}
/* ... */

デモ

result

寄付のお願い

このブログやプロジェクトを継続するために寄付を頂ければ幸いです。


BTC: 1L5r7dDeJfq56UvPA96WCvQSDCfSUmwjT4
1L5r7dDeJfq56UvPA96WCvQSDCfSUmwjT4

XRP: rEb8TK3gBgk5auZkwc6sHnwrGVJH8DuaLh
Destination Tag: 106945877
rEb8TK3gBgk5auZkwc6sHnwrGVJH8DuaLh 106945877