Skip to content

知乎摸鱼 zhihu-fisher — 速记版

📋 速记表(面试前快速扫)

一句话结论关键词
Q1domcontentloaded+5s空闲超时兜底+滚动补全5s超时 滚动补全 重试5次
Q2详情页码文件伪装+侧边栏文件树伪装,token高亮双层伪装 mtk token 失焦
Q3三模式切CSS+占位符+悬浮预览,区分[表情]占位符 悬浮预览 表情36px
Q4全量重写→分层更新,postMessage增量+retainContext增量更新 webviewId 保状态
Q5Cheerio洗HTML,属性级操作非正则Cheerio 属性级 互利清晰
Q65树+120命令分层,when精准控菜单viewsContainers when条件
Q7读走Puppeteer/写走API(CSRF)读写分离 CSRF
Q8模拟真人滚动+批次控制,互斥锁防重复分批加载 随机延迟 互斥锁

Q1: ✅ Puppeteer 反爬采集

面试官可能问:怎么用 Puppeteer 爬知乎?反爬怎么处理?

🗣️ 口述稿

后台无头 Chrome 打开真页面,设 Cookie/UA,goto 用 domcontentloaded 进页。知乎长轮询让 waitForNetworkIdle 永远等不到,所以我加 5 秒超时兜底:try 里 waitForNetworkIdle({timeout:5000}),超时就当内容够取了继续。再配合逻辑滚动、等骨架屏消失,确保内容真挤出来才解析。浏览器启动失败等 5 秒重试、最多 5 次。

💡 关键词提示5s空闲兜底 domcontentloaded 滚动补全 重试5次

⚠️ 备注

  • [重要] 简历写 networkidle2,代码实测是 domcontentloaded + waitForNetworkIdle({timeout:5000})(个别页 networkidle0)。别死咬 networkidle2,按"domcontentloaded进+5s超时+滚动补全"讲更贴代码
  • 反爬手段:BEC cookie 滤重定向、navigator.webdriver 置 undefined、UA 伪造
  • 合规红线:被问"爬不爬/违不违法"强调"个人阅读工具+低频+遵守ToS",别包装成黑产无风险

追问①:5s 超时会抓不全吗?

会,所以 5s 只是兜底不是终点:配合 simulateHumanScroll(先滚到底→上移200px→再下滚)+ 轮询等骨架屏 section.skeleton 消失,把没渲染完的风险压到最低。

追问②:被风控封号怎么办?

低频像真人+有 cookie 失效预案:checkIfPageHasLoginElement 检测登录墙/验证码,失效引导扫码重登。边界是自用浏览+低频率采集。


Q2: 双层智能伪装

面试官可能问:伪装怎么让知乎像代码编辑器?

🗣️ 口述稿

分两层。详情页伪装:WebView 失焦时随机伪装成某类型代码文件,postMessage 通知前端渲染一个"带行号+语法高亮"的假代码界面盖住知乎;侧边栏伪装:失焦后 setContext 让 5 个知乎树替换成模拟的项目文件列表(src/docs/config),聚焦自动恢复。

💡 关键词提示双层伪装 token高亮mtk 失焦聚焦

⚠️ 备注

  • 内容是随机生成但结构是真的——.ts 会生成 interface/type/async,用 span 打 VSCode 的 token 类型(mtk5 关键字/mtk9 变量),前端用原生 CSS 变量上色。目标远观防瞄,不是骗过代码审查
  • 核心技术:code-generator.ts 用 pattern 池随机拼,一次拼上百行还不重样才像真的
  • 别吹"天衣无缝",要有"内容语义是编的、只是远观"的清醒认知

追问①:随机代码会被看出来吗?

会,内容语义是编的。但它是为"远观防瞄"设计的,不是骗代码审查,我有这个边界认识。

追问②:失焦切换是轮询还是事件?

事件驱动,onDidChangeViewState 监听 WebView 焦点,失焦即刻 postMessage 隐藏/显示,不是轮询。


Q3: 多模式媒体控制

面试官可能问:三种显示模式怎么实现?隐藏模式占位符?

🗣️ 口述稿

三种模式靠 CSS class 切:normal/mini/none,值放配置、onDidChangeConfiguration 即时生效。核心在隐藏模式:Cheerio 解析时给每个媒体后面插个默认隐藏的占位符 span,切隐藏模式就媒体隐藏、占位符显示,hover 占位符还能用 rAF 准确定位弹出缩略预览,避免全隐藏后啥也看不见。

💡 关键词提示三模式CSS 占位符+悬浮预览 [表情]36px

