Skip to main content

zytools-fs

zytools-fs 是一个轻量的 Python 工具集合,包含 FTP/SFTP 文件传输、任务心跳、URL 持久化去重、文章抓取、哔哩哔哩视频下载和猫耳 FM 音频下载等功能。各模块均可直接在脚本中导入使用,不依赖额外框架。

目录

功能

安装

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 解压依赖系统中可用的 unrarunar7zipbsdtar 后端。

传入文件夹时,程序会递归扫描全部受支持的压缩包,并在命令执行的当前目录生成 zytools.xlsx。程序会先把全部任务写入表格,再开始逐个解压。使用 -C 指定目标根目录后,输出会保持原文件夹相对层级。例如 archives/a/data.zip 会解压到 extracted/a/data/,不会把所有内容堆放到同一级。扫描、任务写入、开始解压、完成、跳过和失败都会立即输出状态,避免处理大文件时长时间没有反馈。

zytools.xlsx 包含压缩包路径、输出目录、状态(okerrorpending)、尝试次数和错误信息。每个任务开始和结束时都会更新对应行。再次运行同一命令时,程序会合并当前扫描结果和表内任务:已完成且输出目录存在的任务跳过,失败或未完成任务按照表中的压缩包路径和输出目录再次解压。可用 --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.countzytools.unzip 模块路径继续兼容。

FTP 和 SFTP 文件传输

FTPClientSFTPClient 使用相同的上传下载接口。两者均支持单文件传输、目录递归传输、失败重试、传输进度、按大小跳过已有文件、并发传输和结果汇总。

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() 均返回包含 totalsuccesserror 的字典。
  • 目录传输时设置 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_retriesupload_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 文件的绝对路径;链接无效、详情获取失败或没有可用播放地址时抛出 ValueErrorRuntimeError

当 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:文章页面,包含 titledateauthortext
  • 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,并包含 namemachine_idscript_pathenabledtimeout_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_nameproxy_nameswitch_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

zytools_fs-0.0.29.tar.gz (82.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

zytools_fs-0.0.29-py3-none-any.whl (77.1 kB view details)

Uploaded Python 3

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

Hashes for zytools_fs-0.0.29.tar.gz
Algorithm Hash digest
SHA256 21310384f619b0b13283b4a0514f2aef8b8c8efacea4eaef6dd29b0686cdfb16
MD5 2d3ecf0bee1b2c2d4c4dbd79eb8a6585
BLAKE2b-256 3fe534d2f7024b59ce65c4486a873762556f00b7b2224875f92bd80f7348b577

See more details on using hashes here.

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

Hashes for zytools_fs-0.0.29-py3-none-any.whl
Algorithm Hash digest
SHA256 c1f25ad3630e5fc6e8a81cad384b7e7330be66376518a75b98c379f891d1605b
MD5 4322e8144b3910e777562930b7b7fd29
BLAKE2b-256 30fd565e390dc1e4dcba048669f472ddf94274d87695eb6441b2f322ad716313

See more details on using hashes here.

Release history Release notifications | RSS feed

0.0.43

2 files

0.0.42

1 file

0.0.41

1 file

0.0.40

1 file

0.0.39

1 file

0.0.38

1 file

0.0.37

1 file

0.0.36

2 files

0.0.35

2 files

0.0.34

2 files

0.0.33

2 files

0.0.32

2 files

0.0.31

2 files

0.0.30

2 files

This release

0.0.29 This release

2 files

0.0.28

2 files

0.0.27

2 files

0.0.26

2 files

0.0.25

2 files

0.0.24

2 files

0.0.23

2 files

0.0.22

2 files

0.0.21

2 files

0.0.20

2 files

0.0.19

2 files

0.0.17

2 files

0.0.16

2 files

0.0.14

2 files

0.0.13

2 files

0.0.12

2 files

0.0.10

2 files

0.0.9

2 files

0.0.8

2 files

0.0.7

2 files

0.0.6

2 files

0.0.5

2 files

0.0.4

2 files

0.0.3

2 files

0.0.2

2 files

0.0.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page