Python站点定时爬虫脚本开发与腾讯云CVM crontab定时执行完全指南

apphuang2026年07月12日 19:58:261

前言:为什么需要把爬虫搬到云上

在数据驱动的时代,网络爬虫已成为获取公开信息的重要工具。然而,在本地电脑上运行爬虫存在诸多痛点:电脑关机或待机后任务中断、本地网络IP易被限制、无法保证7×24小时持续采集、手动启动脚本效率低下。将爬虫部署到云端服务器,配合定时任务自动执行,是解决这些问题的标准方案。

腾讯云CVM(Cloud Virtual Machine)云服务器提供了稳定可靠的计算资源,结合Linux系统原生的crontab定时任务工具,可以轻松实现爬虫脚本的自动化、周期性执行。本文将从零开始,带领读者完成从爬虫脚本开发到CVM部署、crontab定时配置的完整流程,并提供一套可直接复用代码与配置模板。

需要先登录腾讯云控制台,点击:腾讯云控制台,还没有账号,点击:注册后再关联,已有账号点击:登录后再关联

第一部分:Python站点爬虫脚本开发

1.1 爬虫脚本的核心构成

一个生产级的定时爬虫脚本,至少应包含以下五个核心模块:HTTP请求模块、数据解析模块、异常处理模块、数据持久化模块、执行入口与日志模块。缺少任何一个环节,都可能导致爬虫在无人值守时崩溃或数据丢失。

1.2 依赖库选型与安装

本文选用Python生态中最成熟、最稳定的爬虫技术栈:

  • requests:最流行的HTTP客户端库,用于发送网络请求、获取网页内容
  • BeautifulSoup4:HTML/XML解析库,用于从网页中提取结构化数据
  • lxml:高性能HTML解析器,作为BeautifulSoup的解析引擎可大幅提升解析效率
  • pymysql:Python连接MySQL数据库的驱动库,用于将采集数据存入数据库
  • logging:Python标准日志库,用于记录脚本运行状态与错误信息

在本地开发环境中,通过以下命令安装所有依赖:

pip install requests beautifulsoup4 lxml pymysql

为方便后续在CVM上批量安装,建议使用pip freeze > requirements.txt命令将当前环境的全部依赖导出到requirements.txt文件中,部署时只需执行pip install -r requirements.txt即可一键完成所有依赖安装。

1.3 编写爬虫脚本:完整示例

