相关信息:

  1. 第一章从零开始使用Univer Clipsheet构建自己的爬虫插件(1)
  2. Github 开源代码https://github.com/dream-num/univer-clipsheet
  3. 官方网站https://univer.ai/zh-CN/clip-sheet
  4. Chrome商店下载链接Univer clipsheet
  5. Edge商店下载链接Univer clipsheet
  6. 教程文档https://xakbyahbro.feishu.cn/docx/YU2BdTIqYo4rtIxZxLOctWvxnCh

前言

在之前的章节我们完成了爬虫插件项目的搭建与 univer clipsheet 代码的引入,用 clipsheet 提供的能力自动对当前网页中的表格进行探测。
如果大家没有看过第一章的可以参考第一章的内容进行项目初始化。

本章会继续丰富插件的功能,支持手动选择元素生成table (表格数据) ,以及拦截 Ajax 请求从响应体中解析 table 的能力。
我也建了一个存放教程代码的仓库:GitHub - siam-ese/univer-clipsheet-tutorial-code: 用 univer-clipsheet从零开始构建爬虫插件代码案例, 可以直接通过该仓库开始插件的开发。

1. 手动选择元素

有些时候在网页中自动探测的表格可能跟你想采集的表格并不匹配,这时候我们需要提供给用户自己手动选择元素能力,类似 Chrome Devtools 审查元素的功能。clipsheet也提供了这个代码能力,我们回到 content/package.json中新增一行依赖,并重新执行 pnpm i安装依赖启动项目。

"@univer-clipsheet-core/ui": "workspace:*"

然后回到Chrome extension开发中的 content-script 代码中, 也就是项目 pages/content/src/index.ts的位置,我们加入如下代码,启用 clipsheet 提供的选择元素的功能。

import { ElementInspectService } from '@univer-clipsheet-core/ui';
const elementInspectService = new ElementInspectService();
elementInspectService.shadowComponent.onInspectElement((element) => {
  // 点击页面元素时,会触发该回调函数
  console.log('Inspect Element:', element);
})
 
setTimeout(() => {
  // 激活元素检查功能
  elementInspectService.shadowComponent.activate()
})

@univer-clipsheet-core/ui 引入和激活 elementInspectService之后,我们可以看到我们鼠标 hover 的元素会高亮蓝色,并且在元素上点击后, 会执行 onInspectElement 的回调函数捕获点击的元素。
图片

以上我们已经完成了手动元素的选择,我们接下来只要匹配离他最近的类表格元素即可,继续对 onInspectElement 回调函数中加入代码

const last = <T>(arr: T[]) => arr[arr.length - 1];
elementInspectService.shadowComponent.onInspectElement((element) => {
  // 获取最近匹配到的table标签元素
  const tableElement = last(checkElementTable(element));
  // 获取最近匹配到的ExtractionParams对象
  const tableExtractionParams = last(checkElementApproximationTable(element));
  // 点击页面元素时,会触发该回调函数
  console.log('Inspect Element:', element);
  if (tableElement) {
    // 如果点击的元素是table标签,则生成IInitialSheet对象
    const sheet = generateSheetByElement(tableElement as HTMLTableElement);
    // 打印表格数据
    console.log('Inspect Table:', sheet);
    // 最近匹配到的类表格元素
    console.log('Inspect Table success with element:', tableElement);
  } else if (tableExtractionParams) {
    const sheet = generateSheetByExtractionParams(tableExtractionParams);
    // 打印表格数据
    console.log('Inspect Table:', sheet);
    // 最近匹配到的类表格元素
    console.log('Inspect Table success with element:', tableExtractionParams.element);
  } else {
    console.log('Not found table with element', element);
  }
})

上面的代码的代码都有注释,可以了解具体做了些什么,主要是对元素进行最近 table 标签匹配或者类 table 元素的匹配,然后匹配成功后生成 initialSheet 的数据对象。

2. Ajax响应拦截

接下来我们对网页中的 ajax 响应体做一个拦截,并从可能是json 结构的响应体的尝试解析出 initialSheet 数据。我们先创建一个 ajax-intercept.ts 文件在 pages/content/src下,并写入如下代码

