Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

parseCommentItemV2无法解析新版评论区 #4690

Closed
LightQuanta opened this issue Mar 21, 2024 · 7 comments
Closed

parseCommentItemV2无法解析新版评论区 #4690

LightQuanta opened this issue Mar 21, 2024 · 7 comments

Comments

@LightQuanta
Copy link
Contributor

LightQuanta commented Mar 21, 2024

对应功能

comment-api.ts

问题描述

B站新版评论区疑似是更新了vue版本啥的,使用原先的getVueData无法再正确获取新版评论的相关数据,进而导致parseCommentItemV2失效

部分相关问题:
#4646 #4643

经测试,现在可以使用

document.querySelector(".comment").__vue_app__._context.provides.store._state.data.apiData.replyList

获取评论区相关的数据

脚本版本

v2.8.9-40-g29059634f

脚本管理器及版本

Tampermonkey v5.0.1

浏览器及版本

Firefox 123.0.1

播放器版本

4.8.8-740859de

播放策略

默认

错误信息

Uncaught Error: Invalid comment item

附加截图

图片

致遇到了相同问题准备回复的人

太长不看
1、遇到了相同的问题:请用左下角的 reaction 点赞
2、实时关注该 Issue 的后续进展:请用 Issue 页上的 Subscribe 功能(在 PC 端按钮通常位于页面右侧)。
请您回复前务必确认您有在本 Issue 之外补充了其它相关信息(比如 Log)再行回复以免对他人造成不必要的困扰

完整版 由于每个 Issue 甚至整个项目都是可以订阅通知的, 对于已经有人汇报过的 Bug, 如果您也遇到了并且没有其他信息要补充, 可以直接在左下点个赞表示 +1, 不要再去新增评论。想关注后续的可以用 Subscribe,Subscribe 之后该 Issue 如果有变动都可以收到通知,如果不想被回复打扰可以使用更加进阶的 Customize 功能,勾选 "Closed" & "Reopened"。Closed 通常代表该 Issue 已被解决,极少数情况下的 Reopened 代表该 Issue 的问题重新出现。如果您新增了无意义评论,因为 Issue 这个 bug tracker 与邮件列表类似,订阅某个 Issue 的用户会在每次这个 Issue 有新的动态时收到一封新的提醒邮件。也就是说,在 Issue 中的一个不能提供新的信息的评论,相当于给所有订阅这个 Issue 的用户发了一封垃圾邮件(spam)。尤其项目的维护者也是订阅者中的一员,太多垃圾邮件会对维护者带来很多不必要的困扰。发送垃圾邮件在开源社区通常是不那么受欢迎的行为。
@the1812
Copy link
Owner

the1812 commented Apr 14, 2024

这个能获取到总的评论列表数据, 但如何将单独的评论元素和数据对应起来呢? (现在元素上还是不知道怎么取 id)

@LightQuanta
Copy link
Contributor Author

LightQuanta commented Apr 15, 2024

这个能获取到总的评论列表数据, 但如何将单独的评论元素和数据对应起来呢? (现在元素上还是不知道怎么取 id)

看了下,每一个.reply-item下的.root-reply-container里的.root-reply-avatar元素上有属性data-root-reply-id,里面的值能和评论属性上的rpidrpid_str对上,用这个应该可以?

分别可以用document.querySelector(".comment").__vue_app__._context.provides.store._state.data.apiData.replyList.res.data.replies[*].rpid_str

document.querySelectorAll(".reply-item")[*].querySelector(".root-reply-avatar").dataset.rootReplyId
来获取

@the1812
Copy link
Owner

the1812 commented Apr 16, 2024

看了下,每一个.reply-item下的.root-reply-container里的.root-reply-avatar元素上有属性data-root-reply-id,里面的值能和评论属性上的rpid和rpid_str对上,用这个应该可以?

这个是一级评论的, 评论回复的头像上仍然是这个 ID

Snipaste_2024-04-16_09-16-56

@QingXia-Ela
Copy link

@the1812 目前个人大概摸索出了两种方法来映射DOM与数据:

  1. 使用函数暴露vnode
  2. 使用 mixins

第一种方法操作:

在页面中执行该函数:

/**
 * 将评论区 VNode 进行暴露
 * @param {import('vue').VNode} vnode 评论区实例核心 VNode `window.commentContainer._vnode`
 */
