破解动态网页:如何用JavaScript获取自动消失的联想词

简介: 【6月更文挑战第2天】在获取动态加载的联想词时,遇到问题:输入搜索词后弹出的联想词框在失去焦点时消失,使得直接定位HTML元素困难。解决方案包括:1. 查找DOM节点:在弹框出现时记录其类名或ID,然后通过JavaScript获取元素HTML内容。但由于元素加载有延迟,需在输入框获取焦点后延迟执行,例如使用`setTimeout`。2. 使用`MutationObserver`监视DOM变化:创建观察者监听特定类的元素出现,当元素加载时打印其HTML。为避免获取旧内容,回调函数中使用`setTimeout`确保DOM完全渲染。

背景

前几天在做数据分析时,我尝试获取某网站上输入搜索词后的联想词,输入搜索词后会弹出一个显示联想词的框。有趣的是,当我尝试通过按F12定位这个弹框在HTML中的位置时,输入框失去焦点后,联想词弹框就自动消失了。我观察到 HTML 代码中div元素也从代码中消失了。这种情况下,我该如何才能准确地定位这个元素弹框并获取其中的联想词呢?

3cd8fa33727b41d1b28837d6274150c9~tplv-k3u1fbpfcp-jj-mark_0_0_0_0_q75.gif

解决方案

方法一:查找DOM节点

这算是最简单的操作,通过调试框可以看到网页dom结构的变化,当触发联想词弹框时,记下弹框对应标签的类名或者id,然后查找得到这个标签的html代码,从中解析出联想词。

const elementByClassName = document.querySelector(".luoxiaodou");
if (elementByClassName) {
   
   
    const htmlContent = elementByClassName.innerHTML;
    console.log(htmlContent);
}

输入框获取焦点时,联想词弹框出现,输入框失去焦点时,联想词弹框消失。咱们先给输入框添加焦点,弹框出现时,运行上面的代码获取节点对应的html内容。

这个想法很好,但是这里有一个问题:就是元素的加载需要时间,需要请求服务端获取数据,然后再加载出来。,如果是输入框获取焦点后立即去查找元素,大概率获取的结果是空,所以这中间要有延迟的时间,咱们就说这坑大家有没有踩过吧。

方法二:使用MutationObserver来监视DOM变化

我还有两个想法,第一个是:咱们能不能拦截一下网络,然后从服务器的响应里头捞点联想词出来?我个人觉得这招儿挺靠谱的,但目前还没来得及试试。等我尝试之后写一下关于监听网络请求获取联想词的内容,咱们就先记个账,到时候一定补上。

另一种方式就是使用MutationObserver来监视DOM变化,可以在控制台中编写一个JavaScript函数,当包含特定类的元素出现在DOM中时,打印出该元素的HTML内容。为此,你可以使用MutationObserver来监视DOM变化。

而我选择了另一种可能的解决方案,就是利用MutationObserver这个JavaScript API。一旦检测到DOM中有特定类的元素出现,就立即打印出它的HTML内容。MutationObserver 是 JavaScript 自带的 API,它用于监视DOM树的变化。这是浏览器原生支持的功能,包括我们常用的Chrome在内。正好在插件开发中使用。

咱们可以创建一个函数,传参是标签选择器和一个回调函数,当获取到变化的网页时,调用回调函数解析获取联想词。这里使用一个map集合,可以保证管理多个监视器,以下是实现代码:

const observers = new Map()

/**
 * 启动监视指定选择器的元素变化。
 * @param {string} selector - CSS选择器,可以是类名(以`.`开头)或ID(以`#`开头)。
 * @param {function} callback - 回调函数,当符合选择器的元素添加到DOM时调用。
 */