function interceptRequest(onResponse: (response: any) => void) {
    const XHR = XMLHttpRequest;
    const _fetch = fetch;
 
    const onReadyStateChange = async function (this: XMLHttpRequest) {
        if (this.readyState === 4) {
            onResponse(this.response);
        }
    };
    // 拦截 XMLHttpRequest
    const innerXHR: typeof XMLHttpRequest = function () {
        const xhr = new XHR();
        xhr.addEventListener('readystatechange', onReadyStateChange.bind(xhr), false);
        return xhr;
    };
    innerXHR.prototype = XHR.prototype;
    Object.entries(XHR).forEach(([key, val]) => {
         // @ts-ignore
        innerXHR[key] = val;
    });
 
    // 拦截 fetch
    const innerFetch: typeof _fetch = async (resource, initOptions) => {
        const getOriginalResponse = () => _fetch(resource, initOptions);
        const fetchedResponse = getOriginalResponse();
 
        fetchedResponse.then((response) => {
            if (response instanceof Response) {
                try {
                    response.clone()
                        .json()
                        .then((res) => onResponse(res))
                        .catch(() => {
                            // Do nothing
                        });
                } catch (err) {}
            }
        });
        return fetchedResponse;
    };
    window.XMLHttpRequest = innerXHR;
    window.fetch = innerFetch;
}

这里 interceptRequest 函数里我们通过改下全局的 XHR 对象以及 fetch 函数对 ajax 的响应体做了一个拦截,但是该方法想要在 插件的 content-script 环境中生效,需要用插入 script 标签的形式来引入。所以我们会将 ajax-intercept.ts这个文件先做一次打包,然后在 content-script中引入。
因为用 script 标签引入的原因,ajax-intercept与 content-script不在一个上下文中,因此我们用 window.postMessage来完成它们之间的通信。
继续在 ajax-intercept中加入代码

function serializeToJSON(response: unknown) {
    try {
        return JSON.parse(JSON.stringify(response));
    } catch {
        return null;
    }
}
interceptRequest((res) => {
    // 发送消息到content script
    postMessage({
        type: 'AJAX_INTERCEPT_MESSAGE',
        response: serializeToJSON(res),
    });
});

接下来我们要把该 ts 文件打包成 js, 在 package.json中加入一条命令,执行该命令会把 ajax-intercept打包到 public 文件夹下,public 文件夹会copy 到最终插件打包的文件中。

"build:ajax-interceptor": "npx esbuild src/ajax-interceptor.ts --bundle --outfile=public/ajax-interceptor.js" 

打包成功后,回到 pages/content/src/index.ts 使用如下代码加载 ajax-intercept

// 启动AJAX拦截器
function startAjaxIntercept(scriptSrc: string, onMessage: (message: unknown) => void) {
  const script = document.createElement('script');
  script.src = scriptSrc;
  script.onload = () => {
    window.addEventListener('message', event => {
      const message = event.data;
      if (message.type === 'AJAX_INTERCEPT_MESSAGE') {
        onMessage(message.response);
      }
    });
  };
  document.body.appendChild(script);
  return () => {
    script.remove();
  };
}
 
 
startAjaxIntercept(chrome.runtime.getURL('content/ajax-interceptor.js'), res => {
  if (res) {
    console.log('AJAX response', res);
    const sheets = ajaxJsonToTable([res as UnknownJson]);
    if (sheets.length > 0) {
      console.log('AJAX sheets from response', sheets);
    }
  }
});

图片

插件重新加载后,打开控制台并刷新页面, 可以看到部分响应已被捕获并打印在控制台。

结语

以上是构建爬虫插件第二章的所有内容, 这章继续对爬虫插件的功能做了丰富,后续的章节会继续给我们的爬虫插件开发更强大的功能, 例如采集操作的自动化等,感兴趣的可以继续关注~以上是构建爬虫插件第二章的所有内容, 这章继续对爬虫插件的功能做了丰富,后续的章节会继续给我们的爬虫插件开发更强大的功能, 例如采集操作的自动化等,感兴趣的可以继续关注~

想直接体验Univer Clipsheet功能的可直接下载商店版本:https://chromewebstore.google.com/detail/univer-clipsheet-an-...
有问题或任何建议也可以直接到我们 github 仓库下提 issue: https://github.com/dream-num/univer-clipsheet


Uni_clipsheet
1 声望0 粉丝