
简介面向自媒体运营者与 Python 爬虫/自动化学习者这份资源提供了一套由 deepseek 和 Claude 3.7 辅助开发的今日头条自动发文项目代码。工具通过爬取新闻 API 与知乎热榜获取内容使用 PyQt5 构建可视化操作界面并完整覆盖爬取线程、发布线程、浏览器初始化、账号登录和文章发布等核心功能。针对自动化过程中常见的“发布按钮需要先预览”限制作者给出禁用 JavaScript 与并发处理绕过该限制的排错思路同时支持 cookie 登录和每日定时发布实际已用于自动发布“舔狗日记”等场景。压缩包共 3 个文件包含 Python 主程序、txt 依赖说明与 inscode 项目配置整体仅 8KB轻量且便于直接部署调试。已有 333 人学习下载适合想快速搭建内容自动发布流程或希望研究爬虫采集、浏览器自动化与桌面 GUI 结合实践的开发者参考。 在内容分发这件事上效率就是流量。手动搬文章、定时定点发内容看着简单一旦账号多了、内容量大纯手工操作能把人拖垮。我之前做过一个今日头条自动发文的小项目核心就是用代码对接头条官方开放接口把“写文章→传图片→点发布”这套流程自动化。今天把整个项目的设计思路、关键代码、踩过的坑都整理出来给有同样需求的朋友一个可直接参考的底子。先说明一下这个项目定位是“半自动”工具文章内容由人工准备工具负责定时发布、批量分发、状态回查。不要指望纯AI生成内容直接发头条的内容审核机制很严机器批量发的低质内容很容易被判搬运或降权。工具提升的是效率不是替代内容创作。1. 项目背景与需求拆解1.1 为什么需要一个自动发文工具做头条号运营的人应该都有这种感觉发文这件事本身不复杂但架不住它琐碎。要登录后台、新建图文、粘贴标题、上传封面、选择分类、设置摘要最后还要检查一遍有没有格式问题。一次两次没问题但如果你有多个账号或者需要每天固定时段发好几篇文章反复重复这套操作就是在白白消耗时间。更麻烦的是发布时间窗口。头条的推荐机制决定了发文时间会影响初始推荐量很多运营者会卡在早上7-9点、中午12-14点、晚上18-22点这几个高峰时段发布。但人不可能准点守在电脑前尤其是有正职工作的兼职作者。自动发文工具的核心价值就体现在这里把“内容创作”和“内容发布”彻底解耦文章提前准备好到点自动发人可以直接去做其他事。我在项目启动前梳理过需求最终锁定这几个功能点定时发布、批量上传、发布状态回查、失败重试。定时发布解决准点问题批量上传解决多文章问题状态回查解决“到底发出去没有”的问题失败重试解决网络波动和临时报错的问题。四个功能点对应四条技术路径后面逐一展开。1.2 技术路线的选择API接口优先于浏览器模拟实现自动发文市面上有两种主流方案一种是调用官方开放平台的API接口另一种是用Selenium、Playwright这类工具模拟浏览器操作自动化点击后台按钮。我直接说结论首选API方案除非API能力覆盖不了需求再考虑浏览器模拟。原因有三点。稳定性方面API接口是官方提供的能力只要参数正确、频率不超限响应非常稳定。浏览器自动化则要面对页面结构变化、弹窗拦截、登录态过期等一系列问题头条后台前端改版频率不低很可能今天跑得好好的脚本明天就因为某个按钮的class名变了而全线崩溃。效率方面API请求一个发布接口通常在几百毫秒到几秒之间完成而浏览器自动化要加载整个页面、等待DOM渲染、模拟点击事件单篇文章的发布耗时可能达到几十秒。批量发布时这个差距会被急剧放大。风险方面浏览器模拟行为在平台侧来看与手动操作差异明显如果脚本逻辑写得不够像真人容易触发风控。API接口只要使用合规走的是正规开发者通道反而更稳妥。所以我最终的方案是纯API实现Python作为主力语言。选择Python不是因为它性能好而是生态丰富requests库处理HTTP请求非常顺手APScheduler做定时任务配置简单后续如果要加数据处理、对接其他平台Python都能无缝衔接。2. 核心方案设计从鉴权到发布2.1 今日头条开放平台接入流程要调用头条的API第一步是在今日头条开放平台注册开发者账号并创建应用。这个流程不复杂但我见过很多人卡在第一步找不到入口。这里有一个关键认知——头条号的内容发布API归属在“字节跳动开放平台”open.douyin.com体系下统一管理创建应用时服务类目要选择与内容创作相关的能力审核通过后才能看到内容发布相关的API权限。创建应用后你会拿到两个核心凭证client_id应用唯一标识和client_secret应用密钥。这两个值的权限等级很高绝对不要硬编码在前端代码或公开仓库里我后面会讲正确的配置方式。应用创建完之后还需要配置授权回调地址。这个地址用于OAuth2.0授权流程中接收授权码即使是纯后端调用也必须配置一个有效的回调地址。我在开发测试时填的是http://localhost:8000/callback因为自己写了个简易的本地HTTP服务来处理回调实际生产环境中需要换成一个可访问的服务器地址。2.2 OAuth2.0授权与Token管理策略头条开放平台使用OAuth2.0协议进行授权这个机制的核心思路是用户不直接把账号密码交给第三方应用而是通过授权页确认生成一个短期有效的授权码第三方应用再用这个授权码换取访问令牌access_token。访问令牌是调用API的“通行证”每次请求都要带上。Token管理的核心难点在于它的有效期——头条的access_token不是永久有效的过期之后必须用refresh_token刷新或者让用户重新授权。我在设计项目时专门写了一个Token管理器职责有三块内存中缓存token避免每次请求都从文件或数据库读取。定时检查token的过期时间提前10分钟触发刷新逻辑。刷新失败时发送告警通知避免发布任务全部失败。Token刷新用的是开放平台提供的刷新接口传入client_id、client_secret和refresh_token返回新的access_token和新的refresh_token。这里有个容易忽略的细节刷新后返回的refresh_token也会变化必须持久化不能只拿新的access_token存起来。2.3 发文主流程设计整个发文流程我拆成了四个阶段每个阶段对应一个独立函数方便维护和复用获取access_token带上缓存判断。准备文章内容标题、正文HTML、封面图、摘要、标签。上传图片素材拿到图床的image_id这个image_id在创建文章时必须引用。调用创建文章接口提交正文数据返回article_id和发布状态。这四个阶段是串联的关系任一步失败都要有明确的收尾逻辑。例如图片上传失败时不要直接终止整个任务而是标记该文章为“草稿待处理”然后继续处理下一篇文章最后统一汇总失败原因并告警。3. 代码实现全套发文工具的核心模块3.1 配置文件管理我习惯用YAML格式做配置管理比直接在代码里写常量清晰得多。下面这个配置实例基本覆盖了项目所需的所有配置项# config.yaml app: client_id: your_client_id client_secret: your_client_secret redirect_uri: http://localhost:8000/callback account: # 发布账号绑定 open_id: your_open_id publish: default_summary: 这是我的原创文章欢迎交流讨论。 default_tags: [科技, 生活] default_category: 综合 schedule: # 每天8:30和19:30自动发布 publish_times: [08:30, 19:30] storage: # token持久化文件 token_file: token.json # 发文章件夹待发布、已发布、发布失败 content_dir: ./articlesclient_secret这种敏感信息存放在明文YAML里其实也有风险我实际部署的时候是结合环境变量做的配置文件里存的是占位符运行时从环境变量读取真实值再合入配置对象。从安全角度这个习惯值得保留。3.2 获取与刷新Token模块Token管理是整个自动发文系统的“总闸门”我把这部分封装成一个类核心逻辑如下import json import os import requests import time TOKEN_URL https://open.douyin.com/oauth/access_token/ REFRESH_URL https://open.douyin.com/oauth/refresh_token/ class TokenManager: def __init__(self, client_id, client_secret, token_file): self.client_id client_id self.client_secret client_secret self.token_file token_file self.access_token None self.refresh_token None self.expires_at 0 self._load() def _load(self): if os.path.exists(self.token_file): with open(self.token_file, r, encodingutf-8) as f: data json.load(f) self.access_token data.get(access_token) self.refresh_token data.get(refresh_token) self.expires_at data.get(expires_at, 0) def _save(self): with open(self.token_file, w, encodingutf-8) as f: json.dump({ access_token: self.access_token, refresh_token: self.refresh_token, expires_at: self.expires_at }, f, ensure_asciiFalse, indent2) def get_access_token(self): # 如果token还有效直接返回 if self.access_token and time.time() self.expires_at - 600: return self.access_token # 尝试用refresh_token刷新 if self.refresh_token: self._refresh() return self.access_token raise RuntimeError(No valid token, please re-authorize.) def _refresh(self): resp requests.post(REFRESH_URL, params{ client_id: self.client_id, client_secret: self.client_secret, grant_type: refresh_token, refresh_token: self.refresh_token }, timeout10) data resp.json() if data.get(data) and data[data].get(access_token): self.access_token data[data][access_token] self.refresh_token data[data][refresh_token] self.expires_at time.time() data[data][expires_in] self._save() else: raise RuntimeError(fToken refresh failed: {data})先解释一下为什么缓冲时间设置成600秒很多API请求链路是异步的请求发出去之后头条服务端还要做一些内部校验如果token正好卡在过期边界上刷新很容易出现一个请求成功了下一个请求却报401。提前10分钟是最保守的余量既不会频繁刷新又能保证请求有效期内token不过期。3.3 图片上传与文章发布文章的封面图和正文中的配图在头条的API体系里都要求先用素材上传接口传到平台拿到素材ID之后才能在创建文章时引用。这个设计很好理解你把图片直接传一个URL过来平台不好控制图片的访问稳定性但如果你把图片传到它们的CDN上平台就能保证图片永远可以正常加载。上传图片的代码def upload_image(access_token, image_path): upload_url https://open.douyin.com/api/douyin/v1/image/upload/ with open(image_path, rb) as f: files {image: (os.path.basename(image_path), f, image/jpeg)} params {open_id: OPEN_ID, access_token: access_token} resp requests.post(upload_url, paramsparams, filesfiles, timeout30) result resp.json() if result.get(data) and result[data].get(image_id): return result[data][image_id] else: raise RuntimeError(fImage upload failed: {result})创建文章的请求体构造要特别注意一点正文内容传的是HTML格式。这里有一个从业者才知道的细节——不要去手写繁琐的HTML标签直接用一个小函数把普通的txt文本转换成简单的HTML结构比如把\n\n替换成p.../p把\n替换成br就够用了。文章发布的核心代码def create_article(access_token, title, content_html, cover_image_id, summary, tags): create_url https://open.douyin.com/api/douyin/v1/article/create/ article_data { title: title, content: content_html, cover_image_id: cover_image_id, summary: summary, tags: tags, category: 综合 } params {open_id: OPEN_ID, access_token: access_token} resp requests.post(create_url, jsonarticle_data, paramsparams, timeout15) result resp.json() if result.get(data) and result[data].get(article_id): return result[data][article_id] else: # 记录错误详情便于排查 error_code result.get(error_code) error_msg result.get(message) raise RuntimeError(fCreate article failed: {error_code} - {error_msg})这段发布代码成功返回的是一个article_id需要把它写进发布记录表里。后续如果要查文章状态、删除文章或者更新标签都靠这个ID来定位。我自己的做法是维护一个本地SQLite数据库表结构很简单id、article_id、标题、发布时间、状态、失败原因。等到后面要统计发布成功率、追查某篇文章为什么没发出去的时候你就会发现这个库是救命的。3.4 定时调度定时任务我用的是APScheduler库的BackgroundScheduler它支持cron表达式可以非常灵活地配置发布计划。from apscheduler.schedulers.background import BackgroundScheduler from datetime import datetime import time def publish_job(): token_manager TokenManager(...) # 读取待发布队列 pending_list load_pending_articles() for article in pending_list: try: publish_single_article(article, token_manager) except Exception as e: record_failure(article, str(e)) scheduler BackgroundScheduler(timezoneAsia/Shanghai) scheduler.add_job( publish_job, triggercron, hour8,12,19, minute30, idauto_publish ) scheduler.start() try: while True: time.sleep(60) except KeyboardInterrupt: scheduler.shutdown()这里的核心逻辑是定时任务触发时不是当场去写文章而是从待发布队列里取文章出来发。这意味着你可以在任何时间把新写的文章丢进队列到了设定时间系统会自己跑。时间配置我写的是hour8,12,19配合minute30也就是每天三个时间点实际使用时你可以按自己的发布节奏调整。4. 实际部署中的问题与排查4.1 高频错误码与处理方法我在实际跑这个项目的过程中遇到过不少报错下面这几个是出现频率最高的整理成表格方便对照排查错误码含义处理方法401access_token无效或过期检查token是否被刷新refresh_token是否过期403权限不足确认应用是否已通过类目审核API权限是否已开通4003请求参数错误检查字段名是否与最新文档一致注意参数类型429请求频率超限减小并发数增加请求间隔或申请更高配额2153内容疑似重复调整标题和正文检查是否与历史内容高度重合关于429频率限制我之前踩过一个坑为了测试批量发布直接开了一个for循环连续发文章结果发到第11篇就被限流了。头条的接口对单应用的单接口QPS限制通常在10左右超出后返回429错误。后来我在发布循环里加了一个随机延时每次请求后time.sleep(random.uniform(1, 3))问题就解决了。从平台角度看这更接近人的真实操作节奏从工程角度看也给了接口足够的处理时间。4.2 一个容易被忽略的“定时任务不触发”问题APScheduler配置好之后在本地Windows上测试完全正常但部署到Linux服务器后到点却不触发。排查了很久最后发现是服务器时区问题。服务器默认是UTC时区配置的hour8实际对应的是北京时间下午4点等于任务确实执行了只是在完全意想不到的时间点跑的。解决方案是在创建scheduler时显式指定timezoneAsia/Shanghai不要依赖系统默认时区。这是非常小的一个点但如果线上任务全部跑偏时间后果就是文章全在错误时间点发出来推荐量会大打折扣。顺便说一句如果你的服务器上没有安装对应的时区数据还需要先装一下tzdata包不然直接指定Asia/Shanghai会报错。4.3 内容审核与发布失败的关系头条对图文内容会做机器审核涉及敏感词、疑似广告、低质内容时会被拦截。通过API发布的文章审核结果会体现在发布状态回查接口里状态值通常有发布成功、处理中、审核失败、发布失败。有段时间我批量发布的文章全部返回“审核失败”排查到最后发现是正文里有一批相同的推广段落被系统判定为营销内容。这里我的经验是API工具本身不会增加或降低内容的审核通过率平台看的是内容质量。所以在批量发文之前最好先做一个简单的敏感词过滤把明显有问题的词汇提前在本地过滤掉不要等提交到平台被拒绝后再来改。还有一个细节如果文章涉及医疗、金融、教育这类特殊行业发布前需要确保账号具备对应的行业资质否则大概率会被打回。这个跟工具无关纯粹是平台规则问题但自动发文时遇到这种情况错误信息往往比较模糊需要结合自己的内容领域去判断。5. 项目扩展从单账号到多账号管理跑通单账号自动发文之后我很快遇到一个新问题手上有几个不同定位的账号每个账号的token、发布计划、内容类型都不一样。如果每个账号复制一套代码维护成本成倍增加。我的解决方案是给TokenManager增加一个账号维度用账号名作为key来管理多套tokenclass MultiAccountManager: def __init__(self, accounts_config): self.accounts {} for name, cfg in accounts_config.items(): self.accounts[name] TokenManager( client_idcfg[client_id], client_secretcfg[client_secret], token_filef tokens/{name}_token.json ) # 发布计划也按账号区分 self.schedules self._load_schedules()发布队列也要同步改造文章的属主由账号名标记调度器在触发任务时按账号名过滤出对应的待发文章。这个改动不算复杂但让整个工具从“个人小脚本”变成了“团队可用的小系统”可维护性完全不一样。如果你后续还想加更多功能我建议按这个优先级来排内容源对接比如RSS订阅、公众号同步自动拉取新内容生成待发文章。数据统计模块每天自动拉取已发文章的阅读量、推荐量汇总成报表。多平台分发把头条适配好的内容一键分发到百家号、知乎等其他内容平台。我在实际使用中最大的感受是自动发文这个项目前期搭建需要花一些时间但一旦跑顺了省下来的时间和精力是实打实的。尤其是配合批量生产内容的流程一个人维护多个账号的内容发布在以前是件很折腾的事情现在只需要每天把写好的文章丢进待发布文件夹其他交给工具就行。最后提醒一句做这类自动化工具一定要保持对平台规则的敏感。API接口文档会更新发布规则会调整token过期策略也可能变化。脚本跑在线上不代表可以不再管它建议每周至少检查一次发布日志及时处理异常该换接口换接口该调参数调参数工具才会长期稳定地帮你干活。本文还有配套的精品资源点击获取