灏天阁

基于 Rendora 轻松解决 SPA 网站 SEO 问题

· Yin灏

前言

目前 SPA 项目的 SEO 方案有:

  • 客户端渲染(CSR):对没有执行 js 能力的爬虫来说不友好
  • 服务端渲染(SSR):改造和维护成本高
  • 构建时预渲染:利用构建工具提前构建不同路由对应的页面,问题是没法动态变更
  • 服务端动态渲染:利用 user-agent 判断如果是爬虫则使用无头浏览器渲染页面再返回

对比来看服务端动态渲染可以以比较小的代价换取比较好的结果,而且也有现成的解决方案:github Rendora

Rendora 简介

Rendora 是一个动态渲染器,主要为 web 爬虫提供零配置服务器端渲染,以便轻松地改进在 React.js、Vue.js、Angular.js 等现代 Javascript 框架中开发的网站的 SEO。Rendora 完全独立于前端和后端堆栈。

Rendora 可以被视为一个反向 HTTP 代理服务器,位于后端服务器(例如 Node.js/Express.js、Python/Django 等)和潜在的前端代理服务器(例如 nginx、traefik、apache 等)之间,甚至可以直接连接到外部世界,实际上除了按原样传输请求和响应之外,它什么都不做,除非根据配置。在这种情况下,Rendora 指示无头 Chrome 实例请求并呈现相应的页面,然后将服务器端呈现的页面返回给客户端(即前端代理服务器或外部世界)。这个简单的功能使 Rendora 成为一个强大的动态渲染器,而无需实际更改前端和后端代码。

diagram.png

简单理解就是将页面请求代理到 Rendora,Rendora 内部通过 user-agent 判断是否是爬虫机器人请求,如果不是爬虫请求则代理到 Backend(配置文件配置的服务器地址),如果是爬虫请求,则使用无头浏览器渲染页面,再把渲染好的 html 返回

Rendora 主要特征

  • 前端和后端代码无需更改
  • 基于用户代理和路径的筛选器
  • 用 Golang 编写的单快速二进制
  • 多种缓存策略
  • 支持异步页面
  • Prometheus metrics
  • 选择您的配置系统(YAML、TOML 或 JSON)
  • 开箱即用

环境准备

这里仅以 Windows 环境来搭建,关于 Windows 下安装 Docker 环境准备可以参照这里Docker 部署 npm 私仓 Verdaccio/环境准备

基于 Docker 安装 Rendora

Rendora 官方文档已经给出详细的安装方式,这里不再赘述

拉取 Rendora 相关镜像

docker pull rendora/chrome-headless
docker pull rendora/rendora

成功结果如下,Images 有对应镜像:

20230110-150513.jpg

  • 如果拉取过慢或者出现拉取不成功提示(如:Unable to find image) 可以在 Docker Engine 新增国内镜像设置再重试
  "registry-mirrors": [
    "https://registry.docker-cn.com"
  ]

20230110-150814.jpg

构建 chrome-headless 容器

如下图操作,可以在 Optional settings 修改设置,我这里只修改了容器名称为 chrome-headless 20230110-174248.jpg 成功后,容器列表会有对应容器,点击运行按钮启动即可

screenshot-20230110-175520.png

构建 Rendora 容器

在构建 Rendora 容器前要先准备测试项目,也可以用当前开发的 SPA 项目进行测试

新建一个项目

  • http-server 做个简单服务器,全局安装http-server
npm i -g http-server
  • 新建一个文件夹,创建index.html文件并写入以下内容
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>rendora</title>
  </head>
  <body>
    <div id="root"></div>
    <script>
      document.getElementById("root").innerText = "无头浏览器渲染";
    </script>
  </body>
</html>
  • 启动服务
# 进入上一步新建的文件夹内启动终端执行以下命令
$ http-server

启动结果如下:

Available on:
  http://192.168.204.128:8080
  http://127.0.0.1:8080
Hit CTRL-C to stop the server
  • 新增 Rendora 配置文件

新建 CONFIG_FILE.yaml 配置文件,写入以下配置,注意 target.url 需要取上述服务器地址

listen:
  address: 0.0.0.0
  port: 3001
cache:
  type: none
target:
  # 上面个服务器地址
  url: "http://192.168.204.128:8080"
backend:
  # 上面个服务器地址
  url: "http://192.168.204.128:8080"
