跳转到内容

爬虫与 JS 逆向面试题复盘

约 16 分钟阅读发布于 2025/9/16

这是一组爬虫和 JS 逆向相关的面试题。

这类面试不会只问“会不会用 requests”,更常见的是从一个具体业务场景开始追问:怎么登录、怎么抓动态接口、怎么处理反爬、百万级数据怎么调度、数据怎么进入后续处理链路。

这篇文章按面试题复盘的方式整理,重点是把回答讲得更清楚、更工程化。

同时要先明确一点:爬虫和逆向要遵守法律、站点协议和数据合规要求。面试中可以讲技术思路,但不应该表达绕过风控、攻击站点或采集敏感数据的意图。更稳的说法是:在授权范围内做数据采集和接口分析。

题目:

登录有两种方式,一种是账号密码登录,并且需要输入动态 token;另一种是二维码登录。如果要自动登录,你会选择哪种方式,为什么?说说实现方法。

我的回答倾向是:优先选择账号密码加 token 的方式。

原因:

  • 账号密码登录更适合程序化请求。
  • 登录流程相对稳定,便于抓包分析。
  • 登录成功后可以拿到 token、cookie 或 session。
  • 二维码登录通常依赖人工扫码,不适合长期自动化任务。

可以这样回答:

我会优先选择账号密码加 token 的登录方式。因为它更容易通过请求和响应模拟,流程上可以先请求登录页或初始化接口,拿到登录所需的 token、cookie,再携带账号密码和动态 token 请求登录接口。登录成功后保存 cookie 或 access token,后续请求统一带上认证信息。二维码登录更适合人工确认,自动化成本更高,而且很多二维码登录会绑定设备、时效和扫码确认,不适合爬虫任务长期稳定运行。

一个简化流程:

请求登录页或初始化接口
|
获取 token / csrf / cookie
|
提交账号、密码、动态 token
|
登录成功
|
保存 cookie 或 access token
|
后续请求携带认证信息

需要注意:如果动态 token 是验证码、短信码、二次验证,不能假设可以无成本自动化。面试里可以强调“在授权账号和合规场景下处理登录态”。

题目:

这种前端返回数据的网站,如何爬取数据?

现在很多网站是前端框架渲染,HTML 源码里没有完整数据。此时不要急着解析页面,而是先看网络请求。

常规步骤:

  1. 打开 Chrome DevTools。
  2. 进入 Network 面板。
  3. 过滤 XHR / Fetch 请求。
  4. 找到真正返回 JSON 数据的接口。
  5. 分析 URL、请求方法、参数、Headers、Cookie。
  6. 用 Python 模拟请求。
fetch-api-data.py
import requests
url = "https://example.com/api/list"
headers = {
"User-Agent": "Mozilla/5.0",
"Referer": "https://example.com/list",
}
params = {
"page": 1,
"size": 20,
}
response = requests.get(url, headers=headers, params=params, timeout=10)
data = response.json()
print(data)

如果接口参数是动态生成的,就继续分析 JS。

如果页面确实没有接口,或者数据必须通过浏览器运行后才出现,可以考虑 Selenium 或 Playwright。但大规模采集时,优先分析接口,因为浏览器自动化成本更高。

题目:

面对百万甚至千万数据量的爬取,你的爬取策略是怎么样的?爬取到的数据如何存储?

这题考察的是系统设计,而不是单机脚本。

可以从四层回答:

  • 任务拆分。
  • 并发控制。
  • 反爬与容错。
  • 数据存储和后续处理。

一个比较完整的链路是:

任务调度 -> 爬虫采集 -> Kafka -> Flink 清洗 -> 数据存储

如果只是普通项目,可以存 MySQL 或 CSV;如果是百万、千万规模,就要考虑分批写入、去重、失败重试、数据清洗和存储扩展。

可以这样回答:

