my-readurl-kit
my-readurl-kit 是一个独立的 Python 网页读取与内容提取库。当前的 read_url
实现迁移自 gede/gede/llm/tools/read_url_tool.py,但不依赖 gede 本身。
目标
- 获取给定 URL 的页面内容,支持返回原始 HTML 或可读文本。
- 通过
my-llmkit非流式调用 LLM,提取正文或与查询相关的内容。 - 同一套核心能力既可作为 Python 函数库使用,也可通过 CLI、MCP server 和 HTTP server 调用。
- 将抓取、解析、LLM 提取和传输协议分层,避免任一入口绑死核心库。
当前 Python API
网络 I/O 使用异步 API,成功结果通过 ReadUrlResult 返回;请求、状态码、响应
大小和内容类型错误会抛出 ReadUrlError 的具体子类,不会伪装成普通内容字符串。
import asyncio
from my_readurl_kit import read_url
async def main() -> None:
result = await read_url("https://example.com")
print(result.content)
print(result.final_url, result.status_code)
asyncio.run(main())
output_format 支持以下取值:
| 值 | 行为 |
|---|---|
raw |
返回解码后的原始响应正文;通过 Search1API 时返回服务提供的 Markdown/文本 |
text |
使用 BeautifulSoup 将 HTML 转为规范化纯文本,也是默认值 |
body |
通过 LLM 提取正文并剔除作者来源、推荐等非正文内容 |
relevant |
通过 LLM 提取与 query 直接相关的完整原文段落 |
relevant 传入非空 query 时提取相关段落;未传入或仅传入空白 query 时等同于
body。其他模式会忽略 query。body 和 relevant 只有在实际使用时才需要
LLM 配置;raw 和 text 不会创建或调用模型客户端。
调用方可以注入源站用的 httpx.AsyncClient、请求头和超时。默认跟随最多 10 次
重定向,接受文本、JSON、JavaScript 或 XML 响应,并将解压后的响应正文限制为 5 MiB。
成功抓取的原始页面及其响应元数据会按请求 URL 和抓取来源分别缓存。普通 URL 优先读取
直抓缓存,其次读取之前成功回退的 Search1API 缓存;命中后不会再次请求源站或 Search1API。
Search1API 缓存还按 API base 区分。HTML 解析和 LLM 提取仍会按每次调用执行。
缓存默认存放在
/tmp/my-readurl-kit/caches,也可以通过 MY_READURL_KIT_CACHE_DIR 修改位置:
export MY_READURL_KIT_CACHE_DIR="/path/to/readurl-cache"
缓存没有自动过期时间;需要重新抓取时,删除该目录中对应的缓存文件或清空缓存目录。 缓存文件损坏或缓存目录不可写不会阻止正常抓取。
Search1API 站点解析
设置 SEARCH1API_API_KEY 后,Search1API 会处理 Wikipedia/Wikimedia URL,以及文档列出的
专用解析 URL 模式:LinkedIn 公开公司、职位、文章和个人资料页;Reddit 帖子、社区和用户页;
X 帖子和文章;YouTube 视频和播放列表;GitHub 仓库 README 和文件;Stack Exchange 网络问题页;
Hugging Face 模型、数据集和 Space 页面;微信公众号文章;Hacker News 页面;npm/PyPI 包页面;
Bilibili 视频和 CSDN 博客文章。仅匹配文档支持的主机与路径才会优先调用专用解析器;
Wikimedia/Wikipedia 使用 Search1API 通用 crawl。其他 URL 先直接抓取;如果源站返回
HTTP 状态错误(包括 400、403)或发生连接、超时等网络错误,会回退调用 Search1API
/crawl。无效 URL、响应过大、内容类型不支持不会触发回退。
未设置 key 时,所有 URL 都使用原有直接抓取,失败时保留原错误。Search1API 请求失败会作为
Search1APIError 返回,不会自动再请求源站。对 /crawl 的 429、500、502、503、504
响应最多重试 2 次(总计 3 次请求):429 遵循 Retry-After,其余采用带抖动的指数退避;
若 429 未提供有效的 Retry-After,也采用指数退避。400、401、402、403、404、410、422
等状态不会重试,其中 /crawl 的 404/410 表示目标 URL 已确认失效。
服务默认启用其内部 fallback。成功的 /crawl 请求消耗 1 credit;已确认失效的
404/410 也会计费,服务侧未完成的失败请求不计费。直抓失败后的回退请求同样按这些规则
计费。发给 Search1API 的请求包含目标 URL,因此目标 URL 会由 Search1API 处理。
参考 额度说明、
错误处理说明 和
隐私政策。
export SEARCH1API_API_KEY="your-search1api-api-key"
# 可选;默认是 Search1API 官网 API 地址
export SEARCH1API_API_BASE="https://api.search1api.com"
SEARCH1API_API_BASE 和 Search1APIConfig(api_base=...) 都可用于切换兼容的 API 地址;默认值为
https://api.search1api.com。也可以在 Python API 中传入 Search1APIConfig。
search1api_client= 是单独注入的 httpx.AsyncClient,不会复用源站的 client=、请求头或 Cookie:
import asyncio
from my_readurl_kit import Search1APIConfig, read_url
async def main() -> None:
result = await read_url(
"https://www.reddit.com/r/python/comments/example/",
search1api_config=Search1APIConfig(
api_key="your-api-key",
api_base="https://api.search1api.com",
),
)
print(result.content)
asyncio.run(main())
服务返回 Markdown/文本时,text、body 和 relevant 模式会直接使用该文本,不会按 HTML
解析;raw 返回该内容本身,不保证是 HTML。详细 URL 支持范围以
Search1API 专用解析器文档 为准。
LLM 配置
LLMConfig 接收 API key、API base URL、模型名称和客户端类型。
客户端支持 openai_compatible 和 claude,分别对应 my-llmkit 的
OpenAICompatibleChatCompletion 和 ClaudeChatCompletion。
未显式传入 llm_config 或 llm_client 时,body 和 relevant 会自动
读取以下环境变量:
export LLM_EXTRACT_API_KEY="your-api-key"
export LLM_EXTRACT_API_BASE="https://api.openai.com/v1"
export LLM_EXTRACT_MODEL="gpt-4.1-mini"
export LLM_EXTRACT_CLIENT="openai_compatible"
四个变量需要同时配置。库本身不保存密钥,raw 和 text 模式也不会
读取这些环境变量。
import asyncio
from my_readurl_kit import read_url
async def main() -> None:
body = await read_url(
"https://example.com/article",
output_format="body",
)
relevant = await read_url(
"https://example.com/article",
query="WebAssembly 的技术细节",
output_format="relevant",
)
print(body.content)
print(relevant.content)
asyncio.run(main())
高级调用方也可以通过 llm_client= 直接注入已构造的
my_llmkit.chat.LLMChatCompletion,此时不需要 llm_config,也不会创建
第二个模型客户端。也可显式构造 LLMConfig,或调用
LLMConfig.from_env() 读取上述变量。llm_config 与 llm_client 不能同时传入,
且它们的优先级都高于环境变量。
LLM 通过 LLMChatCompletion.run() 以非流式方式运行。未提供配置时,
body 返回 无法获取正文,relevant 返回 无相关内容。模型调用失败
或返回空内容时,body 回退到规范化后的网页全文,relevant 返回
无相关内容。
CLI
安装项目后可以使用 read-curl 读取网页。URL 是必需的位置参数,
--output-format 支持与 Python API 相同的 raw、text、body 和
relevant,默认值为 text。成功时标准输出只包含读取到的内容,便于通过
管道继续处理或重定向到文件。
read-curl "https://example.com"
read-curl "https://example.com" --output-format raw
read-curl "https://example.com" --log-level debug
--log-level 支持 debug、info、warning、error 和 critical,默认为
warning。日志写入标准错误,不会混入页面内容所在的标准输出。
CLI 启动时会从当前工作目录加载 .env;已经存在于进程环境中的变量优先级更高。
Python API 不会自动加载 .env,需要调用方自行设置环境变量或传入配置对象。
body 和 relevant 会在发起网页请求前从四个 LLM_EXTRACT_* 环境变量
构建并校验 LLMConfig。这些变量缺失、不完整或无效时,命令会向标准错误
输出错误并以非零状态退出。raw 和 text 不读取或要求这些变量。
uv run read-curl \
"https://example.com/article" \
--output-format body
uv run read-curl \
"https://example.com/article" \
--output-format relevant \
--query "WebAssembly 的技术细节"
--query 在 --output-format relevant 模式下用于提取相关段落;未提供或仅包含
空白时,relevant 等同于 body。其他模式会忽略它。
LLM 模式会发起外部模型请求,可能产生费用。
计划中的其他入口
| 入口 | 用途 |
|---|---|
| MCP server | 向支持 MCP 的客户端暴露 read-url 工具 |
| HTTP server | 向其他进程或服务提供 HTTP API |
CLI、MCP 和 HTTP 层保持轻量,只负责参数转换、调用核心 API 以及输出结果。
设计边界
- 基础抓取与 HTML 解析不依赖 LLM,应可单独使用。
- LLM 提取建立在抓取与确定性文本解析结果之上,并通过
my-llmkit接入模型。 - 网络 I/O 以异步 API 为核心;如需同步 API,应由薄封装提供。
- 公共 API 使用明确的类型标注和结构化结果,区分成功结果与请求、解析、提取错误。
- 不在核心库中保存 API key 或其他密钥;配置由调用方显式传入或从
LLM_EXTRACT_*环境变量读取。
当前核心包结构:
src/my_readurl_kit/
├── cli.py # read-curl 命令行适配层
├── errors.py # 明确的错误类型
├── extract.py # 基于 my-llmkit 的非流式内容提取
├── fetch.py # 基于 httpx 的 HTTP 请求与响应处理
├── models.py # 公共结构化结果
├── parse.py # HTML 到可读文本的确定性转换
├── reader.py # read_url 高层 API
└── search1api.py # Search1API 路由与 crawl 集成
后续的协议入口会继续按上述边界拆分为独立模块。
开发环境
要求:
- Python 3.10 或更高版本
uv
安装项目及开发依赖:
uv sync
运行静态检查:
uv run pyright
运行不包含 LLM 的真实网站集成测试(会向微信和 Orchid Files 发起外部网络请求):
uv run pytest -m "network and not llm"
测试 body 模式(启动时从 .env 注入 LLM_EXTRACT_* 变量):
uv run --env-file .env pytest -s tests/test_read_url_network.py::test_read_url_body_with_llm
测试 relevant 模式(启动时从 .env 注入 LLM_EXTRACT_* 变量):
uv run --env-file .env pytest -s tests/test_read_url_network.py::test_read_url_relevant_with_llm
两个 LLM 测试都会抓取 Orchid Files 的公开文章并发起一次非流式模型
请求。测试函数会通过 LLMConfig.from_env() 构造配置,并将它显式传给
read_url(llm_config=...)。测试会输出提取结果,也可能产生模型调用费用。未注入完整环境
变量时,测试会显式跳过,不会误用回退文本当作模型输出。
构建发行包:
uv build
当前实现可用以下方式手工检查(会发起外部网络请求):
uv run python -c 'import asyncio; from my_readurl_kit import read_url; print(asyncio.run(read_url("https://example.com")).content)'
项目默认不要求为改动新增单元测试;需要验证行为时,应在变更说明中提供简短、 可复现的手工测试步骤。只有在明确要求时才新增单元测试。
初步迁移顺序
- 稳定当前 Python API 和错误模型(已完成)。
- 在核心 API 之上增加 CLI(已完成)。
- 后续增加 MCP server 和 HTTP server。
Metadata
Release files for my-readurl-kit 0.0.9
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| my_readurl_kit-0.0.9.tar.gz | 132.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| my_readurl_kit-0.0.9-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 157.3 kB
Release files / my_readurl_kit-0.0.9.tar.gz
| Download URL | my_readurl_kit-0.0.9.tar.gz |
|---|---|
| Size | 132.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a0136a1e83d809f71ff74dbfbb12002386b568c1f0b885f8b50dbb79bd59c4fe
|
|
BLAKE2b-256 checksum How to use checksums |
7063909e18746725d6f1fa65e38bb46562b74984882f816b0c5aae668773ab9e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.18 {"installer":{"name":"uv","version":"0.12.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / my_readurl_kit-0.0.9-py3-none-any.whl
| Download URL | my_readurl_kit-0.0.9-py3-none-any.whl |
|---|---|
| Size | 24.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c0f422800ef790e54c981702bb9e14a0e6ffbbc4f036c39cfa81c62962c9826e
|
|
BLAKE2b-256 checksum How to use checksums |
db29f94736ceaa77030aedc2f5db9e6991e0e7e2524aa77aac0bad499a55da06
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.18 {"installer":{"name":"uv","version":"0.12.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|