前言

小贴士:前言写的比较啰嗦,而且大部分内容都是个人猜测无证据,如果您不感兴趣,可以跳过,直接看第二段的折腾部分

先简单概述一下问题,大部分搜索引擎,尤其是百度,特别偏爱静态网站,也就是代码都写在 index.html 里,然后最好 js 也没有, css 也是直接写在 html 中,所有内容全部用 html 写死。这样的网站呢,百度收录起来非常方便,而且也不容易出现,收录的时候一个样子,用户看到的是另外一个样子的情况。

以此为契机,即使很多人写的是动态网站(后端渲染),也想尽办法要去假装自己是静态网站,来博搜索引擎的欢心。其中最有代表性的就是 PHPASPJSP ,非常符合搜索引擎喜欢的样子,把所有内容,动态拼接到HTML里,拼成百度喜欢的样子返回。

然而显然这样的方式,对于 SPA 网站非常不友好。所谓 SPA ,即一个空白的 index.html 页面,只负责引入 js ,而 js 中包含海量的代码,来动态生成页面内容,包括动态请求服务端 API 接口,多次动态改变页面内容。

SPA 网站对用户来说体验是非常好的,效果非常直观,不再是点一下就白屏半分钟。当用户点击按钮后,如果内容已经在前端js的数据中,可以0.001秒刷新出内容,即使前端没有数据,需要请求 API,也只要请求需要的部分,相比与整页请求,速度可以加倍,且用户等待时间可以有一个动画过度,不会直接白屏。

但是对百度等搜索引擎来说,他没有进行多次渲染以达到普通用户最终看到的效果的功能,他一般只会拿到那个空白的index.html页面,当成你网站的最终效果来收录。且只有这一个页面,即使你有千千万万个url,也不会拿到。最终导致搜索引擎收录不了SPA网站的内容。用户也无法从搜索引擎找到你的网站。

综上所述,为了解决这一问题
我目前找到的最终解决方案为 Rendertron 后端渲染
以下图为例,服务端正常用 nginx 给到 rendora 前端渲染的轻量级代码, rendora 会自动识别出访问者是普通用户还是搜索引擎,普通用户直接不做任何处理返回轻量级的前端渲染代码,提高效率,搜索引擎爬虫就返回完整版后端已渲染完成的代码。


说了这么多 rendertron 优点,也要说一个致命缺点!!!
回到文章一开始我说百度最怕用爬虫来收录的时候内容是一个样子,用户访问是另外一个样子。那百度也有招,就是他也伪装成正常用户,微服私访,只要他伪装的足够像,我们是无法辨别的,那么他的2个身份不一致,拿到的代码也就不一致。网上流传的说法是,一旦被发现爬虫和普通用户访问的内容不一致,可能会遭到百度惩罚,排名降级,收录变不通畅等。到底有没有这回事呢,会有什么后果呢,谁也不得而知了,也许只有百度自家攻城狮知道。只能说选择这个方案,存在一定的风险,请谨慎选择!切记!切记!切记!


折腾


第一部分 安装&调试 rendertron

参考
https://blog.csdn.net/grootxu59/article/details/90453679
https://blog.csdn.net/qq_27868061/article/details/109790962
https://www.w7.wiki/develop/4205.html
https://github.com/GoogleChrome/rendertron

首先是安装

官方建议安装方法如下

  1. git clone https://github.com/GoogleChrome/rendertron.git
  2. cd rendertron
  3. npm install
  4. npm run build

可能会遇到的2个问题
1. github clone 慢或 404
2. npm run build 会自动安装Chromium 然而肯定失败,因为缺少依赖

我这里成功的方法如下
首先在自己电脑访问这个地址
https://github.com/GoogleChrome/rendertron/releases/

目前最新版本是 3.1.0 , 下载压缩包 Source code , zip 还是 tar.gz 都可以 看你系统

然后解压获得源码一份,源码手动上传到服务器,我是 Centos 7.4, 所以下文以 centos 为例

首先给权限

  1. sudo chmod -R 777 /root/git/rendertron


然后安装依赖

  1. npm install
  2. npm run build


这里如果按教程启动,肯定会报错,可以试下

  1. npm run start


我这里的报错信息如下

  1. > rendertron@3.1.0 start /root/git/rendertron-3.1.0
  2. > node build/rendertron.js
  3. Unhandled rejection
  4. Error: Failed to launch the browser process!
  5. /root/git/rendertron-3.1.0/node_modules/puppeteer/.local-chromium/linux-809590/chrome-linux/chrome: error while loading shared libraries: libatk-1.0.so.0: cannot open shared object file: No such file or directory
  6. TROUBLESHOOTING: https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md
  7. at onClose (/root/git/rendertron-3.1.0/node_modules/puppeteer/lib/cjs/puppeteer/node/BrowserRunner.js:193:20)
  8. at Interface.<anonymous> (/root/git/rendertron-3.1.0/node_modules/puppeteer/lib/cjs/puppeteer/node/BrowserRunner.js:183:68)
  9. at Interface.emit (events.js:327:22)
  10. at Interface.close (readline.js:424:8)
  11. at Socket.onend (readline.js:202:10)
  12. at Socket.emit (events.js:327:22)
  13. at endReadableNT (internal/streams/readable.js:1327:12)
  14. at processTicksAndRejections (internal/process/task_queues.js:80:21)
  15. npm ERR! code ELIFECYCLE
  16. npm ERR! errno 1
  17. npm ERR! rendertron@3.1.0 start: `node build/rendertron.js`
  18. npm ERR! Exit status 1
  19. npm ERR!
  20. npm ERR! Failed at the rendertron@3.1.0 start script.
  21. npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
  22. npm ERR! A complete log of this run can be found in:
  23. npm ERR! /root/.npm/_logs/2021-02-18T07_28_37_294Z-debug.log