function startObserving(selector, callback) {
   
   
  /**
   * 观察者回调函数,监视DOM树的变化。
   * @param {MutationRecord[]} mutationsList - DOM变化记录的列表。
   * @param {MutationObserver} observer - 观察者实例。
   */
  const observerCallback = function (mutationsList, observer) {
   
   
    for (const mutation of mutationsList) {
   
   
      if (mutation.type === 'childList') {
   
   
        mutation.addedNodes.forEach((node) => {
   
   
          if (node.nodeType === Node.ELEMENT_NODE) {
   
   
            if (selector.startsWith('#') && node.id === selector.slice(1)) {
   
   
              callback(node.outerHTML)
            } else if (selector.startsWith('.') && node.classList.contains(selector.slice(1))) {
   
   
              callback(node.outerHTML)
            }
          }
        })
      }
    }
  }
  // 创建一个观察者实例并开始观察指定的目标节点和配置
  const observer = new MutationObserver(observerCallback)
  // 观察配置,配置对象指定我们希望观察子节点的变化(`childList: true`)以及观察整个子树中的变化(`subtree: true`)
  const config = {
   
    childList: true, subtree: true }
  // 观察整个文档,所以将目标节点设置为`document.body`。
  observer.observe(document.body, config)
  // 将观察者存储在Map中
  observers.set(selector, observer)

  // 设置定时器,指定时间后自动停止观察并删除观察者,回收资源
  setTimeout(() => {
   
   
    stopObserving(selector)
  }, 5 * 60 * 1000)
}


/**
 * 停止监视指定选择器的元素变化。
 * @param {string} selector - CSS选择器,可以是类名(以`.`开头)或ID(以`#`开头)。
 */
function stopObserving(selector) {
   
   
    const observer = observers.get(selector);

    if (observer) {
   
   
        observer.disconnect();
        observers.delete(selector);
    }
}

export {
   
    startObserving, stopObserving };

然后就是在使用的地方,导入这些方法,传一下参数就可以了,有一点要注意:回调函数中的参数是获取到的网页信息

import {
   
    startObserving, stopObserving } from './element-observer.js';

// 通过类名启动监视
startObserving('.xiaodou-class', (htmlContent) => {
   
   
    console.log('Element with class added:', htmlContent);
});

// 通过ID启动监视
startObserving('#xiaodou-id', (htmlContent) => {
   
   
    console.log('Element with ID added:', htmlContent);
});

// 停止监视
setTimeout(() => {
   
   
    stopObserving('.xiaodou-class');
    stopObserving('#xiaodou-id');
}, 6000);

你是不是以为这就结束?我当时就是这么想的,但是还是有问题:从第二次开始获取到的内容是上一个输入词的联想词。假设第一次输入“猫咪”,然后获取的“猫咪”的联想词,第二次输入“狗狗”,但是获取的还是“猫咪”的联想词,第三次输入“兔兔”,这次获取的是“狗狗”的联想词,这是哪里出问题了呢?

接着咱们来分析一下原因,首先观察一下联想词弹框的变化,比如下图,修改”猫咪狸花猫“到”猫咪狸“的这个过程中,联想词弹框先出来跟”猫咪狸花猫“相关的内容, 再请求服务器获取”猫咪狸“的联想词,再更新弹框内容。

afc1281f232440b5a16cd5a96d942464~tplv-k3u1fbpfcp-jj-mark_0_0_0_0_q75.gif

当页面变动时,监控页面变化的函数会立即执行,这个执行速度是优于请求服务器更新弹框内容的速度,所以这里导致获取的内容是是上一个联想词的。

发现了问题,就好修改了,可以尝试在 MutationObserver 回调函数中使用 setTimeout,以确保 DOM 完全渲染后再获取 outerHTML

