返回小岛
技能市场/策划·运营/横纵分析法

横纵分析法

精选
宇昂出品v1.0.1暂无评价25次安装

横纵分析法(Horizontal-Vertical Analysis)深度研究Skill。由数字生命卡兹克提出,融合了索绪尔的历时-共时分析、社会科学的纵向-横截面研究设计、商学院案例研究法与竞争战略分析的核心思想。

策划运营
安装前安装前
安装后安装后

横纵分析法深度研究

方法论溯源 横纵分析法由数字生命卡兹克(Khazix)提出,融合了语言学中的历时-共时分析(Saussure)、社会科学中的纵向-横截面研究设计、商学院案例研究法、以及竞争战略分析的核心思想,形成了一套适用于产品/公司/概念/人物的通用研究框架。核心原则不变:纵向追时间深度,横向追同期广度,最终交汇出判断。

你正在执行一次横纵分析法深度研究。最终产出一份排版精美的PDF研究报告

前置准备

环境准备

  1. 确认PDF转换脚本可用:本Skill自带 scripts/md_to_pdf.py(基于WeasyPrint),用于将最终Markdown报告转为排版精美的PDF。确保依赖已安装:pip install weasyprint markdown(若系统 Python 受 PEP-668 管理报 externally-managed 错误,再追加 --break-system-packages 或改用虚拟环境)。
  2. 写作风格:本Skill已内置完整的写作风格指南(见下文"写作风格"部分),无需额外加载其他skill。

明确研究对象

拿到用户输入后,确认以下信息。如果用户已经给得足够明确(比如"帮我用横纵分析法研究Hermes Agent"),不需要追问,直接开始:

  1. 研究对象:具体的产品名/公司名/概念名/人名
  2. 类型:产品、公司、概念、人物、还是其他?
  3. 研究动机(可选):为什么要研究它?最近发生了什么?
  4. 特别关注点(可选):有没有特别想深入的方向?

第一步:联网信息收集

这个方法论的质量完全取决于信息的丰富度和准确性。必须联网搜索,不能仅靠已有知识。研究报告的价值在于深度和完整度,所以信息收集阶段宁可多搜,不要因为信息不够导致后面的分析浮于表面。

并行搜索策略

把信息收集拆成三条线推进(若运行环境支持并行子任务则并行展开,不支持则按线串行收集):

  • 纵向线 — 对象自身:研究对象的起源、创始人背景、发展历程、关键事件、版本迭代、融资、战略转向、危机
  • 横向线 — 竞争格局:竞品识别、各竞品的特点和用户口碑、行业对比评测、市场份额
  • 补充线(复杂对象才需要):补充信息,如创始人深度背景、行业环境变化、用户社区讨论(GitHub issues、Reddit、Twitter/X、知乎等)

每条信息线的联网指引(若用并行子任务,把这段写进每个子任务的 prompt):

每条线的检索都应包含以下联网指引:

你需要联网获取信息,用你所在环境实际提供的联网能力即可(不限定具体工具名):

  • 联网搜索:发现信息来源,获取摘要和关键词结果
  • 定向取页:已知具体 URL 时,从页面提取正文内容
  • 如果你的环境提供更强的浏览器/抓取能力(可登录态访问、动态页面渲染、CDP 控制等),优先用它获取需要交互或登录的页面
  • 搜索策略:先用联网搜索发现信息来源和线索,找到具体 URL 后再定向取页深入提取
  • 多次搜索、多个关键词组合,不要只搜一次就放弃
  • 一手来源优于二手来源:官方博客 > 权威媒体原创报道 > 转载/聚合
  • 学术类研究对象必查arxiv:如果研究对象涉及学术概念、算法、AI模型、技术范式等,必须通过arxiv API获取相关论文。调用方式:curl -s "https://export.arxiv.org/api/query?search_query=all:关键词1+AND+all:关键词2&max_results=10",或用定向取页访问同一URL。返回XML格式,包含标题、作者、摘要、发布日期、PDF链接。可按需调整关键词组合和结果数量。找到关键论文后,用定向取页读取论文页面(https://arxiv.org/abs/论文ID)获取更多细节。

prompt要描述目标("获取""调研""了解"),不要用暗示具体手段的动词("搜索""爬取"),让子Agent自主判断最佳获取方式。

信息来源优先级

一手来源优于二手来源,多个媒体引用同一个错误会造成循环印证假象:

信息类型一手来源
产品更新/技术决策官方博客、GitHub Release Notes、创始人推文
融资/商业数据公司官方公告、SEC/工商文件
用户口碑GitHub Issues、Reddit讨论、Twitter/X、知乎帖子
行业分析权威媒体原创报道(非转载)
学术/技术原理arXiv论文(export.arxiv.org/api/query)、Google Scholar、学术会议论文集

信息充分性自检

搜索完成后检查:

  • 纵向:能讲出一个完整的故事吗?有没有明显的信息断层?
  • 横向:竞品列表完整吗?有没有遗漏主要玩家?每个竞品的信息够做对比吗?
  • 来源:关键事实有可靠来源支撑吗?有没有只靠单一来源就下判断的?

信息不够就再补搜。不要凑合。


第二步:纵向分析(Diachronic / Longitudinal)

沿时间轴,完整还原研究对象从诞生到现在的发展全貌。这是报告的主体部分,篇幅应该最重。

内容要求

起源追溯:它诞生的背景是什么?基于什么技术/理念/需求而来?创始团队或核心推动者是谁?这些人之前做过什么,为什么是他们来做这件事?当时的行业环境是什么样的?有没有某个关键事件或灵感直接促成了它的诞生?

诞生节点:明确的首次发布/成立/提出时间,最初的形态和定位,跟现在有什么不同。

演进历程:从诞生到现在,按时间顺序梳理所有关键节点。包括但不限于:重大版本更新、融资事件、团队变动、战略转型、技术架构变化、用户规模里程碑、重大合作或收购、公关危机或争议事件。

决策逻辑:在每个关键节点上,尽可能还原决策背后的原因。为什么选了A而不是B?当时面对的约束条件是什么?哪些早期决策"锁定"了后来的发展方向、难以逆转?什么机制让它越走越深(网络效应、生态绑定、技术栈选择等)?

阶段划分:把整个历程自然分为几个阶段(萌芽期、快速增长期、转型期等),每个阶段有核心特征和核心矛盾。

篇幅

6000-15000字。历史越长、节点越多的对象靠近上限,新生事物靠近下限。核心原则是把故事讲完整、讲透,每个关键节点都值得展开,不要为了压缩而跳过重要细节。宁可写长写细,也不要蜻蜓点水。


第三步:横向分析(Synchronic / Cross-sectional)

以当前时间点为切面,将研究对象与同赛道的竞品/同类进行全面对比。

首先判断竞品情况

分三种场景处理:

场景A:无直接竞品。 如果研究对象是全新品类或独占性极强的领域,跳过逐一对比,改为分析:它为什么没有竞品?是品类太新、壁垒太高、还是市场太小?未来最可能从哪个方向冒出竞争者?有没有间接替代方案或上一代解决方式可以参照?

场景B:少量竞品(1-2个)。 逐一深入对比,每个竞品展开详细分析。

场景C:竞品充分(3个及以上)。 选取最具代表性的3-5个进行对比,其余简要提及。

对比维度

根据研究对象的类型灵活调整,但至少覆盖以下方面:

核心差异对比:技术路线/核心方法论/底层逻辑、产品形态/商业模式/组织结构、目标用户/受众/适用场景、核心优势与明显短板、定价策略/资源投入/规模体量。

用户视角:每个竞品的真实用户口碑如何?社区评价、使用体验中被提及最多的优点和槽点分别是什么?用户实际的使用方式和官方定位有没有偏差?对比不要写成参数对照表的文字版,要讲清楚每个竞品「活成了什么样」,用户选它的真实理由是什么。

生态位分析:在整个赛道的版图中,研究对象占据什么位置?填补了什么空白,还是在跟谁正面竞争?当前格局是百花齐放、两强争霸、还是一家独大?

趋势判断:基于横向对比,研究对象在竞争格局中的走向是什么?机会和风险各是什么?

篇幅

3000-10000字。场景A控制在3000字左右,场景C每个主要竞品至少展开1500字以上的独立分析,不要一笔带过。


第四步:横纵交汇洞察

这是整篇报告的精华段。把纵向发展脉络和横向竞争格局结合起来,给出综合性的、新的判断。不要写成前面内容的缩写版。

需要回答的核心问题:

  1. 历史如何塑造了当下的竞争位置:纵向历程中的哪些决策和事件,决定了它今天在横向对比中的位置?
  2. 竞品的纵向对比:如果把主要竞品也放到时间线上看,它们的起源和演变路径有什么不同?这些不同如何导致了今天各自的特点?
  3. 优势的历史根源:今天的每个核心优势,能追溯到历史上的哪个节点或决策?
  4. 劣势的历史根源:今天的每个核心劣势,能追溯到哪个历史决策?当初的「好决策」有没有变成今天的包袱?
  5. 未来推演:基于纵向趋势和横向竞争格局,给出三个剧本——最可能的、最危险的、最乐观的,每个剧本要有逻辑支撑。

篇幅

1500-3000字。


写作风格

这不是一份冷冰冰的咨询报告,而是一篇让人能从头读到尾的深度研究。写作风格需要在「研究报告的严谨」和「卡兹克的可读性」之间找到平衡点。

从卡兹克文风中借鉴的核心元素

以下风格元素直接应用到报告写作中(要点已在本节列全,无需外部依赖):

节奏感:句子时长时短,段落之间跳跃自然。不要每段都一样长,一句话自成一段制造重量感的技巧可以用。好的节奏像波动,每次围绕主线偏出去一点,再用一句「扣主线句」拉回来。

叙事驱动,不是罗列驱动:纵向部分要有故事弧线,有起承转合。比如一个产品为什么在某个时间点突然爆发,背后的铺垫是什么,转折是什么。不要写成"2023年1月发布了A,2023年3月发布了B"这种流水账。

知识是「聊着聊着顺手掏出来」的:在讲述过程中自然地带出背景知识,不要「下面我来给大家科普一下」。

敢下判断:鼓励给出观点和洞察,但每个观点必须有事实支撑。先摆事实,再给判断。是推测的明确标注。表达判断时用「我觉得」「我的判断是」这种承认主观性的姿态,而不是居高临下的定论。

层层剥开的修辞:不直接讲结论,用"现象→表面解释→更深的追问→核心洞察"的方式展开。让读者参与到思考过程中。

文化升维:在交汇洞察部分,连接到更大的文化/哲学/历史参照物。不是硬凑的升华,是「聊着聊着自然想到了」的感觉。

回环呼应:开头或纵向部分埋的细节和钩子,在交汇洞察或结尾callback回来。前后因果的闭合感,是让报告从「信息流」变成「作品」的关键。

不从卡兹克文风中借鉴的元素

以下元素适合公众号文章但不适合研究报告,需要克制:

  • 过强的口语化:报告可以有聊天感,但不要满篇「这玩意」「不是哥们」「太牛逼了」。偶尔点缀可以,但密度要比公众号文章低很多。
  • 去小标题化:公众号文章追求一口气顺下来不加小标题。研究报告不一样,1-3万字的内容如果没有清晰的结构和导航,读者会迷路。报告需要清晰的章节结构。
  • 标点禁令可以放松:公众号文章禁用冒号和破折号。研究报告中可以正常使用,因为报告需要的是信息传达效率。但「」的使用习惯可以保留。
  • 固定尾部:不要加公众号的三连/星标尾部。

绝对禁区(依然适用)

以下AI味标记无论什么文体都要避免:

  • 套话:"首先...其次...最后"、"综上所述"、"值得注意的是"、"不难发现"
  • 空洞形容词:"赋能"、"抓手"、"打造闭环"
  • 教科书开头:"在当今AI快速发展的时代"、"随着技术的不断进步"
  • 高频踩雷词:"说白了"、"意味着什么?"、"这意味着"、"本质上"、"换句话说"、"不可否认"
  • 空泛工具名:不说"AI工具"、"某个模型",要说具体名字
  • 编造场景:如果某个信息搜不到,诚实标注「该信息暂缺」,绝不编造

用人话写

避免咨询公司式的套话和空洞概括。用具体的细节和例子代替概括性陈述。比如不要写「该公司在这一阶段实现了快速增长」,而要写「从2024年中期的1000万美元ARR到2025年底的10亿美元,增长曲线几乎是垂直的」。


第五步:生成PDF报告

报告写完后,使用本Skill自带的 scripts/md_to_pdf.py 脚本将Markdown转为排版精美的PDF。

转换流程

  1. 先完成Markdown稿件:将完整报告写为标准Markdown格式,保存为 [研究对象]_横纵分析报告.md
  2. 安装依赖(如未安装):pip install weasyprint markdown(PEP-668 受管环境报错再追加 --break-system-packages 或用虚拟环境)
  3. 运行转换脚本
    python [skill目录]/scripts/md_to_pdf.py input.md output.pdf --title "研究对象名称" --author "数字生命卡兹克"
    
  4. 脚本会自动生成中间HTML文件(便于调试)和最终PDF

脚本内置的排版规范

md_to_pdf.py 已内置完整的CSS排版方案,无需手动调整:

  • 页面:A4,页边距上25mm/左右20mm/下20mm
  • 封面页:自动生成,包含标题(28pt深蓝色)、副标题「横纵分析法深度研究报告」、作者信息、装饰分隔线
  • 配色:H1标题=#1a5276深蓝、H2=#1e8449绿色、H3=#2e86c1浅蓝、H4=#5b2c6f紫色,正文=#2c3e50深灰
  • 字体:CSS fallback链 "Droid Sans Fallback", Helvetica, Arial, sans-serif,自动处理中英文混排
  • 正文:10.5pt,行距1.75,两端对齐,孤行/寡行控制
  • 引用块:左侧3pt深蓝竖线 + 浅灰背景
  • 表格:全宽、深蓝表头白字、斑马纹行
  • 页眉:「报告标题 | 横纵分析法深度研究报告」(首页不显示)
  • 页脚:「第 X 页」(首页不显示)
  • Markdown的第一个H1会被自动提取为封面标题,正文中不会重复出现

Markdown写作注意事项

为了让脚本正确解析并生成最佳PDF效果:

  • 第一行用 # 标题 作为报告标题(会自动用于封面)
  • 紧接标题后可用 > 研究时间:... | 所属领域:... | 研究对象类型:... 格式写元信息行,会被提取到封面
  • ## 作为主要章节标题(纵向分析、横向分析、横纵交汇等)
  • ####### 作为子章节
  • 表格使用标准Markdown表格语法
  • 引用使用 > 语法
  • 加粗使用 **文本**

末尾内容

在Markdown稿件末尾加上:

  • 信息来源:所有引用的来源清单,标注URL和访问时间
  • 方法论说明:简要说明横纵分析法的来源(1-2句话即可)

报告结构模板

封面页

目录

一、一句话定义
[用一句话说清楚这个东西是什么]

二、纵向分析:从诞生到当下
[完整的纵向叙事,6000-15000字]

三、横向分析:竞争图谱
[横向对比分析,3000-10000字]

四、横纵交汇洞察
[交叉分析和未来推演,1500-3000字]

五、信息来源
[所有引用的来源列表]

文件命名和交付

PDF文件命名为 [研究对象名称]_横纵分析报告.pdf,保存到用户的工作目录中。


不同研究对象类型的适配

核心原则不变(纵向追时间深度,横向追同期广度),但侧重点不同:

研究产品时:纵轴重点关注版本迭代、技术路线演变、用户增长曲线、关键产品决策;横轴重点关注功能对比、性能对比、用户体验、定价。

研究公司时:纵轴重点关注创始团队、融资历程、战略转向、组织变革、关键人事变动;横轴重点关注商业模式差异、市场份额、营收对比、组织架构差异。

研究概念时(技术范式、商业模式、文化现象):纵轴重点关注概念的起源(谁提出的、基于什么理论/需求)、如何流行起来、经历了哪些争论和演变;横轴重点关注与相近概念的区别、各自适用场景、不同阵营的论证。

研究人物时:纵轴重点关注个人经历、职业轨迹、关键决策、成长曲线、公开言论变化;横轴重点关注与同领域其他人物的对比(做事方式、风格、成就、影响力、路线选择差异)。


篇幅总览

部分字数范围说明
纵向分析6,000 - 15,000字报告主体,不要蜻蜓点水
横向分析3,000 - 10,000字视竞品数量调整
横纵交汇1,500 - 3,000字精华段,给出新判断
全文总计10,000 - 30,000字不要怕长,深度和完整度是价值所在

质检清单

交付前自检:

  • 纵轴是叙事故事体?读起来有因果逻辑和时代脉络?不是年表流水账?
  • 创始人/发起者的背景和动机有足够深度?
  • 每个关键节点都展开写了,没有为了压缩而跳过重要细节?
  • 决策逻辑有还原?不只是「发生了什么」,还有「为什么这么选」?
  • 横轴的竞品场景判断正确(A/B/C)?竞品分析够深?
  • 用户口碑部分引用了真实用户的声音?不只是官方宣传?
  • 横纵交汇产出了新的判断,不是前面内容的缩写版?
  • 未来推演的三个剧本都有逻辑支撑?
  • 写作风格有节奏感、有可读性?不是冷冰冰的咨询报告?
  • 没有触犯绝对禁区里的任何一条?
  • 所有关键事实标注了信息来源?
  • 搜不到的信息诚实标注了「暂缺」,没有编造?
  • PDF排版美观、结构清晰、可读性好?
  • 总字数在 10,000-30,000 字的范围内?
<!-- @@BUNDLED_FILE: references/schema.json -->

{ "$schema": "横纵分析法 / Horizontal-Vertical Analysis Framework", "version": "1.0", "description": "一个用于系统性研究产品、公司、概念或人物的双轴分析框架。纵轴追踪完整生命历程,横轴展开当下竞争对比。",

"meta": { "研究对象": { "名称": "string — 研究对象的名称", "类型": "enum: 产品 | 公司 | 概念 | 人物 | 其他", "一句话定义": "string — 用一句话说清楚这个东西是什么", "所属领域": "string — 所在行业/学科/赛道", "研究发起日期": "date — 开始研究的时间", "研究者": "string — 谁在做这个研究", "研究动机": "string — 为什么要研究它,触发点是什么" } },

"纵轴_生命历程": { "description": "从起源到当下的完整时间线,以叙事方式呈现,越详细越多元越好",

"起源": {
  "诞生背景": "string — 它诞生时的时代背景、行业状态、技术环境",
  "诞生动机": "string — 为什么会出现?解决什么问题?填补什么空缺?",
  "创始人或发起者": {
    "是谁": "string",
    "背景经历": "string — 这些人之前做过什么,为什么是他们来做这件事",
    "初始愿景": "string — 他们最开始想做成什么样"
  },
  "诞生时间": "date",
  "诞生地点或环境": "string",
  "早期形态": "string — 最初的样子是什么,跟现在有什么不同",
  "种子事件": "string — 有没有某个关键事件或灵感直接促成了它的诞生"
},

"关键节点时间线": [
  {
    "时间": "date",
    "事件名称": "string",
    "事件描述": "string — 发生了什么",
    "为什么重要": "string — 这个事件如何改变了后续走向",
    "决策与选择": "string — 在这个节点上做了什么选择,放弃了什么",
    "外部触发因素": "string | null — 是否由外部事件(政策、竞品动作、市场变化)触发",
    "结果与影响": "string — 这个事件的直接和间接后果"
  }
],

"阶段划分": [
  {
    "阶段名称": "string — 例如:萌芽期、快速增长期、转型期、成熟期",
    "时间跨度": "string — 起止时间",
    "核心特征": "string — 这个阶段最显著的特点是什么",
    "核心矛盾": "string — 这个阶段面临的最大挑战或内在张力是什么",
    "关键人物": ["string — 在这个阶段起关键作用的人"],
    "关键决策": ["string — 这个阶段做出的重大决策"],
    "阶段成果": "string — 这个阶段结束时交出了什么答卷"
  }
],

"路径依赖分析": {
  "锁定性决策": ["string — 哪些早期决策锁定了后来的发展方向,难以逆转"],
  "错过的岔路口": ["string — 哪些关键时刻本可以走另一条路"],
  "自我强化机制": "string — 什么机制让它越走越深(网络效应、生态绑定、用户习惯等)"
},

"叙事线索": {
  "主线故事": "string — 如果要用一个故事来概括整个历程,这个故事是什么",
  "反复出现的主题": ["string — 在不同阶段反复出现的模式、矛盾或主题"],
  "转折点": ["string — 最戏剧性的转折是什么"],
  "未解之谜": ["string — 还有哪些信息缺失,需要进一步挖掘"]
}

},

"横轴_竞争对比": { "description": "在当下这个时间截面上,与竞品/同类进行系统性横向对比",

"分析时间截面": "date — 横向对比的基准时间点",

"研究对象画像": {
  "当前定位": "string — 它现在把自己定义成什么",
  "核心能力": ["string — 它最强的几个点"],
  "核心短板": ["string — 它最弱的几个点"],
  "目标用户": "string — 它在服务谁",
  "商业模式": "string — 它怎么赚钱/怎么维持运转",
  "技术路线": "string — 底层技术选择是什么",
  "用户规模与增长": "string — 当前体量和增长趋势",
  "用户口碑": {
    "正面评价": ["string — 用户最常夸的点"],
    "负面评价": ["string — 用户最常骂的点"],
    "典型使用场景": ["string — 用户主要在什么场景下用它"]
  }
},

"竞品列表": [
  {
    "竞品名称": "string",
    "竞品类型": "enum: 直接竞品 | 间接竞品 | 潜在竞品 | 替代方案",
    "一句话定义": "string",
    "当前定位": "string",
    "核心能力": ["string"],
    "核心短板": ["string"],
    "目标用户": "string",
    "商业模式": "string",
    "技术路线": "string",
    "用户规模与增长": "string",
    "用户口碑": {
      "正面评价": ["string"],
      "负面评价": ["string"],
      "典型使用场景": ["string"]
    },
    "与研究对象的关键差异": "string — 跟研究对象相比,最本质的区别是什么",
    "威胁程度": "enum: 高 | 中 | 低",
    "威胁分析": "string — 为什么构成这个程度的威胁"
  }
],

"维度对比矩阵": {
  "description": "选择几个最关键的维度,把研究对象和所有竞品拉到一起比",
  "对比维度": [
    {
      "维度名称": "string — 例如:性能、价格、易用性、生态、社区活跃度",
      "为什么选这个维度": "string — 这个维度为什么重要",
      "各方表现": {
        "研究对象": "string",
        "竞品A": "string",
        "竞品B": "string"
      }
    }
  ]
},

"竞争格局判断": {
  "当前格局": "string — 现在是百花齐放、两强争霸、一家独大还是什么",
  "格局形成原因": "string — 为什么会是这个格局",
  "格局演变趋势": "string — 接下来可能往哪个方向走",
  "研究对象的位置": "string — 它在这个格局里处于什么位置"
}

},

"横纵交叉_洞察": { "description": "纵轴和横轴的交叉分析,这是横纵分析法最有价值的部分",

"历史如何塑造了当下的竞争位置": "string — 纵向历程中的哪些决策和事件,决定了它今天在横向对比中的位置",
"竞品的纵向对比": "string — 如果把竞品也放到时间线上看,它们的起源和演变有什么不同",
"当前优势的历史根源": ["string — 它今天的每个优势,能追溯到历史上的哪个节点"],
"当前劣势的历史根源": ["string — 它今天的每个劣势,能追溯到历史上的哪个决策"],
"未来推演": {
  "基于纵向趋势的推演": "string — 如果延续当前的发展轨迹,接下来会怎样",
  "基于横向竞争的推演": "string — 竞争格局的变化会如何影响它",
  "最可能的剧本": "string",
  "最危险的剧本": "string",
  "最乐观的剧本": "string"
}

},

"输出要求": { "纵轴输出": "以叙事/故事体呈现,不是大事记列表。要有因果逻辑,有细节,有人物,让读者能感受到'为什么一步一步走到了今天'", "横轴输出": "以对比分析体呈现,既有结构化的维度对比,也有定性的判断和洞察", "交叉洞察输出": "以分析评论体呈现,把纵横两条线的信息编织在一起,产出独到的判断", "总字数建议": "纵轴 3000-8000字,横轴 2000-5000字,交叉洞察 1000-2000字", "信息来源标注": "所有关键事实需标注信息来源(链接、文档、采访等)" } }

<!-- @@END_BUNDLED_FILE --> <!-- @@BUNDLED_FILE: scripts/.gitkeep --> <!-- @@END_BUNDLED_FILE --> <!-- @@BUNDLED_FILE: scripts/md_to_pdf.py -->

#!/usr/bin/env python3 """ 横纵分析法报告 Markdown → PDF 转换脚本 (WeasyPrint版) 用法: python md_to_pdf.py input.md output.pdf [--title "报告标题"] [--author "作者"]

依赖: pip install weasyprint markdown --break-system-packages """

import sys import os import re import argparse import markdown

── CSS 样式 ──

CSS_TEMPLATE = """ @page { size: A4; margin: 25mm 20mm 20mm 20mm;

@top-center {
    content: "HEADER_TEXT";
    font-family: "Droid Sans Fallback", Helvetica, Arial, sans-serif;
    font-size: 8pt;
    color: #95a5a6;
    border-bottom: 0.5pt solid #ecf0f1;
    padding-bottom: 3mm;
}

@bottom-center {
    content: "第 " counter(page) " 页";
    font-family: "Droid Sans Fallback", Helvetica, Arial, sans-serif;
    font-size: 8pt;
    color: #95a5a6;
    border-top: 0.8pt solid #1a5276;
    padding-top: 2mm;
}

}

@page :first { @top-center { content: none; } @bottom-center { content: none; } }

body { font-family: "Droid Sans Fallback", Helvetica, Arial, sans-serif; font-size: 10.5pt; line-height: 1.75; color: #2c3e50; text-align: justify; }

/* 封面 */ .cover { page-break-after: always; text-align: center; padding-top: 45%; } .cover h1 { font-size: 28pt; color: #1a5276; margin-bottom: 8mm; font-weight: bold; letter-spacing: 2pt; } .cover .subtitle { font-size: 14pt; color: #95a5a6; margin-bottom: 6mm; } .cover .meta { font-size: 11pt; color: #95a5a6; margin-bottom: 4mm; } .cover .divider { width: 60%; margin: 8mm auto; border: none; border-top: 1.5pt solid #1a5276; }

/* 一级标题 */ h1 { font-size: 20pt; color: #1a5276; margin-top: 16mm; margin-bottom: 6mm; padding-bottom: 3mm; border-bottom: 2pt solid #1a5276; page-break-before: always; font-weight: bold; }

/* 二级标题 */ h2 { font-size: 14pt; color: #1e8449; margin-top: 10mm; margin-bottom: 5mm; font-weight: bold; }

/* 三级标题 */ h3 { font-size: 12pt; color: #2e86c1; margin-top: 6mm; margin-bottom: 3mm; font-weight: bold; }

h4 { font-size: 11pt; color: #5b2c6f; margin-top: 5mm; margin-bottom: 2mm; font-weight: bold; }

/* 段落 */ p { margin-top: 1.5mm; margin-bottom: 1.5mm; orphans: 3; widows: 3; }

/* 引用块 */ blockquote { margin: 4mm 0; padding: 4mm 4mm 4mm 10mm; background: #f8f9fa; border-left: 3pt solid #1a5276; color: #5d6d7e; font-size: 10pt; } blockquote p { margin: 1mm 0; }

/* 粗体 */ strong, b { font-weight: bold; color: #1a252f; }

/* 行内代码 */ code { font-family: "Courier New", Courier, monospace; background: #fdf2e9; color: #c0392b; padding: 0.5mm 1.5mm; border-radius: 2pt; font-size: 9.5pt; }

/* 表格 */ table { width: 100%; border-collapse: collapse; margin: 4mm 0; font-size: 9.5pt; } thead th { background: #1a5276; color: white; padding: 3mm; text-align: left; font-weight: bold; } tbody td { padding: 2.5mm 3mm; border-bottom: 0.5pt solid #bdc3c7; } tbody tr:nth-child(even) { background: #f8f9fa; }

/* 分隔线 */ hr { border: none; border-top: 0.5pt solid #bdc3c7; margin: 4mm 0; }

/* 列表 */ ul, ol { margin: 2mm 0; padding-left: 8mm; } li { margin-bottom: 1mm; }

/* 链接 */ a { color: #2e86c1; text-decoration: none; } """

def md_to_html(md_text, title="横纵分析报告", subtitle="横纵分析法深度研究报告", meta_line="", author="数字生命卡兹克"): """将 Markdown 转为带封面的 HTML"""

# 用 markdown 库转换正文
html_body = markdown.markdown(
    md_text,
    extensions=['tables', 'fenced_code', 'nl2br'],
    output_format='html5'
)

# 移除正文中的第一个 h1(会用在封面上)
first_h1_match = re.search(r'<h1>(.*?)</h1>', html_body)
if first_h1_match:
    extracted_title = first_h1_match.group(1)
    if not title or title == "横纵分析报告":
        title = extracted_title
    html_body = html_body.replace(first_h1_match.group(0), '', 1)

# 替换 CSS 中的页眉占位符
css = CSS_TEMPLATE.replace("HEADER_TEXT", f"{title}  |  横纵分析法深度研究报告")

# 构建封面
cover_html = f"""
<div class="cover">
    <h1 style="page-break-before: avoid; border: none;">{title}</h1>
    <div class="subtitle">{subtitle}</div>
    {"<div class='meta'>" + meta_line + "</div>" if meta_line else ""}
    <hr class="divider">
    <div class="meta">作者: {author}</div>
</div>
"""

full_html = f"""<!DOCTYPE html>
<html lang="zh-CN"> <head> <meta charset="UTF-8"> <style>{css}</style> </head> <body> {cover_html} {html_body} </body> </html>"""
return full_html

def main(): parser = argparse.ArgumentParser(description="横纵分析法报告 Markdown → PDF") parser.add_argument("input", help="输入的 Markdown 文件路径") parser.add_argument("output", help="输出的 PDF 文件路径") parser.add_argument("--title", default=None, help="报告标题") parser.add_argument("--author", default="数字生命卡兹克", help="作者名") args = parser.parse_args()

with open(args.input, "r", encoding="utf-8") as f:
    md_text = f.read()

# 提取元信息
meta_line = ""
for line in md_text.split("\n"):
    stripped = line.strip().lstrip(">").strip()
    if "研究时间" in stripped or "所属领域" in stripped or "研究对象类型" in stripped:
        meta_line = stripped
        break

html = md_to_html(md_text, title=args.title or "横纵分析报告", meta_line=meta_line, author=args.author)

# 保存中间 HTML(便于调试)
html_path = args.output.replace('.pdf', '.html')
with open(html_path, 'w', encoding='utf-8') as f:
    f.write(html)
print(f"[OK] HTML 已生成: {html_path}")

# 转 PDF
from weasyprint import HTML
HTML(string=html).write_pdf(args.output)
size_kb = os.path.getsize(args.output) / 1024
print(f"[OK] PDF 已生成: {args.output} ({size_kb:.1f} KB)")

if name == "main": main()

<!-- @@END_BUNDLED_FILE -->