这里重点就是说缺少 libatk-1.0.so.0 依赖
一个一个补齐太累了,我百度了所有依赖一次安装的命令

  1. yum install -y pango.x86_64 libXcomposite.x86_64 libXcursor.x86_64 libXdamage.x86_64 libXext.x86_64 libXi.x86_64 libXtst.x86_64 cups-libs.x86_64 libXScrnSaver.x86_64 libXrandr.x86_64 GConf2.x86_64 alsa-lib.x86_64 atk.x86_64 gtk3.x86_64


最后再从头跑一次就好了

  1. npm install
  2. npm run build
  3. npm run start


如果一切正常应该看到如下信息

  1. > rendertron@3.1.0 start /root/git/rendertron-3.1.0
  2. > node build/rendertron.js
  3. Listening on port 3000


先按 Ctrl + C 退出

如果还没有装过pm2 现在装一下

  1. # 安装pm2
  2. npm install pm2 -g


然后用 pm2 后台执行一下

  1. # 执行前确保你当前位置是在rendertron根目录下
  2. pm2 start build/rendertron.js

返回值如下

  1. [PM2] Applying action restartProcessId on app [rendertron](ids: [ 1 ])
  2. [PM2] [rendertron](1)
  3. [PM2] Process successfully started
  4. ┌─────┬───────────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
  5. id name namespace version mode pid uptime status cpu mem user watching
  6. ├─────┼───────────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
  7. 0 rendertron default 3.1.0 fork 20251 0s 32 online 0% 10.3mb root disabled
  8. └─────┴───────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘


如果和我一样不放心,可以再确认一下状态

  1. pm2 status

返回值应该是和上面一样,就说明 rendertron 作为后台程序正常运行中

  1. ┌─────┬───────────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
  2. id name namespace version mode pid uptime status cpu mem user watching
  3. ├─────┼───────────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
  4. 0 rendertron default 3.1.0 fork 20251 0s 32 online 0% 10.3mb root disabled
  5. └─────┴───────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘


下一步就非常关键了 尝试使用rendertron 请求一个url并进行后台渲染

  1. curl localhost:3000/render/https://www.baidu.com

正确应该是返回超级长的一段html代码


那么用了 rendertron 代理,和不用的区别是什么呢?

  1. # 不用rendertron 直接请求
  2. curl https://www.baidu.com
  3. # 用了rendertron 请求
  4. curl localhost:3000/render/https://www.baidu.com

就是 rendertron 会模拟浏览器,进行二次渲染,包括加载所有js css文件,并按浏览器来执行js,js中如果有异步请求接口的,rendertron 也会进行请求,并渲染到代码中,最终返回一个渲染好的,百度喜欢的样子的html代码给到前端返回。


第二部分 结合 nginx 实现 seo

需要注意的是
我的 vue 用的是 history 模式,所以必须包括以下代码

  1. underscores_in_headers on;
  2. location / {
  3. root /xxx/xxx/;
  4. try_files $uri $uri/ /index.html;
  5. break;
  6. }

如果你的是 hash 模式,则只需要

  1. location / {
  2. root /xxx/xxx/;
  3. break;
  4. }

中间的过程,坑也非常多,不过多赘述了,直接上最终成果

  1. # 关键部分代码
  2. underscores_in_headers on;
  3. location / {
  4. if ($http_user_agent ~* "googlebot|bingbot|baiduspider") {
  5. rewrite ^/(.*) /render/https://$host/$1 break;
  6. proxy_pass http://localhost:3000;
  7. }
  8. root /xxx/xxx/;
  9. try_files $uri $uri/ /index.html;
  10. break;
  11. }
  1. # 通过2段代码测试即可知道效果
  2. curl https://xxx/xxx
  3. curl -A 'baiduspider' https://xxx/xxx

如果成功,则第一条命令返回的是 index.html 的几行内容,不会执行 js ,且速度非常快
第二条命令则会略慢,返回的是大段内容,且是浏览器最终效果的内容

END

主线内容到此已结束,还有几个补充内容
1 sitemap.xml
sitemap是让浏览器知道你的网站有哪些访问路径的最高效的方法,由于是vue项目,动态生成略麻烦
静态方法是可以是在 vue-cli 项目的 public 目录下,手动创建一个 sitemap.xml ,并且手动更新内容
动态方法可以参考我之前的文章 java springboot 动态生成 sitemap.xml 网站地图

2 标题和描述
我水平有限,并未找到特别神奇的方案
一个简单的方法就是通过每个页面的js去控制。写在vue-router里则可以统一动态实现。