点击展开更新日志

2025

08-13

新增 browserless 部署

09-13

新增 n8n 部署

nexttime

会有些什么呢(❁´◡`❁)

源起

最开始的需求只是想写一个 Python 脚本实现小网站的自动签到,然后还得每天手动跑一遍(可恶,怎么还要手动执行🙄),所以在了解到自动化流程后开始探索自动化方法。

因此行文顺序是按照我的需求实现顺序展开,请根据需要自行跳转。

本文仍在探索阶段,所记录仅为经验之谈,供参考。

致谢

本文引用了多位佬的帖子或 Github 地址,诚挚感谢!

如果带来了任何困扰请随时邮件告知(地址在侧边栏头像下方),会第一时间删除并致歉。

playwright

参考文档

依然是那句话,如果有官网文档,最推荐先看一下官方文档。

概述

这是一个 Web 应用端到端测试工具,支持主流浏览器,如 Chromium、Firefox,主要是方便自动化浏览器操作。如果你曾经了解过 Selenium ,那么可以认为是近似工具。

安装

改命令会同时安装浏览器到 ~/.cache/ms-playwright 目录下,如果需要安装到自定义目录,可以手动设置 PLAYWRIGHT_BROWSERS_PATH 环境变量,再使用以下命令安装。

1
npm init playwright@latest

关于 npm 安装,下载安装程序无脑安装即可,至于是否使用 nvm ,根据个人需求,如果不了解这是什么,那就说明并不需要。

基础使用

因为笨蛋站长🤐已经把学过的 C 和 Java 忘完了,因此下面的示例以 Python 说明:

先在包管理器安装 playwright 包:

清华源镜像:https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple

更换位置在 PyCharm 左下角【包管理器】那里,有个设置。

1
2
3
4
5
6
7
8
9
10
11
12
import time
from playwright.sync_api import sync_playwright, TimeoutError
def checkin():
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
context = browser.new_context()
page = context.new_page()
page.goto("https://www.evergarden.top")
time.sleep(10)
page.close()
context.close()
browser.close()

这就是一个最小的测试用例,playwrigth 中可以选择使用同步函数 sync_api 或 异步函数 async_api ,使用异步,需要在 with 前加关键字 async,不然会报错。

基本的流程就和人工操作浏览器是一样的,只不过进行了拆解:

  • 创建一个 chromium 浏览器对象
  • 创建一个浏览器上下文
  • 创建一个页面 page
  • 打开网址
  • 销毁创建的对象(关闭浏览器)

基于这个逻辑就能把签到啥的逻辑做到代码里实现自动化,但——是,对于验证码的处理就比较麻烦(尝试接入大模型但失败了),因此目前我的处理方法是第一次手动辅助完成登录,保存登录状态到 state.json (cookie),下次就使用状态文件创建上下文就可以直接进入登录状态(playwright测试浏览器是不会自动保存cookie的哦)。
这样的弊端就是对于签到二次验证就还是需要手动辅助,不能完全实现自动化,后续的想法是将这类需要复杂处理的网站单独拎出来,具体处理逻辑可以等做起来了再补充。

Browserless

参考文档

官网文档

概述

无头浏览器,因为上面的代码执行还需要依赖本地安装的浏览器,换个地方就跑不起来了,因此如果能调用独立的浏览器完成操作自然更好(也可以选择使用本地浏览器,但担心污染数据,不予考虑)。

部署

采用 Docker部署

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
name: browserless
services:
browserless:
container_name: browserless
image: ghcr.io/browserless/chromium
ports:
- 3003:3000 # 默认3000,因其它程序已占用,换成了 3003
environment:
- CONCURRENT=5 # 并发数:5
- TOKEN=browserless # token,必要不必需,不设置会随机生成
- TIMEOUT=120000 # 连接超时时间,默认30s ,单位ms
- HOST="192.168.7.100" # 主机绑定,默认 localhost 仅本地访问?(没试过)
- METRICS_JSON_PATH=/root/metrics.json
- DATA_DIR=/data/my-profile
- DOWNLOAD_DIR=/downloads
- PROXY_HOST=""

连接 playwright

参考文档:Browserless.op——Connecting Playwright

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
token = "browserless" # 部署 browserless 时指定的 token

def checkin():
with sync_playwright() as p:
# 如果要封装 Docker 这行必须注释掉
# browser = p.chromium.launch(headless=False)
browser = p.chromium.connect_over_cdp(
f"wss://production-sfo.browserless.io?token={TOKEN}"
)
context = browser.new_context()
page = context.new_page()
page.goto("https://www.evergarden.top")
time.sleep(10)
page.close()
context.close()
browser.close()

CDP (Chrome DevTools Protocol): 推荐使用该方式,使用 websocket 协议连接,有两种方式

  • ws:基于 HTTP
  • wss:基于 HTTPS
    因为这里使用了 Nginx 绑定https域名对 browserless 进行了反代,因此需要使用 wss。

MCP 服务器

部署及基本使用可以参考:MCP初始

简言之就是使用 playwrigth 写好工具类之后集成到 MCP 工具调用。

n8n

概述

引用官网的介绍——灵活的AI自动化工作流。通过使用原生提供的组件(LLM,http,webhook等)以及社区组件,可视化对工作流进行编排和自动化执行,简化任务编排难度。

部署

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 官方镜像
services:
n8n-official:
container_name: n8n-official
image: docker.n8n.io/n8nio/n8n
environment:
- GENERIC_TIMEZONE="Asia/Shanghai"
- TZ="Asia/Shanghai"
- N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
- N8N_RUNNERS_ENABLED=true
ports:
- 5678:5678
volumes:
- n8n_data:/home/node/.n8n
restart: unless-stopped
networks:
- app
volumes:
n8n_data:
name: n8n_data_official
networks:
app:
name: "n8n"
# external: "true"

官方开源版本已经提供了基础的功能使用,但毕竟谁会嫌弃功能多呢,因此这里可以选择使用L站大佬 deluxebear fork 的版本,启用了企业版功能并汉化。

另一个选择同样是L站佬 ChenLuo 提供了 Dockerfile 构建方式开启企业版功能,侵入性较小,但没有提供汉化,自行取舍。

不常见问题

我在使用 n8n 的时候遇到一个罕见的问题,使用 webhook 节点没有连接线,就只有一个图标,经过查阅谷歌、issue都没有找到原因,然后偶然间用ip+端口访问后发现竟然是正常的(没错,我做了反代)!后面排查发现可能是nginx上默认的CSP——Content Security Policy 入门教程-阮一峰导致的,注释掉之后就正常了。

工作流

部署只是前奏,重点还是工作流的构建,因当前我也还在摸索阶段,因此也还没有啥拿得出手的例子,其他大佬的例子可以关键字搜一下就有很多了。

Docker 镜像构建

在本地部署调试正常后,就可以考虑将项目部署到另一台可以长时间运行的服务器,影响最小自然就是构建成 Docker 镜像。

构建前提只需要有一台服务器安装了 Docker 即可,以我的情况为例,主机A进行开发,没有 docker 环境,需要将代码拷贝到具有 docker 环境的主机B进行构建,对构建流程简单说明:

  1. 导出依赖:在项目根目录,执行 pip 命令导出项目依赖
1
uv pip freeze > requirements.txt

这里有个小插曲:有几个依赖装不上(libnssutil3, libsmime3, pywin32),干脆直接删了,如果遇到了类似的问题可以试试

  1. 拷贝项目代码到安装了 docker 的环境
    拷贝项目代码即可,不需要虚拟环境。

  2. 在项目根目录下创建 Dockerfile 文件,内容参考:

1
2
3
4
5
6
7
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -U pip && pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
RUN playwright install chromium
COPY . .
CMD ["python", "main.py"]
  1. 在项目根目录打开命令行工具构建镜像:
1
docker build --platform linux/amd64 --tag webcheck:1.0.0 --progress=plain .

注意末尾有一个点,标识使用当前目录

  1. 如果顺利完成,在 Docker 仓库中就会有镜像了,执行启动:
1
docker run -d --name webcheck --dns 192.168.7.100 -p 8089:80 webcheck:1.0.0

关于开发调试过程算是提前踩了个坑说明一下,就是文件路径问题。因为我将要签到的网站信息存在sqlite.db里面,专门写了一个db_operator.py处理读写,因此在另一个功能脚本checkin.py调用时,会在checkin.py的同级目录下创建数据库文件,同理,在上面的 Dockerfile 中执行main.py入口脚本,也会在同级目录创建一个数据库文件,因此就会有三个文件(做了包区分,如果直接都放一起就没问题),结构如下:

1
2
3
4
5
6
7
├─checkin
│ └─checkin.py
├─database
│ ├─sqlite.sb
│ └─db_operator.py

└─main.py

因此为了保证一致性,在 db_operator.py 中指定的数据库名最好用绝对路径的方式,统一使用database下的数据库文件:

1
2
root_path = Path(__file__).parent
db_name = os.path.join(root_path, "sqlite.db")

n8n 流程

目前想到的流程不多,已经实现并在用的(虽然bug很是不少,偶尔还会执行失败),姑且写一下。

网站定时签到