ts
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
# Web 服务配置
|
# Web 服务配置
|
||||||
HOST=0.0.0.0
|
HOST=0.0.0.0
|
||||||
PORT=18080
|
PORT=18085
|
||||||
|
|
||||||
# 浏览器配置
|
# 浏览器配置
|
||||||
HEADLESS=false
|
HEADLESS=false
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
# Web 服务配置
|
# Web 服务配置
|
||||||
HOST=0.0.0.0
|
HOST=0.0.0.0
|
||||||
PORT=18080
|
PORT=18085
|
||||||
|
|
||||||
# 浏览器配置
|
# 浏览器配置
|
||||||
HEADLESS=false
|
HEADLESS=false
|
||||||
|
|||||||
+2
-2
@@ -50,11 +50,11 @@ COPY . .
|
|||||||
RUN mkdir -p /app/data /app/data/screenshots
|
RUN mkdir -p /app/data /app/data/screenshots
|
||||||
|
|
||||||
# 暴露端口
|
# 暴露端口
|
||||||
EXPOSE 18080
|
EXPOSE 18085
|
||||||
|
|
||||||
# 设置环境变量
|
# 设置环境变量
|
||||||
ENV HOST=0.0.0.0
|
ENV HOST=0.0.0.0
|
||||||
ENV PORT=18080
|
ENV PORT=18085
|
||||||
ENV HEADLESS=false
|
ENV HEADLESS=false
|
||||||
ENV SCREENSHOT_ON_ERROR=true
|
ENV SCREENSHOT_ON_ERROR=true
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ playwright install chromium
|
|||||||
python main.py
|
python main.py
|
||||||
```
|
```
|
||||||
|
|
||||||
服务将在 `http://localhost:18080` 启动。
|
服务将在 `http://localhost:18085` 启动。
|
||||||
|
|
||||||
### 2. 添加用户 ID
|
### 2. 添加用户 ID
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ python main.py
|
|||||||
|
|
||||||
```env
|
```env
|
||||||
HOST=0.0.0.0
|
HOST=0.0.0.0
|
||||||
PORT=18080
|
PORT=18085
|
||||||
HEADLESS=false
|
HEADLESS=false
|
||||||
SCREENSHOT_ON_ERROR=true
|
SCREENSHOT_ON_ERROR=true
|
||||||
MAX_THREADS=5
|
MAX_THREADS=5
|
||||||
@@ -180,7 +180,7 @@ docker build -t xiaohongshu-scraper .
|
|||||||
```bash
|
```bash
|
||||||
# 本地开发模式(可以看到浏览器窗口)
|
# 本地开发模式(可以看到浏览器窗口)
|
||||||
docker run -d \
|
docker run -d \
|
||||||
-p 18080:18080 \
|
-p 18085:18085 \
|
||||||
-v $(pwd)/data:/app/data \
|
-v $(pwd)/data:/app/data \
|
||||||
-e HEADLESS=false \
|
-e HEADLESS=false \
|
||||||
--name xiaohongshu-scraper \
|
--name xiaohongshu-scraper \
|
||||||
@@ -188,7 +188,7 @@ docker run -d \
|
|||||||
|
|
||||||
# 云服务器模式(无头模式)
|
# 云服务器模式(无头模式)
|
||||||
docker run -d \
|
docker run -d \
|
||||||
-p 18080:18080 \
|
-p 18085:18085 \
|
||||||
-v $(pwd)/data:/app/data \
|
-v $(pwd)/data:/app/data \
|
||||||
-e HEADLESS=true \
|
-e HEADLESS=true \
|
||||||
--name xiaohongshu-scraper \
|
--name xiaohongshu-scraper \
|
||||||
@@ -219,7 +219,7 @@ docker exec -it xiaohongshu-scraper bash
|
|||||||
| 变量 | 默认值 | 说明 |
|
| 变量 | 默认值 | 说明 |
|
||||||
|------|--------|------|
|
|------|--------|------|
|
||||||
| HOST | 0.0.0.0 | 监听地址 |
|
| HOST | 0.0.0.0 | 监听地址 |
|
||||||
| PORT | 18080 | 监听端口(范围 10000-50000)|
|
| PORT | 18085 | 监听端口(范围 10000-50000)|
|
||||||
| HEADLESS | false | 是否无头模式(云服务器设为 true)|
|
| HEADLESS | false | 是否无头模式(云服务器设为 true)|
|
||||||
| SCREENSHOT_ON_ERROR | true | 错误时截图 |
|
| SCREENSHOT_ON_ERROR | true | 错误时截图 |
|
||||||
| MAX_THREADS | 5 | 最大线程数 |
|
| MAX_THREADS | 5 | 最大线程数 |
|
||||||
@@ -266,10 +266,10 @@ environment:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 检查 Git 状态
|
# 检查 Git 状态
|
||||||
curl http://your-server:18080/api/git/status
|
curl http://your-server:18085/api/git/status
|
||||||
|
|
||||||
# 拉取最新代码
|
# 拉取最新代码
|
||||||
curl -X POST http://your-server:18080/api/git/pull
|
curl -X POST http://your-server:18085/api/git/pull
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 4. 重启容器
|
#### 4. 重启容器
|
||||||
|
|||||||
+1
-1
@@ -82,7 +82,7 @@ MAX_LOGS = 10000 # 最大日志条数
|
|||||||
|
|
||||||
# ========== Web 配置 ==========
|
# ========== Web 配置 ==========
|
||||||
WEB_HOST = os.getenv("HOST", "0.0.0.0")
|
WEB_HOST = os.getenv("HOST", "0.0.0.0")
|
||||||
WEB_PORT = int(os.getenv("PORT", "18080")) # 默认端口 18080,范围 10000-50000
|
WEB_PORT = int(os.getenv("PORT", "18085")) # 默认端口 18085,范围 10000-50000
|
||||||
SCREENSHOT_ON_ERROR = os.getenv("SCREENSHOT_ON_ERROR", "true").lower() == "true"
|
SCREENSHOT_ON_ERROR = os.getenv("SCREENSHOT_ON_ERROR", "true").lower() == "true"
|
||||||
|
|
||||||
# ========== Git 更新配置 ==========
|
# ========== Git 更新配置 ==========
|
||||||
|
|||||||
+11
@@ -15,6 +15,7 @@ from fastapi.templating import Jinja2Templates
|
|||||||
from fastapi.requests import Request
|
from fastapi.requests import Request
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from typing import List as ListType
|
from typing import List as ListType
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
from app import config
|
from app import config
|
||||||
from app.log_manager import get_log_manager
|
from app.log_manager import get_log_manager
|
||||||
@@ -61,6 +62,16 @@ log_manager = get_log_manager()
|
|||||||
# ========== FastAPI 应用 ==========
|
# ========== FastAPI 应用 ==========
|
||||||
app = FastAPI(title="小红书数据采集器", version="1.0.0")
|
app = FastAPI(title="小红书数据采集器", version="1.0.0")
|
||||||
|
|
||||||
|
# 添加 CORS 中间件
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"], # 生产环境应该指定具体域名
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
expose_headers=["*"]
|
||||||
|
)
|
||||||
|
|
||||||
# 挂载静态文件和模板
|
# 挂载静态文件和模板
|
||||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||||
templates = Jinja2Templates(directory="templates")
|
templates = Jinja2Templates(directory="templates")
|
||||||
|
|||||||
+2
-2
@@ -5,13 +5,13 @@ services:
|
|||||||
build: .
|
build: .
|
||||||
container_name: xiaohongshu-scraper
|
container_name: xiaohongshu-scraper
|
||||||
ports:
|
ports:
|
||||||
- "18085:18080" # 外部端口18081映射到容器内部18080
|
- "18085:18085" # 外部端口18085映射到容器内部18085
|
||||||
volumes:
|
volumes:
|
||||||
# 持久化数据目录(采集结果、日志、登录状态)
|
# 持久化数据目录(采集结果、日志、登录状态)
|
||||||
- ./data:/app/data
|
- ./data:/app/data
|
||||||
environment:
|
environment:
|
||||||
- HOST=0.0.0.0
|
- HOST=0.0.0.0
|
||||||
- PORT=18080
|
- PORT=18085
|
||||||
- HEADLESS=false
|
- HEADLESS=false
|
||||||
- SCREENSHOT_ON_ERROR=true
|
- SCREENSHOT_ON_ERROR=true
|
||||||
- MAX_THREADS=5
|
- MAX_THREADS=5
|
||||||
|
|||||||
+1
-1
@@ -56,7 +56,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "启动 Web 服务..."
|
echo "启动 Web 服务..."
|
||||||
echo "端口: ${PORT:-18080}"
|
echo "端口: ${PORT:-18085}"
|
||||||
echo "无头模式: ${HEADLESS:-false}"
|
echo "无头模式: ${HEADLESS:-false}"
|
||||||
echo "================================"
|
echo "================================"
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -698,7 +698,9 @@ async function openLoginQrModal() {
|
|||||||
elements.qrLoginStatus.textContent = '正在模拟登录...';
|
elements.qrLoginStatus.textContent = '正在模拟登录...';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/login/qrcode');
|
const response = await fetch('/api/login/qrcode', {
|
||||||
|
method: 'POST'
|
||||||
|
});
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
|
|||||||
@@ -201,8 +201,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="login-actions">
|
<div class="login-actions">
|
||||||
<button class="btn btn-primary" id="loginBtn">
|
<button class="btn btn-primary" id="loginBtn" title="模拟登录,不是真实扫码">
|
||||||
<span class="btn-icon">🔐</span> 登录小红书
|
<span class="btn-icon">🔐</span> 模拟登录
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-danger" id="logoutBtn">
|
<button class="btn btn-danger" id="logoutBtn">
|
||||||
<span class="btn-icon">🚪</span> 退出登录
|
<span class="btn-icon">🚪</span> 退出登录
|
||||||
|
|||||||
@@ -1,157 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>登录测试</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
max-width: 600px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
background: white;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
.btn {
|
|
||||||
background: #10b981;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 10px 20px;
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
margin: 10px;
|
|
||||||
}
|
|
||||||
.btn:hover {
|
|
||||||
background: #059669;
|
|
||||||
}
|
|
||||||
.qr-container {
|
|
||||||
margin: 20px 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.status {
|
|
||||||
padding: 10px;
|
|
||||||
margin: 10px 0;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
.status.success {
|
|
||||||
background: #d1fae5;
|
|
||||||
color: #065f46;
|
|
||||||
}
|
|
||||||
.status.error {
|
|
||||||
background: #fee2e2;
|
|
||||||
color: #991b1b;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<h2>小红书登录测试</h2>
|
|
||||||
<button class="btn" onclick="generateQR()">生成二维码</button>
|
|
||||||
<button class="btn" onclick="checkStatus()">检查状态</button>
|
|
||||||
<button class="btn" onclick="logout()">退出登录</button>
|
|
||||||
|
|
||||||
<div id="status" class="status" style="display: none;"></div>
|
|
||||||
|
|
||||||
<div id="qrContainer" class="qr-container" style="display: none;">
|
|
||||||
<p>请使用小红书App扫描二维码登录</p>
|
|
||||||
<div id="qrcode"></div>
|
|
||||||
<div id="countdown"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
let countdownInterval = null;
|
|
||||||
|
|
||||||
async function generateQR() {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/api/login/qrcode');
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
showQR(data.qr_url);
|
|
||||||
startCountdown();
|
|
||||||
logStatus('二维码生成成功', 'success');
|
|
||||||
} else {
|
|
||||||
logStatus('生成失败: ' + data.detail, 'error');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logStatus('网络错误: ' + error.message, 'error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function showQR(qrUrl) {
|
|
||||||
const container = document.getElementById('qrcode');
|
|
||||||
container.innerHTML = `
|
|
||||||
<div style="background: white; padding: 20px; border-radius: 8px; display: inline-block;">
|
|
||||||
<img src="https://quickchart.io/qr?size=200x200&text=${encodeURIComponent(qrUrl)}" alt="登录二维码" style="max-width: 200px;">
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
document.getElementById('qrContainer').style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
function startCountdown() {
|
|
||||||
let seconds = 120;
|
|
||||||
const countdownEl = document.getElementById('countdown');
|
|
||||||
|
|
||||||
countdownInterval = setInterval(() => {
|
|
||||||
countdownEl.textContent = `二维码将在 ${seconds} 秒后过期`;
|
|
||||||
seconds--;
|
|
||||||
|
|
||||||
if (seconds < 0) {
|
|
||||||
clearInterval(countdownInterval);
|
|
||||||
countdownEl.textContent = '二维码已过期';
|
|
||||||
document.getElementById('qrContainer').style.display = 'none';
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkStatus() {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/api/login/status');
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (data.logged_in) {
|
|
||||||
logStatus(`已登录 - 登录时间: ${new Date(data.login_time).toLocaleString()}`, 'success');
|
|
||||||
} else {
|
|
||||||
logStatus('未登录', 'error');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logStatus('检查失败: ' + error.message, 'error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function logout() {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/api/logout', { method: 'POST' });
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
logStatus('已退出登录', 'success');
|
|
||||||
document.getElementById('qrContainer').style.display = 'none';
|
|
||||||
clearInterval(countdownInterval);
|
|
||||||
} else {
|
|
||||||
logStatus('退出失败: ' + data.detail, 'error');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logStatus('退出失败: ' + error.message, 'error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function logStatus(message, type) {
|
|
||||||
const statusEl = document.getElementById('status');
|
|
||||||
statusEl.textContent = message;
|
|
||||||
statusEl.className = `status ${type}`;
|
|
||||||
statusEl.style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 页面加载时检查登录状态
|
|
||||||
window.onload = checkStatus;
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -0,0 +1,352 @@
|
|||||||
|
# 小红书数据采集器使用指南
|
||||||
|
|
||||||
|
## 系统概述
|
||||||
|
|
||||||
|
这是一个基于 FastAPI + Playwright 的小红书数据采集器,具有 Web 可视化监控界面,支持多线程采集、实时进度展示和持久化日志记录。
|
||||||
|
|
||||||
|
## 系统特点
|
||||||
|
|
||||||
|
- 🌐 **Web 可视化界面**:实时监控采集进度和状态
|
||||||
|
- 🔐 **模拟登录系统**:模拟登录成功状态,本地保存,24小时自动过期
|
||||||
|
- 👥 **账号管理**:支持添加、删除用户账号
|
||||||
|
- 📊 **实时日志**:分类显示操作日志,支持过滤和搜索
|
||||||
|
- 🔄 **多线程采集**:支持单线程/多线程模式切换
|
||||||
|
- 🐳 **Docker 部署**:容器化部署,支持自动更新
|
||||||
|
- 📁 **数据持久化**:所有数据本地存储,重启不丢失
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 1. Docker 部署(推荐)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 克隆或下载项目
|
||||||
|
cd /path/to/demo
|
||||||
|
|
||||||
|
# 构建并启动容器(使用18085端口)
|
||||||
|
docker-compose up -d --build
|
||||||
|
|
||||||
|
# 查看容器状态
|
||||||
|
docker-compose ps
|
||||||
|
|
||||||
|
# 查看日志
|
||||||
|
docker-compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 本地运行
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装依赖
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
# 安装浏览器
|
||||||
|
playwright install chromium
|
||||||
|
|
||||||
|
# 创建数据目录
|
||||||
|
mkdir -p data data/screenshots
|
||||||
|
|
||||||
|
# 运行程序
|
||||||
|
python main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 访问系统
|
||||||
|
|
||||||
|
打开浏览器访问:`http://localhost:18085`
|
||||||
|
|
||||||
|
## 系统界面说明
|
||||||
|
|
||||||
|
### 1. 账号管理区域
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- **添加账号**:点击添加账号按钮,输入小红书用户 ID
|
||||||
|
- **刷新账号**:重新加载账号列表和状态
|
||||||
|
- **登录状态**:
|
||||||
|
- 🔴 红色圆点:未登录
|
||||||
|
- 🟢 绿色圆点:已登录
|
||||||
|
- 显示剩余登录时间(小时:分钟)
|
||||||
|
|
||||||
|
### 2. 控制面板
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- **启动采集**:开始数据采集任务
|
||||||
|
- **停止**:停止正在进行的采集任务
|
||||||
|
- **清空日志**:清空所有历史日志
|
||||||
|
- **导出数据**:下载采集到的数据文件
|
||||||
|
- **模式选择**:
|
||||||
|
- 单线程:逐个处理账号
|
||||||
|
- 多线程:同时处理多个账号(可配置线程数)
|
||||||
|
- **线程数**:多线程模式下的并发线程数(1-10)
|
||||||
|
|
||||||
|
### 3. 进度监控
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- **整体进度**:显示采集进度条和百分比
|
||||||
|
- **活跃线程**:多线程模式下显示正在运行的线程数
|
||||||
|
- **用户列表**:显示每个用户的采集状态
|
||||||
|
- ✅ 已完成:显示采集的笔记数量
|
||||||
|
- 🔄 进行中:显示当前处理的笔记数量
|
||||||
|
- ⏳ 待处理:尚未开始处理的账号
|
||||||
|
- ❌ 失败:采集失败的账号
|
||||||
|
|
||||||
|
### 4. 日志面板
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- **日志级别过滤**:可选择查看 ALL/INFO/SUCCESS/WARNING/ERROR/DEBUG 级别的日志
|
||||||
|
- **搜索功能**:搜索特定关键词的日志
|
||||||
|
- **自动滚动**:开启后自动滚动到最新日志
|
||||||
|
- **时间戳**:显示每条日志的具体时间
|
||||||
|
- **线程标识**:多线程模式下显示日志来源线程
|
||||||
|
|
||||||
|
### 5. 错误截图
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
如果采集过程中遇到错误(如风控检测),系统会自动截图并保存:
|
||||||
|
- 截图保存在:`data/screenshots/` 目录
|
||||||
|
- 在界面中显示错误截图缩略图
|
||||||
|
- 可点击查看大图或下载所有截图
|
||||||
|
|
||||||
|
## 操作流程
|
||||||
|
|
||||||
|
### 1. 首次使用
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 启动系统
|
||||||
|
docker-compose up -d --build
|
||||||
|
|
||||||
|
# 2. 访问 Web 界面
|
||||||
|
# 打开 http://localhost:18080
|
||||||
|
|
||||||
|
# 3. 添加要采集的用户 ID
|
||||||
|
# 点击"添加账号"按钮,输入小红书用户 ID
|
||||||
|
# 格式:长字符串(如 5ef74296000000000100003a)
|
||||||
|
# 可从浏览器地址栏获取:https://www.xiaohongshu.com/user/profile/用户ID
|
||||||
|
|
||||||
|
# 4. 模拟登录
|
||||||
|
# 点击"模拟登录"按钮,系统会模拟登录成功并记录登录状态
|
||||||
|
# 注意:这是模拟登录,不是真实的扫码登录
|
||||||
|
# 登录状态有效期:24小时
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 配置采集参数
|
||||||
|
|
||||||
|
1. 选择采集模式:
|
||||||
|
- **单线程**:稳定但速度慢,适合少量账号
|
||||||
|
- **多线程**:速度快,适合大量账号(推荐 3-5 个线程)
|
||||||
|
|
||||||
|
2. 设置线程数(多线程模式):
|
||||||
|
- 建议:3-5 个线程
|
||||||
|
- 过多线程可能触发风控
|
||||||
|
|
||||||
|
### 3. 开始采集
|
||||||
|
|
||||||
|
1. 确保已添加至少一个用户账号
|
||||||
|
2. 确保已登录(显示绿色状态)
|
||||||
|
3. 点击"启动采集"
|
||||||
|
4. 系统会:
|
||||||
|
- 检查登录状态
|
||||||
|
- 读取用户列表
|
||||||
|
- 开始采集并实时显示进度
|
||||||
|
|
||||||
|
### 4. 监控采集
|
||||||
|
|
||||||
|
采集过程中可以:
|
||||||
|
- 查看实时进度和状态
|
||||||
|
- 监控日志输出
|
||||||
|
- 观察各线程工作状态
|
||||||
|
- 随时停止采集(点击"停止")
|
||||||
|
|
||||||
|
### 5. 数据导出
|
||||||
|
|
||||||
|
采集完成后可以:
|
||||||
|
- 查看每个用户的数据文件:`data/{用户ID}.json`
|
||||||
|
- 下载所有日志:点击"导出数据"
|
||||||
|
- 下载错误截图(如有)
|
||||||
|
|
||||||
|
## 用户 ID 获取方法
|
||||||
|
|
||||||
|
### 方法1:浏览器地址栏
|
||||||
|
|
||||||
|
1. 打开小红书网页版
|
||||||
|
2. 访问目标用户主页:https://www.xiaohongshu.com/user/profile/用户名
|
||||||
|
3. 从浏览器地址栏复制用户 ID:
|
||||||
|
```
|
||||||
|
https://www.xiaohongshu.com/user/profile/5ef74296000000000100003a
|
||||||
|
```
|
||||||
|
复制 `5ef74296000000000100003a` 部分
|
||||||
|
|
||||||
|
### 方法2:开发者工具
|
||||||
|
|
||||||
|
1. 在用户页面按 F12 打开开发者工具
|
||||||
|
2. 切换到 Network(网络)标签
|
||||||
|
3. 刷新页面
|
||||||
|
4. 查找包含 "user" 或 "profile" 的请求
|
||||||
|
5. 从请求 URL 中提取用户 ID
|
||||||
|
|
||||||
|
## 数据文件说明
|
||||||
|
|
||||||
|
### 输入文件
|
||||||
|
|
||||||
|
```
|
||||||
|
data/users.txt # 用户 ID 列表,每行一个
|
||||||
|
```
|
||||||
|
|
||||||
|
### 输出文件
|
||||||
|
|
||||||
|
```
|
||||||
|
data/
|
||||||
|
├── auth_state.json # 登录状态缓存
|
||||||
|
├── logs.json # 操作日志历史
|
||||||
|
├── users.txt # 用户列表
|
||||||
|
├── 5ef74296000000000100003a.json # 用户采集数据
|
||||||
|
├── 5b6c1f9e0000000001000388.json # 用户采集数据
|
||||||
|
└── screenshots/ # 错误截图目录
|
||||||
|
├── error_xxx1.png
|
||||||
|
└── error_xxx2.png
|
||||||
|
```
|
||||||
|
|
||||||
|
### 数据格式示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
// data/{用户ID}.json
|
||||||
|
{
|
||||||
|
"user_id": "5ef74296000000000100003a",
|
||||||
|
"last_updated": "2025-01-12T10:30:00",
|
||||||
|
"notes": [
|
||||||
|
{
|
||||||
|
"title": "笔记标题",
|
||||||
|
"publish_time": "2025-01-10 14:30",
|
||||||
|
"location": "上海",
|
||||||
|
"comment_count": 128,
|
||||||
|
"like_count": 5234,
|
||||||
|
"collect_count": 892,
|
||||||
|
"note_url": "https://www.xiaohongshu.com/explore/xxx"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### Q1: 为什么提示"未登录"?
|
||||||
|
|
||||||
|
A: 登录状态 24 小时后会自动过期,请:
|
||||||
|
1. 点击"模拟登录"重新登录
|
||||||
|
2. 检查 `data/auth_state.json` 文件是否存在
|
||||||
|
|
||||||
|
### Q2: 采集速度很慢怎么办?
|
||||||
|
|
||||||
|
A: 可以尝试:
|
||||||
|
1. 使用多线程模式(建议 3-5 线程)
|
||||||
|
2. 检查网络连接
|
||||||
|
3. 避免在高峰期采集
|
||||||
|
|
||||||
|
### Q3: 如何避免被封禁?
|
||||||
|
|
||||||
|
A: 系统已内置防护措施:
|
||||||
|
1. 拟人化延迟(随机 2-60 秒)
|
||||||
|
2. 平滑滚动加载
|
||||||
|
3. 多线程错开启动
|
||||||
|
4. 风控检测自动截图
|
||||||
|
5. 建议不要设置过多线程
|
||||||
|
|
||||||
|
### Q4: 数据采集不全怎么办?
|
||||||
|
|
||||||
|
A: 可能原因:
|
||||||
|
1. 网络不稳定导致加载失败
|
||||||
|
2. 笔记数量过多,滚动加载不完整
|
||||||
|
3. 小红书页面结构变化
|
||||||
|
|
||||||
|
### Q5: Docker 容器无法启动?
|
||||||
|
|
||||||
|
A: 检查:
|
||||||
|
1. Docker 是否运行
|
||||||
|
2. 端口 18080 是否被占用
|
||||||
|
3. Docker Compose 配置是否正确
|
||||||
|
|
||||||
|
## 配置说明
|
||||||
|
|
||||||
|
### 环境变量
|
||||||
|
|
||||||
|
```env
|
||||||
|
# Web 服务器配置
|
||||||
|
PORT=18080
|
||||||
|
HOST=0.0.0.0
|
||||||
|
|
||||||
|
# 浏览器配置
|
||||||
|
HEADLESS=false # 是否无头模式
|
||||||
|
SCREENSHOT_ON_ERROR=true # 错误时截图
|
||||||
|
|
||||||
|
# 多线程配置
|
||||||
|
MAX_CONCURRENT_THREADS=5 # 最大并发线程数
|
||||||
|
THREAD_MIN_DELAY=2 # 线程间最小间隔(秒)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置文件位置
|
||||||
|
|
||||||
|
```python
|
||||||
|
# app/config.py
|
||||||
|
DATA_DIR = "data" # 数据目录
|
||||||
|
SCREENSHOT_DIR = "data/screenshots" # 截图目录
|
||||||
|
MAX_CONCURRENT_THREADS = 5 # 最大并发线程数
|
||||||
|
```
|
||||||
|
|
||||||
|
## 更新和维护
|
||||||
|
|
||||||
|
### 1. 自动更新(Docker)
|
||||||
|
|
||||||
|
系统支持 Git 自动更新:
|
||||||
|
1. 代码提交到远程仓库
|
||||||
|
2. 系统自动拉取最新代码
|
||||||
|
3. 检测到 Python 代码变化时建议重启容器
|
||||||
|
|
||||||
|
### 2. 手动更新
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 拉取最新代码
|
||||||
|
git pull origin master
|
||||||
|
|
||||||
|
# 重新构建容器
|
||||||
|
docker-compose build
|
||||||
|
|
||||||
|
# 重启服务
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 数据备份
|
||||||
|
|
||||||
|
重要数据请定期备份:
|
||||||
|
```bash
|
||||||
|
# 备份数据目录
|
||||||
|
tar -czf backup-$(date +%Y%m%d).tar.gz data/
|
||||||
|
|
||||||
|
# 备份特定用户数据
|
||||||
|
cp data/*.json backup-$(date +%Y%m%d)/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 技术支持
|
||||||
|
|
||||||
|
如果遇到问题:
|
||||||
|
1. 查看 Web 界面的日志面板
|
||||||
|
2. 检查 Docker 容器日志:`docker-compose logs`
|
||||||
|
3. 查看 `data/logs.json` 文件
|
||||||
|
4. 确保网络连接正常
|
||||||
|
|
||||||
|
## 版本更新日志
|
||||||
|
|
||||||
|
### v1.0.0 (2025-01-12)
|
||||||
|
- ✅ 实现基础采集功能
|
||||||
|
- ✅ Web 可视化界面
|
||||||
|
- ✅ 多线程支持
|
||||||
|
- ✅ 实时日志系统
|
||||||
|
- ✅ 账号管理功能
|
||||||
|
- ✅ 模拟登录系统
|
||||||
|
- ✅ Docker 部署支持
|
||||||
|
- ✅ 数据导出功能
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**注意**:本工具仅供学习和研究使用,请遵守小红书的使用条款和相关法律法规。
|
||||||
Reference in New Issue
Block a user