面对百万级数据,我不会用一个单机脚本顺序爬。一般会先把任务拆成分页任务、关键词任务或 ID 区间任务,放到任务队列里,由多个爬虫节点并发消费。采集时会限制请求频率,设置超时重试和代理池,避免单点 IP 或账号压力过大。采集到的数据先进入 Kafka,后续由 Flink 做实时清洗,再写入 MySQL、ES 或数据仓库。对于失败任务会记录状态,后续补偿重试。

常用 Chrome DevTools 的 Network 面板。

主要看:

  • XHR / Fetch 请求。
  • 请求 URL。
  • 请求方法。
  • Query 参数和 Request Payload。
  • Headers。
  • Cookie。
  • Response。
  • Initiator 调用来源。

如果参数是动态生成的,会继续去 Sources 面板断点调试,或在 JS 文件中搜索参数名。

可以从几个现象判断:

  • 请求参数里存在动态加密参数。
  • 接口依赖 token、cookie、签名或时间戳。
  • 请求频率过高会被封 IP。
  • 返回内容出现验证码、空数据或风控页面。
  • 同一个接口在浏览器能访问,程序请求失败。
  • Headers 缺失时返回异常。

可以这样回答:

我会先比较浏览器正常请求和程序模拟请求的差异。如果同样的 URL 在浏览器里返回正常,但程序里返回空数据、验证码、403 或风控响应,就说明可能存在反爬。再继续分析是否有动态参数、token 校验、cookie 校验、频率限制或行为检测。

XPath 和 CSS Selector 都能定位 HTML 节点。

对比项XPathCSS Selector
语法类似路径表达式类似 CSS 选择器
能力更强,支持轴、文本、复杂路径简洁,适合常见选择
可读性复杂表达式可读性一般简单场景更清晰
爬虫常用度很常用也常用

面试可以说:

简单页面我会用 CSS Selector,因为语法简洁;复杂定位,比如按文本、层级、相邻节点查找时,我更倾向 XPath。

可以按流程回答:

  1. 使用 Network 抓包找到目标接口。
  2. 确认哪个参数是动态生成的。
  3. 全局搜索参数名。
  4. 在 Sources 面板下断点。
  5. 观察 Call Stack 调用链。
  6. 找到最终生成参数的函数。
  7. 用 Python 或 Node 复现算法。

更完整的回答:

我在项目中遇到过接口参数由 JS 加密生成的情况。处理时先通过 Network 找到接口和异常参数,然后在 JS 文件中搜索参数名。如果搜索不到,就从请求发起位置或 XHR 断点入手,在 Sources 里下断点,结合 Call Stack 分析调用链,找到参数生成函数。确定算法后,再用 Python 或 Node 复现,最后和浏览器生成结果对比,确保请求参数一致。

这类回答要强调“分析和复现授权接口参数”,不要说成攻击或绕过安全系统。

JS 混淆后,变量名和函数名可能没有意义,所以不要期待完全看懂所有代码。

常见思路:

  • 不全量还原,只找关键链路。
  • 通过 XHR/fetch 断点定位请求发起位置。
  • 使用 Call Stack 看调用链。
  • 打印关键变量。
  • 对关键函数做输入输出对比。
  • 必要时把关键函数拎出来运行。

可以这样回答:

遇到混淆 JS 时,我不会从头读完整文件,而是围绕目标接口定位关键参数。通过断点、调用栈、关键变量打印和函数输入输出分析,逐步缩小范围,最终定位生成参数的函数。

Selenium 是浏览器自动化工具,适合复杂页面、需要真实浏览器环境的场景。

但它的问题也明显:

  • 启动浏览器成本高。
  • 并发能力弱。
  • 资源占用大。
  • 速度慢。
  • 大规模采集不划算。

所以一般优先分析接口直接请求。只有接口很难复现、页面强依赖浏览器环境、或需要真实交互时,才考虑 Selenium 或 Playwright。

面试回答:

