news 2026/10/12 12:35:25

GitHub Actions爬虫定时任务实战:从配置到邮件通知

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
GitHub Actions爬虫定时任务实战:从配置到邮件通知

1. 项目思路拆解:先搞清楚这套流程要解决什么问题

很多做信息采集、行情监控、舆情追踪的朋友,其实都遇到过同一个尴尬场景:人在工位上敲了一天键盘,晚上回家或者第二天一早打开电脑,才发现该抓的数据没抓到、该看的报表没生成、该发的通知没发出去。尤其是那些需要固定频率刷新的数据源,靠手动跑脚本根本撑不住,哪怕你定了闹钟,也总有开会、通勤、临时岔开事情的时候。

这个项目的出发点很朴素:把手里的一个爬虫脚本,从“本地偶尔跑一次”变成“云端定时自动跑”,然后把结果或者异常通知通过邮件送到指定收件箱。之所以选 GitHub Actions 而不是一台长期开机的服务器,核心就三点:免费、配置即代码、上手门槛足够低。GitHub Actions 每个月的免费额度对爬虫这种轻量任务来说非常充裕,而且工作流文件写在仓库里,天然有版本管理,换了电脑、换了环境也能一键复现,不用像以前一样在服务器上装一堆依赖还要担心环境被搞坏。

适合读这篇文章的,是那些已经会写 Python 爬虫、但还不太熟悉 CI/CD 工具链的人;或者对 GitHub Actions 有点概念,但不知道任务计划、密钥管理、邮件发送这几个模块具体怎么拼起来的同学。慢一点没关系,这篇文章会把每一步为什么这么写、哪里有坑、怎么排查都讲清楚,看完你能直接套到自己的项目上去。

2. 关键技术点与方案选型逻辑

2.1 为什么选择 GitHub Actions 而不是自己的服务器

很多人的第一反应是:我有台云服务器,直接 crontab 不就行了?确实,如果你已经有一台稳定运行的服务器,cron 是最直接的方案。但问题在于:不是所有人都有云服务器,也不是每个人的服务器都愿意为了一个爬虫任务长期开机。

GitHub Actions 在这里的核心价值,是把“定时调度”和“执行环境”全部托管了。你的爬虫脚本跑在 GitHub 的虚拟机里,跑完就销毁,不需要你关心系统维护、进程守护、crond 服务是否开启。而且 GitHub Actions 的调度是基于 UTC 时间的 cron 表达式,配好之后到点自动触发,跑完能在网页上直接看日志,失败了还能收到通知,省心程度远超自己搭的定时任务。

另一个优势是隔离性。每跑一次任务,都是在一个全新的虚拟机环境里执行,依赖冲突、脏环境的问题天然不存在。你只需要在 workflow 里把 Python 版本、依赖安装命令写清楚,剩下的交给平台。对于那些对数据敏感度不高、量级也不大的爬虫任务来说,这套方案完全够用。

2.2 邮件发送的两种主流方案对比

爬虫跑完怎么把结果送出去?邮件是最通用、最不用看别人脸色的方式。在实际落地时,有两套方案可以选择,各有各的适用场景。

第一套是标准的 SMTP 方式,也是最贴近底层、兼容性最好的方式。Python 里用smtplib配合email模块,或者直接封装一个yagmail库,把邮件服务器地址、端口、账号、授权码配置好,就能把正文、附件一起发出去。这种方式的优点是只要你有一个邮箱账号(QQ邮箱、网易邮箱等)就可以用,无需额外注册任何云服务;缺点是需要在邮箱后台开启 SMTP 服务并生成专用授权码,对不熟悉邮箱设置的人来说会卡一下。

第二套是利用云服务商的邮件 API,比如 SendGrid、Mailgun 这类专门的邮件发送服务。它们的优势是送达率高、有完整的发送日志、信头规范也做得好,不容易进垃圾箱。缺点是你要去注册账号、配置域名验证、绑定发件人,对于只是“想把爬虫结果发给自己看看”的场景来说,略微重了。

