从 Git 规范到 BFS 爬虫:构建多线程新闻搜索引擎的工程思维(二)

4 阅读4分钟

从 Git 规范到 BFS 爬虫:构建多线程新闻搜索引擎的工程思维(二)

在上一篇我们明确了项目方向,本篇将围绕工程实践展开:
如何规范 Git 操作、如何设计爬虫算法、如何重构代码以支持未来扩展(数据库与 Elasticsearch)、以及如何利用 Java 8 Stream 优化代码表达能力。

本项目的最终目标是:

  • 爬取新浪新闻页面(如 新浪 旗下新闻站点)
  • 使用数据库存储并进行数据分析
  • 随着数据量增长迁移到 Elasticsearch
  • 最终实现一个简单的“新闻搜索引擎”

一、Git 版本回滚与专业提交规范

工程能力,首先体现在版本控制。

1.1 回滚代码的几种情况

① 本地回滚一个版本

git reset --hard HEAD~1

作用:回退到上一个提交。


② 已经 push 到远程的情况

需要分场景:

✔ 在主干(main/master)上

原则:不要破坏公共历史。

正确做法:

  • 删除错误文件
  • 重新提交一个“修复提交”

而不是强行 reset。


✔ 在分支上

如果是个人分支,可以:

git reset --hard HEAD~1
git push -f

强制推送(force push)。


1.2 专业 Commit 怎么写?

一个优秀的 commit 包含两部分:

第一行:总结(不超过 50 字符)

feat: implement BFS crawler core algorithm

下面:具体描述

- add queue-based traversal
- extract link parsing logic
- prepare abstraction for future DB storage

优秀 commit 的意义:

  • 方便 code review
  • 方便回滚
  • 方便未来维护
  • 构建工程专业度

二、为什么爬虫是“虫”?——算法思想

2.1 为什么互联网叫“网”?

因为它本质是:

一个由链接构成的图(Graph)

网页是节点(Node)
超链接是边(Edge)

爬虫从一个节点出发,遍历整个网络。


2.2 爬虫核心算法:广度优先搜索(BFS)

我们采用:

广度优先搜索(Breadth-First Search)的变体

基本逻辑:

  1. 从种子 URL 出发
  2. 解析页面
  3. 提取所有链接
  4. 加入队列
  5. 依次处理

本质是:

队列(Queue)驱动的图遍历

强烈建议:

👉 手写一遍 BFS 遍历一棵树

你会学到:

  • 队列数据结构
  • JDK 中 Queue 的实现
  • 图遍历思想
  • 访问去重策略

这是算法能力的基础训练。


三、如何让爬虫具备“可扩展性”?

我们不是写一次性脚本。

我们是在构建一个:

可扩展、可重构、可演进的系统。

3.1 重构烂代码

原则:

  • 方法保持短小
  • 单一职责
  • 去掉重复逻辑
  • 提高抽象层次

3.2 为未来扩展做准备

未来我们要支持:

  • MySQL
  • MongoDB
  • Elasticsearch
  • 甚至 Kafka

所以:

✔ 抽象存储层

interface NewsRepository {
    void save(News news);
}

将数据库实现与爬虫解耦。

未来只需替换实现类。


3.3 爬虫通用化

目标:

  • 不只爬新浪
  • 可以配置不同网站
  • 可扩展解析规则

这就是架构思维。


四、代码短小化的好处

为什么要把代码写短?

1️⃣ 人脑只能处理有限复杂度

你是人类,不是编译器。

短方法:

  • 更容易理解
  • 更容易 Debug
  • 更容易测试

2️⃣ 更容易复用

短方法更具“原子性”。

例如:

extractLinks(Document doc)

可以在多种场景复用。


3️⃣ 便于多态扩展

Java 支持覆盖(Override):

  • 子类可以重写行为
  • 实现策略替换
  • 构建插件式结构

这就是面向对象真正的力量。


五、Java 8 Stream:从过程式到描述式

传统写法(过程式):

ArrayList<Element> links = doc.select("a");

for (Element aTag : links) {
    linkPool.add(aTag.attr("href"));
}

思维方式:

“我如何一步一步做?”


5.1 Stream 写法(描述式)

links.stream()
     .map(aTag -> aTag.attr("href"))
     .forEach(linkPool::add);

思维方式:

“我要把链接变成 href,然后加入池子。”


5.2 Stream 的核心思想

  • map:把一种数据变成另一种数据
  • filter:筛选
  • collect:收集
  • forEach:消费

优点:

  • 表达意图更清晰
  • 减少模板代码
  • 更符合函数式思想
  • 为并行流做准备

六、Merge 的三种方式

当我们开发 algorithm-basic 分支并准备合并时,有三种常见方式:


6.1 普通 Merge

等价于:

git merge algorithm-basic

特点:

  • 会产生一个新的 merge commit
  • 保留完整历史

适合团队协作。


6.2 Squash Merge(压缩合并)

等价于:

git merge --squash algorithm-basic

特点:

  • 把一堆提交压缩成一个提交
  • 历史更干净

适合:

  • 实验性分支
  • 中间过程较混乱

6.3 Rebase(变基)

特点:

  • 改写提交历史
  • 让提交线性化
  • 历史更整洁

适合:

  • 提交前整理历史
  • 提升可读性

七、工程能力的提升路径

通过这一课,我们学到的不只是:

  • BFS 算法
  • Git 操作
  • Java Stream

更重要的是:

工程思维。

一个真正的工程项目应该:

  • 有清晰的版本控制
  • 有可扩展的架构设计
  • 有良好的代码结构
  • 有未来演进路线(数据库 → Elasticsearch → 搜索引擎)

结语

本项目不仅是一个“多线程网络爬虫”,
它更是一次完整的软件工程训练:

  • 算法能力(BFS)
  • 架构设计能力
  • 代码抽象能力
  • Git 版本管理能力
  • Java 8 表达能力

当数据量增长时,我们将迁移到 Elasticsearch,
构建一个真正可搜索的新闻系统。

这才是一个工程项目真正的成长路径。