Я хочу создать приложение Node.js, которое выполняет веб-очистку некоторых сайтов, сохраняет данные в базе данных PostgreSQL, а затем отображает визуализации (в D3.js) этих данных на веб-странице.
Я думал о разделении входной части (создание и отображение визуализаций) и внутренней части (выполнение очистки и обновление базы данных в Интернете).
Скелет двух приложений (их два, потому что я делю задачи на два приложения) выглядит следующим образом.
Бэк-приложение (scraper
):
- подключение к БД
- создание таблиц, если они не существуют
- скребок данных
- сохранение данных на БД
- отключение от БД.
Это фоновое приложение должно запускаться только пару раз в год (для этого я могу настроить файл CRON, если, например, используется Unix).
Приложение переднего плана (viz
):
- подключение к БД
- запустить сервер, который ожидает на порту 3000 (он мне нужен для визуализации)
- каждый раз, когда пользователь обновляет страницу (
onLoad()
), приложение выполняет запрос (SELECT
), который получает данные из базы данных. Таким образом, данные всегда обновляются.
Это приложение запускается программистом только один раз (в идеале).
Я создал структуру папок этого типа (я использовал 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
С этой структурой у меня уже есть две проблемы, которые я не знаю, как решить:
1. Файл postgreSQLlib.js
(а также utilFunc.js
) одинаков как для scraper
, так и viz
. Как я могу избежать этого дублирования кода?
2. Мне пришлось дважды установить некоторые модули (например, express-handlebars
и express
) в папки 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
- два объекта, которые содержат данные, загруженные из базы данных с использованием запроса SELECT
.
Но при этом данные удаляются только один раз, а не каждый раз, когда пользователь обновляет страницу.
Помощь будет принята с благодарностью. Спасибо!
EDIT
Не могли бы вы быть более точным? Я пытался сделать это, но это не работает:
проект / именно / 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);
}
проект \ а именно \ вид \ 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>
проект \ а именно \ вид \ 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', ...)
два раза с разными значениями?
РЕДАКТИРОВАТЬ 4
Я еще больше упростил код:
/ а именно / 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();
/ ВИЗ / просмотров / test.hbs
<html>
<head>
<title>Server test</title>
</head>
<body>
{{data}}
</body>
</html>
Затем в командной строке я иду на project/viz
и набираю node app.js
+ enter.
Процесс начинается и ждет: ошибок нет.
Когда я иду на http://localhost:3000/
, но я получаю Ошибка подключения .
Я схожу с ума.
РЕДАКТИРОВАТЬ 5
Проблема была не в connect
и не в функциях, которые сделали выбор, поэтому я немного упростил код.
и теперь это работает почти!
Вот код.
а именно / 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);
}
ВИЗ / просмотров / 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, изображение и скрипт (он содержит только console.log('here');
, но идея состоит в том, чтобы поместить в script.js переменную viewData
).
проекта / а именно / просмотров / 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 руля, и они не были полезны.