headless:
  waitAfterDOMLoad: 500
  timeout: 10
  internal:
    url: http://localhost:9222
  blockedURLs:
    [
      "*.png",
      "*.jpg",
      "*.jpeg",
      "*.webp",
      "*.conf",
      "*.gif",
      "*.woff2",
      "*.svg",
      "*.woff",
      "*.ttf",
      "https://www.youtube.com/*",
      "https://www.youtube1.com/*",
      "https://www.google-analytics.com/*",
    ]

output:
  minify: true
filters:
  userAgent:
    defaultPolicy: blacklist
    exceptions:
      keywords:
        - bot
        - Googlebot
        - slurp
        - bing
        - crawler
        - twitter
  paths:
    defaultPolicy: whitelist
    exceptions:
      prefix:
        - /static
        - /users/
server:
  enable: true

基于上述配置文件构建 Rendora 容器,为了方便这里直接在终端使用命令构建,使用 -v 指令指定映射,我的 CONFIG_FILE.yaml 文件在 D 盘 docker 文件夹下,这里直接构建并启动

docker run --name=rendora --net=host -v /D/docker/CONFIG_FILE.yaml:/etc/rendora/config.yaml rendora/rendora

成功后会在容器列表看到 rendora 容器

screenshot-20230111-095345.png

  • 启动 Rendora 注意事项
  1. 先确保 chrome-headless 已经启动,否则 rendora 会因连接不上 chrome-headless 而导致启动失败
  2. 如果是使用映射配置文件启动,需要保证配置文件存在且内容及格式正确,否则也可能导致失败,不过构建时也会有提示

测试

  • 正常请求测试,进入容器终端执行以下命令
curl http://127.0.0.1:3001

结果如下,得到的是 html 原本的样子

screenshot-20230111-100331.png

  • 爬虫请求测试,修改刚才的命令如下
curl -H "User-Agent: Googlebot" http://127.0.0.1:3001

结果如下,经过无头浏览器的渲染,js 已经执行了

20230111-100606.jpg

Rendora 配置文件核心内容讲解

详细的配置说明请看官方文档

listen

这个很容易理解就是 Rendora 自身地址以及监听端口

filters 请求过滤器

filters 参数 可选值/类型 含义
userAgent.defaultPolicy whitelist and blacklist 匹配策略,有白名单和黑名单两种模式
userAgent.exceptions - 只有符合规则的才通过
userAgent.exceptions.keywords String[] 只要user-agent包含keywords中的字符就符合
userAgent.exact - user-agent完全匹配该配置内容才符合

target & backend 目标地址和原地址

参数 含义
target.url 发送给无头浏览器的地址
backend.url 服务器原地址

由于 Rendora 本身也是个代理服务器,其核心工作是根据 filters 区分请求的用户,通过过滤器的则通过无头浏览器获取,否则代理到原服务,而无头浏览器请求的地址实际上也是原服务,如果 Rendora 服务和原服务在一个局域网(一般来讲是在一起的),这里可以配置成原服务(ip 形式),可减少域名解析等网络链路,也就是说 target.url 和 backend.url 是同一个地址时速度比较快

headless 无头浏览器配置

headless 参数 含义 备注
waitAfterDOMLoad 页面 DOMLoad 事件触发后生成快照前等待渲染的时间 DOMLoad 事件触发后还需要等待无头浏览器执行渲染,这个时间就是渲染时间,可根据自己网站情况配置
internal.url 无头浏览器的请求的地址 这里是 headless 服务地址,Rendora 启动时会检查该地址是否可连通
blockedURLs 无头浏览器渲染时获取所有请求黑名单 由于目标是渲染后的 html 页面,像图片、css、埋点 SDK 之类的 js 可能不是必须的,所以不需要加载,不过在屏蔽对应 js 资源时需要做好容错处理,防止因为某些资源未加载从而导致 js 执行失败并最终导致页面渲染失败,另外一些同步资源超时或 404 也可能影响 DOMLoad 事件触发或者影响生成快照

总结

在上面的测试中就可以体验到,走无头浏览器渲染的时间一定稍微久一点,因为启动无头浏览器包括之后的请求和渲染都比正常直接请求页面多一些时间,不过对于爬虫来说还可以接受,同时,还可以使用缓存策略,整体来说 Rendora 非常的简单易用,真正做到开箱即用

如果对于 Rendora 还有什么不满的,可以参考Headless Chrome Google 官方 Demo自行开发一套代理工具

- Book Lists -