⚠️ 备注

  • 反直觉细节很值钱:表情包识别 width/height≤36px 或含 emoji/sticker 归 [表情][图片];GIF 是 .jpg 缩略+播放按钮,插件改 .gif 并去按钮
  • 占位+悬浮是"既要隐私又要能用"的折中,不是简单 display:none 完事

追问①:为什么占位符还要悬浮预览?

直接隐藏体验太差,占位+hover 预览是"既要隐私又要能看到内容"的折中;悬浮框用 rAF 像素级定位因为外层可能被缩放。

追问②:mini 是等比还是裁切?

等比 scale 到 50%,纯 CSS 层面做,不涉及布局重排。


Q4: WebView 双向通信与局部更新

面试官可能问:WebView 和主进程怎么通信?早期全量替换的问题?

🗣️ 口述稿

早年每次加载新回答都整份重写 webview.html,导致滚动位置重置、子评论弹窗关、用户状态丢。后来改分层更新:静态内容首全量渲染,动态部分(评论/投票/导航)用 postMessage 只更新对应 DOM 节点,配 retainContextWhenHidden 让切标签不销毁状态。每个 WebView 有唯一 id,消息按 id 路由回正确面板。

💡 关键词提示全量→增量 postMessage webviewId路由

⚠️ 备注

  • 40+ 种消息靠 webviewId 从 Store.webviewMap 取实例 + onDidDispose 清理 Map 关 Puppeteer 页
  • 坦诚:漏 dispose 会内存泄漏,没做到完美

追问①:40+ 消息不会乱吗?

靠 webviewId 从 map 取对应实例+onDidDispose 清理 Map、同时关相关 Puppeteer 页。机制能路由,但漏 dispose 会泄漏,没做到完美。

追问②:retainContextWhenHidden 为什么不全开会?

它让隐藏不销毁 JS 上下文,代价是即使隐藏也占内存,所以只给要保留交互状态的页面开。


Q5: Cheerio 内容解析

面试官可能问:Cheerio 在里面的分工?

🗣️ 口述稿

跟 Puppeteer 明确分工:Puppeteer 负责拿到 DOM,Cheerio 负责洗数据。Cheerio 是 Node 端类 jQuery 解析,做图片/GIF 真实 URL 提取、重定向还原、盐选付费拦截、MathJax 抽取、广告移除。用 Cheerio 不用正则,因为它对 HTML 结构操作更可靠、不依赖浏览器。

💡 关键词提示分工 属性级操作 链接还原

⚠️ 备注

  • 粒度问题要讲:表情替换作用在文本节点层面非正则全局,避免把 HTML 属性里的内容误替换污染
  • 知乎改版=选择器失效,是"按真实页面抓"的通病,我应对是选择器集中+快速修——敢承认脆弱点

追问①:为什么不直接用 Puppeteer extractText?

Puppeteer 拿的是原始 HTML 串,切改用 jQuery 式 API 比正则可靠;Cheerio 不执行 JS、纯解析,快且不占浏览器。

追问②:知乎改版了怎么办?

选择器大概率失效,这是按真实页面抓的通病。我选择器集中、好维护,改版时快速修,这是已知脆弱点。


Q6: VSCode Extension 深度定制

面试官可能问:TreeView 和 120+ 命令怎么组织?

🗣️ 口述稿

用自定义 viewsContainers 注册一个 activitybar 容器,里面 5 个知乎 TreeView+1 个伪装列表,每个 TreeView 一个 TreeDataProvider。120+ 命令按功能拆 12 个文件,用 registerXxxCommands 返回 Disposable 统一收集;菜单显隐用 when + viewItem 条件精准控制。

💡 关键词提示viewsContainers 当条件 命令分层

⚠️ 备注

  • 亮点是"菜单在正确场景出现":用 when view =~ /^zhihu(Hot|Recommend|...)$/ && viewItem==... 把"在浏览器打开/取消收藏/伪装相关"分给对应视图
  • 你讲"菜单是 when 条件驱动的"就显真写过

追问①:TreeView 和 Webview 怎么同步?

TreeView 列列表、点某项开 WebviewPanel;侧栏数据变 TreeDataProvider.refresh() 重绘;Webview 里关注/投票 postMessage→API→成功再刷列表。链路松散,实时性依赖手动 refresh。


面试官可能问:Cookie 怎么管?API 和 Puppeteer 怎么分工?

🗣️ 口述稿

