![](http://www.zhousa.com/zb_users/theme/quietlee/style/noimg/9.jpg)
阅读《2024 中国开源开发者报告》赢大奖,扫码申请享特权
![](https://www.oschina.net/img/hot3.png)
相关信息:
前情提要: 从零开始使用 Univer Clipsheet 构建自己的爬虫插件(1) [Github 开源代码]: GitHub - dream-num/univer-clipsheet: A powerful Chrome extension for web scraping [官方网站]: Univer | ClipSheet [Chrome商店]: Chrome 插件商店-Clipsheet [Edge商店 ] : Edge 插件商店-Clipsheet 教程文档: 教程文档前言
在之前的章节我们完成了爬虫插件项目的搭建与 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 的回调函数捕获点击的元素。 ![](https://oscimg.oschina.net/oscnet//7e4de0247b72bb263a8664ec6fbb727b.png)
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); } } });
![](https://oscimg.oschina.net/oscnet//41962ab9636b7aa01a4b5934fef7ef8c.png)
结语
以上是构建爬虫插件第二章的所有内容, 这章继续对爬虫插件的功能做了丰富,后续的章节会继续给我们的爬虫插件开发更强大的功能, 例如采集操作的自动化等,感兴趣的可以继续关注~- 想直接体验Univer Clipsheet功能的可直接下载商店版本: https://chromewebstore.google.com/detail/univer-clipsheet-an-ai-dr/mbcpbomfebacllmjjefeifejbbibbope
- 有问题或任何建议也可以直接到我们 github 仓库下提 issue: https://github.com/dream-num/univer-clipsheet
还没有评论,来说两句吧...