DailyNews:AI 自动资讯摘要与微信发布工具

作者:Charlotte

自动抓取每日资讯、摘要重写、分析扩写,并生成适配公众号的 Markdown 和 HTML,支持一键上传微信草稿箱或预览。


✨ 项目特点

  • 🔍 自动抓取微信公众号、企业官网等资讯页面

  • 🧠 使用 OpenAI 模型生成摘要与扩写分析

  • 📄 自动生成 Markdown 和美化 HTML(适配公众号)

  • 🚀 一键推送至微信公众号草稿箱(支持草稿 / 预览 / 群发)

  • 🕓 支持每日定时运行(基于 schedule


📦 项目结构

.
├── main.py               # 主入口脚本,调度全流程
├── fetcher.py            # 支持微信公众号 / 通用网页的文章抓取
├── rewriter.py           # OpenAI 摘要生成与扩写
├── renderer.py           # 将文章列表渲染为 Markdown
├── md2html.py            # Markdown 转公众号样式 HTML
├── publisher.py          # 微信公众号草稿 / 群发管理
├── utils.py              # 配置读取、日志设置、已处理记录管理
├── test.py               # 获取粉丝 OpenID 工具脚本
├── config/
│   ├── urls.txt          # 手动输入模式的文章链接列表
│   └── settings.yaml     # API 密钥与发布参数配置
├── data/
│   ├── seen.json         # 已处理过的文章链接记录
│   ├── output/           # 输出 Markdown 和 HTML
│   └── logs/             # 日志记录

⚙️ 安装依赖

pip install -r requirements.txt

示例依赖包括:openai, wechatpy, loguru, requests, bs4, markdown, pyyaml


🚀 使用方法

▶ 手动模式(读取 config/urls.txt

python main.py --manual

▶ 自动模式(从网站自动抓取)

python main.py --once

▶ 每日定时调度(默认每天早上 8:00)

python main.py

🔑 配置文件说明(config/settings.yaml

openai_api_key: "你的OpenAI API Key"
wechat_app_id: "你的微信公众号 app_id"
wechat_app_secret: "你的微信公众号 app_secret"
preview_openid: "测试预览用 openid"
extra_info: "我认为该技术的关键在于..."
thumb_path: "config/thumb.jpg"  # 封面图路径

📤 发布模式支持(可选项)

  • none:不调用微信 API

  • draft_only:只创建草稿(默认)

  • preview:草稿 + 推送给指定用户预览

  • sendall:草稿 + 群发(⚠危险操作)

配置方式见 main.pypublish_mode 参数说明。


🧪 粉丝 OpenID 查询(选填)

python test.py

用于获取你的微信粉丝 OpenID,用于预览发送。


📌 示例截图

main.py

from pathlib import Path
from typing import List
import argparse
import os
import time
import schedule

from utils import load_settings, load_seen, save_seen, logger, OUTPUT_DIR, ROOT_DIR
from fetcher import get_article, fetch_latest_news
from openai import OpenAI
from rewriter import rewrite_article, expand_summary
from renderer import render_markdown
from md2html import md_to_html
from publisher import publish

CONFIG_URLS_PATH = ROOT_DIR / 'config' / 'urls.txt'

def load_urls_from_file() -> List[str]:
    if not CONFIG_URLS_PATH.exists():
        logger.error(f"未找到 URL 列表文件: {CONFIG_URLS_PATH}")
        return []
    with open(CONFIG_URLS_PATH, 'r', encoding='utf-8') as f:
        return [ln.strip() for ln in f if ln.strip() and not ln.startswith('#')]

def process_articles(urls: List[str], client: OpenAI):
    cfg = load_settings()
    seen = load_seen()
    records = []

    for url in urls:
        if url in seen:
            logger.info(f"跳过已处理 URL: {url}")
            continue

        logger.info(f"抓取文章: {url}")
        try:
            art = get_article(url)
        except Exception:
            logger.exception(f"抓取失败: {url}")
            continue

        logger.info(f"摘要文章: {url}")
        try:
            summary = rewrite_article(art, client)
        except Exception:
            logger.exception(f"摘要失败: {url}")
            continue
        art['summary'] = summary

        logger.info(f"扩写文章: {url}")
        extra_info = cfg.get('extra_info', '我认为该技术的关键在于...')
        try:
            commentary = expand_summary(summary, client, extra=extra_info)
        except Exception:
            commentary = summary
        art['commentary'] = commentary

        records.append(art)
        seen.add(url)
        save_seen(seen)

    if not records:
        logger.info('没有成功处理的文章,结束。')
        return

    md = render_markdown(records)
    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)

    out_md = OUTPUT_DIR / 'digest_latest.md'
    out_md.write_text(md, encoding='utf-8')
    logger.info(f"Markdown 已写入 {out_md}")

    html = md_to_html(md)
    out_html = OUTPUT_DIR / 'digest_latest.html'
    out_html.write_text(html, encoding='utf-8')
    logger.info(f"HTML 已写入 {out_html}")

    if cfg.get('wechat_app_id') and cfg.get('wechat_app_secret'):
        status, media_id = publish(
            markdown=md,
            html=html,
            app_id=cfg['wechat_app_id'],
            app_secret=cfg['wechat_app_secret'],
            publish_mode="draft_only",
            thumb_path=cfg.get('thumb_path', 'config/thumb.jpg'),
        )
        logger.info(f"发布状态: {status}, media_id={media_id}")
    else:
        logger.warning('未配置公众号凭证:跳过发布。')

def run(mode='auto'):
    cfg = load_settings()
    api_key = cfg.get('openai_api_key')
    if not api_key:
        raise RuntimeError('OpenAI API Key 未配置 (openai_api_key)')

    os.environ['OPENAI_API_KEY'] = api_key
    client = OpenAI()
    
    if mode == 'manual':
        urls = load_urls_from_file()
        if not urls:
            logger.warning('urls.txt 为空,退出。')
            return
    else:
        logger.info("自动模式下尝试获取新闻源链接...")
        urls = fetch_latest_news()
        if not urls:
            logger.warning("未获取到任何新闻链接,跳过处理。")
            return

    process_articles(urls, client)

def schedule_job():
    logger.info("定时任务触发中...")
    run(mode='auto')

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--force", action="store_true", help="忽略已处理记录,强制重跑所有 URL")
    parser.add_argument("--manual", action="store_true", help="使用 urls.txt 作为输入(旧逻辑)")
    parser.add_argument("--once", action="store_true", help="只执行一次,不启用定时器")
    args = parser.parse_args()

    if args.manual or args.once:
        run(mode='manual' if args.manual else 'auto')
    else:
        # 每天早上 8:00 定时执行
        schedule.every().day.at("8:00").do(schedule_job)
        logger.info("已启动定时调度,每天 8:00 自动爬取发布")
        while True:
            schedule.run_pending()
            time.sleep(60)

if __name__ == '__main__':
    main()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值