我个人的建议是:如果你只是发给自己的邮箱,或者发给固定几个内部同事,直接走 SMTP 方案就够了,配置量最小,也不依赖第三方服务的免费额度。如果你需要高频发送、对送达率和专业度有要求,再考虑邮件 API 方案。后面讲实操的时候,我会以 SMTP 方案为主展开。

2.3 仓库结构与脚本组织的常规布局

很多新手在布置项目时容易乱,把所有文件堆在一起,workflow 文件乱放,Python 脚本和配置文件混在一起,结果出问题排查半天。这里给一个经过实践检验的标准布局:

project-root/ ├── .github/ │ └── workflows/ │ └── scheduler.yml # 工作流定义文件 ├── src/ │ ├── __init__.py │ ├── crawler.py # 爬虫核心逻辑 │ └── mailer.py # 邮件发送模块 ├── requirements.txt # Python 依赖 └── README.md

这样组织的几个理由:.github/workflows是 GitHub Actions 约定的目录,任何 push 到仓库的 workflow 文件会直接被 GitHub 识别并注册;src目录用来放业务代码,把爬虫和邮件逻辑拆分到两个模块,便于后续单独测试和替换;requirements.txt由 workflow 在运行时读取并安装依赖,项目与运行逻辑分离。

如果你的项目只有一个脚本,也可以简化为脚本文件放在根目录、workflow 放在.github/workflows下,但至少保持“代码和配置分离”“爬虫和邮件逻辑分文件”这两个习惯,会让后续维护舒服很多。

3. 实操全流程:从写代码到配置定时任务

3.1 爬虫脚本的核心实现与注意事项

爬虫部分,我用一个通用示例来讲。假设我们要抓取某个公开网站上的行情列表数据,解析出关键字段后保存为 JSON 文件,并在邮件中附带摘要。这里用requests抓取页面,用BeautifulSoup解析 HTML,用json存储结果。

一个简化但完整的爬虫模块crawler.py可以这样写:

import requests import json from bs4 import BeautifulSoup def fetch_and_parse(url, output_path="data.json"): headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Accept-Language": "zh-CN,zh;q=0.9", } try: resp = requests.get(url, headers=headers, timeout=15) resp.raise_for_status() except requests.RequestException as e: log_error(f"请求失败: {e}") return None soup = BeautifulSoup(resp.text, "html.parser") # 这里根据实际页面的 DOM 结构做抽取 items = [] for row in soup.select(".item-row"): title = row.select_one(".title").get_text(strip=True) price = row.select_one(".price").get_text(strip=True) items.append({"title": title, "price": price}) with open(output_path, "w", encoding="utf-8") as f: json.dump(items, f, ensure_ascii=False, indent=2) return items

这里有两个值得强调的点:

一是User-Agent不要用默认值。很多网站会对空 UA 或者 Python-requests 默认 UA 直接拒绝访问,设置一个常规浏览器的 UA 能减少很多被拦截的概率。二是一定要加超时和异常捕获。GitHub Actions 的虚拟机网络环境不算特别稳定,万一目标网站响应慢或者临时挂了,脚本要能优雅退出,而不是抛一个堆栈异常导致整个任务失败。脚本里我没写全日志函数,实际项目中建议加入 logging,方便在 workflow 日志里定位问题。

抓取频率也要控制,如果你需要每小时跑一次,目标站点是小网站,建议在脚本里做一次本地缓存比对,只有页面内容变化时才发送邮件,避免每次都是一模一样的邮件轰炸收件人。这个逻辑可以很粗暴地实现:比较本次结果和上次结果,如果无差异,直接跳过发送阶段。

3.2 邮件发送模块的编写与授权码获取

邮件模块mailer.py,我优先推荐用yagmail,因为代码量小、可读性好:

import yagmail def send_email(receiver, subject, body, attachments=None): sender = "your_account@example.com" password = "your_smtp_auth_code" yag = yagmail.SMTP(user=sender, password=password, host="smtp.example.com") yag.send( to=receiver, subject=subject, contents=body, attachments=attachments, )

