Skip to main content

zytools-fs

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

文章爬虫(优先使用)

文章爬虫从入口 URL 按广度优先发现同域页面,只保存识别为正文的文章。图片、PDF、音视频、压缩包、JavaScript 和 CSS 等资源会被跳过。使用 UrlFilter 后,重复运行只会继续处理未保存的文章 URL。

from zytools.artice import UrlCrawler
from zytools.utils import UrlFilter

START_URL = "https://www.hothk.com/"

with UrlFilter("article_urls.lmdb") as url_filter:
    crawler = UrlCrawler(
        start_url=START_URL,
        max_saved_urls=100,
        max_depth=3,
        same_domain=True,
        url_fp=url_filter,
    )

    for article in crawler.crawl():
        print(article["title"])
        print(article["url"])
        print(article["content"])

    crawler.save_url_filter()

单页测试:

from zytools.artice import check_response

result = check_response("https://gongjyuhok.hk/a")
print(result["type"], result.get("title"), result.get("text_length"))

已验证可识别正文的站点包括:香港头条、香港24小时、HK Daily Post、港語學。默认 same_domain=True,不会抓取外部域名。

目录

功能

  • 压缩包解压:递归解压常见格式,保留目录层级并记录失败任务。
  • 文件统计:统计视频文件夹或 TXT、JSONL、JSON 文件并生成汇总名称。
  • FTP 和 SFTP:目录递归上传下载,支持重试、进度、跳过和并发。
  • 哔哩哔哩抖音:下载视频并控制画质、代理和文件名。
  • 猫耳 FM小宇宙:下载并转换音频或播客。
  • 喜马拉雅:下载公开单集或整张专辑,支持断点续传和清单记录。

所有媒体下载器均按 zytools.video.<平台>.downloader 组织,并在 zytools.video 导出统一的 download_<平台>_video 入口;旧的音频函数名继续兼容。

所有平台下载过程中统一使用 完整文件名.原后缀.part,例如 视频.mp4.part音频.wav.part;下载及处理成功后才自动重命名回原后缀。

所有平台的 filename 都支持 {Id}_{Title}_{Date} 模板;合集或播客还支持 {order}。默认模板均为 {Id}_{Title}_{Date}

统一命令格式:

zytools video bilibili "链接" -o .
zytools video douyin "链接" -o .\downloads
zytools video maoer "https://www.missevan.com/sound/player?id=13407119" -o .
zytools video xiaoyuzhou "链接" -o .\downloads
zytools video ximalaya "链接" -o .\downloads

Python 进程白名单

以下命令会立即在后台启动守护进程,保留 name.py,并持续结束其他以 python xxx.py 形式运行的 Python 脚本进程:

zytools kill add name.py

可以同时指定多个白名单脚本,并调整扫描间隔:

zytools kill add name.py worker.py --interval 2

也可以多次执行命令,白名单会追加到共享列表,后台只保持一个守护进程:

zytools kill add name.py
zytools kill add name1.py
zytools kill add name2.py

白名单按脚本文件名匹配,不要求传入完整路径。交互式 Python 和 python -m module 进程不属于 python xxx.py,不会被结束。 省略 --interval 时默认每 10 秒扫描一次。

安装

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"

存储单位默认固定为 MB,也可以指定 GB 或 TB,并设置小数位:

zytools count "C:/Videos" --unit GB --precision 3

默认保留 3 位小数;例如 1MB 固定为 GB 时显示为 0.001GB

程序会递归识别 MP4、MKV、MOV、AVI、TS、M2TS、FLV、WEBM、WMV 等常见视频,通过 ffprobe 汇总数量、文件大小和时长。输出最后一行为:

C:\a\b\c C:\a\b\c_20251222_2477条_109.86GB_2191.73H

存储单位默认固定为 MB;可用 --unit GB--unit TB 指定单位,默认保留三位小数,使用 --precision 可调整。统计无错误时,命令默认把原文件夹重命名为汇总名称;使用 --no-rename 可以只返回名称而不修改目录。再次统计已带汇总后缀的目录时会替换旧后缀。目标目录已存在或视频读取失败时不会覆盖或重命名。时长统计需要安装 ffprobe,它随 ffmpeg 一起提供。

统计发现无法读取的视频时,会在源目录生成 error.txt,记录失败原因。默认只记录、不删除文件;增加 --del 后才删除失败视频、零字节文件和清理后形成的空文件夹。只要存在读取错误,源目录不会重命名。

zytools count "C:/Videos" --del

