← Cloudflare Queues / queues / návody
Vytvoření webového prohledávače pomocí Queues a Browser Run
Tento tutoriál vysvětluje, jak sestavit a nasadit webový crawler pomocí Queues, Browser Run, a Puppeteer.
Puppeteer je vysokoúrovňová knihovna pro automatizaci interakcí s prohlížeči Chrome/Chromium. Na každé zadané stránce crawler zjistí počet odkazů na cloudflare.com a pořídit snímek obrazovky webu, přičemž výsledky se uloží do Workers KV.
Pomocí Puppeteer můžete načíst všechny obrázky na stránce, uložit barvy použité na webu a mnoho dalšího.
Předpoklady
- Zaregistrujte si účet Cloudflare ↗.
- Nainstalujte
Node.js↗.
Správce verzí Node.js
Použijte správce verzí Node, jako je Volta ↗ nebo nvm ↗ abyste se vyhnuli problémům s oprávněními a mohli měnit verze Node.js. Wrangler, o kterém se dozvíte dále v této příručce, vyžaduje verzi Node 16.17.0 nebo novější.
1. Vytvořte novou aplikaci Workers
Pro začátek vytvořte aplikaci Worker pomocí create-cloudflare CLI ↗. Otevřete okno terminálu a spusťte následující příkaz:
npm create cloudflare@latest -- queues-web-crawlerPři nastavení vyberte následující možnosti:
- Pro S čím byste chtěli začít?, vyberte
Hello World example. - Pro Jakou šablonu chcete použít?, vyberte
Worker only. - Pro Jaký jazyk chcete použít?, vyberte
TypeScript. - Pro Chcete používat git pro správu verzí?, vyberte
Yes. - Pro Chcete nasadit svou aplikaci?, vyberte
No(před nasazením provedeme ještě několik změn).
Poté přejděte do svého nově vytvořeného adresáře:
cd queues-web-crawler2. Vytvořte KV namespace
Nejprve je nutné vytvořit úložiště KV, a to buď přes Cloudflare dashboard, nebo pomocí Wrangler CLI. V tomto návodu použijeme Wrangler CLI.
npx wrangler kv namespace create crawler_linksnpx wrangler kv namespace create crawler_screenshots🌀 Creating namespace with title "web-crawler-crawler-links"
✨ Success!
Add the following to your configuration file in your kv_namespaces array:
[[kv_namespaces]]
binding = "crawler_links"
id = "<GENERATED_NAMESPACE_ID>"
🌀 Creating namespace with title "web-crawler-crawler-screenshots"
✨ Success!
Add the following to your configuration file in your kv_namespaces array:
[[kv_namespaces]]
binding = "crawler_screenshots"
id = "<GENERATED_NAMESPACE_ID>"Přidejte vazby KV do Konfigurační soubor Wrangler
Poté do svého souboru Wrangler doplňte následující s hodnotami vygenerovanými v terminálu:
{
"kv_namespaces": [
{
"binding": "CRAWLER_SCREENSHOTS_KV",
"id": "<GENERATED_NAMESPACE_ID>",
},
{
"binding": "CRAWLER_LINKS_KV",
"id": "<GENERATED_NAMESPACE_ID>",
},
],
}[[kv_namespaces]]
binding = "CRAWLER_SCREENSHOTS_KV"
id = "<GENERATED_NAMESPACE_ID>"
[[kv_namespaces]]
binding = "CRAWLER_LINKS_KV"
id = "<GENERATED_NAMESPACE_ID>"3. Nastavte Browser Run
Nyní musíte nastavit svůj Worker pro Browser Run.
Ve svém aktuálním adresáři nainstalujte Cloudflare fork Puppeteeru a také robots-parser ↗:
npm i -D @cloudflare/puppeteernpm i robots-parserPoté přidejte binding Browser Run. Přidáním bindingu Browser Run získá Worker přístup k instanci headless Chromium, kterou budete ovládat pomocí Puppeteer.
{
"browser": {
"binding": "CRAWLER_BROWSER",
},
}[browser]
binding = "CRAWLER_BROWSER"4. Nastavte frontu
Nyní nastavíme frontu.
npx wrangler queues create queues-web-crawlerCreating queue queues-web-crawler.
Created queue queues-web-crawler.Přidejte vazby na frontu do konfigurace Wrangler
Poté do svého souboru Wrangler přidejte následující:
{
"queues": {
"consumers": [
{
"queue": "queues-web-crawler",
"max_batch_timeout": 60,
},
],
"producers": [
{
"queue": "queues-web-crawler",
"binding": "CRAWLER_QUEUE",
},
],
},
}[[queues.consumers]]
queue = "queues-web-crawler"
max_batch_timeout = 60
[[queues.producers]]
queue = "queues-web-crawler"
binding = "CRAWLER_QUEUE"Přidání max_batch_timeout 60 sekund pro konzumentskou frontu je důležitá, protože umožňuje frontě shromažďovat zprávy do dávky po delší dobu. To pomáhá spravovat Browser Run rate limity a může zvýšit efektivitu zpracováním více URL adres v jedné dávce pomocí jedné instance prohlížeče.
Výsledný soubor Wrangler by měl vypadat podobně jako ten níže.
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "web-crawler",
"main": "src/index.ts",
// Set this to today's date
"compatibility_date": "2026-08-28",
"compatibility_flags": ["nodejs_compat"],
"kv_namespaces": [
{
"binding": "CRAWLER_SCREENSHOTS_KV",
"id": "<GENERATED_NAMESPACE_ID>",
},
{
"binding": "CRAWLER_LINKS_KV",
"id": "<GENERATED_NAMESPACE_ID>",
},
],
"browser": {
"binding": "CRAWLER_BROWSER",
},
"queues": {
"consumers": [
{
"queue": "queues-web-crawler",
"max_batch_timeout": 60,
},
],
"producers": [
{
"queue": "queues-web-crawler",
"binding": "CRAWLER_QUEUE",
},
],
},
}"$schema" = "./node_modules/wrangler/config-schema.json"
name = "web-crawler"
main = "src/index.ts"
# Set this to today's date
compatibility_date = "2026-08-28"
compatibility_flags = [ "nodejs_compat" ]
[[kv_namespaces]]
binding = "CRAWLER_SCREENSHOTS_KV"
id = "<GENERATED_NAMESPACE_ID>"
[[kv_namespaces]]
binding = "CRAWLER_LINKS_KV"
id = "<GENERATED_NAMESPACE_ID>"
[browser]
binding = "CRAWLER_BROWSER"
[[queues.consumers]]
queue = "queues-web-crawler"
max_batch_timeout = 60
[[queues.producers]]
queue = "queues-web-crawler"
binding = "CRAWLER_QUEUE"5. Přidejte vazby do prostředí
Přidejte vazby do rozhraní prostředí v src/index.ts, aby TypeScript správně typoval vazby. Fronta má typ Queue<Message>, kde Message je definován v následujícím kroku.
import type { BrowserWorker } from "@cloudflare/puppeteer";
export interface Env {
CRAWLER_QUEUE: Queue<Message>;
CRAWLER_SCREENSHOTS_KV: KVNamespace;
CRAWLER_LINKS_KV: KVNamespace;
CRAWLER_BROWSER: BrowserWorker;
}6. Odešlete odkazy k procházení
Přidejte fetch() handler do Workeru pro odesílání odkazů k procházení.
type Message = {
url: string;
};
export interface Env {
CRAWLER_QUEUE: Queue<Message>;
// ... etc.
}
export default {
async fetch(req, env, ctx): Promise<Response> {
await env.CRAWLER_QUEUE.send({ url: await req.text() });
return new Response("Success!");
},
} satisfies ExportedHandler<Env>;Tímto se budou přijímat požadavky na libovolnou podcestu a tělo požadavku se předá k procházení. Očekává se, že tělo požadavku obsahuje pouze URL adresu. V produkčním prostředí byste měli ověřit, že požadavek byl typu POST požadavek a obsahuje ve svém těle správně formátovanou URL. Z důvodu jednoduchosti bylo toto vynecháno.
7. Procházejte pomocí Puppeteer
Přidejte queue() handler do Workeru pro zpracování odkazů, které odešlete.
import puppeteer from "@cloudflare/puppeteer";
import robotsParser from "robots-parser";
async queue(batch, env, ctx): Promise<void> {
let browser: puppeteer.Browser | null = null;
try {
browser = await puppeteer.launch(env.CRAWLER_BROWSER);
} catch {
batch.retryAll();
return;
}
for (const message of batch.messages) {
const { url } = message.body;
let isAllowed = true;
try {
const robotsTextPath = new URL(url).origin + "/robots.txt";
const response = await fetch(robotsTextPath);
const robots = robotsParser(robotsTextPath, await response.text());
isAllowed = robots.isAllowed(url) ?? true; // respect robots.txt!
} catch {}
if (!isAllowed) {
message.ack();
continue;
}
// TODO: crawl!
message.ack();
}
await browser.close();
},Toto je kostra pro crawler. Spouští prohlížeč Puppeteer a prochází zprávy přijaté z fronty. Načítá ze stránky robots.txt a používá robots-parser zkontroluje, zda tento web procházení povoluje. Pokud procházení povoleno není, je zpráva ack'ed, čímž se odstraní z fronty. Pokud je procházení povoleno, můžete pokračovat v procházení webu.
puppeteer.launch() je zabalen do try...catch aby bylo možné celou dávku zopakovat, pokud se nepodaří spustit prohlížeč. Spuštění prohlížeče se může nezdařit, pokud dojde k překročení limitu počtu prohlížečů na účet.
type Result = {
numCloudflareLinks: number;
screenshot: ArrayBuffer;
};
const crawlPage = async (url: string): Promise<Result> => {
const page = await (browser as puppeteer.Browser).newPage();
await page.goto(url, {
waitUntil: "load",
});
const numCloudflareLinks = await page.$$eval("a", (links) => {
links = links.filter((link) => {
try {
return new URL(link.href).hostname.includes("cloudflare.com");
} catch {
return false;
}
});
return links.length;
});
await page.setViewport({
width: 1920,
height: 1080,
deviceScaleFactor: 1,
});
return {
numCloudflareLinks,
screenshot: ((await page.screenshot({ fullPage: true })) as Buffer).buffer,
};
};Tato pomocná funkce otevře v Puppeteer novou stránku a přejde na zadanou URL adresu. numCloudflareLinks využívá funkci Puppeteer $$eval (odpovídá document.querySelectorAll) a zjistěte počet odkazů na cloudflare.com stránka. Kontrola, zda odkaz má href směřuje na cloudflare.com je obalena v try...catch k ošetření případů, kdy hrefy front nesmí být adresy URL.
Poté funkce nastaví velikost viewportu prohlížeče a pořídí snímek celé stránky. Snímek obrazovky se vrátí jako Buffer aby ji bylo možné převést na ArrayBuffer a zapsána do KV.
Chcete-li povolit rekurzivní procházení odkazů, přidejte po kontrole počtu odkazů Cloudflare fragment kódu, který bude rekurzivně odesílat zprávy z consumeru fronty zpět do fronty samotné. Příliš hluboká rekurze, ke které při procházení může dojít, způsobí, že Durable Object Subrequest depth limit exceeded. chybu. Pokud k ní dojde, je zachycena, ale odkazy se neopakují.
// const numCloudflareLinks = await page.$$eval("a", (links) => { ...
await page.$$eval("a", async (links) => {
const urls: MessageSendRequest<Message>[] = links.map((link) => {
return {
body: {
url: link.href,
},
};
});
try {
await env.CRAWLER_QUEUE.sendBatch(urls);
} catch {} // do nothing, likely hit subrequest limit
});
// await page.setViewport({ ...Poté v queue handleru zavolejte crawlPage na URL.
// in the `queue` handler:
// ...
if (!isAllowed) {
message.ack();
continue;
}
try {
const { numCloudflareLinks, screenshot } = await crawlPage(url);
const timestamp = new Date().getTime();
const resultKey = `${encodeURIComponent(url)}-${timestamp}`;
await env.CRAWLER_LINKS_KV.put(resultKey, numCloudflareLinks.toString(), {
metadata: { date: timestamp },
});
await env.CRAWLER_SCREENSHOTS_KV.put(resultKey, screenshot, {
metadata: { date: timestamp },
});
message.ack();
} catch {
message.retry();
}
// ...Tento úryvek kódu ukládá výsledky z crawlPage do příslušných KV namespace. Pokud dojde k neočekávané chybě, se URL zopakuje a znovu odešle do fronty.
Uložení časového razítka procházení do KV vám pomůže vyhnout se příliš častému procházení.
Přidejte úryvek kódu, než zkontrolujete robots.txt zkontroluje KV, zda proběhlo procházení během poslední hodiny. Tím se vypíší všechny klíče KV začínající stejnou URL (procházení stejné stránky) a zkontroluje se, zda k nějakému procházení došlo během poslední hodiny. Pokud k procházení během poslední hodiny došlo, je zpráva ack'ed a nebude se opakovat.
type KeyMetadata = {
date: number;
};
// in the `queue` handler:
// ...
for (const message of batch.messages) {
const sameUrlCrawls = await env.CRAWLER_LINKS_KV.list({
prefix: `${encodeURIComponent(url)}`,
});
let shouldSkip = false;
for (const key of sameUrlCrawls.keys) {
if (timestamp - (key.metadata as KeyMetadata)?.date < 60 * 60 * 1000) {
// if crawled in last hour, skip
message.ack();
shouldSkip = true;
break;
}
}
if (shouldSkip) {
continue;
}
let isAllowed = true;
// ...Konečný skript najdete níže.
import puppeteer, { BrowserWorker } from "@cloudflare/puppeteer";
import robotsParser from "robots-parser";
type Message = {
url: string;
};
export interface Env {
CRAWLER_QUEUE: Queue<Message>;
CRAWLER_SCREENSHOTS_KV: KVNamespace;
CRAWLER_LINKS_KV: KVNamespace;
CRAWLER_BROWSER: BrowserWorker;
}
type Result = {
numCloudflareLinks: number;
screenshot: ArrayBuffer;
};
type KeyMetadata = {
date: number;
};
export default {
async fetch(req, env, ctx): Promise<Response> {
// util endpoint for testing purposes
await env.CRAWLER_QUEUE.send({ url: await req.text() });
return new Response("Success!");
},
async queue(batch, env, ctx): Promise<void> {
const crawlPage = async (url: string): Promise<Result> => {
const page = await (browser as puppeteer.Browser).newPage();
await page.goto(url, {
waitUntil: "load",
});
const numCloudflareLinks = await page.$$eval("a", (links) => {
links = links.filter((link) => {
try {
return new URL(link.href).hostname.includes("cloudflare.com");
} catch {
return false;
}
});
return links.length;
});
// to crawl recursively - uncomment this!
/*await page.$$eval("a", async (links) => {
const urls: MessageSendRequest<Message>[] = links.map((link) => {
return {
body: {
url: link.href,
},
};
});
try {
await env.CRAWLER_QUEUE.sendBatch(urls);
} catch {} // do nothing, might've hit subrequest limit
});*/
await page.setViewport({
width: 1920,
height: 1080,
deviceScaleFactor: 1,
});
return {
numCloudflareLinks,
screenshot: ((await page.screenshot({ fullPage: true })) as Buffer)
.buffer,
};
};
let browser: puppeteer.Browser | null = null;
try {
browser = await puppeteer.launch(env.CRAWLER_BROWSER);
} catch {
batch.retryAll();
return;
}
for (const message of batch.messages) {
const { url } = message.body;
const timestamp = new Date().getTime();
const resultKey = `${encodeURIComponent(url)}-${timestamp}`;
const sameUrlCrawls = await env.CRAWLER_LINKS_KV.list({
prefix: `${encodeURIComponent(url)}`,
});
let shouldSkip = false;
for (const key of sameUrlCrawls.keys) {
if (timestamp - (key.metadata as KeyMetadata)?.date < 60 * 60 * 1000) {
// if crawled in last hour, skip
message.ack();
shouldSkip = true;
break;
}
}
if (shouldSkip) {
continue;
}
let isAllowed = true;
try {
const robotsTextPath = new URL(url).origin + "/robots.txt";
const response = await fetch(robotsTextPath);
const robots = robotsParser(robotsTextPath, await response.text());
isAllowed = robots.isAllowed(url) ?? true; // respect robots.txt!
} catch {}
if (!isAllowed) {
message.ack();
continue;
}
try {
const { numCloudflareLinks, screenshot } = await crawlPage(url);
await env.CRAWLER_LINKS_KV.put(
resultKey,
numCloudflareLinks.toString(),
{ metadata: { date: timestamp } },
);
await env.CRAWLER_SCREENSHOTS_KV.put(resultKey, screenshot, {
metadata: { date: timestamp },
});
message.ack();
} catch {
message.retry();
}
}
await browser.close();
},
} satisfies ExportedHandler<Env, Message>;8. Nasaďte svůj Worker
Chcete-li nasadit svůj Worker, spusťte následující příkaz:
npx wrangler deployÚspěšně jste vytvořili Worker, který dokáže odesílat URL adresy do fronty ke zpracování a ukládat výsledky do Workers KV.
Pro otestování Workeru můžete použít následující požadavek cURL, který pořídí snímek obrazovky této dokumentační stránky.
curl <YOUR_WORKER_URL> \
-H "Content-Type: application/json" \
-d 'https://developers.cloudflare.com/queues/tutorials/web-crawler-with-browser-run/'Viz Repozitář GitHub s kompletním tutoriálem ↗, včetně frontendu nasazeného pomocí Pages pro odesílání URL adres a zobrazení výsledků procházení.