Notion + Google Driveで「映え」な文献管理
院生など、研究をしてると論文が溜まってくるので文献管理ツールが必須ですよね。私はMendeleyを使っているのですが、研究のログや考察にはNotionを使っているので、文献もNotionでうまいこと一元管理できたらいいのになーと考えていました。そこで今回は主に、研究でNotionをヘビーに使われている方や、Notionで既に文献管理をされている方向けに、文献管理の一例 ↓をご紹介します。
実装の一例を早く見たい方は [本題] PaperpileとGAS + Notion APIで実装する、までスキップしてください。
文献管理ツールに求める機能
私が特に求めているのは、以下の4つの機能です。
文献の管理と検索
文献のメタ情報(e.g. タイトル・著者・出版年・ジャーナル)の自動取得
文献のPDF保管(ストレージ)と閲覧・コメント(PDFビューワー)
文献内容のメモ(エディタ)
仮にNotionで1~4を満たそうとすると、1・4に関してはNotionの機能であるデータベース・検索機能・エディタが使えます。
一般に「Notionで文献管理」というと2・3は手動で補うことが想定されていると思いますが、手動でメタ情報を入力したり、PDFをページと紐づけたりするのは少々面倒です。そこで、2: メタ情報の取得をMendeleyやPaperpileといった既存の文献管理ツールで補い、3: ストレージ & PDFビューワーをGoogle Driveにやってもらいます。
さらに、Google Driveに保管したPDFをNotionのページと紐付けることが要求されます。これは簡易的にはノーコードツールのZapierを使ってできますが、無料版では機能が限定的です。フルにカスタマイズするには、Google Apps Script (GAS) でNotion APIを叩き、データベースの属性を自分好みにデザインするという方式が良いように思います。
フローとしては次のような形です。
実装の例
Notionで1〜4を実現するにあたり、以下の二つのステップでツールの選択肢があります。
私が採用したのは、PaperpileとGAS + Notion APIの組み合わせです。最も簡易的かつ無料にやるのであれば、Mendeley + Zapierになると思います。
文献管理ツールの比較
Mendeley
Mendeley Desktopの機能である、Watch Folderが使えます。論文PDFをダウンロードしたらWatch Folderに移し、ファイルが文献情報に基づいてリネームされます。Watch FolderをGoogle Driveと同期させておけば ↓、Google Driveに入った新規PDFをNotionに反映することができます。
ただし、Mendeley Desktopは新規インストールが2022年8月31日に終了したことから、Watch Folderの機能がいつまで残ってくれるか分かりません。Web版に移行していく流れがあるため、中長期的には検討が必要です。
Paperpile
30日間の無料トライアル以降は有料 (月額コーヒー一杯ぐらい) ですが、軽量でかつGoogleと非常に相性が良い文献管理ツールです。特に便利なのが、Chromeの拡張機能です。まず、Google Scholar上でライブラリに既に登録している論文が確認できます。また、ワンクリックするだけで、自動でPDFをダウンロードしに行ってくれてライブラリに登録してくれます (プログラムで制御されたChrome Driverが探しに行っていると思われます)。
Google Driveに自動でPDFを同期することができ、フォルダ構成もMendeleyと比べると自由度が高いです。
DriveとNotionの同期ツール
Zapier
「Driveの新規ファイルを検知して、Notion Databaseに反映」までのワークフローがノーコードで簡単に実装できます。Zapierではイベント発生のトリガーを決め、トリガーに対してアクションを設定できます。
Zapierの無料版は、1ステップまでしか使えません。つまり、トリガーに対して単一のアクションまでしか出せないということです。今回の場合、Notion Databaseにページを追加するというアクションが唯一のステップになります。この場合、リネームされたPDFの名前がそのままページのタイトルになるため、メタ情報をデータベースの属性に割り当てることはできません。
ここからは有料版が必要ですが、データベースの属性にメタ情報を反映するには、リネームされたPDFの名前から文献のメタ情報(タイトル・ジャーナル・著者・発行年)を抽出する必要があります。例えば、論文PDFを次のルールでリネームしたとします。
`[Journal]_[FirstAuthor]_[LastAuthor]_[year]_[Title].pdf`
アンダースコアで連結されたメタ情報を抽出するには、Formatter by Zapierが使えます。
マルチステップが使えると、とても便利なのですが、有料版はやや費用がかさむのが痛いです。
Google Apps Script (GAS)
無料で使える代わりにコーディングの必要がありますが、今回掲載するコードのほぼコピペで動くと思います。
Google Driveの特定フォルダを監視(watch)して、新規ファイルを検知するためのロジック設計が必要です。今回は、1分ごとにフォルダ内のファイル一覧を取得し、新規PDFをNotionに転送、転送済みのPDFにはファイルのDescriptionに"DONE"と書いておく、というロジックにしています*。
PDFのアップロードを検知したら、Zapierの場合と同じように文献情報に基づいてリネームされたPDFのファイル名を分解し、Notion APIを通じてデータベースを更新します。
[本題] PaperpileとGAS + Notion APIで実装する
長い前置きでしたが、実際に実装してみます。
1. Paperpile + Google Driveの設定
Paperpile右上のGoogle Driveアイコンをクリックし、Configureを開いてください。Advancedトグルを開き、ファイルパターンをよしなに変更します。
後述のGASコードの中で、このファイルパターンを分解してメタ情報を抽出します(ハイフンは稀にタイトルにも出現するため、アンダースコアでの連結がオススメです)。
ただし、後述のGASコードはサブフォルダに対応したロジックになっていないため、必ずPDFの一覧は単一フォルダの直下に配置するようにしてください(ファイルパターンには/を含めない)。
2. Notionの設定
任意のページにデータベースを作り、自分が行いたい文献管理の形式にテーブルの属性を整えます。私は次のように設計しました。
次に、Notion APIを使うための事前準備を行います。次のnoteが大変参考になります。上の手順で作った文献のデータベースに対して、「コネクトの追加」から作成したIntegrationを忘れずに追加してください(追加しないと、APIを叩いた際にエラーが出ます)。
3. GASの設定
Google Driveの任意のページ(My Drive直下など)で右クリック > More > Google Apps Scriptを選択してください。
エディタ画面で、以下のコードをコピペしてください。
// フォルダのID, NotionデータベースのID, Notion APIのトークンを設定
const PROPS = PropertiesService.getScriptProperties();
const FOLDER_ID = PROPS.getProperty('FOLDER_ID');
const DB_ID = PROPS.getProperty('NOTION_DB_ID');
const TOKEN = PROPS.getProperty('NOTION_TOKEN');
// すでに送信あるいは失敗したPDFをマークする
// 送信に失敗したPDFはタイトル形式に問題があるためリトライはしない
const STATUS = {
DONE: 'DONE',
ERROR: 'ERROR',
}
// 実行関数
function myFunction() {
const files = getFiles()
scanAndSendFiles(files);
}
// IDで指定されたフォルダ内のファイル一覧を取得
function getFiles() {
const targetFolder = DriveApp.getFolderById(FOLDER_ID);
return targetFolder.getFiles();
}
// ファイルを走査し、送信されていないファイルはNotion API経由で送信
function scanAndSendFiles(files, nMax=500) {
let count = 0;
while (files.hasNext()) {
if (count == nMax) {
break;
}
let file = files.next();
const status = file.getDescription();
if (status == STATUS.DONE || status == STATUS.ERROR) {
continue;
}
const filename = file.getName();
const url = file.getUrl();
const thumbnailURL = getThumbnailUrl(file.getId());
// EDIT HERE: ファイルリネーム設定にしたがって設定してください //
const [journal, firstAuthor, lastAuthor, year, title] = filename.split('_');
// EDIT HERE //
try {
// EDIT HERE: Notionに送信する文献のメタデータにしたがって編集してください //
const result = {
title: title.split('.pdf')[0],
url: url,
journal: journal,
firstAuthor: firstAuthor,
lastAuthor: lastAuthor,
year: year,
thumbnailURL: thumbnailURL,
};
// EDIT HERE //
send2Notion(result);
file.setDescription(STATUS.DONE);
Logger.log(`Succeeded: ${filename}`);
count ++;
}
catch (e) {
file.setDescription(STATUS.ERROR);
Logger.log(`Failed: ${filename} with message ${e.message}`);
}
}
}
// カバー写真用にサムネイル画像のURLを取得
// 表示を成功させるにはNotionのGoogleアカウントと一致している必要があるかも
function getThumbnailUrl(fileId, width=1600, authuser=0){
return `https://lh3.googleusercontent.com/d/${fileId}=w${width}?authuser=${authuser}`;
}
// 文献情報をNotionに送信
function send2Notion(result) {
const apiUrl = 'https://api.notion.com/v1/pages';
const obj = generateObj(result);
const options = {
method: "POST",
headers: {
"Content-type": "application/json",
"Authorization": "Bearer " + TOKEN,
"Notion-Version": '2021-08-16',
},
payload: JSON.stringify(obj),
};
UrlFetchApp.fetch(apiUrl, options);
}
// ページの設定
function generateObj(result) {
const pageObj = {
parent: {
database_id: DB_ID,
},
cover: {
"type": "external",
"external": {
"url": result.thumbnailURL
}
},
// EDIT HERE: データベースの属性にしたがって編集してください //
properties: {
"Name": {
"title": [{
"text": {
"content": result.title
}
}]
},
"URL": {
"url": result.url
},
"Author": {
"multi_select": [
{
"name": result.firstAuthor,
},
{
"name": result.lastAuthor,
}
]
},
"Year": {
"type": "number",
"number": parseInt(result.year),
},
"Journal": {
"type": "select",
"select": {
"name": (result.journal.length > 0) ? result.journal: 'Others'
}
}
}
// EDIT HERE //
}
return pageObj;
}
次で囲まれた部分(3ヶ所)を、設計したい文献データベースの属性に基づいて変更します。
// EDIT HERE //
ここを編集してください
// EDIT HERE //
上記のコードでNotion APIを叩く部分はこちらのnoteを参考にさせていただきました(ありがとうございます!)。
データベースの属性の設定方法は、詳しくはこちらの公式ドキュメントを参照ください。
次に、Notion APIのトークン、NotionデータベースのID、Google DriveフォルダのIDをスクリプトプロパティから登録します。
Google DriveのIDは、フォルダのURLの` です。Paperpile上のPDFをバックアップしている任意のDriveフォルダを指定してください(ただしStarredに限ってはPDFがエイリアス機能で置かれる仕様になっているため、リンクによる紐付けが失敗します)。
NotionデータベースのIDは、データベースのURLの` です。
Notion Tokenは、先ほど作成したNotion Integrationのページから取得できます。
さて、スクリプトプロパティを保存したら、指定のフォルダにPDFがいくつか置いてあることを確認して、関数を実行してみてください。
Google Driveへのアクセスを許可してよいか確認する認証画面が出ます。コードの内容に問題がなければ、Advancedを開いて、Go to {プロジェクトの名前} (unsafe)をクリックし、Allowしてください。
4. 実行結果
うまくいくと、Driveに置いてある文献一覧がNotionデータベースに反映されます。エラーが出る場合、Notionデータベースのコネクト設定(Integrationの許可)、リネームされた論文PDFのタイトルとスクリプトの整合性などを確かめてみてください。
ちなみに、Notion APIでページの内部にPDFを埋め込むことも可能ですが、API経由では埋め込みブロックの幅・高さを指定できないのが痒いところです。見やすくするためには、手動で広げる必要があります。
うまく実行できていそうだったら、定期実行のトリガーを設定しましょう。
左タブのTriggerから、「毎分」を選択してください。ファイル数が数100枚単位など多い場合は、5分に1回とかの方が安全かもしれません。ただ、仮に実行が複数重なっても重複送信などの問題は発生しないはずです…たぶん。
使用感
実際に「Notion文献管理」を作って触ってみた感想です。
私はMendeleyにあった1500件ぐらいの論文をPaperpileにImportして、PDFをGoogle Driveに同期しました。PDFはざっと一枚1MBぐらいと見積もっても、Google Driveはフリーで15GBあるので、論文が1万件ぐらいに達しない限りはストレージの心配はなさそうです。
Notionは、データベースのビューを何種類か用意しておけば切替が簡単です。ジャーナルごとに表示したり、ステータスごとに表示したり、タグごとに表示したり、などの応用がかなり効くのは良いところです。
ただ、やはり論文数が多いと、タグだけだと視認性が悪くて、フォルダのようなファイルシステム的な仕組みも並行して必要だなと思います。DriveからGASでファイルの一覧を取得するロジックを、任意階層のサブフォルダに適用できるように一般化して、Notionでは別々のページにデータベースを作る、とか色々やりようがありそうです。GASのコーディングによって応用の幅が広いのは良いところです。
それよりも実用的なNotionの活用としては、「精読する論文だけ」を自動で同期することでしょうか。例えば、Paperpile上で「Notionフォルダ」を作っておき、PaperpileのGoogle Drive設定は次のようにしておきます。
こうすれば、DriveのPaperpileフォルダ下に「Notion」フォルダが出来上がるので、このフォルダのIDをGASで指定すれば、普段使いはPaperpileだけど、精読するときだけNotionに同期してそこでメモ、みたいなスタイルで使えそうです。
まとめ
今回はGoogle Driveと同期したNotionで文献管理を行うためのアイデアをご紹介しました。Notion APIはまだ公開されたばかりで、今後API経由での埋め込みやテンプレート適用の自由度が上がっていくと楽しいでしょうし、他にも良いアイデアが思いついたら試してみたいなと思います。
この記事が気に入ったらサポートをしてみませんか?