Node.js技术咨询:Express路由渲染与爬虫可视化前后端架构实现
解决方案:Node.js 双场景实现指南(路由渲染 + 爬虫可视化)
针对你提出的两个Node.js相关需求,我整理了详细的实现方案,分模块拆解如下:
一、Express + Handlebars 实现数据查询与视图渲染
这个需求核心是打通「数据库查询 → 路由转发 → 模板渲染」的链路,咱们一步步来:
1. 项目初始化与依赖安装
先创建项目并安装必要依赖:
mkdir express-data-view && cd express-data-view npm init -y npm install express express-handlebars pg
2. 项目结构规划
建议按模块拆分,方便维护:
express-data-view/ ├── src/ │ ├── db/ # 数据库连接模块 │ │ └── connection.js │ ├── routes/ # 路由逻辑 │ │ └── dataRoutes.js │ ├── views/ # Handlebars模板 │ │ ├── layouts/ │ │ │ └── main.hbs # 基础布局模板 │ │ └── template.hbs # 数据展示模板 │ └── app.js # Express主入口 └── package.json
3. 数据库连接配置(db/connection.js)
用pg模块连接PostgreSQL,记得替换成你的数据库信息:
const { Pool } = require('pg'); const pool = new Pool({ user: '你的数据库用户名', host: 'localhost', database: '你的数据库名', password: '你的数据库密码', port: 5432, // 默认PostgreSQL端口 }); module.exports = pool;
4. 路由逻辑实现(routes/dataRoutes.js)
编写路由处理数据库查询,并把数据传给模板:
const express = require('express'); const router = express.Router(); const pool = require('../db/connection'); // 定义数据展示路由 router.get('/show-data', async (req, res) => { try { // 从数据库查询数据(替换成你的表名和字段) const queryResult = await pool.query('SELECT id, title, content FROM your_table'); // 渲染template.hbs模板,传入查询到的数据 res.render('template', { dataList: queryResult.rows }); } catch (error) { console.error('数据库查询失败:', error); res.status(500).send('服务器内部错误'); } }); module.exports = router;
5. Express主应用配置(src/app.js)
配置模板引擎并挂载路由:
const express = require('express'); const { engine } = require('express-handlebars'); const dataRoutes = require('./routes/dataRoutes'); const app = express(); const PORT = 3000; // 配置Handlebars模板引擎 app.engine('hbs', engine({ extname: '.hbs' })); app.set('view engine', 'hbs'); app.set('views', './src/views'); // 挂载路由 app.use('/', dataRoutes); // 启动服务 app.listen(PORT, () => { console.log(`Express服务已启动:http://localhost:${PORT}/show-data`); });
6. Handlebars模板渲染(views/template.hbs)
编写模板展示数据,这里用列表举例:
<h1>数据库数据展示</h1> <div class="data-container"> {{#each dataList}} <div class="data-item"> <h3>{{title}}</h3> <p>{{content}}</p> <span>ID: {{id}}</span> </div> {{else}} <p>暂无数据</p> {{/each}} </div>
二、前后端拆分的爬虫+PostgreSQL+D3.js 可视化架构
这个需求拆分为后端爬虫服务(负责爬取、存储数据)和前端可视化应用(负责用D3.js展示数据),两个应用独立运行:
1. 后端爬虫服务实现
1.1 初始化与依赖安装
mkdir crawler-service && cd crawler-service npm init -y npm install axios cheerio pg node-schedule cors
axios:发送HTTP请求爬取网页cheerio:解析HTML结构pg:连接PostgreSQLnode-schedule:定时执行爬取任务cors:允许前端跨域请求数据
1.2 项目结构
crawler-service/ ├── src/ │ ├── db/ │ │ └── connection.js # 数据库连接(和之前的配置一致) │ ├── crawler/ │ │ └── scraper.js # 网页爬取逻辑 │ ├── services/ │ │ └── dataService.js # 数据存储逻辑 │ └── app.js # 主入口(含API接口+定时任务) └── package.json
1.3 网页爬取逻辑(crawler/scraper.js)
替换成你要爬取的目标网站和元素选择器:
const axios = require('axios'); const cheerio = require('cheerio'); async function scrapeTargetSite() { try { // 发送请求获取网页内容 const response = await axios.get('https://目标网站地址.com'); const $ = cheerio.load(response.data); // 解析网页,提取需要的数据 const scrapedData = []; $('.目标元素选择器').each((index, element) => { const itemName = $(element).find('.名称选择器').text().trim(); const itemValue = $(element).find('.数值选择器').text().trim(); scrapedData.push({ name: itemName, value: itemValue }); }); return scrapedData; } catch (error) { console.error('爬取失败:', error); return []; } } module.exports = { scrapeTargetSite };
1.4 数据存储逻辑(services/dataService.js)
把爬取到的数据存入PostgreSQL:
const pool = require('../db/connection'); async function saveScrapedData(data) { try { // 可选:清空旧数据(根据业务需求决定) await pool.query('TRUNCATE TABLE scraped_data RESTART IDENTITY'); // 批量插入新数据 const insertQuery = ` INSERT INTO scraped_data (item_name, item_value) VALUES ($1, $2) `; for (const item of data) { await pool.query(insertQuery, [item.name, item.value]); } console.log('数据存储成功,共插入', data.length, '条数据'); } catch (error) { console.error('数据存储失败:', error); } } module.exports = { saveScrapedData };
1.5 主入口:API接口+定时任务(src/app.js)
启动服务,提供数据接口,并配置定时爬取:
const express = require('express'); const cors = require('cors'); const { scrapeTargetSite } = require('./crawler/scraper'); const { saveScrapedData } = require('./services/dataService'); const schedule = require('node-schedule'); const pool = require('./db/connection'); const app = express(); const PORT = 3001; // 允许跨域请求 app.use(cors()); // 提供数据API接口,给前端可视化用 app.get('/api/scraped-data', async (req, res) => { try { const result = await pool.query('SELECT * FROM scraped_data'); res.json(result.rows); } catch (error) { console.error('获取数据失败:', error); res.status(500).json({ error: '服务器内部错误' }); } }); // 执行爬取并存储的函数 async function runCrawler() { const data = await scrapeTargetSite(); if (data.length > 0) { await saveScrapedData(data); } } // 启动时立即执行一次爬取 runCrawler(); // 定时任务:每天凌晨3点自动爬取更新数据 schedule.scheduleJob('0 0 3 * * *', async () => { console.log('开始定时爬取更新...'); await runCrawler(); }); // 启动服务 app.listen(PORT, () => { console.log(`爬虫服务已启动:http://localhost:${PORT}`); });
2. 前端可视化应用实现
前端是独立的静态应用,用D3.js从后端API获取数据并渲染可视化图表:
2.1 项目初始化
mkdir visualization-frontend && cd visualization-frontend npm init -y # 可选:用Vite等构建工具,或者直接用静态HTML
2.2 可视化页面(index.html)
这里以柱状图为例,替换成你需要的可视化类型:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>数据可视化展示</title> <script src="https://d3js.org/d3.v7.min.js"></script> <style> #chart-container { width: 900px; margin: 20px auto; } </style> </head> <body> <div id="chart-container"> <h2>爬取数据可视化</h2> <div id="bar-chart"></div> </div> <script> // 从后端API获取数据 d3.json('http://localhost:3001/api/scraped-data') .then(data => { // 配置图表参数 const width = 900; const height = 500; const margin = { top: 30, right: 30, bottom: 60, left: 60 }; // 创建SVG容器 const svg = d3.select('#bar-chart') .append('svg') .attr('width', width) .attr('height', height); // 定义X轴(分类轴) const xScale = d3.scaleBand() .domain(data.map(item => item.item_name)) .range([margin.left, width - margin.right]) .padding(0.2); // 定义Y轴(数值轴) const yScale = d3.scaleLinear() .domain([0, d3.max(data, item => parseInt(item.item_value))]) .range([height - margin.bottom, margin.top]); // 绘制X轴 svg.append('g') .attr('transform', `translate(0, ${height - margin.bottom})`) .call(d3.axisBottom(xScale)) .selectAll('text') .attr('transform', 'rotate(-45)') .style('text-anchor', 'end'); // 绘制Y轴 svg.append('g') .attr('transform', `translate(${margin.left}, 0)`) .call(d3.axisLeft(yScale)); // 绘制柱状图 svg.selectAll('rect') .data(data) .enter() .append('rect') .attr('x', item => xScale(item.item_name)) .attr('y', item => yScale(parseInt(item.item_value))) .attr('width', xScale.bandwidth()) .attr('height', item => height - margin.bottom - yScale(parseInt(item.item_value))) .attr('fill', '#2ecc71') .on('mouseover', function() { d3.select(this).attr('fill', '#27ae60'); }) .on('mouseout', function() { d3.select(this).attr('fill', '#2ecc71'); }); }) .catch(error => { console.error('获取数据失败:', error); document.getElementById('bar-chart').innerHTML = '<p>数据加载失败,请稍后重试</p>'; }); </script> </body> </html>
内容的提问来源于stack exchange,提问作者user3808470
相关产品推荐
相关产品推荐

