Files
firecrawl/apps/api/src/controllers/v1/crawl.ts
T

196 lines
5.6 KiB
TypeScript
Raw Normal View History

import { Response } from "express";
2024-08-06 15:24:45 -03:00
import { v4 as uuidv4 } from "uuid";
2024-08-20 14:19:20 -03:00
import {
CrawlRequest,
crawlRequestSchema,
CrawlResponse,
RequestWithAuth,
2024-11-07 20:57:33 +01:00
toLegacyCrawlerOptions,
2024-08-20 14:19:20 -03:00
} from "./types";
import {
addCrawlJob,
addCrawlJobs,
crawlToCrawler,
lockURL,
lockURLs,
saveCrawl,
StoredCrawl,
} from "../../lib/crawl-redis";
import { logCrawl } from "../../services/logging/crawl_log";
import { getScrapeQueue } from "../../services/queue-service";
import { addScrapeJob } from "../../services/queue-jobs";
import { logger as _logger } from "../../lib/logger";
2024-08-28 12:46:59 -03:00
import { getJobPriority } from "../../lib/job-priority";
2024-09-01 13:44:36 -03:00
import { callWebhook } from "../../services/webhook";
2024-11-07 20:57:33 +01:00
import { scrapeOptions as scrapeOptionsSchema } from "./types";
2024-08-06 15:24:45 -03:00
2024-08-20 14:19:20 -03:00
export async function crawlController(
req: RequestWithAuth<{}, CrawlResponse, CrawlRequest>,
res: Response<CrawlResponse>
) {
const preNormalizedBody = req.body;
req.body = crawlRequestSchema.parse(req.body);
2024-08-20 14:19:20 -03:00
const id = uuidv4();
const logger = _logger.child({ crawlId: id, module: "api/v1", method: "crawlController", teamId: req.auth.team_id, plan: req.auth.plan });
logger.debug("Crawl " + id + " starting", { request: req.body, originalRequest: preNormalizedBody, account: req.account });
2024-08-06 15:24:45 -03:00
await logCrawl(id, req.auth.team_id);
2024-08-06 15:24:45 -03:00
2024-11-07 20:57:33 +01:00
let { remainingCredits } = req.account!;
const useDbAuthentication = process.env.USE_DB_AUTHENTICATION === 'true';
if(!useDbAuthentication){
remainingCredits = Infinity;
}
2024-08-20 14:39:52 -03:00
2024-11-07 20:57:33 +01:00
const crawlerOptions = {
...req.body,
url: undefined,
scrapeOptions: undefined,
};
const scrapeOptions = req.body.scrapeOptions;
2024-08-06 15:24:45 -03:00
// TODO: @rafa, is this right? copied from v0
2024-11-07 20:57:33 +01:00
if (Array.isArray(crawlerOptions.includePaths)) {
for (const x of crawlerOptions.includePaths) {
try {
new RegExp(x);
} catch (e) {
return res.status(400).json({ success: false, error: e.message });
}
}
}
2024-11-07 20:57:33 +01:00
if (Array.isArray(crawlerOptions.excludePaths)) {
for (const x of crawlerOptions.excludePaths) {
try {
new RegExp(x);
} catch (e) {
return res.status(400).json({ success: false, error: e.message });
}
}
}
const originalLimit = crawlerOptions.limit;
2024-08-20 14:39:52 -03:00
crawlerOptions.limit = Math.min(remainingCredits, crawlerOptions.limit);
logger.debug("Determined limit: " + crawlerOptions.limit, { remainingCredits, bodyLimit: originalLimit, originalBodyLimit: preNormalizedBody.limit });
2024-08-20 14:39:52 -03:00
const sc: StoredCrawl = {
originUrl: req.body.url,
2024-11-07 20:57:33 +01:00
crawlerOptions: toLegacyCrawlerOptions(crawlerOptions),
scrapeOptions,
internalOptions: {},
team_id: req.auth.team_id,
createdAt: Date.now(),
2024-08-28 12:46:59 -03:00
plan: req.auth.plan,
};
2024-08-06 15:24:45 -03:00
const crawler = crawlToCrawler(id, sc);
2024-08-06 15:24:45 -03:00
try {
2024-11-07 20:57:33 +01:00
sc.robots = await crawler.getRobotsTxt(scrapeOptions.skipTlsVerification);
} catch (e) {
logger.debug("Failed to get robots.txt (this is probably fine!)", { error: e });
}
2024-08-06 15:24:45 -03:00
await saveCrawl(id, sc);
2024-08-20 14:19:20 -03:00
const sitemap = sc.crawlerOptions.ignoreSitemap
? null
: await crawler.tryGetSitemap();
2024-08-28 12:46:59 -03:00
if (sitemap !== null && sitemap.length > 0) {
logger.debug("Using sitemap of length " + sitemap.length, { sitemapLength: sitemap.length });
2024-08-28 12:46:59 -03:00
let jobPriority = 20;
// If it is over 1000, we need to get the job priority,
// otherwise we can use the default priority of 20
if(sitemap.length > 1000){
// set base to 21
jobPriority = await getJobPriority({plan: req.auth.plan, team_id: req.auth.team_id, basePriority: 21})
}
logger.debug("Using job priority " + jobPriority, { jobPriority });
2024-08-20 14:19:20 -03:00
const jobs = sitemap.map((x) => {
const url = x.url;
const uuid = uuidv4();
return {
name: uuid,
data: {
url,
mode: "single_urls",
team_id: req.auth.team_id,
plan: req.auth.plan,
crawlerOptions,
2024-11-07 20:57:33 +01:00
scrapeOptions,
origin: "api",
crawl_id: id,
sitemapped: true,
2024-08-30 15:21:22 -03:00
webhook: req.body.webhook,
2024-08-25 14:05:46 +02:00
v1: true,
},
opts: {
jobId: uuid,
priority: 20,
2024-08-20 14:19:20 -03:00
},
};
2024-12-03 16:25:55 -03:00
})
logger.debug("Locking URLs...");
2024-08-20 14:19:20 -03:00
await lockURLs(
id,
sc,
2024-08-20 14:19:20 -03:00
jobs.map((x) => x.data.url)
);
logger.debug("Adding scrape jobs to Redis...");
2024-08-20 14:19:20 -03:00
await addCrawlJobs(
id,
jobs.map((x) => x.opts.jobId)
);
logger.debug("Adding scrape jobs to BullMQ...");
await getScrapeQueue().addBulk(jobs);
} else {
logger.debug("Sitemap not found or ignored.", { ignoreSitemap: sc.crawlerOptions.ignoreSitemap });
logger.debug("Locking URL...");
await lockURL(id, sc, req.body.url);
2024-10-25 20:21:12 +02:00
const jobId = uuidv4();
logger.debug("Adding scrape job to Redis...", { jobId });
2024-10-25 20:21:12 +02:00
await addScrapeJob(
2024-08-20 14:19:20 -03:00
{
url: req.body.url,
mode: "single_urls",
team_id: req.auth.team_id,
2024-11-07 20:57:33 +01:00
crawlerOptions,
scrapeOptions: scrapeOptionsSchema.parse(scrapeOptions),
plan: req.auth.plan!,
2024-08-20 14:19:20 -03:00
origin: "api",
crawl_id: id,
webhook: req.body.webhook,
2024-08-25 14:05:46 +02:00
v1: true,
2024-08-20 14:19:20 -03:00
},
{
priority: 15,
2024-10-25 20:21:12 +02:00
},
jobId,
2024-08-20 14:19:20 -03:00
);
logger.debug("Adding scrape job to BullMQ...", { jobId });
2024-10-25 20:21:12 +02:00
await addCrawlJob(id, jobId);
2024-08-06 15:24:45 -03:00
}
logger.debug("Done queueing jobs!");
2024-09-01 13:44:36 -03:00
if(req.body.webhook) {
logger.debug("Calling webhook with crawl.started...", { webhook: req.body.webhook });
2024-09-01 13:44:36 -03:00
await callWebhook(req.auth.team_id, id, null, req.body.webhook, true, "crawl.started");
}
2024-09-05 13:03:43 -03:00
const protocol = process.env.ENV === "local" ? req.protocol : "https";
return res.status(200).json({
success: true,
id,
2024-09-05 13:03:43 -03:00
url: `${protocol}://${req.get("host")}/v1/crawl/${id}`,
});
2024-08-06 15:24:45 -03:00
}
2024-08-30 15:21:22 -03:00