# runoob-download **Repository Path**: xiaomans/runoob-download ## Basic Information - **Project Name**: runoob-download - **Description**: 将菜鸟教程 (runoob.com) 的文章抓取为本地 Markdown 文件,支持在线预览、批量管理和打包下载。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-05-13 - **Last Updated**: 2026-05-15 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Markdown 下载器 将任意技术网站的文章抓取为本地 Markdown 文件,支持在线预览、批量管理和打包下载。 **已优化适配网站:** - ✅ 菜鸟教程 (runoob.com) - ✅ W3Cschool (w3cschool.cn) - ✅ 通用适配:支持任意网站文章爬取 ## 技术栈 | 层级 | 技术 | |------|------| | 运行时 | Node.js >= 18 | | Web 框架 | Express 5.x | | HTTP 请求 | axios | | HTML 解析 | cheerio | | HTML→Markdown | Turndown + turndown-plugin-gfm | | 编码处理 | iconv-lite | | ZIP 打包 | archiver / adm-zip | | 前端 | 原生 HTML + CSS + JavaScript(零框架依赖) | ## 配置说明 创建 `config.json` 文件可自定义配置: ```json { "port": 3002, // 服务端口,默认 3002 "downloadsDir": "downloads", // 文件存储目录 "requestTimeout": 30000 // 请求超时时间(毫秒) } ``` **启动方式:** - Windows: `start.bat` 或 `start.bat 8080` - Linux: `./start.sh` 或 `./start.sh 8080` - 直接启动: `npm start` > 命令行指定的端口优先级高于配置文件 ## 项目结构 ``` runoob-download/ ├── public/ # 前端静态文件 │ ├── index.html # 主页面 │ ├── app.js # 前端交互逻辑 │ └── styles.css # 样式 ├── src/ # 源代码目录 │ ├── adapters/ # 网站适配器层 │ │ ├── base.js # 基础适配器抽象类 │ │ ├── runoob.js # 菜鸟教程适配器 │ │ ├── w3cschool.js # W3Cschool 适配器 │ │ ├── generic.js # 通用网站适配器 │ │ └── index.js # 适配器管理器 │ ├── services/ # 业务服务层 │ │ ├── fileManager.js # 文件管理服务 │ │ └── downloader.js # 下载服务 │ ├── routes/ # 路由层 │ │ ├── files.js # 文件路由 │ │ └── download.js # 下载路由 │ └── utils/ # 工具函数 │ ├── config.js # 配置文件 │ └── helpers.js # 工具函数 ├── downloads/ # 下载的 Markdown 文件存储目录(运行时自动创建) ├── server.js # 服务端入口 ├── package.json # 项目配置与依赖 ├── start.bat # Windows 一键启动脚本 ├── start.sh # Linux 一键启动脚本 ├── .gitignore └── README.md ``` ## 架构说明 ``` ┌─────────────────────────────────────────────────────────┐ │ 浏览器客户端 │ │ ┌────────────────────┐ ┌────────────────────────────┐ │ │ │ 输入 URL 抓取 │ │ 文件列表 / 预览 / 批量操作 │ │ │ └────────┬───────────┘ └────────────┬───────────────┘ │ └───────────┼──────────────────────────┼──────────────────┘ │ HTTP API │ ┌───────────┴──────────────────────────┴──────────────────┐ │ Express 服务器 (server.js) │ │ │ │ ┌──────────────┐ ┌────────────┐ ┌──────────────────┐ │ │ │ POST /api/ │ │ GET/DELETE│ │ 静态文件服务 │ │ │ │ download │ │ /api/files│ │ express.static │ │ │ └──────┬───────┘ └──────┬─────┘ └──────────────────┘ │ │ │ │ │ │ ┌──────┴─────────────────┴───────────────────────────┐ │ │ │ 核心处理层 │ │ │ │ fetchRunoobArticle → cheerio 解析 → Turndown 转换 │ │ │ └──────────────────────┬─────────────────────────────┘ │ │ │ │ │ ┌──────────────────────┴─────────────────────────────┐ │ │ │ downloads/ 目录 │ │ │ │ *.md 文件读写 │ │ │ └────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────┘ ``` **请求处理流程:** 1. 用户在前端输入菜鸟教程文章 URL,提交到 `POST /api/download` 2. 服务端通过 axios 抓取页面 HTML(使用 arraybuffer + iconv-lite 处理编码) 3. cheerio 加载 HTML,定位文章正文区域(`.article-intro` / `.article-body`) 4. 清洗 DOM(移除脚本、样式、广告等,补全图片和链接的相对路径) 5. Turndown 将清洗后的 HTML 转换为 Markdown 6. 写入 `downloads/` 目录,文件名取自 URL slug 7. 返回 Markdown 内容给前端展示 --- ## Windows 部署 ### 环境要求 - Windows 10 / Windows 11 / Windows Server 2016+ - Node.js >= 18([官网下载](https://nodejs.org),安装时勾选 **Add to PATH**) ### 方式一:一键启动(推荐) 双击 `start.bat`,脚本会自动完成以下步骤: | 步骤 | 说明 | |------|------| | 检查 Node.js | 验证是否安装且版本 >= 18 | | 安装依赖 | 若 `node_modules` 不存在则自动 `npm install` | | 端口检测 | 检测目标端口是否被占用并给出提示 | | 启动服务 | 启动 Express 服务并自动打开浏览器 | ```cmd start.bat # 默认端口 3000 start.bat 8080 # 指定端口 8080 ``` ### 方式二:手动启动 ```cmd # 1. 安装依赖(仅首次) npm install # 2. 启动服务 npm start # 默认端口 3000 set PORT=8080 && npm start # 指定端口 8080 ``` ### 方式三:后台运行(PM2) 适合需要在 Windows 上长期后台运行的场景: ```cmd npm install -g pm2 pm2 start server.js --name runoob-download pm2 save pm2 startup # 设置开机自启 ``` ### 访问 浏览器打开 `http://localhost:3000`(或你指定的端口) --- ## Linux 部署 ### 环境要求 - Ubuntu 20.04+ / CentOS 7+ / Rocky Linux / AlmaLinux - Node.js >= 18 ### 方式一:一键启动(推荐) 使用项目自带的 `start.sh`: ```bash chmod +x start.sh ./start.sh # 默认端口 3000 ./start.sh 8080 # 指定端口 8080 ``` 脚本会自动检测 Node.js 环境、安装依赖、检测端口占用并启动服务。 ### 方式二:手动启动 ```bash # 安装依赖 npm install # 启动 npm start # 默认端口 3000 PORT=8080 npm start # 指定端口 8080 ``` ### 方式三:PM2 守护进程(生产环境推荐) PM2 提供后台运行、自动重启、日志管理、零停机重启等功能。 ```bash # 全局安装 npm install -g pm2 # 启动 pm2 start server.js --name runoob-download # 指定端口 PORT=8080 pm2 start server.js --name runoob-download # 开机自启 pm2 startup pm2 save # 常用管理命令 pm2 list # 查看所有进程 pm2 logs runoob-download # 查看日志 pm2 restart runoob-download pm2 stop runoob-download pm2 delete runoob-download ``` ### 方式四:systemd 服务 不使用 PM2 时,可以用系统原生 systemd 管理。 创建 `/etc/systemd/system/runoob-download.service`: ```ini [Unit] Description=Runoob Downloader After=network.target [Service] Type=simple User=www-data WorkingDirectory=/opt/runoob-download Environment=PORT=3000 ExecStart=/usr/bin/node server.js Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target ``` 启用服务: ```bash sudo systemctl daemon-reload sudo systemctl enable runoob-download sudo systemctl start runoob-download sudo systemctl status runoob-download # 查看运行状态 ``` ### Nginx 反向代理(可选) 将服务挂载到 80/443 端口并配置域名和 HTTPS: ```nginx server { listen 80; server_name your-domain.com; client_max_body_size 10m; location / { proxy_pass http://127.0.0.1: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; } } ``` ```bash sudo ln -s /etc/nginx/sites-available/runoob-download /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx ``` ### 防火墙配置 ```bash # 直接暴露 Node.js 端口 sudo ufw allow 3000/tcp # Ubuntu sudo firewall-cmd --add-port=3000/tcp --permanent && \ sudo firewall-cmd --reload # CentOS # 使用 Nginx 反代时只需开放 80/443 sudo ufw allow 80/tcp sudo ufw allow 443/tcp ``` ### 部署方案对比 | 方案 | 适用场景 | 优点 | |------|----------|------| | PM2 + Nginx | 生产环境 | 进程守护、日志、零停机重启 | | systemd + Nginx | 生产环境(无额外依赖) | 系统原生、资源占用低 | | 直接 node 启动 | 开发/测试 | 最简部署、调试方便 | --- ## API 文档 所有接口返回 JSON,成功时 `ok: true`,失败时 `ok: false` 并附带 `error` 字段。 ### GET /api/files 列出已下载的 Markdown 文件。 ```json { "ok": true, "files": [ { "name": "codex-install.md", "size": 12345, "updatedAt": "2026-05-13T10:30:00.000Z" } ] } ``` ### GET /api/files/:name 读取单个 Markdown 文件的完整内容。 ### GET /api/files/:name/download 触发浏览器下载单个 Markdown 文件。 ### POST /api/files/archive 将选中的多个文件打包为 ZIP 下载。 ```json { "files": ["codex-install.md", "python-intro.md"] } ``` ### DELETE /api/files 批量删除文件。 ```json { "files": ["codex-install.md"] } ``` ### POST /api/download 抓取菜鸟教程文章并保存为 Markdown。 ```json { "url": "https://www.runoob.com/codex/codex-install.html" } ``` 响应: ```json { "ok": true, "title": "Codex 安装指南", "fileName": "codex-install.md", "markdown": "# Codex 安装指南\n\nSource: ...\n\n...", "downloadUrl": "/api/files/codex-install.md/download" } ``` ## 安全限制 | 限制项 | 说明 | |--------|------| | 域名白名单 | 仅允许抓取 `runoob.com` 域名 | | 协议限制 | 仅允许 HTTP / HTTPS | | 路径穿越防护 | 文件名过滤 `../`、`\` 等非法字符 | | 请求体限制 | 最大 1MB |