以下是一个完整的站点定时爬虫脚本示例,模拟采集某技术社区的最新文章标题与链接,并将数据存入MySQL数据库。脚本涵盖了请求发送、异常捕获、数据解析、数据库写入和日志记录等全部核心环节。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
站点定时爬虫脚本 - 每日采集技术社区最新文章
适用于腾讯云CVM + crontab 定时执行
"""

import requests
from bs4 import BeautifulSoup
import pymysql
import logging
import time
import json
from datetime import datetime
from typing import List, Dict, Optional

# ========== 日志配置 ==========
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('/var/log/spider.log', encoding='utf-8'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

# ========== 数据库配置 ==========
DB_CONFIG = {
    'host': 'localhost',
    'user': 'spider_user',
    'password': 'your_password',
    'database': 'spider_db',
    'charset': 'utf8mb4'
}

# ========== 爬虫配置 ==========
HEADERS = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
TIMEOUT = 30
MAX_RETRIES = 3


def get_db_connection():
    """获取数据库连接"""
    try:
        conn = pymysql.connect(**DB_CONFIG)
        return conn
    except pymysql.Error as e:
        logger.error(f'数据库连接失败: {e}')
        raise


def init_database():
    """初始化数据库表结构"""
    conn = get_db_connection()
    cursor = conn.cursor()
    create_table_sql = '''
        CREATE TABLE IF NOT EXISTS articles (
            id INT AUTO_INCREMENT PRIMARY KEY,
            title VARCHAR(500) NOT NULL COMMENT '文章标题',
            url VARCHAR(1000) NOT NULL COMMENT '文章链接',
            summary TEXT COMMENT '文章摘要',
            publish_time DATETIME COMMENT '发布时间',
            crawl_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '采集时间',
            UNIQUE KEY uk_url (url(255))
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='采集文章表'
    '''
    cursor.execute(create_table_sql)
    conn.commit()
    cursor.close()
    conn.close()
    logger.info('数据库表初始化完成')


def fetch_page(url: str) -> Optional[str]:
    """
    发送HTTP请求获取网页内容,带重试机制
    """
    for attempt in range(MAX_RETRIES):
        try:
            logger.info(f'正在请求: {url} (尝试 {attempt + 1}/{MAX_RETRIES})')
            response = requests.get(
                url, 
                headers=HEADERS, 
                timeout=TIMEOUT
            )
            response.raise_for_status()  # 检查HTTP状态码
            response.encoding = response.apparent_encoding
            logger.info(f'请求成功,状态码: {response.status_code}')
            return response.text
        except requests.exceptions.Timeout:
            logger.warning(f'请求超时 (尝试 {attempt + 1}/{MAX_RETRIES})')
            time.sleep(2 ** attempt)  # 指数退避
        except requests.exceptions.ConnectionError:
            logger.warning(f'连接错误 (尝试 {attempt + 1}/{MAX_RETRIES})')
            time.sleep(2 ** attempt)
        except requests.exceptions.HTTPError as e:
            logger.error(f'HTTP错误: {e}')
            break
        except Exception as e:
            logger.error(f'未知错误: {e}')
            break
    logger.error(f'请求失败: {url}')
    return None


def parse_articles(html: str) -> List[Dict[str, str]]:
    """
    解析HTML提取文章列表
    """
    soup = BeautifulSoup(html, 'lxml')
    articles = []
    
    # 假设目标站点的文章列表结构为: .article-list > .item
    items = soup.select('.article-list .item')
    
    if not items:
        logger.warning('未找到文章列表元素,请检查选择器')
        # 可在此处添加备选选择器
        return articles
    
    for item in items:
        try:
            title_elem = item.select_one('.title a')
            if not title_elem:
                continue
            title = title_elem.get_text(strip=True)
            url = title_elem.get('href')
            if url and not url.startswith('http'):
                url = 'https://example.com' + url  # 补全完整URL
            
            # 提取摘要(如果有)
            summary_elem = item.select_one('.summary')
            summary = summary_elem.get_text(strip=True) if summary_elem else ''
            
            articles.append({
                'title': title,
                'url': url,
                'summary': summary
            })
        except Exception as e:
            logger.error(f'解析单个文章失败: {e}')
            continue
    
    logger.info(f'成功解析 {len(articles)} 篇文章')
    return articles


def save_articles(articles: List[Dict[str, str]]) -> int:
    """
    将文章数据批量存入数据库(去重)
    """
    if not articles:
        return 0
    
    conn = get_db_connection()
    cursor = conn.cursor()
    
    insert_sql = '''
        INSERT IGNORE INTO articles (title, url, summary, crawl_time)
        VALUES (%s, %s, %s, NOW())
    '''
    
    data = [(a['title'], a['url'], a['summary']) for a in articles]
    
    try:
        cursor.executemany(insert_sql, data)
        conn.commit()
        inserted = cursor.rowcount
        logger.info(f'成功插入 {inserted} 条新记录(跳过 {len(articles) - inserted} 条重复)')
        return inserted
    except pymysql.Error as e:
        logger.error(f'数据库写入失败: {e}')
        conn.rollback()
        return 0
    finally:
        cursor.close()
        conn.close()


def run_spider():
    """
    爬虫主执行函数
    """
    start_time = time.time()
    logger.info('=' * 50)
    logger.info(f'爬虫开始执行 - {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')
    
    try:
        # 1. 初始化数据库
        init_database()
        
        # 2. 抓取目标页面
        target_url = 'https://example.com/articles'  # 替换为实际目标URL
        html = fetch_page(target_url)
        
        if not html:
            logger.error('获取页面内容失败,爬虫终止')
            return
        
        # 3. 解析数据
        articles = parse_articles(html)
        
        if not articles:
            logger.warning('未解析到任何文章数据')
            return
        
        # 4. 存储数据
        saved = save_articles(articles)
        
        elapsed = time.time() - start_time
        logger.info(f'爬虫执行完成 - 耗时: {elapsed:.2f}秒, 新增: {saved}条')
        
    except Exception as e:
        logger.error(f'爬虫执行过程中发生未捕获异常: {e}', exc_info=True)
    finally:
        logger.info('=' * 50)


if __name__ == '__main__':
    run_spider()

1.4 脚本关键设计解析

异常处理机制:爬虫在无人值守的服务器上运行,任何未捕获的异常都会导致任务中断。上述脚本在fetch_page函数中对Timeout、ConnectionError、HTTPError等常见异常进行了分类捕获,并实现了最多3次重试与指数退避策略。同时在run_spider主函数最外层添加了try-except兜底,确保任何意外错误都能被记录而不会导致整个任务崩溃。

数据去重设计:在数据库建表时对url字段设置了唯一索引(UNIQUE KEY),并使用INSERT IGNORE语句写入数据。这种设计可以自动跳过已存在的记录,避免重复采集造成的数据冗余。

日志记录:脚本同时将日志输出到控制台和文件(/var/log/spider.log),既方便在crontab中重定向查看,也保留了持久化的运行记录用于事后排查。

第二部分:腾讯云CVM云服务器环境准备

2.1 CVM实例选购建议

在腾讯云控制台购买CVM实例时,需根据爬虫的实际负载做合理选型:

  • 地域选择:建议选择距离目标网站服务器较近的地域,或选择中国大陆境内的地域以保证网络稳定性。若目标网站为境外站点,可考虑选用腾讯云的海外节点。
  • 操作系统:推荐Ubuntu 22.04 LTS或CentOS 7.9。本文以Ubuntu 22.04 LTS为例进行演示。
  • 实例规格:对于日均采集量在万级以下的轻量爬虫,2核4GB内存的配置已绰绰有余。若涉及大规模并发采集或使用Selenium等重型工具,建议升级至4核8GB或更高配置。
  • 公网带宽:建议选择≥3Mbps的带宽,确保请求响应速度。若采集数据量较大,可考虑按流量计费模式。
  • 安全组配置:至少开放22端口(SSH)用于远程管理。如果爬虫需要对外提供API服务,还需开放对应端口。

2.2 连接CVM与系统初始化

购买实例后,通过SSH登录服务器:

ssh -i 你的密钥.pem ubuntu@服务器公网IP

登录后首先更新系统包并安装基础工具:

sudo apt update && sudo apt upgrade -y
sudo apt install -y python3 python3-pip python3-venv git vim

验证Python环境:

python3 --version
pip3 --version

2.3 Python虚拟环境配置

强烈建议为爬虫项目创建独立的Python虚拟环境,以隔离不同项目的依赖,避免版本冲突。操作步骤如下:

# 创建项目目录
mkdir -p ~/spider_project
cd ~/spider_project

# 创建虚拟环境
python3 -m venv venv

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

# 确认环境已激活(命令行前缀会显示(venv))
which python3

虚拟环境激活后,所有pip install安装的包都会被隔离在~/spider_project/venv目录下,不会影响系统全局的Python环境。

2.4 上传爬虫项目文件

将本地开发完成的爬虫脚本和requirements.txt文件上传到CVM。推荐使用scp命令:

# 在本地电脑执行
scp -i 你的密钥.pem spider.py requirements.txt ubuntu@服务器IP:~/spider_project/

也可以在CVM上直接使用git clone从代码仓库拉取项目:

git clone https://github.com/yourname/spider_project.git

文件上传后,在CVM上激活虚拟环境并安装依赖:

cd ~/spider_project
source venv/bin/activate
pip install -r requirements.txt

至此,爬虫项目已成功部署到CVM,接下来可以通过命令行手动测试脚本是否能正常运行:

python3 spider.py

确认脚本执行无误后,即可进入定时任务配置环节。

第三部分:crontab定时任务配置

3.1 crontab基础概念

crontab是Unix/Linux系统中用于设置周期性执行任务的工具。每个用户都有自己的crontab配置文件,通过crontab -e命令可以编辑当前用户的定时任务列表。cron守护进程会在后台持续运行,按照配置的时间表达式自动执行指定的命令或脚本。

腾讯云CVM基于标准的Linux内核,完全支持crontab的所有功能。此外,腾讯云还提供了云监控(Cloud Monitor)服务,可以监控CVM上定时任务的执行情况并提供告警功能。

3.2 crontab时间表达式详解

crontab时间表达式由5个字段组成,分别表示分钟、小时、日、月、星期:

┌─────────── 分钟 (0 - 59)
│ ┌───────── 小时 (0 - 23)
│ │ ┌─────── 日 (1 - 31)
│ │ │ ┌───── 月 (1 - 12)
│ │ │ │ ┌─── 星期 (0 - 7, 0和7都表示周日)
│ │ │ │ │
* * * * * 要执行的命令

常用的时间表达式示例:

  • 0 1 * * * — 每天凌晨1点执行
  • */30 * * * * — 每30分钟执行一次
  • 0 8,20 * * * — 每天上午8点和晚上8点各执行一次
  • 0 9 * * 1 — 每周一上午9点执行
  • 0 0 1 * * — 每月1日凌晨0点执行

特别提醒:“每5分钟执行一次”的正确写法是*/5 * * * *,但要注意如果脚本执行时间超过5分钟,会出现任务重叠、并发冲突的问题。建议将执行间隔设置为脚本实际运行时间的2倍以上。

3.3 配置crontab定时任务

执行以下命令编辑当前用户的crontab文件:

crontab -e

如果是第一次执行,系统会提示选择编辑器,推荐选择nanovim。在文件末尾添加以下内容:

# 每天凌晨2点执行爬虫脚本
0 2 * * * cd /home/ubuntu/spider_project && /home/ubuntu/spider_project/venv/bin/python3 spider.py >> /var/log/spider_cron.log 2>&1

# 每6小时执行一次(可选)
0 */6 * * * cd /home/ubuntu/spider_project && /home/ubuntu/spider_project/venv/bin/python3 spider.py >> /var/log/spider_cron.log 2>&1

配置中的关键点解析:

  • 使用绝对路径:cron执行时的环境变量与终端不同,必须使用命令和文件的绝对路径。例如/home/ubuntu/spider_project/venv/bin/python3而非python3
  • 切换工作目录:使用cd命令切换到项目目录,确保脚本中的相对路径(如./config.json)能正确解析。
  • 日志重定向:>> /var/log/spider_cron.log 2>&1将标准输出和标准错误都追加写入日志文件。

保存并退出编辑器后,cron任务会自动生效。可以通过以下命令查看当前用户的所有定时任务:

crontab -l

3.4 使用shell脚本封装(推荐做法)

当爬虫项目较为复杂(涉及多个Python文件、配置文件、数据目录等)时,建议编写一个shell脚本来封装启动逻辑,然后在crontab中直接调用该shell脚本。这种方式更易于维护和调试。

创建启动脚本~/spider_project/run_spider.sh

#!/bin/bash
# 爬虫启动脚本

# 设置工作目录
cd /home/ubuntu/spider_project || exit 1

# 激活虚拟环境并执行爬虫
source /home/ubuntu/spider_project/venv/bin/activate
python3 spider.py

# 记录执行状态
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 爬虫执行完成,退出码: $?" >> /var/log/spider_cron.log

给脚本添加可执行权限:

chmod +x ~/spider_project/run_spider.sh

然后在crontab中配置:

0 2 * * * /home/ubuntu/spider_project/run_spider.sh

3.5 crontab日志管理与排障

定时任务配置完成后,需要持续监控其运行状态。以下是常用的排查手段:

查看cron系统日志:

sudo grep CRON /var/log/syslog | tail -20

查看爬虫自定义日志:

tail -f /var/log/spider.log
 tail -f /var/log/spider_cron.log

常见问题与解决方案:

  • 脚本未执行:检查cron服务是否运行 sudo service cron status,确认时间表达式语法正确。
  • 脚本执行但无输出:cron环境与终端环境不同,环境变量(如PATH)可能不完整。解决方法是在crontab中显式设置PATH,或在shell脚本中source ~/.bashrc加载环境变量。
  • 权限被拒绝:确保脚本和日志文件有正确的读写权限,chmod +x赋予执行权限。
  • 任务重叠执行:如果脚本执行时间过长,下一次任务启动时上一次还未结束,会导致资源竞争。可使用flock命令实现任务互斥:0 2 * * * flock -n /tmp/spider.lock -c /path/to/run_spider.sh

第四部分:进阶优化与运维建议

4.1 爬虫合规性提醒

网络爬虫的合法合规使用是每个开发者必须重视的问题。在部署定时爬虫前,请务必注意以下几点:

  • 仅采集公开可访问的数据,不爬取需要登录或涉及用户隐私的内容
  • 遵守目标网站的robots.txt协议
  • 控制合理的请求频率,避免对目标服务器造成过大压力
  • 不得将采集数据用于商业牟利或侵犯他人权益的用途

4.2 应对反爬策略

实际生产环境中,很多站点会部署反爬机制。以下是一些常见的应对策略:

  • 更换User-Agent:定期轮换User-Agent,模拟不同浏览器访问
  • 使用代理IP:通过代理池轮换IP地址,避免单一IP高频请求被封禁
  • 设置随机延迟:在请求之间加入随机间隔(如1-5秒),模拟人类浏览行为
  • 处理验证码:对于简单的验证码,可使用OCR技术识别;复杂的验证码建议人工介入或更换采集策略

4.3 监控与告警配置

生产环境的定时爬虫必须配备监控告警机制,确保在任务失败时能及时感知。腾讯云CVM可结合以下方式实现监控:

  • 云监控告警:在腾讯云控制台配置CPU、内存、磁盘等资源指标的告警策略
  • 日志关键字监控:使用grep配合cron定时检查日志中是否包含"ERROR"或"失败"等关键词,发现问题后通过邮件或短信通知
  • 数据量监控:每日采集完成后,检查数据库新增记录数是否在合理范围内,异常偏低可能意味着采集失败

4.4 使用systemd替代crontab(可选)

对于需要更精细控制(如依赖关系、重启策略、开机自启)的定时任务,可以考虑使用systemd timer替代crontab。systemd timer提供了更丰富的功能,如任务依赖、错过执行后的补偿执行等。但crontab凭借其简洁性和普及度,仍是大多数爬虫定时任务的首选方案。

总结

本文完整地介绍了从Python爬虫脚本开发到腾讯云CVM部署、crontab定时执行的整套流程。核心要点可概括为:

  • 爬虫脚本需包含完善的异常处理、日志记录和数据去重机制,确保无人值守时的稳定性
  • CVM选购时根据爬虫负载合理配置规格,使用虚拟环境隔离项目依赖
  • crontab配置时必须使用绝对路径、切换工作目录、重定向日志,这三项是避免"cron不执行"的黄金法则
  • 生产环境需配合日志监控和告警机制,确保任务异常时能快速发现和恢复

通过以上方法,读者可以快速搭建一套稳定、可靠的云上定时爬虫系统,让数据采集真正实现7×24小时自动化运行。

常见问题问答

问1:crontab配置后脚本没有执行,如何排查?
答:首先检查cron服务是否运行(sudo service cron status);其次查看cron系统日志(sudo grep CRON /var/log/syslog)确认任务是否被调度;最后检查脚本中的路径是否全部使用绝对路径,以及脚本是否有可执行权限。

问2:爬虫脚本在终端手动运行正常,但crontab执行时失败,为什么?
答:这是crontab最常见的坑。cron执行时的环境变量(如PATH、PYTHONPATH)与终端不同。解决方法是在crontab中显式设置PATH,或在shell脚本中source ~/.bashrc加载环境变量,同时所有路径必须使用绝对路径。

问3:如何让crontab任务的输出写入日志文件?
答:在crontab命令末尾添加重定向:>> /var/log/spider.log 2>&1,其中>>表示追加写入,2>&1表示将标准错误也重定向到标准输出。建议同时在Python脚本内部使用logging模块写入独立的日志文件,实现双重保障。

问4:爬虫被目标网站封IP了怎么办?
答:可采取以下措施:降低请求频率、轮换User-Agent、使用代理IP池、设置随机请求间隔。如果封禁严重,可考虑使用腾讯云提供的弹性IP或NAT网关更换出口IP。

问5:定时爬虫的数据如何备份?
答:数据备份可分为数据库备份和文件备份两个层面。数据库可使用mysqldump配合crontab定时导出;文件数据可通过rsync同步到腾讯云COS对象存储,或使用CVM的自动快照功能进行磁盘级备份。

问6:爬虫执行时间过长,与下一次cron任务重叠了怎么办?
答:使用flock命令实现任务互斥锁,确保同一时间只有一个脚本实例在运行。示例:0 2 * * * flock -n /tmp/spider.lock -c /path/to/run_spider.sh,其中-n参数表示如果锁已被占用则直接退出而不等待。

相关文章

腾讯云服务器购买优惠!3 个省钱攻略 + 1 个安全真相,新手必看!

腾讯云服务器购买优惠!3 个省钱攻略 + 1 个安全真相,新手必看!

最近后台总收到小伙伴私信:“腾讯云服务器看着挺好,但价格有点顶,学生党 / 小团队实在买不起咋办?” 别急!今天就来手把手教你 “花小钱办大事”,不光有省钱攻略,还会扒一扒大家最关心的安全问题,看完这…

After 10 Years as a Tencent Cloud Agent, Let Me Talk About Rebates

After 10 Years as a Tencent Cloud Agent, Let Me Talk About Rebates

Lately, I’ve been getting a lot of questions from friends: “Does Tencent offer rebates? Can you…

2026腾讯云代理商返利政策深度解析:头部代理合作指南与成本优化策略

2026腾讯云代理商返利政策深度解析:头部代理合作指南与成本优化策略

一、腾讯云代理商返利机制核心逻辑1. 行业背景与代理模式腾讯云作为国内公有云市场的第二大领导者(据IDC 2025年数据,占据国内27.6%的市场份额),采用渠道商代理模式拓展市场。代理商负…

2026腾讯云代理商返利政策深度解析:头部代理合作指南与成本优化策略

2026腾讯云代理商返利政策深度解析:头部代理合作指南与成本优化策略

一、腾讯云代理商返利机制核心逻辑1. 行业背景与代理模式腾讯云作为国内公有云市场的第二大领导者(据IDC 2025年数据,占据国内27.6%的市场份额),采用渠道商代理模式拓展市场。代理商负…

2026年腾讯云代理深度解析:从折扣体系到最优合作策略

2026年腾讯云代理深度解析:从折扣体系到最优合作策略

上海汪远信息科技有限公司作为腾讯云全国级殿堂级代理,凭借13年云服务经验与深厚的官方合作关系,为企业提供全方位的上云支持,可百度:上海汪远信息科技有限公司,微信:791201210一、腾讯云代理体系全…

上海汪远信息:全国Top5腾讯云代理商,10年深耕为企业上云保驾护航

上海汪远信息:全国Top5腾讯云代理商,10年深耕为企业上云保驾护航

核心摘要本文深度解析腾讯云代理商行业现状,揭示小代理商生存困境的核心原因(低业绩导致提成少、厂商压款、市场淘汰),重点推荐上海汪远信息科技有限公司——一家拥有10年腾讯云代理经验、年销量超2亿的全国T…