Metadata-Version: 2.5
Name: my-websearch-kit
Version: 0.0.4
Summary: A unified search toolkit for web and social platforms.
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: python-dotenv>=1
Description-Content-Type: text/markdown

# my-websearch-kit

`my-websearch-kit` 为不同互联网平台提供统一的搜索入口。发行包名为 `my-websearch-kit`，Python 导入名为 `my_websearch_kit`。

## 当前能力

Python 库提供异步 `web_search` 函数，支持：

- 默认及其他 `search_service`：通过 [Search1API Search](https://s1.dev/docs/basic/search) 搜索。
- `wechat-article`：通过 [TikHub 微信综合搜索](https://docs.tikhub.io/472974860e0) 搜索公众号文章（`business_type=article`）。
- `wechat-channel`：通过同一接口搜索视频号视频（`business_type=video`）。
- `douyin`：通过 [TikHub 抖音视频搜索 V4](https://docs.tikhub.io/501036596e0) 搜索抖音视频。
- `xiaohongshu`：通过 [TikHub 小红书笔记搜索](https://docs.tikhub.io/420136398e0) 搜索图文和视频笔记。
- `weibo`：通过 [TikHub 微博实时搜索](https://docs.tikhub.io/381269427e0) 搜索按时间排序的微博。

所有服务共用 `query` 和 1–50 的 `max_results`；`douyin` 和 `weibo` 忽略有效的 `time_range`。
结果为
`SearchResult` 列表，包含标题、可选的 `url`、摘要、可选发布日期、来源标识
`source_id` 和来源名称 `source_name`。后两者均可为 `None`：`source_id` 是内容标识，
`source_name` 是用于展示的网站或账号名称。TikHub 结果有账号名时使用
“平台名 · 账号名”（如“小红书 · Tibo”）；缺失时为 `None`。Search1API 结果
使用 `link` 的域名并去掉开头的 `www.`，无法提取有效域名时为 `None`。
TikHub 文章的 `url` 来自 `doc_url`、来源标识来自 `docID`；视频号内容没有
可直接访问的链接，`url` 和来源标识均使用 `exportId`。视频号结果不直接提供媒体地址。
抖音视频搜索 V4 的返回结果不提供作品页面地址：`url` 取
`Video.PlayAddr.UrlList` 中首个有效的视频下载地址，来源标识取 `AwemeId`；
没有有效下载地址时 `url` 为 `None`。
小红书响应没有笔记页面地址，因此 `url` 为 `None`，`source_id` 取笔记 ID；
不会将图片或视频媒体地址当作笔记链接。
微博结果的 `url` 取有效的 `post_url`，将 `//weibo.com/...` 补全为
`https://weibo.com/...`；`source_id` 取 `weibo_id`。

CLI 提供 `web-search` 命令。HTTP API 尚未接入。

## Python 库

需要 Python 3.10 或更新版本。库本身不读取环境变量；调用方可自行读取所需服务的
配置。只使用 TikHub 时，无需提供 Search1API 的凭证：

```python
import asyncio
import os

from my_websearch_kit import Config, web_search


async def main() -> None:
    config = Config(
        tikhub_api_key=os.environ["TIKHUB_API_KEY"],
        tikhub_base_url=os.environ["TIKHUB_BASE_URL"],
    )
    results = await web_search(
        "人工智能",
        config=config,
        search_service="wechat-channel",
        time_range="week",
        max_results=5,
    )
    for result in results:
        print(result.title, result.url, result.source_name, result.source_id, result.snippet)


asyncio.run(main())
```

Search1API 仍可使用 `Config(search1api_api_key=..., search1api_base_url=...)`。
省略 `search_service` 时，由 Search1API 选择默认搜索来源；其他服务名称也直接
传给 Search1API。两个基础 URL 均需由调用方显式配置；官方示例分别为
`https://api.search1api.com` 和 `https://api.tikhub.io`。

Search1API 支持 `day`、`week`、`month`、`year` 的 `time_range`。TikHub 微信
搜索的时间筛选按下表映射，其中 `month` 和 `year` **不是精确等价**；抖音视频
搜索 V4 和微博实时搜索接口不支持时间筛选，传入有效的 `time_range` 也会被忽略：

| `time_range` | TikHub `publish_time` | 含义           |
| ------------ | --------------------- | -------------- |
| 省略         | `all`                 | 不限时间       |
| `day`        | `day`                 | 最近一天       |
| `week`       | `week`                | 最近七天       |
| `month`      | `half_year`           | 近似为最近半年 |
| `year`       | `all`                 | 近似为不限时间 |

小红书笔记搜索的 `time_range` 映射为：

| `time_range` | TikHub `time_filter` | 含义                           |
| ------------ | -------------------- | ------------------------------ |
| 省略         | `不限`               | 不限时间                       |
| `day`        | `一天内`             | 最近 24 小时                   |
| `week`       | `一周内`             | 最近七天                       |
| `month`      | `半年内`             | 近似为最近半年，不是最近一个月 |
| `year`       | `不限`               | 近似为不限时间，不是最近一年   |

TikHub 每次 `web_search` 只请求搜索首页，`max_results` 仅限制该页返回给调用方的
数量；首页不足时不会自动翻页。抖音视频搜索 V4 每页固定最多 12 条，因此即使
`max_results` 大于 12，也最多返回首页 12 条。提供的小红书样例首页有 20 条笔记；
实际返回数量以接口响应为准；提供的微博样例首页有 9 条，微博实际返回数量同样
以接口响应为准。文章的 `url` 优先取有效的 `doc_url`，视频号的
`url` 在没有链接时取 `exportId`，因此视频号结果的 `url` **不是网页链接**。
文章摘要来自 `desc`；视频响应没有独立摘要时，`snippet` 为空字符串。抖音结果用
`Desc` 作为标题；无描述时依次尝试 `Video.Title` 和视频 ID。
小红书结果的标题来自笔记 `title`，摘要来自 `desc`；标题为空时使用描述，
两者相同时摘要留空，仍无标题时使用笔记 ID。
微博没有独立标题，使用正文 `content` 作为标题、摘要留空；正文为空时使用微博 ID。
发布日期从响应时间戳转换为中国时区的 `YYYY-MM-DD`，抖音的 `CreateTime` 按毫秒
转换，小红书笔记的 `timestamp` 按秒转换。微博的相对发布时间根据响应自带的
`time_stamp` 换算；无法识别或缺少参照时间时为 `None`。
不会另外请求视频详情或分享链接。TikHub 文档标注微信搜索接口约
[0.01 美元/次](https://docs.tikhub.io/472974860e0)，即使正常返回空结果也可能计费。
抖音视频搜索 V4 也可能产生请求费用，具体费用以 TikHub 为准。
小红书笔记搜索也可能产生请求费用，具体费用以 TikHub 为准。
微博实时搜索也可能产生请求费用，具体费用以 TikHub 为准。

空结果返回空列表；无效输入或缺少所选服务的配置抛出 `ValueError`，请求失败、
HTTP 错误、TikHub 业务错误或无效响应抛出 `WebSearchError`。后者提供可用时的
`status_code` 和 `request_id`；TikHub 返回错误说明时，异常信息会包含其中的
`message_zh` 或 `message`，不会输出响应中回显的请求参数。

Search1API 遇到 `429`、`500`、`502`、`503`、`504`，或连接、读写、超时及服务端
协议错误时，最多发送 3 次请求，重试间隔采用带随机抖动的指数退避。响应包含
`Retry-After` 时会遵守该等待时间（秒数或 HTTP 日期）；如果需要等待超过 60 秒，
则直接抛出当前错误，不会提前重试。其他 HTTP 错误和无效响应不重试。

## CLI

`web-search` 从运行命令时的**当前工作目录**读取 `.env`，不会向父目录查找。
已有的进程环境变量优先于 `.env`。使用 TikHub 的微信、抖音、小红书或微博服务时仅需 `TIKHUB_API_KEY` 和
`TIKHUB_BASE_URL`；使用 Search1API 服务时仅需 `SEARCH1API_API_KEY` 和
`SEARCH1API_BASE_URL`。没有内置的基础 URL 回退值，可复制 `.env.example`
并填写所需服务的密钥。

```bash
uv run web-search "人工智能" --search-service wechat-article --max-results 5
uv run web-search "人工智能" --search-service wechat-channel --time-range week --json
uv run web-search "人工智能" --search-service douyin --max-results 12 --json
uv run web-search "人工智能" --search-service xiaohongshu --time-range week --max-results 20 --json
uv run web-search "人工智能" --search-service weibo --max-results 10 --json
uv run web-search "Python asyncio" --search-service github --time-range month
```

`--search-service` 的完整可选值如下。省略时使用 Search1API 的默认来源；
`wechat` 是 Search1API 的微信搜索，两个带连字符的微信服务、`douyin` 和
`xiaohongshu`、`weibo` 使用 TikHub。
Search1API 枚举以其[官方 Search 文档](https://s1.dev/docs/basic/search)为准。

| 服务             | 来源       | 说明                 |
| ---------------- | ---------- | -------------------- |
| `google`         | Search1API | Google               |
| `bing`           | Search1API | Bing                 |
| `bingcn`         | Search1API | Bing 中国版          |
| `duckduckgo`     | Search1API | DuckDuckGo           |
| `yahoo`          | Search1API | Yahoo                |
| `yandex`         | Search1API | Yandex               |
| `youtube`        | Search1API | YouTube              |
| `x`              | Search1API | X                    |
| `reddit`         | Search1API | Reddit               |
| `github`         | Search1API | GitHub               |
| `arxiv`          | Search1API | arXiv                |
| `wechat`         | Search1API | 微信搜索             |
| `bilibili`       | Search1API | 哔哩哔哩             |
| `imdb`           | Search1API | IMDb                 |
| `wikipedia`      | Search1API | 维基百科             |
| `baidu`          | Search1API | 百度                 |
| `360`            | Search1API | 360 搜索             |
| `quark`          | Search1API | 夸克                 |
| `wechat-article` | TikHub     | 公众号文章           |
| `wechat-channel` | TikHub     | 视频号视频           |
| `douyin`         | TikHub     | 抖音视频             |
| `xiaohongshu`    | TikHub     | 小红书图文和视频笔记 |
| `weibo`          | TikHub     | 微博实时搜索         |

查询为必填参数；`--max-results` 允许 1–50，默认为 5。默认输出标题、`url`
或来源标识、可选的来源名称、摘要及可选发布日期；视频号的 `url` 会显示为 `exportId`。
没有来源名称时不显示来源行。没有结果时输出 `No results.`。`--json`
输出结果对象数组，缺失的 `source_name` 为 `null`，没有结果时输出 `[]`。
`--log-level` 可设为 `debug`、
`info`、`warning`、`error`，默认 `info`。日志写入 stderr，不影响 JSON 的
stdout 输出，也不会记录密钥或认证头。参数或配置错误退出码为 2，搜索失败为 1，
成功为 0。真实搜索可能消耗服务额度。

## 本地开发与验证

项目使用 [`uv`](https://docs.astral.sh/uv/) 管理依赖、锁文件和构建：

```bash
uv sync
uv run pyright
uv build
```

实际搜索和 CLI 输出可由调用方使用自己的配置验证；真实请求可能消耗额度。

## 后续方向

HTTP API Server 将作为输入输出适配层复用库函数。其他内容平台可在接口得到确认后逐步接入。

## 开发指南

开发约定见 [`AGENTS.md`](./AGENTS.md)，`CLAUDE.md` 引用同一份约定。
