晓晓的个人博客Logo
晓晓的个人博客
源网站数据采集方案之解析DOM(四)
AI提炼icon
提炼
本文介绍源网站数据采集方案,采用 Chrome Extension 实现,流程为 “打开 TAB 页 ->Dom 解析”。解析 Dom 有两种方案,本文先探讨不借助第三方库、由 Content 层用 DOM API 提取数据的方案,涉及服务端、插件端 Content 层和 Service Worker 层交互,还展示了获取百度首页搜索按钮文字的代码实现,最后提及该方案价值及后续将介绍的 “cheerio + chrome.scripting” 方案。
本文于 2025-06-25 22:53 首次发布,最后修改于 2025-08-27 13:02

写在开篇

正如《浏览器插件数据采集时,被源网站风控的应对方案(一)》一节中所阐述的:自动操控 TAB 页&解析 DOM 是采集源网站数据最稳妥的方案,主要是因为这个方案最贴近用户的操作路径,特别是当需要采集的数据都 呈现在用户的可视网页上或挂载在页面 DOM 实例上 时。

本小节主要是来展示一下:如何使用 Chrome Extension 来落地整个技术方案。

技术方案设计与说明

事实上,技术方案的整个流程比较简单,只有“ Chrome Extension 打开 TAB 页 -> DOM 解析”两个步骤。

其中,“自动操控 TAB 页”的流程十分明确,并且,上一小节中已经详细阐述过实现过程。而,“DOM 解析”当下有两种比较稳妥的方案:

一种是 Chrome.scripting 结合第三方库 cheerio 的应用【推荐】;

另一种是,不借助第三方库&由 Content 层直接使用浏览器的 DOM API进行提取。

为了主题明确和精减单文的篇幅,所以本文我们先来讲讲第二种方案。那,为什么不先说推荐的方案呢?因为有对比才有伤害

解析dom技术方案流程图
解析dom技术方案流程图

从以上流程图可以看出,本次方案涉及到的端比较多,有 服务端、插件端的各个 Content 层、插件端的 Service Worker 层

其中,服务端的作用是 数据存储、与发起请求的 Content 层进行数据交互、接收来自 Service Worker 的数据请求

另外,几个端的交互流程是这样的(请配合流程图一起享用)

  1. 发起请求的 TAB-Content 层携带批次 ID、采集链接 向 Service Worker 发起“批量采集数据”的请求;

    1. 一般情况下,批次 ID 由服务端产生,批次 ID 与采集链接的关联关系也存储在服务端;

    2. 为什么不直接在插件端进行存储呢?因为多个 Content 层与一个 Service Worker 交互会有并发问题:当 Service Worker 打开多个TAB后,即与多个 Content 进行交互,而多个 Content 层回复 Service Worker 的时机不可控问题会导致消息到达 Service Worker 的时机具有并发性,所以必须要有一个脱离这个环境的存储系统去做存储这件事情。

  2. 当 Service Worker 接收到“批量采集数据”的消息后,就开始逐个:

    1. 打开 TAB 页;

    2. 等待10秒之后,使用短连接通信通道向新 TAB 页的 Content 层发送消息(一般来说,一个网站10秒还打不开的,就可以直接废掉了)

    3. 新 TAB 页的 Content 层接收到消息之后使用浏览器的 DOM API 进行数据提取;

    4. 无论提取成功与否,新 TAB 页的 Content 层都将数据信息反馈给 Service Worker;

    5. Service Worker 接收到信息之后向服务端抛出结果(需要注意,这里处理的进程和上面 Service Worker 操控 TAB页 的进程是不同的两个进程,后续会在代码中有清晰的体现);

    6. 服务端存储结果,该结果会由发起请求的 Content 层拉取得到;

  3. 当 Service Worker 将对所有可以新开的 TAB 操作过以上流程之后,需要定时监听所有的 TAB 页是否已经处理完毕,如果处理完毕的,则需要向服务端进行反馈“插件采集完毕”的消息;

    1. 这一步的目的是为了防止:插件端出现采集超时,服务端无法感知的问题;

  4. 在 Service Worker 循环处理数据采集的同时,发起请求的 Content 层需要定时去服务端拉取当前批次的结果;

以上流程还算清晰,复杂的是:因为涉及到的端比较多,所以里面出现的异常也会比较多,导致我们至少需要处理以下两个问题:

1. 如果 Service Worker 的消息发出去了,但是 Content 层没有接收到怎么办?

这种情况属于消息丢失问题,一般,兜底策略设计为:Service Worker 中记录消息发出时间,当达到一定的时间之后还没得到采集结果的将其判为“采集失败”;

2. Content 层接收到了消息,但是 DOM 节点一直没有被加载出来 或 经过了较长时间(超过10秒)才被加载出来;

这种情况一般出现在源网站的采集节点是异步生成的情况下,这里的兜底策略很简单:循环延迟一段时间进行获取即可,当最后达到较长时间(比如,半分钟,具体还是要看源网站DOM 节点被生成的实际情况)还未获取到的,那么直接向Service Worker抛出“采集失败”的消息,否则,什么时候获取到了,什么时候向 Service Worker 回复“采集成功”的消息;

这里需要注意,不管是采集失败还是采集成功,理论上都应该向 Service Worker 回复一条携带“采集状态”的消息,一方面,之前被插件操控打开的TAB页需要再由 Service Worker 去关闭,另一方面,从交互层面出发,产品设计中应该注意在用户操作中应该给予用户明确且具有引导性的提示,而不是突然的中断导致用户不知所措;

方案落地与 Demo 展示

讲完了理论,当然要展示下代码实力了(代码按照上述技术流程依次展示)。本次我们的demo需求是:获取百度首页的搜索按钮中的文字“百度一下”。

首先,当然是在发起TAB页中插入一个发起请求的按钮了。

接下来,是 Service Worker 处理“批量采集数据”的主线逻辑(次线逻辑是接收来自Content层的采集结果),这块逻辑的执行时间跨度是跨过了 Content 层解析 DOM、Service Worker 处理解析结果的时间线的,需要好好理解哦~

接着,展示Content层接收到Service Worker的短连接消息后,循环采集数据并上报给Service Worker。

最后就是 Service Worker 接收 Content 层的采集结果。

以上代码实践后,就会有这样的效果:

写在最后

如果你看完&理解了本小节所有内容的话,其实会发现:这个方案除了数据交互流程异步处理关键点比较难理解、流程繁琐之外也还行吧,但是在“大道至简”潮流下,我会更推荐下一小节展示的“cheerio + chrome.scripting”方案,那个方案的所有处理都在 Service Worker 层、完全可以同步去处理数据(同步对开发者更友好一些)。

那么,今天这个方案是真的没有用处了么?不是的,我在前几小节中曾讲到过一个案例:源网站改写了 window.fetch API,直接将风控加密相关操作绑定在改写的 window.fetch 函数中,即,只要我们在插件的 Content 层调用 window.fetch 函数时,自动在请求中加入通过源网站的风控系统的参数。所以,结合我们今天的技术方案,是不是只要把 DOM 解析的那段逻辑改成调用 fetch 函数去请求相关接口就可以了呢?

好啦,本小节到此结束,让我们期待一下下一节吧~

与君共勉

学习多维的知识点,恰似安全领域中 “攻” 与 “防” 的深度融合、不可分割 —— 唯有知己知彼,方能百战不殆。

0个赞
喜欢就点个赞吧