为了让智能体能够更精准地决策,我们需要为它构建一个专业知识库,本文将探讨 NestJS 中结合 Playwright
和 Turndown
等工具,实现对网站内容的抓取,并将其转换为Markdown
数据。
1. 关键技术介绍
-
Playwright:一个由微软开发的用于 Web 浏览器自动化的库,支持 Chromium、Firefox 和 WebKit 等多种浏览器,可以轻易的实现页面的各种自动化操作,比如模拟点击,模拟键盘输入,甚至可以通过 JavaScript 或 Python(通过 Playwright for Python)来实现爬虫功能。
-
Turndown:一个 HTML 到 Markdown 的转换器,可以帮助我们将从网页上抓取的 HTML 内容,转换为简洁的 Markdown 格式,便于存储和展示。
2. 安装依赖
为了实现网站内容抓取和转换,我们需要先安装 Playwright
和 Turndown
:
bash
# 安装 Playwright
pnpm add playwright
pnpm exec install playwright
# 安装 Turndown
pnpm add turndown
3. 爬虫代码实现
以下是基于 Playwright
和 Turndown
的网站内容抓取和转换代码:
typescript
import { Injectable } from '@nestjs/common';
import { chromium } from 'playwright';
import TurndownService from 'turndown';
@Injectable()
export class CrawlerService {
private turndown: TurndownService;
constructor() {
// 初始化 Turndown 转换器
this.turndown = new TurndownService({
bullet: '-', // 无序列表符号
codeBlock: '```', // 代码块符号
headingStyle: 'atx', // 标题样式
});
}
async crawl(baseUrl: string): Promise<{ title: string; url: string; content: string }[]> {
const docs: { title: string; url: string; content: string }[] = [];
const urls = new Set<string>();
urls.add(`${baseUrl}/`);
try {
await this.recursiveCrawl(baseUrl, docs, urls);
} catch (err) {
console.error('爬取失败:', err);
}
return docs;
}
private async recursiveCrawl(
url: string,
docs: { title: string; url: string; content: string }[],
urls: Set<string>,
): Promise<void> {
const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();
try {
await page.goto(url, { waitUntil: 'networkidle' });
const title = await page.title();
// 移除所有干扰元素
await page.evaluate(() => {
document.querySelectorAll('script, style, link[rel="stylesheet"], [style]').forEach(el => el.remove());
});
// 获取页面内容并转换为 Markdown
const content = await page.content();
const md = await new Promise(r => {
r(this.turndown.turndown(content));
});
docs.push({ title, url, content: md });
// 提取所有链接并递归抓取
const links = await page.$$eval('a[href]', (elements) =>
elements.map((el) => el.href?.replace(/#.*$/, '')).filter((link) => link.startsWith(baseUrl))
);
console.log(`正在抓取:${title} - ${url}`);
for (const link of links) {
if (!urls.has(link)) {
urls.add(link);
await page.waitForTimeout(50);
await this.recursiveCrawl(link, docs, urls);
}
}
} catch (err) {
console.error(`抓取失败: ${url}`, err);
} finally {
await page.close();
await context.close();
await browser.close();
}
}
}
4. 代码说明
-
无头浏览器启动
使用
chromium.launch()
启动无头浏览器,这是一种在后台运行的浏览器,可以模拟用户的行为,如浏览网站、点击链接等。 -
页面内容抓取与清理
- 使用
page.goto()
访问目标网页,并等待页面加载完成。 - 使用
page.evaluate()
执行 JavaScript 代码,移除网页中的<script>
、<style>
和[style]
等干扰元素,以便更准确地提取内容。
- 使用
-
HTML 转 Markdown
使用 Turndown 将网页的 HTML 内容转换为 Markdown 格式。Turndown 支持自定义配置,可以根据需求调整 Markdown 的输出格式。
-
递归抓取链接
通过
page.$$eval()
提取页面中的所有链接,并递归地对每个链接进行抓取,确保能够抓取到整个网站的内容。
5. 知识库数据存储
我们可以将抓取的 Markdown 数据转换为向量,并存储到 Milvus
中,从而构建一个基于向量的知识库。
6. 应用场景
-
自动化知识库文档构建:
- 从多个网站抓取相关知识内容,并统一存储到知识库中,方便后续RAG系统使用。
-
内容更新监测:
- 定期抓取网站内容,监测内容的变化,并及时更新知识库数据。
7. 注意事项
-
遵守法律法规:
- 在爬取网站内容时,必须遵守相关法律法规,尊重网站的版权和隐私政策。
-
避免过度请求:
- 合理设置爬虫的请求频率和并发数,避免对目标网站造成过大压力,导致服务器崩溃或被封禁。
-
处理反爬机制:
- 部分网站可能会采取反爬措施,如验证码、IP 封禁等。可以通过设置合理的请求头、使用代理 IP 或模拟用户行为等方式,降低被检测的风险。
8. 示例运行
为了验证爬虫功能是否正常,可以运行以下代码:
typescript
async function bootstrap() {
const baseUrl = 'https://docs.nestjs.com';
const crawler = new CrawlerService();
const docs = await crawler.crawl(baseUrl);
console.log('抓取完成,共抓取了', docs.length, '个页面');
// 打印结果到控制台或保存到文件
}
bootstrap();
总结
通过 NestJS、Playwright 和 Turndown 的结合,我们可以轻松实现对网站内容的抓取和转换,并将其存储到知识库中。这种技术不仅可以用于知识文档的自动化抓取,还可以用于构建智能体的内容监测系统。