
实时检测dy直播间所有用户评论内容,支持转发到服务器
python
import uvicorn
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import List, Optional
from datetime import datetime
# 初始化应用
app = FastAPI(title="抖音弹幕接收服务")
# === 1. 配置跨域 (CORS) ===
# 必须配置,否则油猴脚本跨域请求会被拦截
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # 允许所有来源
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# === 2. 定义数据模型 (Dict结构) ===
# 对应油猴脚本发送的单条数据结构: { "username": "xxx", "content": "xxx" }
class CommentSchema(BaseModel):
username: str
content: str
ts: Optional[int] = None # 接收时间戳(可选)
# === 3. 接收接口 ===
@app.post("/api/receive_comments")
async def receive_comments(comments: List[CommentSchema]):
"""
接收评论列表,FastAPI 会自动将 JSON 解析为对象列表
"""
now = datetime.now().strftime("%H:%M:%S")
if not comments:
return {"status": "empty", "count": 0}
print(f"\n[{now}] 收到 {len(comments)} 条新弹幕:")
# 遍历打印,这里已经自动解析为对象了
for item in comments:
# 这里实现了你要的"分开处理用户名和内容"
print(f" 用户: [{item.username}] 说: {item.content}")
return {
"status": "success",
"received_count": len(comments),
"timestamp": now
}
# === 4. 启动入口 ===
if __name__ == "__main__":
# 端口设置为 5000,与脚本对应
uvicorn.run(app, host="192.168.2.114", port=8000)
油候脚本
python
// ==UserScript==
// @name 抖音直播弹幕采集(Class定位修复版)
// @namespace http://tampermonkey.net/
// @version 5.0
// @description 使用CSS Class精准定位用户名和内容,彻底修复用户名为空的问题
// @author You
// @match https://live.douyin.com/*
// @grant GM_xmlhttpRequest
// @connect *
// ==/UserScript==
(function() {
'use strict';
// === 配置区域 ===
// 确保这里的 IP 和端口与你的 Python 服务端一致
const API_URL = "http://192.168.2.114:8000/api/receive_comments";
// 抖音弹幕容器的 class (最外层)
const CONTAINER_SELECTOR = '.NkS2Invn';
// === 关键修改:根据 HTML 源码提取的精准 Class ===
// 用户名所在的 span class
const USER_NAME_SELECTOR = '.v8LY0gZF';
// 评论内容所在的 span class
const CONTENT_SELECTOR = '.cL385mHb';
// 去重缓存池
const dedupCache = new Map();
console.log(`%c[系统] 采集脚本启动 (Class定位模式)...`, "color: green; font-weight: bold");
setInterval(() => {
const batchData = [];
const now = Date.now();
// 1. 获取所有弹幕行 (使用 querySelectorAll 更快)
let messageDivs = document.querySelectorAll(CONTAINER_SELECTOR);
// 2. 遍历处理
messageDivs.forEach(div => {
// DOM级去重:如果该行已经发送过,直接跳过
if (div.getAttribute('data-sent')) return;
try {
// === 核心修复逻辑 ===
// 直接通过 class 查找用户名和内容,不再依赖位置顺序
let nameEl = div.querySelector(USER_NAME_SELECTOR);
let contentEl = div.querySelector(CONTENT_SELECTOR);
if (nameEl && contentEl) {
let uName = nameEl.innerText.trim();
let uContent = contentEl.innerText.trim();
// === 数据清洗 ===
// 1. 去除用户名末尾的中文冒号 ":" 或英文冒号 ":"
uName = uName.replace(/[::]$/, '').trim();
// 2. 过滤掉无意义的空数据
if (!uName || !uContent) return;
// === 去重逻辑 ===
let key = uName + "|" + uContent;
// 检查缓存 (30秒内重复则跳过)
if (dedupCache.has(key) && (now - dedupCache.get(key) < 30000)) {
div.setAttribute('data-sent', 'true');
return;
}
// === 加入发送队列 ===
batchData.push({
"username": uName,
"content": uContent,
"ts": now
});
// 更新缓存
dedupCache.set(key, now);
div.setAttribute('data-sent', 'true');
}
} catch (e) {
console.error("解析错误:", e);
}
});
// 3. 发送数据
if (batchData.length > 0) {
console.log(`%c[发送] 推送 ${batchData.length} 条数据`, "color: blue", batchData);
GM_xmlhttpRequest({
method: "POST",
url: API_URL,
headers: { "Content-Type": "application/json" },
data: JSON.stringify(batchData),
onload: (res) => {
if (res.status !== 200) {
console.log("%c[失败] 状态码: " + res.status, "color: red");
}
},
onerror: (err) => {
console.log("%c[网络错误] 连接被拒绝", "color: red", err);
}
});
}
// 定期清理过期的去重缓存
for (let [k, t] of dedupCache) {
if (now - t > 35000) dedupCache.delete(k);
}
}, 3000); // 3秒轮询一次
})();
本程序仅供学习参考,不涉及任何版权问题,不对任何平台构成侵权