Cookie 走插件配置存储(password 保护+旧 JSON 迁移+removeBECFromCookie 滤重定向参数),登录墙/验证码用 checkIfPageHasLoginElement 检测。API 层用原生 fetch 封装 REST;写操作(点赞/关注/收藏)走 API 因要 CSRF 校验、模拟点击不稳;读操作(内容/详情)走 Puppeteer。

💡 关键词提示读写分离 CSRF cookie迁移

⚠️ 备注:把"读走 Puppeteer/写走 API"的取舍讲清楚,比记接口名强,是这题分水岭。

追问①:Cookie 过期怎么发现?

登录墙检测+过期提示+扫码重登,被挤下线/风控也能快速恢复。


Q8: 回答分批加载与滚动

面试官可能问:几百个回答怎么加载、怎么控节奏?

🗣️ 口述稿

滚动懒加载。首屏解析当前页,滚到倒数第 5 个触发 loadMoreAnswers:模拟真人滚动(scrollTo+mouse.wheel)→随机延迟→查 scrollHeight 是否变化,变了增量解析去重追加、没变认为到底。answersPerBatch(5-50) 控制每批量,越大越容易被反爬。

💡 关键词提示分批加载 随机延迟 互斥锁防重复

⚠️ 备注

  • 核心矛盾想清楚:像人(随机延迟+非机械滚动)vs 别卡死(isLoadingBatch 互斥锁+每步查 WebView 是否还活着)
  • 踩过坑:滚了但没数据/数据没渲染完就拿——提这个很加分

追问①:滚了但没内容渲染出来?

轮询等骨架屏消失+查 scrollHeight,实在没变就 stop。这曾踩过"滚了没数据/没渲染完就拿",后来才稳。


🎙️ 行为面试

为什么做这个项目?

自己上班想摸鱼刷知乎,网页版图片多太显眼,搜一圈现成插件都过期了,干脆自己写。写来自己用,顺手开源。

💡 关键词自用痛点 顺手开源

⚠️ 备注:动机要"真、具体",别像推广文案。


最大难点?

知乎反爬一直在变:早期用 networkidle2 等网络空闲,但知乎长轮询永远等不到、直接卡死,改成 domcontentloaded+5s 超时兜底才稳;落地滚动又踩"滚动高度判断不稳",调了好几版。

💡 关键词反爬多变 5s超时兜底 滚动调优

⚠️ 备注:[需自行确认] 7000+/160Star 是市场页对外数字,仓库验证不到增长曲线,别在"具体哪天到 7000"硬编。


遗憾/弯路?

  1. 几乎没写自动化测试——每次改 Puppeteer 逻辑手跑一遍,低效
  2. 渲染层是模板字符串拼接、没前端框架,维护吃力、牵一发动全身
  3. 反爬/合规红线有数但没成文档——靠"低频自用"自觉约束

💡 关键词零测试 模板字符串 合规未成文

⚠️ 备注:三条都是真实短板,特别第 3 条能体现边界意识,别删。


7000+ 安装怎么来的?

没刻意推广。名自带传播(知乎摸鱼共鸣)+README 带 GIF 展示核心功能(伪装/无广告/媒体可控),用户自发转。

💡 关键词名自带传播 GIF演示 自发转

⚠️ 备注:追问"哪天到 7000"别编,如无法给市场后台就说"没统计过具体曲线"。


怎么处理用户反馈?

GitHub Issues 为主,常用问题(浏览器/Cookie/加载失败)写进 FAQ;还内置故障排除指引+问题反馈入口。教训:工具插件用户不主动报,靠写清楚常见坑降差评。

💡 关键词Issue FAQ沉淀 降求助门槛

⚠️ 备注:能把"用户不报问题"的洞察讲出来,比背"我及时回复"加分。


🃏 极简速记卡(面试前5分钟看)

  • Q1 采集:domcontentloaded+5s超时+滚动补全+重试5次
  • Q2 伪装:详情码文件+侧栏文件树,token高亮
  • Q3 媒体:三模式CSS+占位符+悬浮预览+[表情]
  • Q4 通信:全量→增量postMessage,webviewId路由
  • Q5 解析:Cheerio洗HTML,属性级操作非正则
  • Q6 插件:5树+120命令,when精准控菜单
  • Q7 分工:读走Puppeteer/写走API(CSRF)
  • Q8 加载:分批+随机延迟+互斥锁防重复
  • 行为 动机:自用痛点;难点:反爬多变+滚动调优;遗憾:零测试+模板字符串+合规未成文

上一篇:02-智能回潮率检测仪系统 下一篇:04-惠东移动

前端开发工程师 · 面试准备