我的第一个网站:大前端面试题库 bigerfe.com
今天说说我的第二个网站。
--------------------------------------
之前一直想做个网站,反正五一也不出去,正好这几天可以开发一下。
为了尽快上线,有些功能让Ai来实现的,没想到1天就搞完了。
太快乐了!🎉
从去年开始,Ai领域突飞猛进的发展,国内各种大模型以及Ai应用层出不穷,不计其数,但是我们能了解到的也就阿里、百度、腾讯、字节等这些大厂的AI应用,但是仍然有很多优秀的应用大家无法接触到,更无法使用到。
所以打工人急需一个AI工具导航网站,方便打工人更快速便捷的找到目标应用,同时也能了解到更多其他AI应用,提高我们打工人的效率。
下面是我用1天时间开发的AI导航网站:
网址:dai.bigerfe.com
综上,最后决定使用最成熟,最没有新意的技术 😳。
node+Ai+koa2+ejs+bootstrap3+jQuery+file系统+pm2+ng+图床
首先需要考虑数据源从哪来,导航网站的数据说多不多,说少不少,一条条添加,能把人干报废了。
还是找一个好点的网站,爬一爬吧,不过这样的网站大多数都是服务的直出。
目标网站就不说了,市面上有很多。
为了提高效率,有些功能必须让AI来完成。
一个分类下对应多子分类,每个子分类的url,以及子分类列表还有分页。
这里借助了Kimi - AI 来处理,把页面的分类html输入给Kimi,再输入一些规则和要求,让他输出json格式的数据,包括分类名称,分类别名(用来当作路由),远程地址。
如下:
下面这些分类很多,手动整理很费时间,起码得2个小时,让Ai只需要半个小时。不过有训练不出来的风险。
//分类信息
module.exports = [
{
typeName: 'Ai工具箱',
typeId:0,
alias:'ai-write',
child:[
{
typeName: 'Ai写作对话',
update:false,
alias:'ai-write',
remote:['https://www.xxx.com/ai-write/','https://www.xxx.com/ai-write/list_664_2/'],
items:[]
},
{
typeName:'Ai绘画生成',
alias:'ai-image',
update:false,
remote:['https://www.xxx.com/ai-image/','https://www.xxx.com/ai-image/list_660_2/'],
items:[]
},
{
typeName:'Ai视频生成',
alias:'ai-shipin',
update:false,
remote:['https://www.xxx.com/ai-shipin/'],
items:[]
}
]
}
]
......
这里就必须要使用cheerio
了 ,可以在node里像使用jq一样获取数据。需要排除一些干扰信息,找到每个item的关键信息过滤就行。
//获取页面里的列表网站
async function execPageHtml(pageHtml) {
// 使用cheerio加载HTML字符串
const $ = cheerio.load(pageHtml);
// 选取并提取h1标签中的文本内容
const itemBoxs = $('.line-big');
let lastResult = [];
for (let i = 0; i < itemBoxs.length; i++) {
const list = await getSiteBaseInfo(itemBoxs[i], $);
lastResult = lastResult.concat(list);
}
return lastResult;
}
//获取网站的基本信息 title、url、logo、简介
async function getSiteBaseInfo(itemBox, $) {
const result = [];
const items = $(itemBox).find('a');
for (let i = 0; i < items.length; i++) {
const target = $(items[i]);
const url = target.attr('href');
//图片上传到七牛,下面说
const imgInfo = await qiniu.uploadRemote($(target.find('img')[0]).attr('src'));
if (url.indexOf('/site/') > -1) { //必须是站点
const obj = {
title: $(target.find('strong')[0]).text(),
url,
img: imgInfo ? imgInfo.key : '',
desc: $(target.find('.text-default')[0]).text(),
originId: url.substring(url.lastIndexOf('/') + 1).replace('.html', '')
};
result.push(obj);
}
}
return result;
}
网站列表不经常更新,也没必要搞个管理后台,所以在完成上面步骤后都会写入文件。
规则是按照分类别名存储:
function saveConToFile(filename, obj) {
fs.writeFile(path.resolve(__dirname, `./data/${filename}.txt`), JSON.stringify(obj), (err) => {
if (err) throw err;
console.log('文件写入成功!');
});
}
有个两个图片需要进行处理,内容抄人家的,图片再用人家的,这老脸真挂不住了~。
其实想过放在自己的服务器上,但想了下不至于这么想不开吧。虽然很容易,但并不正确。
最后,放在七牛,还有些免费的空间,足够用了。
这种云平台,有非常完善的sdk,可以辅助开发者快速对接。
还能够直接读取远程图片直接保存,返回远程预览地址,真的香。
1. 需要先安装 qiniu npm包
2. 需要配置accessKey、secretKey、bucket
3. 需要获取 uploadToken
const qiniu = require('qiniu');
const path = require('path');
const encode = require('./encode');
var accessKey = 'xxxxxxsYzzzRcxhNMXV0dQJTtDrK';
var secretKey = 'xxxxxxV_-xNoqXAt8Uqkim'
var bucket = 'xxxxx';
var mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
var putPolicy = new qiniu.rs.PutPolicy({
scope: bucket
});
var uploadToken = putPolicy.uploadToken(mac);
async function uploadRemote(remoteUrl,isMd5Name = true) {
if(!remoteUrl) return '';
let config = {
// 空间对应的机房
//zone: qiniu.zone.Zone_z1,
// 是否使用https域名
useHttpsDomain: false,
// 上传是否使用cdn加速
useCdnDomain: true
}
var bucketManager = new qiniu.rs.BucketManager(mac, config);
var resUrl = remoteUrl;
var bucket = "appandroid";
var fileType = remoteUrl.substring(remoteUrl.lastIndexOf('.'));
var key = remoteUrl.substring(remoteUrl.lastIndexOf('/') + 1);
if(isMd5Name){
key = encode.md5(key) + fileType;
}
return new Promise(resolve=>{
bucketManager.fetch(resUrl, bucket,'daai/' + key, function(err, respBody, respInfo) {
if (err) {
console.log(err);
resolve(null);
} else {
if (respInfo.statusCode == 200) {
console.log(respBody.key);
resolve(respBody);
} else {
resolve(null);
}
}
});
})
}
module.exports = {
uploadRemote
}
一个分类下的应用很多,上百条是有的,logo需要做下加载优化,不然页面打开特别慢。
这里直接问AI就可以了,马上给你结果:
$(document).ready(function() {
var lazyImages = [];
$("img.js-lazy-load").each(function() {
var img = $(this);
lazyImages.push(img);
});
function loadImg(){
var scrollTop = $(window).scrollTop();
for (var i = lazyImages.length - 1; i >= 0; i--) {
var img = lazyImages[i];
if ((img.offset().top - $(window).height()) < scrollTop) {
img.attr('src', img.attr('data-src'));
lazyImages.splice(i, 1);
}
}
if (!lazyImages.length) {
$(window).off('scroll');
}
}
$(window).scroll(function() {
loadImg();
});
loadImg();
如果不设置TDK,爬虫可能无法知道你的网站做啥的。
举例:
<title>懂AI | 一站式AI导航</title>
举例:
<meta name="keywords" content="Leonardo.ai,懂AI" />
所以这里需要搞一个中间件来处理通用的tdk对象,在ejs任何一个页面上都能获取。
定制的描述,就在他所属的controller内改写。
看代码:
//commoninfo 中间件
module.exports= async function (ctx,next) {
//获得基础通用数据并绑定到 ctx 上
ctx.CommonInfo = {
tdk:{
title:'ai人工智能,gpt人工智能,大Ai工具导航',
key:'大Ai工具导航',
desc:'大Ai工具导航是一个不断增长的AI人工智能工具资源库,xxxx'
}
};
await next();
}
在搜索controller内重写。
module.exports = async (ctx) => {
const { key = '' } = ctx.query;
ctx.CommonInfo.tdk.title = '搜索-' + key;
const links = await searchApp(key);
ejsRender.call(ctx, 'newpages/search/search.html', {
...ctx.CommonInfo,
links: links || [],
skey: key
});
}
我用的最多的就是搜索,能很快定位到目标网站。
搜索就比较暴力了,根据路径配置,和本地文件内容,计算出全集,然后进行过滤。
更好的是加个缓存,没必要每次都重新读取,浪费资源(当有了自己的服务器,每个资源都是非常宝贵的)。
//url配置
module.exports = [
{
typeName: 'Ai工具箱',
typeId:0,
alias:'ai-write',
child:[
{
typeName: 'Ai写作对话',
update:false,
alias:'ai-write',
remote:['https://www.xxx.com/ai-write/','https://www.xxx.com/ai-write/list_664_2/'],
items:[]
},
{
typeName:'Ai绘画生成',
alias:'ai-image',
update:false,
remote:['https://www.xxx.com/ai-image/','https://www.xxx.com/ai-image/list_660_2/'],
items:[]
},
{
typeName:'Ai视频生成',
alias:'ai-shipin',
update:false,
remote:['https://www.xxx.com/ai-shipin/'],
items:[]
}
]
}
]
//搜索方法
async function searchApp(key) {
if(!allAppInfo.length){
for (let i = 0; i < urlConfig.length; i++) {
const len = urlConfig[i].child.length;
const childs = urlConfig[i].child;
for (let j = 0; j < len; j++) {
const child = childs[j];
const alias = child.alias;
const linksStr = fs.readFileSync(path.resolve(__dirname, '../common/data/'+alias+'.txt'));
const list = JSON.parse(linksStr);
allAppInfo = allAppInfo.concat(list);
}
}
}
return allAppInfo.filter(item=>item.title.indexOf(key)>-1 || item.desc.indexOf(key)>-1);
}
这里直接使用pm2+nginx即可。
但是端口得是能自定义,从命令中传递过去。
//获取端口
const port = process.env.PORT || config.defaultPort
//pm2命令
PORT=7560 pm2 start app.js -n bigai-7560 -o --watch -- env:production
网站就介绍到这里,技术很古老,再结合下AI,效率真的高,我的宗旨就是稳定,好用就ok。
也可以点击左下角原文链接来体验下。