爬虫代码重构:从脚本到可维护项目

很多开发者的爬虫生涯,都是从一段几十行的脚本 开始的。能跑就行、逻辑直出、一把梭哈,快速拿到数据是第一目标。但随着需求迭代:网站改版、反爬升级、字段增多、多站点复用、定时调度、日志排查...... 原本顺滑的小脚本,很快会变成难读、难改、难扩展的 "屎山"。

真正的生产环境,需要的不是 "能用",而是可维护、可扩展、可监控 的爬虫项目。本文就带你完成一次关键蜕变:从一次性脚本,到工程化、可维护的爬虫项目

一、先认清:脚本式爬虫的致命问题

一段典型的 "脚本爬虫" 长这样:

  • 所有代码塞在一个 .py 文件里
  • 请求、解析、存储、逻辑硬编码混在一起
  • 配置写死在代码中,改个 URL 要翻遍全文
  • 无异常处理,一报错直接崩,不知道死在哪
  • 无日志、无重试、无去重,数据质量不可控
  • 换个网站 / 换个页面,只能复制粘贴改代码

这种写法在小需求、临时任务里没问题,但一旦进入长期维护,成本会指数级上升。

二、重构目标:可维护项目的核心标准

重构不是 "重写",而是让代码变得更好。一个合格的可维护爬虫项目,应满足:

  1. 结构清晰:职责分离,一眼看懂流程
  2. 配置与代码分离:不改代码就能适配站点变化
  3. 健壮稳定:自动重试、异常捕获、降级处理
  4. 易于扩展:新增站点 / 字段只加代码不改旧逻辑
  5. 可观测:日志清晰、进度可见、方便排查
  6. 可测试:核心逻辑可单独运行验证

三、实战重构:从脚本到项目的标准路径

我们以最常见的 Python requests + parsel/lxml 为例,一步步拆解。

1. 项目结构规范化

抛弃单文件,建立标准目录结构:

plaintext

复制代码
spider_project/
├── config/           # 配置文件
│   ├── settings.py   # 全局配置:请求头、超时、重试、路径
│   └── sites.py      # 各站点规则配置:URL、XPath/选择器
├── core/             # 核心通用模块
│   ├── http.py       # 请求封装:重试、代理、请求头
│   ├── parser.py     # 解析封装:通用提取方法
│   └── storage.py    # 存储封装:MySQL/CSV/JSON/ES
├── spiders/          # 具体爬虫
│   ├── __init__.py
│   ├── blog_spider.py
│   └── product_spider.py
├── utils/            # 工具类
│   ├── logger.py     # 日志统一配置
│   ├── time_util.py  # 时间处理
│   └── validate.py   # 数据校验
├── main.py           # 项目入口
└── requirements.txt  # 依赖清单

好处:新人接手也能快速定位功能,不用在几千行里大海捞针。

2. 配置与代码解耦

把所有可变内容抽到配置里:

  • URL、列表页 / 详情页规则
  • 请求头、Cookie、超时时间
  • 数据库连接信息
  • 重试次数、并发数、间隔时间

python

运行

复制代码
# config/settings.py
BASE_HEADERS = {
    "User-Agent": "Mozilla/5.0 ...",
    "Accept-Language": "zh-CN,zh;q=0.9"
}
MAX_RETRY = 3
TIMEOUT = 10

python

运行

复制代码
# config/sites.py
BLOG_SITE = {
    "list_url": "https://xxx.com/page/{}",
    "total_page": 10,
    "title_xpath": "//h1/text()",
    "content_xpath": "//div[@class='content']//text()"
}

以后网站改了 XPath,只改配置,不动逻辑代码

3. 封装通用请求模块(核心!)

脚本里最常见的就是满屏重复的 requests.get()。统一封装 HTTP 请求,内置:

  • 自动重试
  • 随机请求头
  • 代理切换
  • 状态码判断
  • 统一异常捕获

python

运行

复制代码
# core/http.py
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

def get_session():
    session = requests.Session()
    retry = Retry(total=3, backoff_factor=1)
    adapter = HTTPAdapter(max_retries=retry)
    session.mount("https://", adapter)
    session.mount("http://", adapter)
    return session

def fetch(url, headers=None):
    # 统一请求逻辑
    ...

所有爬虫共用这一套,改一次,全项目生效

4. 抽取通用解析函数

把 XPath/CSS 提取、文本清洗、去空格、去换行、时间格式化,抽成通用方法:

python

运行

复制代码
# core/parser.py
def extract_text(selector, xpath):
    result = selector.xpath(xpath).extract_first()
    return result.strip() if result else ""

爬虫里只写:

python

运行

复制代码
title = extract_text(selector, site_config["title_xpath"])

解析规则统一,避免每个人写得五花八门。

5. 统一存储层