function exposeVNode(vnode) {
  if (
    vnode.el &&
    // el 存在并且没有被暴露
    // 此处是为了防止所有暴露的 vnode 都被更改为模板树的 vnode
    // 模板树 vnode 接受的 props 数量很少,而以组件实例接受的 props 会很完整
    // 组件实例 vnode 永远比模板树 vnode 要先被暴露
    // 目前 (2024.04.17) 其结构如下:
    // /** 组件实例,component 属性不为 null */
    // vnode: {
    //   el: dom1,
    //   children: null
    //   component: {
    //     ...,
    //     /** 模板树,children 属性不为 null */
    //     subTree: {
    //       ...,
    //       el: dom1,
    //       children: [vnode1, vnode2, ...],
    //       component: null
    //     }
    //   }
    // }
    !vnode.el._vnode
  ) {
    vnode.el._vnode = vnode
  }
  // 该 vnode 为组件实例
  if (vnode.component?.subTree) {
    exposeVNode(vnode.component.subTree)
  }
  // 该 vnode 为模板树
  else if (Array.isArray(vnode.children)) {
    vnode.children.forEach(exposeVNode)
  }
}

这个函数会将所有的vnode映射到真实DOM上面,假如该DOM是一个组件的根节点,那么他会获得这个组件的 props

随后就可以通过暴露的 vnode 与 store 内的评论数据进行匹配:
图片
图片

这种方案的优缺点如下:

  • 相对来说比较稳定,如果 vue3 没有发生重大更新导致 vnode 结构变化则基本可以一直使用
  • 需要手动调用该方法更新 vnode,因为他不会响应数据变化
  • 该方法是从vnode根部开始更新,可能性能不佳,需要做测试

第二种方法操作

使用 mixin api,直接对每个组件进行注入

该方法已经在个人实现的评论区ip展示脚本进行实践:https://github.com/QingXia-Ela/unlock-bilibili-PcBrowser-comment/blob/main/index.js#L38

commentContainer.__vue_app__.mixin({
    ...hooks,
})

这种方案的优缺点如下:

  • 使用简单,而且自定义程度高,可以直接干涉整个组件的行为
  • 该 api 不再推荐使用,虽然个人觉得这个 api 为了向下兼容而在 vue3 大概率不会被弃用
  • mixin 会对所有的组件应用一遍,可能性能不佳,需要做测试

the1812 added a commit that referenced this issue Apr 19, 2024
@the1812
Copy link
Owner

the1812 commented Apr 19, 2024

按照第一种方法实现成功了
性能方面打开 15 条评论执行了 3842 次 exposeVNode, 目前只加了个遇到 reply item / sub reply item 提前停止递归, 性能我再研究下怎么优化

@QingXia-Ela
Copy link

QingXia-Ela commented Apr 19, 2024

按照第一种方法实现成功了 性能方面打开 15 条评论执行了 3842 次 exposeVNode, 目前只加了个遇到 reply item / sub reply item 提前停止递归, 性能我再研究下怎么优化

可以试一下用类名限定范围,比如我先获取评论区dom元素类名,然后一层层往上查,一直查到根节点,这样就有了一个类名列表,后面再递归暴露vnode的时候只需要看看类名是否存在于类名列表里面就可以了,这样应该可以稍微优化一点性能?只是一个设想,还没有尝试。

测试后效果不佳,且评论是取决于实际展示组件数量,基本所有首屏展示的评论进行递归处理后递归次数都在3500次左右

@the1812
Copy link
Owner

the1812 commented Apr 20, 2024

捣鼓了一些优化的方法, 效果还可以

  1. 递归结束的元素是已知的, 可以利用 parentElement 向上告诉节点递归的范围 (往元素上写入一个 VNodeTargets symbol)
  2. 从根元素开始向下传递 vnode:
  • children 中不属于 vnode.el[VNodeTargets] 的则不需要向下递归
  • Vue Fragment 自身对应一个空的 TextNode, 第一步中的 parentElement 递归不会经过这个节点, 需要将父元素的 VNodeTargets 复制给 Fragment 并继续向下递归
  • 模板树的 vnode 没有 vnode.el, 需要将父元素的 VNodeTargets flatMap 出子元素的 VNodeTargets 后复制到 vnode 上, 下一层的 children 直接从 vnode[VNodeTargets] 上读取
  1. 后续的新元素, 可以直接从最近的已暴露 vnode 的元素开始, 不需要再从根元素开始

测试了 22 条评论 (包含 10 条一级评论, 12 条回复), CPU 6x slowdown

优化前 优化后
递归次数 8459 93
总耗时 255.3ms 46.9ms

解除 CPU slowdown 后耗时 4.5ms

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants