背景
虽然,现在大家对于搜索引擎优化没有很多的需求,但是搜索引擎入口还是一个流量比较多的入口。 所有大部分网站并没有放弃这个入口,并且会为此做优化。现在前端技术发展到今天,现阶段出现了各种渲染(前端渲染指的是如何生成网页代码结构)的技术手段。 如果你的网站是CSR(客户端渲染),那么搜索引擎爬虫在爬到你的时候,就会一脸懵逼,因为他只看到一个空的DIV结构,看不到任何可以收录的有效文字。 所以,我们来聊聊,关于前端SEO上面的一些如何静态化的思路。
目标
为了能让搜索引擎爬虫正确抓取识别到网页内容。
思路
全站静态化
首先,想到的就是全站静态化,也就是编写好URL规则,使用无头浏览器直接遍历所有的地址并且生成静态网页保存到硬盘上,最后将整个文件夹部署到服务器上的一种手段。 我们最常见的就是一些博客系统,他们大部分都采用这种技术架构。在本地写博客,在本地编译,最后发布到线上。 这个的优势是线上部署十分简单并且稳固。劣势也十分明显,每次都需要进行编译部署。不过这对于一些小的站点来说,完全不是问题。
我这里简单的写一个无头浏览器生成本地静态文件的node.js代码。整体思路是,按照映射关系访问对应的路径,并且在本地生成文件,最后整体提交部署。
js
const puppeteer = require("puppeteer");
const fs = require("fs");
const path = require("path");
const axios = require("axios");
const folder = "d:\\dist\\";
const domain = "https://www.baidu.com";
const urlMap = {
"index.html": "https://www.baidu.com",
};
function formatDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0"); // 月份从0开始计数
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
}
(async () => {
const browser = await puppeteer.launch({ headless: true });
const paths = Object.keys(urlMap);
const sitmapXML = ['<?xml version="1.0" encoding="UTF-8"?>'];
sitmapXML.push('<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">');
for (let i = 0; i < paths.length; i++) {
const key = paths[i];
const url = urlMap[key];
const page = await browser.newPage();
sitmapXML.push(`<url>
<loc>${domain}/${key}</loc>
<lastmod>${formatDate(new Date())}</lastmod>
</url>`);
console.log(`准备请求:${url}`);
await page.goto(url);
const pageContent = await page.content();
console.log(`保存网页文件: ${key}`);
fs.writeFileSync(path.join(folder, key), pageContent);
// 获取页面上的所有资源
const resources = await page.evaluate(() => {
const imgs = Array.from(document.querySelectorAll("img")).map((img) => img.src);
const stylesheets = Array.from(document.querySelectorAll('link[rel="stylesheet"]')).map((link) => link.href);
const scripts = Array.from(document.querySelectorAll("script")).map((script) => script.src);
return imgs.concat(stylesheets, scripts).filter((item) => item && !item.startsWith("https://"));
});
console.log("资源列表", resources);
for (const resource of resources) {
try {
const response = await axios.get(resource, { responseType: "arraybuffer" });
const filename = path.basename(resource);
const folderPath = path.join(folder, path.dirname(resource));
if (!fs.existsSync(folderPath)) {
fs.mkdirSync(folderPath, { recursive: true });
}
fs.writeFileSync(path.join(folderPath, filename), response.data);
console.log(`资源下载: ${filename}`);
} catch (error) {
console.error(`资源下载异常 ${resource}: ${error}`);
}
}
console.log("关闭页面");
await page.close();
}
console.log("退出浏览器");
await browser.close();
sitmapXML.push("</urlset>");
console.log("写入站点地图");
fs.writeFileSync(path.join(folderPath, "sitemap.xml"), sitmapXML.join("\n"));
})();
Nginx网关静态化
Prerender.io提供了网关级的预渲染,他也开源了他的程序代码,具体可以参考 GitHub - prerender/prerender: Node server that uses Headless Chrome to render a javascript-rendered page as HTML. To be used in conjunction with prerender middleware.
他整个文档都提供了非常详细的说明。就是安装这个开源包。运行它,直接设置nginx配置文件即可。
js
const prerender = require('prerender');
const server = prerender();
server.start();
然后修改nginx配置文件
c
# Change YOUR_TOKEN to your prerender token
# Change example.com (server_name) to your website url
# Change /path/to/your/root to the correct value
server {
listen 80;
server_name example.com;
root /path/to/your/root;
index index.html;
location / {
try_files $uri @prerender;
}
location @prerender {
proxy_set_header X-Prerender-Token YOUR_TOKEN;
set $prerender 0;
if ($http_user_agent ~* "googlebot|bingbot|yandex|baiduspider|twitterbot|facebookexternalhit|rogerbot|linkedinbot|embedly|quora link preview|showyoubot|outbrain|pinterest\/0\.|pinterestbot|slackbot|vkShare|W3C_Validator|whatsapp") {
set $prerender 1;
}
if ($args ~ "_escaped_fragment_") {
set $prerender 1;
}
if ($http_user_agent ~ "Prerender") {
set $prerender 0;
}
if ($uri ~* "\.(js|css|xml|less|png|jpg|jpeg|gif|pdf|doc|txt|ico|rss|zip|mp3|rar|exe|wmv|doc|avi|ppt|mpg|mpeg|tif|wav|mov|psd|ai|xls|mp4|m4a|swf|dat|dmg|iso|flv|m4v|torrent|ttf|woff|svg|eot)") {
set $prerender 0;
}
#resolve using Google's DNS server to force DNS resolution and prevent caching of IPs
resolver 8.8.8.8;
if ($prerender = 1) {
#setting prerender as a variable forces DNS resolution since nginx caches IPs and doesnt play well with load balancing
set $prerender "service.prerender.io";
rewrite .* /$scheme://$host$request_uri? break;
proxy_pass http://$prerender;
}
if ($prerender = 0) {
rewrite .* /index.html break;
}
}
}
服务端同构
使用Next.js或者Nuxt.js去做服务端渲染。这个方式就更简单了,直接按照Vue或者React方式去开发。最后使用PM2部署到远程服务器上即可。没有任何迁移问题。 同构是结合了服务端渲染和前端渲染一起干活的一种技术架构。 在第一次打开浏览器请求页面的时候,这个页面所有的内容将会在服务端渲染出整个结构输出到客户端。而后续则是一直由客户端进行渲染网页结果内容。 这也是一种迁移量比较低的一种方案。
结论
以上三种方案均可以实现爬虫读取网页内容,但是对于整个技术需要自行评判做一定的取舍。
- 如果服务器自由度不高,直接全栈静态化即可。
- 改造成本低可以使用Prerender配合nginx做静态缓存。
- 需要有一定基础,但是整个技术架构比较统一的,可以采用服务端同构技术方案。