但注意,这里直接把邮箱账号和授权码写死在代码里,是绝对不可取的,原因有两个:第一,仓库一旦是 public 的,密钥等于裸奔,谁都能看到利用你的邮箱发垃圾邮件;第二,就算仓库是 private 的,也存在被泄露的风险。所以正确的做法是在 GitHub 仓库设置里的 Secrets 配置项中存放这些信息,然后在 workflow 中用环境变量的方式读入。

具体操作路径是:仓库页面 -> Settings -> Secrets and variables -> Actions -> New repository secret。依次添加MAIL_ACCOUNT、MAIL_AUTH_CODE、MAIL_RECEIVER等。这样在代码中改成从环境变量读取:

import os import yagmail def send_email(receiver, subject, body): sender = os.environ["MAIL_ACCOUNT"] password = os.environ["MAIL_AUTH_CODE"] yag = yagmail.SMTP(user=sender, password=password, host="smtp.example.com") yag.send(to=receiver, subject=subject, contents=body)

关于授权码,以 QQ 邮箱为例(其他邮箱类似),流程是:进入邮箱设置 -> 账户 -> 开启 POP3/SMTP 服务,开启时系统会给你一个十六位的授权码,把那个授权码作为密码填入即可。不要用邮箱本身的登录密码,这是最常踩的坑。

主机名和端口要注意:SMTP 服务商不同,端口会不一样。QQ 邮箱是smtp.qq.com端口 465,网易是smtp.163.com端口 465 或 994。yagmail 默认会给 SSL 加密连接,所以如果连不上,先检查端口是不是 465,再确认是否需要在 host 参数中显式声明。

3.3 Workflow 文件的完整配置与定时逻辑详解

workflow 是整个项目的调度核心,也是大多数人最容易写错的地方。一个完整可用的scheduler.yml文件长这样:

name: Daily Crawler on: workflow_dispatch: schedule: - cron: "0 1 * * *" jobs: crawl-and-notify: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v5 with: python-version: "3.11" - name: Install dependencies run: | pip install --upgrade pip pip install -r requirements.txt - name: Run crawler env: MAIL_ACCOUNT: ${{ secrets.MAIL_ACCOUNT }} MAIL_AUTH_CODE: ${{ secrets.MAIL_AUTH_CODE }} MAIL_RECEIVER: ${{ secrets.MAIL_RECEIVER }} run: python src/crawler.py - name: Send notification run: python src/mailer.py

重点拆解几个部分:

on字段定义了触发方式。workflow_dispatch允许你在 GitHub 网页上手动点击运行,这个配置对调试非常重要,因为你第一次写完 workflow 后不可能干等定时触发,手动触发是最快的验证方式。schedule字段配置 cron 表达式,格式为标准的五段式:分 时 日 月 周。

jobs下面的runs-on指定运行环境。ubuntu-latest最常用,构建快、软件包全面;如果你的爬虫需要特定系统库,再考虑别的镜像。

steps中,actions/checkout@v4用于拉取仓库代码,actions/setup-python@v5用于指定 Python 版本,然后通过 pip 安装依赖。注意,actions/checkout是必须的,没有这一步,后续的install dependencies、run crawler都会因找不到代码而失败。

env部分的关键作用是把 Secrets 中的值注入到运行环境。代码中通过os.environ读取,整个流程不需要在仓库里出现任何真实密钥。

cron 表达式这块是大多数人最容易懵的地方。GitHub Actions 的调度采用 UTC 时间。也就是说,如果你期望的是每天早上 9 点执行,而你在东八区,实际对应的 UTC 时间是凌晨 1 点,表达式要写成"0 1 * * *"。换算公式很简单:期望北京时间 - 8 小时 = UTC 时间。

3.4 requirements.txt 的锁定策略

依赖锁定这块,有些细节值得单独说。直接用pip install requests beautifulsoup4 yagmail写进 requirements.txt 当然省事,但你没法保证每次运行时安装的都是同一个版本,很可能今天跑得好好的,下个月依赖库升级了一个大版本,接口变动,脚本直接崩了。

