← 返回博客列表
Anthropic

Anthropic SDE 独家 Coding 真题:Web Crawler(BFS + 多线程优化 + 系统设计)

2026-03-30

这道题本质是图遍历。页面是节点,链接是边。给你一个种子 URL,从它开始把同域名页面全部抓出来。

Anthropic


题目描述

实现一个 Web Crawler:

先写同步版本,再优化成多线程版本


一、同步版本(BFS)

为什么是 BFS?

核心点

  1. urlparse 抽取起始域名
  2. 队列做 BFS
  3. visited 集合去重
  4. 只把同域链接加入队列

Python 代码(同步)

from collections import deque
from urllib.parse import urlparse


def crawl_sync(start_url, get_urls):
    host = urlparse(start_url).netloc

    queue = deque([start_url])
    visited = {start_url}
    result = []

    while queue:
        url = queue.popleft()
        result.append(url)

        for nxt in get_urls(url):
            if urlparse(nxt).netloc != host:
                continue
            if nxt in visited:
                continue
            visited.add(nxt)
            queue.append(nxt)

    return result

复杂度


二、优化版本(ThreadPoolExecutor 并发抓取)

同步版本的瓶颈是 I/O 等待(网络请求慢),不是 CPU。最有效优化是并发请求。

设计思路

这就是面试官常问的:如何在任务就绪后立即处理它

Python 代码(多线程)

from concurrent.futures import ThreadPoolExecutor, wait, FIRST_COMPLETED
from urllib.parse import urlparse


def crawl_threaded(start_url, get_urls, max_workers=8):
    host = urlparse(start_url).netloc

    visited = {start_url}
    result = []

    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        in_flight = {executor.submit(get_urls, start_url): start_url}

        while in_flight:
            done, _ = wait(in_flight.keys(), return_when=FIRST_COMPLETED)

            for fut in done:
                url = in_flight.pop(fut)
                result.append(url)

                try:
                    neighbors = fut.result()
                except Exception:
                    # 面试里可说明:这里可做重试/日志
                    continue

                for nxt in neighbors:
                    if urlparse(nxt).netloc != host:
                        continue
                    if nxt in visited:
                        continue
                    visited.add(nxt)
                    in_flight[executor.submit(get_urls, nxt)] = nxt

    return result

为什么不是“分层并发”?

分层并发(每层等全部任务完成再进入下一层)也能做,但吞吐更低。

这里用“谁先完成先处理”的模式,能更快发现新 URL 并持续压满线程池,整体更高效。


三、urlparse 在这题里的作用

urlparse 常用来做三件事:

  1. 同域过滤netloc
  2. URL 规范化前处理(大小写、端口、路径)
  3. 防止把相对路径/外链误当目标页面

面试时你可以明确说:


四、线程 vs 进程:区别与适用场景

对比维度 线程(Thread) 进程(Process)
内存 共享地址空间 独立地址空间
创建/切换成本
Python GIL 影响 受影响(CPU 密集不占优) 不受同一进程 GIL 限制
适用任务 I/O 密集(网络、磁盘) CPU 密集(压缩、模型推理)
本题适配 ✅ 非常适合 ❌ 通常不优先

结论:Web Crawler 通常是 I/O 密集,优先线程/异步模型。


五、礼貌策略(避免服务器过载)

真实爬虫不能只追求快,还要“礼貌抓取”:

  1. 遵守 robots.txt
  2. 限速(每秒请求数上限)
  3. 并发上限(全局 + 每域名)
  4. 重试+退避(429/5xx 使用指数退避)
  5. 合理 UA 和联系信息
  6. 抓取间隔 jitter(避免固定节奏冲击)

简化版限速思路:token bucket 或 sleep 节流。


六、百万级 URL 的分布式系统设计

当规模从“面试代码”升级到“生产爬虫”,架构一般拆成:

  1. URL Frontier(消息队列)

    • Kafka / SQS / RabbitMQ
    • 存待抓取 URL,支持优先级
  2. Fetcher Worker 集群

    • 横向扩展抓取节点
    • 节点从队列拉取 URL,调用抓取器
  3. 去重服务(URL Dedup)

    • Redis/Bloom Filter + 持久化 KV
    • 防止重复抓取
  4. 内容解析与索引

    • 提取正文、标题、出链
    • 写入对象存储 + 搜索索引
  5. 调度与监控

    • 失败重试、死信队列
    • 监控 QPS、错误率、队列积压

一致性与容错要点


七、不同 URL 下的重复内容检测

同一内容可能有多个 URL:

两层去重策略

  1. URL 级去重

    • 规范化 URL(去追踪参数、统一大小写、排序 query)
  2. 内容级去重

    • 对正文做指纹:
      • 精确:SHA-256
      • 近似:SimHash / MinHash
    • 新页面与历史指纹比较,相似度高则判重

面试表达建议:

先做 URL 规范化降低重复抓取,再做内容指纹避免“不同 URL 同内容”的存储浪费。


八、面试回答模板(可直接复述)

  1. 先同步 BFSqueue + visited + same-host filter
  2. 再并发优化ThreadPoolExecutor + FIRST_COMPLETED
  3. 补系统思维:礼貌抓取、重试退避、限流
  4. 扩展到分布式:frontier 队列 + worker + dedup + 存储
  5. 最后讲判重:URL 标准化 + 内容指纹

这样回答通常能覆盖 coding + follow-up 的大部分评分点。


常见坑位


总结

这题不难,但非常考“工程完整度”:

如果你按这条主线答,面试官通常会觉得你不仅会写题,也懂真实系统。


🚀 需要 VO / OA 实战辅导?

oavoservice 长期覆盖 Anthropic、Google、Amazon、Meta、TikTok、Uber、Bloomberg 等高频题与面试场景。

微信:Coding0201

可提供:


联系方式

Email: [email protected]
Telegram: @OAVOProxy