zytools-fs
zytools-fs 是一个轻量的 Python 工具集合,包含 FTP/SFTP 文件传输、任务心跳、URL 持久化去重、文章抓取、哔哩哔哩视频下载和猫耳 FM 音频下载等功能。各模块均可直接在脚本中导入使用,不依赖额外框架。
目录
- 命令行工具:安装 · 解压工具 · 文件统计
- 文件传输:FTP/SFTP
- 媒体下载:哔哩哔哩 · 抖音 · 猫耳 FM · 小宇宙
- 数据处理:URL 去重 · 文章识别 · URL 爬虫
- 运行工具:任务心跳 · Clash 代理
- 许可证
功能
- 压缩包解压:递归解压常见格式,保留目录层级并记录失败任务。
- 文件统计:统计视频文件夹或 TXT、JSONL、JSON 文件并生成汇总名称。
- FTP 和 SFTP:目录递归上传下载,支持重试、进度、跳过和并发。
- 哔哩哔哩与抖音:下载视频并控制画质、代理和文件名。
- 猫耳 FM与小宇宙:下载并转换音频或播客。
- URL 去重、文章识别和URL 爬虫:处理抓取任务和持久化状态。
- 任务心跳与Clash 代理:上报脚本状态并管理代理节点。
安装
pip install zytools-fs
要求 Python 3.9 或更高版本。
哔哩哔哩 DASH 音视频合并和猫耳音频格式转换依赖 ffmpeg,请先安装并确保命令已加入 PATH。未安装 ffmpeg 时,猫耳下载器无法生成最终音频文件。
安装后可运行以下命令检查:
zytools
预期输出:
zytools installed successfully.
解压工具
解压常见压缩包或批量处理文件夹:
zytools unzip archive.zip
zytools unzip archive.zip -C ./output
zytools unzip ./archives
zytools unzip ./archives -C ./extracted --log ./records.xlsx
支持 ZIP、7Z、RAR、TAR、TAR.GZ、TGZ、TAR.BZ2、TBZ2、TAR.XZ、TXZ,以及单文件 GZ、BZ2、XZ。RAR 解压依赖系统中可用的 unrar、unar、7zip 或 bsdtar 后端。
传入文件夹时,程序会递归扫描全部受支持的压缩包,并在命令执行的当前目录生成 zytools.xlsx。程序会先把全部任务写入表格,再开始逐个解压。使用 -C 指定目标根目录后,输出会保持原文件夹相对层级。例如 archives/a/data.zip 会解压到 extracted/a/data/,不会把所有内容堆放到同一级。扫描、任务写入、开始解压、完成、跳过和失败都会立即输出状态,避免处理大文件时长时间没有反馈。
zytools.xlsx 包含压缩包路径、输出目录、状态(ok、error 或 pending)、尝试次数和错误信息。每个任务开始和结束时都会更新对应行。再次运行同一命令时,程序会合并当前扫描结果和表内任务:已完成且输出目录存在的任务跳过,失败或未完成任务按照表中的压缩包路径和输出目录再次解压。可用 --log 指定其他任务表路径。
省略 -C 时,程序会在每个压缩包旁边创建同名目录。旧版的第二位置参数仍兼容。解压器会拒绝绝对路径、目录穿越路径和符号链接。
也可以在 Python 中复用:
from zytools.utils import extract_archive, extract_archive_folder
extract_archive("archive.zip", "./output")
extract_archive_folder("./archives", "./extracted")
文件统计
统计文件夹内的全部视频:
zytools count "C:/a/b/c"
zytools count "C:/a/b/c" --workers 8
zytools count "C:/a/b/c" --no-rename
zytools count "C:/data/rows.jsonl"
zytools count "C:/data/items.json"
zytools count "C:/data/lines.txt"
程序会递归识别 MP4、MKV、MOV、AVI、TS、M2TS、FLV、WEBM、WMV 等常见视频,通过 ffprobe 汇总数量、文件大小和时长。输出最后一行为:
C:\a\b\c C:\a\b\c_20251222_2477条_109.86GB_2191.73H
大小不足 1TB 时使用 GB,达到 1TB 后自动使用 TB;数值保留两位小数。统计无错误时,命令默认把原文件夹重命名为汇总名称;使用 --no-rename 可以只返回名称而不修改目录。再次统计已带汇总后缀的目录时会替换旧后缀。目标目录已存在或视频读取失败时不会覆盖或重命名。时长统计需要安装 ffprobe,它随 ffmpeg 一起提供。
传入 .txt、.jsonl 或 .json 文件时会自动切换为文本统计。TXT 按物理行数统计,JSONL 按非空记录行统计并验证每行 JSON,JSON 顶层数组按元素数统计;单个 JSON 对象按 1 条统计。文本汇总名称不包含小时数,例如:
C:\data\rows.jsonl C:\data\rows_20251222_2477条_1.20GB.jsonl
文本统计成功后默认重命名文件,使用 --no-rename 时仅返回新名称。无效 JSON 或 JSONL 存在错误行时不会重命名。
Python 调用统一从 zytools.utils 导入:
from zytools.utils import count_path, count_text_file, count_videos
result = count_path("C:/data/rows.jsonl", rename=False)
print(result["count"], result["summary"])
旧版的 zytools.count 和 zytools.unzip 模块路径继续兼容。
FTP 和 SFTP 文件传输
FTPClient 和 SFTPClient 使用相同的上传下载接口。两者均支持单文件传输、目录递归传输、失败重试、传输进度、按大小跳过已有文件、并发传输和结果汇总。
FTP 示例
from zytools.utils import FTPClient
with FTPClient(
host="127.0.0.1",
user="user",
password="password",
port=21,
passive=True,
workers=4,
) as ftp:
download_result = ftp.download("/remote/path", "./downloads")
upload_result = ftp.upload("./reports", "/remote/reports")
SFTP 示例
from zytools.utils import SFTPClient
with SFTPClient(
host="127.0.0.1",
user="user",
password="password",
port=22,
# key_filename="~/.ssh/id_rsa",
# allow_unknown_host=True, # 仅建议在可信的私有主机上使用
workers=4,
) as sftp:
download_result = sftp.download("/remote/path", "./downloads")
upload_result = sftp.upload("./reports", "/remote/reports")
传输规则
- 文件路径只传输一个文件,目录路径会递归处理全部子目录和文件。
- 目标文件已存在且大小一致时会跳过,并计为成功。
show_progress=True时显示固定位置的进度条和完成日志;并发模式最多复用workers行进度显示。download()和upload()均返回包含total、success、error的字典。- 目录传输时设置
workers>1可启用并发,每个工作线程使用独立连接。
返回值示例:
{"total": 10, "success": 9, "error": 1}
常用参数:
port:FTP 默认端口为21,SFTP 默认端口为22。encoding:FTP 文件名编码,默认为utf-8。passive:是否使用 FTP 被动模式,默认为True,仅 FTP 可用。key_filename:SFTP 使用的 SSH 私钥路径,支持~。allow_unknown_host:是否允许未知的 SFTP 主机密钥,默认为False。download_retries、upload_retries:下载和上传的尝试次数。retry_wait_seconds:两次重试之间的等待秒数。workers:目录上传下载的并发数,默认为1。show_progress:是否显示进度条和完成日志。
哔哩哔哩视频下载
from zytools.video import download_bili_video
ok = download_bili_video(
"https://www.bilibili.com/video/BVxxxx?p=8",
output_dir="./downloads",
quality="max",
filename="Bilibili_{BV}_{Date}_{Page}_{PartTitle}",
cookie={
"SESSDATA": "your_sessdata",
"bili_jct": "your_bili_jct",
},
proxies={
"http": "http://127.0.0.1:7890",
"https": "http://127.0.0.1:7890",
},
)
print(ok)
参数说明:
quality:"max"选择可用的最高画质,"min"选择最低画质。实际画质取决于账号权限和接口返回的 DASH 流。page:可选的分 P 设置。省略时,URL 中包含p=8就只下载 P8;URL 中没有p则下载全部分 P。也可显式传入"all"、"1"或"1,3-5"。cookie:可选的哔哩哔哩 Cookie,用于需要登录权限的视频。proxies:页面和 API 请求使用的requests格式代理字典;媒体流下载不使用该代理。force:为False时跳过已有的最终视频文件,为True时覆盖。
哔哩哔哩和抖音生成的视频文件名主体最多保留 20 个字符,不包含 .mp4 扩展名。较长的哔哩哔哩文件名会附加短哈希,避免分 P 或相似标题截断后发生重名。
文件名模板支持以下字段:
{Title}:视频标题。{BV}:BV 号。{Date}:YYYYMMDD格式的发布日期。{Page}、{Part}:分 P 序号。{Duration}:时长,单位为秒。{PartTitle}:分 P 标题。
请仅下载自己拥有或已获得授权的内容。
抖音视频下载
from zytools.video import download_douyin_video
output_file = download_douyin_video(
"https://www.douyin.com/video/7530000000000000000",
output_dir="./downloads",
filename="我的视频", # 可省略,默认使用作品标题
cookies={
"msToken": "your-ms-token",
},
proxies={
"http": "http://127.0.0.1:7890",
"https": "http://127.0.0.1:7890",
},
force=False,
)
print(output_file)
参数说明:
output_dir:保存目录,默认为当前目录,父目录会自动创建。filename:可选文件名,可带或不带.mp4扩展名;省略时使用作品标题。清理非法字符后,文件名的标题部分最多保留 20 个字符。cookies:可选 Cookie 字典。传入有效的msToken可减少临时 Token 获取失败的影响。proxies:可选的requests格式代理字典,用于详情接口和视频下载。force:为False时跳过已有目标文件,为True时重新下载并覆盖。
函数成功后返回最终 MP4 文件的绝对路径;链接无效、详情获取失败或没有可用播放地址时抛出 ValueError 或 RuntimeError。
当 Web 详情接口未返回作品数据时,下载器会自动回退到抖音移动分享页,并将分享页播放地址转换为无水印地址,无需调用方额外处理。
请仅下载自己拥有或已获得授权的内容。
猫耳 FM 音频下载
from zytools.video import download_maoer_video
output_file = download_maoer_video(
sound_id=13073155,
filepath="./downloads/1.wav",
proxies={
"http": "http://127.0.0.1:7890",
"https": "http://127.0.0.1:7890",
},
)
print(output_file)
filepath 必须是完整的目标文件路径。扩展名决定输出格式,可使用 .wav、.m4a、.mp3 或 .flac。程序会自动创建父目录,不会使用猫耳页面标题作为文件名。
代理只用于页面、播放列表和 DRM API 请求;音频分片下载会绕过传入的代理及环境代理。函数成功后返回最终音频文件的绝对路径。
请仅下载自己拥有或已获得授权的内容。
小宇宙播客下载
下载单集:
from zytools.video import download_xiaoyuzhou_audio
output_file = download_xiaoyuzhou_audio(
"https://www.xiaoyuzhoufm.com/episode/xxxxxxxx",
output_dir="./downloads",
proxies={
"http": "http://127.0.0.1:7890",
"https": "http://127.0.0.1:7890",
},
force=False,
)
print(output_file)
下载整档播客:
from zytools.video import download_xiaoyuzhou_audio
output_dir = download_xiaoyuzhou_audio(
"https://www.xiaoyuzhoufm.com/podcast/xxxxxxxx",
output_dir="./podcast",
workers=3,
)
print(output_dir)
所有节目默认转换为 WAV,因此需要提前安装 ffmpeg 并加入 PATH。单集下载成功后返回 WAV 文件的绝对路径;整档播客下载成功后返回保存目录的绝对路径,并在目录内生成 episodes.json 元数据文件。
参数说明:
output_dir:保存目录。单集默认使用当前目录,整档播客默认使用播客标题创建目录。workers:整档播客的并发下载数,默认为3。proxies:可选的requests格式代理字典,用于页面、RSS 和音频请求。force:为False时跳过已有的有效 WAV 文件,为True时重新下载并覆盖。
请仅下载自己拥有或已获得授权的内容。
URL 去重
UrlFilter 在 LMDB 中保存 URL 的 MD5 指纹。它不是布隆过滤器,不会主动引入假阳性。
from zytools.utils import UrlFilter
with UrlFilter(file_path="url_seen.lmdb") as url_filter:
url = "https://example.com/video?id=1"
if url_filter.add(url):
print("首次出现")
else:
print("已经存在")
print(len(url_filter))
批量添加、导出和导入:
from zytools.utils import UrlFilter
with UrlFilter("url_seen.lmdb") as url_filter:
added = url_filter.add_many([
"https://example.com/a",
"https://example.com/b",
])
url_filter.to_csv("url_seen.csv")
print(f"新增 {added} 个 URL")
UrlFilter.to_lmdb("url_seen.csv", file_path="url_seen_copy.lmdb")
文章页面识别
使用 check_response 请求一个 URL,并将结果分类为文章、其他 HTML 页面、二进制资源或请求失败。
from zytools.artice import check_response
result = check_response("https://example.com/news/1.html")
if result["type"] == "article":
print(result["title"])
print(result["date"])
print(result["text"][:300])
else:
print(result["type"], result.get("reason"))
type 可能为:
article:文章页面,包含title、date、author和text。other:不像文章的普通 HTML 页面。binary:图片、PDF、JavaScript、CSS、视频、压缩包等非 HTML 资源。fetch_error:请求失败或 HTTP 状态异常。
注意:当前公开模块名为
zytools.artice,请按上述拼写导入。
简单 URL 爬虫
UrlCrawler 从一个 URL 开始广度优先遍历链接,并逐条返回识别到的文章。可配合 UrlFilter 避免跨多次运行重复保存文章 URL。
from zytools.artice import UrlCrawler
from zytools.utils import UrlFilter
with UrlFilter("article_urls.lmdb") as url_filter:
crawler = UrlCrawler(
start_url="https://example.com/",
max_saved_urls=20,
same_domain=True,
max_depth=5,
url_fp=url_filter,
)
for item in crawler.crawl():
print(item["title"], item["url"])
crawler.save_url_filter()
每条结果包含:
title:文章标题。creat_date:文章发布日期(字段名保持现有接口拼写)。content:文章正文。url:最终文章 URL。get_date:当前抓取批次日期。
任务心跳
update_task 用于发送一次心跳;需要限制连续上报频率时可使用 TaskUpdater。
from zytools.utils import TaskUpdater, update_task
result = update_task(
name="daily job",
machine_id="machine-1",
script_path="/path/to/script.py",
server="http://127.0.0.1:8001",
)
print(result)
task = TaskUpdater(
name="daily job",
machine_id="machine-1",
script_path="/path/to/script.py",
server="http://127.0.0.1:8001",
min_interval=60,
)
task.update()
task.update(force=True)
服务端需要接收 POST /tasks 请求,请求体为 JSON,并包含 name、machine_id、script_path、enabled 和 timeout_seconds 字段。
Clash 代理切换
ClashVerge 通过 Clash/Mihomo 外部控制器 API 查看代理组、检测节点延迟,并切换到指定或随机可用节点。控制器需要开启外部连接(默认地址为 http://127.0.0.1:9090)。
主要功能:
get_group():获取全部代理组名称。get_group_nodes():递归展开嵌套代理组,返回去重后的实际节点名称。get_activate():查看指定代理组当前选中的节点。check_group_delay():批量检测代理组成员延迟,超时节点的延迟记为0。check_node_delay():检测单个节点的延迟。switch_node():将 Selector 类型的代理组切换到指定节点。switch_random_node():随机检测候选节点,并切换到第一个延迟检查通过的节点。del_node():暂时排除不可用节点,到达node_delay_reset设置的时间后自动恢复候选资格。
请求失败、代理组不存在、节点不属于代理组或代理组不支持手动切换时,会抛出 ClashAPIError。
from zytools.utils import ClashAPIError, ClashVerge
clash = ClashVerge(
url="http://127.0.0.1:9090",
secret="your-secret",
default_group="主代理",
)
try:
print(clash.get_group())
print(clash.get_group_nodes())
print(clash.get_activate())
# 手动切换到指定节点
clash.switch_node(proxy_name="香港节点")
# 测试延迟并切换到随机可用节点
result = clash.switch_random_node()
print(result) # {"group": "主代理", "node": "香港节点", "delay": 123}
except ClashAPIError as exc:
print(f"Clash 操作失败:{exc}")
switch_node 也支持传入 group_name 和 proxy_name,switch_random_node 会自动跳过检测失败的节点,并在延迟一段时间后重新允许尝试。不要在不可信网络中暴露 Clash 控制器端口。
许可证
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file zytools_fs-0.0.29.tar.gz.
File metadata
- Download URL: zytools_fs-0.0.29.tar.gz
- Upload date:
- Size: 82.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.13.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
21310384f619b0b13283b4a0514f2aef8b8c8efacea4eaef6dd29b0686cdfb16
|
|
| MD5 |
2d3ecf0bee1b2c2d4c4dbd79eb8a6585
|
|
| BLAKE2b-256 |
3fe534d2f7024b59ce65c4486a873762556f00b7b2224875f92bd80f7348b577
|
File details
Details for the file zytools_fs-0.0.29-py3-none-any.whl.
File metadata
- Download URL: zytools_fs-0.0.29-py3-none-any.whl
- Upload date:
- Size: 77.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.13.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c1f25ad3630e5fc6e8a81cad384b7e7330be66376518a75b98c379f891d1605b
|
|
| MD5 |
4322e8144b3910e777562930b7b7fd29
|
|
| BLAKE2b-256 |
30fd565e390dc1e4dcba048669f472ddf94274d87695eb6441b2f322ad716313
|