用算法和爬虫搞副业:一个成都斜杠程序员的CV实战手记
上周五晚上十一点半,我瘫在椅子上盯着 VSCode 里一堆红得发亮的报错,心里直嘀咕:“这破项目再搞不定,下个月房租怕是要靠泡面续命了。”
没错,这就是我——一个坐标成都、白天写业务代码、晚上接外包副业的斜杠程序员。生活节奏是舒服,但钱包节奏就……你懂的。最近一个朋友介绍了个小活儿:帮一家本地宠物店做“自动识别流浪猫品种”的小程序。听起来简单?实则暗坑无数。今天就来聊聊这个从零开始的计算机视觉(CV)实战项目,顺便吐吐槽、晒晒代码。
为啥又搞 CV?被需求逼的
事情起源于上个月。那家宠物店老板想做个公益功能:路人拍张流浪猫照片上传,系统自动识别品种+是否绝育(通过耳标判断),然后推送附近的领养信息。听起来很有爱,对吧?但问题来了——他们没数据、没标注、没预算。
“我们有图!”老板说,“抖音、小红书、微博上一大堆!”
于是,爬虫 + 算法 的组合拳,成了唯一出路。
说实话,我主业是后端+全栈,CV 只是大学时摸过 OpenCV,工作后基本靠现学。但谁让成都的副业市场卷呢?不学点新东西,连外包单子都抢不到。于是咬牙开干:先爬数据,再训模型,最后部署上线。
第一步:爬虫不是你想爬,想爬就能爬
很多人以为爬虫就是 requests + BeautifulSoup 走天下。现实是:反爬机制越来越狠,尤其是小红书和微博这种平台,IP 封得比火锅底料还快。
我一开始用 Scrapy 写了个基础爬虫,目标是关键词“流浪猫”、“猫咪救助”下的图片。结果跑了一小时,IP 被封了三次,还触发了验证码滑块——那一刻我真的想砸键盘。
后来换了策略:
- 代理池轮换:用了免费代理 + 自建轻量级代理池(基于 Redis 队列)
- User-Agent 随机化:从真实浏览器 UA 列表里随机选
- 请求间隔随机化:避免固定频率被识别为机器人
- 图片直链提取:绕过页面渲染,直接解析 JSON 接口(比如小红书的
/api/sns/web/v1/note/...)
关键代码片段如下(简化版):
import requests
import random
import time
from fake_useragent import UserAgent
ua = UserAgent()
proxies = get_random_proxy() # 从代理池获取
headers = {
"User-Agent": ua.random,
"Referer": "https://www.xiaohongshu.com/"
}
def fetch_image_urls(keyword, page=1):
url = f"https://edith.xiaohongshu.com/api/sns/web/v1/search/notes?keyword={keyword}&page={page}"
try:
resp = requests.get(url, headers=headers, proxies=proxies, timeout=10)
data = resp.json()
image_urls = []
for note in data['data']['notes']:
if 'images' in note:
image_urls.extend(note['images'])
return image_urls
except Exception as e:
print(f"爬取失败: {e}")
return []
💡 经验之谈:别硬刚反爬!能走 API 就别解析 HTML,能模拟 App 请求就别用浏览器自动化(除非万不得已)。省下的时间够你多调两轮超参。
最终,我爬了约 1.2 万张图片,涵盖中华田园猫、英短、美短、布偶等主流品种。当然,也混进了不少狗、风景、甚至自拍……数据清洗成了下一关。
数据清洗:脏数据比 Bug 更可怕
原始数据里,有 30% 是无效图(非猫、模糊、截图等)。手动筛?不可能。我写了个多阶段过滤脚本:
- 用预训练模型初筛:加载 torchvision 的 ResNet50(ImageNet 预训练),只保留预测类别为“cat”(class_id=281)的图片。
- 去重:用 perceptual hash(pHash)计算图像指纹,相似度 >95% 的视为重复。
- 分辨率过滤:低于 300x300 的直接丢弃。
这部分代码用了 OpenCV + PIL + torchvision,跑在本地 Docker 容器里,省得污染我的主力开发环境(VSCode 插件已经装到 87 个,不能再乱了)。
清洗后剩下约 6000 张有效图,按品种分文件夹。但问题又来了:类别极度不平衡。中华田园猫占 70%,布偶猫只有 200 张。直接训?模型肯定偏科。
解决方案:过采样 + 数据增强。
- 对少数类(如布偶、暹罗)做旋转、裁剪、色彩抖动
- 使用
torchvision.transforms构建动态增强 pipeline - 训练时启用
WeightedRandomSampler,让每个 batch 中各类别比例接近 1:1
from torch.utils.data import WeightedRandomSampler
# 计算每个类别的权重
class_counts = [4200, 800, 600, 200, 200] # 示例
weights = 1. / torch.tensor(class_counts, dtype=torch.float)
samples_weights = weights[target_labels]
sampler = WeightedRandomSampler(samples_weights, len(samples_weights))
train_loader = DataLoader(dataset, batch_size=32, sampler=sampler)
模型选型:别一上来就上 YOLOv8
很多新手一听说 CV,立刻想到 YOLO、Detectron2 这些大杀器。但在这个项目里,任务只是图像分类(不是检测,也不是分割),而且部署环境是微信小程序后端(资源有限)。
我试了三种方案:
| 模型 | 参数量 | 准确率(val) | 推理速度(CPU) | 是否适合部署 |
|---|---|---|---|---|
| ResNet18 | 11M | 82.3% | 45ms | ✅ |
| EfficientNet-B0 | 5.3M | 84.1% | 38ms | ✅✅ |
| ViT-Tiny | 5.7M | 80.7% | 120ms | ❌ |
结论很明显:EfficientNet-B0 性价比最高。轻量、准确、推理快,还支持 ONNX 导出。
训练过程用了 PyTorch Lightning,省去大量 boilerplate 代码。关键配置:
model:
backbone: efficientnet_b0
num_classes: 5
pretrained: true
training:
epochs: 50
lr: 0.001
scheduler: CosineAnnealingLR
augmentations:
- RandomHorizontalFlip
- ColorJitter(brightness=0.2, contrast=0.2)
- RandomRotation(degrees=15)
跑了 12 小时(RTX 3060 笔记本),最终 val 准确率 86.4%,混淆矩阵显示“中华田园猫”偶尔会被误判为“美短”,但业务可接受——毕竟用户要的是“大致品种”,不是基因检测。
部署上线:别让运维背锅
模型训好了,怎么上线?客户要求:API 响应 <1s,月成本 <200 元。
我放弃了 Flask + Gunicorn 的老套路(太重),改用 FastAPI + Uvicorn + ONNX Runtime:
- 模型导出为 ONNX 格式,体积从 15MB 压到 8MB
- ONNX Runtime 推理速度比原生 PyTorch 快 2.3 倍(实测)
- 用 Docker 打包,部署到腾讯云轻量服务器(成都区,延迟低)
Dockerfile 特意精简:
FROM python:3.9-slim
RUN pip install onnxruntime==1.15.1 fastapi uvicorn pillow
COPY app.py model.onnx ./
EXPOSE 8000
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
上线当天,测试同学提了个 bug:“上传黑猫,返回‘未知品种’”。查日志发现是光照太暗导致特征提取失败。临时加了个亮度校正预处理:
def adjust_brightness(img: Image.Image) -> Image.Image:
hsv = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2HSV)
h, s, v = cv2.split(hsv)
v = cv2.add(v, 30) # 提亮
v = np.clip(v, 0, 255)
final_hsv = cv2.merge((h, s, v))
return Image.fromarray(cv2.cvtColor(final_hsv, cv2.COLOR_HSV2RGB))
搞定!现在系统每天处理 300+ 请求,准确率稳定在 85% 左右。老板说下周要加“耳标检测”功能……唉,副业真是永无止境。
复盘:算法不是万能,但没算法万万不能
这次项目让我深刻体会到:CV 落地 = 30% 算法 + 70% 工程 + 100% 耐心。
- 爬虫是数据源头,但法律边界要小心(只用于公益、不商用、不存原始图)
- 模型不必最先进,但要最适合场景(轻量、快、稳)
- 部署要考虑成本和维护性,别为了炫技堆复杂架构
顺便吐槽一句:产品经理总以为“AI 能自动搞定一切”,殊不知光是清洗那 6000 张图,我就熬了两个通宵。不过看到流浪猫被成功领养的消息推送出去时,又觉得值了。
给想搞 CV 副业的朋友几点建议
- 从小场景切入:别一上来就想做人脸识别、自动驾驶。像“猫品种识别”这种垂直需求,反而更容易变现。
- 善用预训练模型:Hugging Face、TorchVision 里的模型,90% 的场景够用。
- 爬虫要合规:遵守 robots.txt,控制请求频率,别惹官司。
- 重视数据质量:Garbage in, garbage out。宁可少而精,不要多而烂。
- 工具链要顺手:我的 VSCode 里装了 Python Docstring Generator、Pylance、Docker、Remote - SSH……效率翻倍。
最后,如果你也在成都,接外包、搞副业、或者单纯想聊技术,欢迎来找我喝杯盖碗茶。反正这边生活节奏慢,代码可以慢慢调,bug 可以慢慢修——只要 deadline 别卡在周五晚上就行 😅
(完)

评论 0