本文详解如何在输入框失去焦点后自动消失的情况下,利用JavaScript获取联想词。通过对比直接查询DOM与使用MutationObserver监听DOM变化的两种方法,重点解析了后者在应对异步加载时的优势。文章深入分析了因服务器响应延迟导致的获取旧数据问题,并给出了使用setTimeout确保DOM渲染完成后再获取outerHTML的解决方案,帮助开发者精准捕获动态内容。
问题描述
在进行数据分析工作时,我曾尝试抓取某网站搜索联想词。当在输入框键入关键词时,系统会弹出一个包含联想词的提示框。然而,一个棘手的现象是:一旦输入框失去焦点,该提示框便自动隐藏。更令人困扰的是,通过浏览器开发者工具(F12)检查HTML结构时,发现对应的
div元素也随之从DOM树中移除。面对这种动态生成且迅速消失的元素,如何准确定位并提取其中的联想词内容,成为了亟待解决的技术难题。

解决方案
方法一:查找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);
你以为这就结束了吗?起初我也这么认为,但实际运行中仍存在问题:从第二次获取开始,得到的内容往往是上一个输入词的联想词。例如,第一次输入“猫咪”,成功获取其联想词;第二次输入“狗狗”,却仍然获取到“猫咪”的联想词;第三次输入“兔兔”,获取的却是“狗狗”的联想词。这是由什么原因导致的呢?
接下来我们分析原因。观察联想词提示框的变化过程(如下图所示),在将输入从“猫咪狸花猫”修改为“猫咪狸”的过程中,提示框会先显示与“猫咪狸花猫”相关的内容,随后请求服务器获取“猫咪狸”的联想词,最后更新提示框内容。

当页面发生变动时,监控页面变化的函数会立即执行。由于这一执行速度优于服务器请求并更新提示框内容的速度,导致我们获取到的内容实际上是上一个联想词。
既然找到了问题根源,修复也就有了方向。我们可以在 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);
}
}
});
}
}
虽然这确实解决了数据滞后问题,但我仍有一个疑问:在将“猫咪狸花猫”修改为“猫咪狸”的过程中,提示框内容发生了两次变化,但观察者为何只触发了一次?在上文的观察者配置中,我们设置了观察子节点的变化以及整个子树的变化,但这似乎不包含节点内容的直接变化。不知这样的理解是否准确?
以上就是详解如何使用JavaScript获取自动消失的联想词的详细内容,更多关于JavaScript获取联想词的资料请关注本站其它相关文章!