相关信息:
- 第一章:从零开始使用Univer Clipsheet构建自己的爬虫插件(1)
- Github 开源代码: https://github.com/dream-num/univer-clipsheet
- 官方网站:https://univer.ai/zh-CN/clip-sheet
- Chrome商店下载链接:Univer clipsheet
- Edge商店下载链接:Univer clipsheet
- 教程文档: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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。