Convex 的火笼
从 Convex 函数搜索网络并运行 持久地爬行 其进展和页面存储在您的 Convex 数据库中, 因此您的用户界面订阅 而不是投票结果.
const firecrawl = new FirecrawlClient(components.firecrawl);
// One-shot
const page = await firecrawl.scrape(ctx, "https://firecrawl.dev", {
formats: ["markdown"],
});
// Durable: returns immediately, pages stream into your database
const { crawlId } = await firecrawl.startCrawl(ctx, {
url: "https://docs.firecrawl.dev",
options: { limit: 50 },
onComplete: internal.myModule.indexCrawledPages,
});
你得到的东西
scrape | 一个 URL ~ 标记下, HTML, 截图, 摘要, 结构化的 JSON |
map | 每个网站的URL,快速 |
search | 网页搜索, 可选删除每个结果 |
startCrawl | 整个网站,在你的数据库中跟踪: 反应状态,页面到达时,一个完成回调 |
爬行是其中的一部分 而不是几个 fetch 打电话 A 类
爬行一个大网站需要几分钟时间 超过任何单个动作
组件拥有 a crawls 横行和一行 pages 表格,由 Firecrawl 推进
并揭露Convex的查询.
你的客户端通过正常的订阅机制获得现场进展.
安装
npm install @firecrawl/firecrawl-convex
将组件添加到您的应用程序中, 通过已键入组件 env 连接 API 密钥 :
// convex/convex.config.ts
import { defineApp } from "convex/server";
import { v } from "convex/values";
import firecrawl from "@firecrawl/firecrawl-convex/convex.config";
const app = defineApp({
env: {
FIRECRAWL_API_KEY: v.string(),
FIRECRAWL_WEBHOOK_SECRET: v.optional(v.string()),
},
});
app.use(firecrawl, {
// Mounts the webhook route at <your-site>/firecrawl/webhook.
// Required for crawls in webhook mode.
httpPrefix: "/firecrawl/",
env: {
FIRECRAWL_API_KEY: app.env.FIRECRAWL_API_KEY,
FIRECRAWL_WEBHOOK_SECRET: app.env.FIRECRAWL_WEBHOOK_SECRET,
},
});
export default app;
然后在您的部署上设置密钥 :
npx convex env set FIRECRAWL_API_KEY fc-your-key
# Recommended: from the Firecrawl dashboard → Advanced → webhook secret
npx convex env set FIRECRAWL_WEBHOOK_SECRET whsec-your-secret
npx convex dev
找到钥匙 firecraw.dev (英语)。 。 。 .
扫描、地图、搜索
从您的动作中调用组件 。 在你的应用软件里保留一个包装
认证、授权和费率限制归属——组件看不到
ctx.auth编辑 示例应用程序 显示整个图案 :
(单位:千美元) requireUser 每个付费端点的闸门,以及一个应用软件的 craw ~ 用户表
在任何爬行读取、取消或删除之前选中.
// convex/web.ts
import { v } from "convex/values";
import { FirecrawlClient } from "@firecrawl/firecrawl-convex";
import { action } from "./_generated/server";
import { components } from "./_generated/api";
const firecrawl = new FirecrawlClient(components.firecrawl);
export const scrapePage = action({
args: { url: v.string() },
handler: async (ctx, args) => {
await requireUser(ctx);
return await firecrawl.scrape(ctx, args.url, {
formats: ["markdown", { type: "json", prompt: "Extract the pricing table" }],
onlyMainContent: true,
maxAge: 3_600_000, // reuse Firecrawl's cache for an hour
});
},
});
export const siteUrls = action({
args: { url: v.string() },
handler: (ctx, args) => firecrawl.map(ctx, args.url, { limit: 500 }),
});
export const searchWeb = action({
args: { query: v.string() },
handler: (ctx, args) =>
firecrawl.search(ctx, args.query, {
limit: 5,
scrapeOptions: { formats: ["markdown"] },
}),
});
选项名称匹配 Firecrawl v2 API (英语)
并且未受污染,故《活字典》是:
他们做什么。 输入的表面覆盖共同选项;少数企业和
目标profile, (中文(简体) ), threatProtection, (中文(简体) ), auditMetadata搜索 enterprise(中文(简体) )
被故意排除在类型之外。 那些,还有别的什么活生生的船
在这个包裹赶上之前,通过 extra数字 :
await firecrawl.scrape(ctx, url, { extra: { threatProtection: { mode: "off" } } });
scrape, (中文(简体) ), map,以及 search 返回 API 响应为-(已确认为
v.any() 而不是重塑的形状,所以是新的
在Firecrawl号登船的当天就可使用反应场。 这个 FirecrawlClient
方法给您 TypeScript 类型而不是这些响应.
持久爬行
export const crawlDocs = action({
args: { url: v.string() },
handler: async (ctx, args) => {
const userId = await requireUser(ctx);
return await firecrawl.startCrawl(ctx, {
url: args.url,
options: {
limit: 100,
includePaths: ["^/docs/.*"],
scrapeOptions: { formats: ["markdown"], onlyMainContent: true },
},
onComplete: internal.web.onCrawlComplete,
context: { userId },
});
},
});
startCrawl 返回时 { crawlId, jobId } 马上 从之:
// Live status: total, completed, pageCount, creditsUsed, error
export const crawlProgress = query({
args: { crawlId: v.string() },
handler: (ctx, args) => firecrawl.getCrawl(ctx, args.crawlId),
});
// Pages as they land — works with usePaginatedQuery
export const crawlPages = query({
args: { crawlId: v.string(), paginationOpts: paginationOptsValidator },
handler: (ctx, args) => firecrawl.listPages(ctx, args),
});
function CrawlView({ crawlId }: { crawlId: string }) {
const crawl = useQuery(api.web.crawlProgress, { crawlId });
const { results } = usePaginatedQuery(
api.web.crawlPages,
{ crawlId },
{ initialNumItems: 25 },
);
return (
<>
<progress value={crawl?.pageCount ?? 0} max={crawl?.total ?? 1} />
<ul>{results.map((p) => <li key={p._id}>{p.url}</li>)}</ul>
</>
);
}
完成回调
onComplete 需要一个 内部突变 您的应用程序,运行一次
爬行到达终点状态. context 回来时没有动静,所以你可以
携带用户代号,文档代号,随便什么.
export const onCrawlComplete = internalMutation({
args: {
crawlId: v.string(),
jobId: v.optional(v.string()),
status: v.union(v.literal("completed"), v.literal("failed"), v.literal("cancelled")),
pageCount: v.number(),
unstored: v.optional(v.number()),
error: v.optional(v.string()),
context: v.optional(v.any()),
},
handler: async (ctx, args) => {
if (args.status !== "completed") return;
if (args.unstored) console.warn(`${args.unstored} pages were too large to store`);
// e.g. hand the pages to an embedding pipeline
await ctx.scheduler.runAfter(0, internal.rag.indexCrawl, {
crawlId: args.crawlId,
userId: args.context?.userId,
});
},
});
Webhook 模式对投票模式
| webhook (默认) | 民意调查 | |
|---|---|---|
| 页面是如何到达的 | Firecrawl推车 crawl.page 事件; 缓慢的监察民意调查 发现任何下降 | 状态结束点 退到30后 |
| 要求数 | httpPrefix 在互联网上可以找到Firecrawl | 无 |
| 用它当 | 正常云层部署 | 本地 dev, 在防火墙后自行托管 |
await firecrawl.startCrawl(ctx, { url, mode: "poll" });
本地的 Convex 部署无法从 Firecrawl 的服务器上到达, 所以使用
mode: "poll" ——或下文所描述的模拟服务器,它能提供
网络呼唤到你的地方部署.
对分娩进行了两次检查: X-Firecrawl-Signature HMAC( 任何时候)
FIRECRAWL_WEBHOOK_SECRET 和每个缩写符号 组件手
Firecrawl 在它注册网页时。 检查失败的交付就是
以401被否决,没有任何文字.
其他爬行操作
await firecrawl.getCrawlByJobId(ctx, jobId); // look up by Firecrawl's id
await firecrawl.listCrawls(ctx, { status: "scraping", limit: 20 });
await firecrawl.getPage(ctx, { crawlId, url });
await firecrawl.cancelCrawl(ctx, crawlId); // action
await firecrawl.deleteCrawl(ctx, crawlId); // mutation: crawl + its pages
await firecrawl.resumeCrawl(ctx, crawlId); // mutation: see below
在 ~250 状态检查后,该组件停止检查爬行(大约2次)
投票时数或一天的网路监督员),并敲定为 failed
有解释错误,所以订阅者 onComplete 从不离开
等待着永不报到的工作 如果工作还在继续
萤火虫, resumeCrawl 追踪到它留下的地方.
过 storeContent: false 改为 startCrawl 只记录 URL 和元数据 ——
当您仅仅想要回调时, 或者当您在其它地方重新调试内容时, 将会有所帮助 .
很高兴知道
- 错误 为:
ConvexError携带{ code, status, path, message }这样 您可以在error.data.status === 402(贷出)或429(比率有限). 暂时失效(408起、425起、429起、5xx起) 3次有退后,荣誉Retry-After。 。 。 . - 证件限制. Convex 文档上限为 1MB, 所以每页都编入预算
在UTF-8字节中,整个文档在被写入之前。 文本和链接
列表被切换; 截图, 提取
json,或changeTracking块 不合的被全部丢出; 超大小metadata回到它身边 关键钥匙。 任何一套truncated: true在页面上。 如果 Firecrawl 键盘 返回仍然无法存储的页面,计数显示为unstored打开 爬来爬去onComplete有效载荷——绝不沉默。 对于非常大 公司,考虑一下storeContent: false还有你自己的存储器. - 贷项 显示为
creditsUsed在爬行和每页metadata。 。 。 . - 自办Firecrawl: 声明
FIRECRAWL_API_URL在组件内 并用你的例子指着它. - 运行时间 : 一切运行在 Convex 运行时间——没有
"use node",没有 捆绑的SDK。 请求直接到v2 REST API.
测试
在自己的测试中注册组件:
import { convexTest } from "convex-test";
import firecrawl from "@firecrawl/firecrawl-convex/test";
import schema from "./schema";
const modules = import.meta.glob("./**/*.*s");
export function initConvexTest() {
process.env.FIRECRAWL_API_KEY = "fc-test-key";
const t = convexTest(schema, modules);
firecrawl.register(t);
return t;
}
然后结巴 fetch * 返回罐装的Firecrawl反应——见
example/convex/example.test.ts。 。 。 .
试试吧
这个 example/ 应用程序练习每个切入点,以及
example/mock-firecrawl.mjs 代表着
Firecrawl API — 包括签名的 Webhook 交付 — 这样您就可以观看一个 craw
在不使用信贷的情况下取得进展:
npm install
# terminal 1
FIRECRAWL_WEBHOOK_SECRET=whsec-mock npm run dev:mock
# terminal 2
npx convex env set FIRECRAWL_API_KEY fc-mock-key
npx convex env set FIRECRAWL_API_URL http://127.0.0.1:4242
npx convex env set FIRECRAWL_WEBHOOK_SECRET whsec-mock
npm run dev
# terminal 3
npx convex env set DEMO_ALLOW_ANONYMOUS true # local CLI demo only
npx convex run example:startCrawl '{"url":"https://mock.test","limit":3}'
npx convex run example:myCrawls '{}'
npx convex run example:reports '{}'
用真键交换( 并放下) FIRECRAWL_API_URL)击出活API. 见
example/README.md 用于完整命令列表.
发展
npm run dev # component codegen + build watcher + convex dev
npm test # vitest, including type tests
npm run typecheck
npm run lint
npm run dev 运行三步
正在编写文件 描述,在
顺序:组件代码源、软件包构建,然后 convex dev 以应用为例.
许可证
麻省理工学院