/**
* 观察者回调函数,监视DOM树的变化。
* @param {MutationRecord[]} mutationsList - DOM变化记录的列表。
* @param {MutationObserver} observer - 观察者实例。
*/
const observerCallback = function (mutationsList, observer) {
   
   
for (const mutation of mutationsList) {
   
   
  if (mutation.type === 'childList') {
   
   
    mutation.addedNodes.forEach((node) => {
   
   
      if (node.nodeType === Node.ELEMENT_NODE) {
   
   
        if (selector.startsWith('#') && node.id === selector.slice(1)) {
   
   
          // 使用 setTimeout 确保 DOM 完全渲染后获取 outerHTML
          setTimeout(() => callback(node.outerHTML), 1000);
        } else if (selector.startsWith('.') && node.classList.contains(selector.slice(1))) {
   
   
          // 使用 setTimeout 确保 DOM 完全渲染后获取 outerHTML
          setTimeout(() => callback(node.outerHTML), 1000);
        }
      }
    });
  }
}

这里确实解决了问题,但是我还有一个疑问:为什么在修改”猫咪狸花猫“到”猫咪狸“的这个过程中,弹框内容有两次变化,观察者只触发了一次?,在上面的观察者配置是希望观察子节点的变化以及观察整个子树中的变化,这个变化不包含节点内容的变化,不知道这样理解对不对?


做完这些,我觉得监听网络请求或更好操作一点,不知道监听网络请求又会遇到哪些坑……

目录
相关文章
|
30天前
|
存储 JavaScript 前端开发
使用JavaScript构建动态交互式网页:从基础到实践
【10月更文挑战第12天】使用JavaScript构建动态交互式网页:从基础到实践
69 1
|
2月前
|
编解码 前端开发 JavaScript
javascript检测网页缩放演示代码
javascript检测网页缩放演示代码
|
2月前
|
Web App开发 JavaScript 前端开发
添加浮动按钮点击滚动到网页底部的纯JavaScript演示代码 IE9、11,Maxthon 1.6.7,Firefox30、31,360极速浏览器7.5.3.308下测试正常
添加浮动按钮点击滚动到网页底部的纯JavaScript演示代码 IE9、11,Maxthon 1.6.7,Firefox30、31,360极速浏览器7.5.3.308下测试正常
|
12天前
|
JavaScript
js实现简洁实用的网页计算器功能源码
这是一款使用js实现简洁实用的网页计算器功能源码。可实现比较基本的加减乘除四则运算功能,界面简洁实用,是一款比较基本的js运算功能源码。该源码可兼容目前最新的各类主流浏览器。
21 2
|
1月前
|
存储 JavaScript 前端开发
【JavaScript】网页交互的灵魂舞者
本文介绍了 JavaScript 的三种引入方式(行内、内部、外部)和基础语法,包括变量、数据类型、运算符、数组、函数和对象等内容。同时,文章还详细讲解了 jQuery 的基本语法和常用方法,如 `text()`、`html()`、`val()`、`attr()` 和 `css()` 等,以及如何插入和删除元素。通过示例代码和图解,帮助读者更好地理解和应用这些知识。
15 1
【JavaScript】网页交互的灵魂舞者
用CSS+JavaScript打造网页中的选项卡
用CSS+JavaScript打造网页中的选项卡
|
1月前
|
Web App开发 缓存 前端开发
前端RAG:使用Transformers.js手搓纯网页版RAG(二)- 基于qwen1.5-0.5B
本文继续探讨了RAG的后半部分,通过在浏览器中运行qwen1.5-0.5B模型实现了增强搜索全流程。然而,由于浏览器与模型性能限制,该方案更适合研究、离线及高隐私场景。文章提供了完整的前端代码,让读者能够动手尝试。此外,详细介绍了代码框架、知识库准备、模型初始化及问答实现等步骤,并展示了实际运行效果。受限于当前技术,除非在离线或高隐私环境下,网页大模型的应用仍需进一步优化。
|
1月前
|
存储 自然语言处理 文字识别
纯前端RAG:使用Transformers.js实现纯网页版RAG(一)
本文将分两部分教大家如何在网页中实现一个RAG系统,本文聚焦于深度搜索功能。通过浏览器端本地执行模型,可实现文本相似度计算和问答匹配,无需依赖服务器。RAG搜索基于高维向量空间,即使不完全匹配也能找到意义相近的结果。文中详细介绍了如何构建知识库、初始化配置、向量存储及相似度计算,并展示了实际应用效果。适用于列表搜索、功能导航、文档查询及表单填写等多种场景。
165 0
|
2月前
|
Web App开发 前端开发 JavaScript
HTML/CSS/JS学习笔记 Day3(HTML--网页标签 下)
HTML/CSS/JS学习笔记 Day3(HTML--网页标签 下)
|
2月前
|
JavaScript 前端开发
用JavaScript编程控制网页上checkbox选择状态:全选、全部取消、反选
用JavaScript编程控制网页上checkbox选择状态:全选、全部取消、反选