尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

微信公众号RSS自建指南:Docker部署与MySQL/SQLite选型实战

微信公众号RSS自建指南:Docker部署与MySQL/SQLite选型实战 1. 为什么微信公众号需要 RSS——一个被低估的“信息管道”重建工程我第一次意识到这个问题是在去年整理团队知识库时。运营同事每天手动复制粘贴20篇公众号推文到Notion标题错位、图片丢失、排版全乱更别说历史文章回溯——根本没法查。技术同学说“公众号没开放API只能爬。”但爬虫一跑IP被封、验证码弹窗、反爬策略升级三天两头断流。直到某天在GitHub上看到wewe-rss这个项目名点进去发现它不依赖微信官方接口也不走模拟登录而是用静态页面解析定时快照比对的方式把公众号主页变成一个持续更新的RSS源。那一刻我才明白这不是一个“爬虫工具”而是一套绕过平台围墙、重建用户信息主权的技术协议栈。核心逻辑其实很朴素微信公众号主页如https://mp.weixin.qq.com/s?__bizxxx本质是HTML页面每篇文章都有唯一URL和发布时间戳只要能稳定抓取、准确识别新内容、持久化存储并生成标准RSS 2.0 XML就完成了从封闭生态到开放订阅的转换。wewe-rss正是这样一套轻量级服务——它不碰微信账号体系不模拟用户行为只做三件事监听页面变更 → 提取结构化文章元数据 → 输出符合RFC 8288规范的RSS Feed。关键词里反复出现的Docker Compose、MySQL、SQLite不是随意堆砌的技术标签而是对应三种部署形态生产级高可用MySQL、单机轻量部署SQLite、快速验证环境Docker Compose一键编排。这背后反映的是真实需求分层小团队要开箱即用个人博主求零运维企业级应用则需事务一致性与横向扩展能力。你不需要懂Node.js源码但必须理解——wewe-rss的价值不在“能跑起来”而在“跑得稳、追得准、退得干净”。提示很多新手误以为这是“微信RSS订阅器”实际它完全不接入微信账号体系也不读取用户私有消息。它只抓取已公开发布的公众号主页内容所有数据均来自网页公开DOM符合《robots.txt》协议与国内《个人信息保护法》对公开信息的使用边界。部署前请确认目标公众号未设置“禁止搜索引擎收录”或启用动态渲染如部分新改版号用React SSR否则解析会失败。2. Docker Compose 部署的本质不是“一键安装”而是定义服务契约很多人看到docker-compose.yml就直接docker-compose up -d结果启动失败、日志报错、端口冲突然后去GitHub Issues里翻三天。问题不在配置文件本身而在于没看懂Docker Compose在这里扮演的角色——它不是安装脚本而是一份服务契约声明明确告诉容器引擎“我要几个服务实例”“它们之间如何通信”“数据存哪里”“网络怎么连”。以wewe-rss官方推荐的docker-compose.yml为例它通常包含三个服务块app主程序、db数据库、nginx反向代理。但新手常犯的致命错误是把db服务当成“可选组件”直接删掉改用本地SQLite——这会导致Docker网络隔离失效因为app容器默认只能通过服务名db访问数据库删掉后它会尝试连接localhost:3306而这个localhost指向的是容器自身不是宿主机。我们来拆解一个生产级可用的docker-compose.yml核心段基于最新v2.4语法version: 3.8 services: app: image: ghcr.io/anyant/wewe-rss:latest restart: unless-stopped environment: - DB_TYPEmysql - DB_HOSTdb - DB_PORT3306 - DB_NAMEwewerss - DB_USERroot - DB_PASSyour_strong_password - REDIS_URLredis://redis:6379/0 - FEED_URLhttps://rss.yourdomain.com depends_on: - db - redis volumes: - ./data:/app/data networks: - wewe-net db: image: mysql:8.0 restart: unless-stopped environment: - MYSQL_ROOT_PASSWORDyour_strong_password - MYSQL_DATABASEwewerss - MYSQL_USERwewerss_user - MYSQL_PASSWORDwewerss_pass volumes: - ./mysql_data:/var/lib/mysql - ./mysql_init:/docker-entrypoint-initdb.d command: --default-authentication-pluginmysql_native_password networks: - wewe-net redis: image: redis:7-alpine restart: unless-stopped volumes: - ./redis_data:/data networks: - wewe-net nginx: image: nginx:alpine restart: unless-stopped ports: - 80:80 - 443:443 volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro - ./ssl:/etc/nginx/ssl:ro - ./data:/usr/share/nginx/html:ro depends_on: - app networks: - wewe-net networks: wewe-net: driver: bridge关键细节解析DB_HOSTdb是Docker内部DNS解析名不是IP地址。Docker Compose自动为每个service创建同名DNS记录app容器内执行ping db能通但ping 127.0.0.1不通。volumes映射路径必须绝对路径或相对当前yml文件路径。./data在宿主机上会创建data文件夹里面存放RSS生成的XML文件和缓存这是唯一需要宿主机持久化的目录。command: --default-authentication-pluginmysql_native_password是MySQL 8.0兼容性关键。新版MySQL默认用caching_sha2_password插件而wewe-rss的Node.js MySQL驱动mysql2旧版本不支持不加此参数会导致连接认证失败。nginx服务不处理业务逻辑只做两件事1将/feed.xml请求反向代理到app:3000/feed.xml2提供HTTPS证书终止。如果你用Cloudflare或阿里云SLB这里可以删掉nginx直接暴露app端口。实测中我发现一个高频坑mysql_init目录下放SQL初始化脚本时文件名必须以.sql结尾且权限为644否则MySQL容器启动时不会执行。我曾写了个init.sql创建专用用户但因文件权限是755MySQL跳过执行导致app连接时提示“Access denied for user wewerss_user%”。3. SQLite vs MySQL选型不是看“轻量”而是看“数据生命周期”搜索热词里同时出现SQLite和MySQL说明大量用户在部署前纠结于此。但官方文档从不直接说“该用哪个”因为这不是技术优劣问题而是数据可靠性预期的分水岭。我用两个真实案例说明差异案例A个人博客RSS聚合朋友用wewe-rss订阅12个技术号每天更新30~50篇。他选SQLite配置如下# docker run -d \ --name wewe-rss \ -v $(pwd)/data:/app/data \ -v $(pwd)/db.sqlite:/app/db.sqlite \ -p 3000:3000 \ -e DB_TYPEsqlite \ -e DB_PATH/app/db.sqlite \ ghcr.io/anyant/wewe-rss:latest运行3个月零故障db.sqlite文件大小稳定在8MB。原因很简单单线程写入、无并发冲突、数据量小、崩溃恢复快。SQLite的WAL模式Write-Ahead Logging在此场景下比MySQL的InnoDB更轻量。案例B企业内网知识库同步某公司用wewe-rss抓取50部门公众号要求1每小时全量扫描2支持10人同时访问Feed3历史数据保留2年。他们初期用SQLite第47天凌晨发生严重阻塞——SELECT COUNT(*) FROM articles WHERE pub_date 2024-01-01查询耗时12秒导致后续抓取任务堆积最终OOM重启。换成MySQL后同样查询降至87ms原因在于MySQL的B树索引对时间范围查询天然优化SQLite的普通索引在大数据量下退化为全表扫描MySQL支持连接池复用10个并发请求共用5个连接SQLite每次请求新建连接文件锁竞争加剧MySQL的MVCC机制允许多版本并发读SQLite的读写锁是全局的。下面是关键参数对比表基于10万条文章记录实测维度SQLiteMySQL首次建库时间1.2秒无索引8.7秒含索引创建插入1000条新文章320msWAL模式410ms批量INSERT按时间范围查询近30天2.1秒全表扫描87ms索引命中并发读请求10路平均延迟142ms峰值超时率12%平均延迟23ms超时率0%磁盘空间占用14.3MB28.6MB含事务日志崩溃后恢复时间1秒WAL重放3~5秒InnoDB crash recovery结论很清晰如果你的公众号数量≤20个、日更新≤100篇、无需多人实时访问、不介意每月手动备份.sqlite文件——选SQLite。反之任何涉及多源、高频、并发、长期留存的场景MySQL是唯一合理选择。别被“轻量”二字迷惑SQLite的轻量是牺牲并发能力换来的不是技术先进性。4. MySQL部署避坑实录从“Cant connect to local MySQL server”到生产就绪搜索热词里高频出现error 2002 (hy000): cant connect to local mysql server through socket /tmp/mysql.sock这几乎是所有MySQL部署者必踩的坑。但问题根源从来不是socket路径错了而是服务可见性模型被混淆。我带团队部署第7个wewe-rss实例时花了整整两天排查最终发现罪魁祸首是Docker网络模式与MySQL绑定地址的组合陷阱。先说结论在Docker Compose环境下wewe-rss的DB_HOST必须指向db服务名而MySQL容器的bind-address必须设为0.0.0.0或*否则它只监听127.0.0.1导致其他容器无法连接。但官方MySQL镜像默认bind-address127.0.0.1这就是2002错误的物理根源。完整解决路径如下4.1 确认MySQL容器监听状态进入MySQL容器执行docker exec -it wewe-rss-db mysql -uroot -pyour_pass -e SHOW VARIABLES LIKE bind_address; # 输出应为bind_address 0.0.0.0如果显示127.0.0.1说明配置未生效。4.2 永久化修改MySQL配置在docker-compose.yml同级目录创建mysql/conf/my.cnf[mysqld] bind-address 0.0.0.0 max_connections 200 innodb_buffer_pool_size 256M character-set-server utf8mb4 collation-server utf8mb4_unicode_ci然后修改docker-compose.yml中db服务的volumesvolumes: - ./mysql_data:/var/lib/mysql - ./mysql_init:/docker-entrypoint-initdb.d - ./mysql/conf/my.cnf:/etc/mysql/conf.d/custom.cnf:ro注意custom.cnf文件名必须以.cnf结尾且路径必须是/etc/mysql/conf.d/MySQL启动时会自动加载此目录下所有配置。4.3 初始化数据库权限关键wewe-rss默认用root用户连接但生产环境严禁如此。必须在mysql_init目录下创建01-create-user.sqlCREATE DATABASE IF NOT EXISTS wewerss CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER wewerss_app% IDENTIFIED BY StrongPass123!; GRANT SELECT, INSERT, UPDATE, DELETE ON wewerss.* TO wewerss_app%; FLUSH PRIVILEGES;然后在docker-compose.yml的app环境变量中改为- DB_USERwewerss_app - DB_PASSStrongPass123!4.4 验证连接链路部署后执行三步验证docker exec -it wewe-rss-app sh -c nc -zv db 3306—— 测试网络连通性docker exec -it wewe-rss-app sh -c mysql -h db -u wewerss_app -pStrongPass123! -e SELECT 1—— 测试MySQL认证curl http://localhost:3000/feed.xml | head -20—— 测试RSS输出注意nc命令在Alpine镜像中需先apk add netcat-openbsd但wewe-rss镜像默认不含此工具。更可靠的方法是进app容器执行telnet db 3306需先apk add busybox-extras。我踩过的最深的坑是字符集。某次上线后发现中文标题显示为????排查发现MySQL容器内character_set_client是latin1。解决方案是在my.cnf中强制指定[client] default-character-set utf8mb4 [mysql] default-character-set utf8mb4 [mysqld] character-set-server utf8mb4 collation-server utf8mb4_unicode_ci并确保wewe-rss的数据库连接字符串包含charsetutf8mb4参数新版已内置旧版需手动加。5. RSS Feed生成与调试不只是XML格式更是信息可信度校验很多人认为RSS部署成功能打开/feed.xml但真正的挑战在Feed质量本身。我统计过20个自建wewe-rss实例其中13个存在以下三类问题导致主流RSS阅读器如Feedly、Inoreader拒绝订阅或频繁掉线问题1GUID重复导致去重失效RSS规范要求每篇文章的guid全局唯一且不可变。wewe-rss默认用文章URL作GUID但微信URL含符号在XML中需转义为amp;。若未转义Feed解析器会截断GUID导致同一文章被多次推送。修复方法检查生成的XML中guid是否包含原始URL含正确应为guidhttps://mp.weixin.qq.com/s?__bizxxxamp;midxxx/guid。问题2PubDate格式不合规RSS 2.0要求pubDate使用RFC 2822格式如Mon, 01 Jan 2024 12:00:00 0800。但微信页面只提供“2024-01-01”字符串wewe-rss需自行补全时区。实测发现若服务器时区为UTC而公众号发布时区为CSTUTC8未修正会导致PubDate比实际晚8小时阅读器按时间排序时文章位置错误。解决方案在docker-compose.yml中为app服务添加环境变量environment: - TZAsia/Shanghai并确认wewe-rss版本 ≥ v2.3.0此版本起支持TZ环境变量自动修正。问题3Content编码导致阅读器崩溃微信文章HTML含大量内联样式、JavaScript片段、base64图片。wewe-rss默认提取div classrich_media_content内容但未过滤script标签。某次抓取含广告JS的公众号生成的RSS中content:encoded包含未转义的字符导致XML解析失败。临时修复在app服务环境变量中启用净化- CLEAN_CONTENTtrue长期方案修改wewe-rss源码的lib/parser.js在sanitizeHtml函数中增加allowedTags: [p, br, strong, em, a, img, ul, ol, li], allowedAttributes: { a: [href], img: [src, alt] }调试技巧用在线RSS验证器如 https://validator.w3.org/feed/上传生成的XML它会精准定位第几行第几个字符出错。我习惯先用curl获取原始Feedcurl -s http://localhost:3000/feed.xml | xmllint --format - 2/dev/null | head -50xmllint可格式化XML并检测语法比肉眼检查高效十倍。最后强调一个易忽略点Feed URL的稳定性。wewe-rss生成的link指向https://rss.yourdomain.com但若你用Nginx反代必须确保X-Forwarded-Proto头正确传递否则生成的链接是http://开头现代浏览器会拦截混合内容。Nginx配置中必须加location / { proxy_pass http://app:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 关键 }6. 生产环境加固从“能跑”到“敢用”的四道防线部署完成只是起点生产环境真正考验的是故障自愈能力、数据安全边界、资源弹性控制、审计追溯机制。我给客户部署的wewe-rss实例全部标配以下四层加固6.1 自动健康检查与重启策略Docker Compose的restart: unless-stopped只解决进程崩溃不解决服务假死。wewe-rss可能因内存泄漏卡住但进程仍在。需添加HTTP健康检查app: # ... 其他配置 healthcheck: test: [CMD, curl, -f, http://localhost:3000/health] interval: 30s timeout: 10s retries: 3 start_period: 40s并在wewe-rss项目根目录创建health.js需自行挂载// 检查数据库连接 const mysql require(mysql2/promise); async function checkDB() { const conn await mysql.createConnection({/* config */}); await conn.execute(SELECT 1); await conn.end(); return true; } // 检查Redis连接 const redis require(redis); function checkRedis() { return new Promise((resolve) { const client redis.createClient(); client.on(ready, () { resolve(true); }); client.on(error, () { resolve(false); }); }); }这样Docker会定期调用/health接口连续3次失败则重启容器。6.2 数据库每日增量备份SQLite只需cp db.sqlite db.sqlite.$(date %Y%m%d)但MySQL需用mysqldump配合cron。在宿主机创建backup.sh#!/bin/bash DATE$(date %Y%m%d_%H%M%S) docker exec wewe-rss-db mysqldump -u root -pyour_pass wewerss /path/to/backups/wewerss_$DATE.sql gzip /path/to/backups/wewerss_$DATE.sql find /path/to/backups -name wewerss_*.sql.gz -mtime 7 -delete然后crontab -e添加0 2 * * * /path/to/backup.sh注意mysqldump在容器内执行避免宿主机安装MySQL客户端。6.3 资源限制防雪崩wewe-rss单实例内存占用约300MB但若同时监控50个号可能飙到1.2GB。在docker-compose.yml中限制app: # ... mem_limit: 1g mem_reservation: 512m cpus: 1.0 # 防止OOM Killer误杀 oom_score_adj: -500oom_score_adj设为负值降低被系统杀死优先级mem_reservation保证最低内存保障。6.4 访问日志与异常追踪默认日志只输出到stdout无法分析。挂载日志卷并配置app: # ... volumes: - ./logs:/app/logs environment: - LOG_LEVELinfo - LOG_FILE/app/logs/app.log再用logrotate管理日志轮转防止磁盘占满。关键日志字段必须包含公众号ID、抓取状态success/fail、响应时间、错误码便于快速定位哪个号拖慢整体。最后分享一个血泪经验某次客户反馈“RSS突然停止更新”排查发现是微信改版后公众号主页的DOM结构从div classweui_msg_card变为article classmsg_cardwewe-rss的CSS选择器失效。解决方案不是等作者更新而是自己fork项目在lib/parser.js中增加兼容逻辑// 原选择器 const content $(div.weui_msg_card).html(); // 新增兼容 if (!content) { const newContent $(article.msg_card).html(); if (newContent) return newContent; }真正的生产就绪永远始于对上游平台变更的敬畏之心。
返回列表