Initial commit
This commit is contained in:
@@ -0,0 +1,71 @@
|
|||||||
|
# ============================
|
||||||
|
# A 股 Deep Research 平台 .env 示例
|
||||||
|
# 复制为 .env 后填写真实值
|
||||||
|
# 注意: 值后面不能跟 # 注释, python-dotenv 会当成值的一部分
|
||||||
|
# ============================
|
||||||
|
|
||||||
|
# ---- LLM Provider ----
|
||||||
|
LLM_PROVIDER=deepseek
|
||||||
|
|
||||||
|
# ---- DeepSeek ----
|
||||||
|
DEEPSEEK_API_KEY=
|
||||||
|
DEEPSEEK_BASE_URL=https://api.deepseek.com
|
||||||
|
DEEPSEEK_MODEL=deepseek-chat
|
||||||
|
|
||||||
|
# ---- Qwen / 百炼 ----
|
||||||
|
# QWEN_API_KEY 未设时用 DASHSCOPE_API_KEY
|
||||||
|
QWEN_API_KEY=
|
||||||
|
QWEN_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||||
|
QWEN_MODEL=qwen-plus
|
||||||
|
|
||||||
|
# ---- 百炼通用 API Key (LLM Qwen + Embedding DashScope 兜底) ----
|
||||||
|
DASHSCOPE_API_KEY=
|
||||||
|
|
||||||
|
# ---- LLM 通用参数 ----
|
||||||
|
LLM_MODEL= # 兜底模型名 (未设 DEEPSEEK_MODEL/QWEN_MODEL 时使用)
|
||||||
|
LLM_TEMPERATURE=0.1
|
||||||
|
LLM_TIMEOUT_SEC=60
|
||||||
|
|
||||||
|
# ---- Embedding ----
|
||||||
|
# provider: local (本地 BGE-M3) | dashscope (远程百炼)
|
||||||
|
EMBEDDING_PROVIDER=dashscope
|
||||||
|
|
||||||
|
# 远程嵌入 (EMBEDDING_PROVIDER=dashscope 时生效)
|
||||||
|
# DASHSCOPE_EMBEDDING_API_KEY 未设时用 DASHSCOPE_API_KEY
|
||||||
|
DASHSCOPE_EMBEDDING_API_KEY=
|
||||||
|
DASHSCOPE_EMBEDDING_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||||
|
DASHSCOPE_EMBEDDING_MODEL=text-embedding-v3
|
||||||
|
|
||||||
|
# 本地嵌入 (EMBEDDING_PROVIDER=local 时生效, 需 uv sync --extra local-embedding)
|
||||||
|
# LOCAL_EMBEDDING_MODEL=BAAI/bge-m3
|
||||||
|
|
||||||
|
# ---- Qdrant ----
|
||||||
|
QDRANT_HOST=localhost
|
||||||
|
QDRANT_PORT=6333
|
||||||
|
QDRANT_API_KEY=
|
||||||
|
QDRANT_COLLECTION=a_share_news
|
||||||
|
|
||||||
|
# ---- 调度 ----
|
||||||
|
SCHEDULE_TIMES=07:00,12:00,18:00,22:00
|
||||||
|
CNINFO_SCHEDULE_TIME=06:30
|
||||||
|
STOCK_REPORT_TIME=07:30
|
||||||
|
STOCK_REPORT_DAYS=15
|
||||||
|
|
||||||
|
# ---- Pipeline 步骤超时 ----
|
||||||
|
# 优先级: TIMEOUT_{NAME} > PIPELINE_STEP_TIMEOUT > 代码硬编码默认值
|
||||||
|
# PIPELINE_STEP_TIMEOUT 为全局兜底, 对所有未单独配置的步骤生效
|
||||||
|
PIPELINE_STEP_TIMEOUT=1800
|
||||||
|
# 以下为各步骤独立超时(秒), 值不设时取上方的全局值
|
||||||
|
# TIMEOUT_CRAWLER=900
|
||||||
|
# TIMEOUT_XWLB=60
|
||||||
|
# TIMEOUT_EXTRACTOR=300
|
||||||
|
# TIMEOUT_DEDUP=300
|
||||||
|
# TIMEOUT_LLM=900
|
||||||
|
# TIMEOUT_EMBEDDING=300
|
||||||
|
# TIMEOUT_QDRANT=600
|
||||||
|
# TIMEOUT_CNINFO_CRAWL=900
|
||||||
|
# TIMEOUT_CNINFO_EXTRACT=300
|
||||||
|
# TIMEOUT_CNINFO_PDF=300
|
||||||
|
|
||||||
|
# ---- cninfo 公告抓取 ----
|
||||||
|
CNINFO_PDF_BASE=http://static.cninfo.com.cn
|
||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
*.egg-info/
|
||||||
|
*.egg
|
||||||
|
.eggs/
|
||||||
|
|
||||||
|
# uv
|
||||||
|
# uv.lock 应保留以锁定生产环境依赖版本(本项目为应用而非库)
|
||||||
|
|
||||||
|
# 测试 / 覆盖率
|
||||||
|
.pytest_cache/
|
||||||
|
.coverage
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.mypy_cache/
|
||||||
|
.ruff_cache/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# 项目敏感配置
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
*.key
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# 运行产物
|
||||||
|
logs/*.log
|
||||||
|
logs/*.log.*
|
||||||
|
data/raw/
|
||||||
|
data/processed/
|
||||||
|
data/cache/
|
||||||
|
*.sqlite
|
||||||
|
*.sqlite3
|
||||||
|
*.db
|
||||||
|
|
||||||
|
# Docker 卷
|
||||||
|
qdrant_storage/
|
||||||
|
|
||||||
|
# 调试输出
|
||||||
|
debug/
|
||||||
|
tmp/
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"serena-djapi": {
|
||||||
|
"command": "uv",
|
||||||
|
"args": [
|
||||||
|
"run",
|
||||||
|
"--directory",
|
||||||
|
"/Users/summer/Downloads/cc-cursor/mcp-servers/serena",
|
||||||
|
"serena",
|
||||||
|
"start-mcp-server",
|
||||||
|
"--project",
|
||||||
|
"/Users/summer/Downloads/cc-projects/news"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
/cache
|
||||||
|
/project.local.yml
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
# the name by which the project can be referenced within Serena
|
||||||
|
project_name: "news"
|
||||||
|
|
||||||
|
|
||||||
|
# list of languages for which language servers are started; choose from:
|
||||||
|
# al ansible bash clojure cpp
|
||||||
|
# cpp_ccls crystal csharp csharp_omnisharp dart
|
||||||
|
# elixir elm erlang fortran fsharp
|
||||||
|
# go groovy haskell haxe hlsl
|
||||||
|
# java json julia kotlin lean4
|
||||||
|
# lua luau markdown matlab msl
|
||||||
|
# nix ocaml pascal perl php
|
||||||
|
# php_phpactor powershell python python_jedi python_ty
|
||||||
|
# r rego ruby ruby_solargraph rust
|
||||||
|
# scala solidity swift systemverilog terraform
|
||||||
|
# toml typescript typescript_vts vue yaml
|
||||||
|
# zig
|
||||||
|
# (This list may be outdated. For the current list, see values of Language enum here:
|
||||||
|
# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py
|
||||||
|
# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
|
||||||
|
# Note:
|
||||||
|
# - For C, use cpp
|
||||||
|
# - For JavaScript, use typescript
|
||||||
|
# - For Free Pascal/Lazarus, use pascal
|
||||||
|
# Special requirements:
|
||||||
|
# Some languages require additional setup/installations.
|
||||||
|
# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers
|
||||||
|
# When using multiple languages, the first language server that supports a given file will be used for that file.
|
||||||
|
# The first language is the default language and the respective language server will be used as a fallback.
|
||||||
|
# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
|
||||||
|
languages: []
|
||||||
|
|
||||||
|
# the encoding used by text files in the project
|
||||||
|
# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
|
||||||
|
encoding: "utf-8"
|
||||||
|
|
||||||
|
# line ending convention to use when writing source files.
|
||||||
|
# Possible values: unset (use global setting), "lf", "crlf", or "native" (platform default)
|
||||||
|
# This does not affect Serena's own files (e.g. memories and configuration files), which always use native line endings.
|
||||||
|
line_ending:
|
||||||
|
|
||||||
|
# The language backend to use for this project.
|
||||||
|
# If not set, the global setting from serena_config.yml is used.
|
||||||
|
# Valid values: LSP, JetBrains
|
||||||
|
# Note: the backend is fixed at startup. If a project with a different backend
|
||||||
|
# is activated post-init, an error will be returned.
|
||||||
|
language_backend:
|
||||||
|
|
||||||
|
# whether to use project's .gitignore files to ignore files
|
||||||
|
ignore_all_files_in_gitignore: true
|
||||||
|
|
||||||
|
# advanced configuration option allowing to configure language server-specific options.
|
||||||
|
# Maps the language key to the options.
|
||||||
|
# Have a look at the docstring of the constructors of the LS implementations within solidlsp (e.g., for C# or PHP) to see which options are available.
|
||||||
|
# No documentation on options means no options are available.
|
||||||
|
ls_specific_settings: {}
|
||||||
|
|
||||||
|
# list of additional paths to ignore in this project.
|
||||||
|
# Same syntax as gitignore, so you can use * and **.
|
||||||
|
# Note: global ignored_paths from serena_config.yml are also applied additively.
|
||||||
|
ignored_paths: []
|
||||||
|
|
||||||
|
# whether the project is in read-only mode
|
||||||
|
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
|
||||||
|
# Added on 2025-04-18
|
||||||
|
read_only: false
|
||||||
|
|
||||||
|
# list of tool names to exclude.
|
||||||
|
# This extends the existing exclusions (e.g. from the global configuration)
|
||||||
|
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
|
||||||
|
excluded_tools: []
|
||||||
|
|
||||||
|
# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default).
|
||||||
|
# This extends the existing inclusions (e.g. from the global configuration).
|
||||||
|
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
|
||||||
|
included_optional_tools: []
|
||||||
|
|
||||||
|
# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
|
||||||
|
# This cannot be combined with non-empty excluded_tools or included_optional_tools.
|
||||||
|
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
|
||||||
|
fixed_tools: []
|
||||||
|
|
||||||
|
# list of mode names that are to be activated by default, overriding the setting in the global configuration.
|
||||||
|
# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes.
|
||||||
|
# If the setting is undefined/empty, the default_modes from the global configuration (serena_config.yml) apply.
|
||||||
|
# Otherwise, this overrides the setting from the global configuration (serena_config.yml).
|
||||||
|
# Therefore, you can set this to [] if you do not want the default modes defined in the global config to apply
|
||||||
|
# for this project.
|
||||||
|
# This setting can, in turn, be overridden by CLI parameters (--mode).
|
||||||
|
# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes
|
||||||
|
default_modes:
|
||||||
|
|
||||||
|
# list of mode names to be activated additionally for this project, e.g. ["query-projects"]
|
||||||
|
# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes.
|
||||||
|
# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes
|
||||||
|
added_modes:
|
||||||
|
|
||||||
|
# initial prompt for the project. It will always be given to the LLM upon activating the project
|
||||||
|
# (contrary to the memories, which are loaded on demand).
|
||||||
|
initial_prompt: ""
|
||||||
|
|
||||||
|
# time budget (seconds) per tool call for the retrieval of additional symbol information
|
||||||
|
# such as docstrings or parameter information.
|
||||||
|
# This overrides the corresponding setting in the global configuration; see the documentation there.
|
||||||
|
# If null or missing, use the setting from the global configuration.
|
||||||
|
symbol_info_budget:
|
||||||
|
|
||||||
|
# list of regex patterns which, when matched, mark a memory entry as read‑only.
|
||||||
|
# Extends the list from the global configuration, merging the two lists.
|
||||||
|
read_only_memory_patterns: []
|
||||||
|
|
||||||
|
# list of regex patterns for memories to completely ignore.
|
||||||
|
# Matching memories will not appear in list_memories or activate_project output
|
||||||
|
# and cannot be accessed via read_memory or write_memory.
|
||||||
|
# To access ignored memory files, use the read_file tool on the raw file path.
|
||||||
|
# Extends the list from the global configuration, merging the two lists.
|
||||||
|
# Example: ["_archive/.*", "_episodes/.*"]
|
||||||
|
ignored_memory_patterns: []
|
||||||
@@ -0,0 +1,627 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
# Claude Code 开发约束(A股 Deep Research 项目)
|
||||||
|
|
||||||
|
版本:v1.0
|
||||||
|
|
||||||
|
最后更新:2026-06-16
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 一、项目定位
|
||||||
|
|
||||||
|
本项目目标:
|
||||||
|
|
||||||
|
构建一个面向 A 股投资研究的私有化 Deep Research 平台。
|
||||||
|
|
||||||
|
核心能力包括:
|
||||||
|
|
||||||
|
* 财经新闻抓取;
|
||||||
|
* 中文新闻提取;
|
||||||
|
* 投资事件抽取;
|
||||||
|
* 向量知识库;
|
||||||
|
* MCP 服务;
|
||||||
|
* Cherry Studio Agent 深度研究。
|
||||||
|
|
||||||
|
本项目不是:
|
||||||
|
|
||||||
|
* 自动交易系统;
|
||||||
|
* 股票预测系统;
|
||||||
|
* 投资顾问系统。
|
||||||
|
|
||||||
|
Claude Code 必须始终围绕“研究辅助平台”进行设计。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 二、Claude Code 总体行为准则
|
||||||
|
|
||||||
|
Claude Code 必须遵守以下原则:
|
||||||
|
|
||||||
|
## 2.1 分阶段开发
|
||||||
|
|
||||||
|
禁止一次性完成整个项目。
|
||||||
|
|
||||||
|
必须:
|
||||||
|
|
||||||
|
* 每次只完成一个 Milestone;
|
||||||
|
* 等待人工验收;
|
||||||
|
* 验收通过后再进入下一阶段。
|
||||||
|
|
||||||
|
禁止:
|
||||||
|
|
||||||
|
* 擅自推进后续阶段;
|
||||||
|
* 推翻已完成模块。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2.2 最小改动原则
|
||||||
|
|
||||||
|
修改代码时:
|
||||||
|
|
||||||
|
优先局部修改。
|
||||||
|
|
||||||
|
禁止:
|
||||||
|
|
||||||
|
为了优化而重写整个模块。
|
||||||
|
|
||||||
|
除非明确要求:
|
||||||
|
|
||||||
|
“允许重构”。
|
||||||
|
|
||||||
|
否则:
|
||||||
|
|
||||||
|
保持向后兼容。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2.3 先理解,再编码
|
||||||
|
|
||||||
|
开始编码前必须:
|
||||||
|
|
||||||
|
明确:
|
||||||
|
|
||||||
|
* 当前目标;
|
||||||
|
* 输入;
|
||||||
|
* 输出;
|
||||||
|
* 验收标准。
|
||||||
|
|
||||||
|
如果需求冲突:
|
||||||
|
|
||||||
|
必须先提问。
|
||||||
|
|
||||||
|
不得自行猜测。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 三、开发环境规范
|
||||||
|
|
||||||
|
## 3.1 Python 版本
|
||||||
|
|
||||||
|
统一使用:
|
||||||
|
|
||||||
|
Python 3.11
|
||||||
|
|
||||||
|
禁止:
|
||||||
|
|
||||||
|
* Python 3.13;
|
||||||
|
* Python 3.10 以下版本。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3.2 依赖管理
|
||||||
|
|
||||||
|
统一使用:
|
||||||
|
|
||||||
|
uv
|
||||||
|
|
||||||
|
禁止:
|
||||||
|
|
||||||
|
requirements.txt 手工维护。
|
||||||
|
|
||||||
|
依赖定义:
|
||||||
|
|
||||||
|
pyproject.toml
|
||||||
|
|
||||||
|
安装命令:
|
||||||
|
|
||||||
|
uv sync
|
||||||
|
|
||||||
|
新增依赖:
|
||||||
|
|
||||||
|
uv add 包名
|
||||||
|
|
||||||
|
开发依赖:
|
||||||
|
|
||||||
|
uv add --dev 包名
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3.3 虚拟环境
|
||||||
|
|
||||||
|
统一使用:
|
||||||
|
|
||||||
|
.venv
|
||||||
|
|
||||||
|
禁止:
|
||||||
|
|
||||||
|
使用 Conda。
|
||||||
|
|
||||||
|
禁止:
|
||||||
|
|
||||||
|
多个虚拟环境混用。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 四、代码规范
|
||||||
|
|
||||||
|
## 4.1 类型注解
|
||||||
|
|
||||||
|
所有新增代码必须包含类型注解。
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
def search_news(
|
||||||
|
keyword: str,
|
||||||
|
top_k: int
|
||||||
|
) -> list[dict]:
|
||||||
|
...
|
||||||
|
|
||||||
|
禁止:
|
||||||
|
|
||||||
|
省略类型。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4.2 中文说明
|
||||||
|
|
||||||
|
要求:
|
||||||
|
|
||||||
|
代码使用英文命名。
|
||||||
|
|
||||||
|
注释与文档使用中文。
|
||||||
|
|
||||||
|
例如:
|
||||||
|
|
||||||
|
# 新闻去重处理
|
||||||
|
|
||||||
|
def deduplicate_articles():
|
||||||
|
...
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4.3 函数长度
|
||||||
|
|
||||||
|
单个函数:
|
||||||
|
|
||||||
|
建议 ≤ 50 行。
|
||||||
|
|
||||||
|
超过:
|
||||||
|
|
||||||
|
必须拆分。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4.4 文件长度
|
||||||
|
|
||||||
|
单文件:
|
||||||
|
|
||||||
|
建议 ≤ 500 行。
|
||||||
|
|
||||||
|
超过:
|
||||||
|
|
||||||
|
必须拆分模块。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4.5 禁止魔法数字
|
||||||
|
|
||||||
|
禁止:
|
||||||
|
|
||||||
|
importance = 5
|
||||||
|
|
||||||
|
应写成:
|
||||||
|
|
||||||
|
MAX_IMPORTANCE = 5
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 五、日志规范
|
||||||
|
|
||||||
|
统一使用:
|
||||||
|
|
||||||
|
logging
|
||||||
|
|
||||||
|
禁止:
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
日志级别:
|
||||||
|
|
||||||
|
DEBUG
|
||||||
|
|
||||||
|
INFO
|
||||||
|
|
||||||
|
WARNING
|
||||||
|
|
||||||
|
ERROR
|
||||||
|
|
||||||
|
CRITICAL
|
||||||
|
|
||||||
|
日志格式:
|
||||||
|
|
||||||
|
时间 - 模块 - 级别
|
||||||
|
消息
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
2026-06-16 09:00:00 - crawler - INFO
|
||||||
|
开始抓取新浪财经
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 六、异常处理规范
|
||||||
|
|
||||||
|
禁止:
|
||||||
|
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
必须:
|
||||||
|
|
||||||
|
记录日志。
|
||||||
|
|
||||||
|
抛出明确异常。
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(e)
|
||||||
|
raise
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 七、测试规范
|
||||||
|
|
||||||
|
新增功能必须提供测试。
|
||||||
|
|
||||||
|
优先使用:
|
||||||
|
|
||||||
|
pytest
|
||||||
|
|
||||||
|
测试目录:
|
||||||
|
|
||||||
|
tests/
|
||||||
|
|
||||||
|
命名:
|
||||||
|
|
||||||
|
test_xxx.py
|
||||||
|
|
||||||
|
测试覆盖:
|
||||||
|
|
||||||
|
核心模块必须覆盖。
|
||||||
|
|
||||||
|
包括:
|
||||||
|
|
||||||
|
* crawler
|
||||||
|
* extractor
|
||||||
|
* dedup
|
||||||
|
* qdrant
|
||||||
|
* llm
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 八、配置规范
|
||||||
|
|
||||||
|
禁止硬编码。
|
||||||
|
|
||||||
|
配置统一放置:
|
||||||
|
|
||||||
|
configs/
|
||||||
|
|
||||||
|
支持:
|
||||||
|
|
||||||
|
YAML
|
||||||
|
|
||||||
|
环境变量
|
||||||
|
|
||||||
|
.env
|
||||||
|
|
||||||
|
禁止:
|
||||||
|
|
||||||
|
API Key 写入源码。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 九、Prompt 管理规范
|
||||||
|
|
||||||
|
Prompt 必须独立维护。
|
||||||
|
|
||||||
|
目录:
|
||||||
|
|
||||||
|
prompts/
|
||||||
|
|
||||||
|
禁止:
|
||||||
|
|
||||||
|
在代码中直接拼接长 Prompt。
|
||||||
|
|
||||||
|
Prompt 文件命名:
|
||||||
|
|
||||||
|
event_extraction.md
|
||||||
|
|
||||||
|
company_analysis.md
|
||||||
|
|
||||||
|
industry_analysis.md
|
||||||
|
|
||||||
|
risk_analysis.md
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 十、数据模型规范
|
||||||
|
|
||||||
|
统一使用:
|
||||||
|
|
||||||
|
Pydantic
|
||||||
|
|
||||||
|
禁止:
|
||||||
|
|
||||||
|
大量裸 dict。
|
||||||
|
|
||||||
|
核心对象必须定义模型。
|
||||||
|
|
||||||
|
例如:
|
||||||
|
|
||||||
|
Article
|
||||||
|
|
||||||
|
Event
|
||||||
|
|
||||||
|
EmbeddingResult
|
||||||
|
|
||||||
|
SearchResult
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 十一、数据库规范
|
||||||
|
|
||||||
|
Qdrant 为唯一向量数据库。
|
||||||
|
|
||||||
|
SQLite 用于:
|
||||||
|
|
||||||
|
任务状态;
|
||||||
|
缓存;
|
||||||
|
调试。
|
||||||
|
|
||||||
|
禁止:
|
||||||
|
|
||||||
|
引入多种向量数据库。
|
||||||
|
|
||||||
|
除非用户明确要求。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 十二、Docker 规范
|
||||||
|
|
||||||
|
所有服务必须支持 Docker。
|
||||||
|
|
||||||
|
必须提供:
|
||||||
|
|
||||||
|
docker-compose.yml
|
||||||
|
|
||||||
|
必须支持:
|
||||||
|
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
启动。
|
||||||
|
|
||||||
|
不得依赖:
|
||||||
|
|
||||||
|
手工安装。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 十三、Git 提交规范
|
||||||
|
|
||||||
|
完成每个 Milestone 后:
|
||||||
|
|
||||||
|
更新:
|
||||||
|
|
||||||
|
README.md
|
||||||
|
|
||||||
|
continuation.md
|
||||||
|
|
||||||
|
docs/
|
||||||
|
|
||||||
|
提交信息格式:
|
||||||
|
|
||||||
|
feat:
|
||||||
|
新增功能
|
||||||
|
|
||||||
|
fix:
|
||||||
|
问题修复
|
||||||
|
|
||||||
|
refactor:
|
||||||
|
重构
|
||||||
|
|
||||||
|
docs:
|
||||||
|
文档更新
|
||||||
|
|
||||||
|
test:
|
||||||
|
测试
|
||||||
|
|
||||||
|
chore:
|
||||||
|
杂项
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
feat: 完成 Crawl4AI 新闻抓取模块
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 十四、README 更新规范
|
||||||
|
|
||||||
|
每次功能完成后:
|
||||||
|
|
||||||
|
README 必须更新:
|
||||||
|
|
||||||
|
包括:
|
||||||
|
|
||||||
|
功能说明;
|
||||||
|
|
||||||
|
部署方法;
|
||||||
|
|
||||||
|
配置说明;
|
||||||
|
|
||||||
|
示例命令;
|
||||||
|
|
||||||
|
常见问题。
|
||||||
|
|
||||||
|
禁止:
|
||||||
|
|
||||||
|
README 长期不维护。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 十五、continuation.md 维护规范
|
||||||
|
|
||||||
|
Claude Code 每次结束工作前:
|
||||||
|
|
||||||
|
必须更新 continuation.md。
|
||||||
|
|
||||||
|
内容包括:
|
||||||
|
|
||||||
|
当前 Milestone;
|
||||||
|
|
||||||
|
完成内容;
|
||||||
|
|
||||||
|
待办事项;
|
||||||
|
|
||||||
|
已知问题;
|
||||||
|
|
||||||
|
技术债务;
|
||||||
|
|
||||||
|
下次建议。
|
||||||
|
|
||||||
|
用于恢复上下文。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 十六、性能要求
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
支持至少:
|
||||||
|
|
||||||
|
50 个财经新闻源。
|
||||||
|
|
||||||
|
支持:
|
||||||
|
|
||||||
|
并发抓取。
|
||||||
|
|
||||||
|
新闻处理吞吐:
|
||||||
|
|
||||||
|
≥100篇/分钟。
|
||||||
|
|
||||||
|
Qdrant 检索:
|
||||||
|
|
||||||
|
Top-K 响应时间 ≤ 2 秒。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 十七、安全规范
|
||||||
|
|
||||||
|
禁止:
|
||||||
|
|
||||||
|
提交:
|
||||||
|
|
||||||
|
.env
|
||||||
|
|
||||||
|
API Key
|
||||||
|
|
||||||
|
Cookie
|
||||||
|
|
||||||
|
Token
|
||||||
|
|
||||||
|
个人隐私数据
|
||||||
|
|
||||||
|
必须:
|
||||||
|
|
||||||
|
提供:
|
||||||
|
|
||||||
|
.env.example
|
||||||
|
|
||||||
|
示例配置。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 十八、禁止事项
|
||||||
|
|
||||||
|
Claude Code 禁止:
|
||||||
|
|
||||||
|
1. 未经允许重构已验收模块;
|
||||||
|
2. 一次性生成整个项目;
|
||||||
|
3. 擅自修改数据库结构;
|
||||||
|
4. 删除已有测试;
|
||||||
|
5. 使用 print 调试;
|
||||||
|
6. 忽略异常;
|
||||||
|
7. 跳过验收直接进入下一阶段;
|
||||||
|
8. 引入未经说明的新技术栈;
|
||||||
|
9. 将 Prompt 写死在代码中;
|
||||||
|
10. 编写无法运行的伪代码冒充完成。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 十九、输出格式要求
|
||||||
|
|
||||||
|
Claude Code 完成任务时必须输出:
|
||||||
|
|
||||||
|
【任务目标】
|
||||||
|
|
||||||
|
【完成内容】
|
||||||
|
|
||||||
|
【修改文件】
|
||||||
|
|
||||||
|
【运行方法】
|
||||||
|
|
||||||
|
【测试结果】
|
||||||
|
|
||||||
|
【存在问题】
|
||||||
|
|
||||||
|
【下一步建议】
|
||||||
|
|
||||||
|
不得只输出代码。
|
||||||
|
|
||||||
|
必须提供可验证说明。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 二十、最高优先级原则
|
||||||
|
|
||||||
|
当多个原则冲突时,优先级如下:
|
||||||
|
|
||||||
|
第一优先级:
|
||||||
|
|
||||||
|
代码正确、可运行。
|
||||||
|
|
||||||
|
第二优先级:
|
||||||
|
|
||||||
|
稳定性与可维护性。
|
||||||
|
|
||||||
|
第三优先级:
|
||||||
|
|
||||||
|
向后兼容。
|
||||||
|
|
||||||
|
第四优先级:
|
||||||
|
|
||||||
|
性能优化。
|
||||||
|
|
||||||
|
第五优先级:
|
||||||
|
|
||||||
|
代码优雅。
|
||||||
|
|
||||||
|
宁可代码普通,也不要复杂炫技。
|
||||||
|
|
||||||
|
本项目追求:
|
||||||
|
|
||||||
|
“小步迭代、稳定演进、长期维护”。
|
||||||
|
|
||||||
|
—— CLAUDE.md 结束 ——
|
||||||
|
|
||||||
@@ -0,0 +1,532 @@
|
|||||||
|
# A 股 Deep Research 私有投研平台
|
||||||
|
|
||||||
|
> 面向 A 股投资研究的私有化 Deep Research 平台。
|
||||||
|
>
|
||||||
|
> 自动抓取财经新闻 → 中文正文提取 → LLM 投资事件抽取 → 向量化 → Qdrant 知识库 → MCP/Agent 深度研究。
|
||||||
|
|
||||||
|
**项目定位**:A 股研究辅助与知识管理平台,**非**自动交易、**非**预测、**非**投资顾问。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
| 模块 | 选型 |
|
||||||
|
| ---- | ---- |
|
||||||
|
| 抓取 | Crawl4AI(异步 + JS 渲染) |
|
||||||
|
| 正文提取 | GNE(中文新闻正文识别) |
|
||||||
|
| LLM | DeepSeek / Qwen(百炼) |
|
||||||
|
| Embedding | BGE-M3(本地) / Qwen Embedding(远程) |
|
||||||
|
| 向量库 | Qdrant |
|
||||||
|
| 调度 | APScheduler |
|
||||||
|
| 服务化 | MCP |
|
||||||
|
| 运行时 | Python 3.11 + uv |
|
||||||
|
| 部署 | Docker Compose |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 当前状态
|
||||||
|
|
||||||
|
| 已完成 | M0~M8、**M9 投研 Agent Prompt** |
|
||||||
|
| --- | --- |
|
||||||
|
| 进行中 | 等待 M9 验收 |
|
||||||
|
| 下一步 | 项目整合与优化 |
|
||||||
|
|
||||||
|
详见 `continuation.md`。开发遵循 `project_plan.md` 9 个 Milestone,分阶段交付。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Qdrant 部署模式说明
|
||||||
|
|
||||||
|
本项目 Qdrant 支持两种运行模式,**默认本地文件模式(零依赖)**。
|
||||||
|
|
||||||
|
### 模式一:本地文件模式(默认,推荐)
|
||||||
|
|
||||||
|
```
|
||||||
|
类比:SQLite——无独立进程,Python 进程内嵌加载存储引擎
|
||||||
|
数据:data/qdrant_storage/ (纯文件,跟随项目目录)
|
||||||
|
进程:无守护进程
|
||||||
|
端口:不监听任何端口
|
||||||
|
并发:单进程串行读写(批处理场景足够)
|
||||||
|
```
|
||||||
|
|
||||||
|
**优点**:无需 Docker、无需手动安装、无需 root、树莓派 ARM64 兼容。
|
||||||
|
|
||||||
|
**启动方式**:代码中调用即自动加载,无需额外步骤。
|
||||||
|
|
||||||
|
```python
|
||||||
|
from vectorstore import make_qdrant_client, VectorStore
|
||||||
|
c = make_qdrant_client() # 默认 path="data/qdrant_storage"
|
||||||
|
s = VectorStore(c)
|
||||||
|
# ... 读写操作 ...
|
||||||
|
s.close()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 模式二:Docker Server 模式(可选,x86 推荐)
|
||||||
|
|
||||||
|
```
|
||||||
|
类比:PostgreSQL——独立守护进程,通过网络端口访问
|
||||||
|
数据:Docker volume qdrant_storage/
|
||||||
|
进程:容器 a_share_qdrant
|
||||||
|
端口:6333 (REST) / 6334 (gRPC)
|
||||||
|
并发:多客户端网络访问
|
||||||
|
```
|
||||||
|
|
||||||
|
**适用场景**:Cherry Studio 远程连接、多机共享知识库、生产环境。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 启动 Qdrant 服务
|
||||||
|
docker compose --profile m6 up -d
|
||||||
|
|
||||||
|
# 代码中指定 host
|
||||||
|
c = make_qdrant_client(host="192.168.1.160", port=6333)
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意**:树莓派 5 ARM64 内核使用 16KB 内存页,Qdrant 官方 Docker 镜像内置的 jemalloc 仅支持 4KB 页,**在树莓派上会启动即崩溃**(`exit 134`)。树莓派请使用本地文件模式。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 统一 CLI
|
||||||
|
|
||||||
|
所有操作通过 `a-share` 命令完成,替代之前的 8 个脚本入口:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 模块操作
|
||||||
|
uv run a-share crawl # M1 抓取
|
||||||
|
uv run a-share crawl --source cls # 单源
|
||||||
|
uv run a-share extract --date 20260616 # M2 提取
|
||||||
|
uv run a-share dedup --date 20260616 # M3 去重
|
||||||
|
uv run a-share events --date 20260616 # M4 LLM 抽取
|
||||||
|
uv run a-share events --provider qwen # 切换 LLM
|
||||||
|
uv run a-share embed --date 20260616 # M5 向量化
|
||||||
|
uv run a-share ingest --date 20260616 # M6 入库
|
||||||
|
|
||||||
|
# 全链路
|
||||||
|
uv run a-share pipeline --once # 立即执行全链路
|
||||||
|
|
||||||
|
# 检索(直接查 Qdrant,无需写代码或配 Cherry Studio)
|
||||||
|
uv run a-share search "宁德时代固态电池"
|
||||||
|
uv run a-share search "政策" --source cls --sentiment positive
|
||||||
|
uv run a-share search "风险" --stock 001212 --min-importance 3
|
||||||
|
|
||||||
|
# 日报(生成 HTML 报告并上传)
|
||||||
|
uv run a-share report # 生成今日日报
|
||||||
|
uv run a-share report --date 20260616 # 指定日期
|
||||||
|
uv run a-share pipeline --once --report # 全链路末尾自动生成日报
|
||||||
|
|
||||||
|
# 状态总览
|
||||||
|
uv run a-share status
|
||||||
|
```
|
||||||
|
|
||||||
|
### 环境准备
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 安装 uv (macOS)
|
||||||
|
brew install uv
|
||||||
|
|
||||||
|
# 2. 创建虚拟环境并同步依赖
|
||||||
|
uv sync
|
||||||
|
|
||||||
|
# 3. 复制环境变量模板
|
||||||
|
cp .env.example .env
|
||||||
|
# 编辑 .env 填写 API Key 等敏感配置
|
||||||
|
|
||||||
|
# 4. 验证 Python 版本(应为 3.11.x)
|
||||||
|
uv run python --version
|
||||||
|
|
||||||
|
# 5. 首次安装浏览器(Crawl4AI 依赖 Playwright Chromium)
|
||||||
|
uv run python -m playwright install chromium
|
||||||
|
```
|
||||||
|
|
||||||
|
### 运行抓取(M1)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 抓取全部启用源
|
||||||
|
uv run python -m scripts.run_crawler
|
||||||
|
|
||||||
|
# 只抓单个源
|
||||||
|
uv run python -m scripts.run_crawler --source cls
|
||||||
|
|
||||||
|
# 只试跑不写文件
|
||||||
|
uv run python -m scripts.run_crawler --no-save
|
||||||
|
|
||||||
|
# 调试日志
|
||||||
|
uv run python -m scripts.run_crawler --log-level DEBUG
|
||||||
|
```
|
||||||
|
|
||||||
|
抓取产物路径:`data/raw/{source_id}/{YYYYMMDD}/`
|
||||||
|
|
||||||
|
- `{url_hash}.html` 原始 HTML
|
||||||
|
- `{url_hash}.md` Crawl4AI 输出的 Markdown
|
||||||
|
- `index.jsonl` 元数据(每行一条 CrawlResult,不含正文大字段)
|
||||||
|
|
||||||
|
日志:`logs/crawler.log`(自动轮转 10MB,保留 5 份)
|
||||||
|
|
||||||
|
### 运行正文提取(M2)
|
||||||
|
|
||||||
|
M2 以 M1 的产物为输入,从 HTML 抽出标准化 `Article`(标题/正文/时间/作者/图片)。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 处理今日所有源
|
||||||
|
uv run python -m scripts.run_extractor
|
||||||
|
|
||||||
|
# 指定日期
|
||||||
|
uv run python -m scripts.run_extractor --date 20260616
|
||||||
|
|
||||||
|
# 只处理单个源
|
||||||
|
uv run python -m scripts.run_extractor --source sina --date 20260616
|
||||||
|
```
|
||||||
|
|
||||||
|
提取产物路径:`data/processed/{source_id}/{YYYYMMDD}/`
|
||||||
|
|
||||||
|
- `{url_hash}.json` 完整 Article(含正文)
|
||||||
|
- `index.jsonl` 扁平元数据(每行一条,不含正文,便于检索)
|
||||||
|
|
||||||
|
日志:`logs/extractor.log`
|
||||||
|
|
||||||
|
### 运行去重(M3)
|
||||||
|
|
||||||
|
M3 在 M2 输出之上做三层判重(URL Hash → 内容 Hash → SimHash 模糊),指纹持久化到 SQLite。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 处理今日全部源
|
||||||
|
uv run python -m scripts.run_dedup
|
||||||
|
|
||||||
|
# 指定日期
|
||||||
|
uv run python -m scripts.run_dedup --date 20260616
|
||||||
|
|
||||||
|
# 只处理单源
|
||||||
|
uv run python -m scripts.run_dedup --source sina --date 20260616
|
||||||
|
|
||||||
|
# 调阈值/窗口
|
||||||
|
uv run python -m scripts.run_dedup --simhash-threshold 3 --window-days 30
|
||||||
|
|
||||||
|
# 重建指纹库(全量重跑时使用)
|
||||||
|
uv run python -m scripts.run_dedup --reset
|
||||||
|
```
|
||||||
|
|
||||||
|
去重产物路径:
|
||||||
|
|
||||||
|
- `data/dedup/fingerprints.sqlite3` 指纹库(跨日累积)
|
||||||
|
- `data/deduped/{YYYYMMDD}/uniques/{url_hash}.json` 唯一文章(可送 M4+ 处理)
|
||||||
|
- `data/deduped/{YYYYMMDD}/duplicates.jsonl` 重复记录(含命中层 / 命中目标)
|
||||||
|
|
||||||
|
日志:`logs/dedup.log`
|
||||||
|
|
||||||
|
### 运行 LLM 事件抽取(M4)
|
||||||
|
|
||||||
|
M4 调用 DeepSeek 或 Qwen,从 M3 唯一文章中抽取结构化投资事件(stock_codes/sentiment/importance/event_type 等)。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 默认 DeepSeek(读 .env 的 DEEPSEEK_API_KEY)
|
||||||
|
uv run python -m scripts.run_event_extraction --date 20260616
|
||||||
|
|
||||||
|
# 切换 Qwen(百炼)
|
||||||
|
uv run python -m scripts.run_event_extraction --provider qwen --date 20260616
|
||||||
|
|
||||||
|
# 指定模型
|
||||||
|
uv run python -m scripts.run_event_extraction --provider qwen --model qwen-plus
|
||||||
|
|
||||||
|
# 联调小批量
|
||||||
|
uv run python -m scripts.run_event_extraction --limit 5
|
||||||
|
|
||||||
|
# 提速(并发,默认 3)
|
||||||
|
uv run python -m scripts.run_event_extraction --concurrency 5
|
||||||
|
|
||||||
|
# 跳过 M3,直接读 M2 处理产物(单源)
|
||||||
|
uv run python -m scripts.run_event_extraction --no-deduped --source cls
|
||||||
|
```
|
||||||
|
|
||||||
|
抽取产物路径:`data/events/{YYYYMMDD}/`
|
||||||
|
|
||||||
|
- `{url_hash}.json` 完整 ExtractedEvent
|
||||||
|
- `index.jsonl` 扁平摘要(每行一条)
|
||||||
|
- `failed.jsonl` 失败列表(若有)
|
||||||
|
|
||||||
|
环境变量(`.env`):
|
||||||
|
- `LLM_PROVIDER` 默认 `deepseek`,可选 `qwen`
|
||||||
|
- `LLM_MODEL` 覆盖默认模型
|
||||||
|
- `LLM_TEMPERATURE` 默认 0.1
|
||||||
|
- `LLM_TIMEOUT_SEC` 默认 60
|
||||||
|
- `DEEPSEEK_API_KEY` / `DEEPSEEK_BASE_URL`
|
||||||
|
- `DASHSCOPE_API_KEY` / `QWEN_BASE_URL`
|
||||||
|
|
||||||
|
日志:`logs/llm.log`
|
||||||
|
|
||||||
|
### 运行 Embedding 向量化(M5)
|
||||||
|
|
||||||
|
M5 把 M4 输出的事件用 DashScope `text-embedding-v3`(默认,1024 维)或本地 BGE-M3 向量化。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 默认 DashScope(读 .env 的 DASHSCOPE_API_KEY)
|
||||||
|
uv run python -m scripts.run_embedding --date 20260616
|
||||||
|
|
||||||
|
# 强制覆盖 provider/model(避免 .env 旧值干扰)
|
||||||
|
uv run python -m scripts.run_embedding --provider dashscope --model text-embedding-v3
|
||||||
|
|
||||||
|
# 用 M3 唯一文章为输入(跳过 M4 事件)
|
||||||
|
uv run python -m scripts.run_embedding --input deduped
|
||||||
|
|
||||||
|
# 用 M2 处理产物为输入(跳过 M3/M4)
|
||||||
|
uv run python -m scripts.run_embedding --input articles --source cls
|
||||||
|
|
||||||
|
# 启用本地 BGE-M3(需先 uv sync --extra local-embedding)
|
||||||
|
uv run python -m scripts.run_embedding --provider local-bge
|
||||||
|
```
|
||||||
|
|
||||||
|
输出路径:`data/embeddings/{YYYYMMDD}/`
|
||||||
|
|
||||||
|
- `{url_hash}.json` 完整 EmbeddingResult(含 1024 维向量)
|
||||||
|
- `index.jsonl` 扁平摘要(不含向量)
|
||||||
|
- `failed.jsonl` 失败列表
|
||||||
|
|
||||||
|
环境变量(`.env`):
|
||||||
|
- `EMBEDDING_PROVIDER` 默认 `dashscope`,可选 `local-bge`
|
||||||
|
- `DASHSCOPE_EMBEDDING_MODEL` DashScope 模型(默认 `text-embedding-v3`)
|
||||||
|
- `LOCAL_EMBEDDING_MODEL` 本地模型(默认 `BAAI/bge-m3`)
|
||||||
|
- `DASHSCOPE_API_KEY` / `QWEN_BASE_URL`(M4 已配)
|
||||||
|
|
||||||
|
日志:`logs/embedding.log`
|
||||||
|
|
||||||
|
### 运行 Qdrant 入库与检索(M6)
|
||||||
|
|
||||||
|
M6 把 M5 的嵌入向量 + M4 的事件标签写入 Qdrant 本地知识库(文件模式,无需 Docker)。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 入库(默认本地文件模式 data/qdrant_storage/)
|
||||||
|
uv run python -m scripts.run_qdrant_ingest --date 20260616
|
||||||
|
|
||||||
|
# 重建 collection + 全量入库
|
||||||
|
uv run python -m scripts.run_qdrant_ingest --recreate
|
||||||
|
|
||||||
|
# 试跑 N 条
|
||||||
|
uv run python -m scripts.run_qdrant_ingest --limit 10
|
||||||
|
|
||||||
|
# 指定存储路径
|
||||||
|
uv run python -m scripts.run_qdrant_ingest --path /mnt/data/qdrant
|
||||||
|
|
||||||
|
# 内存模式(测试)
|
||||||
|
uv run python -m scripts.run_qdrant_ingest --memory
|
||||||
|
```
|
||||||
|
|
||||||
|
检索示例:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from vectorstore import VectorStore, SearchFilter, make_qdrant_client
|
||||||
|
import json
|
||||||
|
|
||||||
|
c = make_qdrant_client()
|
||||||
|
store = VectorStore(c)
|
||||||
|
|
||||||
|
# 基础语义检索
|
||||||
|
probe = json.load(open("data/embeddings/20260616/abc.json"))
|
||||||
|
hits = store.query(query_vector=probe["vector"], top_k=10)
|
||||||
|
|
||||||
|
# 结构化过滤
|
||||||
|
hits = store.query(
|
||||||
|
query_vector=probe["vector"], top_k=10,
|
||||||
|
filter=SearchFilter(source_id="cls", importance_min=3, sentiment="positive"),
|
||||||
|
)
|
||||||
|
store.close()
|
||||||
|
```
|
||||||
|
|
||||||
|
环境变量(`.env`):
|
||||||
|
- `QDRANT_COLLECTION` Collection 名(默认 `a_share_news`)
|
||||||
|
- `QDRANT_HOST`/`QDRANT_PORT` 远程 HTTP 模式(不常用)
|
||||||
|
|
||||||
|
日志:`logs/qdrant.log`
|
||||||
|
|
||||||
|
### 运行全链路定时任务(M7)
|
||||||
|
|
||||||
|
M7 把 M1→M6 串成一个 Pipeline,支持单次执行和定时守护。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 立即执行一次全链路
|
||||||
|
uv run python -m scripts.run_scheduler --once
|
||||||
|
|
||||||
|
# 指定日期
|
||||||
|
uv run python -m scripts.run_scheduler --once --date 20260616
|
||||||
|
|
||||||
|
# 只执行部分步骤(逗号分隔)
|
||||||
|
uv run python -m scripts.run_scheduler --once --steps crawler,extractor,llm
|
||||||
|
|
||||||
|
# 启动定时守护进程(按 .env 中 SCHEDULE_TIMES 自动触发)
|
||||||
|
uv run python -m scripts.run_scheduler
|
||||||
|
```
|
||||||
|
|
||||||
|
定时时间由 `.env` 中 `SCHEDULE_TIMES` 控制(默认 `07:00,12:00,18:00,22:00`)。
|
||||||
|
|
||||||
|
Pipeline 总耗时约 4-5 分钟(100 篇文章),其中 M1 抓取(含浏览器渲染)最耗时(~3 分钟)。
|
||||||
|
|
||||||
|
日志:`logs/scheduler.log`
|
||||||
|
|
||||||
|
### MCP 服务(M8)
|
||||||
|
|
||||||
|
M8 暴露 5 个 MCP 工具给 Cherry Studio / Claude Code,对接 Qdrant 知识库。
|
||||||
|
|
||||||
|
**5 个工具**:
|
||||||
|
|
||||||
|
| 工具 | 功能 | 过滤字段 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `search_news` | 通用语义检索 | - |
|
||||||
|
| `search_company_news` | 按公司检索 | company_names |
|
||||||
|
| `search_industry_news` | 按行业检索 | industries |
|
||||||
|
| `search_stock_events` | 按股票代码检索 | stock_codes |
|
||||||
|
| `search_sentiment_trend` | 按情绪检索+统计 | sentiment |
|
||||||
|
|
||||||
|
**Cherry Studio 配置**(设置 → MCP 服务器 → 添加):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"a-share-research": {
|
||||||
|
"command": "uv",
|
||||||
|
"args": ["run", "python", "-m", "scripts.run_mcp_server"],
|
||||||
|
"cwd": "/home/pi/news"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Claude Code 配置**(`.mcp.json`):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"a-share-research": {
|
||||||
|
"command": "uv",
|
||||||
|
"args": ["run", "python", "-m", "scripts.run_mcp_server"],
|
||||||
|
"cwd": "/home/pi/news"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**调试**(HTTP SSE 模式,浏览器访问 `http://<host>:8765/sse`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run python -m scripts.run_mcp_server --sse 8765
|
||||||
|
```
|
||||||
|
|
||||||
|
日志:`logs/mcp_server.log`
|
||||||
|
|
||||||
|
**使用示例**(在 Cherry Studio / Claude Code 中输入):
|
||||||
|
|
||||||
|
```
|
||||||
|
用 search_news 搜索"宁德时代固态电池最新进展"
|
||||||
|
|
||||||
|
用 search_stock_events 查 300750 最近的重大事件
|
||||||
|
|
||||||
|
用 search_sentiment_trend 分析"AI 算力"相关新闻的情绪变化
|
||||||
|
|
||||||
|
用 search_company_news 搜"贵州茅台"关于价格调整的新闻
|
||||||
|
|
||||||
|
用 search_industry_news 搜"半导体"行业的最新政策
|
||||||
|
```
|
||||||
|
|
||||||
|
**工作原理**:每个查询 → DashScope 嵌入 → Qdrant 语义检索(2.3ms)→ 格式化 Markdown 返回。
|
||||||
|
|
||||||
|
#### systemd 部署(推荐)
|
||||||
|
|
||||||
|
将调度器安装为系统服务,开机自启、崩溃自动重启。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装
|
||||||
|
sudo cp scripts/a-share-research.service /etc/systemd/system/
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable a-share-research
|
||||||
|
|
||||||
|
# 日常操作
|
||||||
|
sudo systemctl start a-share-research # 启动
|
||||||
|
sudo systemctl stop a-share-research # 停止
|
||||||
|
sudo systemctl restart a-share-research # 重启
|
||||||
|
sudo systemctl status a-share-research # 查看状态
|
||||||
|
|
||||||
|
# 查看日志
|
||||||
|
journalctl -u a-share-research -f # 实时
|
||||||
|
tail -f logs/scheduler.log # 文件
|
||||||
|
```
|
||||||
|
|
||||||
|
服务进程:`uv run python -m scripts.run_scheduler`(PID 见 `systemctl status`)
|
||||||
|
|
||||||
|
### 修改新闻源
|
||||||
|
|
||||||
|
编辑 `configs/sources.yaml`,字段说明见文件顶部注释。新增源**无需改代码**(配置驱动原则)。
|
||||||
|
|
||||||
|
### 运行测试
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run pytest # 单元测试(默认排除集成)
|
||||||
|
uv run pytest -m integration # 真实抓取烟测(需联网+浏览器)
|
||||||
|
uv run pytest tests/test_crawler.py -v
|
||||||
|
uv run pytest tests/test_crawler.py::test_extract_article_links_basic
|
||||||
|
uv run pytest --cov=crawler # 覆盖率
|
||||||
|
```
|
||||||
|
|
||||||
|
### 代码质量
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run ruff check . # Lint
|
||||||
|
uv run ruff format . # 格式化
|
||||||
|
uv run mypy crawler/ # 类型检查
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker(M6 起启用)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose --profile m6 up -d # 启动 Qdrant
|
||||||
|
docker compose logs -f
|
||||||
|
docker compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
news/
|
||||||
|
├─ crawler/ # M1 新闻抓取(Crawl4AI)
|
||||||
|
├─ extractor/ # M2 中文正文提取(GNE)
|
||||||
|
├─ dedup/ # M3 三层去重
|
||||||
|
├─ llm/ # M4 投资事件抽取
|
||||||
|
├─ embedding/ # M5 向量生成
|
||||||
|
├─ vectorstore/ # M6 Qdrant 客户端封装
|
||||||
|
├─ scheduler/ # M7 定时任务
|
||||||
|
├─ mcp_server/ # M8 MCP 服务
|
||||||
|
├─ api/ # 对外接口
|
||||||
|
├─ app/ # 应用入口
|
||||||
|
├─ configs/ # 网站源 / 系统配置(YAML)
|
||||||
|
├─ prompts/ # LLM Prompt 模板
|
||||||
|
├─ tests/ # pytest 测试
|
||||||
|
├─ scripts/ # 运维脚本(含 run_crawler)
|
||||||
|
├─ docs/ # 设计文档与决策记录
|
||||||
|
├─ logs/ # 运行日志(git ignored)
|
||||||
|
├─ data/ # 抓取与缓存(git ignored)
|
||||||
|
├─ pyproject.toml
|
||||||
|
├─ docker-compose.yml
|
||||||
|
├─ .env.example
|
||||||
|
├─ CLAUDE.md # Claude Code 开发约束
|
||||||
|
├─ project_plan.md # 项目计划
|
||||||
|
├─ continuation.md # 上下文恢复
|
||||||
|
└─ README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 开发约束
|
||||||
|
|
||||||
|
详见 `CLAUDE.md`。关键纪律:
|
||||||
|
|
||||||
|
- 分阶段开发,每个 Milestone 验收通过后再进入下一个;
|
||||||
|
- 配置驱动,不硬编码;
|
||||||
|
- 异常必须记录日志并显式抛出;
|
||||||
|
- 所有新增代码须含类型注解;
|
||||||
|
- Prompt 单独文件维护,不写死在代码里;
|
||||||
|
- 代码用英文命名,注释/文档用中文。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 许可
|
||||||
|
|
||||||
|
私有项目,未授权禁止使用与传播。
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
"""A 股 Deep Research 统一 CLI 入口。"""
|
||||||
@@ -0,0 +1,962 @@
|
|||||||
|
"""A 股 Deep Research 统一 CLI。
|
||||||
|
|
||||||
|
用法:
|
||||||
|
uv run a-share crawl # M1 抓取
|
||||||
|
uv run a-share extract --date 20260616 # M2 提取
|
||||||
|
uv run a-share dedup --date 20260616 # M3 去重
|
||||||
|
uv run a-share events --date 20260616 # M4 LLM 抽取
|
||||||
|
uv run a-share embed --date 20260616 # M5 向量化
|
||||||
|
uv run a-share ingest --date 20260616 # M6 入库
|
||||||
|
uv run a-share pipeline --once # 全链路
|
||||||
|
uv run a-share discover https://xxx.com # 分析站点,推荐配置
|
||||||
|
uv run a-share report # 生成日报
|
||||||
|
uv run a-share search "宁德时代" # 检索知识库
|
||||||
|
uv run a-share status # 状态总览
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from collections import Counter
|
||||||
|
from datetime import date, datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# 工具
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# 操作日志
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
_OP_LOG_PATH = Path("logs") / "operations.log"
|
||||||
|
|
||||||
|
def _log_operation_start(command: str, detail: str = "") -> None:
|
||||||
|
"""记录操作开始。"""
|
||||||
|
_OP_LOG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
line = f"[{ts}] START | {command:20} | {detail}".rstrip()
|
||||||
|
with _OP_LOG_PATH.open("a", encoding="utf-8") as f:
|
||||||
|
f.write(line + "\n")
|
||||||
|
|
||||||
|
def _log_operation_end(command: str, success: bool, elapsed: float, detail: str = "") -> None:
|
||||||
|
"""记录操作结束。"""
|
||||||
|
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
status = "OK" if success else "FAIL"
|
||||||
|
detail_str = f" | {detail}" if detail else ""
|
||||||
|
line = f"[{ts}] END | {command:20} | {status} | {elapsed:.1f}s{detail_str}"
|
||||||
|
with _OP_LOG_PATH.open("a", encoding="utf-8") as f:
|
||||||
|
f.write(line + "\n")
|
||||||
|
|
||||||
|
def _wrap_cmd(name: str, func, *cmd_args: object) -> int:
|
||||||
|
"""包装命令函数,自动记录开始/结束日志。"""
|
||||||
|
from time import perf_counter
|
||||||
|
detail_parts = []
|
||||||
|
for a in cmd_args:
|
||||||
|
if a is not None and a is not False and a != "" and a != 0:
|
||||||
|
detail_parts.append(str(a)[:80])
|
||||||
|
detail = " ".join(detail_parts) if detail_parts else ""
|
||||||
|
_log_operation_start(name, detail)
|
||||||
|
started = perf_counter()
|
||||||
|
try:
|
||||||
|
rc = func()
|
||||||
|
elapsed = perf_counter() - started
|
||||||
|
_log_operation_end(name, rc == 0, elapsed)
|
||||||
|
return rc
|
||||||
|
except Exception as e:
|
||||||
|
elapsed = perf_counter() - started
|
||||||
|
_log_operation_end(name, False, elapsed, f"{type(e).__name__}: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def _setup_logger(level: str = "WARNING") -> None:
|
||||||
|
logger.remove()
|
||||||
|
logger.add(sys.stderr, level=level, format="{level} | {message}")
|
||||||
|
|
||||||
|
|
||||||
|
def _today() -> str:
|
||||||
|
return date.today().strftime("%Y%m%d")
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_timeout(step_name: str, hardcoded_default: int) -> int:
|
||||||
|
"""解析步骤超时(秒),与 scheduler.pipeline 保持一致的优先级。
|
||||||
|
|
||||||
|
优先级:
|
||||||
|
1. TIMEOUT_{STEP_NAME} 环境变量
|
||||||
|
2. PIPELINE_STEP_TIMEOUT 环境变量 (全局兜底)
|
||||||
|
3. 硬编码默认值 (本函数参数)
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
specific_key = f"TIMEOUT_{step_name.upper()}"
|
||||||
|
if specific_key in os.environ:
|
||||||
|
return int(os.environ[specific_key])
|
||||||
|
if "PIPELINE_STEP_TIMEOUT" in os.environ:
|
||||||
|
return int(os.environ["PIPELINE_STEP_TIMEOUT"])
|
||||||
|
return hardcoded_default
|
||||||
|
|
||||||
|
|
||||||
|
def _run_module(module: str, extra_args: list[str], timeout: int = 600) -> int:
|
||||||
|
"""调用现有 scripts/run_*.py 模块。"""
|
||||||
|
cmd = ["uv", "run", "python", "-m", module, *extra_args]
|
||||||
|
logger.info("执行: {}", " ".join(cmd))
|
||||||
|
try:
|
||||||
|
result = subprocess.run(cmd, timeout=timeout)
|
||||||
|
return result.returncode
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
logger.error("超时 ({}s): {}", timeout, " ".join(cmd))
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# 子命令
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
def cmd_crawl(args: argparse.Namespace) -> int:
|
||||||
|
extra = []
|
||||||
|
if args.source:
|
||||||
|
extra.extend(["--source", args.source])
|
||||||
|
if args.no_save:
|
||||||
|
extra.append("--no-save")
|
||||||
|
return _run_module("scripts.run_crawler", extra, timeout=_resolve_timeout("crawler", 600))
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_extract(args: argparse.Namespace) -> int:
|
||||||
|
extra = ["--date", args.date or _today()]
|
||||||
|
if args.source:
|
||||||
|
extra.extend(["--source", args.source])
|
||||||
|
return _run_module("scripts.run_extractor", extra, timeout=_resolve_timeout("extractor", 300))
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_dedup(args: argparse.Namespace) -> int:
|
||||||
|
extra = ["--date", args.date or _today()]
|
||||||
|
if args.reset:
|
||||||
|
extra.append("--reset")
|
||||||
|
return _run_module("scripts.run_dedup", extra, timeout=_resolve_timeout("dedup", 120))
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_events(args: argparse.Namespace) -> int:
|
||||||
|
extra = ["--date", args.date or _today()]
|
||||||
|
if args.provider:
|
||||||
|
extra.extend(["--provider", args.provider])
|
||||||
|
if args.model:
|
||||||
|
extra.extend(["--model", args.model])
|
||||||
|
if args.limit:
|
||||||
|
extra.extend(["--limit", str(args.limit)])
|
||||||
|
if args.concurrency:
|
||||||
|
extra.extend(["--concurrency", str(args.concurrency)])
|
||||||
|
return _run_module("scripts.run_event_extraction", extra, timeout=_resolve_timeout("llm", 900))
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_embed(args: argparse.Namespace) -> int:
|
||||||
|
extra = ["--date", args.date or _today()]
|
||||||
|
if args.provider:
|
||||||
|
extra.extend(["--provider", args.provider])
|
||||||
|
if args.model:
|
||||||
|
extra.extend(["--model", args.model])
|
||||||
|
return _run_module("scripts.run_embedding", extra, timeout=_resolve_timeout("embedding", 300))
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_ingest(args: argparse.Namespace) -> int:
|
||||||
|
extra = ["--date", args.date or _today()]
|
||||||
|
if args.recreate:
|
||||||
|
extra.append("--recreate")
|
||||||
|
return _run_module("scripts.run_qdrant_ingest", extra, timeout=_resolve_timeout("qdrant", 120))
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_pipeline(args: argparse.Namespace) -> int:
|
||||||
|
pipeline_timeout = _resolve_timeout("pipeline", 3600)
|
||||||
|
if args.cninfo_once:
|
||||||
|
extra = ["--once", "--date", args.date or _today(),
|
||||||
|
"--steps", "cninfo_crawl,cninfo_extract,cninfo_pdf,dedup,llm,embedding,qdrant"]
|
||||||
|
return _run_module("scripts.run_scheduler", extra, timeout=pipeline_timeout)
|
||||||
|
if args.once:
|
||||||
|
extra = ["--once", "--date", args.date or _today()]
|
||||||
|
steps = args.steps
|
||||||
|
if args.report:
|
||||||
|
steps = (steps + ",report") if steps else "report"
|
||||||
|
if steps:
|
||||||
|
extra.extend(["--steps", steps])
|
||||||
|
return _run_module("scripts.run_scheduler", extra, timeout=pipeline_timeout)
|
||||||
|
# 守护进程模式:不应通过 CLI 子进程启动(subprocess.run 无法正确管理后台进程)。
|
||||||
|
# 直接在当前进程启动 APScheduler。
|
||||||
|
print("🔁 启动守护进程模式 (APScheduler) …")
|
||||||
|
print(" 提示: 生产环境请使用 systemd 管理,见 docs/systemd.md")
|
||||||
|
from scripts.run_scheduler import _daemon as _run_daemon
|
||||||
|
# 构造一个简易的 namespace 给 _daemon
|
||||||
|
daemon_args = argparse.Namespace()
|
||||||
|
return _run_daemon(daemon_args)
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# search — 直接从终端检索知识库
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
def cmd_search(args: argparse.Namespace) -> int:
|
||||||
|
"""嵌入 query → Qdrant 检索 → 格式化输出。"""
|
||||||
|
load_dotenv()
|
||||||
|
_setup_logger("WARNING")
|
||||||
|
|
||||||
|
from embedding import make_sync_provider
|
||||||
|
from vectorstore import SearchFilter, VectorStore, make_qdrant_client
|
||||||
|
|
||||||
|
# 嵌入查询
|
||||||
|
print(f"🔍 检索: {args.query}")
|
||||||
|
emb = make_sync_provider() # 读取 EMBEDDING_PROVIDER 环境变量
|
||||||
|
vec = emb.embed_one(args.query)
|
||||||
|
|
||||||
|
# 构建过滤
|
||||||
|
filt = None
|
||||||
|
has_filter = any([
|
||||||
|
args.source, args.sentiment, args.min_importance,
|
||||||
|
args.stock, args.industry,
|
||||||
|
])
|
||||||
|
if has_filter:
|
||||||
|
kwargs: dict[str, Any] = {}
|
||||||
|
if args.source:
|
||||||
|
kwargs["source_id"] = args.source
|
||||||
|
if args.sentiment:
|
||||||
|
kwargs["sentiment"] = args.sentiment
|
||||||
|
if args.min_importance:
|
||||||
|
kwargs["importance_min"] = args.min_importance
|
||||||
|
if args.stock:
|
||||||
|
# 股票代码可能带或不带后缀,两者都匹配
|
||||||
|
code = args.stock.strip().upper()
|
||||||
|
stock_list = [code] if "." in code else [f"{code}.SZ", f"{code}.SH", f"{code}.BJ"]
|
||||||
|
kwargs["stock_codes"] = stock_list
|
||||||
|
if args.industry:
|
||||||
|
kwargs["industries"] = [args.industry]
|
||||||
|
filt = SearchFilter(**kwargs)
|
||||||
|
|
||||||
|
# 检索
|
||||||
|
client = make_qdrant_client()
|
||||||
|
store = VectorStore(client)
|
||||||
|
hits = store.query(query_vector=vec, top_k=args.top, filter=filt, score_threshold=0.2)
|
||||||
|
store.close()
|
||||||
|
emb.close()
|
||||||
|
|
||||||
|
if not hits:
|
||||||
|
print(f"\n未找到与「{args.query}」相关的结果。")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
print(f"\n共 {len(hits)} 条结果:\n")
|
||||||
|
for i, h in enumerate(hits, 1):
|
||||||
|
ev = h.event or {}
|
||||||
|
sentiment_icon = {"positive": "🟢", "neutral": "⚪", "negative": "🔴"}.get(
|
||||||
|
ev.get("sentiment"), ""
|
||||||
|
)
|
||||||
|
print(f"{i}. {sentiment_icon} {h.title}")
|
||||||
|
print(f" 来源: {h.source_id} | 相似度: {h.score:.4f} | 时间: {h.publish_time}")
|
||||||
|
if ev.get("stock_codes"):
|
||||||
|
print(f" 代码: {','.join(ev['stock_codes'])}")
|
||||||
|
if ev.get("event_type"):
|
||||||
|
print(f" 事件: {ev.get('event_type')} (重要度 {ev.get('importance', '-')})")
|
||||||
|
if ev.get("summary"):
|
||||||
|
print(f" 摘要: {ev['summary']}")
|
||||||
|
print(f" {h.url}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# status — 数据总览
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
def cmd_status(args: argparse.Namespace) -> int: # noqa: ARG001
|
||||||
|
"""输出各层数据统计。"""
|
||||||
|
load_dotenv()
|
||||||
|
today_str = _today()
|
||||||
|
|
||||||
|
def _count_jsonl(path: Path) -> int:
|
||||||
|
if not path.is_file():
|
||||||
|
return 0
|
||||||
|
return sum(1 for _ in open(path, encoding="utf-8"))
|
||||||
|
|
||||||
|
def _count_dir(pattern: str) -> int:
|
||||||
|
return len(list(Path().glob(pattern)))
|
||||||
|
|
||||||
|
def _count_json(pattern: str) -> int:
|
||||||
|
return len(list(Path().glob(pattern)))
|
||||||
|
|
||||||
|
print(f"📊 A 股 Deep Research 状态 — {today_str}")
|
||||||
|
print("─" * 50)
|
||||||
|
|
||||||
|
# M1 raw
|
||||||
|
raw_art = 0
|
||||||
|
for idx in Path("data/raw").glob(f"*/{today_str}/index.jsonl"):
|
||||||
|
raw_art += _count_jsonl(idx)
|
||||||
|
print(f" M1 抓取: {raw_art} 篇原始文章")
|
||||||
|
|
||||||
|
# M2 processed
|
||||||
|
proc = _count_json(f"data/processed/*/{today_str}/*.json")
|
||||||
|
print(f" M2 提取: {proc} 篇正文")
|
||||||
|
|
||||||
|
# M3 deduped
|
||||||
|
deduped = _count_json(f"data/deduped/{today_str}/uniques/*.json")
|
||||||
|
dup_path = Path(f"data/deduped/{today_str}/duplicates.jsonl")
|
||||||
|
dups = _count_jsonl(dup_path) if dup_path.is_file() else 0
|
||||||
|
print(f" M3 去重: {deduped} 篇唯一, {dups} 篇重复")
|
||||||
|
|
||||||
|
# M4 events
|
||||||
|
events = _count_json(f"data/events/{today_str}/*.json")
|
||||||
|
if events > 0:
|
||||||
|
sentiments: Counter = Counter()
|
||||||
|
importances: Counter = Counter()
|
||||||
|
for fp in Path(f"data/events/{today_str}").glob("*.json"):
|
||||||
|
try:
|
||||||
|
obj = json.loads(fp.read_text(encoding="utf-8"))
|
||||||
|
ev = obj.get("event", {})
|
||||||
|
sentiments[ev.get("sentiment", "?")] += 1
|
||||||
|
importances[ev.get("importance", 0)] += 1
|
||||||
|
except (json.JSONDecodeError, OSError):
|
||||||
|
pass
|
||||||
|
high = sum(v for k, v in importances.items() if k >= 4)
|
||||||
|
print(f" M4 事件: {events} 篇 "
|
||||||
|
f"(🟢{sentiments.get('positive', 0)} "
|
||||||
|
f"🔴{sentiments.get('negative', 0)} "
|
||||||
|
f"⚪{sentiments.get('neutral', 0)}, "
|
||||||
|
f"重要≥4: {high})")
|
||||||
|
|
||||||
|
# M5 embeddings
|
||||||
|
emb_count = _count_json(f"data/embeddings/{today_str}/*.json")
|
||||||
|
print(f" M5 向量: {emb_count} 条")
|
||||||
|
|
||||||
|
# cninfo 公告
|
||||||
|
cninfo_raw = 0
|
||||||
|
for idx in Path("data/raw/cninfo").glob("*/index.jsonl"):
|
||||||
|
cninfo_raw += _count_jsonl(idx)
|
||||||
|
if cninfo_raw > 0:
|
||||||
|
cninfo_proc = _count_json("data/processed/cninfo/*/*.json")
|
||||||
|
print(f" cninfo: {cninfo_raw} 条公告 (已提取 {cninfo_proc})")
|
||||||
|
else:
|
||||||
|
print(" cninfo: 暂无数据")
|
||||||
|
|
||||||
|
# M6 Qdrant
|
||||||
|
try:
|
||||||
|
from vectorstore import VectorStore, make_qdrant_client
|
||||||
|
c = make_qdrant_client()
|
||||||
|
s = VectorStore(c)
|
||||||
|
total = s.count()
|
||||||
|
s.close()
|
||||||
|
print(f" M6 Qdrant: {total} 条向量")
|
||||||
|
except Exception:
|
||||||
|
print(" M6 Qdrant: 未连接")
|
||||||
|
|
||||||
|
# M7 systemd
|
||||||
|
try:
|
||||||
|
r = subprocess.run(
|
||||||
|
["systemctl", "is-active", "a-share-research"],
|
||||||
|
capture_output=True, text=True, timeout=5,
|
||||||
|
)
|
||||||
|
svc = r.stdout.strip()
|
||||||
|
icon = "✅" if svc == "active" else "❌"
|
||||||
|
print(f" M7 调度: {icon} {svc}")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
print("─" * 50)
|
||||||
|
|
||||||
|
# 高重要度事件
|
||||||
|
if events > 0:
|
||||||
|
print("\n🔥 今日重要度 ≥4 的事件:")
|
||||||
|
high_events: list[dict] = []
|
||||||
|
for fp in Path(f"data/events/{today_str}").glob("*.json"):
|
||||||
|
try:
|
||||||
|
obj = json.loads(fp.read_text(encoding="utf-8"))
|
||||||
|
ev = obj.get("event", {})
|
||||||
|
if ev.get("importance", 0) >= 4:
|
||||||
|
high_events.append({
|
||||||
|
"title": obj.get("title", ""),
|
||||||
|
"source": obj.get("source_id", ""),
|
||||||
|
"sentiment": ev.get("sentiment", ""),
|
||||||
|
"importance": ev.get("importance", 0),
|
||||||
|
"summary": ev.get("summary", ""),
|
||||||
|
})
|
||||||
|
except (json.JSONDecodeError, OSError):
|
||||||
|
pass
|
||||||
|
high_events.sort(key=lambda e: -e["importance"])
|
||||||
|
for e in high_events[:10]:
|
||||||
|
icon = {"positive": "🟢", "negative": "🔴", "neutral": "⚪"}.get(e["sentiment"], "")
|
||||||
|
print(f" {icon} [{e['source']}] {e['title'][:50]} ({e['summary'][:40]})")
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# report — 生成每日摘要 HTML 报告并上传
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
def cmd_report(args: argparse.Namespace) -> int:
|
||||||
|
"""生成 HTML 日报并上传到 Web 服务器。"""
|
||||||
|
load_dotenv()
|
||||||
|
from scheduler.reporter import generate_report
|
||||||
|
|
||||||
|
day_str = args.date or _today()
|
||||||
|
print(f"📊 生成日报: {day_str}")
|
||||||
|
path = generate_report(day_str, upload=not args.no_upload)
|
||||||
|
if path is None:
|
||||||
|
print("⚠️ 无数据或生成失败")
|
||||||
|
return 1
|
||||||
|
print(f"✅ 日报已保存: {path}")
|
||||||
|
if not args.no_upload:
|
||||||
|
from datetime import date as dt_date
|
||||||
|
today_str = dt_date.today().strftime("%Y%m%d")
|
||||||
|
print(f"✅ 已上传: http://doorcome.cn/echart/research/{today_str}/")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# discover — 自动分析站点,生成 sources.yaml 配置建议
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
def cmd_discover(args: argparse.Namespace) -> int:
|
||||||
|
"""抓取首页 → 提取链接 → 按 URL 模式聚类 → 输出 yaml 配置。"""
|
||||||
|
import re
|
||||||
|
import urllib.parse
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
print(f"🔍 分析站点: {args.url}")
|
||||||
|
print("⏳ 正在抓取首页(含 JS 渲染)...\n")
|
||||||
|
|
||||||
|
# 1. 抓取首页
|
||||||
|
try:
|
||||||
|
from crawl4ai import AsyncWebCrawler, BrowserConfig, CacheMode, CrawlerRunConfig
|
||||||
|
|
||||||
|
async def _fetch():
|
||||||
|
bconf = BrowserConfig(headless=True, verbose=False)
|
||||||
|
rconf = CrawlerRunConfig(cache_mode=CacheMode.BYPASS, page_timeout=30000)
|
||||||
|
async with AsyncWebCrawler(config=bconf) as crawler:
|
||||||
|
return await crawler.arun(url=args.url, config=rconf)
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
result = asyncio.run(_fetch())
|
||||||
|
except ImportError:
|
||||||
|
print("❌ 需要 crawl4ai 依赖,请确保已安装")
|
||||||
|
return 1
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 抓取失败: {e}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if not result or not getattr(result, "success", False):
|
||||||
|
print(f"❌ 页面抓取失败: {getattr(result, 'error_message', 'unknown')}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
html = getattr(result, "html", "") or ""
|
||||||
|
if not html:
|
||||||
|
print("❌ 页面无 HTML 内容")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# 2. 提取所有链接
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
soup = BeautifulSoup(html, "html.parser")
|
||||||
|
parsed_base = urllib.parse.urlparse(args.url)
|
||||||
|
base_domain = f"{parsed_base.scheme}://{parsed_base.netloc}"
|
||||||
|
|
||||||
|
raw_links: list[tuple[str, str]] = []
|
||||||
|
for a in soup.find_all("a", href=True):
|
||||||
|
href = a["href"].strip()
|
||||||
|
if not href or href.startswith(("javascript:", "#", "mailto:", "tel:")):
|
||||||
|
continue
|
||||||
|
absolute = urllib.parse.urljoin(args.url, href).split("#")[0]
|
||||||
|
anchor = (a.get_text() or "").strip()[:40]
|
||||||
|
raw_links.append((absolute, anchor))
|
||||||
|
|
||||||
|
# 3. 过滤出站内链接,排除导航/静态页
|
||||||
|
site_links: list[tuple[str, str]] = []
|
||||||
|
for url, anchor in raw_links:
|
||||||
|
if base_domain not in url:
|
||||||
|
continue
|
||||||
|
# 排除明显的导航/静态页
|
||||||
|
path = urllib.parse.urlparse(url).path.lower()
|
||||||
|
if path in ("/", "") or any(path.endswith(ext) for ext in (".css", ".js", ".png", ".jpg", ".ico", ".svg", ".xml", ".pdf")):
|
||||||
|
continue
|
||||||
|
site_links.append((url, anchor))
|
||||||
|
|
||||||
|
if not site_links:
|
||||||
|
print("❌ 未发现站内链接(可能需要 JS 渲染或页面结构特殊)")
|
||||||
|
print(" 可尝试手动打开浏览器开发者工具查看网络请求")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
unique_links = list(dict.fromkeys(site_links)) # 去重保序
|
||||||
|
|
||||||
|
# 4. 过滤导航/功能页
|
||||||
|
_nav_words = (
|
||||||
|
"about", "download", "feedback", "member", "login", "register", "app",
|
||||||
|
"calendar", "help", "contact", "privacy", "terms", "service", "buy",
|
||||||
|
"markets", "codes", "real", "lives",
|
||||||
|
)
|
||||||
|
article_links: list[tuple[str, str]] = []
|
||||||
|
for url, anchor in unique_links:
|
||||||
|
path_lower = urllib.parse.urlparse(url).path.lower()
|
||||||
|
parts_lower = [p for p in path_lower.split("/") if p]
|
||||||
|
# 跳过明显的导航/功能路径(子串匹配)
|
||||||
|
if any(nav in p for nav in _nav_words for p in parts_lower if len(p) > 2):
|
||||||
|
continue
|
||||||
|
article_links.append((url, anchor))
|
||||||
|
|
||||||
|
if not article_links:
|
||||||
|
# 降级:不过滤
|
||||||
|
article_links = unique_links
|
||||||
|
|
||||||
|
# 5. 按 URL 路径模式聚类
|
||||||
|
clusters: dict[str, list[tuple[str, str]]] = defaultdict(list)
|
||||||
|
for url, anchor in article_links:
|
||||||
|
path = urllib.parse.urlparse(url).path.strip("/")
|
||||||
|
# 把数字/日期/hash 替换为占位符进行聚类
|
||||||
|
pattern = re.sub(r"/\d{4,}", "/{id}", path)
|
||||||
|
pattern = re.sub(r"/[a-f0-9]{32,}", "/{hash}", pattern)
|
||||||
|
pattern = re.sub(r"/\d{4}-\d{2}-\d{2}", "/{date}", pattern)
|
||||||
|
# 恢复目录结构作为聚类键
|
||||||
|
parts = pattern.split("/")
|
||||||
|
# 聚类键:用路径前缀(前两级目录) + 最后一段的模式
|
||||||
|
key = "/".join(parts[:2]) + "/{...}" if len(parts) >= 2 else (parts[0] if parts else "/")
|
||||||
|
clusters[key].append((url, anchor))
|
||||||
|
|
||||||
|
# 过滤只有 1 条的聚类
|
||||||
|
clusters = {k: v for k, v in clusters.items() if len(v) >= 2}
|
||||||
|
if not clusters:
|
||||||
|
print("⚠️ 未发现明显的文章链接模式(每个 URL 路径都不同)")
|
||||||
|
print(" 可手动检查页面结构")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# 6. 按链接数排序,但优先数字 ID 模式(文章特征)
|
||||||
|
def _score(kv: tuple) -> tuple:
|
||||||
|
_key, _links = kv
|
||||||
|
has_num_id = bool(re.search(r"/\d{4,}", _links[0][0])) # URL 含长数字
|
||||||
|
article_word = any(w in _key.lower() for w in ("article", "news", "detail", "story"))
|
||||||
|
return (has_num_id or article_word, len(_links))
|
||||||
|
|
||||||
|
ranked = sorted(clusters.items(), key=_score, reverse=True)
|
||||||
|
|
||||||
|
# 6. 对每个聚类生成正则
|
||||||
|
site_host = urllib.parse.urlparse(args.url).netloc.replace(".", r"\.")
|
||||||
|
results: list[dict] = []
|
||||||
|
for cluster_key, links in ranked:
|
||||||
|
# 从实际 URL 中提取路径前缀来构建更精确的正则
|
||||||
|
sample_paths = [urllib.parse.urlparse(u).path for u, _ in links[:5]]
|
||||||
|
# 找路径公共前缀
|
||||||
|
common_prefix = _common_path_prefix(sample_paths)
|
||||||
|
# 推测数字部分
|
||||||
|
if re.search(r"/\d+", sample_paths[0]):
|
||||||
|
pattern_re = f"^https?://{site_host}{re.escape(common_prefix)}\\d+"
|
||||||
|
else:
|
||||||
|
pattern_re = f"^https?://{site_host}{re.escape(common_prefix)}.*"
|
||||||
|
# 去掉多余的转义
|
||||||
|
pattern_re = pattern_re.replace(r"\d+", r"\d+")
|
||||||
|
results.append({
|
||||||
|
"key": cluster_key,
|
||||||
|
"count": len(links),
|
||||||
|
"regex": pattern_re,
|
||||||
|
"samples": links[:3],
|
||||||
|
})
|
||||||
|
|
||||||
|
# 7. 输出结果
|
||||||
|
for i, r in enumerate(results, 1):
|
||||||
|
stars = "★★★" if i == 1 else ("★★" if i == 2 else "★")
|
||||||
|
print(f"模式 {chr(64+i)} ({r['count']} 条, 推荐 {stars}):")
|
||||||
|
print(f" 正则: {r['regex']}")
|
||||||
|
print(" 样本:")
|
||||||
|
for url, anchor in r["samples"]:
|
||||||
|
print(f" {url}" + (f" [{anchor}]" if anchor else ""))
|
||||||
|
|
||||||
|
# 8. 输出 yaml 建议
|
||||||
|
best = results[0]
|
||||||
|
source_id = parsed_base.netloc.split(".")[0].replace("-", "_")
|
||||||
|
print()
|
||||||
|
print("─" * 60)
|
||||||
|
print("建议 yaml 配置(复制到 configs/sources.yaml):\n")
|
||||||
|
yaml_snippet = f""" - id: {source_id}
|
||||||
|
name: {source_id}
|
||||||
|
enabled: true
|
||||||
|
homepage: {args.url}
|
||||||
|
article_url_pattern: '{best['regex']}'
|
||||||
|
js_render: {str(not getattr(result, 'markdown', '')).lower() if hasattr(result, 'markdown') else 'true'}
|
||||||
|
wait_for: "css:body"
|
||||||
|
page_timeout_ms: 30000
|
||||||
|
max_articles_per_run: 20"""
|
||||||
|
source_name = args.name or source_id
|
||||||
|
yaml_snippet = yaml_snippet.replace(f"name: {source_id}", f"name: {source_name}")
|
||||||
|
|
||||||
|
# 有额外入口时,追加 extra_homepages 字段
|
||||||
|
if args.extra_urls:
|
||||||
|
extra_lines = "\n".join(f" - {u}" for u in args.extra_urls)
|
||||||
|
yaml_snippet += f"\n extra_homepages:\n{extra_lines}"
|
||||||
|
|
||||||
|
print(yaml_snippet)
|
||||||
|
print("─" * 60)
|
||||||
|
|
||||||
|
if args.add:
|
||||||
|
yaml_path = Path("configs/sources.yaml")
|
||||||
|
if yaml_path.is_file():
|
||||||
|
content = yaml_path.read_text(encoding="utf-8")
|
||||||
|
if f"id: {source_id}" in content:
|
||||||
|
print(f"\n⚠️ sources.yaml 中已存在 id={source_id},跳过添加")
|
||||||
|
else:
|
||||||
|
seq = content.count("\n - id:") + 1
|
||||||
|
header = f"\n # ---- {seq}. {source_name} ----"
|
||||||
|
yaml_path.write_text(content.rstrip() + "\n" + header + "\n" + yaml_snippet + "\n", encoding="utf-8")
|
||||||
|
print(f"\n✅ 已追加到 configs/sources.yaml (第 {seq} 个源)")
|
||||||
|
else:
|
||||||
|
print("\n⚠️ configs/sources.yaml 不存在,无法自动添加")
|
||||||
|
|
||||||
|
print(f"\n验证: uv run a-share crawl --source {source_id}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# add-entry — 为已有源追加 extra_homepages
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
def cmd_add_entry(args: argparse.Namespace) -> int:
|
||||||
|
"""给已有源追加 extra_homepages 入口(文本模式,保留 yaml 原有格式)。"""
|
||||||
|
import re
|
||||||
|
|
||||||
|
yaml_path = Path("configs/sources.yaml")
|
||||||
|
if not yaml_path.is_file():
|
||||||
|
print("❌ configs/sources.yaml 不存在")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
content = yaml_path.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
if f"id: {args.source}" not in content:
|
||||||
|
print(f"❌ 未找到源 id={args.source}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# 提取该 source 块中已有的 extra_homepages URL
|
||||||
|
lines = content.splitlines()
|
||||||
|
in_block = False
|
||||||
|
existing: set[str] = set()
|
||||||
|
for line in lines:
|
||||||
|
if f"id: {args.source}" in line and line.strip().startswith("- id:"):
|
||||||
|
in_block = True
|
||||||
|
continue
|
||||||
|
if in_block and (line.strip().startswith("- id:") or line.strip().startswith("settings:")):
|
||||||
|
break
|
||||||
|
m = re.match(r"\s+- (https?://\S+)", line)
|
||||||
|
if m:
|
||||||
|
existing.add(m.group(1))
|
||||||
|
|
||||||
|
added = []
|
||||||
|
seen_this_run: set[str] = set()
|
||||||
|
for url in args.urls:
|
||||||
|
if url in existing or url in seen_this_run:
|
||||||
|
print(f"⏭ 跳过(已存在): {url}")
|
||||||
|
else:
|
||||||
|
seen_this_run.add(url)
|
||||||
|
added.append(url)
|
||||||
|
print(f"✅ 已添加: {url}")
|
||||||
|
|
||||||
|
if not added:
|
||||||
|
print("无新增入口")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# 在 source 块内插入/追加
|
||||||
|
has_extra_header = any("extra_homepages:" in lines[i] for i in range(len(lines)) if in_block_from(lines, i, args.source))
|
||||||
|
|
||||||
|
if has_extra_header:
|
||||||
|
# 找到块内最后一个 extra URL 行,在其后追加
|
||||||
|
insert_at = -1
|
||||||
|
for i in range(len(lines)):
|
||||||
|
if in_block_from(lines, i, args.source) and re.match(r"\s+- https?://", lines[i]) and "extra_homepages:" in "\n".join(lines[max(0, i - 2):i + 1]):
|
||||||
|
insert_at = i
|
||||||
|
if insert_at > 0:
|
||||||
|
indent = " "
|
||||||
|
for u in added:
|
||||||
|
lines.insert(insert_at + 1, f"{indent}- {u}")
|
||||||
|
insert_at += 1
|
||||||
|
else:
|
||||||
|
# 在 max_articles_per_run 行后插入 extra_homepages
|
||||||
|
for i in range(len(lines)):
|
||||||
|
if in_block_from(lines, i, args.source) and "max_articles_per_run:" in lines[i]:
|
||||||
|
indent = " "
|
||||||
|
new_block = [f"{indent}extra_homepages:"]
|
||||||
|
for u in added:
|
||||||
|
new_block.append(f"{indent} - {u}")
|
||||||
|
for j, nl in enumerate(new_block):
|
||||||
|
lines.insert(i + 1 + j, nl)
|
||||||
|
break
|
||||||
|
|
||||||
|
yaml_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||||
|
print(f"\n✅ 已写入 configs/sources.yaml ({len(added)} 个新入口)")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def in_block_from(lines: list[str], idx: int, source_id: str) -> bool:
|
||||||
|
"""检查行 idx 是否在 source_id 的配置块内。"""
|
||||||
|
for i in range(idx, -1, -1):
|
||||||
|
if lines[i].strip().startswith(f"- id: {source_id}"):
|
||||||
|
return True
|
||||||
|
if lines[i].strip().startswith("- id:") or lines[i].strip().startswith("settings:"):
|
||||||
|
return False
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# watchlist 管理
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
_WATCHLIST_PATH = Path("configs/watchlist.yaml")
|
||||||
|
|
||||||
|
|
||||||
|
def _load_watchlist() -> list[dict]:
|
||||||
|
import yaml
|
||||||
|
try:
|
||||||
|
with _WATCHLIST_PATH.open(encoding="utf-8") as f:
|
||||||
|
data = yaml.safe_load(f) or {}
|
||||||
|
return list(data.get("watchlist") or [])
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def _save_watchlist(items: list[dict]) -> None:
|
||||||
|
import yaml
|
||||||
|
_WATCHLIST_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with _WATCHLIST_PATH.open("w", encoding="utf-8") as f:
|
||||||
|
yaml.dump({"watchlist": items}, f, allow_unicode=True, default_flow_style=False, sort_keys=False)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_watchlist_add(args: argparse.Namespace) -> int:
|
||||||
|
items = _load_watchlist()
|
||||||
|
code = args.code.strip()
|
||||||
|
for it in items:
|
||||||
|
if it.get("code") == code:
|
||||||
|
it["name"] = args.name
|
||||||
|
if args.note:
|
||||||
|
it["note"] = args.note
|
||||||
|
_save_watchlist(items)
|
||||||
|
print(f"✅ 已更新: {code} {args.name}")
|
||||||
|
return 0
|
||||||
|
items.append({"code": code, "name": args.name, "note": args.note or ""})
|
||||||
|
_save_watchlist(items)
|
||||||
|
print(f"✅ 已添加: {code} {args.name}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_watchlist_remove(args: argparse.Namespace) -> int:
|
||||||
|
items = _load_watchlist()
|
||||||
|
code = args.code.strip()
|
||||||
|
new_items = [it for it in items if it.get("code") != code]
|
||||||
|
if len(new_items) == len(items):
|
||||||
|
print(f"⚠️ 未找到: {code}")
|
||||||
|
return 1
|
||||||
|
_save_watchlist(new_items)
|
||||||
|
print(f"✅ 已移除: {code}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_watchlist_list(args: argparse.Namespace) -> int: # noqa: ARG001
|
||||||
|
items = _load_watchlist()
|
||||||
|
if not items:
|
||||||
|
print("📋 关注列表为空")
|
||||||
|
print("添加: uv run a-share watchlist add 000001 平安银行")
|
||||||
|
return 0
|
||||||
|
print(f"📋 关注列表 ({len(items)} 家):")
|
||||||
|
for it in items:
|
||||||
|
note = f" — {it.get('note','')}" if it.get("note") else ""
|
||||||
|
print(f" {it['code']} {it['name']}{note}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_stock_report(args: argparse.Namespace) -> int:
|
||||||
|
"""生成所有关注公司个股日报。"""
|
||||||
|
from scheduler.stock_reporter import generate_all_stock_reports
|
||||||
|
n = generate_all_stock_reports(upload=not args.no_upload)
|
||||||
|
print(f"✅ 个股报告完成: {n} 家")
|
||||||
|
return 0 if n > 0 else 1
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_cninfo(args: argparse.Namespace) -> int:
|
||||||
|
"""cninfo watchlist 抓取(公告+调研+互动易,API 优先)。"""
|
||||||
|
from crawler.cninfo import crawl_watchlist, enrich_articles_with_pdf
|
||||||
|
|
||||||
|
if args.enrich_pdf:
|
||||||
|
n = enrich_articles_with_pdf(day_str=None, limit=args.pdf_limit)
|
||||||
|
print(f"✅ PDF 正文提取完成: {n} 篇")
|
||||||
|
return 0 if n > 0 else 1
|
||||||
|
|
||||||
|
# 默认: watchlist 全类型抓取(公告+调研+互动易)
|
||||||
|
results = crawl_watchlist(save=not args.no_save)
|
||||||
|
|
||||||
|
if not results:
|
||||||
|
print("⚠️ cninfo 抓取: 无数据")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# 按类型统计
|
||||||
|
ann = sum(1 for it in results if getattr(it, "item_type", "") == "announcement")
|
||||||
|
res = sum(1 for it in results if getattr(it, "item_type", "") == "research")
|
||||||
|
irm = sum(1 for it in results if getattr(it, "item_type", "") == "irm")
|
||||||
|
print(f"✅ cninfo 抓取完成: 公告 {ann} / 调研 {res} / 互动易 {irm} (共 {len(results)} 条)")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def _common_path_prefix(paths: list[str]) -> str:
|
||||||
|
"""找路径列表的公共前缀(逐字符)。"""
|
||||||
|
if not paths:
|
||||||
|
return "/"
|
||||||
|
shortest = min(paths, key=len)
|
||||||
|
for i, ch in enumerate(shortest):
|
||||||
|
if any(p[i:i+1] != ch for p in paths):
|
||||||
|
prefix = shortest[:i]
|
||||||
|
# 截断到最后一个 /
|
||||||
|
last_slash = prefix.rfind("/")
|
||||||
|
return prefix[:last_slash + 1] if last_slash >= 0 else "/"
|
||||||
|
return shortest
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# 主入口
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
load_dotenv()
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog="a-share",
|
||||||
|
description="A 股 Deep Research 统一 CLI",
|
||||||
|
)
|
||||||
|
sub = parser.add_subparsers(dest="command", help="子命令")
|
||||||
|
|
||||||
|
# ---- crawl ----
|
||||||
|
p = sub.add_parser("crawl", help="M1 新闻抓取")
|
||||||
|
p.add_argument("--source", default=None)
|
||||||
|
p.add_argument("--no-save", action="store_true")
|
||||||
|
p.set_defaults(func=cmd_crawl)
|
||||||
|
|
||||||
|
# ---- extract ----
|
||||||
|
p = sub.add_parser("extract", help="M2 正文提取")
|
||||||
|
p.add_argument("--date", default=None)
|
||||||
|
p.add_argument("--source", default=None)
|
||||||
|
p.set_defaults(func=cmd_extract)
|
||||||
|
|
||||||
|
# ---- dedup ----
|
||||||
|
p = sub.add_parser("dedup", help="M3 三层去重")
|
||||||
|
p.add_argument("--date", default=None)
|
||||||
|
p.add_argument("--reset", action="store_true")
|
||||||
|
p.set_defaults(func=cmd_dedup)
|
||||||
|
|
||||||
|
# ---- events ----
|
||||||
|
p = sub.add_parser("events", help="M4 LLM 事件抽取")
|
||||||
|
p.add_argument("--date", default=None)
|
||||||
|
p.add_argument("--provider", default=None)
|
||||||
|
p.add_argument("--model", default=None)
|
||||||
|
p.add_argument("--limit", type=int, default=0)
|
||||||
|
p.add_argument("--concurrency", type=int, default=0)
|
||||||
|
p.set_defaults(func=cmd_events)
|
||||||
|
|
||||||
|
# ---- embed ----
|
||||||
|
p = sub.add_parser("embed", help="M5 向量化")
|
||||||
|
p.add_argument("--date", default=None)
|
||||||
|
p.add_argument("--provider", default=None)
|
||||||
|
p.add_argument("--model", default=None)
|
||||||
|
p.set_defaults(func=cmd_embed)
|
||||||
|
|
||||||
|
# ---- ingest ----
|
||||||
|
p = sub.add_parser("ingest", help="M6 Qdrant 入库")
|
||||||
|
p.add_argument("--date", default=None)
|
||||||
|
p.add_argument("--recreate", action="store_true")
|
||||||
|
p.set_defaults(func=cmd_ingest)
|
||||||
|
|
||||||
|
# ---- pipeline ----
|
||||||
|
p = sub.add_parser("pipeline", help="M7 全链路 / 定时守护")
|
||||||
|
p.add_argument("--once", action="store_true", help="立即执行一次")
|
||||||
|
p.add_argument("--date", default=None)
|
||||||
|
p.add_argument("--steps", default=None, help="指定步骤(crawler,extractor,...,report)")
|
||||||
|
p.add_argument("--report", action="store_true", help="全链路末尾生成日报")
|
||||||
|
p.add_argument("--cninfo-once", action="store_true", help="cninfo watchlist 全链路(公告+调研+IRM)")
|
||||||
|
p.set_defaults(func=cmd_pipeline)
|
||||||
|
|
||||||
|
# ---- search ----
|
||||||
|
p = sub.add_parser("search", help="检索知识库")
|
||||||
|
p.add_argument("query", help="自然语言查询")
|
||||||
|
p.add_argument("--top", type=int, default=10)
|
||||||
|
p.add_argument("--source", default=None)
|
||||||
|
p.add_argument("--sentiment", default=None, choices=["positive", "negative", "neutral"])
|
||||||
|
p.add_argument("--min-importance", type=int, default=None, dest="min_importance")
|
||||||
|
p.add_argument("--stock", default=None, help="6 位股票代码")
|
||||||
|
p.add_argument("--industry", default=None, help="行业名")
|
||||||
|
p.set_defaults(func=cmd_search)
|
||||||
|
|
||||||
|
# ---- add-entry ----
|
||||||
|
p = sub.add_parser("add-entry", help="为已有源追加 extra_homepages 入口")
|
||||||
|
p.add_argument("source", help="已有源 id(如 wallstreetcn)")
|
||||||
|
p.add_argument("urls", nargs="+", help="额外入口 URL(可多个)")
|
||||||
|
p.set_defaults(func=cmd_add_entry)
|
||||||
|
|
||||||
|
# ---- discover ----
|
||||||
|
p = sub.add_parser("discover", help="分析站点首页,推荐 sources.yaml 配置")
|
||||||
|
p.add_argument("url", help="站点首页 URL(主入口)")
|
||||||
|
p.add_argument("--extra", action="append", default=[], dest="extra_urls",
|
||||||
|
help="额外入口 URL,可重复使用(同站多频道)")
|
||||||
|
p.add_argument("--add", action="store_true", help="自动追加到 configs/sources.yaml")
|
||||||
|
p.add_argument("--name", default=None, help="站点中文名(如 华尔街见闻)")
|
||||||
|
p.set_defaults(func=cmd_discover)
|
||||||
|
|
||||||
|
# ---- report ----
|
||||||
|
p = sub.add_parser("report", help="生成每日 HTML 报告并上传")
|
||||||
|
p.add_argument("--date", default=None)
|
||||||
|
p.add_argument("--no-upload", action="store_true", help="仅生成不上传")
|
||||||
|
p.set_defaults(func=cmd_report)
|
||||||
|
|
||||||
|
# ---- watchlist ----
|
||||||
|
p = sub.add_parser("watchlist", help="管理 cninfo 公告关注列表")
|
||||||
|
sp = p.add_subparsers(dest="wl_cmd")
|
||||||
|
pa = sp.add_parser("add", help="添加关注公司")
|
||||||
|
pa.add_argument("code", help="6 位股票代码")
|
||||||
|
pa.add_argument("name", help="公司简称")
|
||||||
|
pa.add_argument("--note", default="", help="备注")
|
||||||
|
pa.set_defaults(func=cmd_watchlist_add)
|
||||||
|
pr = sp.add_parser("remove", help="移除关注公司")
|
||||||
|
pr.add_argument("code", help="6 位股票代码")
|
||||||
|
pr.set_defaults(func=cmd_watchlist_remove)
|
||||||
|
pl = sp.add_parser("list", help="查看关注列表")
|
||||||
|
pl.set_defaults(func=cmd_watchlist_list)
|
||||||
|
|
||||||
|
# ---- stock-report ----
|
||||||
|
p = sub.add_parser("stock-report", help="生成关注列表个股日报")
|
||||||
|
p.add_argument("--no-upload", action="store_true", help="仅生成不上传")
|
||||||
|
p.set_defaults(func=cmd_stock_report)
|
||||||
|
|
||||||
|
# ---- cninfo ----
|
||||||
|
p = sub.add_parser("cninfo", help="cninfo watchlist 抓取(公告+调研+互动易, API 优先)")
|
||||||
|
p.add_argument("--no-save", action="store_true")
|
||||||
|
p.add_argument("--enrich-pdf", action="store_true", help="下载 PDF 补充正文")
|
||||||
|
p.add_argument("--pdf-limit", type=int, default=50, help="PDF 最多处理 N 篇")
|
||||||
|
p.set_defaults(func=cmd_cninfo)
|
||||||
|
|
||||||
|
# ---- status ----
|
||||||
|
p = sub.add_parser("status", help="数据总览")
|
||||||
|
p.set_defaults(func=cmd_status)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
if args.command is None:
|
||||||
|
parser.print_help()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# 记录操作日志
|
||||||
|
cmd_name = args.command
|
||||||
|
# 提取关键参数作为日志详情
|
||||||
|
detail_parts = []
|
||||||
|
for attr in ["source", "date", "query", "days", "stock", "industry",
|
||||||
|
"sentiment", "once", "cninfo_once", "report"]:
|
||||||
|
val = getattr(args, attr, None)
|
||||||
|
if val is not None and val is not False and val != "" and val != 0:
|
||||||
|
detail_parts.append(f"{attr}={val}")
|
||||||
|
detail = " ".join(detail_parts) if detail_parts else ""
|
||||||
|
|
||||||
|
from time import perf_counter
|
||||||
|
_log_operation_start(cmd_name, detail)
|
||||||
|
started = perf_counter()
|
||||||
|
rc = args.func(args)
|
||||||
|
elapsed = perf_counter() - started
|
||||||
|
_log_operation_end(cmd_name, rc == 0, elapsed)
|
||||||
|
return rc
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,218 @@
|
|||||||
|
# A 股新闻源配置(M1)
|
||||||
|
# ============================================================
|
||||||
|
# 文档:
|
||||||
|
# - id: 短标识,用作目录名(小写字母/数字/下划线)
|
||||||
|
# - name: 中文名,用于日志与展示
|
||||||
|
# - homepage: 列表/首页 URL,抓取入口
|
||||||
|
# - article_url_pattern: 正则,从首页所有链接中筛选文章 URL
|
||||||
|
# - article_link_selector: 可选 CSS,优先在该选择器内查链接(留空=全页)
|
||||||
|
# - js_render: 是否需要 JS 渲染(动态站点必须 true)
|
||||||
|
# - wait_for: 可选 CSS,等待元素出现(JS 渲染时建议设置)
|
||||||
|
# - max_articles_per_run: 单次抓取上限,M1 阶段建议 ≤ 30
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
settings:
|
||||||
|
concurrency: 3 # 并发抓取上限(用户决策:3)
|
||||||
|
retry_max_attempts: 3
|
||||||
|
retry_min_wait_sec: 1.0
|
||||||
|
retry_max_wait_sec: 10.0
|
||||||
|
headless: true
|
||||||
|
output_root: data/raw
|
||||||
|
|
||||||
|
sources:
|
||||||
|
# ---- 1. 财联社(深度频道)----
|
||||||
|
- id: cls
|
||||||
|
name: 财联社
|
||||||
|
enabled: true
|
||||||
|
homepage: https://www.cls.cn/depth?id=1000
|
||||||
|
article_url_pattern: '^https?://www\.cls\.cn/detail/\d+$'
|
||||||
|
article_link_selector: null
|
||||||
|
js_render: true
|
||||||
|
wait_for: "css:body"
|
||||||
|
page_timeout_ms: 30000
|
||||||
|
max_articles_per_run: 20
|
||||||
|
extra_homepages:
|
||||||
|
- https://www.cls.cn/depth?id=1003
|
||||||
|
- https://www.cls.cn/depth?id=1007
|
||||||
|
- https://www.cls.cn/depth?id=1035
|
||||||
|
- https://www.cls.cn/depth?id=1032
|
||||||
|
- https://www.cls.cn/depth?id=1005
|
||||||
|
|
||||||
|
# ---- 2. 东方财富(财经频道)----
|
||||||
|
- id: eastmoney
|
||||||
|
name: 东方财富
|
||||||
|
enabled: true
|
||||||
|
homepage: https://finance.eastmoney.com/
|
||||||
|
article_url_pattern: '^https?://finance\.eastmoney\.com/a/\d+\.html$'
|
||||||
|
article_link_selector: null
|
||||||
|
js_render: true
|
||||||
|
wait_for: "css:body"
|
||||||
|
page_timeout_ms: 30000
|
||||||
|
max_articles_per_run: 20
|
||||||
|
|
||||||
|
# ---- 3. 新浪财经 ----
|
||||||
|
- id: sina
|
||||||
|
name: 新浪财经
|
||||||
|
enabled: true
|
||||||
|
homepage: https://finance.sina.com.cn/
|
||||||
|
# 新浪文章 URL 形如 https://finance.sina.com.cn/stock/.../doc-xxx.shtml
|
||||||
|
article_url_pattern: '^https?://(finance|cj)\.sina\.com\.cn/.+/doc-[a-z0-9]+\.s?html$'
|
||||||
|
article_link_selector: null
|
||||||
|
js_render: false
|
||||||
|
wait_for: null
|
||||||
|
page_timeout_ms: 20000
|
||||||
|
max_articles_per_run: 20
|
||||||
|
extra_homepages:
|
||||||
|
- https://finance.sina.com.cn/stock/newstock/
|
||||||
|
- https://finance.sina.com.cn/stock/
|
||||||
|
- https://finance.sina.com.cn/forex/
|
||||||
|
- https://finance.sina.com.cn/stock/hkstock/
|
||||||
|
- https://finance.sina.com.cn/stock/usstock/
|
||||||
|
|
||||||
|
# ---- 4. 证券时报 ----
|
||||||
|
- id: stcn
|
||||||
|
name: 证券时报
|
||||||
|
enabled: true
|
||||||
|
homepage: https://www.stcn.com/article/list/yw.html
|
||||||
|
article_url_pattern: '^https?://www\.stcn\.com/article/detail/\d+\.html$'
|
||||||
|
article_link_selector: null
|
||||||
|
js_render: true
|
||||||
|
wait_for: "css:body"
|
||||||
|
page_timeout_ms: 30000
|
||||||
|
max_articles_per_run: 20
|
||||||
|
extra_homepages:
|
||||||
|
- https://www.stcn.com/article/list/xw.html
|
||||||
|
- https://www.stcn.com/article/list/finance.html
|
||||||
|
- https://www.stcn.com/article/list/gd.html
|
||||||
|
|
||||||
|
# ---- 5. 第一财经 ----
|
||||||
|
- id: yicai
|
||||||
|
name: 第一财经
|
||||||
|
enabled: true
|
||||||
|
homepage: https://www.yicai.com/news/
|
||||||
|
article_url_pattern: '^https?://www\.yicai\.com/(news|brief)/\d+\.html$'
|
||||||
|
article_link_selector: null
|
||||||
|
js_render: true
|
||||||
|
wait_for: "css:body"
|
||||||
|
page_timeout_ms: 30000
|
||||||
|
max_articles_per_run: 20
|
||||||
|
extra_homepages:
|
||||||
|
- https://www.yicai.com/brief/
|
||||||
|
|
||||||
|
# ---- 6. 华尔街见闻 ----
|
||||||
|
- id: wallstreetcn
|
||||||
|
name: 华尔街见闻
|
||||||
|
enabled: true
|
||||||
|
# 注意: 该站为 React SPA,js_render 触发反爬拦截(Crawl4AI 内部错误),
|
||||||
|
# 非 JS 模式下 Crawl4AI 可抓取首页链接但文章正文提取为空。
|
||||||
|
# TODO: 研究通过 wallstreetcn API 或 RSS 获取全文
|
||||||
|
homepage: https://wallstreetcn.com/news/global
|
||||||
|
article_url_pattern: '^https?://wallstreetcn\.com/(articles|livenews)/\d+'
|
||||||
|
js_render: false
|
||||||
|
wait_for: null
|
||||||
|
page_timeout_ms: 30000
|
||||||
|
max_articles_per_run: 20
|
||||||
|
extra_homepages:
|
||||||
|
- https://wallstreetcn.com/live/global
|
||||||
|
|
||||||
|
# ---- 7. 新华网 ----
|
||||||
|
- id: newscn
|
||||||
|
name: 新华网
|
||||||
|
enabled: true
|
||||||
|
homepage: https://www.news.cn/finance/
|
||||||
|
article_url_pattern: '^https?://www\.news\.cn/finance/\d+'
|
||||||
|
js_render: false
|
||||||
|
wait_for: "css:body"
|
||||||
|
page_timeout_ms: 30000
|
||||||
|
max_articles_per_run: 20
|
||||||
|
extra_homepages:
|
||||||
|
- https://www.news.cn/money/yw/index.html
|
||||||
|
|
||||||
|
# ---- 8. 中国经济网 ----
|
||||||
|
- id: cecn
|
||||||
|
name: 中国经济网
|
||||||
|
enabled: true
|
||||||
|
homepage: http://finance.ce.cn/
|
||||||
|
article_url_pattern: '^https?://[a-z0-9]+\.ce\.cn/.+/t\d{8}_\d+\.shtml$'
|
||||||
|
js_render: false
|
||||||
|
wait_for: "css:body"
|
||||||
|
page_timeout_ms: 30000
|
||||||
|
max_articles_per_run: 20
|
||||||
|
extra_homepages:
|
||||||
|
- http://finance.ce.cn/rolling/index.shtml
|
||||||
|
|
||||||
|
# ---- 9. 中新经纬 ----
|
||||||
|
- id: jwview
|
||||||
|
name: 中新经纬
|
||||||
|
enabled: true
|
||||||
|
homepage: https://www.jwview.com/jr.html
|
||||||
|
article_url_pattern: '^https?://www\.jwview\.com/jingwei/html/\d{2}-\d{2}/\d+'
|
||||||
|
js_render: false
|
||||||
|
wait_for: "css:body"
|
||||||
|
page_timeout_ms: 30000
|
||||||
|
max_articles_per_run: 20
|
||||||
|
extra_homepages:
|
||||||
|
- https://www.jwview.com/kb.html
|
||||||
|
|
||||||
|
# ---- 10. 中国证券网 ----
|
||||||
|
- id: cnstock
|
||||||
|
name: 中国证券网
|
||||||
|
enabled: true
|
||||||
|
homepage: https://www.cnstock.com/channel/10011
|
||||||
|
article_url_pattern: '^https?://www\.cnstock\.com/commonDetail/\d+'
|
||||||
|
js_render: false
|
||||||
|
wait_for: "css:body"
|
||||||
|
page_timeout_ms: 30000
|
||||||
|
max_articles_per_run: 20
|
||||||
|
extra_homepages:
|
||||||
|
- https://www.cnstock.com/fastNews/10004
|
||||||
|
|
||||||
|
# ---- 11. 中证券网 ----
|
||||||
|
- id: cscn
|
||||||
|
name: 中证券网
|
||||||
|
enabled: true
|
||||||
|
homepage: https://www.cs.com.cn/yaowen.html
|
||||||
|
article_url_pattern: '^https?://www\.cs\.com\.cn/xwzx/\d{2}/\d{4}/\d{2}/\d+'
|
||||||
|
js_render: false
|
||||||
|
wait_for: "css:body"
|
||||||
|
page_timeout_ms: 30000
|
||||||
|
max_articles_per_run: 20
|
||||||
|
extra_homepages:
|
||||||
|
- https://www.cs.com.cn/gongsi.html
|
||||||
|
|
||||||
|
# ---- 12. 证券日报 ----
|
||||||
|
- id: zqrb
|
||||||
|
name: 证券日报
|
||||||
|
enabled: true
|
||||||
|
homepage: http://www.zqrb.cn/finance/index.html
|
||||||
|
# zqrb URL 包含日期: /gscy/gongsi/2026-06-17/A1781692801973.html
|
||||||
|
# 限制只匹配 2025 年后的文章,避免抓取到 2022 年旧闻
|
||||||
|
article_url_pattern: '^https?://www\.zqrb\.cn/(?:gscy/gongsi|finance)/(?:202[5-9]|203\d)-\d{2}-\d{2}/'
|
||||||
|
js_render: false
|
||||||
|
wait_for: "css:body"
|
||||||
|
page_timeout_ms: 30000
|
||||||
|
max_articles_per_run: 20
|
||||||
|
extra_homepages:
|
||||||
|
- http://www.zqrb.cn/finance/hongguanjingji/index.html
|
||||||
|
- http://www.zqrb.cn/finance/guojijingji/index.html
|
||||||
|
|
||||||
|
# ---- 13. 经济观察网 ----
|
||||||
|
- id: eeo
|
||||||
|
name: 经济观察网
|
||||||
|
enabled: true
|
||||||
|
homepage: https://www.eeo.com.cn/
|
||||||
|
article_url_pattern: '^https?://www\.eeo\.com\.cn/\d{4}/\d{4}/\d+\.shtml$'
|
||||||
|
js_render: false
|
||||||
|
page_timeout_ms: 60000
|
||||||
|
max_articles_per_run: 20
|
||||||
|
extra_homepages:
|
||||||
|
- https://www.eeo.com.cn/jg/kuaixun/
|
||||||
|
|
||||||
|
# ---- 14. 新闻联播 (API, 不走 Playwright) ----
|
||||||
|
- id: xwlb
|
||||||
|
name: 新闻联播
|
||||||
|
enabled: true
|
||||||
|
homepage: https://api.doorcome.cn/api/xwlbFine/ # dummy, 满足模型校验
|
||||||
|
article_url_pattern: '^xwlb://' # dummy, 不会被 crawler 处理
|
||||||
|
js_render: false
|
||||||
|
max_articles_per_run: 25
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
# cninfo 公告关注列表
|
||||||
|
# code: 6 位股票代码
|
||||||
|
# name: 公司简称
|
||||||
|
# note: 可选备注
|
||||||
|
# orgId: cninfo 机构 ID(从 cninfo 搜索框输入代码后 URL 中获取)
|
||||||
|
watchlist:
|
||||||
|
- code: "000792"
|
||||||
|
name: "盐湖股份"
|
||||||
|
note: "盐湖提锂"
|
||||||
|
orgId: "gssz0000792"
|
||||||
|
|
||||||
|
- code: "300316"
|
||||||
|
name: "晶盛机电"
|
||||||
|
note: ""
|
||||||
|
orgId: "9900022122"
|
||||||
|
|
||||||
|
- code: "688556"
|
||||||
|
name: "高测股份"
|
||||||
|
note: ""
|
||||||
|
orgId: "gfbj0834278"
|
||||||
|
|
||||||
|
- code: "688005"
|
||||||
|
name: "容百科技"
|
||||||
|
note: ""
|
||||||
|
orgId: "9900038937"
|
||||||
|
|
||||||
|
- code: "300124"
|
||||||
|
name: "汇川技术"
|
||||||
|
note: ""
|
||||||
|
orgId: "9900012527"
|
||||||
|
|
||||||
|
- code: "002241"
|
||||||
|
name: "歌尔股份"
|
||||||
|
note: ""
|
||||||
|
orgId: "9900004688"
|
||||||
|
|
||||||
|
- code: "600900"
|
||||||
|
name: "长江电力"
|
||||||
|
note: ""
|
||||||
|
orgId: "gssh0600900"
|
||||||
|
|
||||||
|
- code: "601126"
|
||||||
|
name: "四方股份"
|
||||||
|
note: ""
|
||||||
|
orgId: "9900016847"
|
||||||
|
|
||||||
|
- code: "600089"
|
||||||
|
name: "特变电工"
|
||||||
|
note: ""
|
||||||
|
orgId: "gssh0600089"
|
||||||
|
|
||||||
|
- code: "601179"
|
||||||
|
name: "中国西电"
|
||||||
|
note: ""
|
||||||
|
orgId: "9900010350"
|
||||||
|
|
||||||
|
- code: "688676"
|
||||||
|
name: "金盘科技"
|
||||||
|
note: ""
|
||||||
|
orgId: "9900044215"
|
||||||
|
|
||||||
|
- code: "688469"
|
||||||
|
name: "芯联集成"
|
||||||
|
note: ""
|
||||||
|
orgId: "9900053045"
|
||||||
|
|
||||||
|
- code: "002129"
|
||||||
|
name: "TCL中环"
|
||||||
|
note: ""
|
||||||
|
orgId: "9900002703"
|
||||||
|
|
||||||
|
- code: "600460"
|
||||||
|
name: "士兰微"
|
||||||
|
note: ""
|
||||||
|
orgId: "gssh0600460"
|
||||||
|
|
||||||
|
- code: "601668"
|
||||||
|
name: "中国建筑"
|
||||||
|
note: ""
|
||||||
|
orgId: "9900005970"
|
||||||
+227
@@ -0,0 +1,227 @@
|
|||||||
|
# continuation.md
|
||||||
|
|
||||||
|
> `checkpoint` @ 2026-07-17 07:51
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 当前状态
|
||||||
|
|
||||||
|
| 项目 | 值 |
|
||||||
|
| --- | --- |
|
||||||
|
| 新闻源 | 14 个(13 Web + 1 API: xwlb 新闻联播) |
|
||||||
|
| Qdrant | 本地文件模式 `data/qdrant_storage/` |
|
||||||
|
| 日报 | 5 板块: AI摘要 / 新闻联播 / 财经新闻 / 公告调研 / 数据总览 |
|
||||||
|
| 调度器 | APScheduler,systemd `a-share-research.service` |
|
||||||
|
| LLM | `deepseek-v4-flash`(绝不允许擅自修改) |
|
||||||
|
| 服务器 | `pi@192.168.1.160`,项目 `/home/pi/news/` |
|
||||||
|
| 抓取方式 | js_render=false → httpx 直连;js_render=true → Playwright |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 本次完成 (2026-07-17) — 禁用个股日报
|
||||||
|
|
||||||
|
**操作:** `STOCK_REPORT_TIME=` 设为空,`run_scheduler.py` 加空值守卫。
|
||||||
|
|
||||||
|
**文件:**
|
||||||
|
- `scripts/run_scheduler.py` L127-141:`STOCK_REPORT_TIME` 为空时跳过注册并输出 `已禁用个股日报`
|
||||||
|
- Pi `.env`:`STOCK_REPORT_TIME=08:00` → `STOCK_REPORT_TIME=`
|
||||||
|
|
||||||
|
**恢复方法:** 改回 `STOCK_REPORT_TIME=08:00`,重启 `a-share-research`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 本次完成 (2026-07-13) — eeo 反爬修复 + 静态直连 + 超时保护
|
||||||
|
|
||||||
|
### 问题
|
||||||
|
|
||||||
|
eeo(经济观察网)在 Pi 上用 Playwright 连续抓取后触发反爬,服务器故意不响应导致 `page.goto()` 超时(30s → 60s 均超时)。而在本机 Mac 上一切正常——反爬是 **IP 级别**的,Pi 的 IP 已被限流。
|
||||||
|
|
||||||
|
### 根因
|
||||||
|
|
||||||
|
eeo 是纯静态页面(`js_render: false`),但 Crawl4AI 始终走 Playwright。eeo 检测到 headless Chrome 的连续请求模式后,对后续请求挂起 TCP 连接直至超时。
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
|
||||||
|
**`crawler/engine.py` — 三个改动:**
|
||||||
|
|
||||||
|
1. **新增 `_fetch_static()`**:`js_render=False` 且无 `wait_for` 的源用 `httpx` 直连,绕过 Playwright 反爬。HTML → Markdown 用 `markdownify` + BeautifulSoup 清洗。
|
||||||
|
|
||||||
|
2. **新增 `_TimeoutGuard`**:同源连续超时 2 次后自动跳过剩余请求,防止整个 pipeline 被单一故障源拖死。
|
||||||
|
|
||||||
|
3. **`_crawl_once` 分流逻辑**:
|
||||||
|
```
|
||||||
|
js_render=False 且无 wait_for → _fetch_static() (httpx)
|
||||||
|
其他 → Playwright
|
||||||
|
```
|
||||||
|
|
||||||
|
**`configs/sources.yaml` — eeo 源两处调整:**
|
||||||
|
- 删除 `wait_for: "css:body"`(静态页面无需等待 CSS 选择器)
|
||||||
|
- `page_timeout_ms: 30000 → 60000`(恢复 Crawl4AI 默认值)
|
||||||
|
|
||||||
|
### 测试结果
|
||||||
|
|
||||||
|
| 环境 | Playwright (修复前) | httpx (修复后) |
|
||||||
|
|------|---------------------|-----------------|
|
||||||
|
| Mac 本地 | 全部通过 | 36 篇 / 7 秒 |
|
||||||
|
| Pi 服务器 | **前 3 个请求全部 15s 超时** | 36 篇 / 9 秒,0 失败 |
|
||||||
|
|
||||||
|
### 验证方法
|
||||||
|
|
||||||
|
Pi 上本地复现了反爬(Playwright 前 3 个 URL 全部超时),同批 URL 用 httpx 全部瞬间返回——证明方案有效。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 历史 (2026-06-24)
|
||||||
|
|
||||||
|
### 1. 环境变量全面审计与修复
|
||||||
|
|
||||||
|
**触发:** 服务器 `dedup` 步骤超时 120s。
|
||||||
|
|
||||||
|
**修复 6 个文件:**
|
||||||
|
|
||||||
|
| # | 文件 | 修改 |
|
||||||
|
|---|------|------|
|
||||||
|
| 1 | `scheduler/pipeline.py` | 超时解析重写:`TIMEOUT_{NAME}` > `PIPELINE_STEP_TIMEOUT` > 硬编码 > 1800s |
|
||||||
|
| 2 | `a_share_cli/main.py` | 新增 `_resolve_timeout()`,CLI 命令超时改为读环境变量 |
|
||||||
|
| 3 | `crawler/cninfo.py` | `CNINFO_PDF_BASE` 从硬编码改为 `os.environ.get()` |
|
||||||
|
| 4 | `scheduler/reporter.py` | `STOCK_REPORT_DAYS` 默认值 7→15;`max_tokens` 600→1500 |
|
||||||
|
| 5 | `.env` | 清除 10 个死配置;`EMBEDDING_MODEL`→`LOCAL_EMBEDDING_MODEL`;补全遗漏参数 |
|
||||||
|
| 6 | `.env.example` | 重写为仅包含实际生效配置 |
|
||||||
|
|
||||||
|
**审计结果:**
|
||||||
|
|
||||||
|
| 类别 | 数量 | 处理 |
|
||||||
|
|------|------|------|
|
||||||
|
| ✅ 正常工作 | 23 | — |
|
||||||
|
| ❌ 死配置 | 10 | 已清除 |
|
||||||
|
| ⚠️ 默认值不一致 | 1 | 已统一 |
|
||||||
|
| 🔧 硬编码 | 1 | 已修复 |
|
||||||
|
|
||||||
|
### 2. AI 摘要截断修复
|
||||||
|
|
||||||
|
**触发:** 日报 AI 摘要内容极少(137 字),末尾被截断。
|
||||||
|
|
||||||
|
**根因:** `_llm_call()` `max_tokens=600` 太低,deepseek-v4-flash 输出被硬截断。
|
||||||
|
|
||||||
|
**修复:** `max_tokens` 三处提升 + 截断检测日志。
|
||||||
|
|
||||||
|
| 位置 | 修复前 | 修复后 |
|
||||||
|
|------|--------|--------|
|
||||||
|
| `_llm_call()` 默认 | 600 | 1500 |
|
||||||
|
| 分块摘要 | 400 | 800 |
|
||||||
|
| 合并摘要 | 600 | 1500 |
|
||||||
|
|
||||||
|
**验证:** 手动生成日报,摘要从 137 字 → 486 字,结构完整无截断。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 超时优先级(最终设计)
|
||||||
|
|
||||||
|
```
|
||||||
|
TIMEOUT_{NAME} 环境变量 ← 单步精确控制
|
||||||
|
↓ 未设
|
||||||
|
PIPELINE_STEP_TIMEOUT 环境变量 ← 全局兜底 (覆盖所有硬编码)
|
||||||
|
↓ 未设
|
||||||
|
STEP_TIMEOUTS 硬编码字典 ← 代码内默认值
|
||||||
|
↓ 步骤不在字典中
|
||||||
|
1800s ← 最终兜底
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 服务器 .env 当前配置
|
||||||
|
|
||||||
|
```env
|
||||||
|
# ---- 调度 ----
|
||||||
|
SCHEDULE_TIMES=07:00,12:00,18:00,22:00
|
||||||
|
CNINFO_SCHEDULE_TIME=06:00
|
||||||
|
STOCK_REPORT_TIME=08:00
|
||||||
|
STOCK_REPORT_DAYS=15
|
||||||
|
|
||||||
|
# ---- Pipeline 超时 ----
|
||||||
|
PIPELINE_STEP_TIMEOUT=3600
|
||||||
|
TIMEOUT_CRAWLER=900
|
||||||
|
TIMEOUT_XWLB=60
|
||||||
|
TIMEOUT_EXTRACTOR=300
|
||||||
|
TIMEOUT_DEDUP=900
|
||||||
|
TIMEOUT_LLM=900
|
||||||
|
TIMEOUT_EMBEDDING=900
|
||||||
|
TIMEOUT_QDRANT=1800
|
||||||
|
TIMEOUT_CNINFO_CRAWL=900
|
||||||
|
TIMEOUT_CNINFO_EXTRACT=900
|
||||||
|
TIMEOUT_CNINFO_PDF=900
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 调度器
|
||||||
|
|
||||||
|
| 时间 | 步骤 |
|
||||||
|
|------|------|
|
||||||
|
| 06:00 | cninfo 公告管道 |
|
||||||
|
| 07:00 | crawler→xwlb→extractor→dedup→llm→embedding→qdrant→**日报** |
|
||||||
|
| 08:00 | 个股日报 |
|
||||||
|
| 12:00 | crawler→xwlb→extractor→dedup→llm→embedding→qdrant |
|
||||||
|
| 18:00 | crawler→xwlb→extractor→dedup→llm→embedding→qdrant |
|
||||||
|
| 22:00 | crawler→xwlb→extractor→dedup→llm→embedding→qdrant |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 常用命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 全链路 + 日报
|
||||||
|
uv run a-share pipeline --once --report
|
||||||
|
|
||||||
|
# 仅日报 (指定日期)
|
||||||
|
/home/pi/.local/bin/uv run python -m scripts.run_scheduler --once --date 20260623 --steps report
|
||||||
|
|
||||||
|
# 检索
|
||||||
|
uv run a-share search "关键词" --top 5
|
||||||
|
|
||||||
|
# 同步到服务器
|
||||||
|
scp <file> pi@192.168.1.160:/home/pi/news/<path>/
|
||||||
|
|
||||||
|
# 重启服务
|
||||||
|
ssh pi@192.168.1.160 "sudo systemctl restart a-share-research"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 已知技术债务
|
||||||
|
|
||||||
|
| 问题 | 文件 | 状态 |
|
||||||
|
|------|------|------|
|
||||||
|
| QdrantClient HTTP 超时硬编码 10s | `vectorstore/client.py:93` | 无可配环境变量 |
|
||||||
|
| DashScope HTTP 超时硬编码 60s | `embedding/remote.py:81` | 无可配环境变量 |
|
||||||
|
| 超时解析逻辑在 pipeline.py 和 main.py 各一份 | 两处 | 可抽取公共函数 |
|
||||||
|
| `deepseek-v4-flash` 概率性返回空 | — | 分块处理 + 失败跳过 |
|
||||||
|
| wallstreetcn JS 渲染拦截 | — | 正文提取率 0% |
|
||||||
|
| eeo 列表页仍走 Playwright(仅文章页走 httpx) | `crawler/engine.py` | 列表页目前正常,暂不改动 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 架构备忘:静态/动态抓取分流
|
||||||
|
|
||||||
|
```
|
||||||
|
SourceConfig.js_render == False 且 wait_for == None
|
||||||
|
→ _fetch_static() → httpx 直连 (无浏览器, 快, 不被反爬)
|
||||||
|
→ markdownify + BeautifulSoup (清洗 script/style 后转 Markdown)
|
||||||
|
|
||||||
|
SourceConfig.js_render == True 或 wait_for 有值
|
||||||
|
→ _crawl_once() → Playwright (现有逻辑)
|
||||||
|
```
|
||||||
|
|
||||||
|
`_TimeoutGuard`:
|
||||||
|
- 同源连续超时 2 次 → `skip=True`
|
||||||
|
- 后续请求在 `semaphore` 内检查 `guard.skip`,立即返回 `Skipped`
|
||||||
|
- 一次成功即重置计数器
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 记忆
|
||||||
|
|
||||||
|
- `never-change-llm-model.md` — 绝不允许修改大模型配置
|
||||||
|
- `deploy-html.md` — user_guide.html 同步到 Pi 和 Web 服务器
|
||||||
|
- `sync-sources-yaml.md` — 修改 sources.yaml 前从 Pi 拉取,改完推回
|
||||||
|
- `update-user-guide.md` — 更新 md+html+部署
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
"""新闻抓取模块 (M1)。
|
||||||
|
|
||||||
|
公共 API:
|
||||||
|
- load_crawler_config: 加载 sources.yaml
|
||||||
|
- crawl_all: 一键抓取所有启用的源
|
||||||
|
- crawl_source: 抓取单个源(供测试/调试)
|
||||||
|
- CrawlerConfig / SourceConfig / CrawlResult: 数据模型
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .config import load_crawler_config
|
||||||
|
from .engine import crawl_all, crawl_source, extract_article_links
|
||||||
|
from .models import (
|
||||||
|
ArticleLink,
|
||||||
|
CrawlerConfig,
|
||||||
|
CrawlerSettings,
|
||||||
|
CrawlResult,
|
||||||
|
CrawlStage,
|
||||||
|
SourceConfig,
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"ArticleLink",
|
||||||
|
"CrawlResult",
|
||||||
|
"CrawlStage",
|
||||||
|
"CrawlerConfig",
|
||||||
|
"CrawlerSettings",
|
||||||
|
"SourceConfig",
|
||||||
|
"crawl_all",
|
||||||
|
"crawl_source",
|
||||||
|
"extract_article_links",
|
||||||
|
"load_crawler_config",
|
||||||
|
]
|
||||||
@@ -0,0 +1,499 @@
|
|||||||
|
"""cninfo 巨潮资讯网爬虫 v2.0。
|
||||||
|
|
||||||
|
从 watchlist.yaml 的 code + orgId 拼接 URL,通过 Crawl4AI(Playwright)渲染 SPA 页面提取数据。
|
||||||
|
三种数据类型:
|
||||||
|
1. 公司最新公告 → https://www.cninfo.com.cn/new/disclosure/stock?stockCode={code}&orgId={orgId}#latestAnnouncement
|
||||||
|
2. 投资者调研 → 同上, #research
|
||||||
|
3. 互动易问答 → https://irm.cninfo.com.cn/ircs/search?keyword={code}
|
||||||
|
|
||||||
|
策略:
|
||||||
|
- 公告/调研: Playwright SPA 渲染 → DOM 提取(A 方案,REST API 的 stock 参数不可靠)
|
||||||
|
- 互动易: requests 优先,失败回退 Playwright
|
||||||
|
- 增量: ann_id 去重,首次从 2026-01-01 全量,后续增量 7 天
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
from datetime import date, datetime, timedelta
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
from .models import CninfoItem
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# 常量
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
SOURCE_ID = "cninfo"
|
||||||
|
CNINFO_PDF_BASE = os.environ.get("CNINFO_PDF_BASE", "http://static.cninfo.com.cn")
|
||||||
|
|
||||||
|
# 日期范围: 从 2026-01-01 开始
|
||||||
|
DATE_START = "2026-01-01"
|
||||||
|
|
||||||
|
# 翻页限制
|
||||||
|
MAX_ANNOUNCE_PAGES = 5
|
||||||
|
MAX_RESEARCH_PAGES = 1
|
||||||
|
MAX_IRM_ITEMS = 20
|
||||||
|
|
||||||
|
# 请求间隔(秒)
|
||||||
|
REQUEST_DELAY = 0.5
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# 工具函数
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
def _url_hash(url: str) -> str:
|
||||||
|
return hashlib.sha1(url.encode("utf-8")).hexdigest()[:16]
|
||||||
|
|
||||||
|
|
||||||
|
def _today_str() -> str:
|
||||||
|
return date.today().strftime("%Y%m%d")
|
||||||
|
|
||||||
|
|
||||||
|
def _load_watchlist() -> list[dict]:
|
||||||
|
import yaml
|
||||||
|
try:
|
||||||
|
with open("configs/watchlist.yaml", encoding="utf-8") as f:
|
||||||
|
data = yaml.safe_load(f) or {}
|
||||||
|
return list(data.get("watchlist") or [])
|
||||||
|
except Exception:
|
||||||
|
logger.exception("加载 watchlist.yaml 失败")
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def _make_api_headers() -> dict[str, str]:
|
||||||
|
return {
|
||||||
|
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
|
||||||
|
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36",
|
||||||
|
"Accept": "text/html,application/xhtml+xml",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _build_stock_url(code: str, org_id: str) -> str:
|
||||||
|
"""拼接 cninfo 个股公告页 URL。"""
|
||||||
|
return f"https://www.cninfo.com.cn/new/disclosure/stock?stockCode={code}&orgId={org_id}"
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Crawl4AI SPA 渲染
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
async def _render_page(url: str, timeout_ms: int = 60000,
|
||||||
|
delay_ms: int = 20) -> str:
|
||||||
|
"""用 Crawl4AI 渲染 SPA 页面,返回 HTML 字符串。"""
|
||||||
|
from crawl4ai import AsyncWebCrawler, BrowserConfig, CacheMode, CrawlerRunConfig
|
||||||
|
|
||||||
|
bconf = BrowserConfig(headless=True, verbose=False)
|
||||||
|
rconf = CrawlerRunConfig(
|
||||||
|
cache_mode=CacheMode.BYPASS,
|
||||||
|
page_timeout=timeout_ms,
|
||||||
|
delay_before_return_html=delay_ms,
|
||||||
|
)
|
||||||
|
async with AsyncWebCrawler(config=bconf) as c:
|
||||||
|
result = await c.arun(url=url, config=rconf)
|
||||||
|
return getattr(result, "html", "") or ""
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# 公告 + 调研: 从渲染后的 SPA 页面 DOM 提取
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
def _parse_announcement_list(html: str, code: str,
|
||||||
|
item_type: str = "announcement") -> list[CninfoItem]:
|
||||||
|
"""从 cninfo 个股页面的渲染 HTML 中提取公告/调研列表。
|
||||||
|
|
||||||
|
页面结构: 公告列表以 <a> 标签呈现,关键属性:
|
||||||
|
- data-seccode: 股票代码
|
||||||
|
- data-id: 公告 ID
|
||||||
|
- href: 包含 announcementTime / announcementType 等参数
|
||||||
|
"""
|
||||||
|
soup = BeautifulSoup(html, "html.parser")
|
||||||
|
items: list[CninfoItem] = []
|
||||||
|
seen: set[str] = set()
|
||||||
|
|
||||||
|
# 查找所有带 data-seccode=code 的公告链接
|
||||||
|
for el in soup.find_all(attrs={"data-seccode": code}):
|
||||||
|
ann_id = (el.get("data-id") or "").strip()
|
||||||
|
if not ann_id or ann_id in seen:
|
||||||
|
continue
|
||||||
|
seen.add(ann_id)
|
||||||
|
|
||||||
|
title = el.get_text(strip=True)
|
||||||
|
if len(title) < 5:
|
||||||
|
continue
|
||||||
|
|
||||||
|
href = el.get("href", "")
|
||||||
|
|
||||||
|
# 从 href 提取发布时间
|
||||||
|
pub_time = ""
|
||||||
|
date_match = re.search(r"announcementTime=(\d{4}-\d{2}-\d{2})", href)
|
||||||
|
if date_match:
|
||||||
|
pub_time = date_match.group(1)
|
||||||
|
|
||||||
|
# 公告类型
|
||||||
|
ann_type = ""
|
||||||
|
type_match = re.search(r"announcementType=(\w+)", href)
|
||||||
|
if type_match:
|
||||||
|
ann_type = type_match.group(1)
|
||||||
|
|
||||||
|
# 板块代码
|
||||||
|
plate = ""
|
||||||
|
plate_match = re.search(r"plate=(\w+)", href)
|
||||||
|
if plate_match:
|
||||||
|
plate = plate_match.group(1)
|
||||||
|
|
||||||
|
# PDF URL
|
||||||
|
pdf_url = ""
|
||||||
|
if pub_time and ann_id:
|
||||||
|
pdf_url = f"{CNINFO_PDF_BASE}/finalpage/{pub_time}/{ann_id}.PDF"
|
||||||
|
|
||||||
|
items.append(CninfoItem(
|
||||||
|
stock_code=code,
|
||||||
|
stock_name="",
|
||||||
|
title=title,
|
||||||
|
content="",
|
||||||
|
publish_time=pub_time,
|
||||||
|
item_type=item_type,
|
||||||
|
url=pdf_url,
|
||||||
|
ann_id=ann_id,
|
||||||
|
extra={"announcement_type": ann_type, "plate": plate},
|
||||||
|
))
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_irm_text(text: str, code: str) -> list[CninfoItem]:
|
||||||
|
"""从互动易页面文本中提取问答。
|
||||||
|
|
||||||
|
注意: cninfo 互动易搜索页是 Vue SPA,问答数据通过需认证的 API 加载。
|
||||||
|
公开访问时页面显示"暂无数据",此时应返回空列表。
|
||||||
|
"""
|
||||||
|
items: list[CninfoItem] = []
|
||||||
|
url = f"https://irm.cninfo.com.cn/ircs/search?keyword={code}"
|
||||||
|
|
||||||
|
# 检测是否为无数据页面
|
||||||
|
if "暂无数据" in text:
|
||||||
|
logger.debug(" {} 互动易页面显示'暂无数据'(需登录认证)", code)
|
||||||
|
return items
|
||||||
|
|
||||||
|
# 检测是否为 SPA 空壳(无 JS 渲染时只有导航文本)
|
||||||
|
text_stripped = text.strip()
|
||||||
|
if len(text_stripped) < 200 and code in text_stripped:
|
||||||
|
logger.debug(" {} 互动易页面内容过短(SPA 空壳)", code)
|
||||||
|
return items
|
||||||
|
|
||||||
|
# 按常见分隔模式拆分问答块
|
||||||
|
blocks = re.split(r"\n(?=\d+\.|\b[问答][::])", text)
|
||||||
|
for i, block in enumerate(blocks[:MAX_IRM_ITEMS]):
|
||||||
|
block = block.strip()
|
||||||
|
if len(block) > 30 and code in block:
|
||||||
|
items.append(CninfoItem(
|
||||||
|
stock_code=code,
|
||||||
|
stock_name="",
|
||||||
|
title=f"{code} 互动问答 #{i + 1}",
|
||||||
|
content=block[:5000],
|
||||||
|
publish_time="",
|
||||||
|
item_type="irm",
|
||||||
|
url=url,
|
||||||
|
ann_id=_url_hash(f"{url}#{i}"),
|
||||||
|
))
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# 互动易
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
async def _fetch_irm_playwright(code: str) -> list[CninfoItem]:
|
||||||
|
"""Playwright 渲染互动易搜索页。"""
|
||||||
|
url = f"https://irm.cninfo.com.cn/ircs/search?keyword={code}"
|
||||||
|
try:
|
||||||
|
html = await _render_page(url, timeout_ms=30000, delay_ms=8)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(" {} 互动易 Playwright 失败: {}", code, e)
|
||||||
|
return []
|
||||||
|
|
||||||
|
soup = BeautifulSoup(html, "html.parser")
|
||||||
|
body_text = soup.get_text()
|
||||||
|
return _parse_irm_text(body_text, code)
|
||||||
|
|
||||||
|
|
||||||
|
def _fetch_irm_requests(code: str) -> list[CninfoItem]:
|
||||||
|
"""requests 获取互动易搜索页。"""
|
||||||
|
url = f"https://irm.cninfo.com.cn/ircs/search?keyword={code}"
|
||||||
|
headers = _make_api_headers()
|
||||||
|
headers["Referer"] = "https://irm.cninfo.com.cn/"
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = requests.get(url, headers=headers, timeout=15)
|
||||||
|
r.raise_for_status()
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(" {} 互动易 requests 失败: {}", code, e)
|
||||||
|
return []
|
||||||
|
|
||||||
|
soup = BeautifulSoup(r.text, "html.parser")
|
||||||
|
body_text = soup.get_text()
|
||||||
|
return _parse_irm_text(body_text, code)
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# 单股票抓取
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
async def _crawl_one_stock_async(stock: dict,
|
||||||
|
start_date: str = DATE_START,
|
||||||
|
end_date: str | None = None) -> list[CninfoItem]:
|
||||||
|
"""异步抓取单个公司的公告 + 调研 + 互动易。"""
|
||||||
|
code = stock["code"]
|
||||||
|
name = stock["name"]
|
||||||
|
org_id = stock.get("orgId", "").strip()
|
||||||
|
|
||||||
|
if not org_id:
|
||||||
|
logger.warning("{} ({}) 未配置 orgId,跳过", code, name)
|
||||||
|
return []
|
||||||
|
|
||||||
|
end_date = end_date or date.today().strftime("%Y-%m-%d")
|
||||||
|
base_url = _build_stock_url(code, org_id)
|
||||||
|
results: list[CninfoItem] = []
|
||||||
|
|
||||||
|
# --- 1) 公告 (#latestAnnouncement) ---
|
||||||
|
announce_url = f"{base_url}#latestAnnouncement"
|
||||||
|
logger.info("抓取 {} ({}) 公告: {}", code, name, announce_url[:80])
|
||||||
|
try:
|
||||||
|
html = await _render_page(announce_url, timeout_ms=60000, delay_ms=20)
|
||||||
|
# 日期过滤: 只保留 start_date 之后的
|
||||||
|
items = _parse_announcement_list(html, code, item_type="announcement")
|
||||||
|
filtered = [it for it in items if it.publish_time >= start_date]
|
||||||
|
# 限制页数: 每个页面约 30 条, 5 页 ≈ 150 条(SPA 一次加载可能超过一页)
|
||||||
|
filtered = filtered[:MAX_ANNOUNCE_PAGES * 30]
|
||||||
|
for it in filtered:
|
||||||
|
it.stock_name = name
|
||||||
|
results.extend(filtered)
|
||||||
|
logger.info(" {} 公告: {} 条 (过滤后)", code, len(filtered))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(" {} 公告抓取失败: {}", code, e)
|
||||||
|
|
||||||
|
# --- 2) 调研 (#research) ---
|
||||||
|
research_url = f"{base_url}#research"
|
||||||
|
logger.info("抓取 {} ({}) 调研: {}", code, name, research_url[:80])
|
||||||
|
try:
|
||||||
|
html = await _render_page(research_url, timeout_ms=60000, delay_ms=20)
|
||||||
|
items = _parse_announcement_list(html, code, item_type="research")
|
||||||
|
filtered = [it for it in items if it.publish_time >= start_date]
|
||||||
|
filtered = filtered[:MAX_RESEARCH_PAGES * 30]
|
||||||
|
for it in filtered:
|
||||||
|
it.stock_name = name
|
||||||
|
results.extend(filtered)
|
||||||
|
logger.info(" {} 调研: {} 条 (过滤后)", code, len(filtered))
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(" {} 调研抓取失败(可能无调研页面): {}", code, e)
|
||||||
|
|
||||||
|
# --- 3) 互动易 ---
|
||||||
|
logger.info("抓取 {} ({}) 互动易", code, name)
|
||||||
|
try:
|
||||||
|
irm_items = _fetch_irm_requests(code)
|
||||||
|
if not irm_items:
|
||||||
|
logger.info(" {} 互动易 requests 无结果,回退 Playwright...", code)
|
||||||
|
irm_items = await _fetch_irm_playwright(code)
|
||||||
|
for it in irm_items:
|
||||||
|
it.stock_name = name
|
||||||
|
results.extend(irm_items)
|
||||||
|
logger.info(" {} 互动易: {} 条", code, len(irm_items))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(" {} 互动易抓取失败: {}", code, e)
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# 增量保存
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
def _load_seen_ids(out_dir: Path) -> set[str]:
|
||||||
|
"""从 index.jsonl 加载已保存的公告 ID 集合。"""
|
||||||
|
seen: set[str] = set()
|
||||||
|
index_path = out_dir / "index.jsonl"
|
||||||
|
if index_path.is_file():
|
||||||
|
with index_path.open("r", encoding="utf-8") as f:
|
||||||
|
for line in f:
|
||||||
|
try:
|
||||||
|
rec = json.loads(line.strip())
|
||||||
|
aid = rec.get("ann_id", "")
|
||||||
|
if aid:
|
||||||
|
seen.add(aid)
|
||||||
|
except (json.JSONDecodeError, KeyError):
|
||||||
|
continue
|
||||||
|
return seen
|
||||||
|
|
||||||
|
|
||||||
|
def _save_items(items: list[CninfoItem], out_dir: Path) -> int:
|
||||||
|
"""增量保存 CninfoItem 列表,跳过已存在的 ann_id。返回新增条数。"""
|
||||||
|
out_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
seen = _load_seen_ids(out_dir)
|
||||||
|
logger.info("cninfo 增量模式: 已有 {} 条历史记录", len(seen))
|
||||||
|
|
||||||
|
index_path = out_dir / "index.jsonl"
|
||||||
|
new_count = 0
|
||||||
|
|
||||||
|
with index_path.open("a", encoding="utf-8") as index_f:
|
||||||
|
for item in items:
|
||||||
|
if item.ann_id and item.ann_id in seen:
|
||||||
|
continue
|
||||||
|
seen.add(item.ann_id)
|
||||||
|
new_count += 1
|
||||||
|
|
||||||
|
fname = _url_hash(item.ann_id or item.title + item.stock_code)
|
||||||
|
json_path = out_dir / f"{fname}.json"
|
||||||
|
json_path.write_text(
|
||||||
|
item.model_dump_json(indent=2, ensure_ascii=False),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
meta = item.model_dump(mode="json")
|
||||||
|
meta["source_id"] = SOURCE_ID
|
||||||
|
meta["json_file"] = f"{fname}.json"
|
||||||
|
index_f.write(json.dumps(meta, ensure_ascii=False) + "\n")
|
||||||
|
|
||||||
|
logger.info("cninfo 已保存: 新增 {} 条 (总计 {} 条) -> {}", new_count, len(seen), out_dir)
|
||||||
|
return new_count
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# 主入口
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
def crawl_watchlist(*, save: bool = True) -> list[CninfoItem]:
|
||||||
|
"""从 watchlist 抓取所有公司的公告+调研+互动易。
|
||||||
|
|
||||||
|
首次运行从 2026-01-01 开始,后续增量运行(最近 7 天)。
|
||||||
|
通过判定 data/raw/cninfo/ 下是否有历史 index.jsonl 来区分首次/增量。
|
||||||
|
"""
|
||||||
|
watchlist = _load_watchlist()
|
||||||
|
if not watchlist:
|
||||||
|
logger.warning("关注列表为空")
|
||||||
|
return []
|
||||||
|
|
||||||
|
out_dir = Path("data/raw") / SOURCE_ID / _today_str()
|
||||||
|
|
||||||
|
# 判断首次还是增量
|
||||||
|
has_history = False
|
||||||
|
raw_root = Path("data/raw") / SOURCE_ID
|
||||||
|
if raw_root.is_dir():
|
||||||
|
for d in sorted(raw_root.glob("*"), reverse=True):
|
||||||
|
idx = d / "index.jsonl"
|
||||||
|
if idx.is_file() and idx.stat().st_size > 0:
|
||||||
|
has_history = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if has_history:
|
||||||
|
start = (date.today() - timedelta(days=7)).strftime("%Y-%m-%d")
|
||||||
|
logger.info("cninfo 增量模式: 日期范围 {} ~ 今天", start)
|
||||||
|
else:
|
||||||
|
start = DATE_START
|
||||||
|
logger.info("cninfo 首次全量: 日期范围 {} ~ 今天", start)
|
||||||
|
|
||||||
|
end = date.today().strftime("%Y-%m-%d")
|
||||||
|
|
||||||
|
# 并发抓取所有股票
|
||||||
|
async def _run_all():
|
||||||
|
tasks = [_crawl_one_stock_async(s, start_date=start, end_date=end)
|
||||||
|
for s in watchlist]
|
||||||
|
all_items: list[CninfoItem] = []
|
||||||
|
for coro in asyncio.as_completed(tasks):
|
||||||
|
try:
|
||||||
|
items = await coro
|
||||||
|
all_items.extend(items)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("某股票抓取异常: {}", e)
|
||||||
|
return all_items
|
||||||
|
|
||||||
|
all_items = asyncio.run(_run_all())
|
||||||
|
|
||||||
|
# 统计
|
||||||
|
ann_count = sum(1 for it in all_items if it.item_type == "announcement")
|
||||||
|
res_count = sum(1 for it in all_items if it.item_type == "research")
|
||||||
|
irm_count = sum(1 for it in all_items if it.item_type == "irm")
|
||||||
|
logger.info(
|
||||||
|
"cninfo 抓取完成: 公告 {} / 调研 {} / 互动易 {} ({} 家公司,共 {} 条)",
|
||||||
|
ann_count, res_count, irm_count, len(watchlist), len(all_items),
|
||||||
|
)
|
||||||
|
|
||||||
|
if save and all_items:
|
||||||
|
new_count = _save_items(all_items, out_dir)
|
||||||
|
logger.info("cninfo 本轮新增 {} 条", new_count)
|
||||||
|
|
||||||
|
return all_items
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# PDF 正文提取
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
def enrich_articles_with_pdf(day_str: str | None = None, *, limit: int = 0) -> int:
|
||||||
|
"""下载 PDF 并用 MarkItDown 提取正文补充到 Article.content。"""
|
||||||
|
day_str = day_str or _today_str()
|
||||||
|
proc_dir = Path("data/processed") / SOURCE_ID / day_str
|
||||||
|
if not proc_dir.is_dir():
|
||||||
|
return 0
|
||||||
|
|
||||||
|
from markitdown import MarkItDown
|
||||||
|
|
||||||
|
enriched = 0
|
||||||
|
for fp in sorted(proc_dir.glob("*.json")):
|
||||||
|
if limit and enriched >= limit:
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
article_data = json.loads(fp.read_text(encoding="utf-8"))
|
||||||
|
except (json.JSONDecodeError, OSError):
|
||||||
|
continue
|
||||||
|
|
||||||
|
content = article_data.get("content", "")
|
||||||
|
pdf_url = ""
|
||||||
|
|
||||||
|
# 多种方式提取 PDF URL
|
||||||
|
pdf_match = re.search(r"原文链接:\s*(https?://\S+\.pdf)", content, re.IGNORECASE)
|
||||||
|
if pdf_match:
|
||||||
|
pdf_url = pdf_match.group(1)
|
||||||
|
else:
|
||||||
|
pdf_match = re.search(r"PDF链接:\s*(https?://\S+\.pdf)", content, re.IGNORECASE)
|
||||||
|
if pdf_match:
|
||||||
|
pdf_url = pdf_match.group(1)
|
||||||
|
else:
|
||||||
|
url = article_data.get("url", "")
|
||||||
|
if url.lower().endswith(".pdf"):
|
||||||
|
pdf_url = url
|
||||||
|
|
||||||
|
if not pdf_url:
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = requests.get(pdf_url, headers=_make_api_headers(), timeout=30)
|
||||||
|
r.raise_for_status()
|
||||||
|
tmp_path = Path("/tmp") / f"cninfo_pdf_{_url_hash(pdf_url)}.pdf"
|
||||||
|
tmp_path.write_bytes(r.content)
|
||||||
|
md = MarkItDown()
|
||||||
|
result = md.convert(str(tmp_path))
|
||||||
|
text = (result.text_content or "").strip()[:20000]
|
||||||
|
if tmp_path.exists():
|
||||||
|
tmp_path.unlink()
|
||||||
|
if text and len(text) >= 50:
|
||||||
|
article_data["content"] = text
|
||||||
|
article_data["word_count"] = len(text)
|
||||||
|
fp.write_text(json.dumps(article_data, ensure_ascii=False, indent=2),
|
||||||
|
encoding="utf-8")
|
||||||
|
enriched += 1
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning("PDF 提取失败 {}: {}", pdf_url, e)
|
||||||
|
|
||||||
|
return enriched
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
"""抓取配置加载器。
|
||||||
|
|
||||||
|
从 YAML 文件读取 sources.yaml,并校验为 CrawlerConfig。
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
from .models import CrawlerConfig
|
||||||
|
|
||||||
|
DEFAULT_CONFIG_PATH = Path("configs/sources.yaml")
|
||||||
|
|
||||||
|
|
||||||
|
def load_crawler_config(path: str | Path | None = None) -> CrawlerConfig:
|
||||||
|
"""加载并校验抓取配置。
|
||||||
|
|
||||||
|
参数:
|
||||||
|
path: YAML 配置路径,默认 configs/sources.yaml。
|
||||||
|
|
||||||
|
返回:
|
||||||
|
CrawlerConfig 实例。
|
||||||
|
|
||||||
|
异常:
|
||||||
|
FileNotFoundError: 配置文件不存在。
|
||||||
|
ValueError: YAML 顶层不是 mapping 或 sources 缺失。
|
||||||
|
pydantic.ValidationError: schema 校验失败。
|
||||||
|
"""
|
||||||
|
config_path = Path(path) if path else DEFAULT_CONFIG_PATH
|
||||||
|
if not config_path.is_file():
|
||||||
|
raise FileNotFoundError(f"抓取配置文件不存在: {config_path}")
|
||||||
|
|
||||||
|
with config_path.open("r", encoding="utf-8") as f:
|
||||||
|
raw: Any = yaml.safe_load(f)
|
||||||
|
|
||||||
|
if not isinstance(raw, dict):
|
||||||
|
raise ValueError(f"配置文件顶层必须是 mapping,实际类型: {type(raw).__name__}")
|
||||||
|
if "sources" not in raw:
|
||||||
|
raise ValueError("配置文件缺少 sources 字段")
|
||||||
|
|
||||||
|
config = CrawlerConfig.model_validate(raw)
|
||||||
|
logger.info(
|
||||||
|
"已加载抓取配置: 总 {} 个源, 启用 {} 个, 并发上限 {}",
|
||||||
|
len(config.sources),
|
||||||
|
len(config.enabled_sources()),
|
||||||
|
config.settings.concurrency,
|
||||||
|
)
|
||||||
|
return config
|
||||||
@@ -0,0 +1,445 @@
|
|||||||
|
"""Crawl4AI 异步抓取引擎。
|
||||||
|
|
||||||
|
设计:
|
||||||
|
1. 一个 AsyncWebCrawler 实例服务所有源(共用浏览器,降低开销);
|
||||||
|
2. 每个 URL 独立的 CrawlerRunConfig(wait_for/timeout 来自源配置);
|
||||||
|
3. 全局 Semaphore 控制并发(默认 3);
|
||||||
|
4. 每个 URL 用 tenacity 重试(默认 3 次,指数退避);
|
||||||
|
5. 两阶段: 先抓首页 -> 解析文章链接 -> 并发抓文章详情。
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import re
|
||||||
|
from typing import Any
|
||||||
|
from urllib.parse import urljoin, urlparse
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
from crawl4ai import AsyncWebCrawler, BrowserConfig, CacheMode, CrawlerRunConfig
|
||||||
|
from loguru import logger
|
||||||
|
from markdownify import markdownify as _md_convert
|
||||||
|
|
||||||
|
from .models import (
|
||||||
|
ArticleLink,
|
||||||
|
CrawlerConfig,
|
||||||
|
CrawlerSettings,
|
||||||
|
CrawlResult,
|
||||||
|
CrawlStage,
|
||||||
|
SourceConfig,
|
||||||
|
)
|
||||||
|
from .storage import mark_url_seen, save_result
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# 工具函数
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
def _markdown_text(crawl4ai_markdown: Any) -> str:
|
||||||
|
"""从 Crawl4AI 的 markdown 字段中安全取出字符串。
|
||||||
|
|
||||||
|
新版 Crawl4AI(0.4+) 返回 MarkdownGenerationResult 对象;
|
||||||
|
旧版可能直接是 str。这里做兼容。
|
||||||
|
"""
|
||||||
|
if crawl4ai_markdown is None:
|
||||||
|
return ""
|
||||||
|
if isinstance(crawl4ai_markdown, str):
|
||||||
|
return crawl4ai_markdown
|
||||||
|
raw = getattr(crawl4ai_markdown, "raw_markdown", None)
|
||||||
|
if isinstance(raw, str):
|
||||||
|
return raw
|
||||||
|
fit = getattr(crawl4ai_markdown, "fit_markdown", None)
|
||||||
|
if isinstance(fit, str):
|
||||||
|
return fit
|
||||||
|
return str(crawl4ai_markdown)
|
||||||
|
|
||||||
|
|
||||||
|
def extract_article_links(
|
||||||
|
html: str,
|
||||||
|
base_url: str,
|
||||||
|
source: SourceConfig,
|
||||||
|
) -> list[ArticleLink]:
|
||||||
|
"""从列表页 HTML 中抽取文章链接,使用源配置的正则筛选。
|
||||||
|
|
||||||
|
步骤:
|
||||||
|
1. 若设置了 article_link_selector,先 narrow 到选择器范围;
|
||||||
|
2. 找出所有 <a href>;
|
||||||
|
3. urljoin 转绝对 URL;
|
||||||
|
4. 用 article_url_pattern 正则筛选;
|
||||||
|
5. 去重保持顺序;
|
||||||
|
6. 截断到 max_articles_per_run。
|
||||||
|
"""
|
||||||
|
soup = BeautifulSoup(html, "html.parser")
|
||||||
|
scope = (
|
||||||
|
soup.select_one(source.article_link_selector)
|
||||||
|
if source.article_link_selector
|
||||||
|
else soup
|
||||||
|
)
|
||||||
|
if scope is None:
|
||||||
|
logger.warning(
|
||||||
|
"源 {} 设置了 article_link_selector={!r},但页面中未匹配到", source.id, source.article_link_selector
|
||||||
|
)
|
||||||
|
return []
|
||||||
|
|
||||||
|
pattern = re.compile(source.article_url_pattern)
|
||||||
|
seen: set[str] = set()
|
||||||
|
links: list[ArticleLink] = []
|
||||||
|
|
||||||
|
for a in scope.find_all("a", href=True):
|
||||||
|
href = a["href"].strip()
|
||||||
|
if not href or href.startswith(("javascript:", "#", "mailto:")):
|
||||||
|
continue
|
||||||
|
absolute = urljoin(base_url, href)
|
||||||
|
# 去掉 fragment
|
||||||
|
absolute = absolute.split("#", 1)[0]
|
||||||
|
if not pattern.match(absolute):
|
||||||
|
continue
|
||||||
|
if absolute in seen:
|
||||||
|
continue
|
||||||
|
seen.add(absolute)
|
||||||
|
anchor = (a.get_text() or "").strip() or None
|
||||||
|
links.append(ArticleLink(source_id=source.id, url=absolute, anchor_text=anchor))
|
||||||
|
if len(links) >= source.max_articles_per_run:
|
||||||
|
break
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"源 {} 列表页抽取链接 {} 条 (上限 {})",
|
||||||
|
source.id,
|
||||||
|
len(links),
|
||||||
|
source.max_articles_per_run,
|
||||||
|
)
|
||||||
|
return links
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# 单 URL 抓取
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
def _build_run_config(source: SourceConfig) -> CrawlerRunConfig:
|
||||||
|
"""根据源配置构造 CrawlerRunConfig。"""
|
||||||
|
return CrawlerRunConfig(
|
||||||
|
cache_mode=CacheMode.BYPASS,
|
||||||
|
wait_for=source.wait_for,
|
||||||
|
page_timeout=source.page_timeout_ms,
|
||||||
|
excluded_tags=["script", "style", "noscript"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class _TimeoutGuard:
|
||||||
|
"""单源连续超时计数器,达到阈值后标记跳过,防止整个进程被拖死。"""
|
||||||
|
|
||||||
|
def __init__(self, source_id: str, max_consecutive: int = 2) -> None:
|
||||||
|
self.source_id = source_id
|
||||||
|
self.max_consecutive = max_consecutive
|
||||||
|
self._count = 0
|
||||||
|
self.skip = False
|
||||||
|
|
||||||
|
def record(self, error: str | None, success: bool) -> None:
|
||||||
|
"""根据抓取结果更新计数器。"""
|
||||||
|
if self.skip:
|
||||||
|
return
|
||||||
|
if success:
|
||||||
|
self._count = 0
|
||||||
|
return
|
||||||
|
if error and "timeout" in error.lower():
|
||||||
|
self._count += 1
|
||||||
|
if self._count >= self.max_consecutive:
|
||||||
|
self.skip = True
|
||||||
|
logger.warning(
|
||||||
|
"源 {} 连续超时 {} 次,跳过剩余请求",
|
||||||
|
self.source_id,
|
||||||
|
self._count,
|
||||||
|
)
|
||||||
|
|
||||||
|
def skipped_result(self, url: str, stage: CrawlStage) -> CrawlResult:
|
||||||
|
"""生成"已跳过"结果。"""
|
||||||
|
return CrawlResult(
|
||||||
|
source_id=self.source_id,
|
||||||
|
stage=stage,
|
||||||
|
url=url,
|
||||||
|
success=False,
|
||||||
|
error="Skipped: 源连续超时已跳过",
|
||||||
|
attempts=0,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def _fetch_static(
|
||||||
|
url: str,
|
||||||
|
source: SourceConfig,
|
||||||
|
stage: CrawlStage,
|
||||||
|
attempt: int,
|
||||||
|
) -> CrawlResult:
|
||||||
|
"""使用 httpx 直连抓取静态页面(js_render=False),绕过 Playwright 反爬检测。"""
|
||||||
|
headers = {
|
||||||
|
"User-Agent": (
|
||||||
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
|
||||||
|
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
timeout_sec = source.page_timeout_ms / 1000.0
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient(
|
||||||
|
timeout=timeout_sec,
|
||||||
|
follow_redirects=True,
|
||||||
|
headers=headers,
|
||||||
|
) as client:
|
||||||
|
r = await client.get(url)
|
||||||
|
html = r.text
|
||||||
|
# HTML → Markdown:先清洗再转换
|
||||||
|
markdown = ""
|
||||||
|
if html:
|
||||||
|
try:
|
||||||
|
soup = BeautifulSoup(html, "html.parser")
|
||||||
|
for tag in soup(["script", "style", "noscript", "meta", "link"]):
|
||||||
|
tag.decompose()
|
||||||
|
markdown = _md_convert(str(soup)) or ""
|
||||||
|
except Exception:
|
||||||
|
markdown = ""
|
||||||
|
return CrawlResult(
|
||||||
|
source_id=source.id,
|
||||||
|
stage=stage,
|
||||||
|
url=url,
|
||||||
|
success=True,
|
||||||
|
status_code=r.status_code,
|
||||||
|
html=html,
|
||||||
|
markdown=markdown,
|
||||||
|
attempts=attempt,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning("静态抓取异常 {} {}: {}", source.id, url, e)
|
||||||
|
return CrawlResult(
|
||||||
|
source_id=source.id,
|
||||||
|
stage=stage,
|
||||||
|
url=url,
|
||||||
|
success=False,
|
||||||
|
error=f"{type(e).__name__}: {e}",
|
||||||
|
attempts=attempt,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def _crawl_once(
|
||||||
|
crawler: AsyncWebCrawler,
|
||||||
|
url: str,
|
||||||
|
source: SourceConfig,
|
||||||
|
stage: CrawlStage,
|
||||||
|
attempt: int,
|
||||||
|
) -> CrawlResult:
|
||||||
|
"""单次抓取(无重试),用于被 retry 循环包裹。
|
||||||
|
|
||||||
|
- js_render=False 且无 wait_for → httpx 直连,绕过浏览器反爬;
|
||||||
|
- 其他情况 → Playwright。
|
||||||
|
"""
|
||||||
|
# 静态页面走 httpx 直连
|
||||||
|
if not source.js_render and not source.wait_for:
|
||||||
|
return await _fetch_static(url, source, stage, attempt)
|
||||||
|
|
||||||
|
# 动态页面走 Playwright
|
||||||
|
run_config = _build_run_config(source)
|
||||||
|
try:
|
||||||
|
c4_result = await crawler.arun(url=url, config=run_config)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning("抓取异常 {} {}: {}", source.id, url, e)
|
||||||
|
return CrawlResult(
|
||||||
|
source_id=source.id,
|
||||||
|
stage=stage,
|
||||||
|
url=url,
|
||||||
|
success=False,
|
||||||
|
error=f"{type(e).__name__}: {e}",
|
||||||
|
attempts=attempt,
|
||||||
|
)
|
||||||
|
|
||||||
|
success = bool(getattr(c4_result, "success", False))
|
||||||
|
return CrawlResult(
|
||||||
|
source_id=source.id,
|
||||||
|
stage=stage,
|
||||||
|
url=url,
|
||||||
|
success=success,
|
||||||
|
status_code=getattr(c4_result, "status_code", None),
|
||||||
|
html=getattr(c4_result, "html", "") or "",
|
||||||
|
markdown=_markdown_text(getattr(c4_result, "markdown", None)),
|
||||||
|
error=None if success else (getattr(c4_result, "error_message", None) or "unknown"),
|
||||||
|
attempts=attempt,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def crawl_url_with_retry(
|
||||||
|
crawler: AsyncWebCrawler,
|
||||||
|
url: str,
|
||||||
|
source: SourceConfig,
|
||||||
|
stage: CrawlStage,
|
||||||
|
settings: CrawlerSettings,
|
||||||
|
semaphore: asyncio.Semaphore,
|
||||||
|
guard: _TimeoutGuard | None = None,
|
||||||
|
) -> CrawlResult:
|
||||||
|
"""带并发限制与指数退避重试的单 URL 抓取。
|
||||||
|
|
||||||
|
重试策略:
|
||||||
|
最多 settings.retry_max_attempts 次;
|
||||||
|
失败 (success=False) 触发重试,等待时间 min(min_wait * 2^(n-1), max_wait)。
|
||||||
|
|
||||||
|
若传入 guard,每次重试前检查是否已被跳过。
|
||||||
|
"""
|
||||||
|
async with semaphore:
|
||||||
|
# 检查是否已被跳过
|
||||||
|
if guard and guard.skip:
|
||||||
|
return guard.skipped_result(url, stage)
|
||||||
|
|
||||||
|
max_attempts = settings.retry_max_attempts
|
||||||
|
result: CrawlResult | None = None
|
||||||
|
|
||||||
|
for attempt_no in range(1, max_attempts + 1):
|
||||||
|
result = await _crawl_once(crawler, url, source, stage, attempt_no)
|
||||||
|
if result.success:
|
||||||
|
if guard:
|
||||||
|
guard.record(None, True)
|
||||||
|
return result
|
||||||
|
if attempt_no >= max_attempts:
|
||||||
|
break
|
||||||
|
# 重试前检查是否已被其他并发请求触发跳过
|
||||||
|
if guard and guard.skip:
|
||||||
|
return guard.skipped_result(url, stage)
|
||||||
|
wait = min(
|
||||||
|
settings.retry_min_wait_sec * (2 ** (attempt_no - 1)),
|
||||||
|
settings.retry_max_wait_sec,
|
||||||
|
)
|
||||||
|
logger.info(
|
||||||
|
"源 {} {} 第 {}/{} 次失败,{} 秒后重试: {}",
|
||||||
|
source.id,
|
||||||
|
url,
|
||||||
|
attempt_no,
|
||||||
|
max_attempts,
|
||||||
|
wait,
|
||||||
|
result.error,
|
||||||
|
)
|
||||||
|
await asyncio.sleep(wait)
|
||||||
|
|
||||||
|
# 此处 result 必非 None(循环至少跑一次)
|
||||||
|
assert result is not None
|
||||||
|
if guard:
|
||||||
|
guard.record(result.error, False)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# 抓取流程
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
async def crawl_source(
|
||||||
|
crawler: AsyncWebCrawler,
|
||||||
|
source: SourceConfig,
|
||||||
|
settings: CrawlerSettings,
|
||||||
|
semaphore: asyncio.Semaphore,
|
||||||
|
save: bool = True,
|
||||||
|
) -> list[CrawlResult]:
|
||||||
|
"""抓取单个新闻源:遍历所有入口 → 抽取链接(去重) → 并发抓文章。"""
|
||||||
|
homepages = source.all_homepages()
|
||||||
|
logger.info(
|
||||||
|
"===== 开始抓取源: {} ({}) {} 个入口 =====",
|
||||||
|
source.id, source.name, len(homepages),
|
||||||
|
)
|
||||||
|
|
||||||
|
all_list_results: list[CrawlResult] = []
|
||||||
|
all_links: list[ArticleLink] = []
|
||||||
|
|
||||||
|
# 增量:加载历史 URL hash
|
||||||
|
from .storage import load_seen_urls
|
||||||
|
from .storage import url_hash as storage_url_hash
|
||||||
|
seen_hashes = load_seen_urls(source.id)
|
||||||
|
if seen_hashes:
|
||||||
|
logger.info("源 {} 增量模式: 已记录 {} 条历史 URL", source.id, len(seen_hashes))
|
||||||
|
|
||||||
|
# 遍历每个入口
|
||||||
|
for hp_url in homepages:
|
||||||
|
list_result = await crawl_url_with_retry(
|
||||||
|
crawler=crawler, url=hp_url, source=source,
|
||||||
|
stage=CrawlStage.LIST, settings=settings, semaphore=semaphore,
|
||||||
|
)
|
||||||
|
if save:
|
||||||
|
save_result(list_result, settings.output_root)
|
||||||
|
all_list_results.append(list_result)
|
||||||
|
|
||||||
|
if not list_result.success:
|
||||||
|
logger.warning("源 {} 入口 {} 抓取失败,跳过: {}", source.id, hp_url, list_result.error)
|
||||||
|
continue
|
||||||
|
|
||||||
|
parsed_base = urlparse(hp_url)
|
||||||
|
base = f"{parsed_base.scheme}://{parsed_base.netloc}"
|
||||||
|
links = extract_article_links(list_result.html, base, source)
|
||||||
|
|
||||||
|
# 去重 + 增量:按 hash 去重(当前运行跨入口 + 历史记录)
|
||||||
|
new_links = [ln for ln in links if storage_url_hash(ln.url) not in seen_hashes]
|
||||||
|
for ln in new_links:
|
||||||
|
seen_hashes.add(storage_url_hash(ln.url))
|
||||||
|
all_links.extend(new_links)
|
||||||
|
# 旧链接数(跨入口去重前)
|
||||||
|
old_count = len(links) - len(new_links)
|
||||||
|
logger.info("源 {} 入口 {} 抽取链接 {} 条(去重后新增 {}, 跳过旧 {} 条)",
|
||||||
|
source.id, hp_url, len(links), len(new_links), old_count)
|
||||||
|
|
||||||
|
if not all_links:
|
||||||
|
logger.warning("源 {} 所有入口未发现任何文章链接", source.id)
|
||||||
|
return all_list_results
|
||||||
|
|
||||||
|
# 连续超时保护:同源连续超时 2 次后跳过剩余请求
|
||||||
|
guard = _TimeoutGuard(source.id)
|
||||||
|
|
||||||
|
article_tasks = [
|
||||||
|
crawl_url_with_retry(
|
||||||
|
crawler=crawler, url=link.url, source=source,
|
||||||
|
stage=CrawlStage.ARTICLE, settings=settings, semaphore=semaphore,
|
||||||
|
guard=guard,
|
||||||
|
)
|
||||||
|
for link in all_links
|
||||||
|
]
|
||||||
|
article_results = await asyncio.gather(*article_tasks, return_exceptions=False)
|
||||||
|
|
||||||
|
if save:
|
||||||
|
for r in article_results:
|
||||||
|
save_result(r, settings.output_root)
|
||||||
|
|
||||||
|
success_cnt = sum(1 for r in article_results if r.success)
|
||||||
|
# 记录成功抓取的 URL(增量去重)
|
||||||
|
new_saved = 0
|
||||||
|
for r in article_results:
|
||||||
|
if r.success:
|
||||||
|
mark_url_seen(source.id, r.url)
|
||||||
|
new_saved += 1
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"源 {} 完成: 文章 {}/{} 成功, 新增 {} 条已记录",
|
||||||
|
source.id, success_cnt, len(article_results), new_saved,
|
||||||
|
)
|
||||||
|
return [*all_list_results, *article_results]
|
||||||
|
|
||||||
|
|
||||||
|
async def crawl_all(
|
||||||
|
config: CrawlerConfig,
|
||||||
|
save: bool = True,
|
||||||
|
) -> list[CrawlResult]:
|
||||||
|
"""抓取所有启用的源,返回扁平的 CrawlResult 列表。"""
|
||||||
|
settings = config.settings
|
||||||
|
enabled = config.enabled_sources()
|
||||||
|
if not enabled:
|
||||||
|
logger.warning("没有启用的新闻源,直接返回")
|
||||||
|
return []
|
||||||
|
|
||||||
|
browser_config = BrowserConfig(
|
||||||
|
headless=settings.headless,
|
||||||
|
user_agent=settings.user_agent,
|
||||||
|
verbose=False,
|
||||||
|
)
|
||||||
|
semaphore = asyncio.Semaphore(settings.concurrency)
|
||||||
|
all_results: list[CrawlResult] = []
|
||||||
|
|
||||||
|
async with AsyncWebCrawler(config=browser_config) as crawler:
|
||||||
|
for source in enabled:
|
||||||
|
try:
|
||||||
|
results = await crawl_source(crawler, source, settings, semaphore, save=save)
|
||||||
|
all_results.extend(results)
|
||||||
|
except Exception as e: # 单源异常不应中断整体抓取
|
||||||
|
logger.exception("源 {} 抓取异常: {}", source.id, e)
|
||||||
|
|
||||||
|
total = len(all_results)
|
||||||
|
succ = sum(1 for r in all_results if r.success)
|
||||||
|
logger.info("全部完成: {}/{} 成功 (成功率 {:.0%})", succ, total, succ / max(total, 1))
|
||||||
|
return all_results
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
"""新闻抓取模块的数据模型与配置模型。
|
||||||
|
|
||||||
|
所有核心对象统一使用 Pydantic 定义(CLAUDE.md 第十条要求)。
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from enum import StrEnum
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field, HttpUrl, field_validator
|
||||||
|
|
||||||
|
|
||||||
|
class CrawlStage(StrEnum):
|
||||||
|
"""抓取阶段枚举,区分列表页和文章页。"""
|
||||||
|
|
||||||
|
LIST = "list" # 列表/首页(用于发现文章链接)
|
||||||
|
ARTICLE = "article" # 文章详情页
|
||||||
|
|
||||||
|
|
||||||
|
class SourceConfig(BaseModel):
|
||||||
|
"""单个新闻源的配置。
|
||||||
|
|
||||||
|
映射 configs/sources.yaml 中 sources 列表的每一项。
|
||||||
|
"""
|
||||||
|
|
||||||
|
id: str = Field(..., description="新闻源短 ID,用作目录名,仅小写字母与下划线")
|
||||||
|
name: str = Field(..., description="中文名,用于日志展示")
|
||||||
|
enabled: bool = Field(default=True, description="是否启用")
|
||||||
|
homepage: HttpUrl = Field(..., description="新闻列表页/首页 URL")
|
||||||
|
extra_homepages: list[HttpUrl] = Field(
|
||||||
|
default_factory=list,
|
||||||
|
description="额外的列表页 URL(同站多频道时使用),共用同一个 article_url_pattern",
|
||||||
|
)
|
||||||
|
|
||||||
|
def all_homepages(self) -> list[str]:
|
||||||
|
"""返回所有入口 URL(主入口 + 额外入口)。"""
|
||||||
|
urls = [str(self.homepage)]
|
||||||
|
urls.extend(str(u) for u in self.extra_homepages)
|
||||||
|
return urls
|
||||||
|
|
||||||
|
# 链接发现规则
|
||||||
|
article_url_pattern: str = Field(
|
||||||
|
...,
|
||||||
|
description="正则表达式,从首页所有链接中筛选出文章 URL",
|
||||||
|
)
|
||||||
|
article_link_selector: str | None = Field(
|
||||||
|
default=None,
|
||||||
|
description="可选 CSS 选择器,优先在选择器内查找文章链接",
|
||||||
|
)
|
||||||
|
|
||||||
|
# JS 渲染相关
|
||||||
|
js_render: bool = Field(
|
||||||
|
default=True,
|
||||||
|
description="是否需要 JS 渲染(财联社/东方财富等动态站点必须为 true)",
|
||||||
|
)
|
||||||
|
wait_for: str | None = Field(
|
||||||
|
default=None,
|
||||||
|
description="可选 CSS,等待该元素出现后再抓取(JS 渲染时使用)",
|
||||||
|
)
|
||||||
|
page_timeout_ms: int = Field(default=30_000, ge=1_000, description="页面加载超时(毫秒)")
|
||||||
|
|
||||||
|
# GNE 提取提示(可选)
|
||||||
|
body_xpath: str | None = Field(
|
||||||
|
default=None,
|
||||||
|
description="GNE body_xpath 参数,强制指定正文容器 XPath",
|
||||||
|
)
|
||||||
|
|
||||||
|
# 抓取数量限制
|
||||||
|
max_articles_per_run: int = Field(
|
||||||
|
default=20,
|
||||||
|
ge=1,
|
||||||
|
le=200,
|
||||||
|
description="单次运行最多抓取的文章数",
|
||||||
|
)
|
||||||
|
|
||||||
|
@field_validator("id")
|
||||||
|
@classmethod
|
||||||
|
def _validate_id(cls, v: str) -> str:
|
||||||
|
if not v.replace("_", "").isalnum() or not v.islower():
|
||||||
|
raise ValueError("source.id 必须为小写字母/数字/下划线")
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
|
class CrawlerSettings(BaseModel):
|
||||||
|
"""全局抓取设置。"""
|
||||||
|
|
||||||
|
concurrency: int = Field(default=3, ge=1, le=20, description="并发抓取上限")
|
||||||
|
retry_max_attempts: int = Field(default=3, ge=1, le=10, description="单 URL 最大重试次数")
|
||||||
|
retry_min_wait_sec: float = Field(default=1.0, ge=0.0, description="重试最小等待秒")
|
||||||
|
retry_max_wait_sec: float = Field(default=10.0, ge=0.0, description="重试最大等待秒")
|
||||||
|
user_agent: str = Field(
|
||||||
|
default=(
|
||||||
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 "
|
||||||
|
"(KHTML, like Gecko) Chrome/124.0 Safari/537.36"
|
||||||
|
),
|
||||||
|
description="浏览器 User-Agent",
|
||||||
|
)
|
||||||
|
headless: bool = Field(default=True, description="浏览器是否无头")
|
||||||
|
output_root: str = Field(default="data/raw", description="抓取结果根目录")
|
||||||
|
|
||||||
|
|
||||||
|
class CrawlerConfig(BaseModel):
|
||||||
|
"""整个抓取系统的配置(对应 sources.yaml 顶层)。"""
|
||||||
|
|
||||||
|
settings: CrawlerSettings = Field(default_factory=CrawlerSettings)
|
||||||
|
sources: list[SourceConfig]
|
||||||
|
|
||||||
|
def enabled_sources(self) -> list[SourceConfig]:
|
||||||
|
"""返回启用的源,按 id 排序。"""
|
||||||
|
return sorted([s for s in self.sources if s.enabled], key=lambda s: s.id)
|
||||||
|
|
||||||
|
|
||||||
|
class CninfoItem(BaseModel):
|
||||||
|
"""cninfo 公告/调研/互动易数据条目。
|
||||||
|
|
||||||
|
一条记录对应一条公告、一次调研活动或一组互动问答。
|
||||||
|
"""
|
||||||
|
|
||||||
|
stock_code: str = Field(..., description="股票代码,6 位数字")
|
||||||
|
stock_name: str = Field(..., description="公司简称")
|
||||||
|
title: str = Field(..., description="标题")
|
||||||
|
content: str = Field(default="", description="正文/摘要内容")
|
||||||
|
publish_time: str = Field(default="", description="发布时间,格式 YYYY-MM-DD")
|
||||||
|
item_type: str = Field(
|
||||||
|
default="announcement",
|
||||||
|
description="数据类型: announcement(公告)|research(调研)|irm(互动易)",
|
||||||
|
)
|
||||||
|
url: str = Field(default="", description="详情页 URL 或 PDF 链接")
|
||||||
|
ann_id: str = Field(default="", description="公告唯一 ID,用于增量去重")
|
||||||
|
extra: dict = Field(default_factory=dict, description="附加字段(公告类型等)")
|
||||||
|
|
||||||
|
|
||||||
|
class ArticleLink(BaseModel):
|
||||||
|
"""从列表页发现的文章链接。"""
|
||||||
|
|
||||||
|
source_id: str
|
||||||
|
url: str
|
||||||
|
anchor_text: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class CrawlResult(BaseModel):
|
||||||
|
"""单次抓取结果(列表页或文章页通用)。"""
|
||||||
|
|
||||||
|
source_id: str
|
||||||
|
stage: CrawlStage
|
||||||
|
url: str
|
||||||
|
success: bool
|
||||||
|
status_code: int | None = None
|
||||||
|
title: str | None = None
|
||||||
|
html: str = ""
|
||||||
|
markdown: str = ""
|
||||||
|
error: str | None = None
|
||||||
|
fetched_at: datetime = Field(default_factory=datetime.now)
|
||||||
|
attempts: int = 1
|
||||||
|
|
||||||
|
def short_summary(self) -> str:
|
||||||
|
"""生成单行摘要,便于日志输出。"""
|
||||||
|
flag = "OK" if self.success else "FAIL"
|
||||||
|
size = len(self.html)
|
||||||
|
return f"[{flag}] {self.source_id} {self.stage.value} {self.url} html={size}B"
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
"""抓取结果本地保存。
|
||||||
|
|
||||||
|
目录结构:
|
||||||
|
{output_root}/{source_id}/{YYYYMMDD}/
|
||||||
|
├── {url_hash}.html # 原始 HTML
|
||||||
|
├── {url_hash}.md # Crawl4AI 输出的 Markdown
|
||||||
|
└── index.jsonl # 元数据(每行一条 CrawlResult)
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
from datetime import date
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
from .models import CrawlResult
|
||||||
|
|
||||||
|
|
||||||
|
def url_hash(url: str) -> str:
|
||||||
|
"""对 URL 取 SHA1,取前 16 位作为文件名。"""
|
||||||
|
return hashlib.sha1(url.encode("utf-8")).hexdigest()[:16]
|
||||||
|
|
||||||
|
|
||||||
|
def build_output_dir(output_root: str | Path, source_id: str, day: date | None = None) -> Path:
|
||||||
|
"""构造保存目录: {output_root}/{source_id}/{YYYYMMDD}/。"""
|
||||||
|
target_day = day or date.today()
|
||||||
|
return Path(output_root) / source_id / target_day.strftime("%Y%m%d")
|
||||||
|
|
||||||
|
|
||||||
|
def save_result(
|
||||||
|
result: CrawlResult,
|
||||||
|
output_root: str | Path,
|
||||||
|
day: date | None = None,
|
||||||
|
*,
|
||||||
|
skip_existing: bool = True,
|
||||||
|
) -> Path | None:
|
||||||
|
"""保存单条抓取结果到本地。
|
||||||
|
|
||||||
|
返回 HTML 文件路径;失败结果只追加 index.jsonl,不写 HTML/MD,返回 None。
|
||||||
|
|
||||||
|
skip_existing=True 时:如果当日该 URL 已保存(HTML 文件已存在),跳过写入,
|
||||||
|
仅记录日志,返回已存在路径。保证增量抓取不会重复保存。
|
||||||
|
"""
|
||||||
|
out_dir = build_output_dir(output_root, result.source_id, day)
|
||||||
|
out_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
h = url_hash(result.url)
|
||||||
|
html_path: Path | None = None
|
||||||
|
|
||||||
|
# 增量去重:如果当日已抓取过同一 URL,跳过
|
||||||
|
existing_html = out_dir / f"{h}.html"
|
||||||
|
if skip_existing and existing_html.exists():
|
||||||
|
logger.debug("跳过重复 URL (今日已抓取): {} -> {}", result.url, h)
|
||||||
|
return existing_html
|
||||||
|
|
||||||
|
if result.success and result.html:
|
||||||
|
html_path = out_dir / f"{h}.html"
|
||||||
|
html_path.write_text(result.html, encoding="utf-8")
|
||||||
|
|
||||||
|
if result.markdown:
|
||||||
|
md_path = out_dir / f"{h}.md"
|
||||||
|
md_path.write_text(result.markdown, encoding="utf-8")
|
||||||
|
|
||||||
|
# 元数据(不含 html/markdown 大字段,避免 jsonl 巨大)
|
||||||
|
meta = result.model_dump(exclude={"html", "markdown"}, mode="json")
|
||||||
|
meta["url_hash"] = h
|
||||||
|
meta["html_file"] = html_path.name if html_path else None
|
||||||
|
|
||||||
|
index_path = out_dir / "index.jsonl"
|
||||||
|
with index_path.open("a", encoding="utf-8") as f:
|
||||||
|
f.write(json.dumps(meta, ensure_ascii=False) + "\n")
|
||||||
|
|
||||||
|
logger.debug("已保存抓取结果: {}", result.short_summary())
|
||||||
|
return html_path
|
||||||
|
|
||||||
|
|
||||||
|
def load_seen_urls(source_id: str, output_root: str | Path = "data/raw") -> set[str]:
|
||||||
|
"""加载某个源已抓取的所有 URL hash(跨日期累积)。
|
||||||
|
|
||||||
|
读取 {output_root}/{source_id}/seen_urls.txt,每行一个 url_hash。
|
||||||
|
"""
|
||||||
|
seen_path = Path(output_root) / source_id / "seen_urls.txt"
|
||||||
|
if not seen_path.is_file():
|
||||||
|
return set()
|
||||||
|
with seen_path.open("r", encoding="utf-8") as f:
|
||||||
|
return {line.strip() for line in f if line.strip()}
|
||||||
|
|
||||||
|
|
||||||
|
def mark_url_seen(source_id: str, url: str, output_root: str | Path = "data/raw") -> None:
|
||||||
|
"""追加一个已抓取 URL 的 hash 到 seen_urls.txt。"""
|
||||||
|
seen_path = Path(output_root) / source_id / "seen_urls.txt"
|
||||||
|
seen_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
h = url_hash(url)
|
||||||
|
with seen_path.open("a", encoding="utf-8") as f:
|
||||||
|
f.write(h + "\n")
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"source_id": "cls",
|
||||||
|
"url": "https://www.cls.cn/detail/2401393",
|
||||||
|
"url_hash": "0df0d76545158447",
|
||||||
|
"title": "DeepSeek首轮融资或落地 梁文锋个人出资约200亿元",
|
||||||
|
"content": "《科创板日报》16日讯,《科创板日报》记者从多家投资机构获悉,DeepSeek首轮融资目前或已敲定。创始人梁文锋个人出资约200亿元,为本轮融资中最大单一出资方;腾讯出资约100亿元;宁德时代体系出资约50亿元,包括宁德时代及溥泉资本;网易、京东、Monolith砺思资本、IDG资本分别出资约30亿元;正心谷投资、拾象科技分别出资约15亿元。 (科创板日报记者 徐杰 敖瑾)",
|
||||||
|
"author": null,
|
||||||
|
"source_name": "财联社",
|
||||||
|
"publish_time": "2026-06-16T18:47:41",
|
||||||
|
"publish_time_raw": "2026年06月16日 18:47:41",
|
||||||
|
"extracted_at": "2026-06-16T21:09:08.039269",
|
||||||
|
"images": [],
|
||||||
|
"word_count": 132
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"source_id": "cls",
|
||||||
|
"url": "https://www.cls.cn/detail/2401070",
|
||||||
|
"url_hash": "147ca0fecd079f25",
|
||||||
|
"title": "是否有中国船只已通过霍尔木兹海峡?外交部回应",
|
||||||
|
"content": "6月16日,外交部发言人林剑主持例行记者会。\n法新社记者提问,美国和伊朗都表示船只已经开始通过霍尔木兹海峡,中方昨天也表示希望海峡尽早恢复通行。请问在美伊宣布达成协议之后,有没有中国船只通过霍尔木兹海峡?中方对海峡通行情况有何评论?\n林剑表示,中方注意到伊美双方已达成的第一阶段谅解备忘录包含重开海峡等内容,霍尔木兹海峡是用于国际航行的海峡,尽早恢复海峡的安全自由通行符合各方利益。中方愿同有关国家和国际社会就此保持沟通。\n林剑说,关于你提到的中方船舶的问题,中方一直同各方保持沟通,将继续全力维护中资船舶和中国籍船员的安全。",
|
||||||
|
"author": "王天勇外交部发言人林剑表示",
|
||||||
|
"source_name": "财联社",
|
||||||
|
"publish_time": "2026-06-16T14:50:00",
|
||||||
|
"publish_time_raw": "2026-06-16 14:50",
|
||||||
|
"extracted_at": "2026-06-16T21:09:06.662375",
|
||||||
|
"images": [],
|
||||||
|
"word_count": 240
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"source_id": "cls",
|
||||||
|
"url": "https://www.cls.cn/detail/2401197",
|
||||||
|
"url_hash": "1908fe370193ea75",
|
||||||
|
"title": "世界黄金协会调查:全球央行增持意愿创纪录 黄金战略储备地位进一步提升",
|
||||||
|
"content": "财联社6月16日讯(编辑 牛占林)\n世界黄金协会(WGC)周二发布的《2026年全球央行黄金储备调查报告》显示,受访央行储备管理者中有45%预计未来12个月将增加黄金持有量,该比例较上年上升2个百分点,创下历史新高。\n在参与此次年度调查的74家央行中,54%的受访者表示其黄金储备将维持不变,仅有1%预计将减少持仓。此次调查于今年2月5日至5月19日期间进行。\n大多数问卷回复是在2月底中东冲突爆发后收到的,这场冲突引发油价上涨,并导致金价承压下挫。\n此次调查发布之际,黄金正处于一个具有里程碑意义的时刻。世界黄金协会指出,黄金近期已超越美国国债,成为全球最大的储备资产,这凸显出官方机构在储备资产配置理念上的重大转变。\n世界黄金协会央行部门主管Shaokai Fan表示,各国央行对黄金依然保持浓厚兴趣,近期金价回落并未改变其增持黄金的意愿。“各国央行依然非常看好黄金,事实上比以往任何时候都更加积极。”\n美国和伊朗上周末就结束战争并重启霍尔木兹海峡通航达成协议条款,推动金价周一大涨3%。\n咨询机构Metals Focus预计,2026年全球央行黄金需求按吨计算将同比下降15%,但仍将高于2022年以前的水平,继续为黄金市场提供持续支撑。\n战略性储备资产\n世界黄金协会的调查结果表明,越来越多央行正将黄金视为一种战略性储备资产,而不再仅仅把它当作历史遗留的被动储备资产。84%的受访者预计,未来五年黄金在全球储备资产中的占比将进一步上升;与此同时,74%的受访者预计美元储备占比将在同期下降。\n这一结果进一步印证了过去十年来全球储备管理领域发生的深刻变化。过去四年,全球央行年均购买黄金约1000吨,是此前十年平均水平的两倍。\nShaokai Fan表示,当前最值得关注的变化之一是,越来越多央行开始加入购金行列。“我们正在看到一些新的央行买家出现。”他列举了印尼、马来西亚、危地马拉等国家。这些国家近期开始进入黄金市场,或在多年暂停购金后重新恢复购买。\nFan声称,在地缘政治和经济不确定性持续上升的背景下,各国央行内部关于黄金配置的讨论明显增多,储备管理者正重新评估如何实现资产组合多元化。\n在持有黄金的主要原因中,90%的受访者认为,黄金在危机时期表现良好。其他主要原因还包括长期价值储藏功能以及资产组合多元化需求。对于新兴市场和发展中经济体而言,黄金作为地缘政治风险对冲工具的重要性尤为突出,85%的相关受访者选择了这一因素。\n与此同时,部分央行继续推进黄金储备存放地点调整。调查显示,过去12个月内,有9%的受访央行增加了国内黄金储存规模,高于去年的5%;另有10%的央行分散了海外黄金储存地点,高于去年的2%。\n展望未来12个月,7%的受访者计划进一步增加国内储存,9%计划扩大海外储存地点的多元化布局。\n多家投行看好黄金\n与此同时,多家投行发布了看多黄金的报告。巴克莱认为,近期金价在中东冲突升级后出现20%-25%的大幅回调,并不意味着黄金长期牛市逻辑被破坏,而更像是一轮由市场仓位调整引发的“技术性修正”。因此,该行维持对黄金的长期看涨判断,预计2026年和2027年金价分别达到每盎司4791美元和4900美元。\n巴克莱指出,美元重新走弱以及全球央行恢复稳定购金将成为推动金价反弹的两大关键催化剂。虽然短期内仍可能面临波动,但从中长期来看,黄金牛市逻辑依然完好。\n另一家机构花旗表示,在霍尔木兹海峡危机解除后,全球通胀压力将得到缓解,这将打压美联储加息预期,为金价打开上行通道。\n花旗维持黄金6至12个月5000美元/盎司的看涨预测,但同时警告金价仍面临较大波动风险。",
|
||||||
|
"author": "林琦",
|
||||||
|
"source_name": "财联社",
|
||||||
|
"publish_time": "2026-06-16T16:29:00",
|
||||||
|
"publish_time_raw": "2026-06-16 16:29",
|
||||||
|
"extracted_at": "2026-06-16T21:09:08.356021",
|
||||||
|
"images": [
|
||||||
|
"https://image.cls.cn/images/20260616/KW3oGB01k4_884x412.png"
|
||||||
|
],
|
||||||
|
"word_count": 1230
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"source_id": "yicai",
|
||||||
|
"url": "https://www.yicai.com/news/103232322.html",
|
||||||
|
"url_hash": "1e573f9fa7fc1596",
|
||||||
|
"title": "全球镜场最大光热电站在青海开工,核心技术100%自主可控",
|
||||||
|
"content": "第一财经记者从中国广核集团(下称“中广核”)获悉,6月16日,中广核格尔木350兆瓦(即35万千瓦)光热示范项目在青海省格尔木市乌图美仁光伏光热园区正式开工建设。这也是目前全球单机储热容量最大、镜场面积最大的光热电站。\n据中广核介绍,中广核格尔木350兆瓦光热示范项目镜场总采光面积达370万平方米,相当于518个标准足球场的大小,包括3个110万平方米的塔式镜场和1个40万平方米的槽式镜场。其中,槽式镜场全部采用中广核自主研发的8.6米大开口槽式集热器。\n中国广核新能源控股有限公司副总经理丁业良介绍,该槽式集热技术于今年4月21日在德令哈光热试验基地完成技术验证,聚光比达107.5倍,可实现290摄氏度进口至550摄氏度出口的稳定运行,储能温差达260摄氏度,是传统导热油系统的2.6倍。其整套装备的核心部件——包括集热器支架、柔性连接组件、就地控制器和支架精度面形检测装置等,均由中广核牵头国内产业链优势企业完成攻关,实现核心技术100%自主可控。\n值得一提的是,项目配套15小时大容量熔盐储热装置,储热容量为11747兆瓦时,为目前单机储热容量最大的光热发电项目,可实现稳定发电,具备优异的电网灵活调峰能力,能够有效提升青海电网新能源消纳效率与全域供电保障能力。建成后预计年发电量可达10亿千瓦时,等效节约标煤消耗32万吨、减排二氧化碳86万吨。\n光热发电技术不仅能为电网提供稳定的电力供应,还具备储能功能,能够在需要时释放电力,成为新能源领域安全可靠替代传统能源的有效方案。2025年1月1日起实施的《中华人民共和国能源法》明确提出“积极发展光热发电”,为光热发电的后续发展奠定了基调。\n国家能源局数据显示,2025年,全国光热发电新增装机94万千瓦,同比增长203%。截至2025年12月,全国光热发电装机容量达到182万千瓦,同比增长107%。光热发电量16亿千瓦时,同比增长32%。\n据2025年2月发布的《2024中国太阳能热发电行业蓝皮书》(下称《蓝皮书》),目前中国光热发电产业链单位数量已相当庞大,总计约44万家,其中包括国有企业1.4万家、民营企业42.1万家、外商投资企业3000家以及小微企业2.35万家。\n2025年,国家发改委、国家能源局印发《关于促进光热发电规模化发展的若干意见》,提出到2030年,全国光热发电发展总装机规模力争达到1500万千瓦左右,度电成本与煤电基本相当,技术实现国际领先并完全自主可控,行业实现自主市场化、产业化发展,成为新能源领域具有国际竞争优势的新产业。\n据国家能源局有关负责人去年介绍,经过多年发展,中国已成功掌握塔式、槽式、菲涅尔式等主流光热发电技术,已建成全球领先的光热发电产业链,电站单位千瓦建设成本从10年前的约3万元下降至1.5万元,度电成本降至0.6元上下,光热发电已初步具备规模化发展的基础。但另一方面,中国光热发展还面临初始投资大、市场竞争能力偏弱、系统支撑调节价值未充分体现、产业技术水平仍需提升的问题。",
|
||||||
|
"author": "秦新安",
|
||||||
|
"source_name": "第一财经",
|
||||||
|
"publish_time": "2026-06-16T00:00:00",
|
||||||
|
"publish_time_raw": "2026-06-16",
|
||||||
|
"extracted_at": "2026-06-16T21:09:23.607553",
|
||||||
|
"images": [
|
||||||
|
"https://imgcdn.yicai.com/uppics/images/2026/06/9af01d33a22bc606235877842d494402.jpg",
|
||||||
|
"https://imgcdn.yicai.com/uppics/images/2026/06/33c8c2c2e28ae823ca8312b2ba688255.jpg"
|
||||||
|
],
|
||||||
|
"word_count": 996
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"source_id": "yicai",
|
||||||
|
"url": "https://www.yicai.com/news/103232884.html",
|
||||||
|
"url_hash": "20b1101ad2e6cb5d",
|
||||||
|
"title": "夏日经济|今夏雪糕继续平价化,新品上市数量减半",
|
||||||
|
"content": "随着夏季气温攀升,雪糕市场进入传统销售旺季。记者近日走访北京部分零售终端发现,今年夏天雪糕价格延续了去年的平价化趋势,1元到5元区间的产品占据主流货架。\n与此同时,受健康化、低糖化饮食风潮影响,冷饮市场的整体热度较前几年有所降温,企业推新的节奏更加理性,新品上市数量同比大幅缩减。\n雪糕平价化持续\n夏初的北京气温已经升至30度以上,记者在一家位于大型社区附近的冷饮批发铺看到,冰柜里整齐码放着各类雪糕,其中3元/支以下的产品占据了大部分空间。店员告诉记者,今年冷饮生意才刚刚启动,但由于门店位置好,客流量稳定,目前销量与去年同期基本持平,畅销款大多是1元到5元/支的老冰棍、冰工厂等经典产品。\n而在周边的多家品牌便利店里,雪糕客单价相对较高,但主流价格带也已下移至10元/支以下。有店员透露,店里也出售30元档的高端冰淇淋,但实际走量最快的还是5元/支左右的小规格低价产品。\n市场数据上也能看到这一变化。马上赢市场调查数据显示,雪糕类目组合装每百克均价从2023年6月-2024年5月时间段的2.92元/每百克,下降至2025年6月-2026年5月时间段的2.22元/每百克,降幅约为24%;雪糕类目的非组合装产品,以2026年5月为基点的过去两年每百克均价基本持平,但与2023-2024同期相比下降了5%左右。\n马上赢市场调查首席运营官刘巍巍对第一财经记者表示,雪糕价格走向平价,背后有多重因素:一方面消费者日趋务实,对网红概念和营销噱头的兴趣减弱;另一方面雪糕本身含糖量较高,健康化浪潮让部分消费者在选择时更加审慎;此外,品牌企业之间的价格竞争也在客观上拉低了终端售价。\n而且今年雪糕新品的定价策略也更加理性。报告显示,以2026年5月为基点,过去一年的组合装新品定价大多数在20元/组以下,约占76.3%,非组合装6元/支以下的新品占到74.9%。\n蒙牛乳业作为2026年FIFA世界杯官方冰淇淋,今年推出了4款世界杯专属新品。记者在国内电商平台上看到,其中三款在售组合装产品的零售价格约合每支3元至5元,定价更偏向大众化。\n大众化并非低价化\n记者注意到,虽然今年雪糕市场继续走向平价化,但并非完全低价化,而是更加理性。\n马上赢数据显示,2026年雪糕新品上市数量出现大幅下滑,2025年1月至3月,雪糕新品上市总量接近1800款,而2026年同期则不足900款,减少将近一半。刘巍巍解释称,前几年冷饮市场热度较高且竞争激烈,大量新品涌入后市场表现平平,企业因此在上新决策上更加谨慎。\n与此同时,在2026年上市的雪糕新品中,3元-6元档的新品增长较快,0-3元档的新品占比明显减少,同时企业的创新方向也有所调整。\n雀巢中国回应第一财经表示,今年国内冷饮市场有一个明显的变化,消费者选择变得更加理性、成熟,正在从单一关注价格,转向更综合地评估产品的“质价比”。这也让2026年冷饮市场转向更加精细化、分层化的发展阶段。品牌既要以稳定品质和合理价格满足大众需求,也需要通过产品创新、品牌表达和场景渗透,持续创造消费者愿意选择的理由。\n蒙牛乳业也认为,近几年中国冰淇淋市场已完成从“规模扩张”到“价值升级”的关键转型,消费者需求从单纯的消暑解渴,全面转向情绪疗愈、健康品质和社交体验。因此,今年雪糕新品的研发更有针对性:一方面重点布局零食化、甜品化等新趋势下的创新机会,另一方面对现有品类进行升级迭代,谋求差异化竞争。据蒙牛方面透露,截至今年5月,新品终端表现已超出预期。\n战略定位专家、福建华策品牌定位咨询创始人詹军豪在接受第一财经记者采访时表示,今年国内冰淇淋市场正在褪去炒作热潮,进入冷静调整期。新品数量大幅缩减,说明企业摒弃了盲目上新的旧思路,转而聚焦平价刚需,围绕实用品质升级做文章。未来雪糕市场不再是拼噱头的时代,依靠质价比和品质优化稳住客流的品牌,才能持续在市场站稳脚跟。",
|
||||||
|
"author": "陈姗姗",
|
||||||
|
"source_name": "第一财经",
|
||||||
|
"publish_time": "2026-06-16T00:00:00",
|
||||||
|
"publish_time_raw": "2026-06-16",
|
||||||
|
"extracted_at": "2026-06-16T21:09:26.899561",
|
||||||
|
"images": [],
|
||||||
|
"word_count": 1328
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"source_id": "eastmoney",
|
||||||
|
"url": "https://finance.eastmoney.com/a/202606163771934862.html",
|
||||||
|
"url_hash": "2137efc441a69128",
|
||||||
|
"title": "亿纬锂能上半年业绩预增:实施前置管理应对供应链成本压力",
|
||||||
|
"content": "在2025年年度报告中,亿纬锂能曾明确提出,通过\n期货\n市场套期保值工具,能够实现主要原材料成本的整体可控;同时针对外汇风险,公司也制定了相关业务管理制度来锁定汇兑成本。不仅如此,在一季报中,公司同样强调了面对供应链成本压力,审慎运用金融工具所发挥的关键缓冲作用。",
|
||||||
|
"author": "每日经济新闻",
|
||||||
|
"source_name": "东方财富",
|
||||||
|
"publish_time": "2026-06-16T00:17:00",
|
||||||
|
"publish_time_raw": "2026年06月16日 00:17",
|
||||||
|
"extracted_at": "2026-06-16T21:09:09.337040",
|
||||||
|
"images": [],
|
||||||
|
"word_count": 116
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"source_id": "eastmoney",
|
||||||
|
"url": "https://finance.eastmoney.com/a/202606163771952617.html",
|
||||||
|
"url_hash": "2cae0b0f99501ec9",
|
||||||
|
"title": "“七巨头”已过时?SpaceX爆火后 华尔街机构推“FAB 10”新概念",
|
||||||
|
"content": "专业金融数据,下一代智能终端\nSpaceX\n上周五的IPO在散户的追捧下获得了巨大的成功,也引发了市场重新定义整个科技行业的思考。\n根据Vanda Research上周日发布的分析报告,\nSpaceX\n上市首日吸引散户净买入1.17亿美元,占当天美国股市所有散户股票买盘的56%。\n需要指出的是,Vanda统计的1.17亿美元散户买盘,仅反映上市首日二级市场的买入情况,并不包括通过券商参与IPO配售的大量散户资金。\n另有数据称,在规模达750亿美元的IPO中,散户最终获得约20%的配售份额,高于平均水平;对冲基金获得10%,而长期持有型机构投资者则拿下70%。\n市场对\nSpaceX\n的狂热追捧,再次凸显投资者资金正持续向少数超大市值科技公司集中。这些企业不仅主导着股市表现,也在推动整个科技投资浪潮。\n随着SpaceX正式跻身这一行列,以及未来OpenAI和Anthropic预计陆续上市,华尔街认为原有的“七巨头”(Magnificent Seven)概念已经不再适用。\n所谓“美股七巨头”,是指\n英伟达\n、谷歌、\n苹果\n、\n微软\n、\n亚马逊\n、\n特斯拉\n和Meta。\nVanda表示:“如果过去几年市场由‘七巨头’主导,那么上周五可能是最明确的信号——投资者正开始聚焦我们所称的‘FAB 10’(Frontier AI & Big Tech 10,10家前沿AI与科技巨头)。”\n根据Vanda的说法,FAB 10包括原有七巨头,再加上SpaceX、OpenAI和Anthropic。后两家公司尚未上市,但市场预计它们将在今年晚些时候登陆资本市场,估值可能达到数千亿美元规模。\nVanda认为:“这些公司共同代表着未来十年\n人工智能\n与科技产业的发展方向。”\n值得一提的是,\n美国银行\n首席策略师Michael Hartnett也曾在《投资宇宙指南》提出过一个“AI Big 10”的组合,与FAB 10不同的是,美银更青睐七巨头加\n博通\n、AMD和美光。\nVanda的研究人员指出,SpaceX的火爆可能正在从其他热门板块吸走资金。尤其是此前经历大涨的芯片股,可能正在失去散户投资者的青睐。\n报告写道:“曾经主导散户买盘的\n半导体\n股票,如今越来越成为投资者为新机会腾挪资金的来源。”\n不过,不少华尔街人士认为科技板块估值已经出现泡沫迹象,如果OpenAI和Anthropic上市后表现不佳,整个行业可能都需要重新评估。\nWestwood Capital的创始管理合伙人Dan Alpert表示:“从根本上说,市场缺乏足够优质的资本投向。如果这一轮被高度追捧的IPO最终表现不佳,投资者将重新评估整个科技板块的估值水平。”\n东财图解·加点干货\n想炒股,先开户!选东方财富证券,行情交易一个APP搞定>>\n(文章来源:财联社)",
|
||||||
|
"author": "财联社",
|
||||||
|
"source_name": "东方财富",
|
||||||
|
"publish_time": "2026-06-16T03:19:00",
|
||||||
|
"publish_time_raw": "2026年06月16日 03:19",
|
||||||
|
"extracted_at": "2026-06-16T21:09:10.596833",
|
||||||
|
"images": [
|
||||||
|
"https://webquoteklinepic.eastmoney.com/GetPic.aspx?nid=105.SPCX&imageType=knews&token=28dfeb41d35cc81d84b4664d7c23c49f&at=1",
|
||||||
|
"https://np-newspic.dfcfw.com/download/D25376985640347191054_w1003h866.jpg",
|
||||||
|
"https://np-newspic.dfcfw.com/download/D24832930842412850501_w690h389.jpg",
|
||||||
|
"https://np-newspic.dfcfw.com/download/D24969434611392508183_w1620h2880_o.jpg"
|
||||||
|
],
|
||||||
|
"word_count": 748
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"source_id": "yicai",
|
||||||
|
"url": "https://www.yicai.com/news/103232831.html",
|
||||||
|
"url_hash": "362d99878d5f13c4",
|
||||||
|
"title": "韩国5000亿韩元押注功率半导体,年内A股多起并购密集落地",
|
||||||
|
"content": "近日,韩国政府正式启动“超级创新经济项目”,计划投入5000亿韩元(约合22.3亿元人民币)国家资金专项攻关下一代功率半导体技术。韩国正将功率半导体定位为堪比存储芯片的核心战略产业。\nAI算力需求正带动功率半导体进入新一轮景气周期。今年以来,银河微电、蓝箭电子、东微半导、锴威特、晶丰明源、紫光国微等多家上市公司密集披露并购公告,布局或扩张模拟功率芯片相关业务。与此同时,龙头企业英飞凌、德州仪器也在最近宣布开启年内第二轮全面涨价。\n模拟功率芯片资产并购潮起\n6月12日,银河微电公告称,公司正在筹划通过发行股份方式,购买恒泰柯半导体(上海)有限公司100%的股权,并募集配套资金。根据官网,恒泰柯主营业务是功率半导体芯片的设计和销售,产品应用于各类电源、锂电池保护、新能源汽车(OBC、电控)等领域。\n同日,蓝箭电子公告称,拟以3.36亿元收购成都芯翼科技有限公司60%的股权。根据公告,成都芯翼是一家专注于高可靠模拟集成电路研发、生产及销售的国家级专精特新“小巨人”企业,重点研发产品包括通信接口芯片、模拟信号链芯片等。\n2026年以来披露收购模拟/功率芯片企业的上市公司还包括东微半导、锴威特、晶丰明源、紫光国微、杰华特、芯导科技等多家企业。\n分析以上并购案例,A股公司收购模拟/功率半导体标的具有差异化:一部分企业以横向整合、扩充产品线为导向,另一部分企业则采取上下游打通策略,打造一体化产业平台。\n放眼全球,国际龙头同样在重金布局。5月,全球模拟芯片巨头ADI宣布以15亿美元全现金收购硅谷电源管理初创公司Empower Semiconductor。Empower年营收仅约5400万美元,对应28倍营收估值。高溢价的筹码是Empower的IVR(集成电压调节器)与垂直供电架构技术。该技术可将稳压器直接安装在AI芯片下方或内部,缩短传输距离。\n在最新公布的全球模拟IC销售额排名中,ADI位居第二,仅次于德州仪器(Texas Instruments)。ADI首席执行官Vincent Roche表示:“人工智能基础设施正在从根本上重塑电力输送方式,能源如今已成为下一代系统扩展的最大瓶颈。”\n年内第二轮涨价来临\n在市场并购活跃的同时,模拟芯片与功率半导体行业迎来了全面涨价潮。\n今年年初以来,英飞凌、德州仪器、安森美、意法半导体等龙头企业密集宣布涨价,国内企业如士兰微、新洁能等纷纷跟进。进入5月底,功率半导体行业迎来第二轮涨价。英飞凌再次发出涨价函,宣布自7月1日起进行二次提价,幅度10%~20%,覆盖AI电源芯片、车规IGBT/MOSFET;德州仪器也再次发出涨价函,新价格将适用于自2026年7月1日起生效的所有订单及出货。财报显示,德州仪器第一季度数据中心收入同比增长约90%,AI需求正从GPU扩展至电源管理、服务器电源系统和高压MOSFET等领域。\n全球功率半导体市场正经历新一轮供需震荡。在供应端,据TrendForce数据,由于台积电、三星持续削减八英寸成熟制程产能,全球八英寸产能至2027年上半年将维持负增长;而在需求端,AI服务器对电源管理、功率芯片需求正持续走强。\n5月底,美银发布的一篇名为《AI Power Semis and the Transition to 800V Data Centers》(AI功率半导体与向800V数据中心的转型)的报告显示,随着AI对电力需求的不断增长,模拟半导体市场将迎来爆发式增长,其中高压组件(SiC、GaN)将占据越来越重要的地位。\n开源证券海外市场首席分析师初敏则认为,2025年英伟达台北电脑展发布800VDC架构,认定其为未来数据中心的架构,带来电力设备升级的热潮。复盘海外龙头股价及业绩,功率半导体股价具有明显周期属性,上一轮由汽车缺芯和新能源驱动的周期大约持续三年(2020~2023年上行期),而2026年以来AI数据中心电力需求开始成为新一轮定价主线。",
|
||||||
|
"author": "宁佳彦",
|
||||||
|
"source_name": "第一财经",
|
||||||
|
"publish_time": "2026-06-16T00:00:00",
|
||||||
|
"publish_time_raw": "2026-06-16",
|
||||||
|
"extracted_at": "2026-06-16T21:09:27.080302",
|
||||||
|
"images": [
|
||||||
|
"https://imgcdn.yicai.com/uppics/images/2026/06/8f34d1c120b05e217882b806db61f24e.jpg"
|
||||||
|
],
|
||||||
|
"word_count": 1202
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"source_id": "cls",
|
||||||
|
"url": "https://www.cls.cn/detail/2401342",
|
||||||
|
"url_hash": "36312851d3b45ca4",
|
||||||
|
"title": "“赶超”特斯拉FSD? 理想、小鹏立下“赌约”",
|
||||||
|
"content": "财联社6月16日讯(记者 徐昊)\n随着L3落地进入倒计时,以理想、小鹏等为代表的中国车企正加速迭代各自的智驾系统,均“剑指”特斯拉。\n6月15日,理想汽车宣布其智能驾驶系统“马赫VLA”将于第三季度推送全新版本,第四季度能力对齐特斯拉FSD V14。稍早之前,小鹏汽车董事长何小鹏也曾立下“赌约”——小鹏VLA将在8月达到特斯拉FSD V14.2在硅谷的整体效果。\n对于“对齐”与“整体效果”的评价维度,两家企业并未给出明确标准。不过,从技术角度来看,两家车企均锚定了全栈自研、软硬件一体化路线。\n在理想汽车董事长李想看来,智能汽车应该是具备保护人类安全、独立完成任务、比人类更高效的具身智能体。语言智能由理想汽车自研的大模型马赫Mind-Pro与马赫Mind-Edge承载,负责语言与逻辑思考,听懂指令、推演行动方案;机器智能由自研马赫VLA承载,负责三维视觉感知与躯体动作控制,看清物理世界、精准执行操作。\n同时,理想汽车自研芯片马赫M100于5月实现量产上车,成为全球首款量产的动态数据流AI芯片。随着马赫M100量产部署,理想汽车已实现芯片、编译器、操作系统、AI算法及域控制器的全栈自研。\n基于此,理想汽车在提出第四季度能力对齐特斯拉FSD V14的同时,也规划了三个具身智能系统升级的时间节点,即,7月为“智驾效率整体提升30%”、9月为“像人一样倒车”、12月为“安全和效率超越人类”。\n与理想设定的四季度时点不同,小鹏汽车则把对齐特斯拉FSD V14的时间定在了今年8月。据内部人士透露,小鹏汽车目前正在冲刺这一目标,与何小鹏“打赌”的小鹏汽车通用智能中心负责人刘先明和小鹏在美国的智驾技术团队,也期待赢下这场“赌局”。\n3月正式上车的小鹏第二代VLA物理AI大模型,相较于传统智驾“感知-语言-动作”的分步处理架构,直接砍掉语言转译中间环节,实现视觉信息到车辆动作指令的端到端直接映射,消除中间环节信息损耗与延迟,智驾响应延迟压缩至80ms以内。算力层面,该模型适配小鹏自研图灵AI芯片,可实现2250 TOPS有效算力,支撑数十亿级参数模型纯车端本地化部署,无需依赖云端信号,弱网、偏远路况下仍能稳定运行。按照小鹏汽车方面的说法,其第二代VLA大模型当前核心能力已对标特斯拉FSD v13版本。\n作为行业标杆,特斯拉FSD始终保持高速更新节奏。从早期基础辅助功能,到v13版本实现城市道路高自主通行能力,再到全新v14版本重构神经网络架构、升级自回归Transformer模型,大幅降低路况识别延迟、提升复杂场景决策精度。2026年FSD先后获批在荷兰、丹麦、比利时等欧盟五国公共道路上路许可,同时落地韩国、澳大利亚等市场,海外规模化商用步伐持续加快。\n5月24日,特斯拉中国将FSD正式更名为“特斯拉辅助驾驶”。5月21日,特斯拉宣布其监督版FSD已在包括中国在内的10个国家或地区开放使用,而对于实际落地情况,其曾表态,希望能够在今年第三季度获得中国相关部门批准。目前,特斯拉已在中国建立本地AI训练中心,部署本土化训练能力,为后续更大规模落地做准备。\n“特斯拉FSD落地中国,将对国内智能驾驶普及产生巨大的鲶鱼效应。”里斯战略咨询全球 CEO、中国区主席张云表示,FSD入华将强力推动中国高阶智驾普及,行业有望从L2级别加速向L3,甚至L4级别跨越,技术迭代进程大幅提速。“随着特斯拉监督版FSD加速入华,恰好给了国产高阶智驾正面验证实力的机会。”",
|
||||||
|
"author": "寇建东",
|
||||||
|
"source_name": "财联社",
|
||||||
|
"publish_time": "2026-06-16T18:21:00",
|
||||||
|
"publish_time_raw": "2026-06-16 18:21",
|
||||||
|
"extracted_at": "2026-06-16T21:09:08.402502",
|
||||||
|
"images": [],
|
||||||
|
"word_count": 1114
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"source_id": "cls",
|
||||||
|
"url": "https://www.cls.cn/detail/2400763",
|
||||||
|
"url_hash": "429c7738293f41d6",
|
||||||
|
"title": "国家统计局:5月份一线城市商品住宅销售价格环比上涨 二三线城市环比下降",
|
||||||
|
"content": "财联社6月16日讯,国家统计局数据显示,5月份,一线城市新建商品住宅销售价格环比上涨0.2%,涨幅比上月扩大0.1个百分点。其中,上海、广州和深圳分别上涨0.2%、0.2%和0.4%,北京下降0.2%。\n5月份一二三线城市商品住宅销售价格同比降幅总体收窄\n——国家统计局城市司高级统计师杨彩芳解读2026年5月份商品住宅销售价格变动情况统计数据\n2026年5月份,70个大中城市中,一线城市商品住宅销售价格环比上涨,二三线城市环比下降,一二三线城市同比降幅总体收窄。新建商品住宅销售价格环比上涨城市个数比上月增加。\n一、一线城市商品住宅销售价格环比上涨,二三线城市环比下降\n5月份,一线城市新建商品住宅销售价格环比上涨0.2%,涨幅比上月扩大0.1个百分点。其中,上海、广州和深圳分别上涨0.2%、0.2%和0.4%,北京下降0.2%。二线城市新建商品住宅销售价格环比下降0.1%,降幅与上月相同。三线城市新建商品住宅销售价格环比下降0.4%,降幅比上月扩大0.1个百分点。70个大中城市中,新建商品住宅销售价格环比上涨城市有16个,比上月增加2个。\n5月份,一线城市二手住宅销售价格环比上涨0.4%,涨幅与上月相同。其中,北京、上海、广州和深圳分别上涨0.1%、0.6%、0.1%和0.6%。二线城市二手住宅销售价格环比下降0.2%,降幅与上月相同。三线城市二手住宅销售价格环比下降0.4%,降幅比上月扩大0.1个百分点。70个大中城市中,二手住宅销售价格环比上涨城市有10个,比上月减少2个。\n二、一二三线城市商品住宅销售价格同比降幅总体收窄\n5月份,一线城市新建商品住宅销售价格同比下降1.7%,降幅比上月收窄0.4个百分点。其中,上海上涨3.2%,北京、广州和深圳分别下降2.1%、3.3%和4.5%。二线城市新建商品住宅销售价格同比下降3.2%,降幅比上月收窄0.1个百分点。三线城市新建商品住宅销售价格同比下降4.2%,降幅扩大0.1个百分点。\n5月份,一线城市二手住宅销售价格同比下降5.8%,降幅比上月收窄1.0个百分点。其中,北京、上海、广州和深圳分别下降6.5%、4.3%、7.0%和5.5%。二、三线城市二手住宅销售价格同比分别下降5.7%和6.2%,降幅分别收窄0.2个和0.1个百分点。\n2026年5月份70个大中城市商品住宅销售价格变动情况",
|
||||||
|
"author": "王天勇",
|
||||||
|
"source_name": "财联社",
|
||||||
|
"publish_time": "2026-06-16T09:39:00",
|
||||||
|
"publish_time_raw": "2026-06-16 09:39",
|
||||||
|
"extracted_at": "2026-06-16T21:09:07.215205",
|
||||||
|
"images": [
|
||||||
|
"https://image.cls.cn/images/20260616/3Xgb1F6j06_571x675.png",
|
||||||
|
"https://image.cls.cn/images/20260616/811vO0m7CT_585x666.png",
|
||||||
|
"https://image.cls.cn/images/20260616/6E03VGF2uZ_596x700.png",
|
||||||
|
"https://image.cls.cn/images/20260616/oaN0W459ta_596x700.png",
|
||||||
|
"https://image.cls.cn/images/20260616/W0S9fZMv2j_584x696.png",
|
||||||
|
"https://image.cls.cn/images/20260616/3qY575ZEV8_587x696.png"
|
||||||
|
],
|
||||||
|
"word_count": 698
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"source_id": "cls",
|
||||||
|
"url": "https://www.cls.cn/detail/2400545",
|
||||||
|
"url_hash": "435b40ed9f416078",
|
||||||
|
"title": "【玩转ETF】这些QDII ETF涨幅亮眼,全球资金在买什么?下游需求稳步回暖,该产业链整体景气度持续上行,多个环节涨价有望落地,三季度有望迎来旺季行情",
|
||||||
|
"content": "ETF大扩容时代,跟准机构轮动节奏|618年度史低入局\n→戳此订阅入局,接轨机构逻辑,升级指数认知>>>\nA股机构化进程提速,ETF赛道持续扩容,指数配置已成主流理性配置方向。面对快速轮动的市场格局,个人投资者往往缺乏体系化研判思维,难以厘清市场主线与机构逻辑。\n财联社《玩转ETF》专注指数投研体系搭建,\n轻量化拆解市场逻辑、赛道趋势与资金信号,助力打破信息壁垒,同步机构专业研判视角。\n✅\n赛道趋势研判\n:动态梳理核心行业赛道发展脉络,清晰厘清市场阶段性主线逻辑。\n✅\n机构逻辑拆解\n:通俗解读机构宏观思路与研判维度,助力建立标准化指数投研认知。\n✅\n资金数据观测\n:监测ETF资金、折溢价等数据异动,客观呈现市场边际情绪变化。\n✅\n盘前每日精炼输出核心内容\n,高效梳理当日市场格局,轻量化完成投研复盘。\n618年度史低 限时68折\n原价328元/月|年度底价222元/月\n,历史最低价窗口期,低成本搭建专业指数分析体系。\n→史低价立即订阅>>>",
|
||||||
|
"author": null,
|
||||||
|
"source_name": "财联社",
|
||||||
|
"publish_time": "2026-06-16T08:46:00",
|
||||||
|
"publish_time_raw": "2026.06.16 08:46",
|
||||||
|
"extracted_at": "2026-06-16T21:09:07.883787",
|
||||||
|
"images": [
|
||||||
|
"https://image.cls.cn/images/20260609/9WnL0vZxy6_1280x560.png"
|
||||||
|
],
|
||||||
|
"word_count": 329
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"source_id": "eastmoney",
|
||||||
|
"url": "https://finance.eastmoney.com/a/202606163772201638.html",
|
||||||
|
"url_hash": "4539620825593bcc",
|
||||||
|
"title": "史诗级造富盛宴持续!SpaceX股价再飙20% 马斯克身家升破1.3万亿美元",
|
||||||
|
"content": "此前SpaceX以每股135美元发行5.5556亿股,并于上周五登陆\n纳斯达克\n。据报道,此次IPO共吸引了超过3500亿美元的认购资金,其中散户投资者认购约1000亿美元,机构投资者的认购需求达到约2500亿美元。",
|
||||||
|
"author": "财联社",
|
||||||
|
"source_name": "东方财富",
|
||||||
|
"publish_time": "2026-06-16T08:20:00",
|
||||||
|
"publish_time_raw": "2026年06月16日 08:20",
|
||||||
|
"extracted_at": "2026-06-16T21:09:10.177100",
|
||||||
|
"images": [],
|
||||||
|
"word_count": 70
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"source_id": "yicai",
|
||||||
|
"url": "https://www.yicai.com/news/103232866.html",
|
||||||
|
"url_hash": "477ab44035b241be",
|
||||||
|
"title": "一财社论:营造人尽其才的战略性支撑场景",
|
||||||
|
"content": "综合国力的竞争归根到底是人才的竞争。\n16日第12期《求是》杂志发表中共中央总书记、国家主席、中央军委主席习近平的重要文章《一体推进教育科技人才发展》。文章强调,教育、科技、人才是全面建设社会主义现代化国家的基础性、战略性支撑。文章指出,要强化教育对科技和人才的支撑作用;要加快培养造就一支规模宏大、结构合理、素质优良的创新型人才队伍。\n当前全球经济已步入AI时代,创新驱动已成为经济社会攻坚克难、拾阶而上的主要驱动力。\n经过数十年国家、个人持续不断对教育的深度投入,中国已成为世界上工程师最密集的国家之一,这是支撑中国制造和中国创造在国际市场攻城略地的厚实基础,是中国产业链供应链在全球经济中不可或缺的底气和耐力。\n近年来,随着中国经济和产业沿着微笑曲线向上攀登,中国在基础研究、应用研究领域的投入逐渐步入回报期。当前科教领域越发凸显的正向激励场景,源于中央决策层对科教的高度重视和各级政府对科教有求必应的持续重金投入。不断强化的国家战略科技力量,不断适应经济社会变迁的新型举国体制,科教兴国战略、人才强国战略和创新驱动发展战略的耦合联动等,正在成为推动经济社会高质量发展的战略支撑性框架。\n如果说AI时代,强化一体推进教育科技人才发展、为人才发展提供更多的制度性包容空间等,已成为全社会的共识;那么,将新型举国体制与创新驱动有机结合,发挥新型举国体制的优势助推创新驱动,将是接下来各方积极探索的“干中学”的内容。\n创新是一项通往未知的高风险活动,一大特征就是无法事先规划,属于典型的试错性涌现现象。梳理近年来中国创新力量的成长不难发现,新型举国体制能成为中国创新的重要战略性支撑框架,主要在于新型举国体制营造了一个“有求必应、无事不扰”的创新发展环境,即对科创、教育等搭建的全方位战略支撑性框架,主要围绕三个维度:\n一是对创新和教育提供全方位的防护型保障体系。保障科教资源充足、降低科教活动边际成本、提高科教活动的试错容错能力等战略性支撑框架,通过经年累月的坚持不懈,有效护航国内科教的健康发展。\n二是对创新和教育提供生态化的透明性担保体系,极大释放了国内创新资源的自由发展空间。激发各类创新主体活力是新型举国体制的一项重要内容,优化国家科研机构、高水平研究型大学、科技领军企业定位和布局等工作定位,凸显了国家对科研单位和科研人员的高度重视,给予各类创新主体更多的自由度和更大的灵活性,以及更加包容的容错空间。\n三是激励有保障。创新活动的市场定价越发明显,《一体推进教育科技人才发展》指出,要健全要素参与收入分配机制,更好体现知识、技术、人才的市场价值,营造鼓励创新、宽容失败的良好氛围。市场化的激励、创新成果的实践检验和收益权/受益权分配等,将有助于真正体现创新人才的价值,使创新人才转化为智本,与资本对等合作,而不再是资本支配的要素资源。\n基础有保障、发展有空间,这是新型举国体制下,中国创新能力迸发活力和能量的最有效保障。\n当然,新型举国体制能够有效激活中国创新力量,还缘于当前中国的创新环境和生态,主要处于对标冲刺阶段、国产替代阶段,这使得国内当下的大量创新,主要体现为一种确定性的创新活动,即创新驱动的对象大都是已存在的东西,如诸多卡脖子项目等,属于攻关式创新,这更有利于发挥新型举国体制的集中力量办成事的优势,围绕关键核心技术的突破攻坚,目标确定、任务明确,加之对各种创新主体赛马不相马,释放竞争活力,更加激发了全社会的创新动力。\n满眼生机转化钧,天工人巧日争新。未来已来,AI时代创新是驱动经济社会发展的原动力,是一国综合国力的核心特征,让创新活跃起来,让人才行动起来,需要新型举国体制化身包容失败的守护神,以服务者的角色为高风险创新活动分担风险,为创新活动营造低成本的制度场景,真正让市场为创新活动和创新人才的稀缺性定价,唯有如此才能真正形成激励约束兼容的创新高地。",
|
||||||
|
"author": "刘菁",
|
||||||
|
"source_name": "第一财经",
|
||||||
|
"publish_time": "2026-06-16T00:00:00",
|
||||||
|
"publish_time_raw": "2026-06-16",
|
||||||
|
"extracted_at": "2026-06-16T21:09:27.793981",
|
||||||
|
"images": [],
|
||||||
|
"word_count": 1446
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"source_id": "eastmoney",
|
||||||
|
"url": "https://finance.eastmoney.com/a/202606163772821249.html",
|
||||||
|
"url_hash": "4ad67dd96340b56b",
|
||||||
|
"title": "又一品种“爆单”!AI驱动高端铜箔供不应求 最新高增长潜力股名单出炉",
|
||||||
|
"content": "专业金融数据,下一代智能终端\n6月16日早盘,\n铜\n箔概念延续强势,\n宝鼎科技\n、\n诺德股份\n均录得两连板,\n光华科技\n收获6天3板,\n东材科技\n亦强势涨停,\n生益科技\n、\n宝明科技\n、\n铜冠铜箔\n、\n方邦股份\n等多股跟涨。\n高端\n铜\n箔订单已排至明年下半年\n在算力基建狂飙背景下,HVLP4代\n铜\n箔作为关键基材,成为制约算力供给的短板。\n据经济观察报消息,一家国内头部铜箔厂商透露,其专为AI服务器、高速光模块配套的HVLP4代算力铜箔,在手订单已排至2027年下半年。\n与此同时,行业通行的月结供货模式已经基本失效。过去,铜箔企业与客户按月结算、滚动供货。现在,下游覆铜板、头部\nPCB\n厂商为抢占紧缺产能,主动提出预付保证金,并签署2至3年长期供货协议。\n据悉,这家铜箔厂商目前在国内高端算力铜箔市场的份额约为15%-20%。今年,该公司相关基地新释放的千吨级产能,设备还没调试完,就有三家企业上门洽谈长协锁产。\n此外,海外供应商的供货调整让局面更加紧张。海外头部材料厂商所属日系企业群体长期合计占据全球高端HVLP铜箔超五成市场份额。受全球算力需求集中爆发、现有产能扩充节奏受限影响,相关企业面向中国客户的常规订单交付周期已拉长至6到8个月。\n“算力被高端铜箔制约,不是行业炒作。”上述铜箔厂商市场负责人说:“这是实实在在的供需错配,缺口肉眼可见。”\n铜箔行业迎来量价齐升机遇期\n铜箔是\n电子\n信息与\n新能源\n产业的核心基础材料,下游分为\n锂\n电铜箔(产能占比59%)与\nPCB\n铜箔(占比41%)。\n受AI服务器从H100向GB200、Rubin平台升级驱动,\nPCB\n层数提升至40层以上,铜箔代际从RTF→HVLP1/2→HVLP3/4刚性迭代。而每代需6-12个月验证周期,全程系统级认证或耗时1-3年。同时,\n英伟达\n、AMD、Intel等海外厂商及\n华为昇腾\n等国产厂商对铜箔代际要求严格,仅认证全球少数龙头。\n据\n东吴证券\n测算,2026年全球AI服务器高端铜箔需求将达2.4万吨,同比增长260%,2027年进一步翻番至5万吨,2030年需求或达超11万吨。\n该机构进一步指出,海外高端铜箔龙头扩产保守,订单外溢至中国,看好铜箔板块量利双升。极薄化+高端化为趋势,行业产能利用率回升至满产,加工费触底回升,板块2026年盈利弹性显著。\n东北证券\n最新研报指出,当前行业双重景气共振——\n锂\n电铜箔周期触底回升,盈利拐点确立;PCB铜箔受AI浪潮驱动,高端HVLP供需持续紧张,铜箔行业迎来量价齐升的战略性机遇。\n西部证券\n也认为,高稼动率+产品结构调整驱动盈利触底回升,\n锂\n电铜箔供需格局有望全面反转;AI算力奔涌驱动PCB量价齐升,高端PCB\n电子\n铜箔机遇广阔。\n多只概念股业绩倍增可期\n据\n东方财富\n“热点题材”梳理,目前A股市场有近20股涉及铜箔概念,合计总市值超1.1万亿元,\n生益科技\n体量居首,\n铜冠铜箔\n、\n德福科技\n、\n铜陵有色\n市值均超千亿大关,\n南亚新材\n、\n东材科技\n体量靠前。\n年初至今,除了\n宝明科技\n、\n英联股份\n,其余铜箔概念股均录得股价上涨,\n泰金新能\n股价飙涨超5倍,\n铜冠铜箔\n、\n德福科技\n、\n南亚新材\n涨幅均在3倍以上,\n宝鼎科技\n、\n方邦股份\n、\n生益科技\n、\n诺德股份\n等均已跻身翻倍牛股。\n6月以来,多数铜箔概念股延续强势,\n方邦股份\n、铜冠铜箔、\n南亚新材\n、\n嘉元科技\n、\n东材科技\n月内涨幅均在三成以上。\n资金方面,\n东方财富\nChoice数据显示,本月共8只铜箔概念股获得杠杆资金青睐,东材科技、\n铜陵有色\n分别获融资客抢筹3.67亿和2.29亿元,铜冠铜箔、\n楚江新材\n、南亚新材均获超1亿元融资净买入。\n未来增长潜力方面,共有8只铜箔概念股获得2家及以上机构评级,所有个股预测净利增幅均在60%以上。\n其中,机构预计\n嘉元科技\n今年净利或同比大增近12倍,铜冠铜箔有望实现逾7倍净利增长,\n中一科技\n、东材科技、南亚新材等5股业绩均有望翻倍增长。\n想炒股,先开户!选东方财富证券,行情交易一个APP搞定>>\n(文章来源:\n东方财富\n研究中心)",
|
||||||
|
"author": "东方财富研究中心",
|
||||||
|
"source_name": "东方财富",
|
||||||
|
"publish_time": "2026-06-16T12:05:00",
|
||||||
|
"publish_time_raw": "2026年06月16日 12:05",
|
||||||
|
"extracted_at": "2026-06-16T21:09:12.330715",
|
||||||
|
"images": [
|
||||||
|
"https://np-newspic.dfcfw.com/download/D24903554555390478282_w607h572_o.jpg",
|
||||||
|
"https://np-newspic.dfcfw.com/download/D25061065121364039337_w621h605_o.jpg",
|
||||||
|
"https://np-newspic.dfcfw.com/download/D24832930842412850501_w690h389.jpg"
|
||||||
|
],
|
||||||
|
"word_count": 1254
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"source_id": "cls",
|
||||||
|
"url": "https://www.cls.cn/detail/2400912",
|
||||||
|
"url_hash": "4bbf9d542a87d0f1",
|
||||||
|
"title": "适配高堆叠闪存芯片生产 应用材料推出两款“3D微缩”设备",
|
||||||
|
"content": "《科创板日报》6月16日讯(编辑 宋子乔)\n当地时间6月15日,\n半导体设备龙头应用材料推出两款面向“3D芯片微缩技术”的新设备\n。这些设备旨在解决尖端半导体制造中新兴的挑战:\n在日益深窄的3D结构中实现精密加工\n。\n应用材料表示,这两款设备将帮助芯片制造商扩展逻辑和存储器的尺寸,从而\n为下一代AI芯片带来更高的性能、更佳的能效和更高的良率\n。\n应用材料是全球唯一一家除光刻机外,覆盖晶圆制造全前道工序的设备企业,被称为“半导体设备超市”,其产品组合涵盖从薄膜沉积、材料改性到原子级精度材料去除的广泛半导体制造环节。该公司不仅在AI加速芯片、先进逻辑与存储芯片制造中扮演关键角色,还在推动AI技术发展与加速下一代芯片商业化方面发挥着举足轻重的作用。\n目前,应用材料正积极推进其EPIC创新平台战略。2023年,公司宣布在硅谷设立“设备与制程创新暨商业化(EPIC)”中心,这是美国半导体设备研发领域规模最大的投资项目,预计于2026年正式启用,旨在大幅缩短突破性技术从研发到量产商业化的周期。\n随着行业对AI性能要求越来越高,芯片的物理架构越来越复杂、精密,同样面积的产品上需要堆叠更多的硬件\n。\n正如应用材料半导体产品集团总裁普拉布•拉贾(Prabu Raja)所言,从晶体管结构到存储器堆叠,芯片制造商需要新的方法来精确沉积和选择性地去除极其复杂的3D架构中的材料。\n为此,应用材料推出的两款设备,分别面向先进芯片制造中沉积与刻蚀两大核心工艺环节。\n据介绍,它们结合使用,可为芯片制造商提供对高深宽比结构中介电薄膜沉积和金属去除的精确控制,在先进节点上实现更均匀的材料工程,从而在逻辑和存储器应用中实现持续的3D微缩,并提高器件性能、工艺控制的严格程度以及可制造性。\nCentris Spectral氮化硅ALD沉积设备:适配未来3D堆叠芯片生产\n氮化硅(SiN)是芯片制造中的基础功能材料,广泛应用于器件表面钝化、介质隔离、光刻侧墙制备等关键工序。然而,随着芯片向全环绕栅极(GAA)晶体管、高层数3D NAND等三维架构演进,器件结构日益深窄,传统等离子体沉积技术难以在高深宽比结构中实现自上而下的均匀成膜,导致薄膜质量不达标。\n该设备提供了低温氮化硅镀膜方案,采用创新的高密度微波等离子体技术,可在保持低温工艺条件的同时,在高深宽比3D结构内部生成致密、均匀的氮化硅薄膜。该设备能支撑芯片继续缩小尺寸,且生产效率比老式设备更高,适配未来3D堆叠芯片生产,兼顾良品率、芯片稳定性和生产成本。\nProducer Selectra钼刻蚀机:干法精准去除钼 突破3D NAND瓶颈\n随着3D NAND闪存堆叠层数持续攀升,行业开始采用低电阻金属钼(Mo)制作字线,而字线间的精准隔离成为关键挑战。传统湿法刻蚀技术在超高堆叠结构中,液态化学试剂难以抵达深槽底部,形成“上厚下薄”的刻蚀轮廓,严重制约了存储芯片的性能与堆叠层数的继续增加。\nProducer Selectra钼刻蚀机是应用材料选择性刻蚀产品线的新品,用干法精准只刻掉多余钼,适配几百层高堆叠闪存。此前其设备只用来刻绝缘层、硅材料,现在新增金属钼蚀刻能力,未来可广泛应用于NAND、DRAM以及晶圆代工逻辑芯片等领域。\n该设备已通过大批量量产验证,有效缩小了3D NAND单元间的性能差异,降低了芯片漏电问题,增强了数据保存能力。\n应用材料称,这两款新设备目前已被头部逻辑与存储芯片制造商纳入生产线,将在2026年IEEE超大规模集成电路技术与电路研讨会(VLSI Symposium)上正式亮相展示。",
|
||||||
|
"author": "季晟",
|
||||||
|
"source_name": "财联社",
|
||||||
|
"publish_time": "2026-06-16T13:29:00",
|
||||||
|
"publish_time_raw": "2026-06-16 13:29",
|
||||||
|
"extracted_at": "2026-06-16T21:09:07.258575",
|
||||||
|
"images": [
|
||||||
|
"https://image.cls.cn/images/20260616/81gT4kmfPZ_880x800.png",
|
||||||
|
"https://image.cls.cn/images/20260616/iFFgm5r60s_784x700.png",
|
||||||
|
"https://image.cls.cn/images/20260616/6pm4ql8xof_1123x700.png",
|
||||||
|
"https://image.cls.cn/images/20260616/P8y7R2j6bY_797x700.png"
|
||||||
|
],
|
||||||
|
"word_count": 1176
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"source_id": "yicai",
|
||||||
|
"url": "https://www.yicai.com/news/103232287.html",
|
||||||
|
"url_hash": "4ca65c31e0b2d25c",
|
||||||
|
"title": "远程办公叠加线上社交吞噬“脱单”机会?日本少子化趋势快于预期",
|
||||||
|
"content": "日本高龄少子化现象再度趋紧。\n近日,日本厚生劳动省公布的最新数据显示,2025年日本国内出生人口数量为67.1236万人,总和生育率(平均每名育龄妇女生育子女数)1.14,两项数据均创有统计以来的最低纪录。\n日本新生儿数量已连续10年下降。2025年这一数字较2024年减少近1.5万人,同比下降2.2%。日本国立社会保障与人口问题研究所此前预测显示,日本出生人数降至67万人左右可能发生在2040年,但现实却是,这一情况已提前15年到来。\n日本街头(来源:资料图)\nAI时代见面交流变少了?\n分析认为,导致日本新生人口数量急剧下滑的最直接原因便是结婚数量的减少。日本厚生劳动省的数据显示,在2000年前后,日本每年的结婚人数约为80万人。2025年,这一数字跌破50万,仅为48.91万人。\n数据还显示,目前日本平均初婚年龄男性为31.1岁、女性29.8岁,30岁以下年轻群体的初婚率和生育率持续下降,直接加剧少子化现象。\n但值得注意的是,日本政府的调查显示,年轻人中渴望结婚的想法并没有消退。政府调查显示,约80%的单身人士仍然希望在未来能结婚。但是,意图和现实之间日益扩大的差距正成为影响日本社会发展的一个决定性因素。\n日本政府的上述调查显示,造成这种差距的原因之一则是,单身青年群体认为,在如今的互联网时代,线下面对面相遇的机会减少了。如果说15年前,日本近90%的已婚夫妇是通过学校、工作或朋友介绍自然相识的,如今,这个数字已经下降到74%左右。\n调查显示,远程办公的兴起虽大幅降低了年轻人的日常通勤成本,却也减少了他们在工作场所建立人际关系的机会。而社交场景的线上化,同样让不少年轻人逐渐降低了线下社交的主动性。\n除了社交机会的减少,经济压力也是横亘在日本年轻群体结婚意愿面前的重要阻碍。虽然日本政府虽推出一些婚育支持政策,但各地财政能力存在差异,落实政策力度也不均衡。比如,东京都免除儿童保育费及18岁前的医疗费,每月为每名儿童提供5000日元补贴;而神奈川县的家庭年均保育费支出比东京高出12万至70万日元,医疗费等也需自行承担。\n日本学者的分析认为,尽管日本政府已出台多项举措应对少子化,但当前日本经济增长前景不确定性增大、通胀水平不断上升,家庭承担了更多的养育成本,正持续削弱年轻人的生育意愿。此外,就业和收入与出生率高度关联,日本物价与住房等生活成本持续上涨、实际工资增长缓慢,进一步加重年轻人的经济压力和对未来的不安情绪,导致生育率难以回升。\n“看不出高市有多认真对待人口政策”\n日本新生儿数量逐年减少,直接导致适龄劳动人口的规模不断萎缩。根据日本厚生劳动省的数据,日本劳动年龄人口占总人口的比重已从上世纪90年代的近七成,下降至近年的不足六成,劳动力缺口持续扩大。而为了填补各行业的用工空白,尤其是护理、建筑、农业等本土年轻人不愿进入的行业,此前,日本政府逐步放宽外籍劳工的引入政策,越来越多的外籍务工人员进入日本劳动力市场,成为缓解日本用工荒的重要力量。\n2025年日本政府数据显示,日本在留外国人数已突破400万,创历史新高。这一群体覆盖日本多个细分劳动力市场,尤其活跃在制造业、餐饮住宿等服务业、以及IT行业。\n不过,去年高市早苗就任首相后,尽管与往届首相一样,把应对人口减少直接置于政府议题的优先位置,但高市政府的对策在高调宣布和实际落地之间差距显著。一位在日本的华人告诉第一财经,在人手紧缺之际,高市政府的注意力似乎更倾向于将国家安全和外国人政策捆绑,而负责人口问题的大臣同时还兼顾着领土争端和食品安全等十多个其他事务,“看不出高市在人口问题方面有认真对待”。\n此前日本政府已正式宣布上调在留资格更新手续费。比如,永住许可则从现行的1万日元(约合人民币428元)上调至20万日元(约合8550元)左右,净增加19倍。\n“在当前各国政府都在争抢人才的时候,高市政府却反其道而行,把外国人留在日本的行政成本推高10~20倍不等,从长远看这着实在为日本未来的产业竞争力埋下隐患。”上述华人说道。",
|
||||||
|
"author": "冯迪凡",
|
||||||
|
"source_name": "第一财经",
|
||||||
|
"publish_time": "2026-06-16T00:00:00",
|
||||||
|
"publish_time_raw": "2026-06-16",
|
||||||
|
"extracted_at": "2026-06-16T21:09:23.800403",
|
||||||
|
"images": [
|
||||||
|
"https://imgcdn.yicai.com/uppics/images/2026/06/0fcb42cbafddf7e4a2d503758232747d.jpg"
|
||||||
|
],
|
||||||
|
"word_count": 1401
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"source_id": "sina",
|
||||||
|
"url": "https://finance.sina.com.cn/jjxw/2026-06-16/doc-inicpvty9568298.shtml",
|
||||||
|
"url_hash": "4d4eff7ac2cffd1a",
|
||||||
|
"title": "多艘伊朗船只顺利通过美方海上封锁区",
|
||||||
|
"content": "据伊朗方面当地时间16日消息,3艘油轮和2艘载有伊朗基本物资的船只已突破美国方面实施的海上封锁。\n另有消息称,多艘伊朗船只顺利穿过封锁区。根据船舶航行数据,一艘伊朗超大型原油运输船正从国际水域驶向伊朗港口,并已通过封锁区域。一艘运载牲畜饲料的船只也已穿过封锁区域,正在驶往伊朗。此外,另一艘满载原油的伊朗油轮已通过阿曼湾及封锁线,正驶向其出口目的地。(总台记者 魏然)\n海量资讯、精准解读,尽在新浪财经APP\n责任编辑:丁文武",
|
||||||
|
"author": "丁文武",
|
||||||
|
"source_name": "新浪财经",
|
||||||
|
"publish_time": "2026-06-16T05:10:07+08:00",
|
||||||
|
"publish_time_raw": "2026-06-16T05:10:07+08:00",
|
||||||
|
"extracted_at": "2026-06-16T21:09:21.209361",
|
||||||
|
"images": [
|
||||||
|
"https://n.sinaimg.cn/spider20260616/593/w894h499/20260616/ad57-3a7d5d8b9d0ad9763b29a17565a0f5b1.png",
|
||||||
|
"http://qr.weibo.cn/inf/gen?api_key=bdccdb65d34320c10b65cdf6c3503a53&data=https%3A%2F%2Ffinance.sina.cn%2Fapp%2FQRtoSFA.shtml%3Fsudatrackvalue%3Dcaitou_inQRcode_PcBottom%26schema%3Dtype%253D100%26docid%3Dnicpvty9568298%26sinaglobal%3D60.187.79.169_1781612256.481622&datetime=nocheck&level=H&output_type=img&redirect=0&size=100&type=url&sign=7f4371b7b3026f59a102340c80caf8f7"
|
||||||
|
],
|
||||||
|
"word_count": 185
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"source_id": "sina",
|
||||||
|
"url": "https://finance.sina.com.cn/roll/2026-06-16/doc-inicpvua6369495.shtml",
|
||||||
|
"url_hash": "4ec527df65c5a3b8",
|
||||||
|
"title": "信披整改承诺仅保持九个月 国晟科技再收监管“双罚单”",
|
||||||
|
"content": "登录新浪财经APP 搜索【信披】查看更多考评等级\n来源:财联社\n时隔仅九个月,曾经的光伏大牛股\n国晟科技\n(\n16.040\n,\n-1.78\n,\n-9.99%\n)\n(维权)\n(603778.SH)再次因信息披露不及时,收到北京证监局和上海证券交易所监管警示。\n经查,公司与江苏大航电气贸易有限公司存在买卖合同纠纷,涉案金额5184.62万元,占最近一期经审计净资产比重超10%,但公司未在2026年4月24日披露的累计诉讼公告中包含该笔诉讼,迟至5月22日才补充披露,信息披露不完整且不及时。\n对此,监管部门认为,公司董事长吴君、总经理高飞、董事会秘书张昆对违规行为负有主要责任。北京证监局决定对公司及相关人员采取出具警示函的行政监管措施,并记入证券\n期货\n市场诚信档案。\n财联社记者注意到,这并非\n国晟科技\n首次因信披问题被监管部门点名。去年9月19日,\n国晟科技\n收到北京证监局行政监管措施决定书。\n据当时公告,公司存在园林业务以前年度收入成本确认不准确,导致2022年年报、2023年半年报、2023年年报相关信息披露不准确;对外借款未履行审议程序并及时披露,2023年子公司将8750万元对外借款视为工程预付款,未向公司管理层报告并履行对外借款审议及披露程序;新增光伏业务板块内部控制执行不到位等多重违规事实。\n彼时,北京证监局对公司及回全福、吴君、高飞、姚麒、张永胜等多名时任高管一并采取措施。国晟科技在当时表示,公司及相关人员高度重视《决定书》中涉及的相关问题,将认真吸取教训,积极进行整改,认真学习和严格遵守相关法律法规及规范性文件。\n公司同时表示,将提高公司规范运作水平,健全内部控制制度,提高财务核算规范化水平和信息披露质量,维护公司及全体股东利益,促进公司持续、健康、稳定发展。\n然而实际情况是,不到一年时间,公司连同三位核心高管再次因信息披露不完整、不及时被北京证监局和上海证券交易所出示警示。\n在今日公告中,公司亦将去年9月作出的整改承诺,再次复述一遍,称“将认真吸取教训……提高信息披露质量”等。\n信披屡屡“失守”的背后,是国晟科技基本面持续承压。公司前身为乾景园林,主营业务为园林绿化。2022年,公司通过并购国晟能源及子公司进入光伏行业,开展大尺寸高效异质结光伏\n电池\n、组件、硅片以及风光储能一体化电站开发运维等业务。\n跨界后,公司营收波动增长,但其归属净利润依然连续多年为负。数据显示,2025年公司实现营业收入7.96亿元,同比降低61.97%;归母净利润亏损5.73亿元,较上年同期的亏损1.06亿元增亏逾4倍;到今年一季度,公司继续亏损3483.52万元。\nchoice数据显示,截至6月15日收盘,公司股价为17.82元/股,总市值117.62亿元,相较于今年4月10日创下的高点,累计跌幅达到45.74%。\n海量资讯、精准解读,尽在新浪财经APP\n责任编辑:郭栩彤",
|
||||||
|
"author": "郭栩彤",
|
||||||
|
"source_name": "新浪财经",
|
||||||
|
"publish_time": "2026-06-16T07:20:02+08:00",
|
||||||
|
"publish_time_raw": "2026-06-16T07:20:02+08:00",
|
||||||
|
"extracted_at": "2026-06-16T21:09:18.276202",
|
||||||
|
"images": [
|
||||||
|
"http://qr.weibo.cn/inf/gen?api_key=bdccdb65d34320c10b65cdf6c3503a53&data=https%3A%2F%2Ffinance.sina.cn%2Fapp%2FQRtoSFA.shtml%3Fsudatrackvalue%3Dcaitou_inQRcode_PcBottom%26schema%3Dtype%253D100%26docid%3Dnicpvua6369495%26sinaglobal%3D60.187.79.169_1781612219.689444&datetime=nocheck&level=H&output_type=img&redirect=0&size=100&type=url&sign=69039fca2417f6665c0f930441efd29e"
|
||||||
|
],
|
||||||
|
"word_count": 925
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"source_id": "sina",
|
||||||
|
"url": "https://finance.sina.com.cn/jjxw/2026-06-16/doc-inicqaac4549640.shtml",
|
||||||
|
"url_hash": "537347e895a47574",
|
||||||
|
"title": "特朗普否认美将给伊朗巨资",
|
||||||
|
"content": "当地时间6月16日凌晨,正在法国出席七国集团峰会的美国总统特朗普在社交平台发文称,“伊朗已经同意永远不会拥有核武器!有关‘美国将向伊朗支付3亿美元’的报道是假新闻,是民主党人散布出来的。”\n目前尚不清楚特朗普所指的“3亿美元”具体是什么。而伊朗媒体昨天披露的伊美谅解备忘录14项条款中涉及具体金额的一项为:美国及其盟友有必要提出对伊朗的重建计划,金额至少3000亿美元。(CCTV国际时讯)\n海量资讯、精准解读,尽在新浪财经APP\n责任编辑:郭建",
|
||||||
|
"author": "郭建",
|
||||||
|
"source_name": "新浪财经",
|
||||||
|
"publish_time": "2026-06-16T07:45:21+08:00",
|
||||||
|
"publish_time_raw": "2026-06-16T07:45:21+08:00",
|
||||||
|
"extracted_at": "2026-06-16T21:09:20.164958",
|
||||||
|
"images": [
|
||||||
|
"http://qr.weibo.cn/inf/gen?api_key=bdccdb65d34320c10b65cdf6c3503a53&data=https%3A%2F%2Ffinance.sina.cn%2Fapp%2FQRtoSFA.shtml%3Fsudatrackvalue%3Dcaitou_inQRcode_PcBottom%26schema%3Dtype%253D100%26docid%3Dnicqaac4549640%26sinaglobal%3D60.187.79.169_1781612242.993787&datetime=nocheck&level=H&output_type=img&redirect=0&size=100&type=url&sign=c9c3753769ecd350af317a98c2a4330e"
|
||||||
|
],
|
||||||
|
"word_count": 182
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"source_id": "cls",
|
||||||
|
"url": "https://www.cls.cn/detail/2400330",
|
||||||
|
"url_hash": "5612afd55e85ccac",
|
||||||
|
"title": "【机构调研】这家先进金属粉材制造商微电子锡基焊粉材料国内市场占有率第一",
|
||||||
|
"content": "①年内第二个重要买点到来;②AI光互联升级推动MPO插芯量价齐升,NPO/CPO等新型光互联架构为MPO打开新增应用场景,高阶插芯单价相较传统产品具备明显提升空间;③今日全市场机构研报共发布303篇,万丰奥威等4股评级得到上调,13家公司获得首度覆盖,其中安克创新等4股获新财富分析师深度覆盖;④在个股机构关注度排行中,安井食品首次上榜,前五名依次为太阳纸业>贵州茅台>比音勒芬>安井食品>臻宝科技。",
|
||||||
|
"author": null,
|
||||||
|
"source_name": "财联社",
|
||||||
|
"publish_time": "2026-06-15T19:42:00",
|
||||||
|
"publish_time_raw": "2026.06.15 19:42",
|
||||||
|
"extracted_at": "2026-06-16T21:09:07.577553",
|
||||||
|
"images": [],
|
||||||
|
"word_count": 159
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"source_id": "yicai",
|
||||||
|
"url": "https://www.yicai.com/news/103232824.html",
|
||||||
|
"url_hash": "64ac85d4c4a2a45d",
|
||||||
|
"title": "AI版支付宝发布,超级应用抢滩智能体生态",
|
||||||
|
"content": "6月16日,AI版支付宝正式发布,命名“阿宝”,支付宝完成全端AI化。此前,腾讯管理层曾披露微信会开发AI智能体,伴随着“阿宝”的推出,超级APP们都在按下布局智能体生态的加速键。\n目前,AI版支付宝已启动邀请测试,后续将逐步向全量用户开放。记者体验AI版支付宝发现,更新后的支付宝保留了原版本和AI版,用户进入支付宝显示原页面,左滑则可切换至AI页。AI页界面相对简洁,仅有“阿宝”和“资产”两个功能页,通过与智能体对话可调用大量AI功能。\n例如,直接跟“阿宝”说“帮我查下公积金”,智能体就会自动打开公积金服务入口,支付宝内嵌的生活缴费、服务预约等生态都接入了AI化,用户可以通过AI完成预约家电维修、寄快递、打车、点餐等服务。\n也有一些用户调侃,AI版支付宝最“给力”的场景是蚂蚁森林偷能量。记者发现,只要跟“阿宝”说“偷能量”,智能体就会进入蚂蚁森林页面,挨个点开好友界面,自动收取能量,完全无须用户手动操作。\n在这背后,对于用户数超10亿的超级应用来说,如何吸引许多较少接触原生AI应用的用户走向AI入口也是一项考验。无论是选择同时保留原版本和AI版,还是在蚂蚁森林等非商业化但高频的场景做AI投入,都是超级APP所面临的,需要平衡庞大且多样用户群体不同AI接受度的课题。\n此外,管钱这件事,支付宝没有直接交给 AI,而是用一个独立的“资产”页面呈现给用户。用户的余额宝、小荷包等资金明细在这里整合,个人账户的可视化及金融投资的“盯收益”在这里AI化,不过,涉及资金变动或支付环节,必须本人确认。这意味着,在涉及资金管理等重点领域,AI化所需要面临的安全风险和用户信任问题仍需谨慎处理,边界仍在被探索中。\n值得关注的是,无论是微信还是支付宝,超级App们从功能集合转向智能体中枢正在成为趋势。互联网产业分析师张书乐对第一财经记者分析,在处理部分用户复杂需求时,支付宝和微信都在试图以智能体来兼容平台内小程序,包括其他厂商提供的小程序,达成第二桌面效果。作为超级应用,如果不能实现这种便捷用户体验,未来就可能被其他平台吞噬流量和用户活跃度。但AI版的推出仅仅是个开始,超级应用们AI化成功与否的关键变量还在于这些智能体基于平台调性的智能体验。\n企迈CEO王友运则表示,对于AI版的支付宝,商家端的期待一方面是支付宝能否提供一些入口供商家使用,让消费者有很便利的入口找到附近的店,完成点单、履约交易,二是支付宝能否联合更多SaaS厂商一起去做很多行业的标准,比如门店很多信息标准怎么建设,会员权益怎么建设,把这些标准建成AI能够理解、读取、使用的场景。\n据悉,后续支付宝将推出面向开发者、商家与服务商的 Agent 开放平台。\n这也意味着,一个全面AI化的版本推出只是超级应用们未来之争的开始。当“阿宝”们开始尝试成为更多用户数字生活的智能代理,超级APP的竞争也正从流量入口的抢夺,转向Agent生态厚度与场景理解深度的终极较量。",
|
||||||
|
"author": "宁佳彦",
|
||||||
|
"source_name": "第一财经",
|
||||||
|
"publish_time": "2026-06-16T00:00:00",
|
||||||
|
"publish_time_raw": "2026-06-16",
|
||||||
|
"extracted_at": "2026-06-16T21:09:27.961972",
|
||||||
|
"images": [
|
||||||
|
"https://imgcdn.yicai.com/uppics/images/2026/06/e716008223b796433dc7c23652f1616f.jpg"
|
||||||
|
],
|
||||||
|
"word_count": 1010
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"source_id": "sina",
|
||||||
|
"url": "https://finance.sina.com.cn/roll/2026-06-16/doc-inicpzzw9492841.shtml",
|
||||||
|
"url_hash": "67a3d66fdfa1be7f",
|
||||||
|
"title": "热门产品“一天一个价” “半导体血液”生产企业爆单",
|
||||||
|
"content": "随着人工智能产业景气度不断攀升,AI芯片、高端存储芯片的需求旺盛,而这些芯片的生产中,离不开一种特殊的耗材——电子特种气体。\n电子特种气体,是纯度高于99.99%的电子级气体,属于电子化学品领域的核心材料,也被称作半导体产业的血液。在下游强劲需求的推动下,电子特气市场多款核心产品目前供不应求。\nAI景气催生刚需\n电子特气生产企业爆单\n上海浦东新区一家特种气体物流运输企业,身穿专业防护服的工人,正严格按照操作规范,完成上百种电子特气的卸车、检验与入库作业。企业负责人告诉记者,这些产品的最终去向很多是半导体晶圆厂,由于部分电子特气属于易燃易爆高危化学品,客户通常零库存。\n据企业负责人介绍,今年以来半导体晶圆厂商的电子特气运输需求爆发式增长,几乎把企业的配送节奏拉至满负荷。\n近年来,人工智能行业景气高涨,芯片需求量激增,不少应用于半导体制造领域的电子特气产品供不应求。多家特气生产企业负责人表示,在手订单大幅增加,产线处于高负荷运转状态。\n量价齐升\n电子特气热门产品“一天一个价”\n下游需求爆发,电子特种气体的产能供不应求,吸引众多企业竞相布局这一赛道。行业的表现也传递到了资本市场,相关上市公司的股价今年以来领跑大盘。\n供需缺口推升产品价格\n六氟化钨,是制造存储芯片和先进逻辑芯片时的关键沉积材料,也是本轮电子特气需求景气度高涨的热门品种。受海外部分产能退出、下游存储芯片持续扩产等因素影响,供需缺口扩大,直接推动产品价格进入上行通道。\n上海一家特种气体生产企业负责人表示,近期氦气需求大幅攀升,工厂双班倒运行生产但仍然供不应求。上海尤嘉利特种气体有限公司生产部副经理张志军介绍,半导体行业对氦气需求量非常大,一天一个价,目前每日生产量较年初翻倍。\n记者走访多家电子特气公司,相关负责人介绍,产品量价齐升,对公司经营业绩的拉动效应显著。\nA股电子特气板块表现强势\n行业的景气度也传递到资本市场。今年以来,A股电子特气板块表现强势,\n中船特气\n(\n323.000\n,\n4.70\n,\n1.48%\n)\n、\n和远气体\n(\n59.240\n,\n2.46\n,\n4.33%\n)\n、\n昊华科技\n(\n58.000\n,\n1.73\n,\n3.07%\n)\n、\n雅克科技\n(\n137.760\n,\n-1.07\n,\n-0.77%\n)\n等多只个股涨幅居前,万得资讯工业气体指数今年以来的涨幅超40%。\n多位业内分析人士称,2025年以来,中国电子特气市场规模快速扩张,景气度有望持续提升。\n电子特气企业布局先进制程研发\n拓展高端领域\nAI芯片对特气材料提出更高要求\n当前,AI芯片持续向先进制程演进,工艺复杂度不断提升,对配套的特种气体材料提出更高的要求。央视财经记者在走访中了解到,为提高竞争力,不少电子特气企业正在积极布局先进制程特气材料的研发。\n金宏气体\n(\n30.080\n,\n1.13\n,\n3.90%\n)股份有限公司董事会秘书陈莹介绍,在芯片生产中,特气一旦断供就可能导致产品报废,AI芯片对特气产品要求精度更高、品类更多、保供要求更严。为适应芯片性能的提升,不少头部特气生产企业正在积极布局高端新材料。\n前驱体材料成布局热点\n央视财经记者了解到,前驱体材料覆盖逻辑芯片、存储芯片等,是芯片全制程刚需材料,目前已有多家电子特气上市公司公告入局,多位企业负责人称,近年来电子特气行业的整体国产化率不断提高,在高端领域还有很大拓展空间。\n业内人士表示,下游半导体企业对特气产品的认证周期较长,生产企业实际可供给的特气产能,取决于认证落地的进度,这是衡量生产厂商竞争力的关键指标。\nMACD金叉信号形成,这些股涨势不错!\n海量资讯、精准解读,尽在新浪财经APP\n责任编辑:郭栩彤",
|
||||||
|
"author": "郭栩彤",
|
||||||
|
"source_name": "新浪财经",
|
||||||
|
"publish_time": "2026-06-16T06:46:55+08:00",
|
||||||
|
"publish_time_raw": "2026-06-16T06:46:55+08:00",
|
||||||
|
"extracted_at": "2026-06-16T21:09:22.354346",
|
||||||
|
"images": [
|
||||||
|
"https://n.sinaimg.cn/sinakd20260616s/42/w567h275/20260616/9326-7682cab36d2cf99fc793098ac06dc362.jpg",
|
||||||
|
"https://n.sinaimg.cn/sinakd20260616s/113/w585h328/20260616/6188-1237150e1c9a6de6c0ac556ce1a494d8.jpg",
|
||||||
|
"https://n.sinaimg.cn/sinakd20260616s/51/w574h277/20260616/277e-912f577642b66e212203e5897a9a7751.jpg",
|
||||||
|
"https://n.sinaimg.cn/finance/d3f34f8d/20240626/fireicon.png",
|
||||||
|
"http://qr.weibo.cn/inf/gen?api_key=bdccdb65d34320c10b65cdf6c3503a53&data=https%3A%2F%2Ffinance.sina.cn%2Fapp%2FQRtoSFA.shtml%3Fsudatrackvalue%3Dcaitou_inQRcode_PcBottom%26schema%3Dtype%253D100%26docid%3Dnicpzzw9492841%26sinaglobal%3D60.187.79.169_1781612273.748269&datetime=nocheck&level=H&output_type=img&redirect=0&size=100&type=url&sign=af670d345fce194df4c0023857ca243d"
|
||||||
|
],
|
||||||
|
"word_count": 1222
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"source_id": "eastmoney",
|
||||||
|
"url": "https://finance.eastmoney.com/a/202606163772818076.html",
|
||||||
|
"url_hash": "6b68c4f478db8331",
|
||||||
|
"title": "刚刚加息25个基点!日本央行重大发布!",
|
||||||
|
"content": "三菱日联\n银行\n的Lee Hardman在报告中表示,尽管在美国和伊朗达成临时和平协议后能源价格下跌,但日元仍难以实现有意义的回升。在周二日本央行公布政策决定之前,做空日元的押注继续增加。“加息25个基点已被完全反映在预期中,因此不太可能单凭此举引发日元弱势的反转,从而鼓励了日元空头头寸进一步增加。”他说,如果能源价格继续下跌且对美国加息的押注减少,日本相关部门为支持日元而采取的任何进一步干预措施都将证明更加有效。",
|
||||||
|
"author": "券商中国",
|
||||||
|
"source_name": "东方财富",
|
||||||
|
"publish_time": "2026-06-16T11:45:00",
|
||||||
|
"publish_time_raw": "2026年06月16日 11:45",
|
||||||
|
"extracted_at": "2026-06-16T21:09:12.566094",
|
||||||
|
"images": [],
|
||||||
|
"word_count": 181
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"source_id": "cls",
|
||||||
|
"url": "https://www.cls.cn/detail/2400997",
|
||||||
|
"url_hash": "7240fbc3d215a690",
|
||||||
|
"title": "深圳发布暴雨红色预警,下午全市停课",
|
||||||
|
"content": "财联社6月16日讯,6月16日午间,“深圳教育”微信公众号发文称,深圳市分区暴雨红色预警、分区暴雨橙色预警、分区暴雨黄色预警信号、深圳市雷电预警信号、深圳市地质灾害气象风险预警生效中。\n根据市政府第334号令及《深圳市台风暴雨高级别预警分时段学校防御指引(2025年修订版)》采取分区预警,全市停课。6月16日下午全市(不含深汕)中小学、幼儿园及培训机构停课,12点前所有学生留在教室内,12点后继续留在学校,学校妥善安排好午餐午休。\n深圳市分区暴雨橙色预警信号升级为红色\n过去3小时,龙岗区(平湖、龙城、龙岗街道)、光明区(光明、新湖街道)和龙华区(观湖、福城、观澜街道)累计雨量已达大暴雨,预计未来2小时还将出现40-60毫米降水,深圳市气象台2026年06月16日11时21分在上述区域发布暴雨红色预警信号,全市进入暴雨紧急防御状态,请迅速做好防御工作,远离低洼易涝等危险区域,上述区域山洪风险很大,注意防御暴雨及其引发的内涝、山洪、滑坡、泥石流、地面塌陷等灾害。",
|
||||||
|
"author": "王天勇",
|
||||||
|
"source_name": "财联社",
|
||||||
|
"publish_time": "2026-06-16T13:34:00",
|
||||||
|
"publish_time_raw": "2026-06-16 13:34",
|
||||||
|
"extracted_at": "2026-06-16T21:09:07.116575",
|
||||||
|
"images": [
|
||||||
|
"https://image.cls.cn/images/20260616/mt4bGc8krY_1080x1108.png"
|
||||||
|
],
|
||||||
|
"word_count": 345
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"source_id": "yicai",
|
||||||
|
"url": "https://www.yicai.com/news/103231880.html",
|
||||||
|
"url_hash": "7cc880852846b306",
|
||||||
|
"title": "四只美股存储股价创新高,AI芯片厂商应对存储短缺各出奇招",
|
||||||
|
"content": "存储资源短缺,而AI对存储调用的需求有所提升,近期AI芯片厂商正通过优化存储利用方式来应对这一挑战。当地时间6月15日,AMD(AMD.O)宣布收购MEXT公司,以缓解内存瓶颈。\n据AMD介绍,MEXT开发了用AI预测内存使用的技术,能帮助扩大内存容量并保持性能和效率,这种方式有助于降低计算基础设施的成本、提高资源利用率并帮助客户扩展通用和AI工作负载。AMD将把相关技术整合进数据中心产品组合中。AMD并未公布收购该公司的价格。\nAMD之外,另一些AI芯片公司近期也在推动存储优化技术应用。\n英特尔在推动QAT(快速辅助技术)和IAA(内存分析加速)技术应用。英特尔相关负责人近日解释,AI运行时产生大量KV Cache(一种通过缓存向量避免冗余计算的技术),指向一大笔闪存开销,需要通过QAT技术让压缩比达到50%以节省闪存成本,并在多个智能体运行时用IAA压缩内存数据,这两项技术将是推动Agentic(代理式)AI的重要抓手。\n数据中心之外,端侧设备对成本的要求更加严苛,运行AI时也容易遭遇存储瓶颈。对此,Arm(ARM.O)近期在推动统一内存技术应用。近日接受记者采访时,Arm边缘AI事业部执行副总裁Chris Bergey表示,Arm与英伟达合作推出的个人电脑芯片RTX Spark采用了统一内存架构,以便让CPU和GPU能完整调用全部调用DRAM(动态随机存取存储器)及带宽资源,统一内存技术正在推动边缘计算变革。\n除了提升存储使用率,应对存储短缺的状况,还有厂商近期降低了某些产品的存储配置。\n市场研究机构TrendForce集邦咨询近日发布调研报告称,英伟达(NVDA.O)决定将Vera Rubin Superchip模组所搭载的SOCAMM容量砍半。该机构解释称,这并非英伟达下调存储总需求,而是应对2027年供应端规划配给产能不足时,英伟达选择降低单颗容量并扩大模组出货数量,这也凸显了LPDDR5X存储产品的供应缺口和需求上扬。\n上述机构近日还发布报告称,受智能体普及和云服务厂商的订单带动,今年第一季度全球前五大企业级SSD(固态硬盘)厂商总营收创新高。由于供应商库存水位降至历史低点,产出速度远不及订单增长速度,供应商推高价格,导致该季度合约价增长了80%。\n基于对存储市场需求的判断,华尔街独立投研机构Aletheia Capital还发布了一份颇为激进的研究报告,预测存储器件在AI硬件系统材料清单中的价值占比将超70%,并将美光科技(MU.O)的目标价大幅上调至1600美元。\n美股6月15日,多家美股AI芯片及存储厂商股价大涨。美光科技涨10.84%,西部数据(WDC.O)涨16.1%,闪迪(SNDK.O)涨6.45%,希捷科技(STX.O)涨9.43%,这四家存储公司股价都创历史新高。此外,AMD涨6.98%,英伟达涨3.54%。",
|
||||||
|
"author": "宁佳彦",
|
||||||
|
"source_name": "第一财经",
|
||||||
|
"publish_time": "2026-06-16T00:00:00",
|
||||||
|
"publish_time_raw": "2026-06-16",
|
||||||
|
"extracted_at": "2026-06-16T21:09:26.296326",
|
||||||
|
"images": [
|
||||||
|
"https://imgcdn.yicai.com/uppics/images/2026/06/a324b7087c2cc0cb81483174fd67d318.jpg"
|
||||||
|
],
|
||||||
|
"word_count": 825
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"source_id": "sina",
|
||||||
|
"url": "https://finance.sina.com.cn/jjxw/2026-06-16/doc-inicprni4686869.shtml",
|
||||||
|
"url_hash": "7d028ab11a8bd785",
|
||||||
|
"title": "美官员:美伊协议已电子签署 19日将正式签署",
|
||||||
|
"content": "美国政府一名高级官员15日表示,美国和伊朗已通过电子方式签署了谅解备忘录,但双方仍将于19日举行正式签署仪式。\n该官员在一场记者简报会上说,美国总统特朗普和副总统万斯签署了这份谅解备忘录,伊朗伊斯兰议会议长卡利巴夫代表伊朗方面签署了该文件,预计万斯以及特朗普的特使威特科夫和女婿库什纳将出席19日的仪式。万斯随后将带领美方进入下一阶段谈判。\n该官员说,谅解备忘录将在未来24至48小时内公开发布。不过,特朗普当天在法国出席七国集团峰会期间表示,谅解备忘录将在正式签署仪式之后公布。\n美官员表示,在美伊技术性谈判期间,美国将维持在中东现有的兵力部署;若最终达成协议,美国则将减少在中东地区的兵力。\n关于霍尔木兹海峡,美官员表示,该海峡的交通将很快恢复正常,“肯定会在30天内恢复,但前提是伊朗承诺清除所有水雷”。美国“目前掌握所有水雷的位置”,实际上也能协助进行处置工作。\n美官员表示,美伊协议中并未要求以色列必须从黎巴嫩撤军,“撤军并非该协议的先决条件,如果伊朗无法控制黎巴嫩真主党,且后者对以色列据点或城镇发动袭击,以色列将有权进行自卫并予以回击”。该官员还认为,在黎巴嫩发生的持续冲突不会阻碍特朗普政府与伊朗的协议。\n万斯当天早些时候表示,双方有许多“至关重要”的细节需要在后续为期60天的谈判中敲定。\n海量资讯、精准解读,尽在新浪财经APP\n责任编辑:丁文武",
|
||||||
|
"author": "丁文武",
|
||||||
|
"source_name": "新浪财经",
|
||||||
|
"publish_time": "2026-06-16T02:34:14+08:00",
|
||||||
|
"publish_time_raw": "2026-06-16T02:34:14+08:00",
|
||||||
|
"extracted_at": "2026-06-16T21:09:21.060206",
|
||||||
|
"images": [
|
||||||
|
"https://n.sinaimg.cn/spider20260616/251/w1600h251/20260616/2300-gif5087632f32e31f026bb42da42f405f74.gif",
|
||||||
|
"http://qr.weibo.cn/inf/gen?api_key=bdccdb65d34320c10b65cdf6c3503a53&data=https%3A%2F%2Ffinance.sina.cn%2Fapp%2FQRtoSFA.shtml%3Fsudatrackvalue%3Dcaitou_inQRcode_PcBottom%26schema%3Dtype%253D100%26docid%3Dnicprni4686869%26sinaglobal%3D60.187.79.169_1781612255.449005&datetime=nocheck&level=H&output_type=img&redirect=0&size=100&type=url&sign=a0784dc6b14b510cf43ea3b067413428"
|
||||||
|
],
|
||||||
|
"word_count": 511
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"source_id": "eastmoney",
|
||||||
|
"url": "https://finance.eastmoney.com/a/202606163771956751.html",
|
||||||
|
"url_hash": "7f7648ff872d9d7a",
|
||||||
|
"title": "美伊和谈热点Q&A:霍尔木兹海峡还要多久恢复正常?",
|
||||||
|
"content": "法兴\n银行\n大宗商品研究主管迈克尔·海伊称,航运恢复后,石油仍需52天才能运抵遥远的亚洲买家手中并完成提炼。\n这意味着假设霍尔木兹海峡在6月底重新开放,供应缓解要到8月底才能实现,而真正意义上的正常化则要等到9月。",
|
||||||
|
"author": "财联社",
|
||||||
|
"source_name": "东方财富",
|
||||||
|
"publish_time": "2026-06-16T04:27:00",
|
||||||
|
"publish_time_raw": "2026年06月16日 04:27",
|
||||||
|
"extracted_at": "2026-06-16T21:09:09.811629",
|
||||||
|
"images": [],
|
||||||
|
"word_count": 92
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"source_id": "yicai",
|
||||||
|
"url": "https://www.yicai.com/news/103231922.html",
|
||||||
|
"url_hash": "7f867ba039eef762",
|
||||||
|
"title": "投资增速降幅扩大,国家统计局最新回应",
|
||||||
|
"content": "今年1-5月,全国固定资产投资(不含农户)178512亿元,同比下降4.1%,降幅比上月扩大2.5个百分点;扣除房地产开发的固定资产投资下降1.2%。\n国家统计局新闻发言人付凌晖6月16日在国务院新闻办发布会上表示,固定资产投资降幅有所扩大,有局部地区高温多雨因素的影响,也是新旧发展动能转换、投资由总量扩张向质量提升转变的客观反映。\n付凌晖强调,尽管投资增速下降,但在政策持续发力、创新引领增强、民生保障加力这些因素带动下,投资结构持续优化,投资在强基础、促转型、惠民生方面发挥了不可替代的重要作用。\n分领域看,1-5月份,基础设施投资同比增长0.6%,增速较1-4月回落3.7个百分点;房地产开发投资下降16.2%,降幅扩大2.5个百分点。制造业投资下降0.4%,1-4月为上涨1.2%。\n付凌晖指出,对于投资增速的变化,需要全面、客观、辩证地看,观察投资变化不仅要看总量、规模的变化,更要注重投资结构、质量、效益的情况。\n当前,创新引领作用增强,新兴产业快速发展更新改造的需求扩大,带动相关领域投资增加,不仅拓展了投资的增长空间,更为培育新产业、塑造发展新动能注入强劲动力。\n数据显示,1-5月份,知识产权产品投资同比增长9.3%,增速比1-4月份加快0.4个百分点,拉动全部投资增长1.1个百分点。人工智能、新能源快速发展,高技术产业投资同比增长4.5%,集成电路制造业、锂离子电池制造业投资分别增长11%、24.9%,信息服务业投资增长13.8%。此外,大规模设备更新政策有力带动企业改造升级,1-5月份设备工器具购置投资增长9.3%。\n东方金诚首席宏观分析师王青表示,当前高技术制造业面临市场供需两旺,财政金融政策支持力度很强的有利发展环境,高技术产业投资有望保持在较高增长状态。二季度以高技术制造业为代表的新质生产力领域会延续高增态势,芯片、新能源汽车、工业机器人、3D打印等产品产量有望保持两位数增长。\n民生领域投资也在继续加强。1-5月份,我国农业、渔业投资分别增长10.7%和27.5%;生态保护和环境治理业投资增长3.5%,2026年安排中央财政支持城市更新专项资金的规模达到970亿元,有望带动居住条件改善,惠及更多居民。\n基础设施是畅通要素流动、促进经济发展、服务群众生活的重要保障。今年以来,一批重大基建项目持续推进,“六张网”等基础设施建设加快布局。1-5月份,基础设施投资保持小幅增长,增长0.6%。各方抢抓新一轮科技变革的机遇,算力网和新一代通信设施等新基建有序推进,1-5月份信息传输业投资增长30.4%。\n“六张网”及重点领域建设是今年扩大有效投资的重要发力点。5月召开的国务院常务会议强调,加强水网、新型电网、算力网、新一代通信网、城市地下管网、物流网等规划建设。此前4月28日中共中央政治局会议也指出,加强“六张网”等规划建设;推动条件成熟的重大工程项目开工。\n国家发改委新闻发言人李超近日表示,抓紧出台相关规划和实施方案,进一步统筹“六张网”建设内容,明确各领域投资重点,将目标任务分解到年度,明确时间和进度安排。\n“六张网”等重点项目规划建设离不开资金要素的充足保障。国家发改委明确,推动今年7550亿元中央预算内投资、1万亿元超长期特别国债于6月底前基本下达完毕,进一步提高地方政府专项债券中用于项目建设的比重,加快有序投放8000亿元新型政策性金融工具资金。\n投资是拉动经济增长的“三驾马车”之一,既是扩大内需的关键抓手,也是稳增长、惠民生、利长远的重要支撑。中央经济工作会议在部署今年重点工作时强调,推动投资止跌回稳。\n付凌晖表示,展望下阶段,我国投资空间依然广阔,新型城镇化建设、推进乡村振兴、新质生产力发展、公共服务提升等都需要投资的支持,从而为实现高质量发展打下坚实基础。对此,要发挥政府投资引导带动作用,激发民间投资活力,坚持将投资于物和投资于人紧密结合,更加注重优化提升投资结构和效益,更好发挥有效投资对促进高质量发展的重要作用。",
|
||||||
|
"author": "秦新安",
|
||||||
|
"source_name": "第一财经",
|
||||||
|
"publish_time": "2026-06-16T00:00:00",
|
||||||
|
"publish_time_raw": "2026-06-16",
|
||||||
|
"extracted_at": "2026-06-16T21:09:26.110378",
|
||||||
|
"images": [
|
||||||
|
"https://imgcdn.yicai.com/uppics/images/2026/06/6f869605dafd3a58a77e9c10c1ebf12e.jpg"
|
||||||
|
],
|
||||||
|
"word_count": 1336
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"source_id": "cls",
|
||||||
|
"url": "https://www.cls.cn/detail/2401317",
|
||||||
|
"url_hash": "835b30980e38354f",
|
||||||
|
"title": "“无人晶圆厂”要来了?存储双雄接连布局 AI加速反哺半导体制造",
|
||||||
|
"content": "《科创板日报》6月16日讯\nAI浪潮拉动半导体需求的同时,也开始反哺芯片制造流程。\n据报道,三星电子正在开发和运营“数据共享生态平台(Data Sharing Eco Platform,简称DSEP)”,用于与材料、零部件和设备供应商实时共享半导体工艺数据。其计划与合作伙伴共同分析数据,并将其扩展为基于人工智能的工厂操作系统,最终为无人晶圆厂的建设奠定基础。\n此前,由于安全原因,半导体关键设备数据难以传输到工厂外部。如今随着半导体制造工艺不断演进,制造流程中产生的数据量迅速增长,仅单个工厂便可产生数十亿个数据点。据悉,三星电子计划利用DSEP内置的人工智能模型来增强设备检测、故障预测和缺陷概率分析的能力。\n这与三星电子的“无人晶圆厂战略”相契合,其计划在2030年前将其所有晶圆厂全部改造为由AI驱动的无人化工厂。\n为实现这一目标,该公司已为相关基础设施建设奠定了基础,例如在半导体事业部内设立高性能计算(HPC)中心,作为用于数据分析的枢纽。\n截至目前,参与DSEP的公司数量已超过60家,主要为设备供应商,并且数量还在持续增长。业内人士指出,随着DSEP共享数据的利用,AI在半导体设备制造商中的应用也在不断扩展。他补充道:“这正在加速合作伙伴生态系统向无人化晶圆厂的转型。”\n在存储芯片领域,AI技术正加速渗透进半导体生产流程中。\n譬如SK海力士便开始把AI技术运用到自芯片设计和制造流程里,其正与英伟达探索将数字孪生与现有传统软件及智能体AI工作流打通,让AI系统能够基于晶圆厂数据进行推理、自动执行任务并改进制造决策。\n据日前报道,英伟达与SK海力士宣布建立多年期技术合作伙伴关系,后者将采用英伟达CUDA-X库及PhysicsNeMo框架加速芯片仿真和光刻计算工作流,双方将推动这些工具扩展到半导体电子设计自动化(EDA)和仿真生态系统中去,为芯片制造商、英伟达和EDA软件供应商的三方合作铺路。\n除此之外,\nSK海力士还将借助英伟达Omniverse和cuOpt构建晶圆厂数字孪生,推动工厂自主化运营。\n借助Omniverse库SK海力士得以构建3D工厂场景,用于可视化、模拟和优化复杂的半导体制造环境。\n海通国际证券表示,上述合作为工业AI、物理仿真AI平台新增典型落地场景。伴随AI平台持续迭代,高端存储赛道的竞争核心将转变为技术代际升级、客户深度认证、先进封装协同、跨多代产品持续配套能力。",
|
||||||
|
"author": "季晟",
|
||||||
|
"source_name": "财联社",
|
||||||
|
"publish_time": "2026-06-16T18:08:00",
|
||||||
|
"publish_time_raw": "2026-06-16 18:08",
|
||||||
|
"extracted_at": "2026-06-16T21:09:08.302513",
|
||||||
|
"images": [],
|
||||||
|
"word_count": 793
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"source_id": "cls",
|
||||||
|
"url": "https://www.cls.cn/detail/2400898",
|
||||||
|
"url_hash": "8819e4799652096b",
|
||||||
|
"title": "【盘中宝·记者一线】该公司产品再次涨价,AI浪潮下细分产品供应紧张态势或将长期持续",
|
||||||
|
"content": "①记者深入覆铜板行业一线:PPO树脂供不应求,次日多股异动大涨;②TGV行业处于从研发验证向规模化量产过渡的关键拐点,栏目敏锐捕捉行业最新动态,这家公司5月大涨超61%;③栏目前瞻指出PCB钻针已成为“电子基础设施”里最具弹性的细分赛道,这家公司5月涨不停。",
|
||||||
|
"author": null,
|
||||||
|
"source_name": "财联社",
|
||||||
|
"publish_time": "2026-06-16T11:22:00",
|
||||||
|
"publish_time_raw": "2026.06.16 11:22",
|
||||||
|
"extracted_at": "2026-06-16T21:09:07.768170",
|
||||||
|
"images": [],
|
||||||
|
"word_count": 102
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"source_id": "cls",
|
||||||
|
"url": "https://www.cls.cn/detail/2400667",
|
||||||
|
"url_hash": "8bb2be93f25d517d",
|
||||||
|
"title": "史诗级造富盛宴持续!SpaceX股价再飙20% 马斯克身家升破1.3万亿美元",
|
||||||
|
"content": "财联社6月16日讯(编辑 卞纯)\n周一,马斯克旗下火箭、卫星与AI公司SpaceX股价在其上市第二个交易日(首个完整交易日)大涨逾19.6%,收于192.5美元,延续了上周五上市首日的涨势。与此同时,马斯克旗下电动汽车公司特斯拉股价也小幅上涨了1.2%。\nSpaceX股价的进一步上涨令马斯克的净资产一日之内增加了1648亿美元(约合1.11万亿元人民币),使其身价达到创纪录的1.3万亿美元。\n这一数字让他比在富豪榜上排名第二的谷歌联合创始人拉里·佩奇(身家为3014亿美元)高出约1万亿美元。\n马斯克一天的净资产增值,超过了全球第十大富豪沃伦·巴菲特的全部身家(约为1464亿美元)。\n马斯克持有48亿股SpaceX股票,另有3.5亿股行权价为每股8.40美元的股票期权,使其持股比例达到约38%。\nSpaceX周一宣布,承销商已行使IPO“绿鞋”超额配售权,使公司本次上市募资总额从原先的750亿美元增至857亿美元。此次绿鞋涉及额外约8330万股,以满足投资者的强劲需求。\nSpaceX通过超额配售额外筹集的资金,规模超过了历史上几乎所有科技公司的IPO。通常情况下,当股票价格上涨时,承销商就会行使超额配售权,也称“绿鞋期权”。\n上周五, SpaceX的员工在交易大厅特意穿上绿色协议,以此向“绿鞋期权”致敬,马斯克随后在社交平台X上转发了相关照片。\n此前SpaceX以每股135美元发行5.5556亿股,并于上周五登陆纳斯达克。据报道,此次IPO共吸引了超过3500亿美元的认购资金,其中散户投资者认购约1000亿美元,机构投资者的认购需求达到约2500亿美元。\n上周五,SpaceX的上市首秀(公司股价大涨19%,市值突破2万亿美元)让马斯克所持该公司股份的价值膨胀至约8210亿美元,这也使他成为全球首位万亿美元富翁,当时其净资产达到1.1万亿美元。\n事实上,马斯克的身家在过去一年中一路狂飙,不断突破新的里程碑:去年10月,他成为首位身价达5000亿美元的富豪;随后在12月达到6000亿美元;仅仅四天之后,便突破了7000亿美元大关。\n马斯克周日(6月14日)豪言, SpaceX到2030年有望实现1万亿美元的收入。他在社交媒体平台X上评论道,“如果SpaceX到2031年的收入还不到1万亿美元,我会感到惊讶。”\n作为对比,2025年,SpaceX的收入从一年前的140.2亿美元激增至186.7亿美元,但公司由盈利7.91亿美元转为净亏损49.4亿美元。\n因此,一些华尔街分析师对公司的增长持谨慎态度,这一史上最大的IPO也引发了关于该公司巨额估值是否合理的争论。\nCFRA上周五首次对覆盖该股,给予“卖出”评级,并设定12个月目标价为115美元,较最新收盘价低约40%。CFRA表示,其评级依据是“该公司激进的增长战略、过高的估值预期以及资本密集度”。",
|
||||||
|
"author": "林琦",
|
||||||
|
"source_name": "财联社",
|
||||||
|
"publish_time": "2026-06-16T08:20:00",
|
||||||
|
"publish_time_raw": "2026-06-16 08:20",
|
||||||
|
"extracted_at": "2026-06-16T21:09:08.728405",
|
||||||
|
"images": [
|
||||||
|
"https://image.cls.cn/images/20260616/z29MYhb596_554x431.png",
|
||||||
|
"https://image.cls.cn/images/20260616/p6cCq04238_554x284.png",
|
||||||
|
"https://image.cls.cn/images/20260616/TJ1hsy402W_554x711.png"
|
||||||
|
],
|
||||||
|
"word_count": 836
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"source_id": "cls",
|
||||||
|
"url": "https://www.cls.cn/detail/2386897",
|
||||||
|
"url_hash": "8c0a666c2876215f",
|
||||||
|
"title": "倒计时3天!以专业资讯,执掌下半年主动权",
|
||||||
|
"content": "一年一度的财联社618年中大促,正式进入最后收官倒计时!\n多重福利将于6月18日24:00截止。限时折扣、满额加赠、会员卡钜惠等活动环节等还有最后参与机会。如果你一直在等一个合适的节点,去配置一套真正能帮自己省时间、理思路的资讯——那么,现在就是这个节点。以更低的成本,让自己在下半年的市场波动中,多一分从容,少一分慌乱。\n👉\n<<<点此参与活动>>>\n👈\n福利一:全品类栏目限时抄底,经典&新品双重特惠\n本次大促覆盖平台全部优质栏目,分区让利、性价比拉满,适配不同投资者的投研需求。\n其中8款经典核心栏目包年低至8折\n,长期刚需栏目一键囤购,节省全年使用成本,王牌栏目《盘中宝》最高立省2778元。\n全新上线的\n栏目《玩转etf》和升级栏目《数据研选》专享68折超低折扣\n,力度空前,抢先体验全新投研工具、前沿资讯解读。新旧栏目组合优惠,一站式配齐完整投研体系。\n更重磅的是,凡订阅任意VIP产品,均免费赠送VIP精编资讯+主编私享会专属服务,全程陪伴式赋能,助力投资者高效追踪热点、规避投资风险,让用户学会如何用好VIP。此外,订阅还加赠同等时长的《风口专家会议》,深度了解热门产业。\n重要提醒:收官专属秒杀加场,弥补前期踏空遗憾\n此前6月15日热门的包年75折秒杀专场已圆满结束,不少投资者遗憾错过年度超低折扣。为回馈广大用户,本次大促收官阶段重磅开启6月18日当日(即活动最后一天)限定包季75折加场,专属限时福利弥补前期秒杀踏空遗憾。包季折扣门槛更低、灵活性更强,同样也适合想要短期体验投资者。感兴趣的投资者可千万不要错过。定好闹钟,\n点此进入秒杀>>\n福利二:三档会员卡重磅让利,年度大额直降福利\n本次618大促会员优惠力度拉满,平台三档会员卡开启超大让利活动,\n钻石卡最高直降19910元。\n本次会员卡包含11款新旧栏目全品类,一键解锁多维度资讯服务,适配不同投资周期、不同需求的投资者,让专业资讯不再“高门槛”,让投资更有底气。\n凡订阅任意档位会员卡,除享受万元直降钜惠外,还可同步赠送VIP精编资讯+主编私享会。详情扫描下方二维码添加官方客服财联社小助理进行咨询和订阅。\n福利三:消费满额加码赠礼,福利叠加再升级\n最后在所有折扣优惠基础上,平台叠加满赠福利,活动期间消费满5000元即赠送VIP周卡。折扣降价+免费赠权益双重福利叠加,花更少的钱,享受更长周期、更全面的VIP专属投研服务,全方位助力投资者复盘行情、挖掘题材、追踪主力、把握市场热点。\n时间不等人,心动不如赶快行动。\n注:\n本次活动时间为 2026 年6月8日 00:00-6月18日 24:00;\n财联社 VIP 内所有栏目均为虚拟商品,一经订阅无法退换;\n财联社对本次活动有最终解释权,如有任何问题,可通过添加官方客服微信咨询;\n财联社 VIP 为内容资讯产品,并非投资建议,栏目发布的所有内容仅供参考,据此做出的任何投资决策与本公司及栏目作者无关。",
|
||||||
|
"author": null,
|
||||||
|
"source_name": "财联社",
|
||||||
|
"publish_time": "2026-06-15T17:09:00",
|
||||||
|
"publish_time_raw": "2026-06-15 17:09",
|
||||||
|
"extracted_at": "2026-06-16T21:09:07.427862",
|
||||||
|
"images": [
|
||||||
|
"https://image.cls.cn/images/20260601/A1vfQ0fVy9_1280x560.png",
|
||||||
|
"https://image.cls.cn/images/20260601/cRHMqI03zH_1280x560.png",
|
||||||
|
"https://image.cls.cn/images/20260601/hK4k9l9hs3_750x681.jpg",
|
||||||
|
"https://image.cls.cn/images/20260601/jM19uVz3WC_1280x560.png",
|
||||||
|
"https://image.cls.cn/images/20260601/J2Ii2d83b8_2026x1045.png",
|
||||||
|
"https://image.cls.cn/images/20260601/6WXtTBYU14_1045x1378.png",
|
||||||
|
"https://image.cls.cn/images/20260601/36UZl4YcK9_750x871.jpg"
|
||||||
|
],
|
||||||
|
"word_count": 971
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"source_id": "cls",
|
||||||
|
"url": "https://www.cls.cn/detail/2401230",
|
||||||
|
"url_hash": "8c39081098168808",
|
||||||
|
"title": "一图看懂",
|
||||||
|
"content": "财联社\n©2018-2026\n上海界面财联社科技股份有限公司 版权所有\n沪ICP备14040942号-9\n沪公网安备31010402006047号\n互联网新闻信息服务许可证:31120170007\n沪金信备 [2021] 2号",
|
||||||
|
"author": "张家东收藏阅",
|
||||||
|
"source_name": "财联社",
|
||||||
|
"publish_time": "2026-06-16T16:55:00",
|
||||||
|
"publish_time_raw": "2026-06-16 16:55",
|
||||||
|
"extracted_at": "2026-06-16T21:09:08.144419",
|
||||||
|
"images": [],
|
||||||
|
"word_count": 48
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"source_id": "yicai",
|
||||||
|
"url": "https://www.yicai.com/news/103232427.html",
|
||||||
|
"url_hash": "8cce1a89388e36c9",
|
||||||
|
"title": "上海迪士尼开业十年“成绩单”来了,未来拟IP跨界打造“文旅+”产业",
|
||||||
|
"content": "“我女儿生日,今天特意带她来上海迪士尼,算是给她的生日礼物,同时也是来参与上海迪士尼十周年庆典,可以参与很多特有的活动。”游客蒋女士6月16日一大早成为上海迪士尼乐园当天第一批游客。\n2016年6月16日,上海迪士尼乐园正式开业,今天正好是乐园开业十周年,2025年10月,上海迪士尼乐园迎来了开幕以来的第1亿位游客,依托迪士尼核心引擎,上海国际旅游度假区文旅消费热度持续攀升,比斯特上海购物村十年间销售额复合增长率达25%,2025年接待游客约430万人次,再创历年峰值,上海国际旅游度假区正在从单一乐园向现代化旅游城迈进。\n上海迪士尼度假区方面透露,标志性项目“疯狂动物城:热力追踪”已成为乐园评分最高的游乐项目,开幕两年半以来,游客乘坐“动物城警车”行驶的总“里程”累加在一起已达约91万公里,相当于绕地球赤道22圈。衍生IP经济是迪士尼收入的主要模式,自开业以来,上海迪士尼度假区共计推出超过32000种商品,累计售出超过3100万个迪士尼主题毛绒钥匙圈,如果将这些钥匙圈全部垂直叠放,其总高度相当于16560座东方明珠广播电视塔。自2021年9月玲娜贝儿首发亮相以来,度假区已推出超670款玲娜贝儿主题商品。\n上海迪士尼度假区也是上海及长三角区域经济发展的引擎。建设期间,与项目相关的固定资产投资有力拉动上海GDP和就业增长。目前,度假区直接雇佣约1.5万名演职人员,其中99%来自中国,据估算,每个直接就业岗位可带动相关行业新增6至7个间接就业岗位。\n谈及未来的发展,迪士尼乐园国际总裁及董事总经理Tasia Filippatos接受第一财经记者专访时透露:“我们有非常丰富的迪士尼IP,可以探索与其他品牌的跨界IP资源整合,比如此前迪士尼与F1的合作,覆盖更多消费者群体,F1的粉丝很契合迪士尼消费品的发展战略,我们也与上海迪士尼度假区团队探讨,未来可以在音乐、时尚、体育等多个领域展开跨界合作,覆盖更多消费者。”\n迪士尼体验主席Thomas Mazloum告诉第一财经记者,上海迪士尼乐园在全球市场中非常重要,上海迪士尼永远不会完工,目前第三座主题酒店正式名称为“上海迪士尼星愿酒店”,拟今年冬天开业,蜘蛛侠主题园区建设和迪士尼小镇扩建等都在推进中。“随着上海迪士尼运营的日趋成熟,这里不仅仅是一个旅游目的地,以后还可以充分利用上海迪士尼这个平台做更多跨界联动,发挥飞轮效应,解锁更多商业模式,覆盖更多消费者。”\n上海申迪(集团)有限公司董事长管韬萍表示:“站在新十年的新起点上,申迪集团将持续深化拓展与华特迪士尼公司的合作,依托上海迪士尼度假区持续将上海国际旅游度假区打造为世界级多日旅游目的地,加快度假区‘多日经济’业态布局。”\n据悉,申迪集团正围绕增量、集聚、协同效应,引入兼具国际化、未来感、沉浸式的新IP、新项目、新场景,打造“3+N”业态布局——以体育为主题打造专业潮流运动集群,以动物为主题打造生态疗愈度假集群,以科技文化为主题打造中国创意科技展示集群。同步加快横沔古镇建设,联动上海野生动物园提质增效,配套打造商业、酒店、教育等功能项目,构建多日经济“文旅+”产业生态体系。",
|
||||||
|
"author": "陈姗姗",
|
||||||
|
"source_name": "第一财经",
|
||||||
|
"publish_time": "2026-06-16T00:00:00",
|
||||||
|
"publish_time_raw": "2026-06-16",
|
||||||
|
"extracted_at": "2026-06-16T21:09:22.743745",
|
||||||
|
"images": [
|
||||||
|
"https://imgcdn.yicai.com/uppics/images/ios/yicai/DD4FBE51-46DF-4A39-B908-05FE9C13EE70.jpg",
|
||||||
|
"https://imgcdn.yicai.com/uppics/images/ios/yicai/9A4E326A-1842-4465-98F5-10354357C98E.jpg",
|
||||||
|
"https://imgcdn.yicai.com/uppics/images/ios/yicai/E8DC7C33-4F40-40D0-A6EE-2EC41CE14A11.jpg",
|
||||||
|
"https://imgcdn.yicai.com/uppics/images/ios/yicai/ACC198DE-C38B-439B-BE1C-64B75DA1BA81.jpg"
|
||||||
|
],
|
||||||
|
"word_count": 1074
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"source_id": "sina",
|
||||||
|
"url": "https://finance.sina.com.cn/money/future/roll/2020-03-16/doc-iimxxstf9537645.shtml",
|
||||||
|
"url_hash": "97fd3b235266b9d3",
|
||||||
|
"title": "期指持仓显著回落",
|
||||||
|
"content": "虽然隔夜美联储降息100个基点并重启量化宽松,但周一外围股市未受提振转而大幅走低,沪深两市小幅高开之后随即回落,全天呈现单边下行走势,上证综指跌穿2800点,收报2789.25点。期指三个品种全线下挫,IF、IH及IC主力合约分别下跌4.6%、4.02%及5%。基差方面,由于市场预期转差,期货贴水有所扩大,截至收盘,IF、IH及IC主力合约分别贴水35.8、25.4和51.3点。\n量能方面,本周为交割周,期指持仓出现一定程度回落,三个品种当日共计减仓22249手,总持仓降至429196手。期指各品种持仓均有不同程度下滑,其中IF减仓5552手,持仓降至155417手,IH减仓5356手,持仓降至74363手,IC减仓11521手,持仓降至不足20万手。\n主力持仓方面,虽然期指各品种持仓均出现回落,但其多空主力持仓则呈现增减不一的局面。其中IF多空主力双双增仓2000余手,多头减2760手,空头减2503手,净空持仓小幅增至8509手;与IF不同,IH主力有所增仓,多头增2204手,空头增2680手,净空持仓增至4432手;IC主力大幅减仓,多头减5713手,空头减7246手,净空持仓降至12119手。\n具体席位方面,虽然周一市场大幅下跌,但部分席位多头继续增仓抄底。其中,\n国泰君安\n(\n17.790\n,\n0.30\n,\n1.72%\n)\n期货多头增仓1212手,净多持仓升至6227手,银河期货多头增仓278手,净持仓由多翻空,净多17手,瑞银期货多头增仓552手,净多持仓升至1902手,同时也有部分席位多头持仓出现回落,广发、一德及东吴期货等席位多头持仓均有所下滑。空头方面,中信期货空头继续增仓,当日增1145手,净空持仓升至3141手。此外,海证及招商期货空头分别增仓102和127手至1424和613手。与此同时,国富及国信期货等席位空头持仓则出现一定程度下滑。\n图为IF多空主力持仓\n总体来说,周一期指明显减仓,随着市场波动增大,部分资金平仓离场。短期看,期指经过周一大幅调整,短线跌势将有所放缓,短期有望止跌企稳。\n(作者单位:永安期货)\n责任编辑:张瑶",
|
||||||
|
"author": "张瑶",
|
||||||
|
"source_name": "新浪财经",
|
||||||
|
"publish_time": "2026-06-15T00:00:00",
|
||||||
|
"publish_time_raw": "2026年06月15日",
|
||||||
|
"extracted_at": "2026-06-16T21:09:20.567079",
|
||||||
|
"images": [
|
||||||
|
"https://n.sinaimg.cn/spider2020316/462/w859h403/20200316/1b3e-iqyryku3912931.jpg"
|
||||||
|
],
|
||||||
|
"word_count": 593
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"source_id": "sina",
|
||||||
|
"url": "https://finance.sina.com.cn/money/nmetal/hjzx/2026-06-16/doc-inicqsxv4521793.shtml",
|
||||||
|
"url_hash": "9b43e40b2aa71b8a",
|
||||||
|
"title": "“国家队”购金意愿创历史新高!金价回调就是“抄底”良机?",
|
||||||
|
"content": "文章来源:金十数据\n世界黄金协会(World Gold Council)周二发布的报告显示,在其联合YouGov Plc对74家央行开展的调查中,\n有45%的受访央行计划在未来一年增加黄金储备,这一比例为自2018年开始统计以来的最高水平\n。同时,\n仅有一家央行表示将减持黄金。\n这一结果表明,\n即便今年\n金价\n出现回落,支撑此前创纪录上涨的重要动力仍然存在\n。过去三年间,在各国央行持续加快购金的推动下,金价累计上涨已超过一倍。\n不过,今年以来的市场环境出现变化。中东冲突推升能源成本,并强化了市场对利率将在更长时间维持高位的预期,从而削弱了不产生收益的黄金吸引力,部分涨幅因此被回吐。\n与此同时,投机资金持续撤出相关交易,金价近期已跌至自去年11月以来的最低水平。\n新兴经济体成为主要买方\n从购金主体来看,\n未来一年的潜在需求主要来自新兴市场和发展中经济体\n。调查显示,这类央行中约有53%计划增加黄金储备,而发达经济体央行的对应比例为18%。\n世界黄金协会央行全球主管Shaokai Fan指出,\n价格回调反而为部分央行提供了入场契机\n。他表示:“我认为价格下跌是一些央行开始买入的良机。”\n他还提到,在2025年,不少央行曾因价格偏高而选择观望,“我们确实发现许多央行表示,‘哦,现在的价格有点高,我想等一等,看看是否有机会买入’。”\n尽管土耳其、俄罗斯和阿塞拜疆已开始减持黄金,但从整体来看,\n今年第一季度全球央行的购金节奏仍有所加快\n。\n在资金来源方面,央行更倾向于通过国内渠道积累黄金。调查显示,\n约一半计划增持的央行将利用本币从本国矿商采购黄金,而非动用有限的外汇储备;另有38%的央行打算通过出售其他储备资产来筹资。\n存储安排方面,英国央行仍是全球央行最主要的黄金托管地,57%的受访机构选择将黄金存放于此。不过,去中心化趋势正在显现。\n数据显示,过去一年有9%的央行增加了本土黄金储备,高于上一年的5%;另有10%的央行开始将黄金分散存放在不同地点,这一比例也高于此前的2%。\nFan表示,政治风险“绝对是央行考虑的重要因素”。他认为,这种变化可能为新加坡等新兴黄金存储中心带来机会,这些地区正积极推广央行黄金托管业务,以推动本地黄金市场发展。\n新浪合作大平台期货开户 安全快捷有保障\n海量资讯、精准解读,尽在新浪财经APP\n责任编辑:朱赫楠",
|
||||||
|
"author": "朱赫楠",
|
||||||
|
"source_name": "新浪财经",
|
||||||
|
"publish_time": "2026-06-16T15:42:21+08:00",
|
||||||
|
"publish_time_raw": "2026-06-16T15:42:21+08:00",
|
||||||
|
"extracted_at": "2026-06-16T21:09:19.009266",
|
||||||
|
"images": [
|
||||||
|
"https://n.sinaimg.cn/finance/770/w640h130/20240312/9be9-7bcc77500b41e587aee5a856fe9849e2.png",
|
||||||
|
"https://image.sinajs.cn/n/hf/min/640x360xxfhd/XAU.png",
|
||||||
|
"https://n.sinaimg.cn/finance/transform/340/w170h170/20220415/bd6a-a2376d5226aaa796dfdca62b1d9b1fcb.png",
|
||||||
|
"http://qr.weibo.cn/inf/gen?api_key=bdccdb65d34320c10b65cdf6c3503a53&data=https%3A%2F%2Ffinance.sina.cn%2Fapp%2FQRtoSFA.shtml%3Fsudatrackvalue%3Dcaitou_inQRcode_PcBottom%26schema%3Dtype%253D100%26docid%3Dnicqsxv4521793%26sinaglobal%3D60.187.79.169_1781612229.439570&datetime=nocheck&level=H&output_type=img&redirect=0&size=100&type=url&sign=a214191d9521864262f4f88bfbc54196"
|
||||||
|
],
|
||||||
|
"word_count": 791
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"source_id": "eastmoney",
|
||||||
|
"url": "https://finance.eastmoney.com/a/202606163772205898.html",
|
||||||
|
"url_hash": "9c8ea91c019f7cc1",
|
||||||
|
"title": "美伊局势追踪:已签“电子协议” 特朗普称19日后公布具体内容!",
|
||||||
|
"content": "专业金融数据,下一代智能终端\n美伊局势昨日“拨云见日”后,就有分析师警告称“生变只在一瞬间”。但好在今天各方传来的消息依旧积极:美国和伊朗据称已签署了“\n电子\n版协议”,正式签署伊始仍将于19日(在日内瓦)举行,而具体内容也将在那之后公布。\n综合\n央视新闻等媒体报道,\n当地时间周一(15日)美国副总统万斯表示,美国与伊朗达成的谅解备忘录已经以\n电子\n方式完成签署,协议条款已基本确定生效。\n美国政府一名高级官员当天也表示,美伊已通过\n电子\n方式签署了谅解备忘录,但双方仍将于19日举行正式签署仪式。\n上述官员在一场记者简报会上说,美国总统特朗普和副总统万斯签署了这份谅解备忘录,伊朗伊斯兰议会议长卡利巴夫代表伊朗方面签署了该文件,预计万斯以及特朗普的特使威特科夫和女婿库什纳将出席19日的仪式。万斯随后将带领美方进入下一阶段谈判。\n协议内容何时公开?\n综合\n现有的公开表态来看,应该是本周内。\n上述官员表示,谅解备忘录将在未来24至48小时内(也就是周二或周三)公开发布。\n但当天晚些时候,\n特朗普在法国出席七国集团峰会期间表示,谅解备忘录将在正式签署仪式(19日)之后公布。他补充说,完整协议将在“周五之后某个时候”公布。\n“这是一份非常重要的文件,我希望它能尽快公布。所以,可能很快就会公布,”他说。\n特朗普还表示,他是否会出席周五在日内瓦举行的签署仪式“要看情况”,但万斯肯定会出席。\n万斯则在最新采访中说道:“我们将在本周公布文本,所有人都会看到,除非伊朗履行其义务,否则他们一分钱也拿不到,而我们所说的资金从根本上来说是制裁解除。”\n关于霍尔木兹\n特朗普表示,\n霍尔木兹海峡“已经部分开放”,并将于周五(6月19日)全面开放。\n他还称,霍尔木兹海峡恢复通航“不需要太多(外部)帮助”。此前,法国总统马克龙表示,欧洲多国已做好准备参与霍尔木兹海峡护航行动。\n当天早些时候,特朗普还在社交媒体平台“真实社交”发文称,已有船只开始通过霍尔木兹海峡运输原油并驶离相关水域。他进一步指出,多艘装载石油的船只正在通过霍尔木兹海峡,并沿所谓“南部航道”航行。他称该航道“完全安全、可靠且清洁”。\n上述高级官员则称,该海峡的交通将很快恢复正常,“肯定会在30天内恢复,\n但前提是伊朗承诺清除所有水雷”。美国“目前掌握所有水雷的位置”,实际上也能协助进行处置工作。\n但关于“收费”问题,双方表态又出现了矛盾信号。\n万斯预计,美伊谅解备忘录将使霍尔木兹海峡“长期开放且不收取通行费”。\n然而,据伊朗法尔斯通讯社援引知情人士报道,双方达成的临时协议规定,伊朗仅在60天内允许船只免费通过海峡。期满后,伊朗计划对商业船舶收取安全保障、航行服务、环境保护及保险等相关费用。\n关于后续行动和谈判\n特朗普15日强调,伊朗绝不会拥有核武器。\n他表示,希望能和伊朗建立良好的关系,但在伊朗履行其应尽义务之前,不会解除对其制裁。\n万斯则称,双方有许多“至关重要”的细节需要在后续为期60天的谈判中敲定,若伊朗不承诺放弃发展核武器,美国将继续施加压力。\n上述高级官员也透露,谅解备忘录的概要包括“核实伊朗是否正在研制核武器,以及是否在资助该地区的极端主义和恐怖主义活动”。作为交换,这位官员表示,美国将开放伊朗经济。\n伊朗官员长期以来一直公开坚称,该国的核计划仅用于民用目的,尽管西方官员表示,伊朗的铀浓缩水平已经超过了民用所需。伊朗还表示,它没有制造核武器的野心,但美国官员对此说法提出异议。\n该官员当天还表示,在美伊技术性谈判期间,美国将维持在中东现有的兵力部署;若最终达成协议,美国则将减少在中东地区的兵力。\n关于伊朗方面此前尤为关注的\n黎以冲突\n,该官员回应称,美伊协议中并未要求以色列必须从黎巴嫩撤军。而且他认为,在黎巴嫩发生的持续冲突不会阻碍特朗普政府与伊朗的协议。\n“撤军并非该协议的先决条件,如果伊朗无法控制黎巴嫩真主党,且后者对以色列据点或城镇发动袭击,以色列将有权进行自卫并予以回击。”他说。\n东财图解·加点干货\n全新妙想投研助理,立即体验\n(文章来源:财联社)",
|
||||||
|
"author": "财联社",
|
||||||
|
"source_name": "东方财富",
|
||||||
|
"publish_time": "2026-06-16T08:37:00",
|
||||||
|
"publish_time_raw": "2026年06月16日 08:37",
|
||||||
|
"extracted_at": "2026-06-16T21:09:09.605864",
|
||||||
|
"images": [
|
||||||
|
"https://np-newspic.dfcfw.com/download/D24832930842412850501_w690h389.jpg",
|
||||||
|
"https://np-newspic.dfcfw.com/download/D25434990913863445554_w1005h2084_o.jpg"
|
||||||
|
],
|
||||||
|
"word_count": 1438
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"source_id": "eastmoney",
|
||||||
|
"url": "https://finance.eastmoney.com/a/202606153771870896.html",
|
||||||
|
"url_hash": "9fa5c1c4e4c24d5f",
|
||||||
|
"title": "霍尔木兹海峡何时恢复通行?预测市场Kalshi:8月前重启概率升至58%",
|
||||||
|
"content": "在东方财富看资讯行情,选东方财富证券一站式开户交易>>\n在美国总统特朗普周日宣布与伊朗达成协议、并将重新开放霍尔木兹海峡后,预测市场平台Kalshi上关于霍尔木兹海峡恢复正常通航的概率迅速上升。\nKalshi数据显示,霍尔木兹海峡在8月之前恢复正常通航的概率已升至58%,重新站上50%以上水平,上一次达到如此高位还是在5月底。\n其他相关预测市场也同步上升,预计今年年底前\n航运\n恢复正常的概率为75%。\n过去三个半月里,霍尔木兹海峡在事实上处于基本瘫痪状态。作为全球油气贸易的咽喉,战前每天约2000万桶石油及石油产品经霍尔木兹海峡运输。\n特朗普周日在社交媒体平台上表示,美伊双方已同意签署一份“谅解备忘录”,并批准解除美国海军封锁。\n“全世界的船只,发动引擎吧。让石油流动起来!”特朗普写道。他随后进一步说明,霍尔木兹海峡将在本周五正式签署协议后重新开放,以便开展“排雷作业”。\n伊朗方面也确认将于周五与美国签署谅解备忘录,并强调霍尔木兹海峡将按照“伊朗方面的安排”重新开放。\n不过,协议细节仍存在不确定性。卡塔尔周一表示,希望就美伊之间“尚未解决的问题”获得进一步说明,其中包括“确保霍尔木兹海峡航行自由”。\n值得注意的是,以色列并未被纳入此次协议框架。以色列此前与美国协同行动,于2月28日对伊朗发动打击。\n这种不确定性也解释了为何Kalshi并未给予霍尔木兹海峡恢复正常通航极高的确定性。目前,即便是“今年年底前恢复正常通航”的概率,也仍未达到90%以上。\n伊朗外交部副部长加里巴巴迪表示,“战争结束”安排同样涵盖黎巴嫩局势。但以色列周一宣布,其国防军仍将继续驻留在黎巴嫩、加沙和叙利亚的“安全区”内。\n美国副总统万斯周一表示,协议将确保霍尔木兹海峡长期向国际\n航运\n开放,且不会征收通行费。\n“过去24小时里,我们已经看到更多船舶开始恢复通行,”他说。\n但伊朗方面称,在过去超过96小时内,伊朗伊斯兰革命卫队海军尚未签发任何通行许可。在另行通知之前,霍尔木兹海峡的进出通道对所有船只关闭。\n(文章来源:财联社)",
|
||||||
|
"author": "财联社",
|
||||||
|
"source_name": "东方财富",
|
||||||
|
"publish_time": "2026-06-16T05:13:00",
|
||||||
|
"publish_time_raw": "2026年06月16日 05:13",
|
||||||
|
"extracted_at": "2026-06-16T21:09:09.988048",
|
||||||
|
"images": [
|
||||||
|
"https://np-newspic.dfcfw.com/download/D24832930842412850501_w690h389.jpg"
|
||||||
|
],
|
||||||
|
"word_count": 719
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"source_id": "cls",
|
||||||
|
"url": "https://www.cls.cn/detail/2401326",
|
||||||
|
"url_hash": "9fd008e7e9041cee",
|
||||||
|
"title": "郑栅洁:国家发展改革委将深入贯彻党中央和国务院决策部署,加强“六张网”规划建设",
|
||||||
|
"content": "财联社6月16日讯,据国家发展改革委消息,6月16日上午,国家发展改革委主任郑栅洁主持召开座谈会,邀请奥德集团、哈尔滨九洲集团、上海瀚讯信息技术、河北宝信物流等5家相关领域民营企业负责人沟通交流,围绕当前经济形势和系统推进“六张网”建设、扩大国内有效需求,听取意见建议。\n座谈会上,企业结合生产经营情况,聚焦水网、新型电网、算力网、新一代通信网、城市地下管网、物流网规划建设畅所欲言。大家认为,国家适应当前和未来一段时期经济社会发展需要部署推进“六张网”建设十分必要。“六张网”建设需求大、带动力强、辐射面广,为企业拓展市场、扩大投资、创新发展提供了重大机遇,企业将把握机遇积极参与到“六张网”建设中,在服务国家重大战略中实现更好发展。同时,参会企业围绕完善项目收益机制、优化标准体系、加大资金要素保障、规范市场竞争秩序、拓宽民营企业参与渠道等方面提出意见建议。\n郑栅洁认真倾听每一位企业负责人发言,与大家深入交流互动,逐一回应企业关切。他表示,我国具有突出的制度优势、超大规模市场优势、完整产业体系优势和丰富人才资源优势,经济发展潜力巨大。国家部署推进“六张网”建设,兼顾传统基础设施补短板与新型基础设施布局,既是深入挖掘内需潜力的有力抓手,也是加快建设现代化产业体系的重要支撑。希望企业坚定发展信心、抓住发展机遇,积极参与“六张网”和重点基础设施建设,在建设强大国内市场中发挥更大作用、实现更好发展。企业所提意见建议,将分门别类梳理并在后续工作中认真研究采纳。下一步,国家发展改革委将深入贯彻党中央和国务院决策部署,加强“六张网”规划建设,强化多网协同推进,统筹用好各类政府资金和新型政策性金融工具,加强土地、环评等要素保障,坚持质效并重,加快转化实施,推动形成更多实物工作量。纵深推进全国统一大市场建设,规范地方招商引资行为,推进基础设施竞争性领域向经营主体公平开放,完善民营企业参与重大项目建设长效机制,积极吸引社会资本参与“六张网”建设,有效激发民间投资活力。同时,深化多层次与民营企业常态化沟通交流,加强前瞻性、战略性问题研究,坚持点面结合、远近结合,强化统筹协调,以务实举措促进民营经济发展壮大。\n国家发展改革委副主任沈竹林,办公厅、综合司、民营局主要负责同志和投资司负责同志参会。",
|
||||||
|
"author": "张家东郑栅洁表示",
|
||||||
|
"source_name": "财联社",
|
||||||
|
"publish_time": "2026-06-16T18:07:00",
|
||||||
|
"publish_time_raw": "2026-06-16 18:07",
|
||||||
|
"extracted_at": "2026-06-16T21:09:07.918119",
|
||||||
|
"images": [],
|
||||||
|
"word_count": 840
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"source_id": "cls",
|
||||||
|
"url": "https://www.cls.cn/detail/2401113",
|
||||||
|
"url_hash": "a81812cc857bff06",
|
||||||
|
"title": "存储红利向“非AI”扩散:这两类内存年内涨价翻倍 机构预计下半年“涨声”不断",
|
||||||
|
"content": "《科创板日报》6月16日讯(编辑 宋子乔)\n这波存储红利,正从AI领域向车规和工控领域扩散。\n根据TrendForce集邦咨询最新研究,\n由于存储器大厂的产能规划持续倾向HBM、高层数3D NAND等高附加价值产品,挤压NOR Flash、SLC NAND依赖的成熟制程产能\n,然而因需求稳定,已推动\n2026上半年NOR Flash、SLC NAND累计合约价涨幅分别突破100%\n。由于供应商未有大规模扩产计划,预估下半年两项产品的价格将随供需紧张而继续调升。\nNOR Flash、SLC NAND均属于闪存(Flash Memory),本质都是非易失性存储,设备断电后数据不会丢失。\nNOR Flash能快速运行程序、稳定性强,主要用于车载固件、边缘AI、工控、卫星、路由器,用来存储开机系统程序;SLC NAND的优点是耐用、出错少,广泛应用于工业设备、服务器系统盘、缓存、医疗军工设备。\n该机构表示,2026上半年终端对存储器产品需求逐步恢复,加上AI相关应用快速成长,大幅消耗市场有限的供给,NOR Flash率先出现交期延长与配货现象,\n合约价平均累计涨幅将达100-120%,高容量产品涨势更显著\n;SLC NAND则因部分国际大厂陆续退出小容量及成熟制程产品,供给明显减少,而工控、车用及网通客户又开始建立长期安全库存,导致第二季出现明显备货潮,\n上半年价格平均累计涨幅将达130-150%\n。\n其预期下半年开始,高度依赖长生命周期产品的车用与工控市场将形成长期缺货风险,其中,高容量NOR Flash有车用电子与边缘AI需求支撑,\n预计下半年价格仍有60-65%以上的调涨空间\n;SLC NAND虽然多数产品需求未明显增加,供应来源却持续减少,\n预估2026下半年价格涨势虽将收敛,但仍有70-75%的上涨空间\n,工业、车规级产品涨势可能更强。\n近期多家存储企业的表态和机构调研纪要印证了上述报告。\n东芯股份在日前接受机构调研时表示,自2025年下半年以来,主要受大容量产品结构性供需失衡影响,中小容量SLC NAND、DRAM等产品价格稳步上行。兆易创新称,NOR Flash受益于下游服务器、汽车等领域需求的拉动,叠加价格温和上涨,一季度收入环比增长良好;SLC NAND Flash在海外大厂加速退出2D NAND的背景下,面临显著缺货局面。普冉股份称目前全球存储代工产能呈现结构性紧缺态势,公司NOR Flash及2D NAND产能供给偏紧张。\n信达证券此前表示,由于AI不断挤压大容量存储产能,大厂逐步退出大容量利基型产品市场,竞争格局不断优化。\nTrendForce集邦咨询指出,为避免价格失控影响合作关系或促使客户寻求替代技术,主要供应商未来更可能通过长期供货合约(LTA)与选择性接单策略管理供需,意味未来市场竞争的核心将不再只是价格,而是供货稳定性与客户关系管理能力。",
|
||||||
|
"author": "季晟",
|
||||||
|
"source_name": "财联社",
|
||||||
|
"publish_time": "2026-06-16T15:47:00",
|
||||||
|
"publish_time_raw": "2026-06-16 15:47",
|
||||||
|
"extracted_at": "2026-06-16T21:09:08.095378",
|
||||||
|
"images": [
|
||||||
|
"https://image.cls.cn/images/20260616/EBY18s5044_1080x560.jpg"
|
||||||
|
],
|
||||||
|
"word_count": 828
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"source_id": "eastmoney",
|
||||||
|
"url": "https://finance.eastmoney.com/a/202606163771946392.html",
|
||||||
|
"url_hash": "ac59cc8f9bb74d70",
|
||||||
|
"title": "燧原科技科创板IPO过会 “国产GPU四小龙”共舞资本市场",
|
||||||
|
"content": "燧原科技表示,公司是我国云端AI芯片领域的领军企业之一,致力于成为“通用\n人工智能\n基础设施领军企业”。公司成立8年来,自研迭代了四代架构5款云端AI芯片,构建了覆盖AI芯片、AI加速卡及模组、智算系统及集群和AI计算及编程软件平台的完整产品体系。公司第四代产品L600为训推一体加速模组,目前该产品流片已回片但尚未大规模量产交付。",
|
||||||
|
"author": "上海证券报",
|
||||||
|
"source_name": "东方财富",
|
||||||
|
"publish_time": "2026-06-16T01:44:00",
|
||||||
|
"publish_time_raw": "2026年06月16日 01:44",
|
||||||
|
"extracted_at": "2026-06-16T21:09:08.900265",
|
||||||
|
"images": [],
|
||||||
|
"word_count": 136
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"source_id": "cls",
|
||||||
|
"url": "https://www.cls.cn/detail/2401117",
|
||||||
|
"url_hash": "b3d548345a465cf1",
|
||||||
|
"title": "苏姿丰最近很忙!参建PCB工厂、扫货激光芯片 AMD加入AI军备竞赛",
|
||||||
|
"content": "《科创板日报》6月16日讯\n芯片巨头AMD也加入到新一轮AI军备竞赛中来。\n据报道,AMD已与奥地利PCB制造商奥特斯(AT&S)达成协议。根据协议,奥特斯将在马来西亚投资至多20亿欧元,以扩大居林(Kulim)工厂产能。在当地1号工厂成功投产的基础上,此次扩建包括对2号工厂现有结构进行改造,以及新建一座用于生产集成电路基板和先进印刷电路板(PCB)的制造基地。\n根据商定的关键条款,\n这项至多20亿欧元的投资将完全由AMD及其他客户提供资金支持。\n“这些协议旨在支持长期增长,同时保持财务稳健,”奥特斯方面表示,“与此同时,这些协议也有助于我们进一步增强资产负债表和财务灵活性。”\n奥特斯预计,这些产能投入在2026至2027财年将带来高达数千万欧元的积极贡献。公司预计新财年固定汇率下全球营收同比增长30%至35%,有望触及此前21亿至24亿欧元预测区间上限;EBITDA利润率将进一步提升至25%至29%,较原有预期再度上调。受该消息影响,奥特斯昨夜大涨36%,股价突破200欧元,创下历史新高。\n除了集成电路基板和PCB,AMD近期在光互连等领域同样动作不断。根据TrendForce集邦咨询,\n其正与相关激光供应商洽谈高功率CW激光芯片采购大单,以确保未来产能不受英伟达与其他主要云服务提供商牵制。\n在AMD频繁“烧钱”加码AI硬件背后,\n数据中心业务已成为背负AMD营业额和利润增长的核心支柱。\n根据公司最新财报,一季度总营业额达到103亿美元,毛利率稳步攀升至53%。其数据中心事业部单季度营收高达58亿美元,同比大幅激增57%。\n今年5月,AMD董事会主席兼首席执行官苏姿丰在财报电话会上表示,凭借领先性能、内存带宽与横向扩展能力,客户对赫利俄斯平台(AMD首款机架级全栈AI基础设施平台)需求极为旺盛。赫利俄斯平台开发进展顺利,芯片、软件与系统均按关键节点高效推进。目前已向核心客户送样MI450系列图形处理器,按计划于今年下半年启动赫利俄斯平台量产出货。\n“基于这一扩大的需求可见度,我们愈发有信心。”她强调:“2027年实现数据中心人工智能业务年营收数百亿美元,未来数年超越80%以上的长期增长目标。”",
|
||||||
|
"author": "季晟",
|
||||||
|
"source_name": "财联社",
|
||||||
|
"publish_time": "2026-06-16T15:37:00",
|
||||||
|
"publish_time_raw": "2026-06-16 15:37",
|
||||||
|
"extracted_at": "2026-06-16T21:09:08.446032",
|
||||||
|
"images": [],
|
||||||
|
"word_count": 698
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"source_id": "cls",
|
||||||
|
"url": "https://www.cls.cn/detail/2400664",
|
||||||
|
"url_hash": "b3e2dc8b41dac7d0",
|
||||||
|
"title": "美伊局势追踪:已签“电子协议”,特朗普称19日后公布具体内容!",
|
||||||
|
"content": "财联社6月16日讯(编辑 黄君芝)\n美伊局势昨日“拨云见日”后,就有分析师警告称“生变只在一瞬间”。但好在今天各方传来的消息依旧积极:美国和伊朗据称已签署了“电子版协议”,正式签署伊始仍将于19日(在日内瓦)举行,而具体内容也将在那之后公布。\n综合央视新闻等媒体报道,\n当地时间周一(15日,)美国副总统万斯表示,美国与伊朗达成的谅解备忘录已经以电子方式完成签署,协议条款已基本确定生效。\n美国政府一名高级官员当天也表示,美伊已通过电子方式签署了谅解备忘录,但双方仍将于19日举行正式签署仪式。\n上述官员在一场记者简报会上说,美国总统特朗普和副总统万斯签署了这份谅解备忘录,伊朗伊斯兰议会议长卡利巴夫代表伊朗方面签署了该文件,预计万斯以及特朗普的特使威特科夫和女婿库什纳将出席19日的仪式。万斯随后将带领美方进入下一阶段谈判。\n协议内容何时公开?\n综合现有的公开表态来看,应该是本周内。\n上述官员表示,谅解备忘录将在未来24至48小时内(也就是周二或周三)公开发布。\n但当天晚些时候,\n特朗普在法国出席七国集团峰会期间表示,谅解备忘录将在正式签署仪式(19日)之后公布。他补充说,完整协议将在“周五之后某个时候”公布。\n“这是一份非常重要的文件,我希望它能尽快公布。所以,可能很快就会公布,”他说。\n特朗普还表示,他是否会出席周五在日内瓦举行的签署仪式“要看情况”,但万斯肯定会出席。\n万斯则在最新采访中说道:“我们将在本周公布文本,所有人都会看到,除非伊朗履行其义务,否则他们一分钱也拿不到,而我们所说的资金从根本上来说是制裁解除。”\n关于霍尔木兹\n特朗普表示,\n霍尔木兹海峡“已经部分开放”,并将于周五(6月19日)全面开放。\n他还称,霍尔木兹海峡恢复通航“不需要太多(外部)帮助”。此前,法国总统马克龙表示,欧洲多国已做好准备参与霍尔木兹海峡护航行动。\n当天早些时候,特朗普还在社交媒体平台“真实社交”发文称,已有船只开始通过霍尔木兹海峡运输原油并驶离相关水域。他进一步指出,多艘装载石油的船只正在通过霍尔木兹海峡,并沿所谓“南部航道”航行。他称该航道“完全安全、可靠且清洁”。\n上述高级官员则称,该海峡的交通将很快恢复正常,“肯定会在30天内恢复,\n但前提是伊朗承诺清除所有水雷”。美国“目前掌握所有水雷的位置”,实际上也能协助进行处置工作。\n但关于“收费”问题,双方表态又出现了矛盾信号。\n万斯预计,美伊谅解备忘录将使霍尔木兹海峡“长期开放且不收取通行费”。\n然而,据伊朗法尔斯通讯社援引知情人士报道,双方达成的临时协议规定,伊朗仅在60天内允许船只免费通过海峡。期满后,伊朗计划对商业船舶收取安全保障、航行服务、环境保护及保险等相关费用。\n关于后续行动和谈判\n特朗普15日强调,伊朗绝不会拥有核武器。\n他表示,希望能和伊朗建立良好的关系,但在伊朗履行其应尽义务之前,不会解除对其制裁。\n万斯则称,双方有许多“至关重要”的细节需要在后续为期60天的谈判中敲定,若伊朗不承诺放弃发展核武器,美国将继续施加压力。\n上述高级官员也透露,谅解备忘录的概要包括“核实伊朗是否正在研制核武器,以及是否在资助该地区的极端主义和恐怖主义活动”。作为交换,这位官员表示,美国将开放伊朗经济。\n伊朗官员长期以来一直公开坚称,该国的核计划仅用于民用目的,尽管西方官员表示,伊朗的铀浓缩水平已经超过了民用所需。伊朗还表示,它没有制造核武器的野心,但美国官员对此说法提出异议。\n该官员当天还表示,在美伊技术性谈判期间,美国将维持在中东现有的兵力部署;若最终达成协议,美国则将减少在中东地区的兵力。\n关于伊朗方面此前尤为关注的\n黎以冲突\n,该官员回应称,美伊协议中并未要求以色列必须从黎巴嫩撤军。而且他认为,在黎巴嫩发生的持续冲突不会阻碍特朗普政府与伊朗的协议。\n“撤军并非该协议的先决条件,如果伊朗无法控制黎巴嫩真主党,且后者对以色列据点或城镇发动袭击,以色列将有权进行自卫并予以回击。”他说。",
|
||||||
|
"author": "林琦",
|
||||||
|
"source_name": "财联社",
|
||||||
|
"publish_time": "2026-06-16T08:17:00",
|
||||||
|
"publish_time_raw": "2026-06-16 08:17",
|
||||||
|
"extracted_at": "2026-06-16T21:09:06.931722",
|
||||||
|
"images": [],
|
||||||
|
"word_count": 1409
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"source_id": "yicai",
|
||||||
|
"url": "https://www.yicai.com/news/103232739.html",
|
||||||
|
"url_hash": "b5900c16e48b8a48",
|
||||||
|
"title": "全球最严!英国拟对16岁以下青少年实施社交媒体禁令,九成家长支持",
|
||||||
|
"content": "英国将对青少年社交媒体使用实施“全球最严”监管措施。\n据新华社报道,英国首相斯塔默15日宣布,政府将全面禁止16岁以下未成年人使用一系列社交媒体应用程序,并同步限制游戏及直播平台涉及未成年人的陌生人联系功能。新规预计明年初生效。\n根据公告,禁令将包括照片墙(Instagram)、优兔(YouTube)、色拉布(Snapchat)、脸书(Facebook)和X等平台。WhatsApp和Signal等消息通讯服务平台不在范围内。公告称,上述限制将适用于包括游戏网站在内的更广泛的在线服务,“比任何国家都更为严厉”。\n公告还显示,一些提供特殊服务的AI聊天机器人也仅限18岁以上用户使用,政府还在考虑对18岁以下用户实施“夜间宵禁”以及暂停“无限滚动”等措施,更多细节预计下个月公布。\n美国驻英国大使馆在6月初发布声明称,美国对将特定产品设计限制作为主要监管方式存在担忧,并表示相关监管法规不得对美国科技公司实施歧视,使其待遇低于提供类似服务的外国供应商。斯塔默也表示,他将在七国集团峰会上与美国总统特朗普及其他国家领导人讨论此事,“我真心认为,世界各国领导人一直都认识到领导者必须采取措施保护儿童。”\n英国首相斯塔默在1月初举行的新闻发布会上讲话 (图源:新华社)\n九成家长支持\n斯塔默政府称,这些限制措施是在3月至5月的公众咨询期中收到了来自家长、儿童和科技行业的超过11万份意见后做出的,九成家长支持禁止16岁以下儿童使用社交媒体。\n然而,多家科技公司表示,前述禁令将迫使儿童转向“更不安全的服务”。例如,拥有脸书和照片墙的Meta公司发言人表示:“正如我们在澳大利亚看到的,禁令有可能将青少年与网络社区和信息隔离开来,迫使他们选择缺乏内置保护和家长控制的无监管替代方案。”流媒体平台油管也称:“全面封禁将孩子们从这些精心策划、监督且有益的体验中推向匿名、安全性较低的服务。”\n值得注意的是,华尔街对于英国政府的禁令反应略显平淡。Meta Platforms周一股价上涨4.8%。优兔母公司Alphabet上涨2.7%,色拉布所有者Snap则上涨8.6%。\n瑞穗银行分析师劳埃德·沃姆斯利(Lloyd Walmsley)认为,尽管Meta等科技公司竞相占领年轻用户,但全面禁令的出台,对Meta的竞争对手来说也绝非利好。他认为,股市对于英国提案的冷淡反应,也与当前人工智能交易的蓬勃发展以及科技股克服监管不确定性的历史有关。\n各国监管趋严\n澳大利亚于去年12月10日正式实施全球首个社交媒体禁令,明确禁止16岁以下用户注册脸书、照片墙和X等10个主流平台。此外,加拿大、巴西、印度尼西亚等国已出台相关法规或宣布类似限制,法国、西班牙、丹麦、泰国、韩国等多国正在研究或制定类似措施。\n不过,社交媒体禁令是否有效仍有待观察。根据专注网络安全和自杀预防的非营利组织莫莉·罗斯基金会(Molly Rose Foundation)的数据,截至今年3月,在禁令实施前就拥有社交媒体账号的澳大利亚12至15岁儿童中,超六成人仍能访问他们的账号。\n斯塔默本人也承认一些青少年会试图绕过禁令,但他仍对执行效果充满信心。“我相信我们可以执行它。”斯塔默以禁止未成年人饮酒一事为例,青少年实际饮酒的年纪可能会早于他们被允许饮酒的年纪,但这并不意味着放弃阻禁止成年人饮酒。",
|
||||||
|
"author": "潘寅茹",
|
||||||
|
"source_name": "第一财经",
|
||||||
|
"publish_time": "2026-06-16T00:00:00",
|
||||||
|
"publish_time_raw": "2026-06-16",
|
||||||
|
"extracted_at": "2026-06-16T21:09:28.653723",
|
||||||
|
"images": [
|
||||||
|
"https://imgcdn.yicai.com/uppics/images/2026/06/7176d461d994a47a9dbd84a806721990.jpg"
|
||||||
|
],
|
||||||
|
"word_count": 1083
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"source_id": "yicai",
|
||||||
|
"url": "https://www.yicai.com/news/103232046.html",
|
||||||
|
"url_hash": "bd6d6c1dbf0ef4a3",
|
||||||
|
"title": "国家统计局答一财:人工智能应用对制造业带动明显增强",
|
||||||
|
"content": "今年5月份,规模以上制造业增加值同比增长4.4%,增速比上月加快0.4个百分点,成为经济运行中的一大亮点,工业制造业对国民经济“压舱石”的作用持续显现。\n国家统计局新闻发言人付凌晖16日在国务院新闻办发布会上回答第一财经记者提问时表示,5月份,制造业增速稳中有升,继续呈现动能向新、结构向优的特点,为产业优化升级、推动经济增长做出了重要贡献。\n付凌晖表示,但也要看到,当前外部环境仍然复杂严峻,国际地缘政治冲突对全球能源供应和产供链影响还在显现,国内部分企业面临成本上升、需求不足等阶段性困难。下阶段,要精准助企纾困,推动产业升级,巩固制造业向好发展基础。\n5月份,规模以上制造业增加值同比增长4.4%。其中,装备制造业对制造业增长的贡献非常突出,规模以上装备制造业增长9.5%,比上月加快1.2个百分点,对全部规模以上工业增长的贡献接近八成。\n随着制造业向产业链中高端升级,5月份规模以上高技术制造业增加值同比增长15.1%,比上月加快2.3个百分点,同时,高端制造投资也保持持续增长,为制造业升级发展积蓄了后劲,1—5月份高技术制造业投资同比增长3.4%。\n付凌晖介绍,人工智能应用的快速发展对制造业带动明显增强,5月份集成电路制造、电子专用材料制造等行业增加值分别增长87.0%和29.0%。此外,绿色低碳转型持续深入,带动5月份锂离子电池、生物基化学纤维、碳纤维及复合材料等绿色产品产量分别增长40%、18.1%、13.4%。\n企业效益方面,随着工业生产平稳增长,产品价格回升,制造业企业的营业收入和利润都实现了较快增长。1-4月份,规模以上制造业企业营业收入同比增长5.8%;利润增长20.4%。其中,装备制造业利润增长15.4%,高技术制造业利润增长44.8%。经营效益改善有利于企业预期总体稳定。5月份制造业PMI中的生产指数是51.2%,生产经营活动预期指数为53.9%,均保持在扩张区间。",
|
||||||
|
"author": "秦新安",
|
||||||
|
"source_name": "第一财经",
|
||||||
|
"publish_time": "2026-06-16T00:00:00",
|
||||||
|
"publish_time_raw": "2026-06-16",
|
||||||
|
"extracted_at": "2026-06-16T21:09:25.429106",
|
||||||
|
"images": [
|
||||||
|
"https://imgcdn.yicai.com/uppics/images/2026/06/3221189f0403b6207264b009194358be.jpg"
|
||||||
|
],
|
||||||
|
"word_count": 634
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"source_id": "cls",
|
||||||
|
"url": "https://www.cls.cn/detail/2401394",
|
||||||
|
"url_hash": "c07c2afc143a0859",
|
||||||
|
"title": "5月70城房价出炉:一线城市连续4个月领涨 沪深二手房环比涨0.6%",
|
||||||
|
"content": "财联社6月16日讯(记者 李洁)\n在政策持续落地推动下,5月份全国房价延续复苏态势,上涨城市数量进一步增多,一线城市继续扮演“领头羊”角色。\n6月16日,国家统计局发布的70城房价数据显示,5月全国新建商品住宅价格环比上涨城市达16个,较上月增加2城;二手住宅环比上涨城市为10个。\n“5月新房价格同比跌幅出现近8个月以来首次收窄,市场进入止跌回稳的关键阶段。”易居研究院副院长严跃进表示,一线城市持续领涨行情,是支撑市场预期修复的核心抓手。\n从整体数据来看,5月全国70城新建商品住宅价格指数环比增幅为-0.2%,与上月持平。一线城市新房环比上涨0.2%,涨幅较上月扩大0.1个百分点;二线城市环比下降0.1%,三线城市下降0.3%。\n其中,上海、广州和深圳新房价格环比分别上涨0.2%、0.2%和0.4%。\n严跃进表示,一线城市新房环比已连续4个月保持正增长,同比跌幅持续收窄,未来有望率先实现同比转正。“以一线城市为领头羊的各项指标已经表明市场持续向好,这将对市场信心形成正面影响。”\n58安居客研究院院长张波指出,一线城市近期供求平衡性表现更好,5月一线城市新房在售项目环比仅微增1.3%,房企推盘整体偏谨慎,并未出现大规模放量;同时,新房去化周期维持在13.3个月的合理区间,上海更是低至8.4个月,库存结构健康,去化压力整体偏低。此外,4月底深圳、广州政策发力对成交量的拉动作用明显,进一步稳定了房价预期。\n二手房市场同样释放出积极信号。5月全国70城二手住宅价格环比下降0.3%,但一线城市二手住宅环比上涨0.4%,涨幅与上月持平。其中,北京、上海、广州和深圳环比分别上涨0.1%、0.6%、0.1%和0.6%。\n“目前一线城市二手房成交量处于历史较高水平,市场活跃度提升带动价格企稳回升,一线城市二手房价已连续4个月同比跌幅收窄,由跌转涨的动力持续增强。”一位房地产行业分析师表示。\n从交易端来看,二手房市场的流动性正在改善。张波表示,5月二手房房东调价指数持续回升、热点区域挂牌量持续回落,交易周期连续4个月缩短,“这说明业主心态逐步修复,市场流动性在持续改善”。\n从需求结构看,改善型需求正成为支撑房价稳定的重要力量。数据显示,核心二线城市90-144平方米新房价格环比跌幅控制在0.1%-0.3%之间,呈量稳价微跌态势,成都、杭州、武汉等人口持续流入城市该户型价格稳定性表现更好,核心二线城市此类户型成交占比高达42%,为全国最高。144平方米以上的中高端改善需求在一线城市持续上涨,上海、深圳、广州该户型新房价格环比涨幅更为突出。\n市场温和修复的背后,是政策的持续助力。上述地产分析师介绍,5月全国累计出台47条房地产相关政策,叠加城市更新“十五五”规划落地,宽松类政策占比超八成,公积金提额、租房与装修提取放宽、多地推进住房“以旧换新”成为主流。武汉、苏州、中山等城市月内陆续加码新政,对房价起到较好的拉动作用。\n进入6月,市场热度局部延续。中指院分析师认为,6月上半月,随着年中销售节点临近,房企推盘节奏加快、促销力度加大,北京、上海、广州、深圳等核心城市新房成交量同比均实现增长,市场活跃度边际提升。但回暖动能主要集中于核心区域优质项目,非核心板块及多数低能级城市仍面临去化压力。二手房方面,6月上半月北京、上海、深圳的二手房成交同比继续保持增长,市场热度延续。",
|
||||||
|
"author": "王海春",
|
||||||
|
"source_name": "财联社",
|
||||||
|
"publish_time": "2026-06-16T18:53:00",
|
||||||
|
"publish_time_raw": "2026-06-16 18:53",
|
||||||
|
"extracted_at": "2026-06-16T21:09:08.215931",
|
||||||
|
"images": [],
|
||||||
|
"word_count": 1127
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"source_id": "cls",
|
||||||
|
"url": "https://www.cls.cn/detail/2400952",
|
||||||
|
"url_hash": "c6a0435ed39c0890",
|
||||||
|
"title": "和平协议促使美汽油价格跌破4美元 分析师却警告油价飙升风险仍存",
|
||||||
|
"content": "财联社6月16日讯(编辑 卞纯)\n随着美伊达成将重新开放全球石油运输咽喉要道霍尔木兹海峡的协议,\n美国零售汽油均价自4月中旬以来首次跌破每加仑4美元\n,这可能会缓解美国民众的生活成本压力。\n根据油价信息平台GasBuddy的数据,\n美国全国平均零售汽油价格周日(6月14日)降至每加仑3.997美元,这是自4月中旬以来首次跌破4美元大关\n;不过,这一价格仍较去年同期高出90.8美分。\n另据美国汽车协会(AAA)的数据,周一全美汽油均价为每加仑4.065美元。\n汽油价格回落或将为特朗普政府带来一丝喘息之机。美国总统特朗普曾承诺降低消费者的能源成本,然而美伊战争爆发以来不断上涨的燃油成本令其支持率降至任内最低点。而选民的满意度对于共和党在今年11月的中期选举中保住国会参众两院的多数席位至关重要。\n市场普遍将每加仑4美元的汽油价格视为一道心理关口,一旦突破该水平,部分消费者便会开始改变行为模式,例如减少燃油消耗。\n分析人士表示,只要美伊协议持续有效,\n美国燃油价格应该会继续下跌\n。\nGasBuddy石油分析主管Patrick De Haan表示:“真正的考验现在转向了霍尔木兹海峡,任何重新开放及石油运输恢复正常的消息,都将是这种缓解态势能够持久的最明确信号。就目前而言,只要局势不发生急剧逆转,且美伊双方继续朝着积极的方向推进,全国汽油均价可能会继续下降。”\nDe Haan表示,截至周一,自战争爆发以来,美国人已在汽油上额外支出了约460亿美元。\n上周公布的数据显示,美国5 月消费者价格指数三年来首次突破4%。不过,美国劳工部表示,汽油价格的回落促使本月消费者的通胀预期有所缓和。\n分析师警告油价飙升风险仍存\n然而,这种缓解态势能否持久目前尚不明朗。分析人士警告称,\n市场需要时间才能恢复正常,而且不能保证伊朗和美国达成的协议不会出现变数,油价飙升的风险“并未完全消除”\n。\n“这是一个脆弱的结构,很容易崩溃。可能存在一些无法克服的细节问题。”北欧斯安银行(SEB)首席大宗商品分析师Bjarne Schieldrop在谈及美伊停战备忘录时如此说道。\n特朗普表示,协议的正式签署仪式将于19日在瑞士举行,届时霍尔木兹海峡将全面重新开放,而美伊协议内容将在19日正式签署后对外公布。\n然而专家指出,由于清除水道中的水雷是一项复杂的工程,航运恢复正常可能仍需数周时间。\n海湾石油(Gulf Oil)首席能源顾问Tom Kloza表示,如果在清理海峡、恢复船舶保险等方面没有取得实质性进展,当前的缓解期可能是短暂的。\n“即使霍尔木兹油轮运输很快恢复,夏季或秋季油价飙升的风险也并未完全消除,”Rapidan Energy总裁、前白宫能源顾问Bob McNally表示。“全球石油市场仍需应对历史性的15亿桶供应损失……霍尔木兹原油出口重启后,需要数周甚至数月的时间才能重新平衡全球石油市场。”\n美国汽油市场正面临迫在眉睫的供应紧缩危机。强劲的国内需求和庞大的出口规模威胁着本已偏低的库存,可能导致汽油价格再次攀升。政府数据显示,截至6月第一周,全国汽油库存降至2.151亿桶,为近十年来同期最低水平。\n政府数据还显示,美国战略石油储备上周进一步减少890万桶,降至3.403亿桶,为1983年7月以来的最低水平。",
|
||||||
|
"author": "林琦",
|
||||||
|
"source_name": "财联社",
|
||||||
|
"publish_time": "2026-06-16T12:51:00",
|
||||||
|
"publish_time_raw": "2026-06-16 12:51",
|
||||||
|
"extracted_at": "2026-06-16T21:09:07.347717",
|
||||||
|
"images": [],
|
||||||
|
"word_count": 1072
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user