面试题手册

梳理高频技术问题,帮助你按主题复习和查漏补缺。

服务端阅读 05月29日 00:51

Cheerio 有哪些 DOM 操作方法?修改和读取分别怎么用?

Cheerio 的 DOM 操作分两大类:读取和修改。读取用 .text() .html() .attr() .val() 获取内容;修改用 .append() .prepend() .after() .before() 插入节点,.remove() .empty() 删除,.replaceWith() 替换。关键是区分内部插入(append/prepend,插入子节点)和外部插入(after/before,插入兄弟节点)。遍历用 .find() .children() .parent() .closest() .siblings() 在节点树中移动。追问append 和 after 有什么区别?.append() 是内部插入,把新节点加为目标元素的最后一个子节点;.after() 是外部插入,把新节点放在目标元素后面作为兄弟节点。记忆方式:append 是往肚子里塞,after 是站在旁边。remove 和 empty 有什么区别?.remove() 连元素本身一起删除;.empty() 只清空内容,元素标签保留。爬虫场景中 .empty() 常用于清空某个容器再重新填入清洗后的数据。怎么批量提取数据并转成数组?用 .map() + .get() 组合:$('.item').map((i, el) => $(el).text()).get()。.map() 返回 cheerio 对象,.get() 转为普通数组,否则无法用 Array 方法。.find() 和 .children() 有什么区别?.find() 搜索所有后代元素(含孙子节点),.children() 只找直接子元素。性能上 .children() 更快,层级确定时优先使用。怎么克隆一个节点并插入到其他位置?.clone() 深拷贝节点,返回独立的 cheerio 对象,可链式调用 .appendTo() 或 .insertAfter() 插入。注意 clone 不会复制 data 和事件绑定。写段代码const $ = cheerio.load(html);// 读取:提取所有文章标题和链接const articles = $('.post').map((i, el) => ({ title: $(el).find('h2').text(), link: $(el).find('a').attr('href')})).get();// 修改:给外链加 target 并移除广告$('a[href^="http"]').attr('target', '_blank');$('.ad').remove();
服务端阅读 05月29日 00:51

Elasticsearch 的 fielddata 和 doc_values 有什么区别?

fielddata 是基于 JVM 堆内存的倒排索引补充结构,仅在搜索时按需加载到内存,专用于 text 字段的聚合和排序,内存消耗不可控,易引发 OOM;docvalues 是基于磁盘的列式存储,索引时随文档写入持久化,默认对 keyword 和数值类型启用,不占堆内存,ES 5.x 后成为默认方案。核心区别:存储位置(堆 vs 磁盘)、加载时机(搜索时 vs 索引时)、内存风险(高 vs 低)、适用类型(text vs keyword/numeric)。生产环境中应优先用 docvalues,若字段不需聚合/排序可关闭 doc_values 节省磁盘。追问为什么 fielddata 容易 OOM?fielddata 将字段的全部唯一词项加载到 JVM 堆,高基数字段(如 UUID)可能占用数 GB。且被 LRU 缓存持有,不会主动释放,直到触发 circuit breaker。text 字段需要聚合怎么办?推荐用 multi-fields:主字段 text 做全文检索,子字段 keyword 启用 doc_values 做聚合。避免在 text 上开启 fielddata: true。关闭 doc_values 有什么影响?该字段将无法用于聚合、排序和脚本访问。如果字段只做全文检索不需要这些操作,关闭可节省约 10-15% 磁盘空间。doc_values 的列式存储如何加速聚合?列式存储按字段值连续排列,对同一字段的遍历只需顺序读磁盘,CPU 缓存命中率高。行式存储需跳行读取,I/O 散乱。ES 7.x 后还有必要了解 fielddata 吗?需要。排查旧索引的内存问题时仍需检查 fielddata 占用,GET _nodes/stats/indices?fielddata 可定位高消耗字段。写段代码PUT /products{ "mappings": { "properties": { "title": { "type": "text", "fields": { "keyword": { "type": "keyword", "doc_values": true } } }, "status": { "type": "keyword", "doc_values": true } } }}
服务端阅读 05月29日 00:50

DevOps 的核心概念和关键原则是什么?

DevOps 不是工具或职位,而是一种将开发与运维深度融合的文化实践。其核心可用 CAMLS 概括:Culture(打破部门壁垒、共担责任)、Automation(构建/测试/部署/监控全链路自动化)、Measurement(用 MTTR、部署频率等指标驱动改进)、Sharing(跨团队知识共享与无指责复盘)。关键原则包括基础设施即代码(IaC)保证环境一致性、CI/CD 流水线实现快速可靠交付、监控与可观测性保障生产稳定性,以及通过 blameless postmortem 从故障中学习而非追责。追问DevOps 中的"无指责复盘"(blameless postmortem)怎么做?如何避免流于形式?基础设施即代码(IaC)和传统运维脚本有什么本质区别?Terraform 和 Ansible 各自适用什么场景?如何度量 DevOps 转型的成效?DORA 四项关键指标分别是什么?小团队没有专职运维,如何落地 DevOps 实践?监控(Monitoring)和可观测性(Observability)有什么区别?写段代码# GitLab CI/CD 最小流水线示例stages: - test - deploytest: stage: test script: - npm install && npm testdeploy: stage: deploy script: - npx netlify deploy --prod only: - main
服务端阅读 05月29日 00:50

Cheerio 中如何高效使用选择器提取数据?

Cheerio 支持 CSS 选择器 + jQuery 扩展伪类,核心选择器分四类:基本(元素/ID/类)、层级(后代/子元素/兄弟)、属性(= ^= $= *=)、伪类(:first/:eq(n)/:contains()/:has())。高效用法的关键是缩小搜索范围:先选父容器再 .find(),缓存 $ 变量避免重复查找,用具体选择器替代通配符。追问.find() 和层级选择器 $('div p') 哪个更快?性能差异极小,但 .find() 可读性更好且支持链式调用,推荐优先使用。已缓存的父元素上 .find() 还能跳过全局搜索。如何获取元素的文本、属性和 HTML?.text() 取文本(含子元素文本拼接),.attr('href') 取属性值,.html() 取内部 HTML,.val() 取表单值。注意 .text() 会递归合并所有子节点文本。.each() 和 .map() 有什么区别?.each() 遍历不返回值(适合副作用操作),.map() 返回数组(适合数据提取)。.map() 需链式调用 .get() 才能得到原生数组。:contains() 和 .filter() 如何选择?:contains('text') 在选择器阶段过滤,写法简洁但不支持正则;.filter(fn) 在回调中可写任意判断逻辑,更灵活。写段代码const $ = cheerio.load(html);const $list = $('#article-list');const titles = $list.find('.title').map((i, el) => $(el).text().trim()).get();
服务端阅读 05月29日 00:50

Elasticsearch 如何优化写入性能?

核心思路是减少刷新频率、批量提交、降低副本开销。具体操作:将 refreshinterval 设为 -1 禁用自动刷新,写入完成后手动 refresh;使用 Bulk API 批量提交文档(建议 5-15MB 一批);写入期间将 numberofreplicas 设为 0,写完后恢复;调大 translog.flushthreshold_size 减少 flush 次数;合理路由使热点数据集中写入少数分片,避免跨节点协调开销。追问bulk 请求多大合适?建议单次 bulk 请求体 5-15MB,文档数不超 10000。过大易触发 GC 甚至 OOM,过小则网络开销占比高。用 BulkProcessor 可自动攒批提交。refresh_interval=-1 写完忘改回来怎么办?新写入文档对搜索不可见,但不丢数据。生产中可在索引模板中设置正常值,仅写入任务启动时临时覆盖。副本数为 0 风险多大?单节点故障会丢分片数据。建议仅在大批量初始导入时使用,写完立即恢复副本并等待 allocation 完成。translog 配置怎么调?将 translog.durability 改为 async,translog.syncinterval 设为 30s,flushthreshold_size 从默认 512MB 调到 1GB,减少磁盘 fsync 次数。冷热数据如何隔离写入?用 ILM 策略将新数据写入热节点(SSD),rollover 后迁移到冷节点(HDD)。hot 阶段设短 refresh_interval,cold 阶段恢复默认。写段代码PUT /logs-write{ "settings": { "refresh_interval": "-1", "number_of_replicas": 0, "translog": { "durability": "async", "sync_interval": "30s" } }}
服务端阅读 05月29日 00:50

