Metadata-Version: 2.4
Name: spider_db
Version: 1.0.1
Summary: 爬虫数据多数据库操作框架 - 支持Redis/MongoDB/MySQL
Author-email: zhq_551 <1193989932@qq.com>
License: MIT
Project-URL: Homepage, https://github.com/hqzhao551/my_python
Project-URL: Repository, https://github.com/hqzhao551/my_python
Keywords: crawler,database,redis,mongodb,mysql,spider,data-management
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: redis>=4.0.0
Requires-Dist: pymongo>=4.0.0
Requires-Dist: pymysql>=1.0.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"

# DBManager - 爬虫数据多数据库操作框架

[English](README.md) | [中文](README_CN.md)

A powerful multi-database operations framework for web crawlers, supporting Redis, MongoDB, and MySQL with features like crash recovery, status synchronization, and content persistence.

## 目录

1. [特性](#特性)
2. [安装](#安装)
3. [快速开始](#快速开始)
4. [配置](#配置)
5. [核心功能](#核心功能)
   - [URL管理](#url管理)
   - [内容管理](#内容管理)
   - [状态管理](#状态管理)
   - [批量操作](#批量操作)
   - [异步操作](#异步操作)
   - [并发爬取](#并发爬取)
   - [调度器](#调度器)
   - [健康检查](#健康检查)
   - [数据备份与恢复](#数据备份与恢复)
   - [数据库切换](#数据库切换)
   - [配置管理](#配置管理)
6. [API参考](#api参考)
7. [示例代码](#示例代码)
8. [常见问题](#常见问题)

---

## 特性

- **多数据库支持**: 统一API操作Redis、MongoDB、MySQL
- **断电续爬**: 异常断电后自动恢复爬取进度
- **状态同步**: Redis状态实时同步到持久化数据库
- **内容持久化**: 爬取内容自动持久化到MongoDB/MySQL
- **优先级队列**: 支持优先级排序的URL爬取
- **并发支持**: 多进程+多线程+协程混合并发
- **健康监控**: 内置数据库健康检查和监控
- **分布式锁**: 支持高并发下的分布式锁

---

## 安装

```bash
pip install DBManager
```

或从源码安装:

```bash
git clone https://github.com/DBManager/DBManager.git
cd DBManager
pip install -e .
```

---

## 快速开始

```python
from spider_db import CrawlerDB

# 创建实例（自动连接数据库）
db = CrawlerDB()

# 添加URL到爬取队列
db.add_url("https://example.com", priority=1, metadata={"source": "官网"})

# 获取待爬URL（原子操作，自动标记为crawling）
url, url_hash = db.get_todo_url()

# 保存爬取内容
db.save_content(url, {"html": "<html>...</html>", "text": "page text"})

# 更新爬取状态
db.update_crawl_status(url, "success")

# 获取统计信息
stats = db.get_stats()
print(stats)  # {'todo': 10, 'crawling': 5, 'success': 100, 'failed': 2}
```

---

## 配置

### 配置文件结构

```python
from DBManager.config import config

# Redis配置
REDIS_CONFIGS = {
    "data": {
        "host": "localhost",
        "port": 6379,
        "db": 0,
        "password": None,
    },
    "test": {
        "host": "localhost",
        "port": 6379,
        "db": 1,
    }
}

# MongoDB配置
MONGODB_CONFIGS = {
    "user_spider": {
        "host": "localhost",
        "port": 27017,
        "database": "user_spider",
    }
}

# MySQL配置
MYSQL_CONFIGS = {
    "user_spider": {
        "host": "localhost",
        "port": 3306,
        "database": "user_spider",
        "user": "root",
        "password": "password",
    }
}
```

### 动态修改配置

```python
# 修改Redis键名前缀
config.update_redis_prefix("my_spider")

# 修改Redis配置
config.update_redis_config("data", {"host": "localhost", "port": 6379, "db": 0})

# 启用状态同步
config.enable_status_sync(True)

# 启用内容持久化
config.enable_content_persistence(True)

# 修改失败URL处理策略
config.update_failed_url_strategy("delete")  # 或 "keep"

# 配置最大失败次数
config.update_max_fail_count(5)
```

---

## 核心功能

### URL管理

#### 添加URL

```python
# 基本用法
db.add_url("https://example.com")

# 指定优先级（数字越小优先级越高）
db.add_url("https://example.com", priority=1)

# 添加元数据
db.add_url("https://example.com", metadata={"source": "官网", "category": "tech"})

# 添加到指定数据库
db.add_url("https://example.com", target_db_types=["mongodb"])
db.add_url("https://example.com", target_db_types=["redis", "mongodb"])
```

**参数说明:**
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| url | str | 必填 | URL地址 |
| priority | int | 1 | 优先级，数字越小优先级越高 |
| metadata | dict | None | 元数据字典 |
| target_db_types | List[str] | None | 目标数据库类型列表，可选["redis", "mongodb", "mysql"] |

#### 获取待爬URL

```python
# 基本用法
url, url_hash = db.get_todo_url()
# 返回: ('https://example.com', 'abc123...')

# 指定优先级
url, url_hash = db.get_todo_url(priorities=1)  # 只获取优先级1

# 优先级范围
url, url_hash = db.get_todo_url(priorities=(1, 5))  # 优先级1-5之间

# 多个指定优先级
url, url_hash = db.get_todo_url(priorities=[1, 3, 5])  # 优先级1或3或5

# 从指定数据库获取
url, url_hash = db.get_todo_url(target_db_types=["redis"])
```

**参数说明:**
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| priorities | int/list/tuple/None | None | 优先级过滤 |
| target_db_types | List[str] | None | 目标数据库类型列表 |

#### 重新入队

```python
# 基本用法
db.requeue_url("https://example.com")

# 指定新优先级
db.requeue_url("https://example.com", priority=1)

# 指定数据库
db.requeue_url("https://example.com", target_db_types=["mongodb"])
```

**参数说明:**
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| url | str | 必填 | URL地址 |
| priority | int | None | 新优先级，不指定则保持原优先级 |
| target_db_types | List[str] | None | 目标数据库类型列表 |

#### 检查URL是否存在

```python
# 检查当前数据库
exists = db.check_url_exists("https://example.com")
# 返回: True/False

# 检查指定数据库
exists = db.check_url_exists("https://example.com", target_db_types=["mongodb"])
```

**参数说明:**
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| url | str | 必填 | URL地址 |
| target_db_types | List[str] | None | 目标数据库类型列表 |

#### 获取URL状态

```python
# 获取URL状态
status = db.get_url_status("https://example.com")
# 返回: {'status': 'todo', 'fail_count': 0, 'priority': 1, 'created_at': ..., 'updated_at': ...}

# 指定数据库
status = db.get_url_status("https://example.com", target_db_types=["redis"])
```

**参数说明:**
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| url | str | 必填 | URL地址 |
| target_db_types | List[str] | None | 目标数据库类型列表 |

---

### 内容管理

#### 保存内容

```python
# 基本用法
db.save_content("https://example.com", "页面内容")

# 使用dict格式
db.save_content("https://example.com", {"html": "<html>...</html>", "text": "文本内容"})

# 带元数据
db.save_content("https://example.com", "内容", metadata={"title": "示例", "author": "张三"})

# 保存到指定数据库
db.save_content("https://example.com", "内容", target_db_types=["mongodb"])
db.save_content("https://example.com", "内容", target_db_types=["mongodb", "mysql"])
```

**参数说明:**
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| url | str | 必填 | URL地址 |
| content | str/dict | 必填 | 内容，str或dict格式 |
| metadata | dict | None | 元数据字典 |
| target_db_types | List[str] | None | 目标数据库类型列表 |

#### 获取内容

```python
# 基本用法
content = db.get_content_by_url("https://example.com")
# 返回: {'data': '页面内容', 'metadata': {...}, 'created_at': ...}

# 同时检查持久化数据库
content = db.get_content_by_url("https://example.com", check_persistent=True)

# 指定持久化数据库
content = db.get_content_by_url(
    "https://example.com",
    check_persistent=True,
    persistent_db_type="mongodb",
    persistent_db_name="user_spider",
    persistent_table_name="content"
)
```

**参数说明:**
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| url | str | 必填 | URL地址 |
| check_persistent | bool | False | 是否同时检查持久化数据库 |
| persistent_db_type | str | None | 持久化数据库类型 |
| persistent_db_name | str | None | 持久化数据库名称 |
| persistent_table_name | str/dict | None | 持久化表/集合名 |

#### 检查内容是否存在

```python
# 检查内容是否存在
exists = db.check_content_exists("https://example.com")

# 同时检查持久化数据库
exists = db.check_content_exists("https://example.com", check_persistent=True)
```

**参数说明:**
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| url | str | 必填 | URL地址 |
| check_persistent | bool | False | 是否同时检查持久化数据库 |
| 其他持久化参数 | - | - | 同get_content_by_url |

---

### 状态管理

#### 更新爬取状态

```python
# 更新为成功
db.update_crawl_status("https://example.com", "success")

# 更新为失败
db.update_crawl_status("https://example.com", "failed", error_msg="超时错误")

# 更新为爬取中
db.update_crawl_status("https://example.com", "crawling")

# 重新放回队列
db.update_crawl_status("https://example.com", "todo")
```

**参数说明:**
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| url | str | 必填 | URL地址 |
| status | str | 必填 | 状态，可选: todo/crawling/success/failed |
| error_msg | str | None | 错误信息，status为failed时使用 |

**状态流转规则:**
```
todo → crawling → success
                → failed → todo (可配置重试)
```

#### 获取统计信息

```python
# 获取当前数据库统计
stats = db.get_stats()
# 返回: {'todo': 10, 'crawling': 5, 'success': 100, 'failed': 2, 'total': 117}

# 获取指定数据库统计
stats = db.get_stats(["redis"])
stats = db.get_stats(["redis", "mongodb"])

# 指定数据库名称
stats = db.get_stats(["redis", "mongodb"], {"redis": "0", "mongodb": "default"})
```

**参数说明:**
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| db_types | List[str] | None | 数据库类型列表 |
| db_names | Dict[str, str] | None | 数据库名称字典 |

#### 清理失败URL

```python
# 清理失败次数>=3的URL
count = db.clean_failed_url(threshold=3)

# 策略为delete时返回删除数量
# 策略为keep时返回统计数量
```

#### 检查最大失败次数

```python
# 检查所有超限URL
result = db.check_max_fail_count()
# 返回: {'urls': [...], 'count': 5}

# 检查指定URL
result = db.check_max_fail_count("abc123")
# 返回: {'url_hash': 'abc123', 'fail_count': 3, 'max_retry': 3, 'is_exceeded': True}
```

---

### 批量操作

#### 批量添加URL

```python
# 批量添加URL
urls = ["https://example1.com", "https://example2.com", "https://example3.com"]
duplicates = db.batch_add_url(urls, priority=1, metadata={"batch": "第一批次"})
# duplicates: ["https://example1.com"]  # 已存在的重复URL

# 指定目标数据库
duplicates = db.batch_add_url(urls, target_db_types=["mongodb"])
```

**参数说明:**
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| urls | List[str] | 必填 | URL列表 |
| priority | int | 1 | 优先级 |
| metadata | dict | None | 元数据 |
| target_db_types | List[str] | None | 目标数据库 |

#### 批量获取待爬URL

```python
# 批量获取
urls = db.batch_get_todo_url(count=10)
# 返回: [('https://example1.com', 'hash1'), ('https://example2.com', 'hash2'), ...]

# 指定优先级
urls = db.batch_get_todo_url(count=10, priorities=3)
urls = db.batch_get_todo_url(count=10, priorities=(1, 5))  # 范围
urls = db.batch_get_todo_url(count=10, priorities=[1, 3, 5])  # 列表
```

#### 批量保存内容

```python
# 批量保存
pairs = [
    ("https://example1.com", "内容1"),
    ("https://example2.com", {"html": "<html>", "text": "文本"})
]
results = db.batch_save_content(pairs)
# results: [('https://example1.com', True), ('https://example2.com', True)]

# 指定目标数据库
results = db.batch_save_content(pairs, target_db_types=["mongodb"])
```

#### 批量更新状态

```python
# 批量更新状态
pairs = [
    ("https://example1.com", "success", None),
    ("https://example2.com", "failed", "超时错误")
]
results = db.batch_update_status(pairs)
# results: [('https://example1.com', True), ('https://example2.com', True)]
```

---

### 异步操作

所有同步方法都有对应的异步版本，使用`async_`前缀:

```python
import asyncio
from spider_db import CrawlerDB

async def main():
    db = CrawlerDB()
    
    # 异步添加URL
    await db.async_add_url("https://example.com")
    await db.async_add_url("https://example.com", priority=1, metadata={"source": "test"})
    await db.async_add_url("https://example.com", target_db_types=["mongodb"])
    
    # 异步获取待爬URL
    url, url_hash = await db.async_get_todo_url()
    url, url_hash = await db.async_get_todo_url(priorities=1, target_db_types=["redis"])
    
    # 异步保存内容
    await db.async_save_content(url, "内容")
    await db.async_save_content(url, {"html": "<html>", "text": "文本"}, target_db_types=["mongodb"])
    
    # 异步更新状态
    await db.async_update_crawl_status(url, "success")
    await db.async_update_crawl_status(url, "failed", error_msg="错误")
    
    # 异步重新入队
    await db.async_requeue_url(url)
    await db.async_requeue_url(url, priority=1, target_db_types=["mongodb"])
    
    # 异步获取内容
    content = await db.async_get_content_by_url(url)
    content = await db.async_get_content_by_url(url, check_persistent=True)
    
    # 异步检查URL是否存在
    exists = await db.async_check_url_exists(url)
    exists = await db.async_check_url_exists(url, target_db_types=["mongodb"])
    
    # 异步检查内容是否存在
    exists = await db.async_check_content_exists(url)
    
    # 异步获取URL状态
    status = await db.async_get_url_status(url)
    status = await db.async_get_url_status(url, target_db_types=["redis"])
    
    # 异步获取统计
    stats = await db.async_get_stats()
    stats = await db.async_get_stats(["redis", "mongodb"])
    
    # 异步批量操作
    await db.async_batch_add_url(urls)
    urls = await db.async_batch_get_todo_url(count=10)
    await db.async_batch_save_content(pairs)

asyncio.run(main())
```

---

### 并发爬取

#### 同步并发模式

```python
# 定义爬取函数
def crawl_func(url):
    import requests
    response = requests.get(url)
    return url, response.text

# 并发爬取
results = db.concurrent_crawl(
    urls=["https://example1.com", "https://example2.com", "https://example3.com"],
    crawl_func=crawl_func,
    process_count=2,      # 进程数
    thread_count=2,      # 每进程线程数
    coroutine_count=5    # 每线程协程数
)
# 返回: [('https://example1.com', '内容'), ...]
```

#### 异步并发模式

```python
import asyncio

# 定义异步爬取函数
async def async_crawl_func(url):
    import aiohttp
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return url, await response.text()

# 异步并发爬取
results = await db.async_concurrent_crawl(
    urls=["https://example1.com", "https://example2.com"],
    crawl_func=async_crawl_func,
    process_count=2,
    thread_count=2,
    coroutine_count=5
)
```

#### 并发参数配置

```python
# 方式1: 动态配置
db.set_concurrency(
    process_count=3,
    thread_count=3,
    coroutine_count=5,
    mode="hybrid"  # hybrid/thread/process
)

# 方式2: 通过配置
config.CONCURRENCY_CONFIG = {
    "process_count": 3,
    "thread_count": 3,
    "coroutine_count": 5,
    "mode": "hybrid"
}
```

---

### 调度器

#### 状态同步调度器

```python
# 启动定时同步调度器
db.start_sync_scheduler()

# 配置说明:
# CRASH_RECOVERY_CONFIG = {
#     "sync_interval": 60,    # 同步间隔(秒)，0表示实时同步
#     "batch_size": 100,    # 批量同步大小
# }
```

#### 内容持久化调度器

```python
# 启动定时持久化调度器
db.start_persist_scheduler()

# 配置说明:
# CRASH_RECOVERY_CONFIG = {
#     "persist_interval": 120,     # 持久化间隔(秒)，0表示实时持久化
#     "content_batch_size": 50,  # 内容批量持久化大小
#     "delete_after_persistence": True,  # 持久化后是否删除Redis内容
# }
```

#### 超时恢复调度器

```python
# 启动超时恢复调度器
db.start_timeout_recovery_scheduler()

# 配置说明:
# FLOW_CONTROL_CONFIG = {
#     "max_crawling_time": 3600,    # 最大爬取时间(秒)
#     "recovery_interval": 600,     # 检查间隔(秒)
#     "enable_timeout_recovery": True # 是否启用
# }
```

#### 健康检查监控器

```python
# 启动健康检查监控器
db.start_health_monitor()

# 手动执行健康检查
result = db.health_check()
# 返回: {
#     "overall_status": "healthy",
#     "databases": {
#         "redis/data": "healthy",
#         "mongodb/user_spider": "healthy",
#         "mysql/default": "healthy"
#     },
#     "timestamp": 1234567890
# }

# 配置说明:
# HEALTH_CHECK_CONFIG = {
#     "enable_health_check": True,   # 是否启用
#     "check_interval": 60,          # 检查间隔(秒)
#     "timeout": 5,                 # 超时时间(秒)
#     "retry_count": 3,             # 重试次数
#     "alert_on_failure": True      # 失败时是否告警
# }
```

---

### 数据备份与恢复

#### 手动同步

```python
# 手动同步Redis到持久化数据库
result = db.sync_redis_to_persistent("test", batch_size=100)
# 返回: {"synced_count": 50, "failed_count": 0}

# 手动持久化所有内容
result = db.persist_all_redis_content("test", batch_size=50)
```

#### 从持久化恢复

```python
# 从持久化数据库恢复
result = db.recover_from_persistent(
    source_db_type="mongodb",
    source_db_name="user_spider",
    target_db_type="redis",
    target_db_name="test"
)
```

#### 数据备份

```python
# 备份数据
result = db.backup_data(backup_dir="./backups")
# 返回: {"backup_dir": "./backups", "files": [...]}
```

#### 性能指标

```python
# 获取性能指标
metrics = db.get_performance_metrics()
# 返回: {"url_operations": {...}, "content_operations": {...}, "sync_operations": {...}}
```

---

### 数据库切换

```python
# 切换数据库
db.switch_database("redis", "test")
db.switch_database("mongodb", "user_spider")
db.switch_database("mysql", "default")

# 切换并指定表/集合
db.switch_database("mongodb", "user_spider", "urls")

# 同时指定多个表
db.switch_database("mongodb", "user_spider", {"url": "urls", "content": "content"})

# 获取当前数据库信息
current = db.get_current_database()
# 返回: {"db_type": "redis", "db_name": "test", "table_name": None}

# 列出所有数据库
databases = db.list_databases()
# 返回: {"redis": ["0", "1", "test"], "mongodb": ["default", "user_spider"], ...}
```

---

### 配置管理

```python
# 获取并发配置
config = db.get_concurrency_config()
# 返回: {"process_count": 3, "thread_count": 3, "coroutine_count": 5, "mode": "hybrid"}

# 动态修改配置
db.set_concurrency(
    process_count=5,
    thread_count=5,
    coroutine_count=10,
    mode="hybrid"
)

# 使用config模块
from spider_db.config import config

config.enable_status_sync(True)
config.enable_content_persistence(True)
config.update_redis_prefix("my_spider")
config.update_failed_url_strategy("delete")
```

---

## API参考

### URL操作
#### 只要传入target_db_types参数，操作完成后均自动恢复至操作前链接的数据库。

| 方法 | 描述 | 支持target_db_types |
|------|------|-------------------|
| `add_url()` | 添加URL | ✅ |
| `get_todo_url()` | 获取待爬URL | ✅ |
| `requeue_url()` | 重新入队 | ✅ |
| `check_url_exists()` | 检查URL是否存在 | ✅ |
| `get_url_status()` | 获取URL状态 | ✅ |

### 内容操作

| 方法 | 描述 | 支持target_db_types |
|------|------|-------------------|
| `save_content()` | 保存内容 | ✅ |
| `get_content_by_url()` | 获取内容 | ✅ |
| `check_content_exists()` | 检查内容是否存在 | ✅ |

### 批量操作

| 方法 | 描述 | 支持target_db_types |
|------|------|-------------------|
| `batch_add_url()` | 批量添加URL | ✅ |
| `batch_get_todo_url()` | 批量获取URL | ✅ |
| `batch_save_content()` | 批量保存内容 | ✅ |
| `batch_update_status()` | 批量更新状态 | - |

### 状态操作

| 方法 | 描述 |
|------|------|
| `update_crawl_status()` | 更新爬取状态 |
| `get_stats()` | 获取统计信息 |
| `clean_failed_url()` | 清理失败URL |
| `check_max_fail_count()` | 检查失败次数 |

### 调度器

| 方法 | 描述 |
|------|------|
| `start_sync_scheduler()` | 启动状态同步调度器 |
| `start_persist_scheduler()` | 启动内容持久化调度器 |
| `start_timeout_recovery_scheduler()` | 启动超时恢复调度器 |
| `start_health_monitor()` | 启动健康检查监控器 |

### 数据管理

| 方法 | 描述 |
|------|------|
| `sync_redis_to_persistent()` | 手动同步 |
| `persist_all_redis_content()` | 手动持久化 |
| `recover_from_persistent()` | 从持久化恢复 |
| `backup_data()` | 数据备份 |
| `get_performance_metrics()` | 性能指标 |

### 数据库管理

| 方法 | 描述 |
|------|------|
| `switch_database()` | 切换数据库 |
| `get_current_database()` | 获取当前数据库 |
| `list_databases()` | 列出所有数据库 |
| `close()` / `close_all_connections()` | 关闭连接 |

### 并发操作

| 方法 | 描述 |
|------|------|
| `concurrent_crawl()` | 同步并发爬取 |
| `async_concurrent_crawl()` | 异步并发爬取 |
| `batch_process_concurrent()` | 批量并发处理 |

### 异步操作

所有同步方法都有对应的异步版本:
- `async_add_url()`
- `async_get_todo_url()`
- `async_save_content()`
- `async_update_crawl_status()`
- `async_requeue_url()`
- `async_get_content_by_url()`
- `async_check_content_exists()`
- `async_get_url_status()`
- `async_check_url_exists()`
- `async_get_stats()`
- `async_batch_add_url()`
- `async_batch_get_todo_url()`
- `async_batch_save_content()`
- `async_test_all_functions()`

---

## 示例代码

### 完整爬取流程

```python
from spider_db import CrawlerDB
import requests

def crawl(url):
    response = requests.get(url)
    return response.text

db = CrawlerDB()

# 添加URL
db.add_url("https://example.com", priority=1)

# 循环爬取
while True:
    url_data = db.get_todo_url()
    if not url_data:
        break
    
    url, url_hash = url_data
    
    try:
        # 爬取
        content = crawl(url)
        
        # 保存内容
        db.save_content(url, content)
        
        # 更新状态
        db.update_crawl_status(url, "success")
    except Exception as e:
        db.update_crawl_status(url, "failed", error_msg=str(e))

# 获取统计
stats = db.get_stats()
print(f"成功: {stats['success']}, 失败: {stats['failed']}")
```

### 异步爬取流程

```python
import asyncio
from spider_db import CrawlerDB
import aiohttp

async def async_crawl(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.text()

async def main():
    db = CrawlerDB()
    
    # 批量添加URL
    urls = [f"https://example{i}.com" for i in range(100)]
    await db.async_batch_add_url(urls)
    
    # 并发爬取
    results = await db.async_concurrent_crawl(
        urls=urls,
        crawl_func=async_crawl,
        process_count=2,
        thread_count=2,
        coroutine_count=5
    )
    
    # 批量保存
    pairs = [(url, content) for url, content in results]
    await db.async_batch_save_content(pairs)

asyncio.run(main())
```

---

## 常见问题

### Q: 如何选择目标数据库?
A: 使用`target_db_types`参数，可以是`["redis"]`、`["mongodb"]`、`["mysql"]`或`["redis", "mongodb"]`

### Q: 如何启用断电续爬?
A: 设置`CRASH_RECOVERY_CONFIG`中的`enable_crash_recovery`为True

### Q: 状态不更新到MongoDB怎么办?
A: 检查`CRASH_RECOVERY_CONFIG`中的`sync_on_change`和`sync_interval`配置

### Q: 如何处理失败URL?
 A: 使用`config.update_failed_url_strategy("delete")`或`("keep")`配置删除或保留

 ### Q: 如何提高并发?
 A: 使用`db.set_concurrency()`调整`process_count`、`thread_count`、`coroutine_count`参数

 ---

### URL 标准化说明

系统在内部自动处理 URL 标准化，包括：

1. **协议处理**: 自动识别 `http://` 和 `https://`
2. **斜杠标准化**: 反斜杠、多斜杠、混合斜杠自动统一
   - `https:\\example.com` → `https://example.com/`
   - `https:/example.com` → `https://example.com/`
   - `https://example.com//path` → `https://example.com/path`
3. **时间戳过滤**: 自动过滤 URL 中的时间戳参数（用于去重）
   - `?t=1234567890` 会被忽略
   - `?_=1234567890` 会被忽略
   - `?timestamp=xxx` 会被忽略
4. **尾部斜杠**: `example.com` 和 `example.com/` 视为相同

### Content 格式支持

`save_content` 方法支持多种数据格式：

```python
from spider_db import CrawlerDB

db = CrawlerDB()

# 1. 字符串（最常用）
db.save_content("https://example.com", "<html>...</html>")

# 2. 字典（自动序列化为 JSON）
db.save_content("https://example.com", {
    "title": "Example",
    "content": "Page content",
    "links": ["https://link1.com", "https://link2.com"]
})

# 3. 列表（自动序列化为 JSON）
db.save_content("https://example.com", [1, 2, 3, "a", "b"])

# 4. 元组、集合（自动序列化为 JSON）
db.save_content("https://example.com", (1, 2, 3))
db.save_content("https://example.com", {1, 2, 3})

# 5. 字节数据
db.save_content("https://example.com", b"binary data")

# 6. 基础类型
db.save_content("https://example.com", 12345)      # int
db.save_content("https://example.com", 3.14159)    # float
db.save_content("https://example.com", True)       # bool

# 7. None（空内容）
db.save_content("https://example.com", None)
```

### 查询方法示例

```python
from spider_db import CrawlerDB

db = CrawlerDB()

# 添加测试数据
db.add_url("https://example.com", priority=1, metadata={"source": "test"})

# 1. 获取 URL 状态（支持 URL 或 url_hash）
status = db.get_url_status("https://example.com")
print(status)
# 输出: {'status': {'status': 'todo', 'priority': '1'}, 'metadata': {...}}

# 也可以使用 url_hash 查询
# status = db.get_url_status("abc123...")

# 2. 获取内容（不存在返回 None）
content = db.get_content_by_url("https://example.com")
if content:
    print(content["data"])
else:
    print("内容不存在")

# 3. 检查 URL 是否存在
exists = db.check_url_exists("https://example.com")
print(f"URL是否存在: {exists}")  # True

# 4. 检查内容是否存在
has_content = db.check_content_exists("https://example.com")
print(f"内容是否存在: {has_content}")  # False（刚添加还未爬取）

# 5. 获取统计信息
stats = db.get_stats()
print(stats)
# 输出: {'todo': 10, 'crawling': 2, 'success': 100, 'failed': 5}
```

### 批量操作示例

```python
from spider_db import CrawlerDB

db = CrawlerDB()

# 1. 批量添加 URL
urls = [
    "https://example1.com",
    "https://example2.com", 
    "https://example3.com"
]
duplicates = db.batch_add_url(urls, priority=1)
print(f"重复URL: {duplicates}")  # 已存在的URL列表

# 2. 批量获取待爬 URL
todo_urls = db.batch_get_todo_url(count=10)
print(f"获取到 {len(todo_urls)} 个待爬URL")

# 3. 批量保存内容
url_content_pairs = [
    ("https://example1.com", "<html>Content 1</html>"),
    ("https://example2.com", {"data": "Content 2"}),
    ("https://example3.com", ["item1", "item2"])
]
results = db.batch_save_content(url_content_pairs)
print(f"保存结果: {results}")

# 4. 批量更新状态
status_pairs = [
    ("https://example1.com", "success"),
    ("https://example2.com", "failed", "Network error"),
    ("https://example3.com", "success")
]
results = db.batch_update_status(status_pairs)
print(f"更新结果: {results}")
```

### 数据库切换示例

```python
from spider_db import CrawlerDB

db = CrawlerDB()  # 默认使用配置中的第一个数据库

# 1. 切换到指定数据库操作
db.add_url("https://example.com", target_db_types=["redis"])

# 2. 查询指定数据库
status = db.get_url_status("https://example.com", target_db_types=["mongodb"])

# 3. 保存到多个数据库
db.save_content("https://example.com", "content", target_db_types=["redis", "mongodb"])

# 4. 切换数据库（手动）
db.switch_database("mongodb", "my_database")
```

### 异常处理示例

```python
from spider_db import CrawlerDB

db = CrawlerDB()

# 推荐使用 try-except 包裹操作
try:
    result = db.add_url("https://example.com")
    if result:
        print("添加成功")
    else:
        print("URL已存在")
except Exception as e:
    print(f"操作失败: {e}")

# 空列表/空值不会报错，会返回空结果
urls = []
duplicates = db.batch_add_url(urls)  # 返回 []，不会报错

count = 0
todo_urls = db.batch_get_todo_url(count)  # count=0 返回 []，不会报错
```

---

## 依赖

- Python >= 3.8
- redis >= 4.0.0
- pymongo >= 4.0.0
- pymysql >= 1.0.0

## 许可证

MIT License

## 贡献

欢迎提交Pull Request!
