zytools-fs
zytools-fs is a small collection of Python utilities for personal crawling,
FTP transfer, task heartbeats, persistent URL de-duplication, Bilibili video
downloads, and Maoer FM audio downloads.
The package is intentionally lightweight: every helper can be imported and used directly in scripts without a framework.
Features
- FTP and SFTP recursive download/upload with retry, progress, skip-by-size, and concurrent transfer support.
- Bilibili video downloader based on
requests. - Maoer FM audio downloader with DRM decryption and WAV output.
- LMDB-backed URL filter for large crawl de-duplication.
- Article page detector and simple same-domain crawler.
- Task heartbeat helper for reporting script status to a
/tasksendpoint. zytoolscommand for a quick installation check.
Installation
pip install zytools-fs
Python 3.9 or newer is required.
For Bilibili DASH merging and Maoer audio conversion, install ffmpeg and make
sure it is available in PATH. Without it, Maoer downloads cannot produce the
final audio file.
Quick Check
zytools
Expected output:
zytools installed successfully.
Release Notes
Version 0.0.19 aligns Bilibili proxy handling with Maoer: page and API
requests use the supplied proxies dictionary, while video and audio media
downloads use a separate direct connection and ignore environment proxies.
Version 0.0.18 automatically honors the Bilibili p query parameter when
page is omitted. For example, a URL ending in ?p=8 downloads only P8, while
a URL without p keeps the existing download-all behavior.
Version 0.0.17 changes Maoer filepath to the complete destination filename,
for example ./downloads/1.wav. The extension selects the output format, and
the page title is no longer used to name the downloaded file.
Version 0.0.16 adds Maoer FM audio downloads and fixes several correctness
and security issues across the package:
- Preserve and concatenate every Bilibili
durlsegment instead of downloading only the first segment. - Parse Maoer HLS initialization and media byte ranges, reject incomplete encrypted segments, and avoid logging DRM keys.
- Restore persistent URL de-duplication for newly created empty filters.
- Verify HTTPS certificates during article crawling.
- Validate FTP and SFTP download sizes before replacing local files.
- Handle non-JSON task-service responses without unexpectedly raising when
raise_error=False.
Complete release details are available in CHANGELOG.md in the source
distribution.
FTP and SFTP Transfer
FTPClient and SFTPClient share the same high-level API for recursive
upload/download. Both clients support per-file progress logs, retries,
skip-by-size, concurrent directory transfers, and final transfer summaries.
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:
ftp.download("/remote/path", "./downloads")
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, # only use this for trusted private hosts
workers=4,
) as sftp:
download_result = sftp.download("/remote/path", "./downloads")
upload_result = sftp.upload("./reports", "/remote/reports")
Transfer Behavior
- Direct file paths transfer one file; directory paths are traversed recursively.
- Existing target files with the same size are skipped and counted as success.
- Uploads and downloads use fixed-position progress bars when
show_progress=True. Single-thread transfers use one dynamic line; concurrent transfers reuse up toworkersprogress lines instead of printing endless progress logs. - Each
download()orupload()call logs and returnstotal,success, anderror. workers>1enables concurrent file transfers for directories; each worker uses its own connection.
Example summary and return value:
upload summary: total=10 success=9 error=1
download summary: total=10 success=10 error=0
{"total": 10, "success": 9, "error": 1}
Useful options:
port: FTP defaults to21; SFTP defaults to22.encoding: FTP filename encoding, defaultutf-8.passive: FTP passive mode, defaultTrue(FTP only).key_filename: SSH private key path for SFTP authentication;~is expanded.allow_unknown_host: allow unknown SFTP host keys, defaultFalse.download_retriesandupload_retries: retry count.retry_wait_seconds: wait time between retries.workers: concurrent workers for directory downloads and uploads, default1.show_progress: show fixed-position progress bars and completion logs.
Bilibili Video Download
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)
Parameters:
quality:"max"for the highest available stream,"min"for the lowest. The actual quality depends on Bilibili account permissions and returned DASH streams.page: optional explicit override. When omitted, a URL containingp=8downloads only P8; a URL withoutpdownloads all pages. Explicit values can be"all", a single page such as"1", or a range/list such as"1,3-5".cookie: optional Bilibili cookies for videos that require login.proxies: optional proxies for Bilibili page/API requests; media stream downloads do not use it.force: whenFalse, existing final video files are skipped; whenTrue, they are overwritten.
Filename template fields:
{Title}: video title.{BV}: BV id.{Date}: publish date inYYYYMMDDformat.{Page}: page number.{Part}: same as page number.{Duration}: duration in seconds.{PartTitle}: page title.
Only download content that you own or are allowed to download.
Maoer FM Audio Download
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",
},
)
filepath is the complete output file path. Its extension selects the output
format and must be .wav, .m4a, .mp3, or .flac. The parent directory is
created automatically, and the Maoer page title is not used as the filename.
Proxies are used only for page, playlist, and DRM API requests. Audio segment
downloads bypass both supplied and environment proxies. The function returns
the absolute path of the completed audio file.
Only download content that you own or are allowed to download.
URL Filter
UrlFilter stores compact MD5 fingerprints in LMDB. Unlike a Bloom filter, it
does not intentionally produce false positives.
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("new url")
else:
print("seen before")
print(len(url_filter))
Batch import and export:
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 {added} urls")
UrlFilter.to_lmdb("url_seen.csv", file_path="url_seen_copy.lmdb")
Article Detection
Use check_response to request one URL and classify it as an article, other
HTML page, binary resource, or fetch error.
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"))
Return type values:
article: article page with extractedtitle,date,author, andtext.other: HTML page that does not look like an article.binary: image, PDF, JavaScript, CSS, video, archive, or other non-HTML file.fetch_error: request failed or returned a bad HTTP status.
Simple URL Crawler
UrlCrawler starts from one URL, follows links breadth-first, and yields article
items. It can optionally use UrlFilter to avoid saving the same article URL
across runs.
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()
Each yielded item has:
title: extracted article title.creat_date: extracted article publish date.content: extracted article text.url: final article URL.get_date: crawl batch date.
Task Heartbeat
Use update_task for a single heartbeat request, or TaskUpdater when a script
needs repeated updates with a minimum interval.
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)
The server is expected to accept POST /tasks with a JSON body containing
name, machine_id, script_path, enabled, and timeout_seconds.
Development
Build the package locally:
python -m build
Check the distribution metadata:
python -m twine check dist/*
Publish to PyPI:
python -m twine upload dist/*
License
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.19.tar.gz.
File metadata
- Download URL: zytools_fs-0.0.19.tar.gz
- Upload date:
- Size: 39.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.13.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d814d01652a43252794dcacd564f5700f0503ec1856192d989a82c5a0d66c993
|
|
| MD5 |
1906eba470d9d88896dcf9e3400adec3
|
|
| BLAKE2b-256 |
d161ea5f2c992b269df8e71864853c2e44db02f7aa9d0bfacc01931670f2b232
|
File details
Details for the file zytools_fs-0.0.19-py3-none-any.whl.
File metadata
- Download URL: zytools_fs-0.0.19-py3-none-any.whl
- Upload date:
- Size: 39.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 |
0dc60962210403a46b86042240075a15017d56b1b43be513d199a3998150c4e7
|
|
| MD5 |
d2d54c9188c3654cfa48694590dc37f9
|
|
| BLAKE2b-256 |
b5cb2f13d5ba5a5774f6af9b3513b2a2632aeec27f95c12577bf2eba50c633d4
|