Cheerio 使用中有哪些常见坑?怎么解决?

最常见的五大坑:①中文乱码——需要用 iconv-lite 按实际编码解码,别依赖 axios 自动处理;②选择器找不到元素——先 $.html() 检查 HTML 是否完整加载,再逐步缩小选择器范围调试;③拿不到动态内容——Cheerio 不执行 JS,要么换 Puppeteer 要么直接调后端 API;④大文件内存溢出——分批处理或流式解析,别一次全 load 进来;⑤XML 解析报错——必须加 { xmlMode: true } 选项,否则自闭合标签和命名空间都会出问题。追问中文乱码具体怎么处理?axios 设置 responseType: 'arraybuffer' 拿到原始字节,先从 Content-Type 或 meta 标签检测编码,再用 iconv.decode(buf, charset) 转码。GBK 页面不转码必乱。选择器没匹配到元素,排查步骤是什么?第一步 console.log($.html()) 看 HTML 是否完整;第二步从最外层选择器开始逐步缩小;第三步用 :contains() 按文本内容定位;第四步确认元素不在 iframe 或 shadow DOM 中。处理大文件怎么避免内存泄漏?分批 .slice(i, i+batchSize) 处理,每批处理完置空引用;更优方案是用 stream 按 </item> 分割,逐块 cheerio.load 解析,内存恒定。XML 模式和 HTML 模式有什么区别?xmlMode: true 会保留大小写(HTML 模式全部转小写)、保留自闭合标签、不补全缺失的闭合标签。解析 RSS/SVG/配置文件必须开启,否则数据丢失。提取的文本满是空白字符怎么办?.text() 结果链式调用 .replace(/\s+/g, ' ').trim(),或在 load 时加 { normalizeWhitespace: true },但后者会把换行也合并成空格,按需选择。写段代码// 正确处理 GBK 编码页面const resp = await axios.get(url, { responseType: 'arraybuffer' });const charset = resp.headers['content-type']?.match(/charset=([^;]+)/i)?.[1] || 'utf-8';const html = charset.toLowerCase() === 'utf-8' ? resp.data.toString() : iconv.decode(Buffer.from(resp.data), charset);const $ = cheerio.load(html);
服务端阅读 05月29日 00:50

Cheerio 加载 HTML 有哪几种方式?

核心方法是 cheerio.load(html, options),支持三种输入源:HTML 字符串、fs.readFileSync 读取的文件内容、Buffer。第二个参数控制解析行为:xmlMode 用于 XML 文档,decodeEntities 控制是否解码 HTML 实体(如 & → &),withDomLvl1 决定是否遵循 DOM Level1 规范。对大文件可用流式读取拼接后再 load。追问xmlMode 什么时候必须开启?解析 XML/RSS/Atom feed 时必须开启,否则标签大小写会被统一转小写,XML 的自闭合标签也会被错误处理。decodeEntities 设为 false 有什么用?保留原始 HTML 实体不解码,适合需要原样输出 HTML 的场景,如内容编辑器。默认 true 会把 & 转成 &。能直接加载 URL 吗?不能。Cheerio 是纯解析库,没有网络能力。需先用 axios/fetch 获取 HTML 字符串,再传入 load()。cheerio.load() 返回的 $ 是什么?返回一个函数,既可当选择器 $(selector) 使用,也挂载了 .html()、.xml() 等静态方法,本质是包装后的 DOM 操作入口。Buffer 输入和字符串输入有区别吗?功能一致,Buffer 会被内部自动转字符串。用 Buffer 可避免编码问题,尤其处理非 UTF-8 内容时更安全。写段代码const cheerio = require('cheerio');const $ = cheerio.load('<root><item>值</item></root>', { xmlMode: true, decodeEntities: false});console.log($('item').text()); // 值
服务端阅读 05月29日 00:50

如何用 Cheerio 实现网页爬虫和数据抓取?

