dy直播间评论保存插件

实时检测dy直播间所有用户评论内容,支持转发到服务器

server.py

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秒轮询一次

})();

本程序仅供学习参考,不涉及任何版权问题,不对任何平台构成侵权

相关推荐
涡能增压发动积21 小时前
同样的代码循环 10次正常 循环 100次就抛异常?自定义 Comparator 的 bug 让我丢尽颜面
后端
云烟成雨TD21 小时前
Spring AI Alibaba 1.x 系列【6】ReactAgent 同步执行 & 流式执行
java·人工智能·spring
Csvn21 小时前
🌟 LangChain 30 天保姆级教程 · Day 13|OutputParser 进阶!让 AI 输出自动转为结构化对象,并支持自动重试!
python·langchain
Wenweno0o21 小时前
0基础Go语言Eino框架智能体实战-chatModel
开发语言·后端·golang
于慨21 小时前
Lambda 表达式、方法引用(Method Reference)语法
java·前端·servlet
swg32132121 小时前
Spring Boot 3.X Oauth2 认证服务与资源服务
java·spring boot·后端
tyung21 小时前
一个 main.go 搞定协作白板:你画一笔,全世界都看见
后端·go
2501_9449347321 小时前
产品策划需要哪些数据分析能力?如何用数据验证需求优先级
信息可视化·数据挖掘·数据分析
gelald21 小时前
SpringBoot - 自动配置原理
java·spring boot·后端
殷紫川21 小时前
深入理解 AQS:从架构到实现,解锁 Java 并发编程的核心密钥
java