Selenium 可以用,但我不会作为首选。因为大规模采集更关注吞吐和稳定性,直接请求接口效率更高。Selenium 更适合登录、复杂交互或无法绕开浏览器渲染的页面。

长期稳定运行靠的不是一个脚本,而是容错和监控。

常见机制:

  • 请求超时。
  • 失败重试。
  • 指数退避。
  • 异常捕获。
  • 失败任务记录。
  • 账号状态检测。
  • IP 或代理状态检测。
  • 任务监控。
  • 健康检查。
  • 失败告警。

可以这样回答:

我会为爬虫设计超时重试、异常捕获、失败任务记录和任务监控机制。如果请求失败,会根据错误类型决定重试、切换账号、切换代理或标记任务失败。系统层面会有健康监测和失败上报,保证爬虫可以长期稳定运行。

如果项目里支持 500万+ / 日 的采集规模,可以这样回答:

系统支持 500 万以上日采集量。采集任务不是由单个脚本完成,而是通过任务调度系统统一拆分和分发,多节点并发执行。采集结果进入 Kafka,再由 Flink 进行实时清洗和处理。

面试时不要只报数字,最好补上支撑数字的架构。

整体链路可以这样描述:

调度系统
|
v
爬虫节点
|
v
Kafka
|
v
Flink
|
v
MySQL / Elasticsearch / 数据仓库

各模块职责:

模块作用
调度系统生成任务、分配任务、协调账号
爬虫节点执行采集、解析数据、处理重试
Kafka解耦采集和处理,缓冲流量
Flink实时清洗、过滤、转换
存储层存储清洗后的业务数据

这类回答会比“我用 Scrapy 分布式”更有工程感。

调度系统主要负责任务生成和账号协调。

你笔记中的规模是:

  • 1400+ 爬虫任务。
  • 400+ 账号 Cookie。
  • 任务信息存储在 Redis。

可以这样回答:

调度系统会把采集目标拆成具体任务,任务状态存储在 Redis 中。爬虫节点从 Redis 获取任务,执行后回写任务状态。账号 Cookie 也由调度系统统一管理,分配任务时会根据账号状态选择可用账号,避免单个账号压力过大。

Redis 适合做任务队列和状态缓存。

原因:

  • 读写性能高。
  • 支持 List、Set、Hash、Sorted Set 等结构。
  • 适合存任务状态、账号状态和临时调度数据。
  • 操作简单,延迟低。

可以补一句:

如果任务需要更强的可靠性、确认机制和重试语义,也可以引入消息队列;Redis 更适合轻量级任务调度和状态管理。

账号失效的表现:

  • 登录失败。
  • Cookie 失效。
  • 返回 401、403。
  • 返回验证码或风控页面。
  • 请求结果为空或异常。

处理方式:

  • 标记账号不可用。
  • 暂停该账号任务。
  • 重新调度任务。
  • 切换可用账号。
  • 触发重新登录或人工处理。

可以这样回答:

系统会根据响应状态和页面内容判断账号是否异常。一旦发现 Cookie 失效或登录状态异常,就标记账号状态,避免继续分配任务,同时把未完成任务重新放回队列,交给其他可用账号处理。

常用库:

  • requests:发送 HTTP 请求。
  • httpx:支持同步和异步请求。
  • scrapy:爬虫框架。
  • lxml:解析 HTML,支持 XPath。
  • beautifulsoup4:HTML 解析。
  • selenium:浏览器自动化。
  • playwright:现代浏览器自动化。

项目里如果主要使用 requests + XPath,可以这样说:

普通接口采集我主要使用 requests,请求接口后用 XPath 或 JSON 解析数据。如果是复杂任务调度和大规模采集,会考虑 Scrapy 或自研调度系统。

基本做法:

request-timeout.py
import requests
try:
response = requests.get(
"https://example.com/api",
timeout=(3, 10),
)
response.raise_for_status()
except requests.Timeout:
# 记录超时并重试
pass
except requests.RequestException:
# 记录其他请求异常
pass

