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

229 lines
6.0 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-12-11 19:46:11 -03:00
toLegacyCrawlerOptions
2024-08-20 14:19:20 -03:00
} from "./types";
import {
addCrawlJob,
addCrawlJobs,
crawlToCrawler,
lockURL,
lockURLs,
saveCrawl,
2024-12-11 19:46:11 -03:00
StoredCrawl
2024-08-20 14:19:20 -03:00
} 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();
2024-12-11 19:46:11 -03:00
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!;
2024-12-11 19:46:11 -03:00
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,
2024-12-11 19:46:11 -03:00
scrapeOptions: undefined
2024-11-07 20:57:33 +01:00
};
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);
2024-12-11 19:46:11 -03:00
logger.debug("Determined limit: " + crawlerOptions.limit, {
remainingCredits,
bodyLimit: originalLimit,
originalBodyLimit: preNormalizedBody.limit
});
const sc: StoredCrawl = {
originUrl: req.body.url,
2024-11-07 20:57:33 +01:00
crawlerOptions: toLegacyCrawlerOptions(crawlerOptions),
scrapeOptions,
2024-12-10 21:12:31 +01:00
internalOptions: { disableSmartWaitCache: true }, // NOTE: smart wait disabled for crawls to ensure contentful scrape, speed does not matter
team_id: req.auth.team_id,
createdAt: Date.now(),
2024-12-11 19:46:11 -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) {
2024-12-11 19:46:11 -03:00
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-12-11 19:46:11 -03:00
2024-08-28 12:46:59 -03:00
if (sitemap !== null && sitemap.length > 0) {
2024-12-11 19:46:11 -03:00
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
2024-12-11 19:46:11 -03:00
if (sitemap.length > 1000) {
// set base to 21
2024-12-11 19:46:11 -03:00
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,
2024-12-10 21:12:31 +01:00
internalOptions: sc.internalOptions,
origin: "api",
crawl_id: id,
sitemapped: true,
2024-08-30 15:21:22 -03:00
webhook: req.body.webhook,
2024-12-11 19:46:11 -03:00
v1: true
},
opts: {
jobId: uuid,
2024-12-11 19:46:11 -03:00
priority: 20
}
};
2024-12-11 19:46:11 -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 {
2024-12-11 19:46:11 -03:00
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),
2024-12-10 21:12:31 +01:00
internalOptions: sc.internalOptions,
2024-11-07 20:57:33 +01:00
plan: req.auth.plan!,
2024-08-20 14:19:20 -03:00
origin: "api",
crawl_id: id,
webhook: req.body.webhook,
2024-12-11 19:46:11 -03:00
v1: true
2024-08-20 14:19:20 -03:00
},
{
2024-12-11 19:46:11 -03:00
priority: 15
2024-10-25 20:21:12 +02:00
},
2024-12-11 19:46:11 -03: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-12-11 19:46:11 -03:00
if (req.body.webhook) {
logger.debug("Calling webhook with crawl.started...", {
webhook: req.body.webhook
});
await callWebhook(
req.auth.team_id,
id,
null,
req.body.webhook,
true,
"crawl.started"
);
2024-09-01 13:44:36 -03:00
}
2024-09-05 13:03:43 -03:00
const protocol = process.env.ENV === "local" ? req.protocol : "https";
2024-12-11 19:46:11 -03:00
return res.status(200).json({
success: true,
id,
2024-12-11 19:46:11 -03:00
url: `${protocol}://${req.get("host")}/v1/crawl/${id}`
});
2024-08-06 15:24:45 -03:00
}