- 网页爬虫
- 后端
【免费下载链接】headless-chrome-crawler
Distributed crawler powered by Headless Chrome
本指南以 headless-chrome-crawler 官方 API 参考文档(docs/API.md)为骨架,系统讲解基于 Headless Chrome 的分布式爬虫的全部公开接口:从HCCrawler.launch()/connect()的启动方式,到crawler.queue()的 20+ 配置项、11 个生命周期事件,再到SessionCache/RedisCache缓存体系与CSVExporter/JSONLineExporter结果导出。读完本文,你将能够仅凭公开 API 组合出可上线的爬虫任务,并具备按需自定义缓存、导出器与抓取逻辑的能力。
一、HCCrawler 类总览与最小可用示例
HCCrawler是 headless-chrome-crawler 的核心类,它封装了 Puppeteer 的浏览器实例,并提供「启动/连接 → 入队 → 空闲等待 → 关闭」的完整爬取生命周期。所有对外方法集中在 lib/hccrawler.js 中实现,包入口为 index.js。
一份最小可用示例(摘自 API 文档)如下:
const HCCrawler = require('headless-chrome-crawler'); (async () => { const crawler = await HCCrawler.launch({ // 在浏览器中执行的取数函数,返回可序列化对象 evaluatePage: (() => ({ title: $('title').text(), })), // 每次请求成功后回调 onSuccess: (result => { console.log(result); }), }); crawler.queue('https://example.com/'); // 入队一个请求 await crawler.onIdle(); // 等待队列清空 await crawler.close(); // 关闭浏览器 })();整个流程可以概括为四条链路:启动(launch/connect)→ 调度(queue + PriorityQueue)→ 抓取(Crawler 单页处理)→ 收尾(onIdle/close),每条链路在本文后续章节都有对应的源码级拆解。
二、启动与连接:HCCrawler.launch / connect / executablePath / defaultArgs
2.1 HCCrawler.launch([options])
launch()负责全新启动一个 Chromium 实例,返回 Promise 并 resolve 出HCCrawler实例。源码实现为:
static async launch(options) { const browser = await Puppeteer.launch(pick(options, LAUNCH_OPTIONS)); const crawler = new HCCrawler(browser, omit(options, LAUNCH_OPTIONS)); await crawler.init(); return crawler; }参考 lib/hccrawler.js,它通过pick将以下选项透传给puppeteer.launch()(对应源码常量 LAUNCH_OPTIONS):
ignoreHTTPSErrors, headless, executablePath, slowMo, args, ignoreDefaultArgs, handleSIGINT, handleSIGTERM, handleSIGHUP, dumpio, userDataDir, env, devtools这些参数的含义与 Puppeteer 官方一致:例如headless: false可关闭无头模式便于调试,args: ['--no-sandbox']常用于容器环境(测试用例 test/hccrawler/index.test.js 就默认携带该参数)。
2.2 HCCrawler.connect([options])
connect()用于连接到一个已经运行的 Chromium 实例(例如通过wsEndpoint()拿到的 WebSocket 端点),返回同样结构的HCCrawler实例。透传给puppeteer.connect()的选项只有三个(源码 CONNECT_OPTIONS):
browserWSEndpoint, ignoreHTTPSErrors, slowMo其中browserWSEndpoint是连接已运行浏览器所必需的。测试 test/hccrawler/index.test.js 验证了「同一浏览器多次连接」与「断线后重连」两种场景:先launch拿到wsEndpoint(),再用HCCrawler.connect({ browserWSEndpoint })接入。
注意:
connect()与launch()都允许把url、allowedDomains、deniedDomains、timeout、priority、depthPriority、delay、retryCount、retryDelay、jQuery、browserCache、device、username、password、evaluatePage、cookies、extraHeaders作为默认值传入,这样每次queue()时只需覆盖差异项,避免重复设置(API 文档原话:每次入队都写全套配置是冗余的)。
2.3 两个静态工具方法
HCCrawler.executablePath():返回随包下载的 Chromium 可执行文件的预期路径(string),测试断言该路径真实存在。HCCrawler.defaultArgs():返回 Chromium 启动时的默认参数数组,测试断言其中包含--no-first-run。
两者都是对 Puppeteer 同名静态方法的直接转发,参考 lib/hccrawler.js。
2.4 构造器公共选项
无论是launch还是connect,都会接收下列公共选项:
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
maxConcurrency | number | 10 | 同时打开的最大页面数(并发数) |
maxRequest | number | 0 | 最大请求数上限,传0表示不限制 |
exporter | Exporter | null | 继承 BaseExporter 接口的导出器对象 |
cache | Cache | SessionCache | 继承 BaseCache 接口的缓存对象,用于记忆并跳过重复请求 |
persistCache | boolean | false | 关闭或断开连接时是否清空缓存;false表示关闭时清空 |
preRequest(options) | Function | — | 每次请求前执行,可修改options;返回false可跳过该请求 |
customCrawl(page, crawl) | Function | — | 自定义抓取流程,暴露 Puppeteer 原始page对象 |
onSuccess(result) | Function | — | evaluatePage()成功时回调 |
onError(error) | Function | — | 请求失败时回调 |
对应的默认值合并逻辑见源码 lib/hccrawler.js:maxDepth: 1、maxConcurrency: 10、retryCount: 3、retryDelay: 10000、timeout: 30000、jQuery: true、browserCache: true等都在此处统一初始化。
三、队列控制:crawler.queue([options]) 全参数详解
queue()是入队入口,接受字符串、对象或数组三种形态:
- 传字符串:等价于
{ url: 字符串 }; - 传对象:按对象中的字段覆盖默认配置;
- 传数组:数组内每个元素依次入队,元素可以是字符串或对象(示例见 examples/multiple-queue.js)。
测试 test/hccrawler/index.test.js 覆盖了三种形态的组合用法。注意queue()返回的 Promise 在入队完成时 resolve,而非请求完成时。
3.1 queue 选项速查表
| 选项 | 类型/取值 | 默认值 | 说明 |
|---|---|---|---|
url | string | — | 要导航的地址,必须带 scheme,如https://;缺失会抛错 |
maxDepth | number | 1 | 自动跟随链接的最大深度;保持默认1即不跟随链接 |
priority | number | 1 | 队列基础优先级,数值越大越先被处理 |
depthPriority | boolean | true | 是否按深度调整优先级;保持默认会提高更深层级的优先级,即深度优先搜索(DFS) |
skipDuplicates | boolean | true | 是否跳过重复请求;url、userAgent、device、extraHeaders完全相同即视为重复 |
skipRequestedRedirect | boolean | false | 是否跳过已出现在重定向链中的请求;skipDuplicates=false时忽略 |
obeyRobotsTxt | boolean | true | 是否遵守 robots.txt |
followSitemapXml | boolean | false | 是否用 sitemap.xml 发现更多地址 |
allowedDomains | Array<string|RegExp> | — | 允许请求的域名列表,null表示不校验 |
deniedDomains | Array<string|RegExp> | — | 禁止请求的域名列表,null表示不校验 |
delay | number | 0 | 每次请求后的等待毫秒数;设置后maxConcurrency必须为1 |
timeout | number | 30000 | 导航超时(毫秒),传0禁用超时 |
waitUntil | string|Array<string> | load | 判定导航成功的时机,透传给 Puppeteerpage.goto()的waitUntil |
waitFor | Object | — | 等待某个元素/谓词/超时后再取值,透传给 Puppeteerpage.waitFor();含selectorOrFunctionOrTimeout、options、args三个字段 |
retryCount | number | 3 | 失败重试上限次数 |
retryDelay | number | 10000 | 每次重试失败后的等待毫秒数 |
jQuery | boolean | true | 是否自动向页面注入 jQuery |
browserCache | boolean | true | 是否开启浏览器缓存 |
device | string | — | 要模拟的设备名(如Nexus 7),支持列表以 Puppeteer 的 DeviceDescriptors 为准 |
username/password | string | — | HTTP Basic 认证凭证,不需要时传null |
screenshot | Object | null | 截图选项,透传给 Puppeteerpage.screenshot();null表示不截图 |
viewport | Object | — | 视口设置,透传给page.setViewport();含width、height |
userAgent | string | — | 覆盖页面 UA |
extraHeaders | Object | — | 每次请求附加的请求头,所有值必须是字符串 |
cookies | Array<Object> | — | 每次请求携带的 Cookie 列表,每个 Cookie 必须指定url或domain;name、value必填,其余字段与 Cookie 标准字段一致(expires为 Unix 秒、sameSite取值"Strict"或"Lax") |
evaluatePage() | Function | — | 在浏览器中求值的函数,须返回可序列化对象;若不可序列化则结果为undefined |
3.2 queue 的源码级行为约束
queue()在 lib/hccrawler.js 中做了四件事,理解这些有助于避开常见报错:
- 禁止覆盖构造器选项:若在
queue()的对象里出现maxConcurrency、cache、exporter、onSuccess等构造器专属选项,会直接抛出Overriding xxx is not allowed!(测试 test/hccrawler/index.test.js 验证了这一点)。 - evaluatePage 会被包装为立即执行函数:源码
mergedOptions.evaluatePage =(${mergedOptions.evaluatePage})()`` 说明该函数在浏览器里以 IIFE 形式运行。 - 强校验:
url缺失抛Url must be defined!;device不在支持列表抛Specified device is not supported!;delay > 0且maxConcurrency !== 1抛Max concurrency must be 1 when delay is set!(三条均有对应测试)。 - URL 规范化:通过
url.parse()统一为规范 href 后再入队。
3.3 优先级与遍历策略的实现
优先级队列由 lib/priority-queue.js 实现:入队时把priority作为最后一个参数传给缓存层,SessionCache内部用二分插入(lowerBound)保持队列按优先级降序(cache/session.js),出队时取队首。Redis 版本则用有序集合ZADD按分数排序、ZREVRANGE取出最高分项(cache/redis.js)。
深度优先的逻辑在 lib/hccrawler.js:当未显式指定priority且depthPriority为true时,直接用depth作为优先级,深度越大的链接优先级越高,从而形成 DFS;若想改为广度优先(BFS),将depthPriority置为false即可。完整的优先级用法可参考 examples/priority-queue.js。
3.4 链接跟随、robots.txt 与 sitemap.xml
- 链接跟随:单页抓取完成后,lib/crawler.js 会通过
exposeFunction收集页面中所有a[href]以及iframe/frame内的链接(含相对路径解析、锚点剔除、协议过滤与去重);随后 lib/hccrawler.js 在depth < maxDepth时把子链接以depth + 1入队,否则发出maxdepthreached事件。 - robots.txt:默认开启。抓取前 lib/hccrawler.js 会用
request-promise拉取并缓存robots.txt(失败则按空内容处理并发出robotstxtrequestfailed事件),再用robots-parser判断当前 URL 是否被允许;被禁止则发出requestdisallowed并跳过。 - sitemap.xml:默认关闭。开启后从 robots.txt 的
Sitemap:条目读取 sitemap 地址,解析其中所有<loc>节点(见 lib/helper.js)并逐个入队。测试 test/hccrawler/index.test.js 验证了开启前后行为差异。
3.5 域白名单 / 黑名单
allowedDomains与deniedDomains均支持字符串与正则两种元素,匹配逻辑见 lib/helper.js:正则用test匹配 hostname,字符串要求严格相等。判定顺序为「先黑名单后白名单」,见 lib/hccrawler.js。测试 test/hccrawler/index.test.js 覆盖了精确匹配、正则匹配、未命中跳过三种场景。
3.6 重试、延迟与去重
- 重试:
_request()(lib/hccrawler.js)在失败后若retryCount < options.retryCount则等待retryDelay后递归重试并发出requestretried,超过上限才发出requestfailed并回调onError。 - 去重键:
skipDuplicates为true时,lib/helper.js 取url、device、userAgent、extraHeaders四个字段做稳定 JSON 序列化后计算 MD5 取前 10 位作为缓存键;命中缓存即跳过(requestskipped事件)。 - 重定向去重:
skipRequestedRedirect开启后,会把重定向链中出现的每个 URL 都标记为已请求,避免重复抓取(测试 test/hccrawler/index.test.js)。
四、生命周期与流量控制:pause / resume / setMaxRequest / onIdle / close / disconnect
| 方法 | 返回值 | 说明 |
|---|---|---|
crawler.setMaxRequest(maxRequest) | — | 动态修改构造时传入的maxRequest |
crawler.pause() | — | 暂停处理队列,配合resume()恢复 |
crawler.resume() | — | 恢复处理队列;常用于pause()之后或达到maxRequest上限被自动暂停之后 |
crawler.clearCache() | Promise | 清空当前使用的缓存 |
crawler.close() | Promise | 关闭浏览器,等价于终止整个爬虫 |
crawler.disconnect() | Promise | 断开与浏览器的连接(不关闭浏览器本身) |
crawler.onIdle() | Promise | 队列清空或暂停时 resolve |
关键的自动暂停逻辑在 lib/hccrawler.js:每完成一次请求requestedCount自增,一旦达到maxRequest就发出maxrequestreached事件并自动调用pause()。因此经典用法是「达到上限 →setMaxRequest调高 →resume()继续」,见 examples/pause-resume.js。
onIdle()的底层实现是 lib/priority-queue.js:它注册一个 resolve 函数,当_pull()发现队列为空或处于暂停状态时触发 resolve。close()/disconnect()都会依次执行「结束队列 → 关闭/断开浏览器 → 结束导出器 → 按persistCache决定是否清空缓存 → 关闭缓存」(lib/hccrawler.js)。
五、信息查询方法
| 方法 | 返回值 | 说明 |
|---|---|---|
crawler.version() | Promise<string> | Chromium 版本号(测试断言含HeadlessChrome) |
crawler.userAgent() | Promise<string> | 默认 User-Agent(测试断言含HeadlessChrome) |
crawler.wsEndpoint() | string | 浏览器 WebSocket 端点,供connect()复用(测试断言以ws://开头) |
crawler.isPaused() | boolean | 队列是否处于暂停状态 |
crawler.queueSize() | Promise<number> | 队列中的任务总数 |
crawler.pendingQueueSize() | number | 正在处理中的任务数 |
crawler.requestedCount() | number | 已完成的请求总数 |
这些方法大多是对队列或浏览器的薄封装(lib/hccrawler.js),常用于监控与调试。对应断言见 test/hccrawler/index.test.js。
六、事件系统:11 个公开事件
HCCrawler继承自EventEmitter,公开事件名定义在 lib/hccrawler.js,全部为小写字符串:
| 事件 | 回调参数 | 触发时机(源码位置) |
|---|---|---|
requestdisallowed | options | 请求被 robots.txt 拒绝(lib/hccrawler.js) |
requeststarted | options | 请求开始(lib/hccrawler.js) |
requestskipped | options | 请求被跳过(去重/域过滤/preRequest 拦截) |
requestfinished | options | 请求成功完成(lib/hccrawler.js) |
requestretried | options | 请求失败并进入重试(lib/hccrawler.js) |
requestfailed | error | 重试耗尽后请求失败;error含options、depth、previousUrl |
robotstxtrequestfailed | error | robots.txt 拉取失败(lib/hccrawler.js) |
sitemapxmlrequestfailed | error | sitemap.xml 拉取失败(lib/hccrawler.js) |
maxdepthreached | options | 队列达到maxDepth上限(lib/hccrawler.js) |
maxrequestreached | — | 达到maxRequest上限并自动暂停(lib/hccrawler.js) |
disconnected | — | 浏览器实例断开连接(lib/hccrawler.js) |
使用方式与 Node.js 标准 EventEmitter 一致:crawler.on('requeststarted', options => { ... })。测试中大量用事件计数验证行为,例如 test/hccrawler/index.test.js 通过requeststarted/requestfinished各触发 1 次来断言一次完整请求。error类事件携带的error对象额外附加了options、depth、previousUrl三个字段,便于定位失败来源。
七、回调扩展点:preRequest、onSuccess、onError、customCrawl
7.1 preRequest(options)
每个请求真正执行前调用(lib/hccrawler.js),接收「已合并默认值的 queue options」,可就地修改options,返回false则跳过该请求。典型用途是条件化截图:examples/conditional-screenshot.js 中自定义saveAs字段,preRequest据此决定是否注入screenshot选项,未携带saveAs的请求直接返回false被跳过。
7.2 onSuccess(result) 的完整 result 结构
result对象由 lib/crawler.js 组装,包含 9 个字段:
options:queue 选项合并默认值后的完整对象;depth:当前链接深度;previousUrl:上一请求的 URL,初始请求为null;response:最终响应对象,含ok(状态码是否在 200–299)、status(状态码)、url(最终请求的 URL,重定向后可能与options.url不同)、headers;redirectChain:重定向链数组,每项含url与headers;result:evaluatePage()的返回值(可序列化);screenshot:截图 Buffer,未传screenshot选项时为null;cookies:Cookie 数组,每项含name、value、domain、path、expires(Unix 秒)、httpOnly、secure、session、sameSite;links:页面中找到的链接列表(已解析为绝对地址并去重)。
测试对response.ok、response.status、redirectChain长度与每跳 URL 都有断言(test/hccrawler/index.test.js)。
7.3 onError(error)
请求最终失败时回调。error为 Error 对象,并附加options、depth、previousUrl字段(lib/hccrawler.js)。
7.4 customCrawl(page, crawl) 深入底层
这是扩展性最强的入口:回调接收 Puppeteer 原始page对象与内置的crawl()函数(lib/hccrawler.js)。你可以:
- 在
crawl()前操作页面:如开启请求拦截; - 在
crawl()后补充字段:如追加result.content = await page.content(); - 必须把扩展后的 result 返回给
onSuccess。
完整示例见 examples/custom-crawl.js:它用page.setRequestInterception(true)只放行以/结尾的请求,其余全部abort(),并给结果追加页面 HTML。
八、缓存体系:SessionCache、RedisCache 与自定义 BaseCache
8.1 SessionCache(默认)
HCCrawler默认使用SessionCache(lib/hccrawler.js),它在内存Map中记录已请求的 URL 与 robots.txt 内容(cache/session.js)。传入cache: null可关闭去重:
const crawler = await HCCrawler.launch({ cache: null }); // 禁用缓存8.2 RedisCache:分布式去重
RedisCache(cache/redis.js)把已请求 URL 与 robots.txt 持久化到 Redis,适用于多台服务器组成的分布式爬虫,避免重复请求。构造参数除expire(每个值写入后的过期秒数,默认null不过期)外,其余全部透传给 NodeRedis 的redis.createClient()(如host、port):
const HCCrawler = require('headless-chrome-crawler'); const RedisCache = require('headless-chrome-crawler/cache/redis'); const cache = new RedisCache({ host: '127.0.0.1', port: 6379 }); (async () => { const crawler = await HCCrawler.launch({ persistCache: true, // 关闭爬虫时不清空缓存 cache, }); // ... })();persistCache: true是关键配合项:只有置为true,close()时才不会执行flushdb清空缓存。RedisCache 内部用有序集合承载优先级队列,并借助 Lua 脚本原子完成「取出最高优先级任务并删除」(cache/redis.js)。跨进程复用缓存的完整演示见 examples/redis-cache.js:第一次启动请求example.com、example.net并保留缓存,第二次启动后example.net直接跳过、只新增请求example.org。
8.3 自定义 BaseCache
任何缓存实现都必须继承 cache/base.js 的BaseCache接口,并实现 8 个方法:init()、close()、clear()、get(key)、set(key, value)、enqueue(key, value, priority)、dequeue(key)、size(key)、remove(key)。仓库提供了基于文件系统的完整参考实现 examples/custom-cache.js(FsCache),其中enqueue按priority降序维护队列、dequeue弹出队首,行为与内置缓存保持一致。
九、结果导出:CSVExporter、JSONLineExporter 与自定义 BaseExporter
9.1 CSVExporter
构造选项:
file:输出文件路径(必填);fields:作为列(同时用作表头)的字段列表(必填,用点号路径取值,如response.url);separator:列分隔字符,默认,。
const HCCrawler = require('headless-chrome-crawler'); const CSVExporter = require('headless-chrome-crawler/exporter/csv'); const FILE = './tmp/result.csv'; const exporter = new CSVExporter({ file: FILE, fields: ['response.url', 'response.status', 'links.length'], separator: '\t', }); (async () => { const crawler = await HCCrawler.launch({ exporter }); // ... })();实现见 exporter/csv.js:writeHeader()写出表头行,writeLine()通过get(result, field)按点号路径取值,并用escapeQuotes(lib/helper.js)对含分隔符、引号或换行的值做 RFC4180 风格转义。落地示例见 examples/csv-exporter.js。
9.2 JSONLineExporter
构造选项:
file:输出文件路径(必填);fields:要过滤保留的字段列表,默认null(不过滤);jsonReplacer:自定义序列化 replacer 函数,默认null,可用于固定键的排序顺序。
const HCCrawler = require('headless-chrome-crawler'); const JSONLineExporter = require('headless-chrome-crawler/exporter/json-line'); const FILE = './tmp/result.json'; const exporter = new JSONLineExporter({ file: FILE, fields: ['options', 'response'], }); (async () => { const crawler = await HCCrawler.launch({ exporter }); // ... })();实现见 exporter/json-line.js:每行输出一个 JSON 对象(JSON Lines 格式),可搭配fields裁剪体积。
9.3 自定义 BaseExporter
自定义导出器需继承 exporter/base.js 的BaseExporter:基类负责创建写入流(默认utf8编码)与end()/onEnd(),子类必须实现writeLine(result)、writeHeader()、writeFooter()。仓库提供了用util.inspect输出调试结果的参考实现 examples/custom-exporter.js。导出器的生命周期由爬虫自动驱动:构造时writeHeader(),每次请求成功后writeLine(result),close()/disconnect()时writeFooter()+end()(lib/hccrawler.js)。
十、抓取单页的完整内部流程
为了理解所有选项的生效顺序,这里串讲单次请求的内部调用链(实现见 lib/crawler.js):
_prepare()(lib/crawler.js)并行执行:拦截window.open防止新开标签页、Basic 认证(username/password)、设备模拟(device)、视口设置(viewport)、CSP 绕过(jQuery开启时)、浏览器缓存开关(browserCache=false时关闭)、UA 覆盖、附加请求头、注入 Cookie、监听页面 console/dialog/pageerror;_request():以timeout/waitUntil调用page.goto(url);_waitFor():若配置了waitFor则等待元素/谓词/超时——测试用 200ms 延迟渲染的页面验证了该选项的价值(test/hccrawler/index.test.js);- 并行执行
_scrape()(注入 jQuery 后执行evaluatePage)、_screenshot()、_getCookies()、_collectLinks(); - 组装 result 对象返回给上层。
十一、综合实战:一个可复制的完整爬虫
将以上 API 组合起来,可以得到一个兼顾「深度跟随、重试、截图、CSV 导出」的完整任务(各文件均可直接运行,示例需在仓库根目录执行NODE_PATH=../ node examples/xxx.js):
const HCCrawler = require('headless-chrome-crawler'); const CSVExporter = require('headless-chrome-crawler/exporter/csv'); const exporter = new CSVExporter({ file: './tmp/result.csv', fields: ['response.url', 'response.status', 'result.title', 'links.length'], }); (async () => { const crawler = await HCCrawler.launch({ maxConcurrency: 4, // 并发 4 个页面 maxRequest: 100, // 最多 100 个请求 maxDepth: 2, // 跟随两层链接 retryCount: 3, retryDelay: 5000, exporter, evaluatePage: () => ({ title: $('title').text() }), onSuccess: result => { console.log(`[${result.response.status}] ${result.options.url}`); }, onError: error => { console.error(`Failed: ${error.options.url}`); }, }); crawler.on('requestskipped', () => console.log('Skipped a duplicate.')); crawler.on('maxrequestreached', () => { crawler.setMaxRequest(200); // 动态扩容后继续 crawler.resume(); }); await crawler.queue({ url: 'https://example.com/', allowedDomains: ['example.com'], obeyRobotsTxt: true, }); await crawler.onIdle(); await crawler.close(); // persistCache 默认 false,缓存随之清空 })();十二、环境与依赖说明
- headless-chrome-crawler 内部打包了 Puppeteer,安装(
yarn add headless-chrome-crawler或npm i headless-chrome-crawler)时会自动下载对应版本的 Chromium; - 本仓库
package.json声明puppeteer: 1.20.0、jquery: 3.5.1、redis为 peerDependency(仅使用RedisCache时才需要安装),Node 引擎要求>= 8.10.0; RedisCache与test/hccrawler/redis.test.js依赖本机 Redis 服务,仓库默认测试命令会排除 Redis 相关用例(见 package.json 的jest-exclude-redis-cache脚本);- 更多调试手段可阅读 docs/TIPS.md,更多运行示例可浏览 examples/ 目录。
- 网页爬虫
- 后端
【免费下载链接】headless-chrome-crawler
Distributed crawler powered by Headless Chrome
相关推荐
OpCore Simplify 指南:三步生成一份可启动的 OpenCore EFI
OpCore Simplify 指南:三步生成一份可启动的 OpenCore EFI 如果你想在电脑上装 macOS,只想要一份能启动的 OpenCore EF
开发工具CLImxbai-embed-2d-large-v1-openmind社区资源与支持指南
mxbai embed 2d large v1 openmind社区资源与支持指南 mxbai embed 2d large v1 openmind是一款高效的
Headless Chrome Crawler配置详解:从基础到高级的完整指南
Headless Chrome Crawler配置详解:从基础到高级的完整指南 想要快速掌握Headless Chrome Crawler的完整配置方法吗?这个
网页爬虫后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考