传入 .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",
    output_dir=".",
    quality="max",
    filename="{Id}_{Title}_{Date}",
    cookies={
        "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 流。
  • cookies:可选的 Cookie 字典,用于需要登录权限的内容。
  • proxies:页面和 API 请求使用的 requests 格式代理字典;媒体流下载不使用该代理。

哔哩哔哩和抖音生成的视频文件名主体最多保留 20 个字符,不包含 .mp4 扩展名。较长的哔哩哔哩文件名会附加短哈希,避免分 P 或相似标题截断后发生重名。

B站下载中的最终文件使用 文件名.mp4.part,下载、合并并通过完整性校验后自动重命名为 文件名.mp4。中断或校验失败时不会生成正式 .mp4

文件名模板支持以下字段:

  • {Id}:视频 BV 号,默认模板使用该字段。
  • {Title}:视频标题。
  • {Date}YYYYMMDD 格式的发布日期。

当链接对应合集(多个分集)时,在模板中加入小写 {order} 会下载全部分集,并按 123 编号;不加入 {order} 时只下载第 1 集。

请仅下载自己拥有或已获得授权的内容。

抖音视频下载

from zytools.video import download_douyin_video

output_file = download_douyin_video(
    "https://www.douyin.com/video/7530000000000000000",
    output_dir="./downloads",
    filename="{Id}_{Title}_{Date}",
    cookies={
        "msToken": "your-ms-token",
    },
    proxies={
        "http": "http://127.0.0.1:7890",
        "https": "http://127.0.0.1:7890",
    },
)

print(output_file)

参数说明:

  • output_dir:保存目录,默认为当前目录,父目录会自动创建。
  • filename:可选文件名,可带或不带 .mp4 扩展名;省略时使用作品标题。清理非法字符后,文件名的标题部分最多保留 20 个字符。
  • cookies:可选 Cookie 字典。传入有效的 msToken 可减少临时 Token 获取失败的影响。
  • proxies:可选的 requests 格式代理字典,用于详情接口和视频下载。

函数成功后返回最终 MP4 文件的绝对路径;链接无效、详情获取失败或没有可用播放地址时抛出 ValueErrorRuntimeError

当 Web 详情接口未返回作品数据时,下载器会自动回退到抖音移动分享页,并将分享页播放地址转换为无水印地址,无需调用方额外处理。

请仅下载自己拥有或已获得授权的内容。

猫耳 FM 音频下载

from zytools.video import download_maoer_video

output_file = download_maoer_video(
    "https://www.missevan.com/sound/player?id=13407119",
    output_dir=".",
    filename="{Id}_{Title}_{Date}",
    cookies={},
    proxies={
        "http": "http://127.0.0.1:7890",
        "https": "http://127.0.0.1:7890",
    },
)

print(output_file)

也支持广播剧合集:https://www.missevan.com/mdrama/94476。单集链接直接下载一个音频;合集未使用 {order} 时下载第 1 集,使用 filename="{order}_{Id}_{Title}_{Date}" 时下载全部分集。

filename 不写扩展名时自动保存为 .m4a,与其他平台一样可以直接使用统一模板。只有需要转换格式时才显式添加 .wav.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",
    filename="{Id}_{Title}_{Date}",
    cookies={},
    proxies={
        "http": "http://127.0.0.1:7890",
        "https": "http://127.0.0.1:7890",
    },
)

print(output_file)

下载整档播客:

from zytools.video import download_xiaoyuzhou_audio

output_dir = download_xiaoyuzhou_audio(
    "https://www.xiaoyuzhoufm.com/podcast/xxxxxxxx",
    output_dir="./podcast",
)

print(output_dir)

所有节目默认转换为 WAV,因此需要提前安装 ffmpeg 并加入 PATH。单集下载成功后返回 WAV 文件的绝对路径;整档播客下载成功后返回保存目录的绝对路径,并在目录内生成 episodes.json 元数据文件。

参数说明:

  • output_dir:保存目录。单集默认使用当前目录,整档播客默认使用播客标题创建目录。
  • filename:单集文件名或整档播客目录名。
  • cookies:可选 Cookie 字典。
  • proxies:可选的 requests 格式代理字典,用于页面、RSS 和音频请求。

请仅下载自己拥有或已获得授权的内容。

喜马拉雅下载

支持公开的喜马拉雅单集和专辑链接,音频默认保留服务端格式(通常为 .m4a),文件名最多 20 个字符。

命令行:

zytools video ximalaya "https://www.ximalaya.com/sound/123456789" -o .
zytools video ximalaya "https://www.ximalaya.com/album/123456789" -o .

Python:

from zytools.video import download_ximalaya

result = download_ximalaya(
    "https://www.ximalaya.com/album/123456789",
    output_dir=".",
    filename="{Id}_{order}_{Title}_{Date}",
    cookies={},
    proxies=None,
)
print(result["output_dir"])

专辑链接默认只下载第 1 集;filename 中包含 {order} 时才会按顺序下载全部分集。下载过程会显示进度,并在专辑目录写入 manifest.json;已存在且校验有效的文件会自动跳过,临时文件支持断点续传。

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.34.tar.gz (99.9 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.34-py3-none-any.whl (92.0 kB view details)

Uploaded Python 3

File details

Details for the file zytools_fs-0.0.34.tar.gz.

File metadata

  • Download URL: zytools_fs-0.0.34.tar.gz
  • Upload date:
  • Size: 99.9 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.34.tar.gz
Algorithm Hash digest
SHA256 bca9d40669ad618bf62eeb2fd3aeec0d86aac8f6f97921e4f659cba2baf2909b
MD5 5f0375d809528418a881598c8490c4e5
BLAKE2b-256 085362b9f45421a4b27db47239b57407433f2875a014a68aa66f945cea4535da

See more details on using hashes here.

File details

Details for the file zytools_fs-0.0.34-py3-none-any.whl.

File metadata

  • Download URL: zytools_fs-0.0.34-py3-none-any.whl
  • Upload date:
  • Size: 92.0 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.34-py3-none-any.whl
Algorithm Hash digest
SHA256 f9e4a81d4c6560917d25d4d8b9be7199dfd5baf31560771523029056cc717499
MD5 29cb755502ccdb8c29be2a88c87798bd
BLAKE2b-256 9bba3f299ae1500cc4e3adbef69482b85640b581e5427e791c7dd4bb98fe3140

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

This release

0.0.34 This release

2 files

0.0.33

2 files

0.0.32

2 files

0.0.31

2 files

0.0.30

2 files

0.0.29

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