Web Clipperを作ってみた - Dify,Chrome拡張,Firecrawl,GPT-4o mini,Notionを活用
はじめに
Web Clipperを普段の業務や生活で活用している方がいると思います。一方で、Web Clipperをご存じでない方のために説明すると、ウェブページの内容を保存し、整理するためのツールのことを言います。仕事や勉強、研究などの様々なテーマでネットを検索していくと思いますが、テーマに関連する重要記事をブックマーク管理したり、内容を整理して管理できます。Notionやはてなブックマークを活用している方が一定いるのではと推察しています。
今回、Chromeに表示しているWebサイトの情報をNotionへ保存する仕組みを作りたいと思い取り掛かりました。Notionへ保存するWebサイトの情報は「URL」「タイトル」「Webサイトの概要」の3点としました。
この記事はDifyへAPI通信する方法、スクレイピングの方法などのテクニックが記載されており、Difyの開発を考えている方にとってそれなりに参考になるないようかなと思っています!
アーキテクチャ
Chrome拡張機能を作り、ブラウザからクイックにWeb Clipperへ保存できるようにします。今回「Webサイトの概要」をLLMに生成してもらうと考えて、Difyを間に挟んでいます。Difyが受信できる文字列長には制限があるためFirecrawlを活用して連携されたURLをスクレイピングします。スクレイピング結果をLLMへ送り概要を生成してもらいます。そのうえで、「URL」「タイトル」「Webサイトの概要」をNotionへ連携します。
Notionの準備
先日投稿した下記記事がNotion準備で参考になるので、ご参考にしていただけたらと思います。
Firecrawlの準備
下記でユーザ登録を行いAPIキーを発行しましょう。500クレジットまでは無料で使えるようです。
Difyの構築
ワークフローの全体像
下記となります。「開始」で「URL」「タイトル」を受信します。その後、「SCRAPE」が「URL」をもとにスクレイピングをしてWebサイトの内容を取得します。その後「LLM」がWebサイトの内容を元に「Webサイトの概要」を生成します。「HTTPリクエスト」で「URL」「タイトル」「Webサイトの概要」をNotionへ連携します。
DifyのAPI Doc
まずは公式の下記を見ると思いますが、ちょっとほしい情報がありませんでした。
環境変数の設定
Notionへの連携まわりで環境変数を設定していきます。
下記記事に具体的な手順を載せています。
開始の設定
「URL」と「タイトル」を受信できるようにするために、入力フィールドを追加しています。
SCRAPEの設定
「開始」で受信した「URL」を入力変数に設定しています。それ以外はデフォルトです。
SCRAPEデータを変数に格納の設定
SCRAPEの結果を整形しています。というか変数に格納するための処理です。
{{ json[0].data.content|truncate(10000, true, '') }}
APIキーの生成
「APIアクセス」を開いて、「APIキー」をクリックしてAPIキーを生成しましょう。Chrome拡張を使うときに必要です。
LLMの設定
GPT-4o miniを採用しています。スクレイピング結果を元に「Webサイトの概要」を生成します。
下記Webサイトの情報を日本語で200文字で要約してください。
"""
{{#xxx.output#}}
HTTPリクエストのデータ整形の設定
Notionへ連携するためにデータを整形しています。「URL」「タイトル」「Webサイトの概要」を設定しています。
{
"parent": { "database_id": "{{ NOTION_DATABASE_ID }}" },
"properties": {
"Name": {
"title": [
{
"text": {
"content": "{{ title }}"
}
}
]
},
"URL": {
"url": "{{ url }}"
},
"コメント": {
"rich_text": [
{
"text": {
"content": "{{ comment }}"
}
}
]
}
}
}
HTTPリクエストの設定
「HTTPリクエストのデータ整形」で整形したデータをNotionへ連携します。
Chrome拡張
下記3ファイルを準備します。
- manifest.json
- popup.html
- popup.js
{
"manifest_version": 2,
"name": "URL Sender to Dify.ai Workflow",
"version": "1.0",
"description": "Sends the current tab's URL to Dify.ai Workflow API when a button is clicked",
"permissions": [
"activeTab",
"storage",
"https://api.dify.ai/"
],
"browser_action": {
"default_popup": "popup.html"
}
}
<!DOCTYPE html>
<html>
<head>
<title>URL Sender to Dify.ai Workflow</title>
<style>
body { width: 300px; padding: 10px; }
input { width: 100%; margin-bottom: 10px; }
button { width: 100%; margin-bottom: 10px; }
</style>
</head>
<body>
<input type="text" id="apiKey" placeholder="Enter your Dify.ai API Key">
<button id="saveApiKey">Save API Key</button>
<button id="sendUrl">Send URL to Dify.ai Workflow</button>
<div id="status"></div>
<script src="popup.js"></script>
</body>
</html>
document.addEventListener('DOMContentLoaded', function() {
// Load saved API key
chrome.storage.sync.get('difyApiKey', function(data) {
if (data.difyApiKey) {
document.getElementById('apiKey').value = data.difyApiKey;
}
});
// Save API key
document.getElementById('saveApiKey').addEventListener('click', function() {
let apiKey = document.getElementById('apiKey').value;
chrome.storage.sync.set({difyApiKey: apiKey}, function() {
document.getElementById('status').textContent = 'API Key saved';
});
});
// Send URL and Title
document.getElementById('sendUrl').addEventListener('click', function() {
chrome.storage.sync.get('difyApiKey', function(data) {
if (!data.difyApiKey) {
document.getElementById('status').textContent = 'Please save your API Key first';
return;
}
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
let currentUrl = tabs[0].url;
let currentTitle = tabs[0].title;
fetch('https://api.dify.ai/v1/workflows/run', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${data.difyApiKey}`
},
body: JSON.stringify({
inputs: {
url: currentUrl,
title: currentTitle
},
response_mode: "blocking",
user: 'chrome-extension-user'
})
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(result => {
console.log('Workflow result:', result);
document.getElementById('status').textContent = `URL and Title sent successfully. Workflow run ID: ${result.workflow_run_id}`;
})
.catch((error) => {
console.error('Error:', error);
document.getElementById('status').textContent = 'Error sending URL and Title: ' + error.message;
});
});
});
});
});
Chrome拡張として機能追加する
- 作成した3つファイルを一つのディレクトリに保存します。
- Chrome拡張機能の管理ページ(chrome://extensions/)を開きます。
- 「デベロッパーモード」を有効にします。
- 「パッケージ化されていない拡張機能を読み込む」をクリックし、作成したディレクトリを選択します。
使ってみる
GitHubにあるDify v0.7.0のリリースノートをWeb Clipperしてみましょう。
黒塗りはDifyのAPIキーとなります。「Send URL to Dify.ai Workflow」をクリックしました。
結果、無事に下記のようにNotionに「URL」「タイトル」「Webサイトの概要」が連携されて登録されました。
さいごに
いかがでしたか。Dify,Chrome拡張,Firecrawl,GPT-4o mini,Notionを活用してWeb Clipperを作ってみました。すごいのがDifyやLLMを駆使しながら1日もかからずにこういったツールが作れることですよね。ぜひ、この記事を参考にみなさんもアイディアを形にしていただけたら嬉しく思います!
Discussion