Python爬虫零基础入门【第八章:项目实战演练·第3节】上线与运维入门:定时运行、日志轮转、失败告警(轻量版)!

🔥本期内容已收录至专栏《Python爬虫实战》,持续完善知识体系与项目实战,建议先订阅收藏,后续查阅更方便~持续更新中!!

全文目录:

      • [🌟 开篇语](#🌟 开篇语)
      • [📚 上期回顾](#📚 上期回顾)
      • [🎯 本篇目标](#🎯 本篇目标)
      • [💡 上线前的准备清单](#💡 上线前的准备清单)
      • [🛠️ 模块 1:配置管理](#🛠️ 模块 1:配置管理)
      • [🛠️ 模块 2:日志管理](#🛠️ 模块 2:日志管理)
      • [🛠️ 模块 3:定时任务](#🛠️ 模块 3:定时任务)
        • [方案 1:系统 Crontab(推荐 Linux)](#方案 1:系统 Crontab(推荐 Linux))
        • [方案 2:APScheduler(推荐 Windows 或需要复杂调度)](#方案 2:APScheduler(推荐 Windows 或需要复杂调度))
      • [🛠️ 模块 4:异常告警](#🛠️ 模块 4:异常告警)
      • [🛠️ 模块 5:主程序改造](#🛠️ 模块 5:主程序改造)
      • [📝 运维文档模板](#📝 运维文档模板)
      • [📝 小结](#📝 小结)
      • [🎓 专栏总结](#🎓 专栏总结)
      • [🌟 文末](#🌟 文末)
        • [📌 专栏持续更新中|建议收藏 + 订阅](#📌 专栏持续更新中|建议收藏 + 订阅)
        • [✅ 互动征集](#✅ 互动征集)

🌟 开篇语

哈喽,各位小伙伴们你们好呀~我是【喵手】。

运营社区: C站 / 掘金 / 腾讯云 / 阿里云 / 华为云 / 51CTO

欢迎大家常来逛逛,一起学习,一起进步~🌟

我长期专注 Python 爬虫工程化实战 ,主理专栏 👉 《Python爬虫实战》:从采集策略反爬对抗 ,从数据清洗分布式调度 ,持续输出可复用的方法论与可落地案例。内容主打一个"能跑、能用、能扩展 ",让数据价值真正做到------抓得到、洗得净、用得上

📌 专栏食用指南(建议收藏)

  • ✅ 入门基础:环境搭建 / 请求与解析 / 数据落库
  • ✅ 进阶提升:登录鉴权 / 动态渲染 / 反爬对抗
  • ✅ 工程实战:异步并发 / 分布式调度 / 监控与容错
  • ✅ 项目落地:数据治理 / 可视化分析 / 场景化应用

📣 专栏推广时间 :如果你想系统学爬虫,而不是碎片化东拼西凑,欢迎订阅/关注专栏《Python爬虫实战》

订阅后更新会优先推送,按目录学习更高效~

📚 上期回顾

恭喜你完成了信息聚合站项目《项目 2:信息聚合站 Demo(列表+详情+增量+质量报告)》!现在你有了一个功能完整的爬虫系统------能采集、能去重、能生成质量报告。但它还只是一个"玩具",离真正的"生产级"还差最后一步。

你有没有想过这些问题:

  • 怎么让爬虫每天自动运行,不用手动敲命令?
  • 日志文件越来越大,怎么自动清理
  • 采集失败了,怎么第一时间知道

今天,我们就来搞定这最后一公里------让爬虫真正上线运行

🎯 本篇目标

看完这篇,你能做到:

  1. 配置定时任务(crontab/理**(日志轮转、分级记录)
  2. 异常监控告警(邮件/企业微信/钉钉)
  3. 编写运维文档(让别人也能接手)

验收标准:爬虫能每天凌晨 2 点自动运行,失败时发送告警通知

💡 上线前的准备清单

检查清单
json 复制代码
✅ 代码能稳定运行(至少手动跑 3 次不出错)
✅ 数据库路径是绝对路径(不要用相对路径)
✅ 敏感信息已配置化(API Key、数据库密码等)
✅ 依赖已冻结(requirements.txt)
✅ README 写清楚了(别人能看懂怎么用)
目录结构规范
json 复制代码
my_spider/
├── config/
│   ├── config.yaml          # 配置文件
│   └── logging.yaml         # 日志配置
├── src/
│   ├── crawler.py           # 爬虫主逻辑
│   ├── db_manager.py        # 数据库管理
│   └── utils.py             # 工具函数
├── logs/                    # 日志目录
├── data/                    # 数据目录
│   └── aggregator.db
├── requirements.txt         # 依赖清单
├── main.py                  # 入口文件
└── README.md                # 使用文档

🛠️ 模块 1:配置管理

配置文件设计
yaml 复制代码
# config/config.yaml
app:
  name: "信息聚合爬虫"
  version: "1.0.0"
  
crawler:
  source_name: "示例新闻网"
  max_pages: 10
  timeout: 15
  retry: 3
  delay: 2  # 请求间隔(秒)
  
database:
  path: "/home/user/my_spider/data/aggregator.db"
  
logging:
  level: "INFO"
  log_dir: "/home/user/my_spider/logs"
  max_bytes: 10485760  # 10MB
  backup_count: 5
  
alert:
  enabled: true
  type: "email"  # email/wechat/dingtalk
  email:
    smtp_server: "smtp.qq.com"
    smtp_port: 587
    sender: "your_email@qq.com"
    password: "your_password"
    receivers: ["admin@example.com"]
  threshold:
    fail_rate: 0.3  # 失败率超过 30% 告警
配置加载器
python 复制代码
# src/config_loader.py
import yaml
from pathlib import Path

class Config:
    """配置管理器"""
    
    def __init__(self, config_file="config/config.yaml"):
        self.config_file = Path(config_file)
        self.config = self._load_config()
    
    def _load_config(self):
        """加载配置文件"""
        if not self.config_file.exists():
            raise FileNotFoundError(f"配置文件不存在:{self.config_file}")
        
        with open(self.config_file, 'r', encoding='utf-8') as f:
            return yaml.safe_load(f)
    
    def get(self, key_path, default=None):
        """
        获取配置项(支持多级路径)
        
        示例:
            config.get('crawler.timeout')  # 获取 crawler.timeout
        """
        keys = key_path.split('.')
        value = self.config
        
        for key in keys:
            if isinstance(value, dict):
                value = value.get(key)
            else:
                return default
            
            if value is None:
                return default
        
        return value

# 全局配置实例
config = Config()

🛠️ 模块 2:日志管理

日志配置
python 复制代码
# src/logger.py
import logging
import logging.handlers
from pathlib import Path
from datetime import datetime

def setup_logger(name='spider', log_dir='logs', level='INFO'):
    """
    配置日志系统
    
    特性:
    - 按日期自动创建日志文件
    - 日志轮转(超过大小自动切分)
    - 同时输出到控制台和文件
    """
    # 创建日志目录
    log_dir = Path(log_dir)
    log_dir.mkdir(exist_ok=True)
    
    # 创建 logger
    logger = logging.getLogger(name)
    logger.setLevel(getattr(logging, level.upper()))
    
    # 清除已有的 handlers(避免重复)
    logger.handlers.clear()
    
    # 日志格式
    formatter = logging.Formatter(
        '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        datefmt='%Y-%m-%d %H:%M:%S'
    )
    
    # 1. 控制台输出
    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.INFO)
    console_handler.setFormatter(formatter)
    logger.addHandler(console_handler)
    
    # 2. 文件输出(按日期)
    today = datetime.now().strftime('%Y%m%d')
    log_file = log_dir / f'spider_{today}.log'
    
    file_handler = logging.handlers.RotatingFileHandler(
        log_file,
        maxBytes=10*1024*1024,  # 10MB
        backupCount=5,
        encoding='utf-8'
    )
    file_handler.setLevel(logging.DEBUG)
    file_handler.setFormatter(formatter)
    logger.addHandler(file_handler)
    
    # 3. 错误日志单独记录
    error_file = log_dir / f'error_{today}.log'
    error_handler = logging.handlers.RotatingFileHandler(
        error_file,
        maxBytes=10*1024*1024,
        backupCount=5,
        encoding='utf-8'
    )
    error_handler.setLevel(logging.ERROR)
    error_handler.setFormatter(formatter)
    logger.addHandler(error_handler)
    
    return logger

# 创建全局 logger
logger = setup_logger()
日志使用示例
python 复制代码
from src.logger import logger

# 不同级别的日志
logger.debug("调试信息:变量值为 xxx")
logger.info("✅ 采集成功:获得 100 条数据")
logger.warning("⚠️ 部分字段缺失:author 字段为空")
logger.error("❌ 采集失败:网络超时")

# 记录异常堆栈
try:
    # 可能出错的代码
    pass
except Exception as e:
    logger.exception(f"未处理的异常:{e}")

🛠️ 模块 3:定时任务

方案 1:系统 Crontab(推荐 Linux)

步骤 1:编写启动脚本

bash 复制代码
#!/bin/bash
# run_spider.sh

# 设置环境变量
export PATH=/usr/local/bin:/usr/bin:/bin
export LANG=zh_CN.UTF-8

# 进入项目目录
cd /home/user/my_spider

# 激活虚拟环境
source venv/bin/activate

# 运行爬虫
python main.py >> logs/cron_$(date +\%Y\%m\%d).log 2>&1

# 记录运行时间
echo "爬虫运行完成:$(date)" >> logs/cron.log

步骤 2:添加执行权限

bash 复制代码
chmod +x run_spider.sh

步骤 3:配置 crontab

json 复制代码
# 编辑定时任务
crontab -e

# 添加以下内容(每天凌晨 2 点运行)
0 2 * * * /home/user/my_spider/run_spider.sh

crontab 时间语法:

json 复制代码
# ┌───────────── 分钟 (0 - 59)
# │ ┌───────────── 小时 (0 - 23)
# │ │ ┌───────────── 日期 (1 - 31)
# │ │ │ ┌───────────── 月份 (1 - 12)
# │ │ │ │ ┌───────────── 星期 (0 - 7,0 和 7 都是周日)
# │ │ │ │ │
# * * * * * 命令

# 示例
0 2 * * *        # 每天凌晨 2 点
0 */6 * * *      # 每 6 小时
30 9 * * 1       # 每周一上午 9:30
0 0 1 * *        # 每月 1 号零点
方案 2:APScheduler(推荐 Windows 或需要复杂调度)
python 复制代码
# scheduler.py
from apscheduler.schedulers.blocking import BlockingScheduler
from apscheduler.triggers.cron import CronTrigger
from datetime import datetime
from src.logger import logger
from main import run_spider

def scheduled_job():
    """定时任务入口"""
    logger.info("="*50)
    logger.info(f"⏰ 定时任务开始:{datetime.now()}")
    logger.info("="*50)
    
    try:
        run_spider()
        logger.info("✅ 定时任务完成")
    except Exception as e:
        logger.exception(f"❌ 定时任务失败:{e}")

def main():
    scheduler = BlockingScheduler()
    
    # 添加定时任务
    scheduler.add_job(
        scheduled_job,
        trigger=CronTrigger(hour=2, minute=0),  # 每天凌晨 2 点
        id='daily_crawl',
        name='每日采集',
        replace_existing=True
    )
    
    logger.info("📅 调度器已启动")
    logger.info("下次运行时间:" + str(scheduler.get_jobs()[0].next_run_time))
    
    try:
        scheduler.start()
    except (KeyboardInterrupt, SystemExit):
        logger.info("调度器已停止")

if __name__ == '__main__':
    main()

运行方式:

bash 复制代码
# 后台运行
nohup python scheduler.py > logs/scheduler.log 2>&1 &

# 或者用 systemd(更专业)

🛠️ 模块 4:异常告警

邮件告警
python 复制代码
# src/alerter.py
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from src.config_loader import config
from src.logger import logger

class EmailAlerter:
    """邮件告警器"""
    
    def __init__(self):
        self.smtp_server = config.get('alert.email.smtp_server')
        self.smtp_port = config.get('alert.email.smtp_port')
        self.sender = config.get('alert.email.sender')
        self.password = config.get('alert.email.password')
        self.receivers = config.get('alert.email.receivers')
    
    def send_alert(self, subject, content):
        """
        发送告警邮件
        
        Args:
            subject: 邮件主题
            content: 邮件内容(支持 HTML)
        """
        if not config.get('alert.enabled'):
            logger.info("告警已禁用,跳过发送")
            return
        
        try:
            # 创建邮件
            msg = MIMEMultipart()
            msg['From'] = self.sender
            msg['To'] = ','.join(self.receivers)
            msg['Subject'] = subject
            
            # 添加正文
            msg.attach(MIMEText(content, 'html', 'utf-8'))
            
            # 发送
            with smtplib.SMTP(self.smtp_server, self.smtp_port) as server:
                server.starttls()
                server.login(self.sender, self.password)
                server.send_message(msg)
            
            logger.info(f"📧 告警邮件已发送:{subject}")
            
        except Exception as e:
            logger.error(f"邮件发送失败:{e}")
    
    def send_crawl_report(self, stats):
        """发送采集报告"""
        total = stats.get('total', 0)
        success = stats.get('success', 0)
        failed = stats.get('failed', 0)
        fail_rate = failed / total if total > 0 else 0
        
        # 判断是否需要告警
        threshold = config.get('alert.threshold.fail_rate', 0.3)
        
        if fail_rate > threshold:
            subject = f"⚠️ 爬虫失败率告警:{fail_rate*100:.1f}%"
            status_emoji = "🚨"
        else:
            subject = f"✅ 爬虫运行报告:成功率 {(1-fail_rate)*100:.1f}%"
            status_emoji = "✅"
        
        # 构造 HTML 内容
        content = f"""
        <html>
        <body>
            <h2>{status_emoji} 爬虫运行报告</h2>
            <table border="1" cellpadding="10">
                <tr><td>总计</td><td>{total}</td></tr>
                <tr><td>成功</td><td style="color: green">{success}</td></tr>
                <tr><td>失败</td><td style="color: red">{failed}</td></tr>
                <tr><td>失败率</td><td>{fail_rate*100:.2f}%</td></tr>
            </table>
            
            <p>时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
            
            {'<p style="color: red">⚠️ 失败率超过阈值,请检查日志!</p>' if fail_rate > threshold else ''}
        </body>
        </html>
        """
        
        self.send_alert(subject, content)

# 全局告警器
alerter = EmailAlerter()
企业微信告警(可选)
python 复制代码
import requests

class WeChatAlerter:
    """企业微信告警器"""
    
    def __init__(self, webhook_url):
        self.webhook_url = webhook_url
    
    def send_alert(self, content):
        """发送文本消息"""
        data = {
            "msgtype": "text",
            "text": {
                "content": content
            }
        }
        
        try:
            resp = requests.post(self.webhook_url, json=data, timeout=5)
            if resp.json().get('errcode') == 0:
                logger.info("📱 企业微信告警已发送")
            else:
                logger.error(f"企业微信发送失败:{resp.text}")
        except Exception as e:
            logger.error(f"企业微信发送异常:{e}")

# 使用
# wechat = WeChatAlerter('https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx')
# wechat.send_alert('⚠️ 爬虫采集失败率达到 50%,请检查!')

🛠️ 模块 5:主程序改造

python 复制代码
# main.py(增强版)
from src.crawler import AggregatorSpider
from src.config_loader import config
from src.logger import logger
from src.alerter import alerter
from datetime import datetime
import traceback

def run_spider():
    """主运行逻辑"""
    start_time = datetime.now()
    logger.info("="*60)
    logger.info(f"🚀 爬虫启动:{start_time.strftime('%Y-%m-%d %H:%M:%S')}")
    logger.info("="*60)
    
    stats = {
        'total': 0,
        'success': 0,
        'failed': 0,
        'error': None
    }
    
    try:
        # 读取配置
        source_name = config.get('crawler.source_name')
        max_pages = config.get('crawler.max_pages', 10)
        
        # 创建爬虫实例
        spider = AggregatorSpider(source_name)
        
        # 运行采集
        spider.run_full_crawl(
            list_url_template='https://news.example.com/list?page={page}',
            max_pages=max_pages
        )
        
        # 获取统计信息
        stats = spider.db.get_stats()
        stats['total'] = sum(stats.values())
        
        # 打印统计
        logger.info("\n" + "="*60)
        logger.info("📊 采集统计")
        logger.info("="*60)
        logger.info(f"总计:{stats['total']}")
        logger.info(f"成功:{stats.get('SUCCESS', 0)}")
        logger.info(f"失败:{stats.get('FAILED', 0)}")
        
    except Exception as e:
        logger.exception(f"❌ 爬虫运行异常:{e}")
        stats['error'] = traceback.format_exc()
    
    finally:
        # 计算耗时
        end_time = datetime.now()
        duration = (end_time - start_time).total_seconds()
        
        logger.info("="*60)
        logger.info(f"⏱️ 运行耗时:{duration:.2f} 秒")
        logger.info("="*60)
        
        # 发送告警(如果配置了)
        try:
            formatted_stats = {
                'total': stats['total'],
                'success': stats.get('SUCCESS', 0),
                'failed': stats.get('FAILED', 0)
            }
            alerter.send_crawl_report(formatted_stats)
        except Exception as e:
            logger.error(f"告警发送失败:{e}")

if __name__ == '__main__':
    run_spider()

📝 运维文档模板

markdown 复制代码
# 信息聚合爬虫 - 运维手册

## 1. 部署说明

### 环境要求
- Python 3.8+
- 磁盘空间 > 10GB
- 内存 > 2GB

### 安装步骤

```json
# 1. 克隆代码
git clone https://github.com/yourname/my_spider.git
cd my_spider

# 2. 创建虚拟环境
python -m venv venv
source venv/bin/activate

# 3. 安装依赖
pip install -r requirements.txt

# 4. 配置文件
cp config/config.example.yaml config/config.yaml
# 编辑 config.yaml,填写数据库路径、告警配置等

# 5. 初始化数据库
python -c "from src.db_schema import init_database; init_database()"

# 6. 测试运行
python main.py

## 2. 定时任务配置


# 编辑 crontab
crontab -e

# 添加定时任务(每天凌晨 2 点)
0 2 * * * /home/user/my_spider/run_spider.sh

## 3. 日志查看

# 查看今日日志
tail -f logs/spider_$(date +\%Y\%m\%d).log

# 查看错误日志
tail -f logs/error_$(date +\%Y\%m\%d).log

# 搜索关键词
grep "失败" logs/spider_*.log

## 4. 常见问题

### 问题 1:数据库锁定

**现象**:`database is locked`
**原因**:多个进程同时写入
**解决**:检查是否有重复的定时任务在运行

### 问题 2:告警邮件发不出去

**检查清单**:

* SMTP 服务器地址是否正确
* 端口是否开放(587/465)
* 密码是否是"授权码"(QQ 邮箱需要)
* 防火墙是否拦截

## 5. 监控指标

每日检查:

* 采集成功率 > 90%
* 日志无 ERROR 级别错误
* 数据库增长正常

📝 小结

今天我们完成了爬虫的上线部署全流程

  1. 配置管理(YAML 配置文件)
  2. 日志系统(分级记录、自动轮转)
  3. 定时任务(Crontab/APScheduler)
  4. 异常告警(邮件/企业微信)
  5. 运维文档(让别人也能维护)

至此,你的爬虫已经从"玩具"升级为"生产级系统"------能自动运行、能监控告警、能稳定服务!

🎓 专栏总结

恭喜你完成全部 28 篇的学习!我们一起走过了:

第 0-1 章 :环境搭建、网页基础
第 2-3 章 :Requests 采集、解析清洗
第 4-5 章 :数据入库、增量去重、断点续爬
第 6-7 章 :Playwright 动态页面、API 逆向
第 8 章:完整项目、上线部署

现在你已经掌握了 爬虫工程化的完整链路 ,可以独立完成从需求分析到上线运维的全流程。稳定性第一(容错、重试、去重)

合规合法 (robots.txt、频率控制)

质量优先 (数据清洗、质量检查)

可维护性(日志、文档、监控)

加油!你已经是一个合格的爬虫工程师了!往后,我就直接上硬通货了,带大家做各种爬虫项目!

🌟 文末

好啦~以上就是本期 《Python爬虫实战》的全部内容啦!如果你在实践过程中遇到任何疑问,欢迎在评论区留言交流,我看到都会尽量回复~咱们下期见!

小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦~
三连就是对我写作道路上最好的鼓励与支持! ❤️🔥

📌 专栏持续更新中|建议收藏 + 订阅

专栏 👉 《Python爬虫实战》,我会按照"入门 → 进阶 → 工程化 → 项目落地"的路线持续更新,争取让每一篇都做到:

✅ 讲得清楚(原理)|✅ 跑得起来(代码)|✅ 用得上(场景)|✅ 扛得住(工程化)

📣 想系统提升的小伙伴:强烈建议先订阅专栏,再按目录顺序学习,效率会高很多~

✅ 互动征集

想让我把【某站点/某反爬/某验证码/某分布式方案】写成专栏实战?

评论区留言告诉我你的需求,我会优先安排更新 ✅


⭐️ 若喜欢我,就请关注我叭~(更新不迷路)

⭐️ 若对你有用,就请点赞支持一下叭~(给我一点点动力)

⭐️ 若有疑问,就请评论留言告诉我叭~(我会补坑 & 更新迭代)


免责声明:本文仅用于学习与技术研究,请在合法合规、遵守站点规则与 Robots 协议的前提下使用相关技术。严禁将技术用于任何非法用途或侵害他人权益的行为。

相关推荐
weixin_660096782 小时前
flash-attention总是安装失败
python·flash-attention
yaoxin5211232 小时前
303. Java Stream API - 查找元素
java·windows·python
子午2 小时前
【2026计算机毕设】蔬菜识别系统~Python+深度学习+人工智能+算法模型+TensorFlow
人工智能·python·深度学习
kong79069282 小时前
Python 调用大模型(LLM)
人工智能·python·大模型llm
深蓝电商API2 小时前
Selenium 爬取 Canvas 渲染的数据图表
爬虫·python·selenium
Just right2 小时前
python安装包问题
开发语言·python
hhy_smile2 小时前
Function in Python
python
dxz_tust2 小时前
flow match简单直观理解
开发语言·python·深度学习·扩散模型·流匹配·flow match
写代码的【黑咖啡】2 小时前
Python 中的时间序列特征自动提取工具:tsfresh
开发语言·python