一部のサイトの Web スクレイピングを行い、データを PostgreSQL データベースに保存してから、このデータを (D3.js で) 視覚化して Web ページに表示する Node.js アプリケーションを作成したいと考えています。フロントエンド部分 (ビジュアライゼーションの作成と表示) とバックエンド部分 (Web スクレイピングとデータベースの更新) を分割することを考えました。
2つのアプリ(タスクを2つのアプリに分けているので2つあります)の骨格は以下の通りです。
バックエンド アプリ ( scraper
):
- データベースへの接続
- テーブルが存在しない場合の作成
- データのスクレーパー
- データベースにデータを保存する
- データベースからの切断。
このバックエンド アプリケーションは、年に数回だけ起動する必要があります (たとえば、Unix を使用している場合は、これを行うために CRON ファイルを構成できます)。
フロントエンド アプリ ( viz
):
- データベースへの接続
- ポート 3000 で待機しているサーバーを起動します (可視化のために必要です)
- ユーザーがページを更新するたびに (
onLoad()
)、アプリはSELECT
データベースからデータを取得するクエリを作成します ( )。このようにして、データは常に更新されます。
このアプリケーションは、プログラマーによって (理想的には) 1 回だけ開始されます。
このタイプのフォルダー構造を作成しました (私は と を使用npm init
しましたExpress
):
project
|_ scraper
|_ helpers // contains some useful .js files
|_ elaborateJson.js
|_ saveOnDb.js
|_ utilFunc.js
|_ node_modules // modules installed using `npm install moduleName --save`
|_ routes // contains the files that make scraping
|_ downloaderHome.js
|_ downloaderWork.js
|_ services // contains a files concerning the db
|_ postgreSQLlib.js
|_ app.js
|_ package.json
|_ package-lock.json
|_ viz
|_ helpers // // contains some useful .js files
|_ utilFunc.js
|_ node_modules // modules installed using `npm install moduleName --save`
|_ public // contains files for visualizations
|_ index.handlebars
|_ script.js
|_ style.css
|_ services // contains a file concerning the db
|_ postgreSQLlib.js
|_ app.js
|_ package.json
|_ package-lock.json
この構造では、解決方法がわからない2つの問題がすでにあります。
1.postgreSQLlib.js
ファイル (およびutilFunc.js
) は と の両方scraper
で同じviz
です。このコードの重複を避けるにはどうすればよいですか?
2. 一部のモジュール (たとえばexpress-handlebars
and )を andフォルダーexpress
に 2 回インストールする必要がありました。scraper
viz
これはproject/scraper/app.js
次のとおりです。
const downloaderHome = require('./routes/downloaderHome.js');
const downloaderWork = require('./routes/downloaderWork.js');
const postgreSQLlib = require('./services/postgreSQLlib.js');
const saveOnDb = require('./helpers/saveOnDb.js');
const utilFunc = require('./helpers/utilFunc.js');
const express = require('express');
const exphbs = require('express-handlebars');
var app = express();
start();
async function start() {
console.log('\n Connect to db');
await postgreSQLlib.connect();
console.log('\n Create tables if they do not exist');
await postgreSQLlib.createHomeTable();
await postgreSQLlib.createWorkTable();
console.log('\n Check if table \'home\' is updated or not');
if(!await utilFunc.isTableUpdated('home', 6418)) { // 6308
console.log('\n Download data for home');
await downloaderHome.download();
console.log('\n Saving data for home on db');
await saveOnDb.saveHome();
}
console.log('\n Check if table \'work\' is updated or not');
if(!await utilFunc.isTableUpdated('work', 6804)) {
console.log('\n Download data for work');
await downloaderWork.download();
console.log('\n Saving data for work on db');
await saveOnDb.saveWork();
}
console.log('\n Disconnect from db');
await postgreSQLlib.disconnect();
}
これはproject/viz/app.js
次のとおりです。
const postgreSQLlib = require('./services/postgreSQLlib.js');
const utilFunc = require('./helpers/utilFunc.js');
const express = require('express');
const exphbs = require('express-handlebars');
const http = require('http');
var app = express();
var response;
var callback;
start();
async function start() {
console.log('\n Connect to db');
await postgreSQLlib.connect();
// how do I check when page is refreshed?!
http.get({
hostname: 'localhost',
port: 3000,
path: '/',
agent: false
}, callback);
callback = function(res) {
response = res;
console.log(response); // here response will return an object
console.log('refresh callback');
}
console.log(response);
console.log('refresh');
///////////////////////////////////////////////
// How do I check the disconnection from the db?
// If I disconnect now, the visualizations are no longer work.
// So when do I get disconnected?
// Create problems leaving the connection to the active db?
///////////////////////////////////////////////
//console.log('\n Disconnect from db');
//await postgreSQLlib.disconnect();
}
最初のアプリケーション ( project/scraper/app.js
) は完全に機能します。
第二の申請(project/viz/app.js
)番号。私はあなたにこれをしてもらいたい:
- データベースへの接続[完了。できます]
- ポート 3000 で待機しているサーバーを起動します (視覚化のために必要です) [どうすればよいですか? 下を向いて(*) ]
- ユーザーがページを更新するたびに (
onLoad()
)、アプリはSELECT
データベースからデータを取得するクエリ ( ) を作成します [どうすればいいですか?]
(*)私は次のようなことを考えていました:
async function start() {
console.log('\n Connect to db');
await postgreSQLlib.connect();
console.log('\n Get data from db');
var dataHome = await postgreSQLlib.getTableHome();
var dataWork = await postgreSQLlib.getTableWork();
//console.log('\n Connect to my server');
pageLoad(dataHome, dataWork);
}
function pageLoad(dataHome, dataWork) {
var hbs = exphbs.create({
helpers: {
getDataHome: function() {
return JSON.stringify(dataHome);
},
getDataWork: function() {
return JSON.stringify(dataWork);
}
}
});
app.engine('handlebars', hbs.engine);
app.set('view engine', 'handlebars');
app.get('/', function(req, res, next) {
res.render('index', { // index is html filename
showTitle: true,
});
});
console.log('Go to http://localhost:3000/ to see visualizations');
app.listen(3000);
}
dataHome
とは、クエリdataWork
を使用してデータベースからダウンロードされたデータを含む 2 つのオブジェクトですSELECT
。ただし、この方法では、ユーザーがページを更新するたびにデータが破棄されるわけではなく、1 回だけ破棄されます。
助けていただければ幸いです。ありがとうございました!
編集
もっと正確に言えますか?私はそれをやろうとしましたが、うまくいきません:
project/viz/app.js :
const postgreSQLlib = require('../shared_libs/postgreSQLlib.js');
const express = require('express');
var app = express();
start();
async function start() {
console.log('Connect to db');
await postgreSQLlib.connect();
app.get('/', fetchFreshData);
}
async function fetchFreshData(req, res) {
// download data from db
var dataHome = await postgreSQLlib.getTableHome();
var dataWork = await postgreSQLlib.getTableWork();
// fill this JSON using the results
var viewData = {dataHome, dataWork};
// pass data to view
res.render('index', viewData);
}
project\viz\view\index.handlebars :
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<title>Map</title>
<script src='https://d3js.org/d3.v5.js' charset='utf-8'></script>
<link rel='stylesheet' type='text/css' href='/style.css' media='screen'/>
</head>
<body>
<div id='example'></div>
</body>
<script src='/script.js'></script>
</html>
project\viz\view\script.js :
console.log('viewData:', viewData);
どこが間違っていますか?
編集2
わかりました、viz/app.js
コードを再度変更します。
const postgreSQLlib = require('../shared_libs/postgreSQLlib.js');
const express = require('express');
const exphbs = require('express-handlebars');
var app = express();
start();
async function start() {
await postgreSQLlib.connect();
var hbs = Handlebars.registerHelper('json', function(context) {
return JSON.stringify(context);
});
app.engine('handlebars', hbs.engine);
app.set('view engine', 'handlebars');
app.get('/', fetchFreshData);
console.log('Go to http://localhost:3000/ to see data');
app.listen(3000);
}
async function fetchFreshData(req, res) {
// download data from db
var dataHome = await postgreSQLlib.getTableHome();
var dataWork = await postgreSQLlib.getTableWork();
// fill this JSON using the results
var viewData = {};
viewData.timestamp = Date.now();
viewData.entries = dataHome;
// pass data to view
res.render('index', viewData);
}
アプリを実行するとエラーは発生しませんが、http://localhost:3000/に接続すると、ブラウザーはサイトにアクセスできないと通知します。ちょっとバカな気がする…
編集3
私があなたのコードを正しく理解していれば、あなたのコードには (気を散らすような) エラーがあります。returnOBJ()
代わりに(ファイルに関連する)にres.render('index', viewData);
する必要があります。右?res.render('obj', viewData);
obj.hbs
この方法で index.hbs ファイルを変更します。
<html lang='en'>
<head>
<meta charset='utf-8'>
<title>Index</title>
<script src='https://d3js.org/d3.v5.js' charset='utf-8'></script>
<link rel='stylesheet' type='text/css' href='/style.css' media='screen'/>
</head>
<body>
<h1>INDEX<small>{{timestamp}}</small></h1>
</body>
<script>
// add global variables in the .hbs file
window.viewData_dataWork = {{ json entries }}
console.log(window.viewData);
</script>
<script src='/script.js'></script>
</html>
しかし、私は得る:
(node:207156) UnhandledPromiseRejectionWarning: Error: callback function required
at Function.engine (C:\...\node_modules\express\lib\application.js:295:11)
at start (C:\...\viz\app.js:20:6)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:182:7)
(node:207156) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:207156) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
このコードもわかりません。
app.set('view engine', 'hbs');
app.engine('hbs', hbs.__express);
hbs.registerHelper('json', function(context) {
return JSON.stringify(context);
});
app.engine('handlebars', hbs.engine);
app.set('view engine', 'handlebars');
app.set('view engine', ...)
異なる値で 2 回呼び出すのはなぜですか?
編集4
コードをさらに単純化しました。
/viz/app.js :
const postgreSQLlib = require(__dirname + './../shared_libs/services/postgreSQLlib.js');
const express = require('express');
const hbs = require('hbs');
var app = express();
// Server initiator
async function start() {
await postgreSQLlib.connect();
// hbs
app.set('views', '' + __dirname + '/views');
app.set('view engine', 'hbs');
app.engine('hbs', hbs.__express);
hbs.registerHelper('json', function(context) {
return JSON.stringify(context);
});
app.engine('handlebars', hbs.engine);
app.set('view engine', 'handlebars');
// router
app.get('/', testMe);
console.log('Go to http://localhost:3000/ to see data');
app.listen(3000);
}
// Your section with fresh data has been populated properly
async function testMe(req, res) {
console.log('testMe');
// fill this JSON using the results
var viewData = {};
viewData.data = 'this string';
// pass data to view
res.render('test', viewData);
}
// start the server
start();
/viz/views/test.hbs :
<html>
<head>
<title>Server test</title>
</head>
<body>
{{data}}
</body>
</html>
次に、プロンプト コマンドで に移動しproject/viz
、node app.js
+ Enter と入力します。プロセスが開始され、待機します。エラーはありません。http://localhost:3000/
に行くとConnection failed が表示されます。
私は夢中になっています。
編集5
問題はconnect
選択を行った関数でもなかったので、コードを少し単純化しました。そして今、それはほとんど動作します!
これがコードです。
viz/app.js :
const postgreSQLlib = require(__dirname + './../shared_libs/services/postgreSQLlib.js');
const express = require('express');
var app = express()
const hbs = require('hbs');
const webapp_opts = {"port":3000};
Initialize();
//.: Setup & Start Server
async function Initialize(){
await postgreSQLlib.connect();
console.log("[~] starting ...")
//:[HBS]:Setup
app.set('view engine', 'hbs');
app.engine('hbs', hbs.__express)
app.set('views', "" + __dirname + "/views")
//:[HBS]:Helpers
hbs.registerHelper('json', function(context) {
return JSON.stringify(context);
})
//:[EXPRESS]:Router.Paths
app.get("/", IndexPathFunction);
// app.get("/script.js", scriptFile); <-- for script.js file
//:[EXPRESS]:Start
app.listen(webapp_opts.port,()=>{
console.log("[i] ready & listening","\n http://localhost:"+webapp_opts.port+"/")
})
}
/*async function scriptFile(req, res) { <-- for script.js file
console.log('\nscriptFile');
var viewData = {};
viewData.number = 50;
console.log('viewData:', viewData);
res.render('script.js', viewData);
}*/
//.: Router Function : "/"
async function IndexPathFunction(req,res){
var viewData = {};
viewData.timestamp = Date.now();
viewData.exJson = [{color: 'red', year: '1955'}, {color: 'blue', year: '2000'}, {color: 'yellow', year: '2013'}];
viewData.exString = 'example of string';
console.log('viewData:', viewData);
res.render('index', viewData);
}
viz/views/index.hbs :
<html lang='en'>
<head>
<meta charset='utf-8'>
<title>Index</title>
<script src='https://d3js.org/d3.v5.js' charset='utf-8'></script>
<link rel='stylesheet' type='text/css' href='/style.css' media='screen'/>
</head>
<body>
<h1>INDEX timestamp: <small>{{timestamp}}</small></h1>
</body>
<script>
viewData = {};
console.log('viewData:', viewData);
viewData.exJson = JSON.parse('{{ json exJson }}'.replace(/"/g, '"').replace(/</, ''));
viewData.timestamp = {{timestamp}}; // doesn't work
viewData.exString = {{ exString }}; // doesn't work
console.log('viewData.exJson:', viewData.exJson);
console.log('viewData.timestamp:', viewData.timestamp);
console.log('viewData.exString:', viewData.exString);
</script>
<!--<script src='/script.js'></script>-->
</html>
問題は、json ではないデータ型を取得することです。たとえば、タイムスタンプと exString を出力しようとするとエラーが発生します。なんで?
また、コードを少しクリーンアップして、javascript の部分をを使用してscript.js
呼び出されるファイル内に配置したいと思います。index.hbs
<script src='/script.js'></script>
編集6
私にとって非常に役立つこのチュートリアルを見つけました。
index.hbs
cssファイル、画像、スクリプトを追加してファイルを編集しました(これにはaのみが含まれていますconsole.log('here');
が、script.jsにviewData
変数を配置することを考えています)。
project/viz/views/index.hbs :
<html lang='en'>
<head>
<meta charset='utf-8'>
<title>Index</title>
<script src='https://d3js.org/d3.v5.js' charset='utf-8'></script>
<link href="/css/style.css" rel="stylesheet">
</head>
<body>
<img src="/images/logo.png"/>
<h1>timestamp: <small>{{timestamp}}</small></h1>
<h2>Welcome in index.hbs</h2>
</body>
<script>
viewData = {};
console.log('viewData:', viewData);
viewData.exJson = JSON.parse('{{json exJson }}'.replace(/"/g, '"').replace(/</, ''));
viewData.timestamp = {{timestamp}};
viewData.exString = '{{exString}}';
console.log('viewData.exJson:', viewData.exJson);
console.log('viewData.timestamp:', viewData.timestamp);
console.log('viewData.exString:', viewData.exString);
</script>
<link href='/script/script.js' rel='script'>
</html>
私のファイル構造は次のとおりです。
project
|_ node_modules
|_ scraper
|_ shared_libs
|_ viz
|_ app.js
|_ public
|_ css
|_ style.css
|_ images
|_ logo.png
|_ script
|_ script.js
|_ views
|_ index.hbs
これで画像が表示され、css が使用されます。しかし、ここに文字列が出力されていないため、スクリプトは機能していないようです。
スクリプトタグから外部jsファイルに変数を渡す方法についてインターネットで検索しましたが、自分に合ったものが見つからなかったようです。ハンドルバー API を読みましたが、役に立ちませんでした。