我正在构建一个工具,其核心结构是:向 Cloudflare Worker 发出 AJAX 请求,Cloudflare Worker 获取 HTML 数据,然后返回它。
所以步骤是:
第一个很简单:我收到工作人员的响应,并将返回的 HTML 插入隐藏的
<div>
中的某个位置,然后解析它。
不过,我更愿意选择第二个的原因是,在将 HTML 从 Cloudflare Worker 传送回客户端时不要浪费带宽,因为原始页面有很多不相关的膨胀。我的意思是,例如,原始页面看起来像这样:
<div class="very-much-bloat" id="some-other-bloat" useful_parameter ="value">
<div id="some-other-irrelevant-info" id="really-great-id">
something that I need
</div>
</div>
我所需要的只是,例如
{
"really-great-id" : "something that I need",
"useful_parameter" : "value"
}
如果我采取第一步,在浏览器中解析它会非常简单,但是我会浪费带宽来提供大量稍后处理的信息。
但是,如果第二个涉及使用复杂的库,则可能不是一种可行的方法,因为每个请求的最大执行时间为 10 毫秒(这是 Cloudflare 上的免费计划,否则就足够了:每天 100,000 个请求比我使用这个应用程序可能需要的更多)。
问题是:是否有任何有效的方法可以在 Cloudflare Worker 上解析 HTML,而不打破 10 毫秒的时间限制?使用worker获得的页面大小约为10-100 KB,解析的数据大小约为1-10KB(大约比原始数据小10倍)。虽然我知道 100KB 听起来可能不是很多,但它仍然主要是垃圾,最好尽快过滤。
Cloudflare Workers 目前不支持 DOM API。但是,它支持可能适合您的替代 HTML 解析 API:
HTMLRewriter
https://developers.cloudflare.com/workers/runtime-apis/html-rewriter/
此 API 与 DOM 不同,因为它以流式方式运行:当 HTML 数据从服务器流入时,将调用 JavaScript 回调,而无需一次性将整个文档保存在内存中。如果它适合您的用例,与基于 DOM 的解决方案相比,它可能会让您响应更快并使用更少的资源。
HTMLRewriter
本身使用的 CPU 时间甚至不计入 10 毫秒的限制——只有回调所花费的时间才算在内。因此,如果您仔细设计回调,保持在限制范围内应该没有问题。
请注意,
HTMLRewriter
主要设计用于支持在 HTML 文档流经时对其进行修改。但是,让它使用文档并生成完全不同类型的数据(例如 JSON)应该不会太难。本质上,您可以设置重写器,以便丢弃“重写”的 HTML,并且您可以让回调单独填充一些其他数据结构或写入表示最终结果的其他流。
这是一个完整的例子
// worker.js
async function extractValues(html) {
const result = {
"useful_parameter": "",
"really-great-id": ""
}
console.log('Input HTML:', html)
// Convert HTML string to bytes
const encoder = new TextEncoder()
const bytes = encoder.encode(html)
// Create response directly from bytes
const htmlResponse = new Response(bytes)
let currentId = null
const rewriter = new HTMLRewriter()
.on('div', {
element(element) {
console.log('Found div element:', element.getAttribute('id'), element.getAttribute('useful_parameter'))
if (element.getAttribute('useful_parameter')) {
result.useful_parameter = element.getAttribute('useful_parameter')
console.log('Set useful_parameter:', result.useful_parameter)
}
const id = element.getAttribute('id')
if (id === 'really-great-id') {
currentId = id
console.log('Found really-great-id div')
}
},
text(text) {
if (currentId === 'really-great-id') {
const trimmed = text.text.trim()
if (trimmed) {
result['really-great-id'] = trimmed
console.log('Set really-great-id text:', trimmed)
}
}
}
})
await rewriter.transform(htmlResponse).text()
console.log('Final result:', result)
return result
}
export default {
async fetch(request) {
// Test HTML
const testHtml = `
<div class="very-much-bloat" id="some-other-bloat" useful_parameter="value">
<div id="really-great-id">
something that I need
</div>
</div>
`
try {
const result = await extractValues(testHtml)
console.log('Worker response:', result)
// Return the result as JSON
return new Response(JSON.stringify(result, null, 2), {
headers: {
'content-type': 'application/json',
'access-control-allow-origin': '*'
}
})
} catch (error) {
console.error('Error:', error)
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: {
'content-type': 'application/json',
'access-control-allow-origin': '*'
}
})
}
}
}
退货
{ "useful_parameter": "值", "really-great-id": "我需要的东西" }