更稳的做法是把精确版本号写死,比如:

requests==2.31.0 beautifulsoup4==4.12.2 yagmail==0.15.293

版本号怎么确定?在你的本地环境跑一遍pip freeze,把与项目相关的那个几个库版本抄进去就行。锁定版本的好处是,GitHub Actions 每次执行安装的都是指定版本,线上环境和本地开发环境一致,排查问题时候不会出现“本地跑得好好的,一到线上就报错”这类经典尴尬。

如果你担心未来某个依赖有安全漏洞想升级,那就升级后在本地测试通过,再提交新的版本号,流程上完全受控。

4. 定时任务部署后的调参与经验总结

4.1 首次部署的调试顺序建议

很多人第一次配置完就在那等定时触发,结果等了两小时没跑,还以为配置错了。这里分享一个稳妥的调试顺序:先手动触发,再改定时规则。

具体做法是:推送代码后,打开仓库的 Actions 页面,你会看到刚推送的东西已经生成了一个名为Daily Crawler的工作流。右侧有一个Run workflow按钮,点击后选择分支运行。这个操作会立即激活一次任务。你可以用这个方式验证 3.3 节中的 workflow 是否完全跑通,观察每一步日志输出,确认爬虫脚本有没有出错、邮件有没有发送成功。

确认手动触发没问题之后,再检查 cron 设置。GitHub Actions 的调度有个小特点需要注意:cron 的最小单位是分钟,但实际执行可能会因为服务器负载而出现一定延迟,所以如果你的任务要求极高精度的发送时间,这套方案并不适合。常规的信息推送、每日报表,完全没问题。

第一次跑通后,哪怕工作流文件没有改动,每次在仓库里推送任意代码,都会让workflow_dispatch保持可点击状态,但不会因为普通 push 就自动触发定时任务。定时触发是独立于 push 的,只有 cron 满足时才执行。

4.2 常见报错与排查实录

下面几个问题,是我自己实操和帮人调试时遇到频率最高的,整理成表格方便查阅。

现象可能原因处理方式
定时任务从不触发cron 时区换算错误确认 UTC 时间与本地时间差,调低频率后手动触发验证逻辑
邮件发送失败,提示 535 认证错误授权码错误或者 SMTP 服务没开重新检查邮箱后台,确认使用的是授权码而非登录密码
脚本运行时报ModuleNotFoundErrorrequirements.txt 遗漏依赖,或版本拼写错误本地pip freeze核对,确认库名大小写
页面抓取不到数据,返回空列表目标网站 DOM 结构变化,或请求被反爬拦截检查 UA、cookie、页面结构选择器是否失效
工作流直接失败,日志显示No such file or directorycheckout 步骤缺失或路径错误确认 workdir 是否正确,文件是否在仓库里
任务运行时间远超预期脚本未设置最长执行时间,目标站响应慢给每个请求加 timeout,控制等待时间

有些日期的潜在问题需要单独强调。周末不少站点只维护或数据不更新,如果你的任务每天固定跑,这会导致一整天的邮件内容与前一天重复。常见的解决方式是加一个去重逻辑,或者把 cron 表达式限定在工作日,例如"0 1 * * 1-5",表示每周一到周五凌晨 1 点执行。

另外,如果 GitHub Actions 的免费额度超出限制,任务会排队不执行。个人项目的爬虫频率控制在每小时一次以内,一般不会触碰用量限制。但在高峰期,免费账户的任务可能等待更久,需要预留一点时间余量。

4.3 权限、安全与合规的补充提醒

可能有人会觉得,爬虫只是个脚本而已,怎么还要考虑安全和合规?实际上这种自动化任务,安全意识必须前置,否则不只是技术问题。

第一,脚本中不要写死任何个人信息,包括邮箱地址、收件人列表。哪怕仓库是 private,也应该通过 Secrets 管理。因为 GitHub 的 fork 机制、协作成员权限都可能导致信息暴露面扩大,密钥放代码里百害无一利。

