diff --git a/backend/crawler_copy/jd_pc_search/jd_competitor_report.py b/backend/crawler_copy/jd_pc_search/jd_competitor_report.py index 8ba6c9a..e68398a 100644 --- a/backend/crawler_copy/jd_pc_search/jd_competitor_report.py +++ b/backend/crawler_copy/jd_pc_search/jd_competitor_report.py @@ -3,7 +3,7 @@ 关键词 → 调用 ``jd_keyword_pipeline`` 全链路采集 → 生成 **标准化竞品分析报告**(Markdown)。 报告结构对齐常见竞品分析框架:研究范围与方法、执行摘要、**整体市场观察(列表可见度 proxy)**、 -市场与竞争结构、**按细分类目分组的竞品对比矩阵**、价格分析、产品与宣称、**按细分类目的消费者反馈与用户画像**、策略提示与附录;并明确数据边界。 +市场与竞争结构、**按细分类目分组的竞品结构(统计图 + 合并表外置)**、价格分析、产品与宣称、**按细分类目的消费者反馈与用户画像**、策略提示与附录;并明确数据边界。 若运行配置中提供了外部市场规模摘录(``EXTERNAL_MARKET_TABLE_ROWS``),则追加对应表格小节;否则不输出占位行。 依赖:全量抓取时与 ``jd_keyword_pipeline.py`` 相同(Node、h5st、Playwright、``common/jd_cookie.txt``)。 @@ -1655,7 +1655,7 @@ def _lines_4_reading_category( "", f"- 列表侧可读类目/简称共 **{len(cm_structure)}** 种取值,合计 **{total}** 行;" f"其中「{_md_cell(top_lbl, 40)}」行数最多,约占 **{100 * share:.1f}%**。", - "- 若头部类目占比极高,说明当前关键词下货架被少数品类定义;跨品类机会需结合商详矩阵(§5)再核对。", + "- 若头部类目占比极高,说明当前关键词下货架被少数品类定义;跨品类机会需结合 §5 细类结构与合并表再核对。", "", "| 类目/简称(Top 5) | 列表行数 | 占本章结构样本 |", "| --- | ---: | ---: |", @@ -1800,7 +1800,7 @@ def build_competitor_markdown( "- **用途/场景**:对每条评价独立判断是否命中预设场景词;一条可计入多个场景,统计的是「提及该场景的评价条数」而非用户数。", "- **用户画像(第八章)**:正负面粗判含**口语短语**级摘录;关注词与场景**仅按细类**以条形图展示(场景图为**占该细类有效文本比例 %**);见 §8.3~8.4。", "- **各章衔接(可选)**:若任务配置 ``llm_section_bridges``(或部署侧环境变量启用),则在「## 一」至「## 九」各章二级标题后插入大模型撰写的**衔接分析**段落,便于阅读过渡;**定量结论仍以正文表格与摘要 JSON 为准**。", - "- **细类大模型归纳(可选)**:若任务配置 ``llm_matrix_group_summaries`` / ``llm_price_group_summaries`` / ``llm_comment_group_summaries``(或对应 ``MA_ENABLE_LLM_*`` 环境变量),则在 **§5.1、§6.2、§8.6** 分别插入卖点/价盘/评论关注词的细类归纳;**仍以同章表格与 CSV 为准**。", + "- **细类大模型归纳(可选)**:若任务配置 ``llm_price_group_summaries`` / ``llm_comment_group_summaries``(或对应 ``MA_ENABLE_LLM_*`` 环境变量),则在 **§6.2、§8.6** 插入价盘/评论关注词的细类归纳;**§5 不再输出矩阵表**,卖点/配料等仍以 ``keyword_pipeline_merged.csv`` 与 JSON 为准。", "- **检索结果规模**:来自京东 PC 搜索返回的「结果条数」类指标,表示平台侧申报的匹配数量级,**不等于**动销、库存或独立 SKU 数。", "", "### 1.4 主要局限", @@ -1863,7 +1863,7 @@ def build_competitor_markdown( ) elif list_export and cr1_deep is not None and top_brand_deep and not brands_s: exec_bullets.append( - f"列表导出缺少品牌标题字段,**深入 {n_sku} SKU** 商详品牌第一大品牌份额 ≈ **{100 * cr1_deep:.1f}%**(「{top_brand_deep}」),供与 §5 矩阵对照。" + f"列表导出缺少品牌标题字段,**深入 {n_sku} SKU** 商详品牌第一大品牌份额 ≈ **{100 * cr1_deep:.1f}%**(「{top_brand_deep}」),供与 §5 细类结构对照。" ) if pst: price_src_short = ( @@ -2053,7 +2053,7 @@ def build_competitor_markdown( lines.append( f"*列表导出中店铺/品牌标题有效 **{brand_rows_n}** 条," f"低于建议阈值(≥{min_brand_rows}),品牌集中度未展开。**店铺结构见 §4.2**;" - f"商详品牌在 **§5**。*" + f"商详品牌在 **§5 细类结构**。*" ) else: lines.append("*深入子样本无可用品牌字段。*") @@ -2116,24 +2116,17 @@ def build_competitor_markdown( [ "---", "", - "## 五、竞品对比矩阵(按细分类目分组)", + "## 五、竞品结构(按细分类目分组)", "", - "优先按商详**类目路径**列分组:**三级路径**取中间一段(如 … > **饼干** > 粗粮饼干)," - "**四级及以上**取倒数第二段(如 … > **面条** > 挂面)。若该列为空,退化为搜索列表中的类目或规格属性;仍无则「未归类」。全量合并模式下另有更多商详字段可供核对。", + "SKU 依商详**类目路径**聚合为细类:**三级路径**取中间一段(如 … > **饼干** > 粗粮饼干)," + "**四级及以上**取倒数第二段(如 … > **面条** > 挂面);类目列为空时退化为列表类目或规格,仍无则「未归类」。", "", - "维度说明:**产品**(标题/规格)、**价格**(列表展示)、**渠道**(京东店铺)、**推广**(卖点/榜单文案)、" - "**类目**、**配料表**(见下)、**声量**(评价量与摘要)。", - "", - "**配料表**:优先使用配料正文列(开启配料视觉解析时为识别出的文字);" - "仅有详情长图链接时列内会提示;若商详参数含「配料/配料表:」则摘录该段。" - "均为页面信息摘录,**以包装实物与法规标签为准**。", + "**本版不再在正文打印细类矩阵表**(宽表影响阅读);各 SKU 的标题、品牌、店铺、标价/详情价、卖点、配料、评价量与摘要等," + "请直接查看本批次目录下 ``keyword_pipeline_merged.csv``,或调用 API 结构化字段 ``matrix_by_group``。", + "下列按细类给出 **SKU 款数** 及 **展示价 / 评价量** 统计图(与合并表、``matrix_by_group`` 同源)。", "", ] ) - matrix_header = [ - "| SKU | 产品(标题) | 品牌 | 标价 | 详情价 | 渠道(店铺) | 推广(卖点) | 榜单/标签 | 类目 | 配料表 | 评价量(搜索) | 消费者反馈摘要 |", - "| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |", - ] grouped_matrix = _merged_rows_grouped_for_matrix(merged_rows) if not grouped_matrix: lines.append("*无合并表 SKU。*") @@ -2141,39 +2134,20 @@ def build_competitor_markdown( for gi, (gname, grows) in enumerate(grouped_matrix): lines.append(f"### {gname}(**{len(grows)}** 款)") lines.append("") - lines.extend(matrix_header) - grows_sorted = sorted(grows, key=lambda r: _cell(r, sku_header) or "") - for row in grows_sorted: - lines.append( - _competitor_matrix_md_line( - row, sku_header=sku_header, title_h=title_h - ) - ) + lines.append( + "*SKU 级字段明细见 ``keyword_pipeline_merged.csv``(本细类 SKU 与上节分组规则一致)。*" + ) lines.append("") slug_mx = _scenario_group_asset_slug(gname, gi) lines.extend( _embed_chart( run_dir, f"chart_matrix_prices_reviews__{slug_mx}.png", - f"「{_md_cell(gname, 24)}」细类 · **展示价与评价量**(条形图;与上表同源:" + f"「{_md_cell(gname, 24)}」细类 · **展示价与评价量**(条形图;与合并表及 ``matrix_by_group`` 同源:" f"价取 detail_price_final→标价→券后 优先可解析数值;评价量为搜索侧「评价量」字段摘录)", ) ) - _mx_llm = (llm_matrix_section_md or "").strip() - if _mx_llm: - lines.extend( - [ - "", - "### 5.1 细类要点归纳(大模型)", - "", - "> **说明**:模型按 §5 同细类拆分,仅基于上表矩阵摘录(标题/卖点/配料等)与每细类 ``price_stats``(与 §6 分位数同源对象)归纳;**不含**医学或功效结论。配料与宣称以页面为准。", - "", - _mx_llm, - "", - ] - ) - ch6_price_title = ( "## 六、价格分析(PC 搜索列表全量)" if list_export and pst_list.get("n", 0) > 0 @@ -2237,7 +2211,7 @@ def build_competitor_markdown( "### 6.2 细类价盘要点归纳(大模型)", "", "> **说明**:以下为模型按 §5 同细类拆分、仅基于上文本节价统计与价字段摘录的归纳(价带与价差);" - "不含配料/宣称/场景关键词分析——见 §5「细类要点归纳(大模型)」。具体数值仍以正文表格及批次 CSV 为准。", + "不含配料/宣称/场景关键词分析。具体数值仍以 **§6** 表格及 ``keyword_pipeline_merged.csv`` 为准。", "", _pr_llm, "", @@ -2265,7 +2239,7 @@ def build_competitor_markdown( "", "### 8.1 方法", "", - "- **细类划分**:与 **§5 竞品矩阵** 相同,依据商详类目路径解析为「饼干 / 西式糕点 / …」等(规则见 §5 章首说明)。", + "- **细类划分**:与 **§5** 相同的商详类目路径规则(见 §5 章首)。", "- **归因**:每条评价按其 SKU 对应到深入样本,再映射到该 SKU 所属细类;SKU 不在合并表中的评价单独归入说明性分组。", "- **正负面粗判(§8.2)**:先以关键词规则与图表做粗分;若任务开启 **llm_comment_sentiment**,可附**大模型对抽样原文的主题归因**(尤其负向「用户在抱怨什么」),与词频条形图互补。", "- **关注词按细类(§8.3)**:对组内评价正文做子串计数并出条形图;若无逐条正文则用该细类下评价摘要列拼接兜底;与配置关注词及联想扩展同源。", diff --git a/backend/pipeline/jd_runner.py b/backend/pipeline/jd_runner.py index 0307c92..e6b264d 100644 --- a/backend/pipeline/jd_runner.py +++ b/backend/pipeline/jd_runner.py @@ -431,24 +431,8 @@ def write_competitor_analysis_for_run_dir( ) if want_mx and not skip_mx and merged_rows: - pl_mx = jcr.build_matrix_groups_llm_payload( - merged_rows, sku_header=sku_h, title_h=title_h - ) - if pl_mx: - matrix_llm_rec["attempted"] = True - try: - from .llm_generate import generate_matrix_group_summaries_llm - - llm_matrix_md = generate_matrix_group_summaries_llm( - pl_mx, keyword=kw - ) - matrix_llm_rec["ok"] = True - matrix_llm_rec["chars"] = len(llm_matrix_md) - except Exception as e: - matrix_llm_rec["ok"] = False - matrix_llm_rec["error"] = str(e) - else: - matrix_llm_rec["skipped"] = "empty_matrix_payload" + # §5 正文已取消细类矩阵表,不再向报告嵌入矩阵要点 LLM 段落。 + matrix_llm_rec["skipped"] = "matrix_table_removed_from_report" elif skip_mx: matrix_llm_rec["skipped"] = "MA_SKIP_LLM_MATRIX_GROUP_SUMMARIES" elif not want_mx: