【笑小枫】https://www.xiaoxiaofeng.com上线啦
资源持续整合中,程序员必备网站,快点前往围观吧~
我的个人博客【笑小枫】又一次版本大升级,虽然知道没有多少访问量,但我还是整天没事瞎折腾。因为一些功能在Halo上不太好实现,所以又切回了Vue3项目,本文就是对于Vue单页面项目SEO优化的一个简单的完整方案。
此次优化的最大好处,就是SSR时对已有的vue项目0侵入,不需要改任何的代码,赞啊
唉,说实话,是个毛线的选型,我是先吭哧吭哧把项目上线了,才又想起来SEO这个坑,心累~
对于Vue来说,SEO最大的坑,如何返回渲染后的页面,也就是下面说的SSR了,然后就是动态Meta。
看看Vue.js关于SSR的介绍吧,很详细,也给出了方案
https://cn.vuejs.org/guide/scaling-up/ssr.html#overview
确实,Vue
也给出了更通用的方案。
全程百度+cv
写出的Vue
代码,这些通用的方案并不适合我。
Nuxt.js
,但是用我已经开发好的项目来改,说实话,前端小白的我实在是改不动了。Quasar
不太了解,不过多介绍了。Vite SSR
,Vite
提供了内置的Vue
服务端渲染支持。也要改已有代码,不想改,不会改。好吧,官方给的方案被我否定了,就是想找个不用修改已有代码的方法。太难了~
经过不懈的百度努力,发现了一线生机phantomjs
,简单说说这个吧
Phantomjs是一个基于webkit内核的无头浏览器,即没有UI界面,即它就是一个浏览器,只是其内的点击、翻页等人为相关操作需要程序设计实现。虽然“PhantomJS宣布终止开发”,但是已经满足对Vue的SEO处理。
这种解决方案其实是一种旁路机制,原理就是通过Nginx配置,判断访问的来源UA是否是爬虫访问,如果是则将搜索引擎的爬虫请求转发到一个node server,再通过PhantomJS来解析完整的HTML,返回给爬虫。
废了好大一番功夫,然后爬取到的数据仍然是没有渲染的数据,尴尬的一批。
说了这么多废话,说说我的最终解决方案吧~
用的方式类似于PhantomJS
,使用的Puppeteer
Puppeteer 是一个 Node.js 库,它提供了一个高级 API 来通过开发工具协议控制 Chrome/Chromium。 Puppeteer 默认以无头模式运行,但可以配置为在完整 (“有头”) Chrome/Chromium 中运行。
Puppeteer的作用,我主要用到的是SSR:
meta标签用于设置HTML的元数据(描述数据的数据),该数据不会显示在页面中,主要用于浏览器(如和现实内容或重新加载页面)、搜索引擎(如SEO)及其他web服务,这里使用vue-meta
进行设置。
Vue-meta 是一个 Vue.js 的插件,允许你在组件中操作应用的 meta 信息,如标题、描述等。它对于单页应用 (SPA) 和服务端渲染 (SSR) 的项目特别有价值,因为它们在处理 meta 信息方面要比传统的多页面应用要复杂得多。
为了使用 Vue-meta,首先需要安装。推荐使用 npm 进行安装,执行以下命令即可:
npm i -S vue-meta@next
目前 vue-meta3 还是处于 alpha 阶段,不要低于 3.0.0-alpha.7
import { createApp } from "vue";
import { createMetaManager} from 'vue-meta'
const app = createApp(App);
app.use(createMetaManager(false, {
meta: { tag: 'meta', nameless: true }
}));
app.mount("#app");
App.vue
中添加
标签,一定要添加,不然不生效哟
<script setup>
import {onMounted} from "vue";
import { useMeta } from 'vue-meta';
onMounted(() => {
useMeta({
title: '笑小枫 - 程序员的世外桃源',
meta: [
{ name: 'keywords', content: '笑小枫,java,SpringBoot,程序员' },
{ name: 'description', content: '欢迎来到笑小枫,我们致力于打造一个开放、友好的技术社区,让知识和智慧在这里自由碰撞、绽放。欢迎加入我们的旅程,一起在技术的海洋中探索无限可能!' }
]
});
});
</script>
<script setup>
import {onMounted} from "vue";
import { useMeta } from 'vue-meta';
onMounted(() => {
useMeta({
title: '笑小枫 - 程序员的世外桃源',
meta: [
{ name: 'keywords', content: '笑小枫,java,SpringBoot,程序员' },
{ name: 'description', content: '欢迎来到笑小枫,我们致力于打造一个开放、友好的技术社区,让知识和智慧在这里自由碰撞、绽放。欢迎加入我们的旅程,一起在技术的海洋中探索无限可能!' }
],
link: [
{
rel: 'stylesheet',
href: 'https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css'
}
],
script: [
{
src: 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js',
async: true,
body: true,
}
]
});
});
</script>
onMounted
中,因为数据也是放在onMounted
中,感觉数据没有请求完,meta就渲染完了,导致不生效,暂时不知道怎么解决,误打误撞中,动态的页面会触发onUpdated
事件,于是就取巧放在了onUpdated
中,希望知道原因的前端大佬可以给予指导省略了非关键性代码,正常不会触发onUpdated
,这里误打误撞了…
Puppeteer中文网
上文简单介绍了Puppeteer,这里就不过多的介绍Puppeteer了,如需了解,可以去官网
搭建用到的工具
已经安装过node.js的机器忽略这部分即可,可以通过node -v
查看
cd /opt
wget https://nodejs.org/dist/v17.9.0/node-v17.9.0-linux-x64.tar.gz
tar -xzvf node-v17.9.0-linux-x64.tar.gz
ln -s /opt/node-v17.9.0-linux-x64/bin/node /usr/local/bin/
ln -s /opt/node-v17.9.0-linux-x64/bin/npm /usr/local/bin/
node -v
查看是否安装成功cd /home/wwwroot
mkdir puppeteer
cd puppeteer
npm install puppeteer --save
npm install express
npm install html-minifier
yum install pango.x86_64 libXcomposite.x86_64 libXcursor.x86_64 libXdamage.x86_64 libXext.x86
const puppeteer = require('./node_modules/puppeteer');//由于目录不一致,所以使用的是绝对路径
const WSE_LIST = require('./puppeteer-pool.js'); //这里注意文件的路径和文件名
const spider = async (url) => {
let tmp = Math.floor(Math.random() * WSE_LIST.length);
//随机获取浏览器
let browserWSEndpoint = WSE_LIST[tmp];
//连接
const browser = await puppeteer.connect({
browserWSEndpoint
});
//打开一个标签页
var page = await browser.newPage();
// Intercept network requests.
await page.setRequestInterception(true);
page.on('request', req => {
// Ignore requests for resources that don't produce DOM
// (images, stylesheets, media).
const whitelist = ['document', 'script', 'xhr', 'fetch'];
if (!whitelist.includes(req.resourceType())) {
return req.abort();
}
// Pass through all other requests.
req.continue();
});
//打开网页
await page.goto(url, {
timeout: 20000, //连接超时时间,单位ms
waitUntil: 'networkidle0' //网络空闲说明已加载完毕
});
//获取渲染好的页面源码。不建议使用await page.content();获取页面,因为在我测试中发现,页面还没有完全加载。就获取到了。页面源码不完整。也就是动态路由没有加载。vue路由也配置了history模式
let html = await page.evaluate(() => {
return document.getElementsByTagName('html')[0].outerHTML;
});
await page.close();
return html;
}
module.exports = spider;
const puppeteer = require('./node_modules/puppeteer');
const MAX_WSE = 2; //启动几个浏览器
let WSE_LIST = []; //存储browserWSEndpoint列表
//负载均衡
(async () => {
for (var i = 0; i < MAX_WSE; i++) {
const browser = await puppeteer.launch({
//无头模式
headless: true,
//参数
args: [
'--disable-gpu',
'--disable-dev-shm-usage',
'--disable-setuid-sandbox',
'--no-first-run',
'--no-sandbox',
'--no-zygote',
'--single-process'
],
//一般不需要配置这条,除非启动一直报错找不到谷歌浏览器
//executablePath:'chrome.exe在你本机上的路径,例如C:/Program Files/Google/chrome.exe'
});
let browserWSEndpoint = await browser.wsEndpoint();
WSE_LIST.push(browserWSEndpoint);
}
})();
module.exports = WSE_LIST
需要和spider.js放在一个目录
https://www.xiaoxiaofeng.com需要替换成你自己的域名
const express = require('./node_modules/express');
var app = express();
var spider = require("./spider.js");
var minify = require('html-minifier').minify;
app.get('*', async (req, res) => {
let url = "https://www.xiaoxiaofeng.com" + req.originalUrl;
console.log('请求的完整URL:' + url);
let content = await spider(url).catch((error) => {
console.log(error);
res.send('获取html内容失败');
return;
});
// 通过minify库压缩代码
content=minify(content,{removeComments: true,collapseWhitespace: true,minifyJS:true, minifyCSS:true});
res.send(content);
});
app.listen(3000, () => {
console.log('服务已启动!');
});
nohup node server.js &
启动成功后,可以通过tail -f nohup.out
查看日志,出现服务已启动!
则代表运行成功,期间可能会出现各式各样的问题,再百度一下吧,这里就不一一列举了。
相当与启动了一个端口为3000的puppeteer服务。
启动的时候可能端口占用 3000被占用的话就换一个其他端口。
同时也会在/root/.cache/puppeteer/chrome/下装一个对应版本的谷歌浏览器
我这里用的是docker容器启动的Nginx,proxy_pass换成对应的地址就行了,然后重启Nginx。
location / {
proxy_set_header Host $host:$proxy_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
if ($http_user_agent ~* "Baiduspider|twitterbot|facebookexternalhit|rogerbot|linkedinbot|embedly|quora link preview|showyoubot|outbrain|pinterest|slackbot|vkShare|W3C_Validator|bingbot|Sosospider|Sogou Pic Spider|Googlebot|360Spider") {
proxy_pass http://172.17.0.1:3000;
}
alias /usr/share/nginx/html/;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
这样就可以了,让我们一起来测试一下吧
首先我们先正常的请求,不加请求头,请求结果如下,可以看到是没有渲染的vue页面。
然后我们加上请求头(这里直接复制了百度请求头)User-Agent:Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)
,再次访问,可以看到请求到了渲染后的网页。
我们最后再去百度收录看一下爬取诊断吧。
处理前,百度爬取到的内容:
处理后,百度爬取到的内容(截图有限,看滚动条就可以清楚看见):
最后,看一下我们网站访问有没有受影响吧,完美收官~
PS:莫名奇妙不知道优化了啥,速度一下提上来了,百度爬虫从原来的7s降到了1s,上文中的代码已经是优化后最新的代码。
本文到此就结束了,如果帮助到你了,帮忙点个赞
我是笑小枫,全网皆可搜的【笑小枫】