越是喧闹,越是孤独。越是寂寞,越是丰富
The more noisy, the more lonely. The more lonely, the more rich
越是喧闹,越是孤独。越是寂寞,越是丰富
The more noisy, the more lonely. The more lonely, the more rich
最近,“科目三”的舞蹈爆红,虽然该舞蹈跟驾考的科目三考试关系不大,但是“科目三”已经成为我的网站的搜索关键词之一,同时还有“科目二”。于是打算趁这个热度,整理一下目前我的网站对于SEO(Search Engine Optimization,搜索引擎优化)的处理方式。
01
—
Bing对本站的收录
我的网站于2021年4月部署上线,并同步上线了科目二和科目三的网页版模拟程序,如下图所示。
(可以到“编程作品”里查看 https://mengchen.cc/applications)
当时因为刚拿到驾照,同时想丰富一下网站的内容,于是趁热打铁做了这两个小程序。之后我在公众号上发布了科目二/三的学习笔记,并在“海淀驾校”APP上发帖引流,虽然最后粉丝也不多,但也算是一次有意义的尝试,也有学车的小伙伴对我表示了称赞,还是颇有成就感的。
(截图来自 https://zhuanlan.zhihu.com/p/371224539)
后来我将这两个页面提交给百度、Bing、Google等搜索引擎收录,起初也就是碰碰运气,没指望能收录到什么程度,但万万没想到的是,Bing居然对这两个页面收录得还不错!
搜索“科目二模拟”,我的页面排第一:
搜索“科目三灯光模拟”,我的页面依然排第一:
这下就有点让我喜出望外了!我只是做了两个很简单的页面,咋就能排行第一了呢?同时,这两个页面也成了我的网站的主要流量,由于百度并没有收录我的科目二/三的关键词,所以我的流量基本上是Bing提供的。虽然Bing在国内的市场占有率不及百度,但鉴于Bing内置于Edge浏览器,而Edge又内置于Windows系统,对于国内庞大的windows用户群体,所以也是有一定流量的入口。另外我发现在夏季的时候,网站访问量会增大一些,我猜测应该是暑假学车的人比较多的原因。
通过Bing Webmaster,可以看到我的网站的各个关键词的搜索排名:
可以看出,基本上所有关于“科目二模拟”和“科目三灯光模拟”的关键词都被我“霸占”了,这些词的排名基本上都是数一数二。我感到开心的同时,也有一丝惭愧,毕竟我觉得我做的这两个程序还有不足,却长期霸占搜索榜第一,若不能给用户带来有效的帮助,有点“德不配位”的感觉,哈哈哈。
言归正传,本文主要是来介绍一下我是如何对我的单页应用(Single Page Application,SPA)进行SEO的。
02
—
使用Puppeteer处理SEO
我的网站主站是Vue+VueRouter的单页面应用,纯前端构建,用Nginx部署静态页面,这样带来的问题就是对搜索引擎不友好。多数搜索引擎一般只能获取首次请求从服务端返回的网页源代码,无法通过执行页面中的异步脚本来获取动态内容。而我的网站用的前后端分离,静态页面和API是两个不同的域名,静态页面通过Ajax获取数据并渲染内容。因此对于爬虫们来说,它们通常并不能获取到我网站的实际内容,进而会影响收录情况。
关于SPA的SEO问题,一种解决方案就是用SSR(Server-Side Rendering,服务端渲染)。但经评估后发现如果使用Vue SSR改造,工作量不小,而且我更倾向于前端渲染的开发过程和交互方式。相比之下,预渲染(Pre-rendering)或静态站点生成(SSG,Static-Site Generation)的方式更适合我。关于SSR和SSG这两种方式在Vue的官方文档中有详细介绍(链接见附录)。
我最终采用的方式,是一种懒加载(lazy-load)的预渲染,我称之为“按需预渲染”。其大致流程是,在Nginx端针对普通请求与爬虫请求执行不同的操作,普通请求直接返回原始的静态文件并在前端动态渲染,而爬虫请求,则是被重定向到另一个模块,这个模块将会启动puppeteer去加载页面,并将渲染后的页面内容返回。整体大致流程图为:
关于判断请求是否是爬虫请求,我们可以直接从user-agent中查找是否含有"spider"或者"bot"字样即可,同时对css/js/txt/ico等静态文件的爬虫请求不做额外处理。最终nginx配置文件代码如下:
location / {
try_files $uri $uri/ /index.html;
# 0 - Non-static files 1 - Static files
set $flag 0;
if ($uri ~* "\.css|\.js|\.txt|\.ico") {
set $flag 1;
}
if ($http_user_agent ~* "spider|bot") {
set $flag 1$flag;
}
# SEO Condition: Spider && Non-static files
if ($flag = 10) {
proxy_pass http://127.0.0.1:3001;
}
}
在Node.js程序中启动Puppeteer加载页面的主要代码如下:
if (!globalBrowser) {
globalBrowser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox']
});
}
const page = await globalBrowser.newPage();
await page.setCacheEnabled(false);
await page.goto(url);
try {
await page.waitForSelector('.seo-tag', { timeout: 3000 });
} catch (err) {
error(`Error: page.waitForSelector, URL: ${url} message: ${err.message}`);
}
let renderedContent = await page.content();
await page.close()
其中globalBrowser是通过puppeteer创建的headless browser全局变量,随server进程只保留一份,通过该变量创建不同的page来加载相应的页面。同时为了指定何时加载完成,这里我选择了等待某个css选择器的方式,我将我的网站页面的主要内容部分的外层div加上class="seo-tag",这样当seo-tag这个元素出现时,也就说明网页的内容已渲染完成,此时puppeteer检测到这个元素出现,就可以通过page.content()获取渲染后的html代码。
为了提升性能,将页面渲染后的内容保存到文件,同时在Redis中缓存当前爬虫请求的url,这样可以在缓存期内对相同的请求可以立即返回,减少开销。
关于Puppeteer,我们直接拿官网(链接见附录)中的介绍:
Puppeteer is a Node.js library which provides a high-level API to control Chrome/Chromium over the DevTools Protocol. Puppeteer runs in headless mode by default, but can be configured to run in full ("headful") Chrome/Chromium.
Puppeteer能做哪些事情呢?
Generate screenshots and PDFs of pages.
Crawl a SPA (Single-Page Application) and generate pre-rendered content (i.e. "SSR" (Server-Side Rendering)).
Automate form submission, UI testing, keyboard input, etc.
Create an automated testing environment using the latest JavaScript and browser features.
Capture a timeline trace of your site to help diagnose performance issues.
Test Chrome Extensions.
其中第二点就是本文所介绍的——抓取SPA页面并生成预渲染的内容。关于第一点:生成网页屏幕截图和保存网页为PDF也非常有用,我们将在下一期中介绍。
(全文完)
附录链接:
1、Vue SSR
https://cn.vuejs.org/guide/scaling-up/ssr.html
2、Puppeteer
https://pptr.dev/