BaiFu
Committed by GitHub

Merge pull request #601 from DoiiarX/mindspider/fix-2026-03-07

MindSpider: 修复多项运行时错误,提升初始化健壮性
... ... @@ -59,6 +59,8 @@ class DeepSentimentCrawling:
if not summary['has_data']:
print("⚠️ 没有找到话题数据,无法进行爬取")
print("💡 请先运行以下命令获取今日话题数据:")
print(" uv run main.py --broad-topic")
return {"success": False, "error": "没有话题数据"}
# 2. 获取关键词(不分配,所有平台使用相同关键词)
... ...
... ... @@ -33,10 +33,14 @@ class PlatformCrawler:
self.supported_platforms = ['xhs', 'dy', 'ks', 'bili', 'wb', 'tieba', 'zhihu']
self.crawl_stats = {}
# 确保MediaCrawler目录存在
if not self.mediacrawler_path.exists():
raise FileNotFoundError(f"MediaCrawler目录不存在: {self.mediacrawler_path}")
# 确保MediaCrawler子模块已初始化
db_config_path = self.mediacrawler_path / "config" / "db_config.py"
if not self.mediacrawler_path.exists() or not db_config_path.exists():
logger.error("MediaCrawler子模块未初始化或不完整")
logger.error("请在项目根目录运行以下命令初始化子模块:")
logger.error(" git submodule update --init --recursive")
raise FileNotFoundError("MediaCrawler子模块未初始化,请先运行: git submodule update --init --recursive")
logger.info(f"初始化平台爬虫管理器,MediaCrawler路径: {self.mediacrawler_path}")
def configure_mediacrawler_db(self):
... ... @@ -122,19 +126,19 @@ mongodb_config = {{
"db_name": MONGODB_DB_NAME,
}}
# postgresql config - 使用MindSpider的数据库配置(如果DB_DIALECT是postgresql)或环境变量
POSTGRESQL_DB_PWD = os.getenv("POSTGRESQL_DB_PWD", "{pg_password}")
POSTGRESQL_DB_USER = os.getenv("POSTGRESQL_DB_USER", "{pg_user}")
POSTGRESQL_DB_HOST = os.getenv("POSTGRESQL_DB_HOST", "{pg_host}")
POSTGRESQL_DB_PORT = os.getenv("POSTGRESQL_DB_PORT", "{pg_port}")
POSTGRESQL_DB_NAME = os.getenv("POSTGRESQL_DB_NAME", "{pg_db_name}")
# postgres config - 使用MindSpider的数据库配置(如果DB_DIALECT是postgresql)或环境变量
POSTGRES_DB_PWD = os.getenv("POSTGRES_DB_PWD", "{pg_password}")
POSTGRES_DB_USER = os.getenv("POSTGRES_DB_USER", "{pg_user}")
POSTGRES_DB_HOST = os.getenv("POSTGRES_DB_HOST", "{pg_host}")
POSTGRES_DB_PORT = os.getenv("POSTGRES_DB_PORT", "{pg_port}")
POSTGRES_DB_NAME = os.getenv("POSTGRES_DB_NAME", "{pg_db_name}")
postgresql_db_config = {{
"user": POSTGRESQL_DB_USER,
"password": POSTGRESQL_DB_PWD,
"host": POSTGRESQL_DB_HOST,
"port": POSTGRESQL_DB_PORT,
"db_name": POSTGRESQL_DB_NAME,
postgres_db_config = {{
"user": POSTGRES_DB_USER,
"password": POSTGRES_DB_PWD,
"host": POSTGRES_DB_HOST,
"port": POSTGRES_DB_PORT,
"db_name": POSTGRES_DB_NAME,
}}
'''
... ... @@ -169,8 +173,8 @@ postgresql_db_config = {{
# 判断数据库类型,确定 SAVE_DATA_OPTION
db_dialect = (config.settings.DB_DIALECT or "mysql").lower()
is_postgresql = db_dialect in ("postgresql", "postgres")
save_data_option = "postgresql" if is_postgresql else "db"
save_data_option = "postgres" if is_postgresql else "db"
base_config_path = self.mediacrawler_path / "config" / "base_config.py"
# 将关键词列表转换为逗号分隔的字符串
... ... @@ -181,26 +185,42 @@ postgresql_db_config = {{
content = f.read()
# 修改关键配置项
# skip_until_paren: 当原始行是多行赋值(以"("结尾)被替换为单行后,
# 需要跳过后续续行直到遇到配对的")"
lines = content.split('\n')
new_lines = []
skip_until_paren = False
for line in lines:
# 跳过多行赋值的续行
if skip_until_paren:
if line.strip() == ')':
skip_until_paren = False
continue
replaced = None
if line.startswith('PLATFORM = '):
new_lines.append(f'PLATFORM = "{platform}" # 平台,xhs | dy | ks | bili | wb | tieba | zhihu')
replaced = f'PLATFORM = "{platform}" # 平台,xhs | dy | ks | bili | wb | tieba | zhihu'
elif line.startswith('KEYWORDS = '):
new_lines.append(f'KEYWORDS = "{keywords_str}" # 关键词搜索配置,以英文逗号分隔')
replaced = f'KEYWORDS = "{keywords_str}" # 关键词搜索配置,以英文逗号分隔'
elif line.startswith('CRAWLER_TYPE = '):
new_lines.append(f'CRAWLER_TYPE = "{crawler_type}" # 爬取类型,search(关键词搜索) | detail(帖子详情)| creator(创作者主页数据)')
replaced = f'CRAWLER_TYPE = "{crawler_type}" # 爬取类型,search(关键词搜索) | detail(帖子详情)| creator(创作者主页数据)'
elif line.startswith('SAVE_DATA_OPTION = '):
new_lines.append(f'SAVE_DATA_OPTION = "{save_data_option}" # csv or db or json or sqlite or postgresql')
replaced = f'SAVE_DATA_OPTION = "{save_data_option}" # csv or db or json or sqlite or postgres'
elif line.startswith('CRAWLER_MAX_NOTES_COUNT = '):
new_lines.append(f'CRAWLER_MAX_NOTES_COUNT = {max_notes}')
replaced = f'CRAWLER_MAX_NOTES_COUNT = {max_notes}'
elif line.startswith('ENABLE_GET_COMMENTS = '):
new_lines.append('ENABLE_GET_COMMENTS = True')
replaced = 'ENABLE_GET_COMMENTS = True'
elif line.startswith('CRAWLER_MAX_COMMENTS_COUNT_SINGLENOTES = '):
new_lines.append('CRAWLER_MAX_COMMENTS_COUNT_SINGLENOTES = 20')
replaced = 'CRAWLER_MAX_COMMENTS_COUNT_SINGLENOTES = 20'
elif line.startswith('HEADLESS = '):
new_lines.append('HEADLESS = True') # 使用无头模式
replaced = 'HEADLESS = True'
if replaced is not None:
new_lines.append(replaced)
# 若原始行是多行赋值开头(以"("结尾),跳过后续续行
if line.rstrip().endswith('('):
skip_until_paren = True
else:
new_lines.append(line)
... ... @@ -253,15 +273,16 @@ postgresql_db_config = {{
# 判断数据库类型,确定 save_data_option
db_dialect = (config.settings.DB_DIALECT or "mysql").lower()
is_postgresql = db_dialect in ("postgresql", "postgres")
save_data_option = "postgresql" if is_postgresql else "db"
save_data_option = "postgres" if is_postgresql else "db"
# 构建命令
cmd = [
sys.executable, "main.py",
"--platform", platform,
"--lt", login_type,
"--type", "search",
"--save_data_option", save_data_option
"--save_data_option", save_data_option,
"--headless", "false"
]
logger.info(f"执行命令: {' '.join(cmd)}")
... ...
... ... @@ -25,7 +25,7 @@ MindSpider 运行示例
- **编程语言**: Python 3.9+
- **AI框架**: 默认Deepseek,可以接入多种api (话题提取与分析)
- **爬虫框架**: Playwright (浏览器自动化)
- **数据库**: MySQL (数据持久化存储)
- **数据库**: MySQL / PostgreSQL (数据持久化存储)
- **并发处理**: AsyncIO (异步并发爬取)
## 项目结构
... ... @@ -223,7 +223,7 @@ cd BettaFish/MindSpider
git submodule update --init --recursive
```
> **注意**:MediaCrawler 的 Python 依赖会在首次运行 `python main.py` 时由系统自动检测并静默安装到当前环境。
> **注意**:MediaCrawler 的 Python 依赖会在首次运行 `uv run main.py --deep-sentiment` 时由系统自动检测并安装到当前环境。
### 2. 创建并激活环境
... ... @@ -275,13 +275,18 @@ playwright install
复制.env.example文件为.env文件,放置在项目根目录。编辑 `.env` 文件,设置数据库和API配置:
```python
# MySQL数据库配置
# 数据库配置(MySQL 示例)
DB_DIALECT = "mysql" # mysql 或 postgresql
DB_HOST = "your_database_host"
DB_PORT = 3306
DB_USER = "your_username"
DB_PASSWORD = "your_password"
DB_NAME = "mindspider"
DB_CHARSET = "utf8mb4"
DB_CHARSET = "utf8mb4" # PostgreSQL 时可省略此项
# PostgreSQL 示例(将上方 DB_DIALECT 改为 postgresql,DB_PORT 改为 5432)
# DB_DIALECT = "postgresql"
# DB_PORT = 5432
# MINDSPIDER API密钥
MINDSPIDER_BASE_URL=your_api_base_url
... ... @@ -294,6 +299,8 @@ MINDSPIDER_MODEL_NAME=deepseek-chat
```bash
# 检查系统状态
python main.py --status
# 或
uv run main.py --status
```
## 使用指南
... ... @@ -303,12 +310,18 @@ python main.py --status
```bash
# 1. 运行话题提取(获取热点新闻和关键词)
python main.py --broad-topic
# 或
uv run main.py --broad-topic
# 2. 运行爬虫(基于关键词爬取各平台内容)
python main.py --deep-sentiment --test
# 或
uv run main.py --deep-sentiment --test
# 或者一次性运行完整流程
python main.py --complete --test
# 或
uv run main.py --complete --test
```
### 单独使用模块
... ... @@ -316,12 +329,18 @@ python main.py --complete --test
```bash
# 只获取今日热点和关键词
python main.py --broad-topic
# 或
uv run main.py --broad-topic
# 只爬取特定平台
python main.py --deep-sentiment --platforms xhs dy --test
# 或
uv run main.py --deep-sentiment --platforms xhs dy --test
# 指定日期
python main.py --broad-topic --date 2024-01-15
# 或
uv run main.py --broad-topic --date 2024-01-15
```
## 爬虫配置(重要)
... ... @@ -334,6 +353,8 @@ python main.py --broad-topic --date 2024-01-15
```bash
# 测试小红书爬取(会弹出二维码)
python main.py --deep-sentiment --platforms xhs --test
# 或
uv run main.py --deep-sentiment --platforms xhs --test
# 用小红书APP扫码登录,登录成功后会自动保存状态
```
... ... @@ -341,25 +362,27 @@ python main.py --deep-sentiment --platforms xhs --test
```bash
# 测试抖音爬取
python main.py --deep-sentiment --platforms dy --test
# 或
uv run main.py --deep-sentiment --platforms dy --test
# 用抖音APP扫码登录
```
3. **其他平台同理**
```bash
# 快手
python main.py --deep-sentiment --platforms ks --test
uv run main.py --deep-sentiment --platforms ks --test
# B站
python main.py --deep-sentiment --platforms bili --test
uv run main.py --deep-sentiment --platforms bili --test
# 微博
python main.py --deep-sentiment --platforms wb --test
uv run main.py --deep-sentiment --platforms wb --test
# 贴吧
python main.py --deep-sentiment --platforms tieba --test
uv run main.py --deep-sentiment --platforms tieba --test
# 知乎
python main.py --deep-sentiment --platforms zhihu --test
uv run main.py --deep-sentiment --platforms zhihu --test
```
### 登录问题排除
... ... @@ -385,9 +408,13 @@ https://github.com/666ghj/BettaFish/issues/185
```bash
# 小规模测试(推荐先这样测试)
python main.py --complete --test
# 或
uv run main.py --complete --test
# 调整爬取数量
python main.py --complete --max-keywords 20 --max-notes 30
# 或
uv run main.py --complete --max-keywords 20 --max-notes 30
```
### 高级功能
... ... @@ -396,18 +423,26 @@ python main.py --complete --max-keywords 20 --max-notes 30
```bash
# 提取指定日期的话题
python main.py --broad-topic --date 2024-01-15
# 或
uv run main.py --broad-topic --date 2024-01-15
# 爬取指定日期的内容
python main.py --deep-sentiment --date 2024-01-15
# 或
uv run main.py --deep-sentiment --date 2024-01-15
```
#### 2. 指定平台爬取
```bash
# 只爬取B站和抖音
python main.py --deep-sentiment --platforms bili dy --test
# 或
uv run main.py --deep-sentiment --platforms bili dy --test
# 爬取所有平台的特定数量内容
python main.py --deep-sentiment --max-keywords 30 --max-notes 20
# 或
uv run main.py --deep-sentiment --max-keywords 30 --max-notes 20
```
## 常用参数
... ... @@ -443,12 +478,16 @@ HEADLESS = False
# 重新运行登录
python main.py --deep-sentiment --platforms xhs --test
# 或
uv run main.py --deep-sentiment --platforms xhs --test
```
### 2. 数据库连接失败
```bash
# 检查配置
python main.py --status
# 或
uv run main.py --status
# 检查config.py中的数据库配置是否正确
```
... ...
... ... @@ -8,6 +8,8 @@ MindSpider - AI爬虫项目主程序
import os
import sys
import argparse
import difflib
import re
from datetime import date, datetime
from pathlib import Path
import subprocess
... ... @@ -73,7 +75,7 @@ class MindSpider:
def build_async_url() -> str:
dialect = (settings.DB_DIALECT or "mysql").lower()
if dialect == "postgresql":
if dialect in ("postgresql", "postgres"):
return f"postgresql+asyncpg://{settings.DB_USER}:{quote_plus(settings.DB_PASSWORD)}@{settings.DB_HOST}:{settings.DB_PORT}/{settings.DB_NAME}"
# 默认使用 mysql 异步驱动 asyncmy
return (
... ... @@ -104,7 +106,7 @@ class MindSpider:
def build_async_url() -> str:
dialect = (settings.DB_DIALECT or "mysql").lower()
if dialect == "postgresql":
if dialect in ("postgresql", "postgres"):
return f"postgresql+asyncpg://{settings.DB_USER}:{quote_plus(settings.DB_PASSWORD)}@{settings.DB_HOST}:{settings.DB_PORT}/{settings.DB_NAME}"
return (
f"mysql+asyncmy://{settings.DB_USER}:{quote_plus(settings.DB_PASSWORD)}"
... ... @@ -230,23 +232,27 @@ class MindSpider:
return True
logger.info("正在安装MediaCrawler依赖...")
install_commands = [
[sys.executable, "-m", "pip", "install", "-r", str(mediacrawler_req), "-q"],
["uv", "pip", "install", "-r", str(mediacrawler_req), "-q"],
]
try:
result = subprocess.run(
[sys.executable, "-m", "pip", "install", "-r", str(mediacrawler_req), "-q"],
capture_output=True,
text=True,
timeout=300 # 5分钟超时
)
if result.returncode == 0:
# 创建标记文件
marker_file.touch()
logger.info("MediaCrawler依赖安装成功")
return True
else:
logger.error(f"MediaCrawler依赖安装失败: {result.stderr}")
return False
for cmd in install_commands:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=300 # 5分钟超时
)
if result.returncode == 0:
marker_file.touch()
logger.info(f"MediaCrawler依赖安装成功 (via {cmd[0]})")
return True
logger.debug(f"{cmd[0]} 安装失败,尝试下一种方式: {result.stderr.strip()}")
logger.error("MediaCrawler依赖安装失败:所有安装方式均不可用")
return False
except subprocess.TimeoutExpired:
logger.error("MediaCrawler依赖安装超时")
return False
... ... @@ -298,10 +304,13 @@ class MindSpider:
test_mode: bool = False) -> bool:
"""运行DeepSentimentCrawling模块"""
logger.info("运行DeepSentimentCrawling模块...")
# 自动检查并初始化数据库表
if not self._ensure_database_ready():
return False
# 自动安装MediaCrawler依赖
self._install_mediacrawler_dependencies()
if not target_date:
target_date = date.today()
... ... @@ -430,9 +439,42 @@ class MindSpider:
logger.info("MindSpider项目初始化完成!")
return True
PLATFORM_CHOICES = ['xhs', 'dy', 'ks', 'bili', 'wb', 'tieba', 'zhihu']
PLATFORM_ALIASES = {
'weibo': 'wb', 'webo': 'wb', '微博': 'wb',
'douyin': 'dy', '抖音': 'dy',
'kuaishou': 'ks', '快手': 'ks',
'bilibili': 'bili', 'b站': 'bili', 'bstation': 'bili',
'xiaohongshu': 'xhs', '小红书': 'xhs', 'redbook': 'xhs',
'zhihu': 'zhihu', '知乎': 'zhihu',
'tieba': 'tieba', '贴吧': 'tieba',
}
class SuggestiveArgumentParser(argparse.ArgumentParser):
"""在参数错误时给出相似候选项提示"""
def error(self, message: str):
match = re.search(r"invalid choice: '([^']+)'", message)
if match:
bad = match.group(1)
alias = PLATFORM_ALIASES.get(bad.lower())
suggestions = difflib.get_close_matches(bad, PLATFORM_CHOICES, n=3, cutoff=0.3)
if alias:
print(f"错误: '{bad}' 不是合法的平台代码。您是否想输入 '{alias}'?", file=sys.stderr)
elif suggestions:
print(f"错误: '{bad}' 不是合法的平台代码。最接近的选项: {suggestions}", file=sys.stderr)
else:
print(f"错误: '{bad}' 不是合法的平台代码。合法平台: {PLATFORM_CHOICES}", file=sys.stderr)
print(f"完整错误: {message}", file=sys.stderr)
else:
print(f"错误: {message}", file=sys.stderr)
self.print_usage(sys.stderr)
sys.exit(2)
def main():
"""命令行入口"""
parser = argparse.ArgumentParser(description="MindSpider - AI爬虫项目主程序")
parser = SuggestiveArgumentParser(description="MindSpider - AI爬虫项目主程序")
# 基本操作
parser.add_argument("--setup", action="store_true", help="初始化项目设置")
... ... @@ -446,8 +488,8 @@ def main():
# 参数配置
parser.add_argument("--date", type=str, help="目标日期 (YYYY-MM-DD),默认为今天")
parser.add_argument("--platforms", type=str, nargs='+',
choices=['xhs', 'dy', 'ks', 'bili', 'wb', 'tieba', 'zhihu'],
parser.add_argument("--platforms", type=str, nargs='+',
choices=PLATFORM_CHOICES,
help="指定爬取平台")
parser.add_argument("--keywords-count", type=int, default=100, help="话题提取的关键词数量")
parser.add_argument("--max-keywords", type=int, default=50, help="每个平台最大关键词数量")
... ...