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

})();

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

相关推荐
涛声依旧-底层原理研究所17 小时前
响应式编程:map与flatMap实战解析
java
枕星而眠17 小时前
C++ 面向对象核心机制深度解析:多态性、虚函数、虚继承与 final 类
运维·开发语言·c++·后端
Soofjan17 小时前
其它(6):分布式知识体系
后端
Sammyyyyy17 小时前
Google I/O 2026 Antigravity 更新解析与 SDK 实战指南
python·ai编程·servbay
迷茫运维路17 小时前
golang_Viper配置管理器
后端·golang
嫂子的姐夫17 小时前
047-MD5:飞卢网
爬虫·python·js逆向·逆向
babe小鑫17 小时前
2026年进入体制内学习数据分析的前景分析
信息可视化
DXM052117 小时前
第8期| 传统机器学习遥感解译:SVM & 随机森林分类全流程实操
人工智能·python·随机森林·机器学习·支持向量机·arcgis·自然语言处理
装不满的克莱因瓶17 小时前
深入PyTorch模型的训练与可视化 —— 掌握迁移学习等模型训练效果提升的办法
人工智能·pytorch·python·深度学习·神经网络·ai·迁移学习
java_cj17 小时前
Elasticsearch索引管理完全指南:从基础API到ILM生命周期管理
大数据·后端·elasticsearch·性能优化