不要在爬虫里直接写 INSERT INTO。封装存储接口,支持切换存储介质:

  • CSV/JSON(本地测试)
  • MySQL/PostgreSQL(生产)
  • Elasticsearch(搜索)
  • Kafka/MQ(数据流)

python

运行

复制代码
# core/storage.py
def save_item(item):
    # 统一去重、校验、入库
    ...

以后换存储引擎,爬虫代码完全不用改

6. 标准化日志系统

用日志代替 print(),记录:

  • 当前爬取 URL
  • 响应状态
  • 解析失败字段
  • 异常信息
  • 爬取速度、总量

python

运行

复制代码
# utils/logger.py
import logging
def get_logger(name):
    # 按文件/控制台输出,按级别切割
    ...

线上出问题,看日志就能定位,不用复现现场。

7. 爬虫业务只写 "业务"

经过封装,最终爬虫文件会极度干净:

python

运行

复制代码
# spiders/blog_spider.py
from config.sites import BLOG_SITE
from core.http import fetch
from core.parser import extract_text
from core.storage import save_item
from utils.logger import get_logger

logger = get_logger(__name__)

def run():
    for page in range(1, BLOG_SITE["total_page"] + 1):
        url = BLOG_SITE["list_url"].format(page)
        resp = fetch(url)
        # 解析
        items = parse_list(resp.text)
        for item in items:
            save_item(item)
        logger.info(f"页面 {page} 完成")

def parse_list(html):
    # 只写当前页面专属解析逻辑
    ...

只关注业务,不关注底层细节

8. 统一入口与可执行性

提供一个干净的入口:

python

运行

复制代码
# main.py
from spiders.blog_spider import run as run_blog

if __name__ == "__main__":
    run_blog()

配合 argparse,可以支持:

plaintext

复制代码
python main.py --spider blog --page 5

四、重构后的进阶能力

当你把爬虫改成项目结构,很多能力会自然解锁:

  1. 单元测试:请求、解析、存储可单独测试
  2. 并发 / 异步改造:只改 core/http.py,不影响业务
  3. 接入调度:配合 Celery/Apscheduler 定时任务
  4. 监控告警:统一统计失败率、响应时间
  5. 多环境切换:开发 / 测试 / 生产配置分离
  6. 代码评审与协作:多人开发不互相覆盖

五、什么时候该重构?

满足任意一条,就别犹豫了:

  • 单个爬虫文件超过 300 行
  • 修改一个字段要改 N 个地方
  • 报错要花半小时定位
  • 不敢删代码,怕崩
  • 新同事看不懂你的逻辑

能跑就行 ≠ 可以上线

六、总结

从脚本到可维护项目,本质是三件事:

  1. 职责分离:请求、解析、存储、日志互不干扰
  2. 通用下沉:重复代码封装成底层模块
  3. 可变上浮:把配置抽离,不写死在逻辑里

重构不会让爬虫 "跑得更快",但会让你维护更轻松、排错更快速、扩展更无痛

如果你是从 "脚本小子" 走向工程化的开发者,这套思路,适用于:普通 requests 爬虫、Playwright 爬虫、Scrapy 项目、甚至分布式爬虫架构。

先写对,再写快;先能用,再好用;先可维护,再谈高性能。

相关推荐
ipooipoo11882 小时前
kookeey 动态住宅 IP 使用场景详解:跨境电商、爬虫、社媒养号一站式解决方案
爬虫·网络协议·tcp/ip
ruanyongjing2 小时前
Python中的简单爬虫
爬虫·python·信息可视化
lzp07913 小时前
python爬虫——爬取全年天气数据并做可视化分析
开发语言·爬虫·python
喵手14 小时前
Python爬虫实战:用代码守护地球,追踪WWF濒危物种保护动态!
爬虫·python·爬虫实战·濒危物种·零基础python爬虫教学·wwf·濒危物种保护动态追踪
喵手14 小时前
Python爬虫实战:自动化抓取 Pinterest 热门趋势与创意!
爬虫·python·爬虫实战·pinterest·零基础python爬虫教学·采集pinterest热门趋势·热门趋势预测
怪侠_岭南一只猿17 小时前
爬虫阶段一实战练习题二:爬取当当网图书列表
css·爬虫·python·html
tang7778918 小时前
哪些行业用动态代理ip?哪些行业用静态代理IP?怎样区分动态ip和静态ip?(互联网人必码·实用长文)
大数据·网络·爬虫·python·网络协议·tcp/ip·智能路由器
tang7778919 小时前
爬虫代理IP池到底有啥用?
网络·爬虫·python·网络协议·tcp/ip·ip
深蓝电商API20 小时前
爬虫监控告警:结合企业微信或钉钉,打造 7×24 小时实时预警系统
爬虫·python·钉钉·企业微信