可以配合:

  • 固定次数重试。
  • 指数退避。
  • 失败任务入库。
  • 切换代理或账号。

常见方式:

  • 设置请求间隔。
  • 限制并发数量。
  • 使用任务队列控制消费速度。
  • 对单域名限速。
  • 对单账号限速。
  • 对异常响应动态降速。

面试里可以说:

控制速度不只是 sleep,而是结合并发数、任务队列、账号维度和站点响应来动态调整,避免触发反爬,也保护目标站点和自身系统。

Docker 的价值:

  • 保证运行环境一致。
  • 方便部署。
  • 方便横向扩展多个爬虫节点。
  • 便于隔离依赖。
  • 适合配合 CI/CD。

爬虫系统里尤其适合把爬虫节点容器化。需要扩容时,可以快速启动多个容器实例。

Kafka 主要承担数据通道和缓冲层。

作用:

  • 解耦采集和处理。
  • 缓冲高峰流量。
  • 支持高吞吐数据传输。
  • 方便后续多个消费者处理数据。

可以这样回答:

爬虫采集速度和后续清洗入库速度不一定一致,所以中间用 Kafka 解耦。爬虫只负责把原始数据写入 Kafka,Flink 再从 Kafka 消费并清洗处理。

Redis 在项目中可以承担:

  • 任务队列。
  • 任务状态缓存。
  • 账号 Cookie 管理。
  • 去重集合。
  • 临时失败记录。
  • 限速计数。

面试回答:

Redis 主要用于调度层,保存任务队列、任务状态和账号 Cookie。因为它读写快,并且数据结构丰富,适合管理这种高频变化的临时状态。

可以回答 JS 加密参数逆向。

更完整的说法:

最难的是 JS 加密参数逆向。因为网站 JS 做了混淆,不能直接通过阅读代码看懂逻辑。我通过 Network 定位接口和动态参数,再用 Sources 下断点,结合调用栈分析参数生成流程,最后把关键算法用 Python 或 Node 复现出来。这个过程比较考验调试能力和耐心。

处理步骤:

  1. 先复现问题,确认是哪些请求失败。
  2. 对比正常浏览器请求和爬虫请求差异。
  3. 判断新增机制:token、cookie、签名、频率、验证码、行为检测。
  4. 如果是参数变化,重新调试 JS。
  5. 如果是频率问题,调整限速和调度策略。
  6. 如果涉及强验证或合规风险,停止采集或走授权接口。

可以这样回答:

我会先分析新增反爬属于哪一类,再决定策略。如果是参数签名变化,就重新定位 JS 生成逻辑;如果是频率限制,就降低并发、调整账号和代理策略;如果是登录态或 Cookie 变化,就更新账号状态检测和重新登录流程。对于验证码或强风控场景,需要评估合规性,不能盲目绕过。

如果面试官让你整体介绍这个爬虫项目,可以这样组织:

这个项目主要做大规模数据采集。整体链路是爬虫采集、Kafka 缓冲、Flink 清洗、最终写入存储。爬虫侧通过 Chrome DevTools 分析接口,优先直接请求接口而不是 Selenium。调度系统负责管理 1400 多个任务和 400 多个账号 Cookie,任务状态存储在 Redis。系统支持超时重试、失败任务记录、账号失效检测和健康监控。项目中比较难的是 JS 加密参数逆向,我通过断点调试、调用栈分析和算法复现解决过接口动态参数问题。

爬虫和逆向面试题,重点不是只会某个库,而是能把采集链路讲完整:

接口分析 -> 登录态处理 -> 参数逆向 -> 任务调度 -> 并发控制 -> 数据通道 -> 清洗入库 -> 监控补偿

如果能把这条链路讲清楚,再结合自己实际做过的规模、账号调度、Kafka/Flink、Redis 和 Docker,回答就会更像真实项目经验,而不是零散知识点。