核心流程三步走:用 axios/fetch 获取 HTML → cheerio.load() 加载 → 选择器提取数据。Cheerio 只负责解析,HTTP 请求需搭配其他库。关键要注意设置 User-Agent、处理相对 URL 转绝对路径、以及加入请求延迟避免被封禁。追问如何处理分页爬取?循环拼接页码参数逐页请求,每页之间加 1-2 秒延迟,收集数据后统一存储。注意检测是否有下一页(如"下一页"按钮或总页数标识)来决定终止条件。爬取的数据如何清洗?用 .trim() 去空白,正则移除 HTML 标签和特殊字符,parseFloat/parseInt 转数字类型,过滤空值和无效条目。如何实现请求重试机制?封装 fetchWithRetry 函数,失败后指数退避等待重试(1s→2s→4s),超过最大次数抛出异常。并发爬取如何控制?用 p-limit 等并发控制库限制同时请求数,或手动实现队列,避免瞬间大量请求导致 IP 被封。写段代码const axios = require('axios');const cheerio = require('cheerio');async function scrape(url) { const { data } = await axios.get(url); const $ = cheerio.load(data); const items = []; $('.item').each((i, el) => { items.push({ title: $(el).find('h2').text().trim() }); }); return items;}
服务端阅读 05月29日 00:50

Cheerio 和 Puppeteer 有什么区别?爬虫场景怎么选?

Cheerio 是纯 HTML 解析器,不执行 JavaScript,解析速度比 Puppeteer 快 100 倍以上,内存占用极低;Puppeteer 启动真实 Chromium,能执行 JS、处理动态渲染、模拟用户交互,但资源消耗大、速度慢。选择依据很简单:目标页面内容是否由 JS 动态生成——查看网页源码(Ctrl+U)能看到数据就用 Cheerio,看不到就用 Puppeteer。追问Puppeteer 能不能用 Cheerio 解析页面?可以,这是常见的混合模式:Puppeteer 负责加载动态页面拿到渲染后的 HTML,再传给 Cheerio 做数据提取,兼顾动态能力与解析效率。怎么判断一个网站是否需要 Puppeteer?右键查看网页源码,搜索目标数据。如果源码中没有,说明是 JS 动态加载,需要 Puppeteer;源码中已有,直接 Cheerio 即可。也可以用 curl 请求看返回的原始 HTML。Cheerio 能否处理需要登录的页面?Cheerio 本身只做解析,登录请求通过 axios/fetch 带 cookie 发送即可,不需要 Puppeteer。只有登录过程涉及 JS 渲染或验证码交互时才需要浏览器。两者的资源消耗差异有多大?Cheerio 解析一个页面通常 <10ms、内存 <50MB;Puppeteer 启动浏览器就要 500ms+、内存 100-300MB。批量爬取时差异是数量级的,100 个页面 Cheerio 几秒搞定,Puppeteer 可能要几分钟。写段代码// Puppeteer 拿动态页面 + Cheerio 解析const browser = await puppeteer.launch();const page = await browser.newPage();await page.goto(url, { waitUntil: 'networkidle2' });const html = await page.content();await browser.close();const $ = cheerio.load(html);$('.item').each((i, el) => { console.log($(el).text());});
服务端阅读 05月29日 00:50

Cheerio 和 jQuery 有什么区别?

Cheerio 是 Node.js 端的 HTML 解析库,实现了 jQuery 核心 API,但剥离了浏览器依赖——不渲染 DOM、不处理 CSS 布局、不执行 JavaScript,因此比 jQuery 轻量且解析速度快 8 倍以上。jQuery 运行在浏览器中操作真实 DOM,Cheerio 则在服务端解析 HTML 字符串构建虚拟 DOM 树,只做数据提取与结构操作。追问Cheerio 为什么比 jsdom 快?jsdom 会构建完整的浏览器环境(CSSOM、布局计算等),Cheerio 仅构建 DOM 树,跳过了渲染管线,所以解析速度快一个数量级。Cheerio 能替代 jQuery 做前端开发吗?不能。Cheerio 没有 render 树,修改 DOM 后不会触发页面重绘,也无法绑定事件,它只适合服务端数据提取场景。Cheerio 的底层解析器是什么?默认使用 htmlparser2,也可切换到 parse5。htmlparser2 速度更快,parse5 则更严格遵循 HTML 规范。什么场景下必须用 jQuery 而非 Cheerio?需要事件监听、CSS 动画、视口计算、用户交互等浏览器原生能力时,必须用 jQuery。写段代码const cheerio = require('cheerio');const $ = cheerio.load('<ul><li>A</li><li>B</li></ul>');$('li').each((i, el) => console.log($(el).text()));