第二,控制抓取频率。如果你盯着的目标网站访问比较频繁,请把定时任务的间隔放宽,并在脚本里加限速逻辑。这不只是对目标服务器负责,也是在保护你的任务不被封 IP。很多站点有反爬策略,频率过高会被拉黑,届时整个脚本就废了。

第三,明确数据使用边界。爬取的数据应该仅用于个人的学习研究或者内部整理,不要直接对外发布二次加工的敏感信息,这一点每个从业者心里都要有根弦。

4.4 从单任务到多任务的扩展思路

这套方案跑顺之后,你大概率会想接入更多数据源。到时候不一定要把每个爬虫都单独写一个 workflow 文件,可以复用同一条流水线,只是给每个任务传入不同的参数。

我的建议是:把爬虫脚本设计成支持从命令行接收参数,比如第一个参数是数据源名称,第二个参数是目标 URL,在 workflow 的run步骤里用${GITHUB_REF_NAME}或者环境变量区分任务。结合 GitHub Actions 的矩阵构建功能,定义多个目标 URL,让同一个 job 批量跑多组数据,整体维护成本会下降很多。

如果你有更复杂的需求,比如需要把结果存到云存储、推送到其他平台,也可以在这个 workflow 里继续加 steps,思路完全一致。GitHub Actions 的能力远不止跑爬虫和发邮件,但凡你想定时做的自动化操作,都可以用同一套模式去套。

最后再分享一个小技巧:如果你不想每次跑完都去网页上看日志,可以在 workflow 里启用失败通知,让 GitHub Actions 在任务失败时自动发邮件到仓库关联邮箱。在 workflow 文件底部加一个用于异常上报的 job 就行,配合通知类 action 在你定义的if: failure()条件下执行。这样,你的监控系统才真正闭环了,出了问题第一时间就能收到信号,不至于等用户来投诉才发现数据已经断更好几天。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/12 12:35:20

从一维到二维:用Python实现细胞自动机与生命游戏

1. 从一行规则到一片森林:细胞自动机到底在算什么第一次接触“简单细胞自动机”这个概念,很多人会以为它不过是屏幕上跑马灯一样的像素动画。但真正动手写一遍就会发现,这东西的野心远不止于此——它试图用一条极其简单的局部规则&#xff0c…

作者头像 李华
网站建设 2026/10/12 12:33:35

Python与NumPy从零实现BP神经网络手写字体识别源码详解

简介:基于Python实现BP神经网络识别手写字体的完整项目源码,源自大三期末大作业,经导师指导并获98分评审核定。资源定位于计算机相关专业的学生与实战学习者,可直接用于课程设计、期末大作业或深度学习入门练习。压缩包共10个文件…

作者头像 李华
网站建设 2026/10/12 12:33:07

风电功率超短期预测:BiTCN-BiGRU-Attention+EVO工程实践

简介:本资源是一套面向新能源电力系统研究者与高校科研人员的风电功率短期预测完整Matlab实现方案,聚焦于前沿智能优化与深度学习融合建模。方案创新性地将能量谷优化算法(EVO)与双向时间卷积网络(BiTCN)、…

作者头像 李华
网站建设 2026/10/12 12:30:57

Matlab小波变换图像处理实战:分解、去噪、压缩与边缘检测

1. 从傅里叶的局限说起:为什么图像处理需要小波变换做图像处理这行的人,迟早会撞上傅里叶变换这堵墙。傅里叶变换把图像从空间域搬到频率域,告诉你这张图里有哪些频率成分,但它有个致命短板——丢掉了时间信息,或者说在…

作者头像 李华
网站建设 2026/10/12 12:30:12

【Cursor实战】浏览器Tab自动分组插件:把Base URL改到TaoToken

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/12 12:29:32

Windows Server 2012多用户远程桌面配置:会话原理与故障排查

简介:Windows Server 2012 默认每个用户最多只允许两个远程会话,多人同时登录常需通过组策略手动调整默认限制。配置文档面向系统管理员与 IT 运维人员,专门解决多用户远程桌面接入的问题,覆盖从启用远程桌面到设置最大连接数的关…

作者头像 李华