随着大规模模型技术的兴起,我们可以看到百模大战、各种智能体、百花齐放的应用场景,那么作为一名前端开发者,以前端的视角,我们应当如何积极做好技术储备,开拓技术视野,在智能体时代保持一定的竞争力呢?我尝试通过一系列文章来总结一下!
前言
可能很多人会好奇,你写爬虫就写爬虫为啥要和大模型扯上关系,明显的蹭热点吧。其实我们在做大模型应用时,比如问答系统、内容生成等应用时,通常会用到检索增强生成 (RAG) 技术,标准 RAG(以智能问答🌰) 流程图如下,大致分为以下几个步骤:

离线部分
- 知识库:数据从各种来源收集并存储在知识库中。
- 清洗、装载:数据进行预处理和清洗,以确保数据质量、清洗后的数据被装载为文档。
- 文档:将预处理后的数据组织成文档格式。
- 切分:将文档切分成更小的段(chunks),以便后续处理。
- 向量化:将切分后的文档段通过向量模型转换为向量表示。
- 向量数据库:向量化的文档段存储在向量数据库中,便于快速检索。
在线部分
- 用户请求:用户发出查询请求。
- Prompt: 用户的请求被处理为一个prompt,准备进行向量化。
- 向量化:用户请求的prompt通过向量模型转换为向量表示。
- 相似度查询:根据向量化的用户请求在向量数据库中进行相似度查询,检索相关的文档段。
- 提取相关知识:检索到的相关文档段作为背景知识注入到提示词模板中。
- 提示词模板:将检索到的背景知识与用户请求结合,生成完整的提示词。
- LLM(大型语言模型):使用提示词生成最终的回答或内容。
- 返回用户:最终的生成内容返回给用户。
我们可以看到,我们的回答是否准确,是否具备差异性,来源于我们的知识库内容的丰富程度和相关性。而知识库内容的获取途径,其中重要的组成部分就是爬虫。
关键概念
如果你以前了解过爬虫相关概念,在讲到爬虫的时候,脑子里蹦出来的会有以下关键词
- JS 渲染(JavaScript Rendering)
在现代网页中,很多内容是通过 JavaScript 动态加载和渲染的。这意味着传统的静态网页爬虫(如直接抓取 HTML 内容的爬虫)可能无法获取这些动态加载的内容。因此,爬虫需要具备执行 JavaScript 的能力,以便能够完全渲染页面并获取所有需要的数据。
- 无头浏览器(Headless Browsers)
无头浏览器是一种在没有图形用户界面的环境中运行的浏览器。这类浏览器能够执行 JavaScript,并可以模拟用户在浏览器中的操作,如点击、输入文本和滚动页面。常用的无头浏览器有 Puppeteer 和 Playwright,它们基于 Chrome 和 Firefox 等主流浏览器内核,提供了强大的网页自动化和爬取功能。
- 等待元素渲染(Waiting for Elements to Render)
由于网页内容可能是动态加载的,爬虫在抓取页面内容时需要等待特定元素加载完成。无头浏览器通常提供了等待元素出现的方法(如 waitForSelector),以确保页面完全加载后再进行数据提取。这样可以避免抓取到不完整的内容或空白页面。
- 代理服务器(Proxy Server)
为了防止被目标网站封禁和提高爬取效率,爬虫通常会使用代理服务器。代理服务器可以隐藏爬虫的真实 IP 地址,并通过轮换多个代理 IP 来模拟不同的访问来源,避免爬取行为被检测到并封禁。使用代理服务器还能绕过地理限制,访问特定区域的网站内容。
接下来我给大家介绍一个爬虫框架,并实现一个简单的例子,让大家了解下这个框架。
功能介绍
Crawlee\] 是一个高效的网页爬虫和抓取工具,旨在帮助开发者快速构建可靠的爬虫系统。它兼具HTTP请求和无头浏览器的爬取能力,适用于各种动态和静态网页内容的抓取。包含 node 和 python两个版本  ###### 🌈 主要功能 * **HTTP和无头浏览器爬取**:支持传统的HTTP请求和现代无头浏览器(如Playwright和Puppeteer),可以抓取动态生成的内容。 * **持久化队列**:自动管理和持久化URL队列,确保高效和可靠的抓取过程。(广度优先和深度优先) * **自动扩展**:支持根据需求动态调整爬取规模,提高抓取效率。 * **代理轮换**:内置代理轮换功能,避免IP被封,提高爬取的稳定性。 * **生命周期管理**:提供灵活的生命周期管理,允许自定义爬虫的各个阶段。 * **错误处理和重试机制**:自动处理爬取过程中遇到的错误,并进行重试,确保数据完整性。 ###### 👍 优势 * **单一接口**:统一的接口处理HTTP和浏览器爬取,简化开发过程,减少学习成本。 * **JavaScript渲染支持**:通过无头浏览器渲染页面,抓取动态内容,使得爬取结果更完整。 * **丰富的配置选项** :提供多种配置选项,适应不同的爬取需求,增强灵活性。**http2** 、**浏览器请求他** 、**fingerprints** * **TypeScript编写**:利用TypeScript的强类型检查和自动补全功能,提高代码质量和开发效率。 * **内置快速HTML解析器**,如Cheerio和JSDOM * **CLI和Docker支持**:提供命令行工具和Docker支持,方便集成和部署,适应不同的开发环境。 ###### 🆚 与其他爬虫框架对比 * **Scrapy**:Scrapy 是一个强大的传统HTTP爬取框架,但不支持动态内容的抓取。Crawlee 在处理动态内容方面更有优势,特别是对现代Web应用的抓取。 * **BeautifulSoup**:BeautifulSoup 主要用于解析HTML,功能相对简单,不具备Crawlee 的自动扩展、代理轮换和生命周期管理等高级功能。 * **Selenium**:Selenium 可以抓取动态内容,但缺少持久化队列和自动扩展功能,且性能相对较低。Crawlee 提供了更高效和集成的解决方案。 `Crawlee` 主打一个高效、可高、速度极快,接下来我们通过一个 demo,来了解它有哪些功能! ##### Live Demo > 目标:挑战 20 分钟,完成掘金前端话题内容的爬取,并完成结构化存储 > ⚠️注意以下示例代码,大部分都是 github 的 copilot 帮我补全的,我只提供思路,他就帮我写完了!!! ###### 初始化项目 打开一个终端,执行以下命令 crawlee 提供 3 个类,方便用户针对不同场景选择不同的类 * \[`CheerioCrawler`\] 主要针对普通 http 的爬取,并通过 cheerio 库来解释 html,快速高效,但无法处理 js 渲染 * \[`PuppeteerCrawler`\] google 开源的无头浏览器\[Puppeteer\],提供标准 API 来控制 Chrome/Firefox * \[`PlaywrightCrawler`\] microsoft 开源的无头浏览器\*\*\[playwright\])\*\* ,提供标准 API 来控制 Chrome/Firefox、Webkit 等浏览器,功能更加齐全 由于笔者原来一直使用 puppeteer,以下会使用 PuppeteerCrawler 来完成 demo,其中 cheerio 可以理解为 node 版本的 jQuery,提供强大 dom 操作 # 你也可以通过以下命令,通过官方模版初始化应用 # npx crawlee create juejin-crawler cd juejin-crawler npm init npm install --save crawlee cheerio puppeteer ###### 安装依赖、启动脚本 打开 `package.json`修改以下内容,关键两个改动 * 为了简单,我们是用 esm 来解析 js 文件,需增加 `"type": "module",` * 增加一个启动脚本 `"start": "rm -rf storage/ && node src/main.mjs",` 其中 main 文件在后续创建 { "name": "juejin-crawler", "version": "1.0.0", "main": "index.js", "scripts": { "start": "rm -rf storage/ && node src/main.mjs", "test": "echo \"Error: no test specified\" && exit 1" }, "type": "module", "author": "", "license": "ISC", "description": "", "dependencies": { "cheerio": "^1.0.0-rc.12", "crawlee": "^3.11.1", "puppeteer": "^22.15.0" } } ###### 增加入口文件 增加一个入口文件 `src/main.mjs` * 需要引入一个 router 文件,用来处理整体 handler 操作,包括如何处理列表页、如何处理详情页等等 * 需要引入一个 config 文件,主要用来配置结构化数据所需的配置,比如爬取页面 URL、页面对于的选择器,如 title 对应详情页的 `article-title` * 初始化一个PuppeteerCrawler实例,具体配置作用,见代码 * 把请求入口链接加到队列,并启动任务 `main.mjs` 整体内容如下 import { PuppeteerCrawler, ProxyConfiguration, log } from 'crawlee'; import { router } from './routes.mjs'; import config from './config.mjs'; log.setLevel(log.LEVELS.DEBUG); log.debug('Setting up crawler.'); // const proxyConfiguration = new ProxyConfiguration({ // proxyUrls: [], // }); // 创建 PuppeteerCrawler 实例 const crawler = new PuppeteerCrawler({ // 是否使用会话池,启用后爬虫会重用会话和 Cookie // useSessionPool: true, // 是否为每个会话持久化 Cookie,启用后每个会话会有自己的 Cookie 存储 // persistCookiesPerSession: true, // 代理配置,用于通过代理服务器发送请求,可以用于绕过 IP 限制或地理位置限制 // proxyConfiguration, // 最大并发请求数,控制同时进行的请求数量 maxConcurrency: 10, // 每分钟最大请求数,控制每分钟发送的请求数量 maxRequestsPerMinute: 20, // 启动上下文配置 launchContext: { launchOptions: { // 是否以无头模式启动浏览器(无头模式:没有图形界面),设置为 false 表示有图形界面 headless: false, }, }, // 爬虫运行时的最大请求数量,当达到这个数量时,爬虫会停止 maxRequestsPerCrawl: 1000, // requestHandler 定义了爬虫在访问每个页面时要做的操作 // 可以在这里提取数据、处理数据、保存数据、调用 API 等 requestHandler: router, // 处理请求失败的情况,记录失败的请求并输出错误日志 failedRequestHandler({ request, log }) { log.error(`Request ${request.url} failed too many times.`); }, }); // 添加要爬取的请求,config 是一个包含 URL 和标签的数组 // 这里将每个 item 的 url 和 label 添加到爬虫的请求队列中 await crawler.addRequests(config.map(item => { return { url: item.url, label: item.label } })); // 启动爬虫 await crawler.run(); ###### 增加配置文件,增加灵活性 增加一个配置文件 `src/config.mjs` ,具体实现以下功能 * 配置要爬取的页面 URL,给定一个标签,如 `juejin` * 配置各个字段的选择器,这个对于前端工程师来说,小菜一碟,截图看看例子  具体配置信息如下: export default [ { label: 'juejin', url: 'https://juejin.cn/frontend', selector: { detail: '.entry-list .title-row a', title: 'h1.article-title', author: '.author-info-box .author-name a span', modifiedDate: '.author-info-box .meta-box time', hit: '.author-info-box .meta-box .views-count', readTime: '.author-info-box .meta-box .read-time', description: '.article-header p', content: '.article-viewer', }, } ] ###### 增加处理逻辑 增加真正处理逻辑文件 `src/routes` 这里增加两个处理逻辑,列表页和详情页 * 列表页:通过模拟滚动的方式,触发滚动底部加载更多,并把列表页所有文章加入爬取队列 * 详情页:通过配置的选择器,把详情页的数据结构化,并存储到数据集 * 如果这里为了实现知识库相关功能,也可以把拿到的数据存储到向量数据库,这里不做演示 import { createPuppeteerRouter, Dataset } from 'crawlee'; // import { Article } from './db.mjs'; export const router = createPuppeteerRouter(); import config from './config.mjs'; config.map(async item => { const dataset = await Dataset.open(item.label); // 打开列表页操作 router.addHandler(`${item.label}`, async ({ request, page, enqueueLinks, log }) => { page.setDefaultTimeout(5000); log.debug(`Enqueueing pagination: ${request.url}`) // 由于掘金是滚动加载,这里通过模拟滚动到底部,实现页面切换 const scrollToBottom = async (page, scrollDelay = 5000) => { let previousHeight; let newHeight; let reachedEnd = false; let count = 0; // 这里只滚动2次,如果还没有到底部,可以根据实际情况调整 while (!reachedEnd && count < 2) { previousHeight = await page.evaluate('document.body.scrollHeight'); await page.evaluate(`window.scrollBy(0, ${previousHeight})`); await new Promise(resolve => setTimeout(resolve, scrollDelay)); newHeight = await page.evaluate('document.body.scrollHeight'); count++; if (previousHeight === newHeight) { reachedEnd = true; } } }; // 模拟滚动加载 await scrollToBottom(page); // 等待页面加载完成,这里只为演示,在这个例子中,不需要等待 await page.waitForSelector(item.selector.detail); // 把列表页的详情页链接加入到爬虫队列中 await enqueueLinks({ selector: item.selector.detail, label: `${item.label}-DETAIL`, }); await page.close(); }); // 打开详情页操作 router.addHandler(`${item.label}-DETAIL`, async ({ request, page, log }) => { page.setDefaultTimeout(5000); log.debug(`Extracting data: ${request.url}`) const urlParts = request.url.split('/').slice(-2); const url = request.url; await page.waitForSelector(item.selector.content); const details = await page.evaluate((url, urlParts, item) => { return { url, uniqueIdentifier: urlParts.join('/'), type: urlParts[0], title: document.querySelector(item.selector.title).innerText, author: document.querySelector(item.selector.author).innerText, modifiedDate: document.querySelector(item.selector.modifiedDate).innerText, hit: document.querySelector(item.selector.hit).innerText, readTime: document.querySelector(item.selector.readTime).innerText, content: document.querySelector(item.selector.content).innerText, contentHTML: document.querySelector(item.selector.content).innerHTML, }; }, url, urlParts, item); log.debug(`Saving data: ${request.url}`) // await Article.create(details); await dataset.pushData(details); await page.close(); }); }); router.addDefaultHandler(async ({ request, page, enqueueLinks, log }) => { log.error(`Unhandled request: ${request.url}`); }); ###### 启动爬虫 npm start  打开 chrome 浏览器,按照逻辑自动打开列表页、详情页  数据存储到 storage 目录下,并按照我们定义的结构,以 json 格���保存起来 ###### 增加代理 IP Server 由于上面的例子我做了限制,如果爬取并发数太高的话,大概率会被封禁,所以这个时候就需要用到代理 ip 服务 和 session 管理 * Proxy 管理:如果有代理 URL,只需以下几行代码,即可实现代理访问,并通过 proxyInfo 方法检查当前代理 import { PuppeteerCrawler, ProxyConfiguration } from 'crawlee'; const proxyConfiguration = new ProxyConfiguration({ proxyUrls: ['http://proxy-1.com', 'http://proxy-2.com'], }); const crawler = new PuppeteerCrawler({ useSessionPool: true, persistCookiesPerSession: true, proxyConfiguration, async requestHandler({ proxyInfo }) { console.log(proxyInfo); }, }); * session 管理:使用 session 管理,主要为了过滤掉被阻止或者不工作的代理,大致代码如下: import { PuppeteerCrawler, ProxyConfiguration } from 'crawlee'; const proxyConfiguration = new ProxyConfiguration({ /* opts */ }); const crawler = new PuppeteerCrawler({ // To use the proxy IP session rotation logic, you must turn the proxy usage on. proxyConfiguration, // Activates the Session pool (default is true). useSessionPool: true, // Overrides default Session pool configuration sessionPoolOptions: { maxPoolSize: 100 }, // Set to true if you want the crawler to save cookies per session, // and set the cookies to page before navigation automatically (default is true). persistCookiesPerSession: true, async requestHandler({ page, session }) { const title = await page.title(); if (title === 'Blocked') { session.retire(); } else if (title === 'Not sure if blocked, might also be a connection error') { session.markBad(); } else { // session.markGood() - this step is done automatically in PuppeteerCrawler. } }, }); 以上,大功告成!! *** ** * ** ***  ### 如何学习AI大模型? 我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。 我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。  第一阶段: 从大模型系统设计入手,讲解大模型的主要方法; 第二阶段: 在通过大模型提示词工程从Prompts角度入手更好发挥模型的作用; 第三阶段: 大模型平台应用开发借助阿里云PAI平台构建电商领域虚拟试衣系统; 第四阶段: 大模型知识库应用开发以LangChain框架为例,构建物流行业咨询智能问答系统; 第五阶段: 大模型微调开发借助以大健康、新零售、新媒体领域构建适合当前领域大模型; 第六阶段: 以SD多模态大模型为主,搭建了文生图小程序案例; 第七阶段: 以大模型平台应用与开发为主,通过星火大模型,文心大模型等成熟大模型构建大模型行业应用。  👉学会后的收获:👈 • 基于大模型全栈工程实现(前端、后端、产品经理、设计、数据分析等),通过这门课可获得不同能力; • 能够利用大模型解决相关实际项目需求: 大数据时代,越来越多的企业和机构需要处理海量数据,利用大模型技术可以更好地处理这些数据,提高数据分析和决策的准确性。因此,掌握大模型应用开发技能,可以让程序员更好地应对实际项目需求; • 基于大模型和企业数据AI应用开发,实现大模型理论、掌握GPU算力、硬件、LangChain开发框架和项目实战技能, 学会Fine-tuning垂直训练大模型(数据准备、数据蒸馏、大模型部署)一站式掌握; • 能够完成时下热门大模型垂直领域模型训练能力,提高程序员的编码能力: 大模型应用开发需要掌握机器学习算法、深度学习框架等技术,这些技术的掌握可以提高程序员的编码能力和分析能力,让程序员更加熟练地编写高质量的代码。  > ***1.AI大模型学习路线图 > 2.100套AI大模型商业化落地方案 > 3.100集大模型视频教程 > 4.200本大模型PDF书籍 > 5.LLM面试题合集 > 6.AI产品经理资源合集*** 👉获取方式: 😝有需要的小伙伴,可以保存图片到wx扫描二v码免费领取【保证100%免费】🆓 