Showing preview only (687K chars total). Download the full file or copy to clipboard to get everything.
Repository: JoeanAmier/XHS_Downloader
Branch: master
Commit: 8a8c1acb4b84
Files: 75
Total size: 615.4 KB
Directory structure:
gitextract_832couqt/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ ├── dependabot.yml
│ └── workflows/
│ ├── Close_Stale_Issues_and_PRs.yaml
│ ├── Delete_untagged_images.yml
│ ├── Manually_build_executable_programs.yml
│ ├── Manually_docker_image.yml
│ ├── Release_build_executable_program.yml
│ └── Release_docker_image.yml
├── .gitignore
├── .python-version
├── Dockerfile
├── LICENSE
├── README.md
├── README_EN.md
├── example.py
├── locale/
│ ├── README.md
│ ├── en_US/
│ │ └── LC_MESSAGES/
│ │ ├── xhs.mo
│ │ └── xhs.po
│ ├── generate_path.py
│ ├── po_to_mo.py
│ ├── xhs.pot
│ └── zh_CN/
│ └── LC_MESSAGES/
│ ├── xhs.mo
│ └── xhs.po
├── main.py
├── pyproject.toml
├── requirements.txt
├── source/
│ ├── CLI/
│ │ ├── __init__.py
│ │ └── main.py
│ ├── TUI/
│ │ ├── __init__.py
│ │ ├── about.py
│ │ ├── app.py
│ │ ├── index.py
│ │ ├── loading.py
│ │ ├── monitor.py
│ │ ├── progress.py
│ │ ├── record.py
│ │ ├── setting.py
│ │ └── update.py
│ ├── __init__.py
│ ├── application/
│ │ ├── __init__.py
│ │ ├── app.py
│ │ ├── download.py
│ │ ├── explore.py
│ │ ├── image.py
│ │ ├── request.py
│ │ ├── user_posted.py
│ │ └── video.py
│ ├── expansion/
│ │ ├── __init__.py
│ │ ├── browser.py
│ │ ├── cleaner.py
│ │ ├── converter.py
│ │ ├── error.py
│ │ ├── file_folder.py
│ │ ├── namespace.py
│ │ ├── pyi_rth_beartype.py
│ │ └── truncate.py
│ ├── module/
│ │ ├── __init__.py
│ │ ├── extend.py
│ │ ├── manager.py
│ │ ├── mapping.py
│ │ ├── model.py
│ │ ├── recorder.py
│ │ ├── script.py
│ │ ├── settings.py
│ │ ├── static.py
│ │ └── tools.py
│ └── translation/
│ ├── __init__.py
│ └── translate.py
└── static/
├── 20250619.js
├── Release_Notes.md
├── XHS-Downloader.icns
├── XHS-Downloader.js
├── XHS-Downloader.tcss
└── 自动滚动页面.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: 报告项目问题
title: '[功能异常] '
labels: ''
assignees: JoeanAmier
---
**问题描述**
清晰简洁地描述该错误是什么。
A clear and concise description of what the bug is.
**重现步骤**
重现该问题的步骤:
Steps to reproduce the behavior:
1. ...
2. ...
3. ...
**预期结果**
清晰简洁地描述您预期会发生的情况。
A clear and concise description of what you expected to happen.
**补充信息**
在此添加有关该问题的任何其他上下文信息,例如:操作系统、运行方式、配置文件、错误截图、运行日志等。
请注意:提供配置文件时,请删除 Cookie 内容,避免敏感数据泄露!
Add any other contextual information about the issue here, such as operating system, runtime mode, configuration files,
error screenshots, runtime logs, etc.
Please note: When providing configuration files, please delete cookie content to avoid sensitive data leakage!
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: 功能优化建议
title: '[优化建议] '
labels: ''
assignees: JoeanAmier
---
**功能请求**
清晰简洁地描述问题是什么。例如:当 [...] 时,我总是感到沮丧。
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**描述您希望的解决方案**
清晰简洁地描述您希望发生的情况。
A clear and concise description of what you want to happen.
**描述您考虑过的替代方案**
清晰简洁地描述您考虑过的任何替代解决方案或功能。
A clear and concise description of any alternative solutions or features you've considered.
**补充信息**
在此添加有关功能请求的任何其他上下文或截图。
Add any other context or screenshots about the feature request here.
================================================
FILE: .github/dependabot.yml
================================================
version: 2
registries:
pip_mirror:
type: python-index
url: https://mirrors.ustc.edu.cn/pypi/simple
updates:
- package-ecosystem: "uv"
directory: "/"
schedule:
interval: "cron"
cronjob: "0 0 * * 6"
timezone: "Asia/Shanghai"
target-branch: "develop"
registries:
- pip_mirror
================================================
FILE: .github/workflows/Close_Stale_Issues_and_PRs.yaml
================================================
name: "自动管理过时的问题和PR"
on:
schedule:
- cron: "0 0 * * 6"
workflow_dispatch:
permissions:
issues: write
pull-requests: write
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
stale-issue-message: |
⚠️ 此 Issue 已超过一定时间未活动,如果没有进一步更新,将在 14 天后关闭。
⚠️ This issue has been inactive for a certain period of time. If there are no further updates, it will be closed in 14 days.
close-issue-message: |
🔒 由于长时间未响应,此 Issue 已被自动关闭。如有需要,请重新打开或提交新 issue。
🔒 Due to prolonged inactivity, this issue has been automatically closed. If needed, please reopen it or submit a new issue.
stale-pr-message: |
⚠️ 此 PR 已超过一定时间未更新,请更新,否则将在 14 天后关闭。
⚠️ This PR has not been updated for a certain period of time. Please update it, otherwise it will be closed in 14 days.
close-pr-message: |
🔒 此 PR 已因无更新而自动关闭。如仍需合并,请重新打开或提交新 PR。
🔒 This PR has been automatically closed due to inactivity. If you still wish to merge it, please reopen it or submit a new PR.
days-before-stale: 28
days-before-close: 14
ascending: true
stale-issue-label: "未跟进问题(Stale)"
close-issue-label: "自动关闭(Close)"
stale-pr-label: "未跟进问题(Stale)"
close-pr-label: "自动关闭(Close)"
exempt-issue-labels: "功能异常(bug),文档补充(docs),功能优化(enhancement),适合新手(good first issue),"
exempt-pr-labels: "功能异常(bug),文档补充(docs),功能优化(enhancement),适合新手(good first issue),"
================================================
FILE: .github/workflows/Delete_untagged_images.yml
================================================
name: 删除 GHCR Untagged 镜像
on:
schedule:
- cron: "0 0 15 * *"
release:
types: [ published ]
workflow_dispatch:
jobs:
delete-untagged:
runs-on: ubuntu-latest
steps:
- name: Delete all containers from package without tags
uses: Chizkiyahu/delete-untagged-ghcr-action@v6
with:
token: ${{ secrets.PAT_TOKEN }}
repository_owner: ${{ github.repository_owner }}
repository: ${{ github.repository }}
package_name: "xhs-downloader"
untagged_only: true
owner_type: user
================================================
FILE: .github/workflows/Manually_build_executable_programs.yml
================================================
name: 构建可执行文件
on:
workflow_dispatch:
jobs:
build:
name: 构建于 ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ windows-latest, windows-11-arm, macos-15-intel, macos-latest ]
steps:
- name: 签出存储库
uses: actions/checkout@v4
- name: 设置 Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: 安装依赖项
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pyinstaller
- name: 构建 Win 可执行文件
if: runner.os == 'Windows'
run: |
echo "DATE=$(Get-Date -Format 'yyyyMMdd')" >> $env:GITHUB_ENV
pyinstaller --icon=./static/XHS-Downloader.ico --add-data "static:static" --add-data "locale:locale" --collect-all fastmcp --collect-all rich --runtime-hook ./source/expansion/pyi_rth_beartype.py main.py
shell: pwsh
- name: 构建 Mac 可执行文件
if: runner.os == 'macOS'
run: |
echo "DATE=$(date +'%Y%m%d')" >> $GITHUB_ENV
pyinstaller --icon=./static/XHS-Downloader.icns --add-data "static:static" --add-data "locale:locale" --collect-all fastmcp --collect-all rich --runtime-hook ./source/expansion/pyi_rth_beartype.py main.py
- name: 上传文件
uses: actions/upload-artifact@v4
with:
name: XHS-Downloader_${{ runner.os }}_${{ runner.arch }}_${{ env.DATE }}
path: dist/main/
================================================
FILE: .github/workflows/Manually_docker_image.yml
================================================
name: 构建并发布 Docker 镜像
on:
workflow_dispatch:
inputs:
is_beta:
type: boolean
required: true
description: "开发版"
default: true
custom_version:
type: string
required: false
description: "版本号"
default: ""
permissions:
contents: read
packages: write
attestations: write
id-token: write
env:
REGISTRY: ghcr.io
DOCKER_REPO: ${{ secrets.DOCKERHUB_USERNAME }}/xhs-downloader
GHCR_REPO: ghcr.io/${{ secrets.DOCKERHUB_USERNAME }}/xhs-downloader
jobs:
publish-docker:
runs-on: ubuntu-latest
steps:
- name: 拉取源码
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: 获取最新的发布标签
id: get-latest-release
run: |
if [ -z "${{ github.event.inputs.custom_version }}" ]; then
LATEST_TAG=$(curl -s \
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
https://api.github.com/repos/${{ github.repository }}/releases/latest \
| jq -r '.tag_name')
else
LATEST_TAG=${{ github.event.inputs.custom_version }}
fi
if [ -z "$LATEST_TAG" ]; then
exit 1
fi
echo "LATEST_TAG=$LATEST_TAG" >> $GITHUB_ENV
- name: 设置 QEMU
uses: docker/setup-qemu-action@v3
- name: 设置 Docker Buildx
uses: docker/setup-buildx-action@v3
- name: 生成标签
id: generate-tags
run: |
if [ "${{ inputs.is_beta }}" == "true" ]; then
LATEST_TAG="${LATEST_TAG%.*}.$(( ${LATEST_TAG##*.} + 1 ))"
echo "LATEST_TAG=$LATEST_TAG" >> $GITHUB_ENV
TAGS="${{ env.DOCKER_REPO }}:${LATEST_TAG}-dev,${{ env.GHCR_REPO }}:${LATEST_TAG}-dev"
else
TAGS="${{ env.DOCKER_REPO }}:${LATEST_TAG},${{ env.DOCKER_REPO }}:latest,${{ env.GHCR_REPO }}:${LATEST_TAG},${{ env.GHCR_REPO }}:latest"
fi
echo "TAGS=$TAGS" >> $GITHUB_ENV
- name: 登录到 Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: 登录到 GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: 构建和推送 Docker 镜像到 Docker Hub 和 GHCR
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
tags: ${{ env.TAGS }}
provenance: false
sbom: false
================================================
FILE: .github/workflows/Release_build_executable_program.yml
================================================
name: 自动构建并发布可执行文件
on:
release:
types: [ published ]
permissions:
contents: write
discussions: write
jobs:
build:
name: 构建于 ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ windows-latest, windows-11-arm, macos-15-intel, macos-latest ]
steps:
- name: 签出存储库
uses: actions/checkout@v4
- name: 设置 Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: 安装依赖项
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pyinstaller
- name: 构建 Win 可执行文件
if: runner.os == 'Windows'
run: |
pyinstaller --icon=./static/XHS-Downloader.ico --add-data "static:static" --add-data "locale:locale" --collect-all fastmcp --collect-all rich --runtime-hook ./source/expansion/pyi_rth_beartype.py main.py
shell: pwsh
- name: 构建 Mac 可执行文件
if: runner.os == 'macOS'
run: |
pyinstaller --icon=./static/XHS-Downloader.icns --add-data "static:static" --add-data "locale:locale" --collect-all fastmcp --collect-all rich --runtime-hook ./source/expansion/pyi_rth_beartype.py main.py
- name: 创建压缩包
run: |
7z a "XHS-Downloader_V${{ github.event.release.tag_name }}_${{ runner.os }}_${{ runner.arch }}.zip" ./dist/main/*
shell: bash
- name: 上传文件到 release
uses: softprops/action-gh-release@v2
with:
files: |
./XHS-Downloader_V*.zip
name: XHS-Downloader V${{ github.event.release.tag_name }}
body_path: ./static/Release_Notes.md
draft: ${{ github.event.release.draft }}
prerelease: ${{ github.event.release.prerelease }}
================================================
FILE: .github/workflows/Release_docker_image.yml
================================================
name: 自动构建并发布 Docker 镜像
on:
release:
types: [published]
permissions:
contents: read
packages: write
attestations: write
id-token: write
env:
REGISTRY: ghcr.io
DOCKER_REPO: ${{ secrets.DOCKERHUB_USERNAME }}/xhs-downloader
GHCR_REPO: ghcr.io/${{ secrets.DOCKERHUB_USERNAME }}/xhs-downloader
jobs:
publish-docker:
runs-on: ubuntu-latest
steps:
- name: 拉取源码
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: 设置 QEMU
uses: docker/setup-qemu-action@v3
- name: 设置 Docker Buildx
uses: docker/setup-buildx-action@v3
- name: 登录到 Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: 登录到 GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: 构建和推送 Docker 镜像到 Docker Hub 和 GHCR
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
tags: |
${{ env.DOCKER_REPO }}:${{ github.event.release.tag_name }}
${{ env.DOCKER_REPO }}:latest
${{ env.GHCR_REPO }}:${{ github.event.release.tag_name }}
${{ env.GHCR_REPO }}:latest
provenance: false
sbom: false
================================================
FILE: .gitignore
================================================
__pycache__/
*.pyc
/.venv/
/.ruff_cache/
/.idea/
/.run/
/Volume/
!/.github/
================================================
FILE: .python-version
================================================
3.12
================================================
FILE: Dockerfile
================================================
# ---- 阶段 1: 构建器 (Builder) ----
# 使用一个功能完整的镜像,它包含编译工具或可以轻松安装它们
FROM python:3.12-bullseye as builder
# 安装编译 uvloop 和 httptools 所需的系统依赖 (C编译器等)
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# 设置工作目录
WORKDIR /app
# 复制需求文件
COPY requirements.txt .
# 在这个具备编译环境的阶段安装所有 Python 依赖
# 安装到一个独立的目录 /install 中,以便后续复制
RUN pip install --no-cache-dir --prefix="/install" -r requirements.txt
# ---- 阶段 2: 最终镜像 (Final Image) ----
# 使用轻量级 slim 镜像作为最终的运行环境
FROM python:3.12-slim
# 设置工作目录
WORKDIR /app
# 添加元数据标签
LABEL name="XHS-Downloader" authors="JoeanAmier" repository="https://github.com/JoeanAmier/XHS-Downloader"
# 从构建器阶段,将已经安装好的依赖包复制到最终镜像的系统路径中
COPY --from=builder /install /usr/local
# 复制项目代码和相关文件
COPY locale /app/locale
COPY source /app/source
COPY static/XHS-Downloader.tcss /app/static/XHS-Downloader.tcss
COPY LICENSE /app/LICENSE
COPY main.py /app/main.py
# 暴露端口
EXPOSE 5556
# 创建挂载点
VOLUME /app/Volume
# 设置容器启动命令
CMD ["python", "main.py"]
================================================
FILE: LICENSE
================================================
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
================================================
FILE: README.md
================================================
<div align="center">
<img src="static/XHS-Downloader.png" alt="XHS-Downloader" height="256" width="256"><br>
<h1>XHS-Downloader</h1>
<p>简体中文 | <a href="README_EN.md">English</a></p>
<a href="https://trendshift.io/repositories/5435" target="_blank"><img src="https://trendshift.io/api/badge/repositories/5435" alt="JoeanAmier%2FXHS-Downloader | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
<br>
<img alt="GitHub" src="https://img.shields.io/github/license/JoeanAmier/XHS-Downloader?style=flat-square">
<img alt="GitHub forks" src="https://img.shields.io/github/forks/JoeanAmier/XHS-Downloader?style=flat-square&color=55efc4">
<img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/JoeanAmier/XHS-Downloader?style=flat-square&color=fda7df">
<img alt="GitHub code size in bytes" src="https://img.shields.io/github/languages/code-size/JoeanAmier/XHS-Downloader?style=flat-square&color=a29bfe">
<img alt="GitHub release (with filter)" src="https://img.shields.io/github/v/release/JoeanAmier/XHS-Downloader?style=flat-square&color=48dbfb">
<br>
<img alt="Static Badge" src="https://img.shields.io/badge/Python-3.12-b8e994?style=flat-square&logo=python&labelColor=3dc1d3">
<img alt="Static Badge" src="https://img.shields.io/badge/UserScript-ffec3d?style=flat-square&logo=tampermonkey&logoColor=%2300485B">
<img src="https://img.shields.io/badge/Sourcery-enabled-884898?style=flat-square&color=1890ff" alt="">
<img alt="Static Badge" src="https://img.shields.io/badge/Docker-badc58?style=flat-square&logo=docker">
<img alt="GitHub all releases" src="https://img.shields.io/github/downloads/JoeanAmier/XHS-Downloader/total?style=flat-square&color=ffdd59">
</div>
<br>
<p>🔥 <b>小红书链接提取/作品采集工具</b>:提取账号发布、收藏、点赞、专辑作品链接;提取搜索结果作品链接、用户链接;采集小红书作品信息;提取小红书作品下载地址;下载小红书作品文件!</p>
<p>🔥 “小红书”、“XiaoHongShu”、“RedNote” 含义相同,本项目统称为 “小红书”</p>
<h1>📑 项目功能</h1>
<details>
<summary>项目程序与用户脚本功能清单(点击展开)</summary>
<ul><b>程序功能</b>
<li>✅ 采集小红书作品信息</li>
<li>✅ 提取小红书作品下载地址</li>
<li>✅ 下载小红书作品文件</li>
<li>✅ 下载小红书 livePhoto 文件</li>
<li>✅ 自动跳过已下载的作品文件</li>
<li>✅ 作品文件完整性处理机制</li>
<li>✅ 自定义图文作品文件下载格式</li>
<li>✅ 持久化储存作品信息至文件</li>
<li>✅ 作品文件储存至单独文件夹</li>
<li>✅ 后台监听剪贴板下载作品</li>
<li>✅ 记录已下载作品 ID</li>
<li>✅ 支持命令行下载作品文件</li>
<li>☑️ 从浏览器读取 Cookie</li>
<li>✅ 自定义文件名称格式</li>
<li>✅ 支持 API 调用功能</li>
<li>✅ 支持 MCP 调用功能</li>
<li>✅ 支持文件断点续传下载</li>
<li>✅ 智能识别作品文件类型</li>
<li>✅ 支持设置作者备注</li>
<li>✅ 自动更新作者昵称</li>
</ul>
<ul><a href="#user-scripts"><b>脚本功能</b></a>
<li>✅ 下载小红书作品文件</li>
<li>✅ 提取推荐页面作品链接</li>
<li>✅ 提取账号发布作品链接</li>
<li>✅ 提取账号收藏作品链接</li>
<li>✅ 提取账号点赞作品链接</li>
<li>✅ 提取账号专辑作品链接</li>
<li>✅ 提取搜索结果作品链接</li>
<li>✅ 提取搜索结果用户链接</li>
</ul>
</details>
<h1>📸 程序截图</h1>
<p><a href="https://www.bilibili.com/video/BV1Fcb3zWEjt/">前往 bilibili 观看演示</a>;<a href="https://youtu.be/VIjDytHaopg">前往 YouTube 观看演示</a></p>
<img src="static/screenshot/程序运行截图CN1.png" alt="">
<hr>
<img src="static/screenshot/程序运行截图CN2.png" alt="">
<hr>
<img src="static/screenshot/程序运行截图CN3.png" alt="">
<h1>🔗 支持链接</h1>
<ul>
<li><code>https://www.xiaohongshu.com/explore/作品ID?xsec_token=XXX</code></li>
<li><code>https://www.xiaohongshu.com/discovery/item/作品ID?xsec_token=XXX</code></li>
<li><code>https://www.xiaohongshu.com/user/profile/作者ID/作品ID?xsec_token=XXX</code></li>
<li><code>https://xhslink.com/分享码</code></li>
<br/>
<p><b>支持单次输入多个作品链接,链接之间使用空格分隔;程序会自动提取有效链接,无需额外处理!</b></p>
</ul>
<h1>🪟 关于终端</h1>
<p>⭐ 推荐使用 <a href="https://learn.microsoft.com/zh-cn/windows/terminal/install">Windows 终端</a> (Windows 11 默认终端)运行程序以便获得最佳显示效果!</p>
<h1>🥣 使用方法</h1>
<p>如果仅需下载作品文件,建议选择 <b>程序运行</b> 或 <b>Docker 运行</b>;如果有其他需求,建议选择 <b>源码运行</b>!</p>
<p>⚠️ Cookie 配置为非强制项;如遇功能异常,建议配置或更新 Cookie 后再次尝试!</p>
<p>⚠️ 未设置 Cookie 时,视频作品只能下载低分辨率文件;建议配置 Cookie 以获取更高画质(无需登录账号)!</p>
<h2>🖱 程序运行</h2>
<p>⭐ Mac OS、Windows 10 及以上用户可前往 <a href="https://github.com/JoeanAmier/XHS-Downloader/releases/latest">Releases</a> 或者 <a href="https://github.com/JoeanAmier/XHS-Downloader/actions">Actions</a> 下载程序压缩包,解压后打开程序文件夹,双击运行 <code>main</code> 即可使用。</p>
<p>⭐ 本项目包含自动构建可执行文件的 GitHub Actions,使用者可以随时使用 GitHub Actions 将最新源码构建为可执行文件!</p>
<p>⭐ 自动构建可执行文件教程请查阅本文档的 <code>构建可执行文件指南</code> 部分;如果需要更加详细的图文教程,请 <a href="https://mp.weixin.qq.com/s/TorfoZKkf4-x8IBNLImNuw">查阅文章</a>!</p>
<p><strong>注意:由于 Mac OS 平台的可执行文件 <code>main</code> 未经过代码签名,首次运行时会受到系统安全限制。请先在终端执行 <code>xattr -cr 项目文件夹路径</code> 命令移除安全标记,执行一次后即可正常运行。</strong></p>
<p>若通过此方式使用程序,文件默认下载路径为:<code>.\_internal\Volume\Download</code>;配置文件路径为:<code>.\_internal\Volume\settings.json</code></p>
<h3>程序更新</h3>
<p><strong>方案一:</strong>下载并解压文件,将旧版本的 <code>_internal\Volume</code> 文件夹复制到新版本的 <code>_internal</code> 文件夹。</p>
<p><strong>方案二:</strong>下载并解压文件(不要运行程序),复制全部文件,直接覆盖旧版本文件。</p>
<h2>⌨️ 源码运行</h2>
<ol>
<li>安装 <code>≥3.12</code> 版本的 <a href="https://www.python.org/">Python</a> 解释器</li>
<li>下载最新的源码或 <a href="https://github.com/JoeanAmier/XHS-Downloader/releases/latest">Releases</a> 发布的源码至本地</li>
<ol><b>使用 pip 安装项目依赖</b>
<li>运行 <code>python -m venv venv</code> 命令创建虚拟环境(可选)</li>
<li>运行 <code>.\venv\Scripts\activate.ps1</code> 或者 <code>venv\Scripts\activate</code> 命令激活虚拟环境(可选)</li>
<li>运行 <code>pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt</code> 命令安装程序所需模块</li>
<li>运行 <code>python .\main.py</code> 或者 <code>python main.py</code> 命令启动 XHS-Downloader</li>
</ol>
<ol><b>使用 uv 安装项目依赖(推荐)</b>
<li>运行 <code>uv sync --no-dev</code> 命令同步环境依赖</li>
<li>运行 <code>uv run main.py</code> 命令启动 XHS-Downloader</li>
</ol>
</ol>
<h2>⌨️ Docker 运行</h2>
<ol>
<li>获取镜像</li>
<ul>
<li>方式一:使用 <code>Dockerfile</code> 文件构建镜像</li>
<li>方式二:使用 <code>docker pull joeanamier/xhs-downloader</code> 命令拉取镜像</li>
<li>方式三:使用 <code>docker pull ghcr.io/joeanamier/xhs-downloader</code> 命令拉取镜像</li>
</ul>
<li>创建容器</li>
<ul>
<li>TUI 模式:<code>docker run --name 容器名称(可选) -p 主机端口号:5556 -v xhs_downloader_volume:/app/Volume -it <镜像名称></code></li>
<li>API 模式:<code>docker run --name 容器名称(可选) -p 主机端口号:5556 -v xhs_downloader_volume:/app/Volume -it <镜像名称> python main.py api</code></li>
<li>MCP 模式:<code>docker run --name 容器名称(可选) -p 主机端口号:5556 -v xhs_downloader_volume:/app/Volume -it <镜像名称> python main.py mcp</code></li>
<br><b>注意:</b>此处的 <code><镜像名称></code> 需与您在第一步中使用的镜像名称保持一致(<code>joeanamier/xhs-downloader</code> 或 <code>ghcr.io/joeanamier/xhs-downloader</code>)
</ul>
<li>运行容器
<ul>
<li>启动容器:<code>docker start -i 容器名称/容器 ID</code></li>
<li>重启容器:<code>docker restart -i 容器名称/容器 ID</code></li>
</ul>
</li>
</ol>
<p>Docker 运行项目时不支持 <b>命令行调用模式</b>,无法使用 <b>读取剪贴板</b> 与 <b>监听剪贴板</b> 功能,可以正常粘贴内容,其他功能如有异常请反馈!</p>
<h1>🛠 命令行模式</h1>
<p>项目支持命令行运行模式,若想要下载图文作品的部分图片,可以使用此模式设置需要下载的图片序号!</p>
<p><strong>注意:</strong>未设置 <code>--index</code> 参数时,支持传入多个作品链接,全部链接需要使用引号包围,链接之间使用空格分隔;已设置 <code>--index</code> 参数时,不支持传入多个作品链接,即使传入多个作品链接,程序仅处理首个作品链接!</p>
<p><code>bool</code> 类型参数支持使用 <code>true</code>、<code>false</code>、<code>1</code>、<code>0</code>、<code>yes</code>、<code>no</code>、<code>on</code> 或 <code>off</code>(不区分大小写)来设置。</p>
<h2>从浏览器读取 Cookie</h2>
<p>该功能已失效,请参考 <a href="#cookie">获取 Cookie</a> 教程!</p>
<p><del>可以使用命令行实现 <b>从浏览器读取 Cookie 并写入配置文件!</b></del></p>
<p><del>命令示例:<code>python .\main.py --browser_cookie Chrome --update_settings</code></del></p>
<p><del>兼容性提醒:此功能依赖的第三方模块已长期未更新,可能无法正常支持最新浏览器版本。若功能出现异常,请尝试手动获取 Cookie!</del></p>
<hr>
<img src="static/screenshot/命令行模式截图CN1.png" alt="">
<hr>
<img src="static/screenshot/命令行模式截图CN2.png" alt="">
<h1>🖥 服务器模式</h1>
<p>服务器模式包含 API 模式和 MCP 模式!</p>
<h2>API 模式</h2>
<p><b>启动:</b>运行命令:<code>python .\main.py api</code></p>
<p><b>关闭:</b>按下 <code>Ctrl</code> + <code>C</code> 关闭服务器</p>
<p>访问 <code>http://127.0.0.1:5556/docs</code> 或者 <code>http://127.0.0.1:5556/redoc</code>;你会看到自动生成的交互式 API 文档!</p>
<p><b>请求接口:</b><code>/xhs/detail</code></p>
<p><b>请求方法:</b><code>POST</code></p>
<p><b>请求格式:</b><code>JSON</code></p>
<p><b>请求参数:</b></p>
<table>
<thead>
<tr>
<th align="center">参数</th>
<th align="center">类型</th>
<th align="center">含义</th>
<th align="center">默认值</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">url</td>
<td align="center">str</td>
<td align="center">小红书作品链接,自动提取,不支持多链接;必需参数</td>
<td align="center">无</td>
</tr>
<tr>
<td align="center">download</td>
<td align="center">bool</td>
<td align="center">是否下载作品文件;设置为 <code>true</code> 将会耗费更多时间;可选参数</td>
<td align="center">false</td>
</tr>
<tr>
<td align="center">index</td>
<td align="center">list[int]</td>
<td align="center">下载指定序号的图片文件,仅对图文作品生效;<code>download</code> 参数设置为 <code>false</code> 时不生效;可选参数</td>
<td align="center">null</td>
</tr>
<tr>
<td align="center">cookie</td>
<td align="center">str</td>
<td align="center">请求数据时使用的 Cookie;可选参数</td>
<td align="center">配置文件 cookie 参数</td>
</tr>
<tr>
<td align="center">proxy</td>
<td align="center">str</td>
<td align="center">请求数据时使用的代理;可选参数</td>
<td align="center">配置文件 proxy 参数</td>
</tr>
<tr>
<td align="center">skip</td>
<td align="center">bool</td>
<td align="center">是否跳过存在下载记录的作品;设置为 <code>true</code> 将不会返回存在下载记录的作品数据;可选参数</td>
<td align="center">false</td>
</tr>
</tbody>
</table>
<p><b>代码示例:</b></p>
<pre>
async def example_api():
"""通过 API 设置参数,适合二次开发"""
server = "http://127.0.0.1:5556/xhs/detail"
data = {
"url": "", # 必需参数
"download": True,
"index": [
3,
6,
9,
],
"proxy": "http://127.0.0.1:10808",
}
response = post(server, json=data, timeout=10)
print(response.json())
</pre>
<h2>MCP 模式</h2>
<p><b>启动:</b>运行命令:<code>python .\main.py mcp</code></p>
<p><b>关闭:</b>按下 <code>Ctrl</code> + <code>C</code> 关闭服务器</p>
<h3>MCP 配置示例</h3>
[//]: # (<h4>STDIO</h4>)
<h4>Streamable HTTP</h4>
<p><b>MCP URL:</b><code>http://127.0.0.1:5556/mcp/</code></p>
<img src="static/screenshot/MCP配置示例.png" alt="MCP配置示例">
<h3>MCP 调用示例</h3>
<details>
<summary>MCP 功能及调用示例(点击展开)</summary>
<h4><strong>获取小红书作品信息</strong></h4>
<img src="static/screenshot/MCP获取数据.png" alt="MCP获取数据">
<hr>
<h4><strong>下载小红书作品文件</strong></h4>
<p>下载图文作品时可以指定需要下载的图片序号;默认不返回作品信息,如需返回作品信息,请在对话时明确表述。</p>
<img src="static/screenshot/MCP下载文件1.png" alt="MCP下载文件">
<hr>
<img src="static/screenshot/MCP下载文件2.png" alt="MCP下载文件">
</details>
<h1>📜 其他说明</h1>
<ul>
<li>由于作品链接携带日期信息,使用先前日期获取的作品链接可能会被风控,建议下载作品文件时使用最新获取的作品链接</li>
<li><del>Windows 系统需要以管理员身份运行程序才能读取 Chromium、Chrome、Edge 浏览器 Cookie</del></li>
<li>如果开启保存作品数据至文件功能,作品数据默认储存至 <code>./Volume/Download/ExploreData.db</code> 文件</li>
<li>程序下载记录数据储存至 <code>./Volume/ExploreID.db</code> 文件</li>
<li>为了避免请求频率过高对平台服务器造成影响,本项目内置请求延时机制</li>
</ul>
<h1 id="user-scripts">🕹 用户脚本</h1>
<p>如果您的浏览器安装了 <a href="https://www.tampermonkey.net/">Tampermonkey</a> 浏览器扩展程序,可以使用用户脚本体验项目功能!</p>
<p>用户脚本链接(右键单击复制链接):<a href="https://raw.githubusercontent.com/JoeanAmier/XHS-Downloader/refs/heads/master/static/XHS-Downloader.js">master 分支</a>、<a href="https://raw.githubusercontent.com/JoeanAmier/XHS-Downloader/refs/heads/develop/static/XHS-Downloader.js">develop 分支</a></p>
<img src="static/screenshot/脚本安装教程.png" alt="">
<hr>
<details>
<summary>查看 Tampermonkey 用户脚本截图(点击展开)</summary>
<img src="static/screenshot/用户脚本截图1.png" alt="">
<hr>
<img src="static/screenshot/用户脚本截图2.png" alt="">
<hr>
<img src="static/screenshot/用户脚本截图3.png" alt="">
<hr>
<img src="static/screenshot/用户脚本截图4.png" alt="">
</details>
<p>提示:使用 XHS-Downloader 用户脚本批量提取作品链接,搭配 XHS-Downloader 程序可以实现批量下载作品文件!</p>
<p><b>修改用户脚本语言</b></p>
<img src="static/screenshot/脚本切换语言.png" alt="切换语言">
<h2>🌏 连接服务器</h2>
<p>⭐ 本项目支持通过浏览器用户脚本与主程序联动,实现一键推送下载任务。</p>
<ul><b>功能说明:</b>
<li>在项目程序的配置文件中,需要将 <code>script_server</code> 参数设置为 <code>true</code></li>
<li>保持项目程序在后台运行,它将作为服务器,接收用户脚本的指令(TUI、MCP 和 API 模式均支持)</li>
<li>当您在浏览器中访问作品页面时,点击用户脚本菜单中的 <code>推送下载任务</code> 选项</li>
<li>用户脚本会将下载任务发送给项目程序,由项目程序负责处理和下载文件</li>
</ul>
<h2>📜 脚本说明</h2>
<ul>
<li>下载小红书作品文件时,脚本需要花费时间处理文件,请等待片刻,请勿多次点击下载按钮</li>
<li>提取账号发布、收藏、点赞、专辑作品链接时,脚本可以自动滚动页面直至加载全部作品</li>
<li>提取推荐作品链接、搜索作品、用户链接时,脚本可以自动滚动指定次数加载更多内容,默认滚动次数:50 次</li>
<li>自动滚动页面功能默认关闭;用户可以自由开启,并修改滚动页面次数,修改后立即生效</li>
<li>如果未开启自动滚动页面功能,用户需要手动滚动页面以便加载更多内容后再进行其他操作</li>
<li>支持作品文件打包下载;该功能默认开启,多个文件的作品将会以压缩包格式下载</li>
<li>向服务器推送下载任务时,文件格式、名称规则等设置以服务器配置文件中的设置为准</li>
<li>使用全局代理工具可能会导致脚本下载文件失败,如有异常,请尝试关闭代理工具,必要时向作者反馈</li>
<li>XHS-Downloader 用户脚本仅实现可见即可得的数据采集功能,无任何收费功能和破解功能</li>
</ul>
<p><strong>自动滚动页面功能代码已重构,该功能默认关闭!启用该功能可能会被小红书检测为自动化操作,从而导致账号受到风控或封禁风险!</strong></p>
<h1>💻 二次开发</h1>
<p>如果有其他需求,可以根据 <code>example.py</code> 的注释提示进行代码调用或修改!</p>
<pre>
async def example():
"""通过代码设置参数,适合二次开发"""
# 示例链接
demo_link = "https://www.xiaohongshu.com/explore/XXX?xsec_token=XXX"
# 实例对象
work_path = "D:\\" # 作品数据/文件保存根路径,默认值:项目根路径
folder_name = "Download" # 作品文件储存文件夹名称(自动创建),默认值:Download
name_format = "作品标题 作品描述"
user_agent = "" # User-Agent
cookie = "" # 小红书网页版 Cookie,无需登录,可选参数,登录状态对数据采集有影响
proxy = None # 网络代理
timeout = 5 # 请求数据超时限制,单位:秒,默认值:10
chunk = 1024 * 1024 * 10 # 下载文件时,每次从服务器获取的数据块大小,单位:字节
max_retry = 2 # 请求数据失败时,重试的最大次数,单位:秒,默认值:5
record_data = False # 是否保存作品数据至文件
image_format = "WEBP" # 图文作品文件下载格式,支持:AUTO、PNG、WEBP、JPEG、HEIC
folder_mode = False # 是否将每个作品的文件储存至单独的文件夹
image_download = True # 图文、图集作品文件下载开关
video_download = True # 视频作品文件下载开关
live_download = False # 图文动图文件下载开关
download_record = True # 是否记录下载成功的作品 ID
language = "zh_CN" # 设置程序提示语言
author_archive = True # 是否将每个作者的作品存至单独的文件夹
write_mtime = True # 是否将作品文件的 修改时间 修改为作品的发布时间
# read_cookie = None # 读取浏览器 Cookie,支持设置浏览器名称(字符串)或者浏览器序号(整数),设置为 None 代表不读取
# async with XHS() as xhs:
# pass # 使用默认参数
async with XHS(
work_path=work_path,
folder_name=folder_name,
name_format=name_format,
user_agent=user_agent,
cookie=cookie,
proxy=proxy,
timeout=timeout,
chunk=chunk,
max_retry=max_retry,
record_data=record_data,
image_format=image_format,
folder_mode=folder_mode,
image_download=image_download,
video_download=video_download,
live_download=live_download,
download_record=download_record,
language=language,
# read_cookie=read_cookie,
author_archive=author_archive,
write_mtime=write_mtime,
) as xhs: # 使用自定义参数
download = True # 是否下载作品文件,默认值:False
# 返回作品详细信息,包括下载地址
# 获取数据失败时返回空字典
print(
await xhs.extract(
demo_link,
download,
index=[
1,
2,
5,
],
)
)
</pre>
<h1>📋 读取剪贴板</h1>
<p>项目使用 <code>pyperclip</code> 实现读取剪贴板功能,该模块在不同的系统上会有差异。</p>
<p>在 Windows 上,不需要额外的模块。</p>
<p>在 Mac 上,该模块使用 pbcopy 和 pbpaste 命令,这些命令应该随操作系统一起提供。</p>
<p>在 Linux 上,该模块使用 xclip 或 xsel 命令,这些命令应该随操作系统一起提供。否则,请运行 "sudo apt-get install xclip" 或 "sudo apt-get install xsel"(注意:xsel 似乎并不总是有效)</p>
<p>在其他 Linux 系统上,你需要安装 qtpy 或 PyQT5 模块。</p>
<h1>⚙️ 配置文件</h1>
<p>项目根目录下的 <code>./Volume/settings.json</code> 文件,首次运行自动生成,可以自定义程序运行参数;如果设置了无效的参数值,程序将会使用参数默认值!</p>
<p>如果您在程序界面修改配置时无法正常交互,可以直接编辑配置文件;如果您的计算机没有合适的程序编辑 JSON 文件,建议使用 <a href="https://www.toolhelper.cn/JSON/JSONFormat">在线工具</a> 编辑配置文件内容,修改后需要重启软件才能生效。</p>
<table>
<thead>
<tr>
<th align="center">参数</th>
<th align="center">类型</th>
<th align="center">含义</th>
<th align="center">默认值</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">mapping_data</td>
<td align="center">str: str</td>
<td align="center"><sup><a href="#author_archive">#</a></sup>作者别名映射表,格式:<code>作者ID: 作者别名</code></td>
<td align="center">无</td>
</tr>
<tr>
<td align="center">work_path</td>
<td align="center">str</td>
<td align="center">作品数据 / 文件保存根路径</td>
<td align="center">项目根路径/Volume</td>
</tr>
<tr>
<td align="center">folder_name</td>
<td align="center">str</td>
<td align="center">作品文件储存文件夹名称</td>
<td align="center">Download</td>
</tr>
<tr>
<td align="center">name_format</td>
<td align="center">str</td>
<td align="center">作品文件名称格式,字段之间使用空格分隔,支持字段:<code>收藏数量</code>、<code>评论数量</code>、<code>分享数量</code>、<code>点赞数量</code>、<code>作品标签</code>、<code>作品ID</code>、<code>作品标题</code>、<code>作品描述</code>、<code>作品类型</code>、<code>发布时间</code>、<code>最后更新时间</code>、<code>作者昵称</code>、<code>作者ID</code></td>
<td align="center"><code>发布时间 作者昵称 作品标题</code></td>
</tr>
<tr>
<td align="center">user_agent</td>
<td align="center">str</td>
<td align="center">浏览器 User Agent</td>
<td align="center">内置 Chrome User Agent</td>
</tr>
<tr>
<td align="center">cookie</td>
<td align="center">str</td>
<td align="center">小红书网页版 Cookie,<b>无需登录,非必需参数!</b></td>
<td align="center">无</td>
</tr>
<tr>
<td align="center">proxy</td>
<td align="center">str</td>
<td align="center">设置程序代理</td>
<td align="center">null</td>
</tr>
<tr>
<td align="center">timeout</td>
<td align="center">int</td>
<td align="center">请求数据超时限制,单位:秒</td>
<td align="center">10</td>
</tr>
<tr>
<td align="center">chunk</td>
<td align="center">int</td>
<td align="center">下载文件时,每次从服务器获取的数据块大小,单位:字节</td>
<td align="center">2097152(2 MB)</td>
</tr>
<tr>
<td align="center">max_retry</td>
<td align="center">int</td>
<td align="center">请求数据失败时,重试的最大次数,单位:秒</td>
<td align="center">5</td>
</tr>
<tr>
<td align="center">record_data</td>
<td align="center">bool</td>
<td align="center">是否保存作品数据至文件,保存格式:<code>SQLite</code></td>
<td align="center">false</td>
</tr>
<tr>
<td align="center">image_format</td>
<td align="center">str</td>
<td align="center">图文作品文件下载格式,支持:<code>AUTO</code>、<code>PNG</code>、<code>WEBP</code>、<code>JPEG</code>、<code>HEIC</code><br><strong>部分作品没有 <code>HEIC</code> 格式的文件,此时下载的文件可能为 <code>WEBP</code> 格式!</strong><br><strong>设置为 <code>AUTO</code> 时表示动态格式,实际格式取决于服务器响应数据!</strong></td>
<td align="center">JPEG</td>
</tr>
<tr>
<td align="center">image_download</td>
<td align="center">bool</td>
<td align="center">图文、图集作品文件下载开关</td>
<td align="center">true</td>
</tr>
<tr>
<td align="center">video_download</td>
<td align="center">bool</td>
<td align="center">视频作品文件下载开关</td>
<td align="center">true</td>
</tr>
<tr>
<td align="center">live_download</td>
<td align="center">bool</td>
<td align="center">图文动图文件下载开关</td>
<td align="center">false</td>
</tr>
<tr>
<td align="center">video_preference</td>
<td align="center">str</td>
<td align="center">视频作品文件下载偏好;含义:<code>resolution</code>:分辨率优先;<code>bitrate</code>:码率优先;<code>size</code>:文件大小优先</td>
<td align="center">resolution</td>
</tr>
<tr>
<td align="center">folder_mode</td>
<td align="center">bool</td>
<td align="center">是否将每个作品的文件储存至单独的文件夹;文件夹名称与文件名称保持一致</td>
<td align="center">false</td>
</tr>
<tr>
<td align="center">download_record</td>
<td align="center">bool</td>
<td align="center">是否记录下载成功的作品 ID,如果开启,程序将会自动跳过下载存在记录的作品</td>
<td align="center">true</td>
</tr>
<tr>
<td align="center">author_archive</td>
<td align="center">bool</td>
<td align="center"><sup><a href="#author_archive">#</a></sup>是否将每个作者的作品储存至单独的文件夹;文件夹名称为 <code>作者ID_作者昵称</code></td>
<td align="center">false</td>
</tr>
<tr>
<td align="center">write_mtime</td>
<td align="center">bool</td>
<td align="center">是否将作品文件的 <code>修改时间</code> 属性修改为作品的发布时间</td>
<td align="center">false</td>
</tr>
<tr>
<td align="center">language</td>
<td align="center">str</td>
<td align="center">设置程序语言,目前支持:<code>zh_CN</code>、<code>en_US</code></td>
<td align="center">zh_CN</td>
</tr>
<tr>
<td align="center">script_server</td>
<td align="center">bool</td>
<td align="center">是否开启用户脚本服务器,用于接收浏览器用户脚本的下载任务(TUI、MCP 和 API 模式生效)</td>
<td align="center">false</td>
</tr>
</tbody>
</table>
<hr>
<div id="author_archive">
<p>如果 <code>author_archive</code> 参数设置为 <code>true</code>,程序会把每个作者的作品储存至单独的文件夹;当作者的昵称发生变化时,程序会自动更新已下载作品文件名称中的作者昵称部分!</p>
<p>除此之外,你还可以通过设置 <code>mapping_data</code> 参数为某个作者设置别名;如果对某个作者设置了别名,程序会使用你设置的作者别名去替代作者昵称!</p>
</div>
<hr>
<p><b>其他说明:<code>user_agent</code>参数获取示例;强烈建议根据实际浏览器信息进行设置!</b></p>
<img src="static/screenshot/请求头示例图.png" alt="">
<h1 id="cookie">🌐 Cookie</h1>
<ol>
<li>打开浏览器(可选无痕模式启动),访问 <code>https://www.xiaohongshu.com/explore</code></li>
<li>登录小红书账号(可跳过)</li>
<li>按下 <code>F12</code> 打开开发人员工具</li>
<li>选择 <code>网络</code> 选项卡</li>
<li>勾选 <code>保留日志</code></li>
<li>在 <code>过滤</code> 输入框输入 <code>cookie-name:web_session</code></li>
<li>选择 <code>Fetch/XHR</code> 筛选器</li>
<li>点击小红书页面任意作品</li>
<li>在 <code>网络</code> 选项卡选择任意数据包(如果无数据包,重复步骤7)</li>
<li>全选复制 Cookie 写入程序或配置文件</li>
</ol>
<br>
<img src="static/screenshot/获取Cookie示意图.png" alt="">
<h1>🗳 下载记录</h1>
<p>XHS-Downloader 会将下载过的作品 ID 储存至数据库,当重复下载相同的作品时,XHS-Downloader 会自动跳过该作品的文件下载(即使作品文件不存在),如果想要重新下载作品文件,请先删除数据库中对应的作品 ID,再使用 XHS-Downloader 下载作品文件!</p>
<p>该功能默认开启,如果关闭该功能,XHS-Downloader 会检查文件是否存在,若文件存在则跳过下载!</p>
<h2>构建可执行文件指南</h2>
<details>
<summary><b>构建可执行文件指南(点击展开)</b></summary>
本指南将引导您通过 Fork 本仓库并执行 GitHub Actions 自动完成基于最新源码的程序构建和打包!
---
## 使用步骤
### 1. Fork 本仓库
1. 点击项目仓库右上角的 **Fork** 按钮,将本仓库 Fork 到您的个人 GitHub 账户中
2. 您的 Fork 仓库地址将类似于:`https://github.com/your-username/this-repo`
---
### 2. 启用 GitHub Actions
1. 前往您 Fork 的仓库页面
2. 点击顶部的 **Settings** 选项卡
3. 点击右侧的 **Actions** 选项卡
4. 点击 **General** 选项
5. 在 **Actions permissions** 下,选择 **Allow all actions and reusable workflows** 选项,点击 **Save** 按钮
---
### 3. 手动触发打包流程
1. 在您 Fork 的仓库中,点击顶部的 **Actions** 选项卡
2. 找到名为 **构建可执行文件** 的工作流
3. 点击右侧的 **Run workflow** 按钮:
- 选择 **master** 或者 **develop** 分支
- 点击 **Run workflow**
---
### 4. 查看打包进度
1. 在 **Actions** 页面中,您可以看到触发的工作流运行记录
2. 点击运行记录,查看详细的日志以了解打包进度和状态
---
### 5. 下载打包结果
1. 打包完成后,进入对应的运行记录页面
2. 在页面底部的 **Artifacts** 部分,您将看到打包的结果文件
3. 点击下载并保存到本地,即可获得打包好的程序
---
## 注意事项
1. **资源使用**:
- Actions 的运行环境由 GitHub 免费提供,普通用户每月有一定的免费使用额度(2000 分钟)
2. **代码修改**:
- 您可以自由修改 Fork 仓库中的代码以定制程序打包流程
- 修改后重新触发打包流程,您将得到自定义的构建版本
3. **与主仓库保持同步**:
- 如果主仓库更新了代码或工作流,建议您定期同步 Fork 仓库以获取最新功能和修复
---
## Actions 常见问题
### Q1: 为什么我无法触发工作流?
A: 请确认您已按照步骤 **启用 Actions**,否则 GitHub 会禁止运行工作流
### Q2: 打包流程失败怎么办?
A:
- 检查运行日志,了解失败原因
- 确保代码没有语法错误或依赖问题
- 如果问题仍未解决,可以在本仓库的 [Issues 页面](https://github.com/JoeanAmier/XHS-Downloader/issues) 提出问题
### Q3: 我可以直接使用主仓库的 Actions 吗?
A: 由于权限限制,您无法直接触发主仓库的 Actions。请通过 Fork 仓库的方式执行打包流程
</details>
<h1>⭐ Star 趋势</h1>
<p>
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=JoeanAmier/XHS-Downloader&type=Timeline"/>
</p>
<h1>♥️ 支持项目</h1>
<p>如果 <b>XHS-Downloader</b> 对您有帮助,请考虑为它点个 <b>Star</b> ⭐,感谢您的支持!</p>
<table>
<thead>
<tr>
<th align="center">微信(WeChat)</th>
<th align="center">支付宝(Alipay)</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><img src="./static/微信赞助二维码.png" alt="微信赞助二维码" height="200" width="200"></td>
<td align="center"><img src="./static/支付宝赞助二维码.png" alt="支付宝赞助二维码" height="200" width="200"></td>
</tr>
</tbody>
</table>
<p>如果您愿意,可以考虑提供资助为 <b>XHS-Downloader</b> 提供额外的支持!</p>
<h1>🌟 贡献指南</h1>
<p><strong>欢迎对本项目做出贡献!为了保持代码库的整洁、高效和易于维护,请仔细阅读以下指南,以确保您的贡献能够顺利被接受和整合。</strong></p>
<ul>
<li>在开始开发前,请从 <code>develop</code> 分支拉取最新的代码,以此为基础进行修改;这有助于避免合并冲突并保证您的改动基于最新的项目状态。</li>
<li>如果您的更改涉及多个不相关的功能或问题,请将它们分成多个独立的提交或拉取请求。</li>
<li>每个拉取请求应尽可能专注于单一功能或修复,以便于代码审查和测试。</li>
<li>遵循现有的代码风格;请确保您的代码与项目中已有的代码风格保持一致;建议使用 Ruff 工具保持代码格式规范。</li>
<li>编写可读性强的代码;添加适当的注释帮助他人理解您的意图。</li>
<li>每个提交都应该包含一个清晰、简洁的提交信息,以描述所做的更改。提交信息应遵循以下格式:<code><类型>: <简短描述></code></li>
<li>当您准备提交拉取请求时,请优先将它们提交到 <code>develop</code> 分支;这是为了给维护者一个缓冲区,在最终合并到 <code>master</code>
分支之前进行额外的测试和审查。</li>
<li>建议在开发前或遇到疑问时与作者沟通,确保开发方向一致,避免重复劳动或无效提交。</li>
</ul>
<p><strong>参考资料:</strong></p>
<ul>
<li><a href="https://www.contributor-covenant.org/zh-cn/version/2/1/code_of_conduct/">贡献者公约</a></li>
<li><a href="https://opensource.guide/zh-hans/how-to-contribute/">如何为开源做贡献</a></li>
</ul>
<h1>✉️ 联系作者</h1>
<ul>
<li>作者邮箱:yonglelolu@foxmail.com</li>
<li>作者微信: Downloader_Tools</li>
<li>微信公众号: Downloader Tools</li>
<li><b>Discord 社区</b>: <a href="https://discord.com/invite/ZYtmgKud9Y">点击加入社区</a></li>
<li>QQ 群聊: <a href="https://github.com/JoeanAmier/XHS-Downloader/blob/master/static/QQ%E7%BE%A4%E8%81%8A%E4%BA%8C%E7%BB%B4%E7%A0%81.png">扫码加入群聊</a></li>
</ul>
<p><b>说明:</b>QQ 群聊仅限于讨论项目使用问题,严禁发布任何广告,严禁讨论任何账号交易、账号流量、流量变现、灰色产业等相关的内容!</p>
<p>✨ <b>作者的其他开源项目:</b></p>
<ul>
<li><b>DouK-Downloader(抖音、TikTok)</b>:<a href="https://github.com/JoeanAmier/TikTokDownloader">https://github.com/JoeanAmier/TikTokDownloader</a></li>
<li><b>KS-Downloader(快手、KuaiShou)</b>:<a href="https://github.com/JoeanAmier/KS-Downloader">https://github.com/JoeanAmier/KS-Downloader</a></li>
</ul>
# 💰 项目赞助
## DartNode
[](https://dartnode.com "Powered by DartNode - Free VPS for Open Source")
***
## ZMTO
<a href="https://www.zmto.com/"><img src="https://console.zmto.com/templates/2019/dist/images/logo_dark.svg" alt="ZMTO"></a>
<p><a href="https://www.zmto.com/">ZMTO</a>:一家专业的云基础设施提供商,以可靠的尖端技术与专业支持,提供高效的解决方案,并为符合条件的开源项目提供企业级VPS基础设施,支持开源生态系统的可持续发展与创新。</p>
<h1>⚠️ 免责声明</h1>
<ol>
<li>使用者对本项目的使用由使用者自行决定,并自行承担风险。作者对使用者使用本项目所产生的任何损失、责任、或风险概不负责。</li>
<li>本项目的作者提供的代码和功能是基于现有知识和技术的开发成果。作者按现有技术水平努力确保代码的正确性和安全性,但不保证代码完全没有错误或缺陷。</li>
<li>本项目依赖的所有第三方库、插件或服务各自遵循其原始开源或商业许可,使用者需自行查阅并遵守相应协议,作者不对第三方组件的稳定性、安全性及合规性承担任何责任。</li>
<li>使用者在使用本项目时必须严格遵守 <a href="https://github.com/JoeanAmier/XHS-Downloader/blob/master/LICENSE">GNU
General Public License v3.0</a> 的要求,并在适当的地方注明使用了 <a
href="https://github.com/JoeanAmier/XHS-Downloader/blob/master/LICENSE">GNU General Public License
v3.0</a> 的代码。
</li>
<li>使用者在使用本项目的代码和功能时,必须自行研究相关法律法规,并确保其使用行为合法合规。任何因违反法律法规而导致的法律责任和风险,均由使用者自行承担。</li>
<li>使用者不得使用本工具从事任何侵犯知识产权的行为,包括但不限于未经授权下载、传播受版权保护的内容,开发者不参与、不支持、不认可任何非法内容的获取或分发。</li>
<li>本项目不对使用者涉及的数据收集、存储、传输等处理活动的合规性承担责任。使用者应自行遵守相关法律法规,确保处理行为合法正当;因违规操作导致的法律责任由使用者自行承担。</li>
<li>使用者在任何情况下均不得将本项目的作者、贡献者或其他相关方与使用者的使用行为联系起来,或要求其对使用者使用本项目所产生的任何损失或损害负责。</li>
<li>本项目的作者不会提供 XHS-Downloader 项目的付费版本,也不会提供与 XHS-Downloader 项目相关的任何商业服务。</li>
<li>基于本项目进行的任何二次开发、修改或编译的程序与原创作者无关,原创作者不承担与二次开发行为或其结果相关的任何责任,使用者应自行对因二次开发可能带来的各种情况负全部责任。</li>
<li>本项目不授予使用者任何专利许可;若使用本项目导致专利纠纷或侵权,使用者自行承担全部风险和责任。未经作者或权利人书面授权,不得使用本项目进行任何商业宣传、推广或再授权。</li>
<li>作者保留随时终止向任何违反本声明的使用者提供服务的权利,并可能要求其销毁已获取的代码及衍生作品。</li>
<li>作者保留在不另行通知的情况下更新本声明的权利,使用者持续使用即视为接受修订后的条款。</li>
</ol>
<b>在使用本项目的代码和功能之前,请您认真考虑并接受以上免责声明。如果您对上述声明有任何疑问或不同意,请不要使用本项目的代码和功能。如果您使用了本项目的代码和功能,则视为您已完全理解并接受上述免责声明,并自愿承担使用本项目的一切风险和后果。</b>
# 💡 项目参考
* https://github.com/encode/httpx/
* https://github.com/tiangolo/fastapi
* https://github.com/textualize/textual/
* https://github.com/pyinstaller/pyinstaller
* https://github.com/zbowling/beartype-pyinstaller-repro
* https://github.com/jlowin/fastmcp
* https://github.com/omnilib/aiosqlite
* https://github.com/carpedm20/emoji/
* https://github.com/asweigart/pyperclip
* https://github.com/lxml/lxml
* https://github.com/yaml/pyyaml
* https://github.com/pallets/click/
* https://github.com/encode/uvicorn
* https://github.com/Tinche/aiofiles
================================================
FILE: README_EN.md
================================================
<div align="center">
<img src="static/XHS-Downloader.png" alt="XHS-Downloader" height="256" width="256"><br>
<h1>XHS-Downloader</h1>
<p><a href="README.md">简体中文</a> | English</p>
<a href="https://trendshift.io/repositories/5435" target="_blank"><img src="https://trendshift.io/api/badge/repositories/5435" alt="JoeanAmier%2FXHS-Downloader | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
<br>
<img alt="GitHub" src="https://img.shields.io/github/license/JoeanAmier/XHS-Downloader?style=flat-square">
<img alt="GitHub forks" src="https://img.shields.io/github/forks/JoeanAmier/XHS-Downloader?style=flat-square&color=55efc4">
<img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/JoeanAmier/XHS-Downloader?style=flat-square&color=fda7df">
<img alt="GitHub code size in bytes" src="https://img.shields.io/github/languages/code-size/JoeanAmier/XHS-Downloader?style=flat-square&color=a29bfe">
<img alt="GitHub release (with filter)" src="https://img.shields.io/github/v/release/JoeanAmier/XHS-Downloader?style=flat-square&color=48dbfb">
<br>
<img alt="Static Badge" src="https://img.shields.io/badge/Python-3.12-b8e994?style=flat-square&logo=python&labelColor=3dc1d3">
<img alt="Static Badge" src="https://img.shields.io/badge/UserScript-ffec3d?style=flat-square&logo=tampermonkey&logoColor=%2300485B">
<img src="https://img.shields.io/badge/Sourcery-enabled-884898?style=flat-square&color=1890ff" alt="">
<img alt="Static Badge" src="https://img.shields.io/badge/Docker-badc58?style=flat-square&logo=docker">
<img alt="GitHub all releases" src="https://img.shields.io/github/downloads/JoeanAmier/XHS-Downloader/total?style=flat-square&color=ffdd59">
</div>
<br>
<p>🔥 <b>RedNote Link Extraction/Content Collection Tool</b>:Extract account-published, favorites, and liked notes links; extract search result notes links and user links; collect RedNote notes information; extract RedNote notes download addresses; download RedNote notes files!</p>
<p>🔥 "RedNote", "XiaoHongShu" and "小红书" have the same meaning, and this project is collectively referred to as "RedNote".</p>
<p>⭐ Due to the author's limited energy, I was unable to update the English document in a timely manner, and the content may have become outdated, partial translation is machine translation, the translation result may be incorrect, Suggest referring to Chinese documentation. If you want to contribute to translation, we warmly welcome you.</p>
<h1>📑 Project Features</h1>
<details>
<summary>Program Features and User Script Features (Click to Expand)</summary>
<ul><b>Program Features</b>
<li>✅ Collect RedNote notes information</li>
<li>✅ Extract RedNote notes download addresses</li>
<li>✅ Download RedNote notes files</li>
<li>✅ Download RedNote livePhoto files</li>
<li>✅ Automatically skip already downloaded notes files</li>
<li>✅ notes file integrity handling mechanism</li>
<li>✅ Customizable image notes file download format</li>
<li>✅ Persistently store notes information to files</li>
<li>✅ Store notes files to a separate folder</li>
<li>✅ Background clipboard monitoring for notes download</li>
<li>✅ Record downloaded notes IDs</li>
<li>✅ Support command line for downloading notes files</li>
<li>☑️ Read cookies from browser</li>
<li>✅ Customizable file name format</li>
<li>✅ Support API call functionality</li>
<li>✅ Support MCP call functionality</li>
<li>✅ Support file breakpoint resume download</li>
<li>✅ Intelligent recognition of notes file types</li>
<li>✅ Supports author alias configuration</li>
<li>✅ Automatic author nickname updates</li>
</ul>
<ul><a href="#user-scripts"><b>Script Features</b></a>
<li>✅ Download RedNote notes files</li>
<li>✅ Extract discovery page notes links</li>
<li>✅ Extract account-published notes links</li>
<li>✅ Extract account-favorited notes links</li>
<li>✅ Extract account-liked notes links</li>
<li>✅ Extract account-board notes links</li>
<li>✅ Extract search result notes links</li>
<li>✅ Extract search result user links</li>
</ul>
</details>
<h1>📸 Program Screenshots</h1>
<p><a href="https://www.bilibili.com/video/BV1Fcb3zWEjt/">Watch Demo on Bilibili</a>;<a href="https://youtu.be/VIjDytHaopg">Watch Demo on YouTube</a></p>
<img src="static/screenshot/程序运行截图EN1.png" alt="">
<hr>
<img src="static/screenshot/程序运行截图EN2.png" alt="">
<hr>
<img src="static/screenshot/程序运行截图EN3.png" alt="">
<h1>🔗 Supported Links</h1>
<ul>
<li><code>https://www.xiaohongshu.com/explore/NoteID?xsec_token=XXX</code></li>
<li><code>https://www.xiaohongshu.com/discovery/item/NoteID?xsec_token=XXX</code></li>
<li><code>https://www.xiaohongshu.com/user/profile/AuthorID/NoteID?xsec_token=XXX</code></li>
<li><code>https://xhslink.com/ShareCode</code></li>
<br/>
<p><b>Supports entering multiple notes links at once, separated by spaces; the program will automatically extract valid links without additional processing!</b></p>
</ul>
<h1>🪟 About the Terminal</h1>
<p>⭐ It is recommended to use the <a href="https://learn.microsoft.com/en-us/windows/terminal/install">Windows Terminal</a> (default terminal for Windows 11) to run the program for the best display effect!</p>
<h1>🥣 Usage</h1>
<p>If you only need to download notes files, it is recommended to choose <b>Program Run</b>; if you have other needs, it is recommended to choose <b>Source Code Run</b>!</p>
<p>⚠️ Cookies are optional. If issues occur, please try configuring or updating them and retry!</p>
<p>⚠️ When Cookie is not set, video works can only be downloaded in low resolution; it is recommended to configure Cookie to obtain higher quality (no need to log in to the account)!</p>
<h2>🖱 Program Run</h2>
<p>⭐ Mac OS, Windows 10 and above users can go to <a href="https://github.com/JoeanAmier/XHS-Downloader/releases/latest">Releases</a> or <a href="https://github.com/JoeanAmier/XHS-Downloader/actions">Actions</a> to download the program package, unzip it, open the program folder, and double-click to run <code>main</code> to use.</p>
<p>⭐ This project includes GitHub Actions for automatic building executable files. Users can use GitHub Actions to build the latest source code into executable files at any time!</p>
<p>⭐ For the automatic building executable files tutorial, please refer to the <code>Build of Executable File Guide</code> section of this document. If you need a more detailed step-by-step tutorial with illustrations, please <a href="https://mp.weixin.qq.com/s/TorfoZKkf4-x8IBNLImNuw">check out this article</a>!</p>
<p><strong>Note: Due to the macOS platform's executable file <code>main</code> not being code-signed, it will be restricted by system security measures on first run. Please execute the command <code>xattr -cr project_folder_path</code> in the terminal to remove the security flag, after which it can run normally.</strong></p>
<p>If you use the program in this way, the default download path for files is: <code>.\_internal\Volume\Download</code>; the configuration file path is: <code>.\_internal\Volume\settings.json</code></p>
<h3>Update Methods</h3>
<p><strong>Method 1:</strong> Download and extract the files, then copy the old version of the <code>_internal\Volume</code> folder into the new version's <code>_internal</code> folder.</p>
<p><strong>Method 2:</strong> Download and extract the files (do not run the program), then copy all files and directly overwrite the old version.</p>
<h2>⌨️ Run from Source Code</h2>
<ol>
<li>Install <a href="https://www.python.org/">Python</a> interpreter version <code>≥3.12</code></li>
<li>Download the latest source code or the source code released in <a href="https://github.com/JoeanAmier/XHS-Downloader/releases/latest">Releases</a> to your local machine</li>
<ol><b>Install project dependencies using pip</b>
<li>Run the command <code>python -m venv venv</code> to create a virtual environment (optional)</li>
<li>Run the command <code>.\venv\Scripts\activate.ps1</code> or <code>venv\Scripts\activate</code> to activate the virtual environment (optional)</li>
<li>Run the command <code>pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt</code> to install the required modules for the program</li>
<li>Run the command <code>python .\main.py</code> or <code>python main.py</code> to start XHS-Downloader</li>
</ol>
<ol><b>Install project dependencies using uv (recommended)</b>
<li>Run the command <code>uv sync --no-dev</code> to synchronize environment dependencies</li>
<li>Run the command <code>uv run main.py</code> to start XHS-Downloader</li>
</ol>
</ol>
<h2>⌨️ Docker Run</h2>
<ol>
<li>Get Image</li>
<ul>
<li>Method 1: Build the image using the <code>Dockerfile</code></li>
<li>Method 2: Pull the image using the command <code>docker pull joeanamier/xhs-downloader</code></li>
<li>Method 3: Pull the image using the command <code>docker pull ghcr.io/joeanamier/xhs-downloader</code></li>
</ul>
<li>Create Container</li>
<ul>
<li>TUI Mode: <code>docker run --name ContainerName(optional) -p HostPort:5556 -v xhs_downloader_volume:/app/Volume -it <image name></code></li>
<li>API Mode: <code>docker run --name ContainerName(optional) -p HostPort:5556 -v xhs_downloader_volume:/app/Volume -it <image name> python main.py api</code></li>
<li>MCP Mode: <code>docker run --name ContainerName(optional) -p HostPort:5556 -v xhs_downloader_volume:/app/Volume -it <image name> python main.py mcp</code></li>
<br><b>Note:</b> The <code><image name></code> here must be consistent with the image name you used in the first step (<code>joeanamier/xhs-downloader</code> or <code>ghcr.io/joeanamier/xhs-downloader</code>)
</ul>
<li>Run Container
<ul>
<li>Start Container: <code>docker start -i ContainerName/ContainerID</code></li>
<li>Restart Container: <code>docker restart -i ContainerName/ContainerID</code></li>
</ul>
</li>
</ol>
<p>When running the project via Docker, the <b>command line call mode</b> is not supported. The <b>clipboard reading</b> and <b>clipboard monitoring</b> functions are unavailable, but pasting content notes fine. Please provide feedback if other features are not functioning properly!</p>
<h1>🛠 Command Line Mode</h1>
<p>The project supports command line mode. If you want to download specific images from a text and image notes, you can use this mode to set the image sequence number you want to download!</p>
<p><strong>Note:</strong> When the <code>--index</code> parameter is not set, multiple notes links can be passed in. All links must be enclosed in quotation marks and separated by spaces. When the <code>--index</code> parameter is set, multiple notes links are not supported. Even if multiple links are passed in, the program will only process the first link!</p>
<p>The <code>bool</code> type parameters support setting with <code>true</code>, <code>false</code>, <code>1</code>, <code>0</code>, <code>yes</code>, <code>no</code>, <code>on</code> or <code>off</code> (case insensitive).</p>
<h2>Read Browser Cookies</h2>
<p>This feature is no longer available. Please refer to the <a href="#cookie">Obtain Cookie</a> tutorial!</p>
<p><del>You can use the command line to <b>read cookies from browser and write them to the configuration file!</b></del></p>
<p><del>Command example: <code>python .\main.py --browser_cookie Chrome --update_settings</code></del></p>
<p><del>Compatibility note: The third-party module this feature depends on has not been updated for a long time and may not properly support the latest browser versions. If the feature is not working properly, please try obtaining cookies manually!</del></p>
<hr>
<img src="static/screenshot/命令行模式截图EN1.png" alt="">
<hr>
<img src="static/screenshot/命令行模式截图EN2.png" alt="">
<h1>🖥 Server Mode</h1>
<p>Server modes include API mode and MCP mode!</p>
<h2>API Mode</h2>
<p><b>Start:</b> Run the command: <code>python .\main.py api</code></p>
<p><b>Stop:</b> Press <code>Ctrl</code> + <code>C</code> to stop the server</p>
<p>Open <code>http://127.0.0.1:5556/docs</code> or <code>http://127.0.0.1:5556/redoc</code>; you will see automatically generated interactive API documentation!</p>
<p><b>Request endpoint:</b>
<code>/xhs/detail</code></p>
<p><b>Request method:</b>
<code>POST</code></p>
<p><b>Request format:</b>
<code>JSON</code></p>
<p><b>Request parameters:</b></p>
<table>
<thead>
<tr>
<th align="center">Parameter</th>
<th align="center">Type</th>
<th align="center">Description</th>
<th align="center">Default</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">url</td>
<td align="center">str</td>
<td align="center">RedNote notes link, auto-extraction, does not support multiple links; Required parameter</td>
<td align="center">None</td>
</tr>
<tr>
<td align="center">download</td>
<td align="center">bool</td>
<td align="center">Whether to download the notes file; set to <code>true</code> will take more time; Optional parameter</td>
<td align="center">false</td>
</tr>
<tr>
<td align="center">index</td>
<td align="center">list[int]</td>
<td align="center">Download specific image files by index, only effective for text and image notes; not effective when the <code>download</code> parameter is set to <code>false</code>; Optional parameter</td>
<td align="center">null</td>
</tr>
<tr>
<td align="center">cookie</td>
<td align="center">str</td>
<td align="center">Cookie used when requesting data; Optional parameter</td>
<td align="center">Settings cookie Value</td>
</tr>
<tr>
<td align="center">proxy</td>
<td align="center">str</td>
<td align="center">Proxy used when requesting data; Optional parameter</td>
<td align="center">Settings proxy Value</td>
</tr>
<tr>
<td align="center">skip</td>
<td align="center">bool</td>
<td align="center">Whether to skip notes with download records; set to <code>true</code> will not return notes data with download records; Optional parameter</td>
<td align="center">false</td>
</tr>
</tbody>
</table>
<p><b>Code example:</b></p>
<pre>
async def example_api():
"""通过 API 设置参数,适合二次开发"""
server = "http://127.0.0.1:5556/xhs/detail"
data = {
"url": "", # 必需参数
"download": True,
"index": [
3,
6,
9,
],
"proxy": "http://127.0.0.1:10808",
}
response = post(server, json=data, timeout=10)
print(response.json())
</pre>
<h2>MCP Mode</h2>
<p><b>Start:</b> Run the command: <code>python .\main.py mcp</code></p>
<p><b>Stop:</b> Press <code>Ctrl</code> + <code>C</code> to stop the server</p>
<h3>MCP Configuration Example</h3>
[//]: # (<h4>STDIO</h4>)
<h4>Streamable HTTP</h4>
<p><b>MCP URL:</b><code>http://127.0.0.1:5556/mcp/</code></p>
<img src="static/screenshot/MCP配置示例.png" alt="MCP Configuration Example">
<h3>MCP Invocation Example</h3>
<details>
<summary>MCP Function and Call Example (Click to Expand)</summary>
<h4><strong>Retrieve RedNote Notes Information</strong></h4>
<img src="static/screenshot/MCP获取数据.png" alt="MCP Data Retrieval">
<hr>
<h4><strong>Download RedNote Notes Files</strong></h4>
<p>When downloading images, you can specify the sequence numbers of the images to download. By default, post information is not returned. If you need the post information, please explicitly state so during the conversation.</p>
<img src="static/screenshot/MCP下载文件1.png" alt="MCP File Download">
<hr>
<img src="static/screenshot/MCP下载文件2.png" alt="MCP File Download">
</details>
<h1>📜 Others</h1>
<ul>
<li>Due to the date information carried in the links of RedNote notes, using links obtained from previous dates may be subject to risk control. It is recommended to use the latest RedNote notes links when downloading RedNote work files</li>
<li><del>Windows system requires running programs as an administrator to read Chromium, Chrome, Edge browser cookies</del></li>
<li>If the function to save notes data to a file is enabled, the notes data will be stored by default in the <code>./Volume/Download/ExploreData.db</code> file</li>
<li>The program's download records will be stored in the <code>./Volume/ExploreID.db</code> file</li>
<li>To prevent high-frequency requests from impacting the platform's servers, this project includes a built-in request delay mechanism</li>
</ul>
<h1 id="user-scripts">🕹 User Script</h1>
<p>If your browser has the <a href="https://www.tampermonkey.net/">Tampermonkey</a> extension installed, you can use the userscript to try the project's features!</p>
<p>Userscript links (right-click to copy the link): <a href="https://raw.githubusercontent.com/JoeanAmier/XHS-Downloader/refs/heads/master/static/XHS-Downloader.js">master branch</a>, <a href="https://raw.githubusercontent.com/JoeanAmier/XHS-Downloader/refs/heads/develop/static/XHS-Downloader.js">develop branch</a></p>
<img src="static/screenshot/脚本安装教程.png" alt="">
<hr>
<details>
<summary>View Tampermonkey userscript screenshots (click to expand)</summary>
<img src="static/screenshot/用户脚本截图1.png" alt="">
<hr>
<img src="static/screenshot/用户脚本截图2.png" alt="">
<hr>
<img src="static/screenshot/用户脚本截图3.png" alt="">
<hr>
<img src="static/screenshot/用户脚本截图4.png" alt="">
</details>
<p>Note: Using the XHS-Downloader user script to batch extract notes links, in combination with the XHS-Downloader program, can achieve batch downloading of notes files!</p>
<p><b>Modify user script language</b></p>
<img src="static/screenshot/脚本切换语言.png" alt="切换语言">
<h2>🌐 Connect to Server</h2>
<p>⭐ This project supports interaction with the main program through a browser userscript, enabling one-click push of download tasks.</p>
<ul><b>Function Description:</b>
<li>In the project program's configuration file, you need to set the <code>script_server</code> parameter to <code>true</code></li>
<li>Keep the project program running in the background, where it will act as a server to receive commands from the userscript (TUI, MCP, and API modes are all supported)</li>
<li>When you visit a post page in your browser, click the <code>Push Download Task</code> option in the userscript menu</li>
<li>The userscript will send the download task to the project program, which will handle and download the files</li>
</ul>
<h2>📜 Script Instructions</h2>
<ul>
<li>When downloading notes from RedNote, the script requires time to process the files. Please wait for a moment and do not click the download button multiple times.</li>
<li>When extracting links for posts, collects, likes, and board from an account, the script can automatically scroll the page until all notes are loaded.</li>
<li>When extracting recommended notes links, search notes, and user links, the script can automatically scroll a specified number of times to load more content. The default number of page scrolls is 50.</li>
<li>The automatic scrolling page function is turned off by default; Users can freely open and modify the number of times the page is scrolled, and the modification will take effect immediately.</li>
<li>If the automatic page scroll feature is not enabled, users need to manually scroll the page to load more content before performing other actions.</li>
<li>Support packaging and downloading of work files; This feature is enabled by default, and notes from multiple files will be downloaded in compressed file format</li>
<li>When pushing download tasks to the server, the file format, name rules, etc. settings will be based on the server configuration file settings</li>
<li>Using global proxy tools may cause script download failures. If there are issues, please try disabling the proxy tool. If necessary, contact the author for feedback.</li>
<li>XHS-Downloader userscript only implements the data collection functionality for visible content and does not include any paid or cracked features.</li>
</ul>
<p><strong>The automatic page scroll feature has been refactored and is turned off by default! Enabling this feature may be detected as automated behavior by RedNote, potentially resulting in account risk control or banning.</strong></p>
<h1>💻 Secondary Development</h1>
<p>If you have other needs, you can perform code calls or modifications based on the comments in <code>example.py</code>!</p>
<pre>
async def example():
"""通过代码设置参数,适合二次开发"""
# 示例链接
demo_link = "https://www.xiaohongshu.com/explore/XXX?xsec_token=XXX"
# 实例对象
work_path = "D:\\" # 作品数据/文件保存根路径,默认值:项目根路径
folder_name = "Download" # 作品文件储存文件夹名称(自动创建),默认值:Download
name_format = "作品标题 作品描述"
user_agent = "" # User-Agent
cookie = "" # 小红书网页版 Cookie,无需登录,可选参数,登录状态对数据采集有影响
proxy = None # 网络代理
timeout = 5 # 请求数据超时限制,单位:秒,默认值:10
chunk = 1024 * 1024 * 10 # 下载文件时,每次从服务器获取的数据块大小,单位:字节
max_retry = 2 # 请求数据失败时,重试的最大次数,单位:秒,默认值:5
record_data = False # 是否保存作品数据至文件
image_format = "WEBP" # 图文作品文件下载格式,支持:AUTO、PNG、WEBP、JPEG、HEIC
folder_mode = False # 是否将每个作品的文件储存至单独的文件夹
image_download = True # 图文、图集作品文件下载开关
video_download = True # 视频作品文件下载开关
live_download = False # 图文动图文件下载开关
download_record = True # 是否记录下载成功的作品 ID
language = "zh_CN" # 设置程序提示语言
author_archive = True # 是否将每个作者的作品存至单独的文件夹
write_mtime = True # 是否将作品文件的 修改时间 修改为作品的发布时间
# read_cookie = None # 读取浏览器 Cookie,支持设置浏览器名称(字符串)或者浏览器序号(整数),设置为 None 代表不读取
# async with XHS() as xhs:
# pass # 使用默认参数
async with XHS(
work_path=work_path,
folder_name=folder_name,
name_format=name_format,
user_agent=user_agent,
cookie=cookie,
proxy=proxy,
timeout=timeout,
chunk=chunk,
max_retry=max_retry,
record_data=record_data,
image_format=image_format,
folder_mode=folder_mode,
image_download=image_download,
video_download=video_download,
live_download=live_download,
download_record=download_record,
language=language,
# read_cookie=read_cookie,
author_archive=author_archive,
write_mtime=write_mtime,
) as xhs: # 使用自定义参数
download = True # 是否下载作品文件,默认值:False
# 返回作品详细信息,包括下载地址
# 获取数据失败时返回空字典
print(
await xhs.extract(
demo_link,
download,
index=[
1,
2,
5,
],
)
)
</pre>
<h1>📋 Read Clipboard</h1>
<p>The project uses <code>pyperclip</code> to implement clipboard reading functionality, which varies across different systems.</p>
<p>On Windows, no additional modules are needed.</p>
<p>On Mac, this module makes use of the pbcopy and pbpaste commands, which should come with the os.</p>
<p>On Linux, this module makes use of the xclip or xsel commands, which should come with the os. Otherwise run "sudo apt-get install xclip" or "sudo apt-get install xsel" (Note: xsel does not always seem to work.)</p>
<p>Otherwise on Linux, you will need the qtpy or PyQT5 modules installed.</p>
<h1>⚙️ Configuration File</h1>
<p>The <code>./Volume/settings.json</code> file in the project's root directory is automatically generated on the first run. You can use it to customize the program's operating parameters. If an invalid parameter value is set, the program will revert to its default value.</p>
<p>If you are unable to modify settings through the program's interface, you can edit this configuration file directly. If your computer lacks a suitable program for editing JSON files, we recommend using an <a href="https://www.toolhelper.cn/JSON/JSONFormat">online tool</a>. Remember to restart the software after making changes for them to take effect.</p>
<table>
<thead>
<tr>
<th align="center">Parameter</th>
<th align="center">Type</th>
<th align="center">Description</th>
<th align="center">Default Value</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">mapping_data</td>
<td align="center">str: str</td>
<td align="center"><sup><a href="#author_archive">#</a></sup>Author alias mapping data, format: <code>author ID: author alias</code></td>
<td align="center">null</td>
</tr>
<tr>
<td align="center">work_path</td>
<td align="center">str</td>
<td align="center">Root path for saving notes data/files</td>
<td align="center">Project root path/Volume</td>
</tr>
<tr>
<td align="center">folder_name</td>
<td align="center">str</td>
<td align="center">Name of the folder for storing notes files</td>
<td align="center">Download</td>
</tr>
<tr>
<td align="center">name_format</td>
<td align="center">str</td>
<td align="center"><sup><a href="#fields">#</a></sup>Format of notes file name, separated by spaces between fields, supports fields: <code>收藏数量</code>、<code>评论数量</code>、<code>分享数量</code>、<code>点赞数量</code>、<code>作品标签</code>、<code>作品ID</code>、<code>作品标题</code>、<code>作品描述</code>、<code>作品类型</code>、<code>发布时间</code>、<code>最后更新时间</code>、<code>作者昵称</code>、<code>作者ID</code></td>
<td align="center"><code>发布时间 作者昵称 作品标题</code></td>
</tr>
<tr>
<td align="center">user_agent</td>
<td align="center">str</td>
<td align="center">Browser User Agent</td>
<td align="center">Built-in Chrome User Agent</td>
</tr>
<tr>
<td align="center">cookie</td>
<td align="center">str</td>
<td align="center">RedNote web version cookie, <b>No login required, non essential parameters!</b></td>
<td align="center">None</td>
</tr>
<tr>
<td align="center">proxy</td>
<td align="center">str</td>
<td align="center">Set program proxy</td>
<td align="center">null</td>
</tr>
<tr>
<td align="center">timeout</td>
<td align="center">int</td>
<td align="center">Request data timeout limit, in seconds</td>
<td align="center">10</td>
</tr>
<tr>
<td align="center">chunk</td>
<td align="center">int</td>
<td align="center">Size of data chunk to fetch from the server each time when downloading files, in bytes</td>
<td align="center">2097152(2 MB)</td>
</tr>
<tr>
<td align="center">max_retry</td>
<td align="center">int</td>
<td align="center">Maximum number of retries when requesting data fails</td>
<td align="center">5</td>
</tr>
<tr>
<td align="center">record_data</td>
<td align="center">bool</td>
<td align="center">Whether to save notes data to a file, saved in <code>SQLite</code> format</td>
<td align="center">false</td>
</tr>
<tr>
<td align="center">image_format</td>
<td align="center">str</td>
<td align="center">Download format for image notes files, supported: <code>AUTO</code>、<code>PNG</code>、<code>WEBP</code>、<code>JPEG</code>、<code>HEIC</code><br><strong>Some notes do not have files in HEIC format, and the downloaded files may be in WEBP format</strong><br><strong>When set to<code>AUTO</code>, it represents dynamic format, and the actual format depends on the server's response data</strong></td>
<td align="center">JPEG</td>
</tr>
<tr>
<td align="center">image_download</td>
<td align="center">bool</td>
<td align="center">Switch for downloading image and atlas notes files</td>
<td align="center">true</td>
</tr>
<tr>
<td align="center">video_download</td>
<td align="center">bool</td>
<td align="center">Switch for downloading video notes files</td>
<td align="center">true</td>
</tr>
<tr>
<td align="center">live_download</td>
<td align="center">bool</td>
<td align="center">Switch for downloading animated image files</td>
<td align="center">false</td>
</tr>
<tr>
<td align="center">video_preference</td>
<td align="center">str</td>
<td align="center">Video notes file download preference; Meaning: <code>resolution</code>: resolution priority; <code>bitrate</code>: bitrate priority; <code>size</code>: file size priority</td>
<td align="center">resolution</td>
</tr>
<tr>
<td align="center">folder_mode</td>
<td align="center">bool</td>
<td align="center">Whether to store each notes files in a separate folder; the folder name matches the file name</td>
<td align="center">false</td>
</tr>
<tr>
<td align="center">download_record</td>
<td align="center">bool</td>
<td align="center">Do record the ID of successfully downloaded notes? If enabled, the program will automatically skip downloading notes with records</td>
<td align="center">true</td>
</tr>
<tr>
<td align="center">author_archive</td>
<td align="center">bool</td>
<td align="center"><sup><a href="#author_archive">#</a></sup>Whether to save each author's notes into a separate folder; The folder name is <code>authorID_nickname</code></td>
<td align="center">false</td>
</tr>
<tr>
<td align="center">write_mtime</td>
<td align="center">bool</td>
<td align="center">Whether to modify the <code>modified time</code> attribute of the notes file to the publication time of the notes.</td>
<td align="center">false</td>
</tr>
<tr>
<td align="center">language</td>
<td align="center">str</td>
<td align="center">Set program language. Currently supported: <code>zh_CN</code>, <code>en_US</code></td>
<td align="center">zh_CN</td>
</tr>
<tr>
<td align="center">script_server</td>
<td align="center">bool</td>
<td align="center">Whether to enable the user script server for receiving download tasks from the browser user script (effective in TUI, MCP, and API modes)</td>
<td align="center">false</td>
</tr>
</tbody>
</table>
<hr>
<div id="fields">
<p>name_format instructions (Currently only supports Chinese values) :</p>
<ul>
<li><code>收藏数量</code>: Number of Collections</li>
<li><code>评论数量</code>: Number of Comments</li>
<li><code>分享数量</code>: Number of Shares</li>
<li><code>点赞数量</code>: Number of Likes</li>
<li><code>作品标签</code>: Notes Tags</li>
<li><code>作品ID</code>: Notes ID</li>
<li><code>作品标题</code>: Notes Title</li>
<li><code>作品描述</code>: Notes Description</li>
<li><code>作品类型</code>: Notes Type</li>
<li><code>发布时间</code>: Publish Time</li>
<li><code>最后更新时间</code>: Last Updated Time</li>
<li><code>作者昵称</code>: Author Nickname</li>
<li><code>作者ID</code>: Author ID</li>
</ul>
</div>
<hr>
<div id="author_archive">
<p>When <code>author_archive</code> is set to <code>true</code>, the program will store each author's notes in dedicated folders. If an author's nickname changes, the program automatically updates the nickname portion in existing downloaded filenames!</p>
<p>Additionally, you can configure author aliases through the <code>mapping_data</code> parameter. When an alias is set, the program will use your custom alias instead of the original nickname in filenames!</p>
</div>
<hr>
<p><b>Additional Notes: The parameters <code>user_agent</code> examples are provided for reference; Strongly recommend setting according to actual browser information!</b></p>
<img src="static/screenshot/请求头示例图.png" alt="">
<h1 id="cookie">🌐 Cookie</h1>
<ol>
<li>Open the browser (optional: start in incognito mode) and visit <code>https://www.xiaohongshu.com/explore</code></li>
<li>Log in to your RedNote account (can be skipped)</li>
<li>Press <code>F12</code> to open the developer tools</li>
<li>Select the <code>Network</code> tab</li>
<li>Check <code>Preserve log</code></li>
<li>In the <code>Filter</code> input box, enter <code>cookie-name:web_session</code></li>
<li>Select the <code>Fetch/XHR</code> filter</li>
<li>Click on any piece of notes on the RedNote page</li>
<li>In the <code>Network</code> tab, select any data packet (if no packets appear, repeat step 7)</li>
<li>Copy and paste the entire Cookie into the program or configuration file</li>
</ol>
<br>
<img src="static/screenshot/获取Cookie示意图.png" alt="">
<h1>🗳 Download Records</h1>
<p>XHS-Downloader will store the IDs of downloaded notes in a database. When downloading the same notes again, XHS-Downloader will automatically skip the file download (even if the notes file does not exist). If you want to re-download the notes file, please delete the corresponding notes ID from the database and then use XHS-Downloader to download the notes file again!</p>
<p>This feature is enabled by default. If it is turned off, XHS-Downloader will check if the file exists. If the file exists, it will skip the download!</p>
<h2>Build of Executable File Guide</h2>
<details>
<summary>Build of Executable File Guide (Click to Expand)</summary>
This guide will walk you through forking this repository and executing GitHub Actions to automatically build and package
the program based on the latest source code!
---
## Steps to Use
### 1. Fork the Repository
1. Click the **Fork** button at the top right of the project repository to fork it to your personal GitHub account
2. Your forked repository address will look like this: `https://github.com/your-username/this-repo`
---
### 2. Enable GitHub Actions
1. Go to the page of your forked repository
2. Click the **Settings** tab at the top
3. Click the **Actions** tab on the right
4. Click the **General** option
5. Under **Actions permissions**, select **Allow all actions and reusable workflows** and click the **Save** button
---
### 3. Manually Trigger the Build Process
1. In your forked repository, click the **Actions** tab at the top
2. Find the workflow named **构建可执行文件**
3. Click the **Run workflow** button on the right:
- Select the **master** or **develop** branch
- Click **Run workflow**
---
### 4. Check the Build Progress
1. On the **Actions** page, you can see the execution records of the triggered workflow
2. Click on the run record to view detailed logs to check the build progress and status
---
### 5. Download the Build Result
1. Once the build is complete, go to the corresponding run record page
2. In the **Artifacts** section at the bottom of the page, you will see the built result file
3. Click to download and save it to your local machine to get the built program
---
## Notes
1. **Resource Usage**:
- GitHub provides free build environments for Actions, with a monthly usage limit (2000 minutes) for free-tier
users
2. **Code Modifications**:
- You are free to modify the code in your forked repository to customize the build process
- After making changes, you can trigger the build process again to get your customized version
3. **Stay in Sync with the Main Repository**:
- If the main repository is updated with new code or workflows, it is recommended that you periodically sync your
forked repository to get the latest features and fixes
---
## Frequently Asked Questions
### Q1: Why can't I trigger the workflow?
A: Please ensure that you have followed the steps to **Enable Actions**. Otherwise, GitHub will prevent the workflow
from running
### Q2: What should I do if the build process fails?
A:
- Check the run logs to understand the cause of the failure
- Ensure there are no syntax errors or dependency issues in the code
- If the problem persists, please open an issue on
the [Issues page](https://github.com/JoeanAmier/XHS-Downloader/issues)
### Q3: Can I directly use the Actions from the main repository?
A: Due to permission restrictions, you cannot directly trigger Actions from the main repository. Please use the forked
repository to execute the build process
</details>
<h1>⭐ Star History</h1>
<p>
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=JoeanAmier/XHS-Downloader&type=Timeline"/>
</p>
<h1>♥️ Support the Project</h1>
<p>If <b>XHS-Downloader</b> has been helpful to you, please consider giving it a <b>Star</b> ⭐, Thank you for your support!</p>
<table>
<thead>
<tr>
<th align="center">微信(WeChat)</th>
<th align="center">支付宝(Alipay)</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><img src="./static/微信赞助二维码.png" alt="微信赞助二维码" height="200" width="200"></td>
<td align="center"><img src="./static/支付宝赞助二维码.png" alt="支付宝赞助二维码" height="200" width="200"></td>
</tr>
</tbody>
</table>
<p>If you are willing, you may consider making a donation to provide additional support for <b>XHS-Downloader</b>!</p>
<h1>🌟 Contribution Guidelines</h1>
<p><strong>Welcome to contributing to this project! To keep the codebase clean, efficient, and easy to maintain, please read the following guidelines carefully to ensure that your contributions can be accepted and integrated smoothly.</strong></p>
<ul>
<li>Before starting development, please pull the latest code from the <code>develop</code> branch as the basis for your modifications; this helps avoid merge conflicts and ensures your changes are based on the latest state of the project.</li>
<li>If your changes involve multiple unrelated features or issues, please split them into several independent commits or pull requests.</li>
<li>Each pull request should focus on a single feature or fix as much as possible, to facilitate code review and testing.</li>
<li>Follow the existing coding style; make sure your code is consistent with the style already present in the project; please use the Ruff tool to maintain code formatting standards.</li>
<li>Write code that is easy to read; add appropriate annotation to help others understand your intentions.</li>
<li>Each commit should include a clear and concise commit message describing the changes made. The commit message should follow this format: <code><type>: <short description></code></li>
<li>When you are ready to submit a pull request, please prioritize submitting them to the <code>develop</code> branch; this provides maintainers with a buffer zone for additional testing and review before final merging into the <code>master</code> branch.</li>
<li>It is recommended to communicate with the author before starting development or when encountering questions to ensure alignment in direction and avoid redundant efforts or unnecessary commits.</li>
</ul>
<p><strong>Reference materials:</strong></p>
<ul>
<li><a href="https://www.contributor-covenant.org/version/2/1/code_of_conduct/">Contributor Covenant</a></li>
<li><a href="https://opensource.guide/how-to-contribute/">How to Contribute to Open Source</a></li>
</ul>
<h1>✉️ Contact the Author</h1>
<ul>
<li>Author's Email:yonglelolu@foxmail.com</li>
<li>Author's WeChat: Downloader_Tools</li>
<li><b>Discord Community</b>: <a href="https://discord.com/invite/ZYtmgKud9Y">Click to Join the Community</a></li>
</ul>
<p>✨ <b>Other Open Source Projects by the Author:</b></p>
<ul>
<li><b>DouK-Downloader(抖音、TikTok)</b>:<a href="https://github.com/JoeanAmier/TikTokDownloader">https://github.com/JoeanAmier/TikTokDownloader</a></li>
<li><b>KS-Downloader(快手、KuaiShou)</b>:<a href="https://github.com/JoeanAmier/KS-Downloader">https://github.com/JoeanAmier/KS-Downloader</a></li>
</ul>
# 💰 Project Sponsorship
## DartNode
[](https://dartnode.com "Powered by DartNode - Free VPS for Open Source")
***
## ZMTO
<a href="https://www.zmto.com/"><img src="https://console.zmto.com/templates/2019/dist/images/logo_dark.svg" alt="ZMTO"></a>
<p><a href="https://www.zmto.com/">ZMTO</a>: A professional cloud infrastructure provider offering sophisticated solutions with reliable technology and expert support. We also empower qualified open source initiatives with enterprise-grade VPS infrastructure, driving sustainable development and innovation in the open source ecosystem. </p>
<h1>⚠️ Disclaimer</h1>
<ol>
<li>The user's use of this project is entirely at their own discretion and responsibility. The author assumes no liability for any losses, claims, or risks arising from the user's use of this project.</li>
<li>The code and functionalities provided by the author of this project are based on current knowledge and technological developments. The author strives to ensure the correctness and security of the code according to existing technical capabilities but does not guarantee that the code is entirely free of errors or defects.</li>
<li>All third-party libraries, plugins, or services relied upon by this project follow their respective open-source or commercial licenses. Users must review and comply with those license agreements. The author assumes no responsibility for the stability, security, or compliance of third-party components.</li>
<li>Users must strictly comply with the requirements of the <a href="https://github.com/JoeanAmier/XHS-Downloader/blob/master/LICENSE">GNU General Public License v3.0</a> when using this project and properly indicate that the code was used under the <a href="https://github.com/JoeanAmier/XHS-Downloader/blob/master/LICENSE">GNU General Public License v3.0</a>.</li>
<li>When using the code and features of this project, users must independently research relevant laws and regulations and ensure their actions are legal and compliant. Any legal liabilities or risks arising from violations of laws and regulations shall be borne solely by the user.</li>
<li>Users must not use this tool to engage in any activities that infringe intellectual property rights, including but not limited to downloading or distributing copyright-protected content without authorization. The developers do not participate in, support, or endorse any unauthorized acquisition or distribution of illegal content.</li>
<li>This project assumes no responsibility for the compliance of any data processing activities (including collection, storage, and transmission) conducted by users. Users must comply with relevant laws and regulations and ensure that their processing activities are lawful and proper. Legal liabilities resulting from non-compliant operations shall be borne by the user.</li>
<li>Under no circumstances may users associate the author, contributors, or other related parties of this project with their usage of the project, nor may they hold these parties responsible for any loss or damage arising from such usage.</li>
<li>The author of this project will not provide a paid version of the XHS-Downloader project, nor will they offer any commercial services related to the XHS-Downloader project.</li>
<li>Any secondary development, modification, or compilation based on this project is unrelated to the original author. The original author assumes no liability for any consequences resulting from such secondary development. Users bear full responsibility for all outcomes arising from such modifications.</li>
<li>This project grants no patent licenses; if the use of this project leads to patent disputes or infringement, the user bears all associated risks and responsibilities. Without written authorization from the author or rights holder, users may not use this project for any commercial promotion, marketing, or re-licensing.</li>
<li>The author reserves the right to terminate service to any user who violates this disclaimer at any time and may require them to destroy all obtained code and derivative notes.</li>
<li>The author reserves the right to update this disclaimer at any time without prior notice. Continued use of the project constitutes acceptance of the revised terms.</li>
</ol>
<b>Before using the code and functionalities of this project, please carefully consider and accept the above disclaimer. If you have any questions or disagree with the statement, please do not use the code and functionalities of this project. If you use the code and functionalities of this project, it is considered that you fully understand and accept the above disclaimer, and willingly assume all risks and consequences associated with the use of this project.</b>
# 💡 Project References
* https://github.com/encode/httpx/
* https://github.com/tiangolo/fastapi
* https://github.com/textualize/textual/
* https://github.com/pyinstaller/pyinstaller
* https://github.com/zbowling/beartype-pyinstaller-repro
* https://github.com/jlowin/fastmcp
* https://github.com/omnilib/aiosqlite
* https://github.com/carpedm20/emoji/
* https://github.com/asweigart/pyperclip
* https://github.com/lxml/lxml
* https://github.com/yaml/pyyaml
* https://github.com/pallets/click/
* https://github.com/encode/uvicorn
* https://github.com/Tinche/aiofiles
================================================
FILE: example.py
================================================
from asyncio import run
from pyperclip import paste
from httpx import post
from rich import print
from source import XHS
async def example():
"""通过代码设置参数,适合二次开发"""
# 示例链接
demo_link = "https://www.xiaohongshu.com/explore/XXX?xsec_token=XXX"
# 实例对象
work_path = "D:\\" # 作品数据/文件保存根路径,默认值:项目根路径
folder_name = "Download" # 作品文件储存文件夹名称(自动创建),默认值:Download
name_format = "作品标题 作品描述"
user_agent = "" # User-Agent
cookie = "" # 小红书网页版 Cookie,无需登录,可选参数,登录状态对数据采集有影响
proxy = None # 网络代理
timeout = 5 # 请求数据超时限制,单位:秒,默认值:10
chunk = 1024 * 1024 * 10 # 下载文件时,每次从服务器获取的数据块大小,单位:字节
max_retry = 2 # 请求数据失败时,重试的最大次数,单位:秒,默认值:5
record_data = False # 是否保存作品数据至文件
image_format = "WEBP" # 图文作品文件下载格式,支持:AUTO、PNG、WEBP、JPEG、HEIC
folder_mode = False # 是否将每个作品的文件储存至单独的文件夹
image_download = True # 图文、图集作品文件下载开关
video_download = True # 视频作品文件下载开关
live_download = False # 图文动图文件下载开关
download_record = True # 是否记录下载成功的作品 ID
language = "zh_CN" # 设置程序提示语言
author_archive = True # 是否将每个作者的作品存至单独的文件夹
write_mtime = True # 是否将作品文件的 修改时间 修改为作品的发布时间
# read_cookie = None # 读取浏览器 Cookie,支持设置浏览器名称(字符串)或者浏览器序号(整数),设置为 None 代表不读取
# async with XHS() as xhs:
# pass # 使用默认参数
async with XHS(
work_path=work_path,
folder_name=folder_name,
name_format=name_format,
user_agent=user_agent,
cookie=cookie,
proxy=proxy,
timeout=timeout,
chunk=chunk,
max_retry=max_retry,
record_data=record_data,
image_format=image_format,
folder_mode=folder_mode,
image_download=image_download,
video_download=video_download,
live_download=live_download,
download_record=download_record,
language=language,
# read_cookie=read_cookie,
author_archive=author_archive,
write_mtime=write_mtime,
) as xhs: # 使用自定义参数
download = True # 是否下载作品文件,默认值:False
# 返回作品详细信息,包括下载地址
# 获取数据失败时返回空字典
print(
await xhs.extract(
demo_link,
download,
index=[
1,
2,
5,
],
)
)
async def example_api():
"""通过 API 设置参数,适合二次开发"""
server = "http://127.0.0.1:5556/xhs/detail"
data = {
"url": "", # 必需参数
"download": True,
"index": [
3,
6,
9,
],
"proxy": "http://127.0.0.1:10808",
}
response = post(server, json=data, timeout=10)
print(response.json())
async def test():
url = "" or paste()
if not url:
return
async with XHS(
download_record=False,
) as xhs:
print(
await xhs.extract(
url,
# download=True,
)
)
if __name__ == "__main__":
# run(example())
# run(example_api())
run(test())
================================================
FILE: locale/README.md
================================================
# 命令参考
**运行命令前,确保已经安装了 `gettext` 软件包,并配置好环境变量。**
**Before running the command, ensure that the `gettext` package is installed and the environment variables are properly
configured.**
* `xgettext --files-from=py_files.txt -d xhs -o xhs.pot`
* `mkdir zh_CN\LC_MESSAGES`
* `msginit -l zh_CN -o zh_CN/LC_MESSAGES/xhs.po -i xhs.pot`
* `mkdir en_US\LC_MESSAGES`
* `msginit -l en_US -o en_US/LC_MESSAGES/xhs.po -i xhs.pot`
* `msgmerge -U zh_CN/LC_MESSAGES/xhs.po xhs.pot`
* `msgmerge -U en_US/LC_MESSAGES/xhs.po xhs.pot`
# 翻译贡献指南
* 如果想要贡献支持更多语言,请在终端切换至 `locale` 文件夹,运行命令
`msginit -l 语言代码 -o 语言代码/LC_MESSAGES/xhs.po -i xhs.pot`
生成 po 文件并编辑翻译。
* 如果想要贡献改进翻译结果,请直接编辑 `xhs.po` 文件内容。
* 仅需提交 `xhs.po` 文件,作者会转换格式并合并。
# Translation Contribution Guide
* If you want to contribute support for more languages, please switch to the `locale` folder in the terminal and run the
command `msginit -l language_code -o language_code/LC_MESSAGES/xhs.po -i xhs.pot` to generate the po file and edit the
translation.
* If you want to contribute to improving the translation, please directly edit the content of the `xhs.po` file.
* Only the `xhs.po` file needs to be submitted, and the author will convert the format and merge it.
================================================
FILE: locale/en_US/LC_MESSAGES/xhs.po
================================================
# English translations for XHS-Downloader package.
# Copyright (C) 2024 THE XHS-Downloader'S COPYRIGHT HOLDER
# This file is distributed under the same license as the XHS-Downloader package.
# FIRST AUTHOR <yonglelolu@foxmail.com>, 2024.
#
msgid ""
msgstr ""
"Project-Id-Version: XHS-Downloader 2.7\n"
"Report-Msgid-Bugs-To: <yonglelolu@foxmail.com>\n"
"POT-Creation-Date: 2026-02-06 16:45+0800\n"
"PO-Revision-Date: 2024-12-22 14:14+0800\n"
"Last-Translator: <yonglelolu@foxmail.com>\n"
"Language-Team: English\n"
"Language: en_US\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:223
#, python-brace-format
msgid "作品 {0} 存在下载记录,跳过下载"
msgstr "notes {0} has a download record, skip download"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:247
msgid "提取作品文件下载地址失败"
msgstr "Failed to extract the download address for the RedNote notes files"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:280
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:328
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:743
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:930
msgid "提取小红书作品链接失败"
msgstr "Failed to extract the links for RedNote notes"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:288
#, python-brace-format
msgid "共 {0} 个小红书作品待处理..."
msgstr "{0} notes from RedNote are awaiting processing..."
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:309
#, python-brace-format
msgid "共处理 {0} 个作品,成功 {1} 个,失败 {2} 个,跳过 {3} 个"
msgstr "Processed a total of {0} notes: {1} succeeded, {2} failed, {3} skipped"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:400
#, python-brace-format
msgid "作品 {0} 存在下载记录,跳过处理"
msgstr "notes {0} has a download record, skip processing"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:404
#, python-brace-format
msgid "开始处理作品:{0}"
msgstr "Start processing the notes: {0}"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:412
#, python-brace-format
msgid "{0} 获取数据失败"
msgstr "{0} failed to retrieve data"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:425
#, python-brace-format
msgid "{0} 提取数据失败"
msgstr "{0} failed to extract data"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:439
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:82
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:81
msgid "视频"
msgstr "video"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:442
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:89
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:82
msgid "图文"
msgstr "image"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:443
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:90
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:81
msgid "图集"
msgstr "LivePhoto"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:447
#, python-brace-format
msgid "未知的作品类型:{0}"
msgstr "Unknown notes type: {0}"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:505
#, python-brace-format
msgid "作品处理完成:{0}"
msgstr "notes processing completed: {0}"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:611
msgid ""
"程序会自动读取并提取剪贴板中的小红书作品链接,并自动下载链接对应的作品文件,"
"如需关闭,请点击关闭按钮,或者向剪贴板写入 “close” 文本!"
msgstr ""
"The program will automatically read and extract the link to RedNote notes "
"from the clipboard, and automatically download the corresponding work file. "
"If you want to close it, please click the close button or write the "
"\"close\" text to the clipboard!"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:712
msgid "跳转至项目 GitHub 仓库"
msgstr "Jump to the project's GitHub repository"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:713
msgid "重定向至项目 GitHub 仓库主页"
msgstr "Redirect to the project's GitHub repository homepage"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:721
msgid "获取作品数据及下载地址"
msgstr "Fetch notes data and download links"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:753
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:937
msgid "获取小红书作品数据成功"
msgstr "Successfully obtained data on RedNote notes"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:755
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:939
msgid "获取小红书作品数据失败"
msgstr "Failed to obtain data on RedNote notes"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:825
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:870
msgid "小红书作品链接"
msgstr "Link to RedNote notes"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:873
msgid "指定需要下载的图文作品序号"
msgstr "Specify the serial number of the images notes to be downloaded"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:877
msgid "是否需要返回作品信息数据"
msgstr "Whether to return notes information data"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:891
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:896
msgid "作品文件下载任务执行完毕"
msgstr "notes file download task completed"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:901
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:906
msgid "作品文件下载任务未执行"
msgstr "notes file download task not executed"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:131
msgid "视频作品下载功能已关闭,跳过下载"
msgstr "The video download function has been turned off, skip download"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:150
msgid "图文作品下载功能已关闭,跳过下载"
msgstr "The image download function has been turned off, skip download"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:182
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:192
#, python-brace-format
msgid "{0} 文件已存在,跳过下载"
msgstr "{0} already exists, skipping download"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:221
#, python-brace-format
msgid "文件 {0} 缓存异常,重新下载"
msgstr "File {0} cache exception, download again"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:248
#, python-brace-format
msgid "文件 {0} 下载成功"
msgstr "file {0} download successful"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:254
#, python-brace-format
msgid "网络异常,{0} 下载失败,错误信息: {1}"
msgstr "Network error, {0} download failed, error message: {1}"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:332
#, python-brace-format
msgid "文件 {0} 格式判断失败,错误信息:{1}"
msgstr "Format recognition failed for file {0}, error message: {1}"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:53
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:58
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:79
msgid "未知"
msgstr "unknown"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\request.py:66
#, python-brace-format
msgid "网络异常,{0} 请求失败: {1}"
msgstr "Network error, {0} request failed: {1}"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:129
msgid "小红书作品链接,多个链接使用空格分隔"
msgstr "RedNote notes links, separate multiple links with spaces"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:136
msgid ""
"下载指定序号的图片文件,仅对图文/图集作品生效;多个序号输入示例:\"1 3 5 7\""
msgstr ""
"Download images files with specified serial numbers, only effective for "
"images notes; Example of multiple serial numbers input: \"1 3 5 7\""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:141
msgid "作品数据/文件保存根路径"
msgstr "Root path for saving notes data / files"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:142
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:40
msgid "作品文件储存文件夹名称"
msgstr "Name of the folder for storing notes files"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:143
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:49
msgid "作品文件名称格式"
msgstr "Format of notes file name"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:145
msgid "小红书网页版 Cookie,无需登录"
msgstr "RedNote web version cookie, no need to log in"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:146
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:78
msgid "网络代理"
msgstr "Network proxy"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:147
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:88
msgid "请求数据超时限制,单位:秒"
msgstr "Network request timeout limit, in seconds"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:153
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:98
msgid "下载文件时,每次从服务器获取的数据块大小,单位:字节"
msgstr ""
"When downloading a file, the size of the data block obtained from the server "
"each time, in bytes"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:156
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:108
msgid "请求数据失败时,重试的最大次数"
msgstr "The maximum number of retries when data request fails"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:157
msgid "是否记录作品数据至文件"
msgstr "Record notes data to file"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:162
msgid "图文作品文件下载格式,支持:PNG、WEBP、JPEG、HEIC、AUTO"
msgstr ""
"Image notes file download format, supporting: PNG、WEBP、JPEG、HEIC、AUTO"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:164
msgid "动态图片下载开关"
msgstr "LivePhoto download switch"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:165
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:149
msgid "作品下载记录开关"
msgstr "Download record switch"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:170
msgid "是否将每个作品的文件储存至单独的文件夹"
msgstr "Whether to save each work's files into separate folders"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:176
msgid "是否将每个作者的作品储存至单独的文件夹"
msgstr "Whether to save each author's notes into separate folders"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:183
msgid "是否将作品文件的修改时间属性修改为作品的发布时间"
msgstr ""
"Would you like to set the file's modified time attribute to match the work's "
"publication time"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:187
msgid "设置程序语言,目前支持:zh_CN、en_US"
msgstr "Set the programming language, currently supports: zh_CN、en_US"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:188
msgid "读取指定配置文件"
msgstr "Read specified configuration file"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:190
#, python-brace-format
msgid "从指定的浏览器读取小红书网页版 Cookie,支持:{0}; 输入浏览器名称或序号"
msgstr ""
"Read RedNote web version cookies from the specified browser, supporting: "
"{0}; Enter browser name or serial number"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:203
msgid "是否更新配置文件"
msgstr "Do you need to update the configuration file"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:209
msgid "查看详细参数说明"
msgstr "View detailed parameter descriptions"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:210
msgid "查看 XHS-Downloader 版本"
msgstr "View XHS Downloader Version"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\browser.py:53
#, python-brace-format
msgid ""
"读取指定浏览器的 Cookie 并写入配置文件\n"
"Windows 系统需要以管理员身份运行程序才能读取 Chromium、Chrome、Edge 浏览器 "
"Cookie!\n"
"{options}\n"
"请输入浏览器名称或序号:"
msgstr ""
"Read cookies from the specified browser and write them to the configuration "
"file\n"
"The Windows system requires running programs as an administrator to read "
"Chromium, Chrome, Edge browser cookies!\n"
"{options}\n"
"Please enter your browser name or serial number:"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\browser.py:63
msgid "未选择浏览器!"
msgstr "Browser not selected!"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\browser.py:75
msgid "浏览器名称或序号输入错误!"
msgstr "Browser name or serial number input error!"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\browser.py:81
msgid "获取 Cookie 失败,未找到 Cookie 数据!"
msgstr "Failed to retrieve cookie, no cookie data found!"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\browser.py:119
msgid "从浏览器读取 Cookie 功能不支持当前平台!"
msgstr ""
"The cookie reading function from the browser is not supported on the current "
"platform!"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\cleaner.py:45
msgid "不受支持的操作系统类型,可能无法正常去除非法字符!"
msgstr ""
"Unsupported operating system type, may not be able to remove illegal "
"characters properly!"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\manager.py:240
#, python-brace-format
msgid "代理 {0} 测试成功"
msgstr "Agent {0} tested successfully"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\manager.py:244
#, python-brace-format
msgid "代理 {0} 测试超时"
msgstr "Agent {0} test timeout"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\manager.py:252
#, python-brace-format
msgid "代理 {0} 测试失败:{1}"
msgstr "Agent {0} test failed: {1}"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:55
#, python-brace-format
msgid "{old_folder} 文件夹不存在,跳过处理"
msgstr "{old_folder} directory does not exist, skipping processing"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:81
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:101
msgid "文件夹"
msgstr "folder"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:85
#, python-brace-format
msgid "文件夹 {old_folder} 已重命名为 {new_folder}"
msgstr "The folder {old_folder} has been renamed to {new_folder}"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:105
#, python-brace-format
msgid "文件夹 {old_} 重命名为 {new_}"
msgstr "The folder {old_} has been renamed to {new_}"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:171
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:185
msgid "文件"
msgstr "file"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:175
#, python-brace-format
msgid "文件 {old_file} 重命名为 {new_file}"
msgstr "The file {old_file} has been renamed to {new_file}"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:193
#, python-brace-format
msgid "{type} {old}被占用,重命名失败: {error}"
msgstr "{type} {old} is occupied, renaming failed: {error}"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:202
#, python-brace-format
msgid "{type} {new}名称重复,重命名失败: {error}"
msgstr "{type} {new} already exists, renaming failed: {error}"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:211
#, python-brace-format
msgid "处理{type} {old}时发生预期之外的错误: {error}"
msgstr "An unexpected error occurred while processing {type} {old}: {error}"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\tools.py:32
msgid ""
"如需重新尝试处理该对象,请关闭所有正在访问该对象的窗口或程序,然后直接按下回"
"车键!\n"
"如需跳过处理该对象,请输入任意字符后按下回车键!"
msgstr ""
"If you want to retry processing this object, please close all windows or "
"programs currently accessing it, then press Enter directly!\n"
"If you want to skip processing this object, please enter any character and "
"then press Enter!"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:20
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:29
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\monitor.py:20
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:15
msgid "退出程序"
msgstr "Quit"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:21
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:30
msgid "检查更新"
msgstr "Update"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:22
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\record.py:35
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:16
msgid "返回首页"
msgstr "Return"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:35
msgid "如果 XHS-Downloader 对您有帮助,请考虑为它点个 Star,感谢您的支持!"
msgstr ""
"If XHS-Downloader is helpful to you, please consider giving it Star. Thank "
"you for your support!"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:42
msgid "Discord 社区"
msgstr "Discord Community"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:46
msgid "邀请链接:"
msgstr "Invitation link: "
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:48
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:61
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:70
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:56
msgid "点击访问"
msgstr "Click to visit"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:51
msgid "作者的其他开源项目"
msgstr "Other open-source projects of the author"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:31
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:231
msgid "程序设置"
msgstr "Settings"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:32
msgid "下载记录"
msgstr "Record"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:33
msgid "开启监听"
msgstr "Monitor"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:34
msgid "关于项目"
msgstr "About"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:49
msgid "开源协议: "
msgstr "Open source protocol: "
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:52
msgid "项目地址: "
msgstr "Repository link: "
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:59
msgid "请输入小红书图文/视频作品链接"
msgstr "Please enter the link to the RedNote image or video notes"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:62
msgid "多个链接之间使用空格分隔"
msgstr "Separate multiple links with spaces"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:64
msgid "下载作品文件"
msgstr "Download notes files"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:65
msgid "读取剪贴板"
msgstr "Read the clipboard"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:66
msgid "清空输入框"
msgstr "Clear the input box"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:82
msgid "免责声明\n"
msgstr ""
"Disclaimer for XHS-Downloader:\n"
"\n"
"1.The use of this project is entirely at the user's own discretion and risk. "
"The author assumes no responsibility or liability of any kind for any loss, "
"damage, or risk arising from the user's use of this project.\n"
"2.The code and functionalities provided by the author of this project are "
"developed based on current knowledge and technology. The author makes every "
"effort to ensure the correctness and security of the code according to "
"current technical standards but does not guarantee that the code is "
"completely free of errors or defects.\n"
"3.All third-party libraries, plugins, or services used by this project "
"follow their original open-source or commercial licenses. Users must review "
"and comply with these license agreements accordingly. The author assumes no "
"responsibility for the stability, security, or compliance of any third-party "
"components.\n"
"4.When using this project, users must strictly comply with the requirements "
"of the GNU General Public License v3.0 and clearly indicate in appropriate "
"places that the code was used under the GNU General Public License v3.0.\n"
"5.When using the code and functionalities of this project, users must "
"independently research relevant laws and regulations and ensure that their "
"usage is legal and compliant. Any legal liabilities or risks arising from "
"violations of laws and regulations shall be borne solely by the user.\n"
"6.Users must not use this tool to engage in any activities that infringe "
"intellectual property rights, including but not limited to downloading or "
"distributing copyrighted content without authorization. Developers do not "
"participate in, support, or endorse the acquisition or distribution of any "
"illegal or unauthorized content.\n"
"7.This project assumes no responsibility for the compliance of data "
"processing activities (including collection, storage, and transmission) "
"performed by users. Users must comply with relevant laws and regulations and "
"ensure that such activities are lawful and proper. Legal liabilities "
"resulting from non-compliant operations shall be borne by the user.\n"
"8.Under no circumstances may users associate the author, contributors, or "
"other related parties of this project with their usage of the project, nor "
"may they hold these parties liable for any loss or damage resulting from "
"such usage.\n"
"9.The author of this project will not provide a paid version of the XHS-"
"Downloader project, nor will they offer any commercial services related to "
"it.\n"
"10.Any secondary development, modification, or compilation based on this "
"project is unrelated to the original author. The original author assumes no "
"liability for any consequences resulting from such secondary development. "
"Users bear full responsibility for all outcomes arising from such "
"modifications.\n"
"11.This project does not grant users any patent licenses. If the use of this "
"project leads to patent disputes or infringement, the user assumes all "
"associated risks and responsibilities. Without written authorization from "
"the author or rights holder, users may not use this project for any "
"commercial promotion, advertising, or re-licensing.\n"
"12.The author reserves the right to terminate service to any user who "
"violates this disclaimer at any time and may require them to destroy all "
"obtained code and derivative works.\n"
"13.The author reserves the right to update this disclaimer at any time "
"without prior notice. Continued use of the project constitutes acceptance of "
"the revised terms.\n"
"\n"
"Before using the code and functionalities of this project, please carefully "
"consider and accept the above disclaimer. If you have any questions or "
"disagree with the above statements, please do not use the code and "
"functionalities of this project. If you do use the code and functionalities "
"of this project, it shall be deemed that you have fully understood and "
"accepted the above disclaimer and voluntarily assume all risks and "
"consequences associated with its use.\n"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:93
msgid "未输入任何小红书作品链接"
msgstr "No RedNote notes links provided"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:122
msgid "下载小红书作品文件失败"
msgstr "Failed to download the RedNote notes files"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\loading.py:19
msgid "程序处理中..."
msgstr "Processing..."
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\monitor.py:21
msgid "关闭监听"
msgstr "Close"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\monitor.py:33
msgid "已启动监听剪贴板模式"
msgstr "Currently in monitoring clipboard mode"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\monitor.py:35
msgid "退出监听剪贴板模式"
msgstr "Exit monitoring clipboard mode"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\record.py:23
msgid "请输入待删除的小红书作品链接或作品 ID"
msgstr "Please enter the link or ID of the RedNote notes to be deleted"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\record.py:26
msgid ""
"支持输入作品 ID 或包含作品 ID 的作品链接,多个链接或 ID 之间使用空格分隔"
msgstr ""
"Support input of notes ID or links containing notes ID, with multiple links "
"or IDs separated by spaces"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\record.py:32
msgid "删除指定作品 ID"
msgstr "Delete specified notes ID"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\record.py:46
msgid "删除下载记录成功"
msgstr "Successfully deleted download record"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:30
msgid "作品数据 / 文件保存根路径"
msgstr "Root path for saving notes data / files"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:35
msgid "程序根路径"
msgstr "Program root path"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:64
msgid "内置 Chrome User Agent"
msgstr "Chrome User Agent"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:69
msgid "小红书网页版 Cookie"
msgstr "RedNote Web Cookie"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:83
msgid "不使用代理"
msgstr "No proxy"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:120
msgid "记录作品详细数据"
msgstr "Record notes data"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:125
msgid "作品归档保存模式"
msgstr "notes Archiving Mode"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:130
msgid "视频作品下载开关"
msgstr "Video download switch"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:135
msgid "图文作品下载开关"
msgstr "Image download switch"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:144
msgid "动图文件下载开关"
msgstr "LivePhoto download switch"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:154
msgid "作者归档保存模式"
msgstr "Author Archiving Mode"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:159
msgid "更新文件修改时间"
msgstr "Update File Modification Time"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:168
msgid "脚本服务器开关"
msgstr "Script server switch"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:176
msgid "图片下载格式"
msgstr "Image download format"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:180
msgid "程序语言"
msgstr "Program language"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:184
msgid "视频下载偏好"
msgstr "Video preferences"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:213
msgid "保存配置"
msgstr "Save settings"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:217
msgid "放弃更改"
msgstr "Discard changes"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:227
msgid "小红书网页版 Cookie,无需登录,参数已设置"
msgstr ""
"RedNote web version cookie, no login required, parameters have been set"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:228
msgid "小红书网页版 Cookie,无需登录,参数未设置"
msgstr "RedNote web version cookie, no login required, parameters not set"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:26
msgid "正在检查新版本,请稍等..."
msgstr "Checking for new version, please wait..."
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:45
#, python-brace-format
msgid "检测到新版本:{0}.{1}"
msgstr "Detected new version: {0} {1}"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:53
msgid "当前版本为开发版, 可更新至正式版"
msgstr "Detected a new official version"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:58
msgid "当前已是最新开发版"
msgstr "You are already using the latest development version"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:63
msgid "当前已是最新正式版"
msgstr "You are already using the latest official version"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:70
msgid "检测新版本失败"
msgstr "Failed to check for a new version"
================================================
FILE: locale/generate_path.py
================================================
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
def find_python_files(dir_, file):
with open(file, "w", encoding="utf-8") as f:
for py_file in dir_.rglob("*.py"): # 递归查找所有 .py 文件
f.write(str(py_file) + "\n") # 写入文件路径
# 设置源目录和输出文件
source_directory = ROOT.joinpath("source") # 源目录
output_file = "py_files.txt" # 输出文件名
find_python_files(source_directory, output_file)
print(f"所有 .py 文件路径已保存到 {output_file}")
================================================
FILE: locale/po_to_mo.py
================================================
from pathlib import Path
from subprocess import run
ROOT = Path(__file__).resolve().parent
def scan_directory():
return [
item.joinpath("LC_MESSAGES/xhs.po") for item in ROOT.iterdir() if item.is_dir()
]
def generate_map(files: list[Path]):
return [(i, i.with_suffix(".mo")) for i in files]
def generate_mo(maps: list[tuple[Path, Path]]):
for i, j in maps:
command = f'msgfmt --check -o "{j}" "{i}"'
print(run(command, shell=True, text=True))
if __name__ == "__main__":
generate_mo(generate_map(scan_directory()))
================================================
FILE: locale/xhs.pot
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: XHS-Downloader 2.7\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-02-06 16:45+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:223
#, python-brace-format
msgid "作品 {0} 存在下载记录,跳过下载"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:247
msgid "提取作品文件下载地址失败"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:280
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:328
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:743
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:930
msgid "提取小红书作品链接失败"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:288
#, python-brace-format
msgid "共 {0} 个小红书作品待处理..."
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:309
#, python-brace-format
msgid "共处理 {0} 个作品,成功 {1} 个,失败 {2} 个,跳过 {3} 个"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:400
#, python-brace-format
msgid "作品 {0} 存在下载记录,跳过处理"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:404
#, python-brace-format
msgid "开始处理作品:{0}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:412
#, python-brace-format
msgid "{0} 获取数据失败"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:425
#, python-brace-format
msgid "{0} 提取数据失败"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:439
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:82
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:81
msgid "视频"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:442
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:89
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:82
msgid "图文"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:443
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:90
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:81
msgid "图集"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:447
#, python-brace-format
msgid "未知的作品类型:{0}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:505
#, python-brace-format
msgid "作品处理完成:{0}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:611
msgid ""
"程序会自动读取并提取剪贴板中的小红书作品链接,并自动下载链接对应的作品文件,"
"如需关闭,请点击关闭按钮,或者向剪贴板写入 “close” 文本!"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:712
msgid "跳转至项目 GitHub 仓库"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:713
msgid "重定向至项目 GitHub 仓库主页"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:721
msgid "获取作品数据及下载地址"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:753
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:937
msgid "获取小红书作品数据成功"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:755
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:939
msgid "获取小红书作品数据失败"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:825
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:870
msgid "小红书作品链接"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:873
msgid "指定需要下载的图文作品序号"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:877
msgid "是否需要返回作品信息数据"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:891
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:896
msgid "作品文件下载任务执行完毕"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:901
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:906
msgid "作品文件下载任务未执行"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:131
msgid "视频作品下载功能已关闭,跳过下载"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:150
msgid "图文作品下载功能已关闭,跳过下载"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:182
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:192
#, python-brace-format
msgid "{0} 文件已存在,跳过下载"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:221
#, python-brace-format
msgid "文件 {0} 缓存异常,重新下载"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:248
#, python-brace-format
msgid "文件 {0} 下载成功"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:254
#, python-brace-format
msgid "网络异常,{0} 下载失败,错误信息: {1}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:332
#, python-brace-format
msgid "文件 {0} 格式判断失败,错误信息:{1}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:53
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:58
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:79
msgid "未知"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\request.py:66
#, python-brace-format
msgid "网络异常,{0} 请求失败: {1}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:129
msgid "小红书作品链接,多个链接使用空格分隔"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:136
msgid ""
"下载指定序号的图片文件,仅对图文/图集作品生效;多个序号输入示例:\"1 3 5 7\""
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:141
msgid "作品数据/文件保存根路径"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:142
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:40
msgid "作品文件储存文件夹名称"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:143
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:49
msgid "作品文件名称格式"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:145
msgid "小红书网页版 Cookie,无需登录"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:146
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:78
msgid "网络代理"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:147
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:88
msgid "请求数据超时限制,单位:秒"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:153
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:98
msgid "下载文件时,每次从服务器获取的数据块大小,单位:字节"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:156
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:108
msgid "请求数据失败时,重试的最大次数"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:157
msgid "是否记录作品数据至文件"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:162
msgid "图文作品文件下载格式,支持:PNG、WEBP、JPEG、HEIC、AUTO"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:164
msgid "动态图片下载开关"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:165
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:149
msgid "作品下载记录开关"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:170
msgid "是否将每个作品的文件储存至单独的文件夹"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:176
msgid "是否将每个作者的作品储存至单独的文件夹"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:183
msgid "是否将作品文件的修改时间属性修改为作品的发布时间"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:187
msgid "设置程序语言,目前支持:zh_CN、en_US"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:188
msgid "读取指定配置文件"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:190
#, python-brace-format
msgid "从指定的浏览器读取小红书网页版 Cookie,支持:{0}; 输入浏览器名称或序号"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:203
msgid "是否更新配置文件"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:209
msgid "查看详细参数说明"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:210
msgid "查看 XHS-Downloader 版本"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\browser.py:53
#, python-brace-format
msgid ""
"读取指定浏览器的 Cookie 并写入配置文件\n"
"Windows 系统需要以管理员身份运行程序才能读取 Chromium、Chrome、Edge 浏览器 "
"Cookie!\n"
"{options}\n"
"请输入浏览器名称或序号:"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\browser.py:63
msgid "未选择浏览器!"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\browser.py:75
msgid "浏览器名称或序号输入错误!"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\browser.py:81
msgid "获取 Cookie 失败,未找到 Cookie 数据!"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\browser.py:119
msgid "从浏览器读取 Cookie 功能不支持当前平台!"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\cleaner.py:45
msgid "不受支持的操作系统类型,可能无法正常去除非法字符!"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\manager.py:240
#, python-brace-format
msgid "代理 {0} 测试成功"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\manager.py:244
#, python-brace-format
msgid "代理 {0} 测试超时"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\manager.py:252
#, python-brace-format
msgid "代理 {0} 测试失败:{1}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:55
#, python-brace-format
msgid "{old_folder} 文件夹不存在,跳过处理"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:81
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:101
msgid "文件夹"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:85
#, python-brace-format
msgid "文件夹 {old_folder} 已重命名为 {new_folder}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:105
#, python-brace-format
msgid "文件夹 {old_} 重命名为 {new_}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:171
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:185
msgid "文件"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:175
#, python-brace-format
msgid "文件 {old_file} 重命名为 {new_file}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:193
#, python-brace-format
msgid "{type} {old}被占用,重命名失败: {error}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:202
#, python-brace-format
msgid "{type} {new}名称重复,重命名失败: {error}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:211
#, python-brace-format
msgid "处理{type} {old}时发生预期之外的错误: {error}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\tools.py:32
msgid ""
"如需重新尝试处理该对象,请关闭所有正在访问该对象的窗口或程序,然后直接按下回"
"车键!\n"
"如需跳过处理该对象,请输入任意字符后按下回车键!"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:20
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:29
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\monitor.py:20
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:15
msgid "退出程序"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:21
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:30
msgid "检查更新"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:22
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\record.py:35
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:16
msgid "返回首页"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:35
msgid "如果 XHS-Downloader 对您有帮助,请考虑为它点个 Star,感谢您的支持!"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:42
msgid "Discord 社区"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:46
msgid "邀请链接:"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:48
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:61
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:70
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:56
msgid "点击访问"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:51
msgid "作者的其他开源项目"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:31
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:231
msgid "程序设置"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:32
msgid "下载记录"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:33
msgid "开启监听"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:34
msgid "关于项目"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:49
msgid "开源协议: "
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:52
msgid "项目地址: "
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:59
msgid "请输入小红书图文/视频作品链接"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:62
msgid "多个链接之间使用空格分隔"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:64
msgid "下载作品文件"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:65
msgid "读取剪贴板"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:66
msgid "清空输入框"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:82
msgid "免责声明\n"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:93
msgid "未输入任何小红书作品链接"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:122
msgid "下载小红书作品文件失败"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\loading.py:19
msgid "程序处理中..."
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\monitor.py:21
msgid "关闭监听"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\monitor.py:33
msgid "已启动监听剪贴板模式"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\monitor.py:35
msgid "退出监听剪贴板模式"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\record.py:23
msgid "请输入待删除的小红书作品链接或作品 ID"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\record.py:26
msgid ""
"支持输入作品 ID 或包含作品 ID 的作品链接,多个链接或 ID 之间使用空格分隔"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\record.py:32
msgid "删除指定作品 ID"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\record.py:46
msgid "删除下载记录成功"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:30
msgid "作品数据 / 文件保存根路径"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:35
msgid "程序根路径"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:64
msgid "内置 Chrome User Agent"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:69
msgid "小红书网页版 Cookie"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:83
msgid "不使用代理"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:120
msgid "记录作品详细数据"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:125
msgid "作品归档保存模式"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:130
msgid "视频作品下载开关"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:135
msgid "图文作品下载开关"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:144
msgid "动图文件下载开关"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:154
msgid "作者归档保存模式"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:159
msgid "更新文件修改时间"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:168
msgid "脚本服务器开关"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:176
msgid "图片下载格式"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:180
msgid "程序语言"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:184
msgid "视频下载偏好"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:213
msgid "保存配置"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:217
msgid "放弃更改"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:227
msgid "小红书网页版 Cookie,无需登录,参数已设置"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:228
msgid "小红书网页版 Cookie,无需登录,参数未设置"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:26
msgid "正在检查新版本,请稍等..."
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:45
#, python-brace-format
msgid "检测到新版本:{0}.{1}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:53
msgid "当前版本为开发版, 可更新至正式版"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:58
msgid "当前已是最新开发版"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:63
msgid "当前已是最新正式版"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:70
msgid "检测新版本失败"
msgstr ""
================================================
FILE: locale/zh_CN/LC_MESSAGES/xhs.po
================================================
# Chinese translations for XHS-Downloader package
# Copyright (C) 2024 THE XHS-Downloader'S COPYRIGHT HOLDER
# This file is distributed under the same license as the XHS-Downloader package.
# FIRST AUTHOR <yonglelolu@foxmail.com>, 2024.
#
msgid ""
msgstr ""
"Project-Id-Version: XHS-Downloader 2.7\n"
"Report-Msgid-Bugs-To: <yonglelolu@foxmail.com>\n"
"POT-Creation-Date: 2026-02-06 16:45+0800\n"
"PO-Revision-Date: 2024-12-22 14:14+0800\n"
"Last-Translator: <yonglelolu@foxmail.com>\n"
"Language-Team: Chinese (simplified)\n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:223
#, python-brace-format
msgid "作品 {0} 存在下载记录,跳过下载"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:247
msgid "提取作品文件下载地址失败"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:280
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:328
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:743
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:930
msgid "提取小红书作品链接失败"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:288
#, python-brace-format
msgid "共 {0} 个小红书作品待处理..."
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:309
#, python-brace-format
msgid "共处理 {0} 个作品,成功 {1} 个,失败 {2} 个,跳过 {3} 个"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:400
#, python-brace-format
msgid "作品 {0} 存在下载记录,跳过处理"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:404
#, python-brace-format
msgid "开始处理作品:{0}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:412
#, python-brace-format
msgid "{0} 获取数据失败"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:425
#, python-brace-format
msgid "{0} 提取数据失败"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:439
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:82
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:81
msgid "视频"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:442
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:89
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:82
msgid "图文"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:443
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:90
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:81
msgid "图集"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:447
#, python-brace-format
msgid "未知的作品类型:{0}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:505
#, python-brace-format
msgid "作品处理完成:{0}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:611
msgid ""
"程序会自动读取并提取剪贴板中的小红书作品链接,并自动下载链接对应的作品文件,"
"如需关闭,请点击关闭按钮,或者向剪贴板写入 “close” 文本!"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:712
msgid "跳转至项目 GitHub 仓库"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:713
msgid "重定向至项目 GitHub 仓库主页"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:721
msgid "获取作品数据及下载地址"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:753
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:937
msgid "获取小红书作品数据成功"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:755
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:939
msgid "获取小红书作品数据失败"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:825
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:870
msgid "小红书作品链接"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:873
msgid "指定需要下载的图文作品序号"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:877
msgid "是否需要返回作品信息数据"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:891
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:896
msgid "作品文件下载任务执行完毕"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:901
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\app.py:906
msgid "作品文件下载任务未执行"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:131
msgid "视频作品下载功能已关闭,跳过下载"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:150
msgid "图文作品下载功能已关闭,跳过下载"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:182
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:192
#, python-brace-format
msgid "{0} 文件已存在,跳过下载"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:221
#, python-brace-format
msgid "文件 {0} 缓存异常,重新下载"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:248
#, python-brace-format
msgid "文件 {0} 下载成功"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:254
#, python-brace-format
msgid "网络异常,{0} 下载失败,错误信息: {1}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\download.py:332
#, python-brace-format
msgid "文件 {0} 格式判断失败,错误信息:{1}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:53
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:58
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\explore.py:79
msgid "未知"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\application\request.py:66
#, python-brace-format
msgid "网络异常,{0} 请求失败: {1}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:129
msgid "小红书作品链接,多个链接使用空格分隔"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:136
msgid ""
"下载指定序号的图片文件,仅对图文/图集作品生效;多个序号输入示例:\"1 3 5 7\""
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:141
msgid "作品数据/文件保存根路径"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:142
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:40
msgid "作品文件储存文件夹名称"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:143
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:49
msgid "作品文件名称格式"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:145
msgid "小红书网页版 Cookie,无需登录"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:146
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:78
msgid "网络代理"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:147
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:88
msgid "请求数据超时限制,单位:秒"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:153
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:98
msgid "下载文件时,每次从服务器获取的数据块大小,单位:字节"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:156
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:108
msgid "请求数据失败时,重试的最大次数"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:157
msgid "是否记录作品数据至文件"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:162
msgid "图文作品文件下载格式,支持:PNG、WEBP、JPEG、HEIC、AUTO"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:164
msgid "动态图片下载开关"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:165
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:149
msgid "作品下载记录开关"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:170
msgid "是否将每个作品的文件储存至单独的文件夹"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:176
msgid "是否将每个作者的作品储存至单独的文件夹"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:183
msgid "是否将作品文件的修改时间属性修改为作品的发布时间"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:187
msgid "设置程序语言,目前支持:zh_CN、en_US"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:188
msgid "读取指定配置文件"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:190
#, python-brace-format
msgid "从指定的浏览器读取小红书网页版 Cookie,支持:{0}; 输入浏览器名称或序号"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:203
msgid "是否更新配置文件"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:209
msgid "查看详细参数说明"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\CLI\main.py:210
msgid "查看 XHS-Downloader 版本"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\browser.py:53
#, python-brace-format
msgid ""
"读取指定浏览器的 Cookie 并写入配置文件\n"
"Windows 系统需要以管理员身份运行程序才能读取 Chromium、Chrome、Edge 浏览器 "
"Cookie!\n"
"{options}\n"
"请输入浏览器名称或序号:"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\browser.py:63
msgid "未选择浏览器!"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\browser.py:75
msgid "浏览器名称或序号输入错误!"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\browser.py:81
msgid "获取 Cookie 失败,未找到 Cookie 数据!"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\browser.py:119
msgid "从浏览器读取 Cookie 功能不支持当前平台!"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\expansion\cleaner.py:45
msgid "不受支持的操作系统类型,可能无法正常去除非法字符!"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\manager.py:240
#, python-brace-format
msgid "代理 {0} 测试成功"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\manager.py:244
#, python-brace-format
msgid "代理 {0} 测试超时"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\manager.py:252
#, python-brace-format
msgid "代理 {0} 测试失败:{1}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:55
#, python-brace-format
msgid "{old_folder} 文件夹不存在,跳过处理"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:81
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:101
msgid "文件夹"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:85
#, python-brace-format
msgid "文件夹 {old_folder} 已重命名为 {new_folder}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:105
#, python-brace-format
msgid "文件夹 {old_} 重命名为 {new_}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:171
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:185
msgid "文件"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:175
#, python-brace-format
msgid "文件 {old_file} 重命名为 {new_file}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:193
#, python-brace-format
msgid "{type} {old}被占用,重命名失败: {error}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:202
#, python-brace-format
msgid "{type} {new}名称重复,重命名失败: {error}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\mapping.py:211
#, python-brace-format
msgid "处理{type} {old}时发生预期之外的错误: {error}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\module\tools.py:32
msgid ""
"如需重新尝试处理该对象,请关闭所有正在访问该对象的窗口或程序,然后直接按下回"
"车键!\n"
"如需跳过处理该对象,请输入任意字符后按下回车键!"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:20
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:29
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\monitor.py:20
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:15
msgid "退出程序"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:21
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:30
msgid "检查更新"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:22
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\record.py:35
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:16
msgid "返回首页"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:35
msgid "如果 XHS-Downloader 对您有帮助,请考虑为它点个 Star,感谢您的支持!"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:42
msgid "Discord 社区"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:46
msgid "邀请链接:"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:48
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:61
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:70
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:56
msgid "点击访问"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\about.py:51
msgid "作者的其他开源项目"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:31
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:231
msgid "程序设置"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:32
msgid "下载记录"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:33
msgid "开启监听"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:34
msgid "关于项目"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:49
msgid "开源协议: "
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:52
msgid "项目地址: "
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:59
msgid "请输入小红书图文/视频作品链接"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:62
msgid "多个链接之间使用空格分隔"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:64
msgid "下载作品文件"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:65
msgid "读取剪贴板"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:66
msgid "清空输入框"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:82
msgid "免责声明\n"
msgstr ""
"关于 XHS-Downloader 的 免责声明:\n"
"\n"
"1.使用者对本项目的使用由使用者自行决定,并自行承担风险。作者对使用者使用本项"
"目所产生的任何损失、责任、或风险概不负责。\n"
"2.本项目的作者提供的代码和功能是基于现有知识和技术的开发成果。作者按现有技术"
"水平努力确保代码的正确性和安全性,但不保证代码完全没有错误或缺陷。\n"
"3.本项目依赖的所有第三方库、插件或服务各自遵循其原始开源或商业许可,使用者需"
"自行查阅并遵守相应协议,作者不对第三方组件的稳定性、安全性及合规性承担任何责"
"任。\n"
"4.使用者在使用本项目时必须严格遵守 GNU General Public License v3.0 的要求,并"
"在适当的地方注明使用了 GNU General Public License v3.0 的代码。\n"
"5.使用者在使用本项目的代码和功能时,必须自行研究相关法律法规,并确保其使用行"
"为合法合规。任何因违反法律法规而导致的法律责任和风险,均由使用者自行承担。\n"
"6.使用者不得使用本工具从事任何侵犯知识产权的行为,包括但不限于未经授权下载、"
"传播受版权保护的内容,开发者不参与、不支持、不认可任何非法内容的获取或分"
"发。\n"
"7.本项目不对使用者涉及的数据收集、存储、传输等处理活动的合规性承担责任。使用"
"者应自行遵守相关法律法规,确保处理行为合法正当;因违规操作导致的法律责任由使"
"用者自行承担。\n"
"8.使用者在任何情况下均不得将本项目的作者、贡献者或其他相关方与使用者的使用行"
"为联系起来,或要求其对使用者使用本项目所产生的任何损失或损害负责。\n"
"9.本项目的作者不会提供 XHS-Downloader 项目的付费版本,也不会提供与 XHS-"
"Downloader 项目相关的任何商业服务。\n"
"10.基于本项目进行的任何二次开发、修改或编译的程序与原创作者无关,原创作者不承"
"担与二次开发行为或其结果相关的任何责任,使用者应自行对因二次开发可能带来的各"
"种情况负全部责任。\n"
"11.本项目不授予使用者任何专利许可;若使用本项目导致专利纠纷或侵权,使用者自行"
"承担全部风险和责任。未经作者或权利人书面授权,不得使用本项目进行任何商业宣"
"传、推广或再授权。\n"
"12.作者保留随时终止向任何违反本声明的使用者提供服务的权利,并可能要求其销毁已"
"获取的代码及衍生作品。\n"
"13.作者保留在不另行通知的情况下更新本声明的权利,使用者持续使用即视为接受修订"
"后的条款。\n"
"\n"
"在使用本项目的代码和功能之前,请您认真考虑并接受以上免责声明。如果您对上述声"
"明有任何疑问或不同意,请不要使用本项目的代码和功能。如果您使用了本项目的代码"
"和功能,则视为您已完全理解并接受上述免责声明,并自愿承担使用本项目的一切风险"
"和后果。\n"
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:93
msgid "未输入任何小红书作品链接"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\index.py:122
msgid "下载小红书作品文件失败"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\loading.py:19
msgid "程序处理中..."
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\monitor.py:21
msgid "关闭监听"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\monitor.py:33
msgid "已启动监听剪贴板模式"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\monitor.py:35
msgid "退出监听剪贴板模式"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\record.py:23
msgid "请输入待删除的小红书作品链接或作品 ID"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\record.py:26
msgid ""
"支持输入作品 ID 或包含作品 ID 的作品链接,多个链接或 ID 之间使用空格分隔"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\record.py:32
msgid "删除指定作品 ID"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\record.py:46
msgid "删除下载记录成功"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:30
msgid "作品数据 / 文件保存根路径"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:35
msgid "程序根路径"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:64
msgid "内置 Chrome User Agent"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:69
msgid "小红书网页版 Cookie"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:83
msgid "不使用代理"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:120
msgid "记录作品详细数据"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:125
msgid "作品归档保存模式"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:130
msgid "视频作品下载开关"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:135
msgid "图文作品下载开关"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:144
msgid "动图文件下载开关"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:154
msgid "作者归档保存模式"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:159
msgid "更新文件修改时间"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:168
msgid "脚本服务器开关"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:176
msgid "图片下载格式"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:180
msgid "程序语言"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:184
msgid "视频下载偏好"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:213
msgid "保存配置"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:217
msgid "放弃更改"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:227
msgid "小红书网页版 Cookie,无需登录,参数已设置"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\setting.py:228
msgid "小红书网页版 Cookie,无需登录,参数未设置"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:26
msgid "正在检查新版本,请稍等..."
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:45
#, python-brace-format
msgid "检测到新版本:{0}.{1}"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:53
msgid "当前版本为开发版, 可更新至正式版"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:58
msgid "当前已是最新开发版"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:63
msgid "当前已是最新正式版"
msgstr ""
#: C:\Users\You\PycharmProjects\XHS-Downloader\source\TUI\update.py:70
msgid "检测新版本失败"
msgstr ""
================================================
FILE: main.py
================================================
from asyncio import run
from asyncio.exceptions import CancelledError
from contextlib import suppress
from sys import argv
from source import Settings
from source import XHS
from source import XHSDownloader
from source import cli
async def app():
async with XHSDownloader() as xhs:
await xhs.run_async()
async def api_server(
host="0.0.0.0",
port=5556,
log_level="info",
):
async with XHS(**Settings().run()) as xhs:
await xhs.run_api_server(
host,
port,
log_level,
)
async def mcp_server(
transport="streamable-http",
host="0.0.0.0",
port=5556,
log_level="INFO",
):
async with XHS(**Settings().run()) as xhs:
await xhs.run_mcp_server(
transport=transport,
host=host,
port=port,
log_level=log_level,
)
if __name__ == "__main__":
with suppress(
KeyboardInterrupt,
CancelledError,
):
# TODO: 重构优化
if len(argv) == 1:
run(app())
elif argv[1].upper() == "API":
run(api_server())
elif argv[1].upper() == "MCP":
run(mcp_server())
# run(mcp_server("stdio"))
else:
cli()
================================================
FILE: pyproject.toml
================================================
[project]
name = "XHS-Downloader"
version = "2.8"
description = "小红书(XiaoHongShu、RedNote)链接提取/作品采集工具:提取账号发布、收藏、点赞、专辑作品链接;提取搜索结果作品、用户链接;采集小红书作品信息;提取小红书作品下载地址;下载小红书无水印作品文件"
authors = [
{ name = "JoeanAmier", email = "yonglelolu@foxmail.com" },
]
readme = "README.md"
license = "GPL-3.0"
requires-python = ">=3.12"
dependencies = [
"aiofiles>=25.1.0",
"aiosqlite>=0.22.1",
"click>=8.3.1",
"emoji>=2.15.0",
"fastapi>=0.128.5",
"fastmcp>=2.14.5",
"httpx[http2,socks]>=0.28.1",
"lxml>=6.0.2",
"pyperclip>=1.11.0",
"pyyaml>=6.0.3",
"textual>=7.5.0",
"uvicorn>=0.40.0",
"websockets>=16.0",
]
[project.urls]
Repository = "https://github.com/JoeanAmier/XHS-Downloader"
[tool.uv.pip]
index-url = "https://mirrors.ustc.edu.cn/pypi/simple"
[tool.ruff]
# Exclude a variety of commonly ignored directories.
exclude = [
".bzr",
".direnv",
".eggs",
".git",
".git-rewrite",
".hg",
".ipynb_checkpoints",
".mypy_cache",
".nox",
".pants.d",
".pyenv",
".pytest_cache",
".pytype",
".ruff_cache",
".svn",
".tox",
".venv",
".vscode",
"__pypackages__",
"_build",
"buck-out",
"build",
"dist",
"node_modules",
"site-packages",
"venv",
]
# Same as Black.
line-length = 88
indent-width = 4
# Assume Python 3.12
target-version = "py312"
[tool.ruff.lint]
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
# McCabe complexity (`C901`) by default.
select = ["E4", "E7", "E9", "F"]
ignore = []
# Allow fix for all enabled rules (when `--fix`) is provided.
fixable = ["ALL"]
unfixable = []
# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
[tool.ruff.format]
# Like Black, use double quotes for strings.
quote-style = "double"
# Like Black, indent with spaces, rather than tabs.
indent-style = "space"
# Like Black, respect magic trailing commas.
skip-magic-trailing-comma = false
# Like Black, automatically detect the appropriate line ending.
line-ending = "auto"
# Enable auto-formatting of code examples in docstrings. Markdown,
# reStructuredText code/literal blocks and doctests are all supported.
#
# This is currently disabled by default, but it is planned for this
# to be opt-out in the future.
docstring-code-format = false
# Set the line length limit used when formatting code snippets in
# docstrings.
#
# This only has an effect when the `docstring-code-format` setting is
# enabled.
docstring-code-line-length = "dynamic"
[dependency-groups]
dev = [
"nuitka>=4.0",
"pycryptodome>=3.23.0",
"pyinstaller>=6.17.0",
"textual-dev>=1.7.0",
]
================================================
FILE: requirements.txt
================================================
# This file was autogenerated by uv via the following command:
# uv pip compile pyproject.toml --no-deps --no-strip-extras -o requirements.txt
aiofiles==25.1.0
# via xhs-downloader (pyproject.toml)
aiosqlite==0.22.1
# via xhs-downloader (pyproject.toml)
click==8.3.1
# via xhs-downloader (pyproject.toml)
emoji==2.15.0
# via xhs-downloader (pyproject.toml)
fastapi==0.135.3
# via xhs-downloader (pyproject.toml)
fastmcp>=3.1.0
# via xhs-downloader (pyproject.toml)
httpx[http2,socks]==0.28.1
# via xhs-downloader (pyproject.toml)
lxml==6.0.2
# via xhs-downloader (pyproject.toml)
pyperclip==1.11.0
# via xhs-downloader (pyproject.toml)
pyyaml==6.0.3
# via xhs-downloader (pyproject.toml)
textual==8.2.0
# via xhs-downloader (pyproject.toml)
uvicorn==0.42.0
# via xhs-downloader (pyproject.toml)
websockets==16.0
# via xhs-downloader (pyproject.toml)
================================================
FILE: source/CLI/__init__.py
================================================
from .main import cli
__all__ = ["cli"]
================================================
FILE: source/CLI/main.py
================================================
from asyncio import run
from contextlib import suppress
from pathlib import Path as Root
from textwrap import fill
from click import Context
from click import (
command,
option,
Path,
Choice,
pass_context,
echo,
)
from rich import print
from rich.panel import Panel
from rich.table import Table
from source.application import XHS
# from source.expansion import BrowserCookie
from source.module import (
ROOT,
PROJECT,
)
from source.module import Settings
from source.translation import switch_language, _
__all__ = ["cli"]
def check_value(function):
def inner(ctx: Context, param, value):
return function(ctx, param, value) if value else None
return inner
class CLI:
def __init__(self, ctx: Context, **kwargs):
self.ctx = ctx
self.url = ctx.params.pop("url")
self.index = self.__format_index(ctx.params.pop("index"))
self.path = ctx.params.pop("settings")
self.update = ctx.params.pop("update_settings")
self.settings = Settings(self.__check_settings_path())
self.parameter = (
self.settings.run()
| self.__clean_params(ctx.params)
| {"script_server": False}
)
self.APP = XHS(**self.parameter)
async def __aenter__(self):
await self.APP.__aenter__()
return self
async def __aexit__(self, exc_type, exc_value, traceback):
await self.APP.__aexit__(exc_type, exc_value, traceback)
async def run(self):
if self.url:
await self.APP.extract_cli(self.url, index=self.index)
self.__update_settings()
def __update_settings(self):
if self.update:
self.settings.update(self.parameter)
def __check_settings_path(self) -> Path:
if not self.path:
return ROOT
return s.parent if (s := Root(self.path)).is_file() else ROOT
@staticmethod
def __merge_cookie(data: dict) -> None:
if not data["cookie"] and (bc := data["browser_cookie"]):
data["cookie"] = bc
data.pop("browser_cookie")
def __clean_params(self, data: dict) -> dict:
# self.__merge_cookie(data)
return {k: v for k, v in data.items() if v != None}
@staticmethod
def __format_index(index: str) -> list:
if index:
result = []
values = index.split()
for i in values:
with suppress(ValueError):
result.append(int(i))
return result
return []
@staticmethod
@check_value
def version(ctx: Context, param, value) -> None:
echo(PROJECT)
ctx.exit()
# @staticmethod
# @check_value
# def read_cookie(ctx: Conte
gitextract_832couqt/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ ├── dependabot.yml
│ └── workflows/
│ ├── Close_Stale_Issues_and_PRs.yaml
│ ├── Delete_untagged_images.yml
│ ├── Manually_build_executable_programs.yml
│ ├── Manually_docker_image.yml
│ ├── Release_build_executable_program.yml
│ └── Release_docker_image.yml
├── .gitignore
├── .python-version
├── Dockerfile
├── LICENSE
├── README.md
├── README_EN.md
├── example.py
├── locale/
│ ├── README.md
│ ├── en_US/
│ │ └── LC_MESSAGES/
│ │ ├── xhs.mo
│ │ └── xhs.po
│ ├── generate_path.py
│ ├── po_to_mo.py
│ ├── xhs.pot
│ └── zh_CN/
│ └── LC_MESSAGES/
│ ├── xhs.mo
│ └── xhs.po
├── main.py
├── pyproject.toml
├── requirements.txt
├── source/
│ ├── CLI/
│ │ ├── __init__.py
│ │ └── main.py
│ ├── TUI/
│ │ ├── __init__.py
│ │ ├── about.py
│ │ ├── app.py
│ │ ├── index.py
│ │ ├── loading.py
│ │ ├── monitor.py
│ │ ├── progress.py
│ │ ├── record.py
│ │ ├── setting.py
│ │ └── update.py
│ ├── __init__.py
│ ├── application/
│ │ ├── __init__.py
│ │ ├── app.py
│ │ ├── download.py
│ │ ├── explore.py
│ │ ├── image.py
│ │ ├── request.py
│ │ ├── user_posted.py
│ │ └── video.py
│ ├── expansion/
│ │ ├── __init__.py
│ │ ├── browser.py
│ │ ├── cleaner.py
│ │ ├── converter.py
│ │ ├── error.py
│ │ ├── file_folder.py
│ │ ├── namespace.py
│ │ ├── pyi_rth_beartype.py
│ │ └── truncate.py
│ ├── module/
│ │ ├── __init__.py
│ │ ├── extend.py
│ │ ├── manager.py
│ │ ├── mapping.py
│ │ ├── model.py
│ │ ├── recorder.py
│ │ ├── script.py
│ │ ├── settings.py
│ │ ├── static.py
│ │ └── tools.py
│ └── translation/
│ ├── __init__.py
│ └── translate.py
└── static/
├── 20250619.js
├── Release_Notes.md
├── XHS-Downloader.icns
├── XHS-Downloader.js
├── XHS-Downloader.tcss
└── 自动滚动页面.js
SYMBOL INDEX (342 symbols across 40 files)
FILE: example.py
function example (line 9) | async def example():
function example_api (line 77) | async def example_api():
function test (line 94) | async def test():
FILE: locale/generate_path.py
function find_python_files (line 6) | def find_python_files(dir_, file):
FILE: locale/po_to_mo.py
function scan_directory (line 7) | def scan_directory():
function generate_map (line 13) | def generate_map(files: list[Path]):
function generate_mo (line 17) | def generate_mo(maps: list[tuple[Path, Path]]):
FILE: main.py
function app (line 12) | async def app():
function api_server (line 17) | async def api_server(
function mcp_server (line 30) | async def mcp_server(
FILE: source/CLI/main.py
function check_value (line 32) | def check_value(function):
class CLI (line 39) | class CLI:
method __init__ (line 40) | def __init__(self, ctx: Context, **kwargs):
method __aenter__ (line 54) | async def __aenter__(self):
method __aexit__ (line 58) | async def __aexit__(self, exc_type, exc_value, traceback):
method run (line 61) | async def run(self):
method __update_settings (line 66) | def __update_settings(self):
method __check_settings_path (line 70) | def __check_settings_path(self) -> Path:
method __merge_cookie (line 76) | def __merge_cookie(data: dict) -> None:
method __clean_params (line 81) | def __clean_params(self, data: dict) -> dict:
method __format_index (line 86) | def __format_index(index: str) -> list:
method version (line 98) | def version(ctx: Context, param, value) -> None:
method help_ (line 114) | def help_(ctx: Context, param, value) -> None:
function cli (line 354) | def cli(ctx, help, language, **kwargs):
FILE: source/TUI/about.py
class About (line 18) | class About(Screen):
method __init__ (line 25) | def __init__(
method compose (line 30) | def compose(self) -> ComposeResult:
method on_mount (line 74) | def on_mount(self) -> None:
method action_quit (line 77) | async def action_quit(self) -> None:
method action_back (line 80) | async def action_back(self) -> None:
method action_update (line 83) | async def action_update(self):
FILE: source/TUI/app.py
class XHSDownloader (line 18) | class XHSDownloader(App):
method __init__ (line 22) | def __init__(self):
method __aenter__ (line 28) | async def __aenter__(self):
method __aexit__ (line 32) | async def __aexit__(self, exc_type, exc_value, traceback):
method __initialization (line 35) | def __initialization(self) -> None:
method on_mount (line 42) | async def on_mount(self) -> None:
method action_settings (line 66) | async def action_settings(self):
method refresh_screen (line 73) | async def refresh_screen(self):
method update_result (line 107) | def update_result(self, args: tuple[str, str]) -> None:
method action_update (line 113) | async def action_update(self):
method close_database (line 121) | async def close_database(self):
FILE: source/TUI/index.py
class Index (line 27) | class Index(Screen):
method __init__ (line 37) | def __init__(
method compose (line 46) | def compose(self) -> ComposeResult:
method on_mount (line 76) | def on_mount(self) -> None:
method deal_button (line 88) | async def deal_button(self):
method reset_button (line 102) | def reset_button(self):
method paste_button (line 106) | def paste_button(self):
method deal (line 110) | async def deal(self):
method action_quit (line 132) | async def action_quit(self) -> None:
method action_update (line 135) | async def action_update(self) -> None:
method action_settings (line 138) | async def action_settings(self):
method action_monitor (line 141) | async def action_monitor(self):
method action_about (line 148) | async def action_about(self):
method action_record (line 151) | async def action_record(self):
FILE: source/TUI/loading.py
class Loading (line 11) | class Loading(ModalScreen):
method __init__ (line 12) | def __init__(
method compose (line 17) | def compose(self) -> ComposeResult:
FILE: source/TUI/monitor.py
class Monitor (line 18) | class Monitor(Screen):
method __init__ (line 24) | def __init__(
method compose (line 31) | def compose(self) -> ComposeResult:
method close_button (line 39) | async def close_button(self):
method run_monitor (line 43) | async def run_monitor(self):
method on_mount (line 47) | def on_mount(self) -> None:
method action_close (line 52) | async def action_close(self):
method action_quit (line 56) | async def action_quit(self) -> None:
FILE: source/TUI/progress.py
class Progress (line 7) | class Progress(Screen):
method compose (line 8) | def compose(self) -> ComposeResult:
FILE: source/TUI/record.py
class Record (line 13) | class Record(ModalScreen):
method __init__ (line 14) | def __init__(
method compose (line 21) | def compose(self) -> ComposeResult:
method delete (line 40) | async def delete(self, text: str):
method save_settings (line 49) | async def save_settings(self):
method reset (line 55) | def reset(self):
FILE: source/TUI/setting.py
class Setting (line 13) | class Setting(Screen):
method __init__ (line 19) | def __init__(
method compose (line 26) | def compose(self) -> ComposeResult:
method __check_cookie (line 225) | def __check_cookie(self) -> str:
method on_mount (line 230) | def on_mount(self) -> None:
method save_settings (line 234) | def save_settings(self):
method reset (line 263) | def reset(self):
method action_quit (line 266) | async def action_quit(self) -> None:
method action_index (line 269) | async def action_index(self):
FILE: source/TUI/update.py
class Update (line 16) | class Update(ModalScreen):
method __init__ (line 17) | def __init__(
method compose (line 24) | def compose(self) -> ComposeResult:
method check_update (line 32) | async def check_update(self) -> None:
method on_mount (line 75) | def on_mount(self) -> None:
method compare_versions (line 79) | def compare_versions(
FILE: source/application/app.py
function data_cache (line 67) | def data_cache(function):
class Print (line 85) | class Print:
method __init__ (line 86) | def __init__(
method __call__ (line 92) | def __call__(
class XHS (line 98) | class XHS:
method __new__ (line 111) | def __new__(cls, *args, **kwargs):
method __init__ (line 116) | def __init__(
method __extract_image (line 195) | def __extract_image(self, container: dict, data: Namespace):
method __extract_video (line 200) | def __extract_video(
method __download_files (line 213) | async def __download_files(
method save_data (line 252) | async def save_data(
method __add_record (line 262) | async def __add_record(
method extract (line 268) | async def extract(
method show_statistics (line 304) | def show_statistics(
method extract_cli (line 317) | async def extract_cli(
method extract_links (line 358) | async def extract_links(
method extract_id (line 377) | def extract_id(self, links: list[str]) -> list[str]:
method _get_html_data (line 386) | async def _get_html_data(
method _extract_data (line 417) | def _extract_data(
method _deal_download_tasks (line 430) | async def _deal_download_tasks(
method __deal_extract (line 462) | async def __deal_extract(
method deal_script_tasks (line 508) | async def deal_script_tasks(
method json_to_namespace (line 539) | def json_to_namespace(data: dict) -> Namespace:
method update_author_nickname (line 542) | async def update_author_nickname(
method __extract_link_id (line 558) | def __extract_link_id(url: str) -> str:
method __generate_data_object (line 562) | def __generate_data_object(self, html: str) -> Namespace:
method __naming_rules (line 566) | def __naming_rules(self, data: dict) -> str:
method __get_name_time (line 591) | def __get_name_time(data: dict) -> str:
method __get_name_title (line 594) | def __get_name_title(self, data: dict) -> str:
method monitor (line 603) | async def monitor(
method __get_link (line 622) | async def __get_link(self, delay: int):
method __push_link (line 631) | async def __push_link(
method __receive_link (line 644) | async def __receive_link(self, delay: int, *args, **kwargs):
method stop_monitor (line 650) | def stop_monitor(self):
method skip_download (line 653) | async def skip_download(self, id_: str) -> bool:
method __aenter__ (line 656) | async def __aenter__(self):
method __aexit__ (line 662) | async def __aexit__(self, exc_type, exc_value, traceback):
method close (line 668) | async def close(self):
method run_api_server (line 685) | async def run_api_server(
method setup_routes (line 706) | def setup_routes(
method run_mcp_server (line 758) | async def run_mcp_server(
method deal_detail_mcp (line 919) | async def deal_detail_mcp(
method init_script_server (line 942) | def init_script_server(
method switch_script_server (line 950) | async def switch_script_server(
method run_script_server (line 966) | def run_script_server(
method _run_script_server (line 974) | async def _run_script_server(
method stop_script_server (line 982) | async def stop_script_server(self):
method _script_server_debug (line 989) | async def _script_server_debug(self):
method logging (line 994) | def logging(self, text, style=INFO):
FILE: source/application/download.py
class Download (line 30) | class Download:
method __init__ (line 42) | def __init__(
method run (line 71) | async def run(
method __generate_path (line 114) | def __generate_path(self, nickname: str, filename: str):
method __ready_download_video (line 124) | def __ready_download_video(
method __ready_download_image (line 140) | def __ready_download_image(
method __check_exists_glob (line 176) | def __check_exists_glob(
method __check_exists_path (line 186) | def __check_exists_path(
method __download (line 197) | async def __download(
method __create_progress (line 270) | def __create_progress(
method __update_progress (line 279) | def __update_progress(bar, advance: int):
method __extract_type (line 284) | def __extract_type(cls, content: str) -> str:
method __head_file (line 287) | async def __head_file(
method __get_resume_byte_position (line 305) | def __get_resume_byte_position(file: Path) -> int:
method __update_headers_range (line 308) | def __update_headers_range(
method __suffix_with_file (line 316) | async def __suffix_with_file(
FILE: source/application/explore.py
class Explore (line 9) | class Explore:
method run (line 12) | def run(self, data: Namespace) -> dict:
method __extract_data (line 15) | def __extract_data(self, data: Namespace) -> dict:
method __extract_interact_info (line 26) | def __extract_interact_info(container: dict, data: Namespace) -> None:
method __extract_tags (line 33) | def __extract_tags(container: dict, data: Namespace):
method __extract_info (line 39) | def __extract_info(self, container: dict, data: Namespace):
method __extract_time (line 49) | def __extract_time(self, container: dict, data: Namespace):
method __extract_user (line 65) | def __extract_user(container: dict, data: Namespace):
method __classify_works (line 75) | def __classify_works(data: Namespace) -> str:
FILE: source/application/image.py
class Image (line 8) | class Image:
method get_image_link (line 10) | def get_image_link(cls, data: Namespace, format_: str) -> tuple[list, ...
method __generate_auto_link (line 42) | def __generate_auto_link(token: str) -> str:
method __generate_fixed_link (line 46) | def __generate_fixed_link(
method __extract_image_token (line 53) | def __extract_image_token(url: str) -> str:
method __get_live_link (line 57) | def __get_live_link(items: list) -> list:
FILE: source/application/request.py
class Html (line 15) | class Html:
method __init__ (line 16) | def __init__(
method request_url (line 27) | async def request_url(
method format_url (line 72) | def format_url(url: str) -> str:
method update_cookie (line 75) | def update_cookie(
method __request_url_head (line 81) | async def __request_url_head(
method __request_url_head_proxy (line 93) | async def __request_url_head_proxy(
method __request_url_get (line 110) | async def __request_url_get(
method __request_url_get_proxy (line 122) | async def __request_url_get_proxy(
FILE: source/application/user_posted.py
class UserPosted (line 10) | class UserPosted:
method __init__ (line 13) | def __init__(
method get_cookie (line 31) | def get_cookie(self, cookies: str = None) -> dict | str:
method run (line 37) | def run(
method get_data (line 43) | async def get_data(self):
method get_headers (line 65) | def get_headers(self):
FILE: source/application/video.py
class Video (line 7) | class Video:
method deal_video_link (line 15) | def deal_video_link(
method generate_video_link (line 23) | def generate_video_link(cls, data: Namespace) -> list:
method get_video_link (line 31) | def get_video_link(
method get_video_items (line 50) | def get_video_items(data: Namespace) -> list:
FILE: source/expansion/browser.py
class BrowserCookie (line 26) | class BrowserCookie:
method run (line 41) | def run(
method get (line 67) | def get(
method __browser_object (line 85) | def __browser_object(cls, browser: str | int):
method __match_browser (line 101) | def __match_browser(cls, browser: str):
FILE: source/expansion/cleaner.py
class Cleaner (line 14) | class Cleaner:
method __init__ (line 17) | def __init__(self):
method default_rule (line 24) | def default_rule():
method set_rule (line 50) | def set_rule(self, rule: dict[str, str], update=True):
method filter (line 59) | def filter(self, text: str) -> str:
method filter_name (line 70) | def filter_name(
method clear_spaces (line 95) | def clear_spaces(string: str):
method remove_control_characters (line 100) | def remove_control_characters(
FILE: source/expansion/converter.py
class Converter (line 9) | class Converter:
method run (line 24) | def run(self, content: str) -> dict:
method _extract_object (line 27) | def _extract_object(self, html: str) -> str:
method _convert_object (line 35) | def _convert_object(cls, text: str) -> dict:
method _filter_object (line 40) | def _filter_object(cls, data: dict) -> dict:
method deep_get (line 48) | def deep_get(cls, data: dict, keys: list | tuple, default=None):
method safe_get (line 62) | def safe_get(data: Union[dict, list, tuple, set], index: int):
method get_script (line 70) | def get_script(scripts: list) -> str:
FILE: source/expansion/error.py
class CacheError (line 1) | class CacheError(Exception):
method __init__ (line 2) | def __init__(self, message: str):
method __str__ (line 6) | def __str__(self):
FILE: source/expansion/file_folder.py
function file_switch (line 5) | def file_switch(path: Path) -> None:
function remove_empty_directories (line 12) | def remove_empty_directories(path: Path) -> None:
FILE: source/expansion/namespace.py
class Namespace (line 8) | class Namespace:
method __init__ (line 9) | def __init__(self, data: dict) -> None:
method generate_data_object (line 13) | def generate_data_object(data: dict) -> SimpleNamespace:
method safe_extract (line 26) | def safe_extract(
method __safe_extract (line 34) | def __safe_extract(
method object_extract (line 58) | def object_extract(
method __dict__ (line 71) | def __dict__(self):
method convert_to_dict (line 75) | def convert_to_dict(cls, data) -> dict:
method __bool__ (line 83) | def __bool__(self):
FILE: source/expansion/pyi_rth_beartype.py
function _is_pyinstaller_frozen (line 37) | def _is_pyinstaller_frozen():
function _patch_beartype_claw (line 42) | def _patch_beartype_claw():
FILE: source/expansion/truncate.py
function is_chinese_char (line 4) | def is_chinese_char(char: str) -> bool:
function truncate_string (line 8) | def truncate_string(s: str, length: int = 64) -> str:
function trim_string (line 19) | def trim_string(s: str, length: int = 64) -> str:
function beautify_string (line 24) | def beautify_string(s: str, length: int = 64) -> str:
FILE: source/module/extend.py
class Account (line 4) | class Account:
FILE: source/module/manager.py
class Manager (line 28) | class Manager:
method __init__ (line 53) | def __init__(
method __check_path (line 134) | def __check_path(self, path: str) -> Path:
method __check_folder (line 141) | def __check_folder(self, folder: str) -> Path:
method __check_root_again (line 146) | def __check_root_again(root: Path) -> bool | Path:
method __check_image_format (line 153) | def __check_image_format(image_format) -> str:
method is_exists (line 166) | def is_exists(path: Path) -> bool:
method delete (line 170) | def delete(path: Path):
method archive (line 175) | def archive(root: Path, name: str, folder_mode: bool) -> Path:
method move (line 179) | def move(
method update_mtime (line 191) | def update_mtime(file: Path, mtime: int):
method __clean (line 194) | def __clean(self):
method filter_name (line 197) | def filter_name(self, name: str) -> str:
method check_bool (line 202) | def check_bool(value: bool, default: bool) -> bool:
method close (line 205) | async def close(self):
method __check_name_format (line 212) | def __check_name_format(self, format_: str) -> str:
method check_video_preference (line 220) | def check_video_preference(preference: str) -> str:
method __check_proxy (line 225) | def __check_proxy(
method print_proxy_tip (line 261) | def print_proxy_tip(
method clean_cookie (line 268) | def clean_cookie(cls, cookie_string: str) -> str:
method delete_cookie (line 278) | def delete_cookie(cls, cookie_string: str, patterns: list | tuple) -> ...
method create_folder (line 287) | def create_folder(
method compatible (line 293) | def compatible(
method cookie_str_to_dict (line 304) | def cookie_str_to_dict(cookie_str: str) -> dict:
FILE: source/module/mapping.py
class Mapping (line 16) | class Mapping:
method __init__ (line 17) | def __init__(
method update_cache (line 28) | async def update_cache(
method has_mapping (line 43) | async def has_mapping(self, id_: str) -> str:
method __check_file (line 46) | def __check_file(
method __rename_folder (line 71) | def __rename_folder(
method __rename_works_folder (line 90) | def __rename_works_folder(
method __scan_file (line 112) | def __scan_file(
method __batch_rename (line 143) | def __batch_rename(
method __rename_file (line 160) | def __rename_file(
method __rename (line 181) | def __rename(
FILE: source/module/model.py
class ExtractParams (line 4) | class ExtractParams(BaseModel):
class ExtractData (line 13) | class ExtractData(BaseModel):
FILE: source/module/recorder.py
class IDRecorder (line 13) | class IDRecorder:
method __init__ (line 14) | def __init__(self, manager: "Manager"):
method _connect_database (line 22) | async def _connect_database(self):
method select (line 30) | async def select(self, id_: str):
method add (line 35) | async def add(
method __delete (line 46) | async def __delete(self, id_: str) -> None:
method delete (line 51) | async def delete(self, ids: list[str]):
method all (line 55) | async def all(self):
method __aenter__ (line 60) | async def __aenter__(self):
method __aexit__ (line 65) | async def __aexit__(self, exc_type, exc_value, traceback):
method compatible (line 70) | def compatible(
class DataRecorder (line 81) | class DataRecorder(IDRecorder):
method __init__ (line 103) | def __init__(self, manager: "Manager"):
method _connect_database (line 110) | async def _connect_database(self):
method select (line 118) | async def select(self, id_: str):
method add (line 121) | async def add(self, **kwargs) -> None:
method __delete (line 133) | async def __delete(self, id_: str) -> None:
method delete (line 136) | async def delete(self, ids: list | tuple):
method all (line 139) | async def all(self):
method __generate_values (line 142) | def __generate_values(self, data: dict) -> tuple:
class MapRecorder (line 146) | class MapRecorder(IDRecorder):
method __init__ (line 147) | def __init__(self, manager: "Manager"):
method _connect_database (line 153) | async def _connect_database(self):
method select (line 164) | async def select(self, id_: str):
method add (line 171) | async def add(self, id_: str, name: str, *args, **kwargs) -> None:
method __delete (line 182) | async def __delete(self, id_: str) -> None:
method delete (line 185) | async def delete(self, ids: list[str]):
method all (line 188) | async def all(self):
FILE: source/module/script.py
class ScriptServer (line 10) | class ScriptServer:
method __init__ (line 11) | def __init__(
method handler (line 22) | async def handler(self, websocket):
method start (line 28) | async def start(self):
method stop (line 36) | async def stop(self):
method __aenter__ (line 42) | async def __aenter__(self):
method __aexit__ (line 46) | async def __aexit__(self, exc_type, exc_val, exc_tb):
FILE: source/module/settings.py
class Settings (line 10) | class Settings:
method __init__ (line 41) | def __init__(self, root: Path = ROOT):
method run (line 52) | def run(self):
method read (line 62) | def read(self) -> dict:
method create (line 72) | def create(self) -> dict:
method update (line 83) | def update(self, data: dict):
method compatible (line 93) | def compatible(
method migration_file (line 115) | def migration_file(self):
FILE: source/module/tools.py
function retry (line 13) | def retry(function):
function retry_limited (line 25) | def retry_limited(function):
function logging (line 42) | def logging(log: Callable, text, style=INFO):
function get_wait_time (line 54) | def get_wait_time(
function sleep_time (line 62) | async def sleep_time():
FILE: source/translation/translate.py
class TranslationManager (line 8) | class TranslationManager:
method __new__ (line 13) | def __new__(cls, *args, **kwargs):
method __init__ (line 18) | def __init__(self, domain="xhs", localedir=None):
method get_language_code (line 28) | def get_language_code() -> str:
method setup_translation (line 46) | def setup_translation(self, language: str = "zh_CN"):
method switch_language (line 61) | def switch_language(self, language: str = "en_US"):
method gettext (line 65) | def gettext(self, message):
function _translate (line 74) | def _translate(message):
function switch_language (line 79) | def switch_language(language: str = "en_US"):
FILE: static/20250619.js
function L (line 1) | function L(h, b) {
function F (line 33) | function F() {
function b (line 306) | function b() {
FILE: static/XHS-Downloader.js
function showTextModal (line 1146) | function showTextModal(opts) {
function showListSelectionModal (line 1816) | function showListSelectionModal(list, options = {}) {
function showToast (line 2164) | function showToast(message, duration = 5000) {
class WebSocketManager (line 2420) | class WebSocketManager {
method constructor (line 2421) | constructor(url) {
method onOpen (line 2426) | onOpen() {
method onMessage (line 2429) | onMessage(message) {
method onClose (line 2433) | onClose(event) {
method onError (line 2437) | onError(error) {
method isConnected (line 2442) | get isConnected() {
method connect (line 2446) | connect() {
method disconnect (line 2467) | disconnect() {
method send (line 2473) | send(data) {
Condensed preview — 75 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (703K chars).
[
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 709,
"preview": "---\nname: Bug report\nabout: 报告项目问题\ntitle: '[功能异常] '\nlabels: ''\nassignees: JoeanAmier\n\n---\n\n**问题描述**\n\n清晰简洁地描述该错误是什么。\n\nA c"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 570,
"preview": "---\nname: Feature request\nabout: 功能优化建议\ntitle: '[优化建议] '\nlabels: ''\nassignees: JoeanAmier\n\n---\n\n**功能请求**\n\n清晰简洁地描述问题是什么。例"
},
{
"path": ".github/dependabot.yml",
"chars": 325,
"preview": "version: 2\nregistries:\n pip_mirror:\n type: python-index\n url: https://mirrors.ustc.edu.cn/pypi/simple\nupdates:\n "
},
{
"path": ".github/workflows/Close_Stale_Issues_and_PRs.yaml",
"chars": 1569,
"preview": "name: \"自动管理过时的问题和PR\"\non:\n schedule:\n - cron: \"0 0 * * 6\"\n workflow_dispatch:\n\npermissions:\n issues: write\n pull-r"
},
{
"path": ".github/workflows/Delete_untagged_images.yml",
"chars": 566,
"preview": "name: 删除 GHCR Untagged 镜像\non:\n schedule:\n - cron: \"0 0 15 * *\"\n release:\n types: [ published ]\n workflow_dispat"
},
{
"path": ".github/workflows/Manually_build_executable_programs.yml",
"chars": 1471,
"preview": "name: 构建可执行文件\n\non:\n workflow_dispatch:\n\njobs:\n build:\n name: 构建于 ${{ matrix.os }}\n runs-on: ${{ matrix.os }}\n "
},
{
"path": ".github/workflows/Manually_docker_image.yml",
"chars": 2680,
"preview": "name: 构建并发布 Docker 镜像\n\non:\n workflow_dispatch:\n inputs:\n is_beta:\n type: boolean\n required: true\n"
},
{
"path": ".github/workflows/Release_build_executable_program.yml",
"chars": 1783,
"preview": "name: 自动构建并发布可执行文件\n\non:\n release:\n types: [ published ]\n\npermissions:\n contents: write\n discussions: write\n\njobs:\n"
},
{
"path": ".github/workflows/Release_docker_image.yml",
"chars": 1517,
"preview": "name: 自动构建并发布 Docker 镜像\n\non:\n release:\n types: [published]\n\npermissions:\n contents: read\n packages: write\n attest"
},
{
"path": ".gitignore",
"chars": 76,
"preview": "__pycache__/\n*.pyc\n/.venv/\n/.ruff_cache/\n/.idea/\n/.run/\n/Volume/\n!/.github/\n"
},
{
"path": ".python-version",
"chars": 5,
"preview": "3.12\n"
},
{
"path": "Dockerfile",
"chars": 1021,
"preview": "# ---- 阶段 1: 构建器 (Builder) ----\n# 使用一个功能完整的镜像,它包含编译工具或可以轻松安装它们\nFROM python:3.12-bullseye as builder\n\n# 安装编译 uvloop 和 htt"
},
{
"path": "LICENSE",
"chars": 35149,
"preview": " GNU GENERAL PUBLIC LICENSE\n Version 3, 29 June 2007\n\n Copyright (C) 2007 Free "
},
{
"path": "README.md",
"chars": 26506,
"preview": "<div align=\"center\">\n<img src=\"static/XHS-Downloader.png\" alt=\"XHS-Downloader\" height=\"256\" width=\"256\"><br>\n<h1>XHS-Dow"
},
{
"path": "README_EN.md",
"chars": 43453,
"preview": "<div align=\"center\">\n<img src=\"static/XHS-Downloader.png\" alt=\"XHS-Downloader\" height=\"256\" width=\"256\"><br>\n<h1>XHS-Dow"
},
{
"path": "example.py",
"chars": 3004,
"preview": "from asyncio import run\nfrom pyperclip import paste\nfrom httpx import post\nfrom rich import print\n\nfrom source import XH"
},
{
"path": "locale/README.md",
"chars": 1217,
"preview": "# 命令参考\n\n**运行命令前,确保已经安装了 `gettext` 软件包,并配置好环境变量。**\n\n**Before running the command, ensure that the `gettext` package is in"
},
{
"path": "locale/en_US/LC_MESSAGES/xhs.po",
"chars": 28108,
"preview": "# English translations for XHS-Downloader package.\n# Copyright (C) 2024 THE XHS-Downloader'S COPYRIGHT HOLDER\n# This fil"
},
{
"path": "locale/generate_path.py",
"chars": 462,
"preview": "from pathlib import Path\n\nROOT = Path(__file__).resolve().parent.parent\n\n\ndef find_python_files(dir_, file):\n with op"
},
{
"path": "locale/po_to_mo.py",
"chars": 567,
"preview": "from pathlib import Path\nfrom subprocess import run\n\nROOT = Path(__file__).resolve().parent\n\n\ndef scan_directory():\n "
},
{
"path": "locale/xhs.pot",
"chars": 18305,
"preview": "# SOME DESCRIPTIVE TITLE.\n# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n# This file is distributed under the same "
},
{
"path": "locale/zh_CN/LC_MESSAGES/xhs.po",
"chars": 19665,
"preview": "# Chinese translations for XHS-Downloader package\n# Copyright (C) 2024 THE XHS-Downloader'S COPYRIGHT HOLDER\n# This file"
},
{
"path": "main.py",
"chars": 1259,
"preview": "from asyncio import run\nfrom asyncio.exceptions import CancelledError\nfrom contextlib import suppress\nfrom sys import ar"
},
{
"path": "pyproject.toml",
"chars": 2766,
"preview": "[project]\nname = \"XHS-Downloader\"\nversion = \"2.8\"\ndescription = \"小红书(XiaoHongShu、RedNote)链接提取/作品采集工具:提取账号发布、收藏、点赞、专辑作品链接"
},
{
"path": "requirements.txt",
"chars": 905,
"preview": "# This file was autogenerated by uv via the following command:\n# uv pip compile pyproject.toml --no-deps --no-strip-e"
},
{
"path": "source/CLI/__init__.py",
"chars": 41,
"preview": "from .main import cli\n\n__all__ = [\"cli\"]\n"
},
{
"path": "source/CLI/main.py",
"chars": 8950,
"preview": "from asyncio import run\nfrom contextlib import suppress\nfrom pathlib import Path as Root\nfrom textwrap import fill\n\nfrom"
},
{
"path": "source/TUI/__init__.py",
"chars": 60,
"preview": "from .app import XHSDownloader\n\n__all__ = [\"XHSDownloader\"]\n"
},
{
"path": "source/TUI/about.py",
"chars": 2250,
"preview": "from rich.text import Text\nfrom textual.app import ComposeResult\nfrom textual.binding import Binding\nfrom textual.screen"
},
{
"path": "source/TUI/app.py",
"chars": 3328,
"preview": "from textual.app import App\n\nfrom ..application import XHS\nfrom ..module import (\n ROOT,\n Settings,\n)\nfrom .about "
},
{
"path": "source/TUI/index.py",
"chars": 4179,
"preview": "from pyperclip import paste\nfrom rich.text import Text\nfrom textual import on, work\nfrom textual.app import ComposeResul"
},
{
"path": "source/TUI/loading.py",
"chars": 481,
"preview": "from textual.app import ComposeResult\nfrom textual.containers import Grid\nfrom textual.screen import ModalScreen\nfrom te"
},
{
"path": "source/TUI/monitor.py",
"chars": 1491,
"preview": "from rich.text import Text\nfrom textual import on, work\nfrom textual.app import ComposeResult\nfrom textual.binding impor"
},
{
"path": "source/TUI/progress.py",
"chars": 175,
"preview": "from textual.app import ComposeResult\nfrom textual.screen import Screen\n\n__all__ = [\"Progress\"]\n\n\nclass Progress(Screen)"
},
{
"path": "source/TUI/record.py",
"chars": 1446,
"preview": "from textual import on\nfrom textual.app import ComposeResult\nfrom textual.containers import Grid, HorizontalScroll\nfrom "
},
{
"path": "source/TUI/setting.py",
"chars": 8804,
"preview": "from textual import on\nfrom textual.app import ComposeResult\nfrom textual.binding import Binding\nfrom textual.containers"
},
{
"path": "source/TUI/update.py",
"chars": 2605,
"preview": "from textual import work\nfrom textual.app import ComposeResult\nfrom textual.containers import Grid\nfrom textual.screen i"
},
{
"path": "source/__init__.py",
"chars": 184,
"preview": "from .CLI import cli\nfrom .TUI import XHSDownloader\nfrom .application import XHS\nfrom .module import Settings\n\n__all__ ="
},
{
"path": "source/application/__init__.py",
"chars": 40,
"preview": "from .app import XHS\n\n__all__ = [\"XHS\"]\n"
},
{
"path": "source/application/app.py",
"chars": 27464,
"preview": "from asyncio import (\n Event,\n Queue,\n QueueEmpty,\n create_task,\n gather,\n sleep,\n Future,\n Canc"
},
{
"path": "source/application/download.py",
"chars": 9713,
"preview": "from asyncio import Semaphore, gather\nfrom pathlib import Path\nfrom typing import TYPE_CHECKING, Any\n\nfrom aiofiles impo"
},
{
"path": "source/application/explore.py",
"chars": 3036,
"preview": "from datetime import datetime\n\nfrom ..expansion import Namespace\nfrom ..translation import _\n\n__all__ = [\"Explore\"]\n\n\ncl"
},
{
"path": "source/application/image.py",
"chars": 1979,
"preview": "from source.expansion import Namespace\n\nfrom .request import Html\n\n__all__ = [\"Image\"]\n\n\nclass Image:\n @classmethod\n "
},
{
"path": "source/application/request.py",
"chars": 3418,
"preview": "from typing import TYPE_CHECKING\n\nfrom httpx import HTTPError\nfrom httpx import get\n\nfrom ..module import ERROR, Manager"
},
{
"path": "source/application/user_posted.py",
"chars": 1829,
"preview": "from xhshow import Xhshow\nfrom typing import TYPE_CHECKING\nfrom ..module import retry, sleep_time\nfrom httpx import get\n"
},
{
"path": "source/application/video.py",
"chars": 1534,
"preview": "from source.expansion import Namespace\nfrom .request import Html\n\n__all__ = [\"Video\"]\n\n\nclass Video:\n VIDEO_LINK = (\n"
},
{
"path": "source/expansion/__init__.py",
"chars": 359,
"preview": "# from .browser import BrowserCookie\nfrom .cleaner import Cleaner\nfrom .converter import Converter\nfrom .error import Ca"
},
{
"path": "source/expansion/browser.py",
"chars": 3262,
"preview": "from contextlib import suppress\nfrom sys import platform\n\nfrom rich.console import Console\nfrom rookiepy import (\n ar"
},
{
"path": "source/expansion/cleaner.py",
"chars": 2697,
"preview": "from platform import system\nfrom re import compile\nfrom string import whitespace\nfrom warnings import warn\n\nfrom emoji i"
},
{
"path": "source/expansion/converter.py",
"chars": 2220,
"preview": "from typing import Union\nfrom re import compile\nfrom lxml.etree import HTML\nfrom yaml import safe_load\n\n__all__ = [\"Conv"
},
{
"path": "source/expansion/error.py",
"chars": 184,
"preview": "class CacheError(Exception):\n def __init__(self, message: str):\n super().__init__(message)\n self.messag"
},
{
"path": "source/expansion/file_folder.py",
"chars": 564,
"preview": "from contextlib import suppress\nfrom pathlib import Path\n\n\ndef file_switch(path: Path) -> None:\n if path.exists():\n "
},
{
"path": "source/expansion/namespace.py",
"chars": 2522,
"preview": "from copy import deepcopy\nfrom types import SimpleNamespace\nfrom typing import Union\n\n__all__ = [\"Namespace\"]\n\n\nclass Na"
},
{
"path": "source/expansion/pyi_rth_beartype.py",
"chars": 2977,
"preview": "\"\"\"\nPyInstaller runtime hook for beartype compatibility.\n\nThis runtime hook runs BEFORE any user code and patches bearty"
},
{
"path": "source/expansion/truncate.py",
"chars": 845,
"preview": "from unicodedata import name\n\n\ndef is_chinese_char(char: str) -> bool:\n return \"CJK\" in name(char, \"\")\n\n\ndef truncate"
},
{
"path": "source/module/__init__.py",
"chars": 751,
"preview": "from .extend import Account\nfrom .manager import Manager\nfrom .model import (\n ExtractData,\n ExtractParams,\n)\nfrom"
},
{
"path": "source/module/extend.py",
"chars": 48,
"preview": "__all__ = [\"Account\"]\n\n\nclass Account:\n pass\n"
},
{
"path": "source/module/manager.py",
"chars": 8894,
"preview": "from pathlib import Path\nfrom re import compile, sub\nfrom shutil import move, rmtree\nfrom os import utime\nfrom http.cook"
},
{
"path": "source/module/mapping.py",
"chars": 5397,
"preview": "from pathlib import Path\nfrom typing import TYPE_CHECKING\n\nfrom ..translation import _\nfrom .static import ERROR\nfrom .t"
},
{
"path": "source/module/model.py",
"chars": 311,
"preview": "from pydantic import BaseModel\n\n\nclass ExtractParams(BaseModel):\n url: str\n download: bool = False\n index: list"
},
{
"path": "source/module/recorder.py",
"chars": 5670,
"preview": "from asyncio import CancelledError\nfrom contextlib import suppress\nfrom typing import TYPE_CHECKING\nfrom shutil import m"
},
{
"path": "source/module/script.py",
"chars": 1114,
"preview": "from contextlib import suppress\nfrom json import loads\nfrom websockets import ConnectionClosed, serve\nfrom typing import"
},
{
"path": "source/module/settings.py",
"chars": 3232,
"preview": "from json import dump, load\nfrom pathlib import Path\nfrom platform import system\nfrom shutil import move\nfrom .static im"
},
{
"path": "source/module/static.py",
"chars": 2297,
"preview": "from pathlib import Path\n\nVERSION_MAJOR = 2\nVERSION_MINOR = 8\nVERSION_BETA = True\n__VERSION__ = f\"{VERSION_MAJOR}.{VERSI"
},
{
"path": "source/module/tools.py",
"chars": 1432,
"preview": "from asyncio import sleep\nfrom random import lognormvariate\nfrom math import log\nfrom typing import Callable\n\nfrom rich "
},
{
"path": "source/translation/__init__.py",
"chars": 42,
"preview": "from .translate import switch_language, _\n"
},
{
"path": "source/translation/translate.py",
"chars": 2290,
"preview": "from gettext import translation\nfrom locale import getlocale\nfrom pathlib import Path\n\nROOT = Path(__file__).resolve().p"
},
{
"path": "static/20250619.js",
"chars": 202062,
"preview": "function L(h, b) {\n var C = F();\n L = function (f, v) {\n f = f - 0x1aa;\n var t = C[f];\n retur"
},
{
"path": "static/Release_Notes.md",
"chars": 110,
"preview": "**项目更新内容:**\n\n1. 修复 CLI 模式 KeyError 异常\n2. 修复控制字符导致程序报错的问题\n3. 重构项目内置请求延时机制\n\n*****\n\n**用户脚本更新内容:**\n\n**版本号:2.3.1**\n"
},
{
"path": "static/XHS-Downloader.js",
"chars": 99993,
"preview": "// ==UserScript==\n// @name XHS-Downloader\n// @namespace xhs_downloader\n// @homepage https://github."
},
{
"path": "static/XHS-Downloader.tcss",
"chars": 854,
"preview": "Button {\n width: 1fr;\n margin: 1 1;\n}\n.vertical-layout {\n layout: vertical;\n height: auto;\n}\n.horizontal-lay"
},
{
"path": "static/自动滚动页面.js",
"chars": 3349,
"preview": "// ==UserScript==\n// @name 自动滚动页面\n// @namespace http://tampermonkey.net/\n// @version 0.1\n// @description"
}
]
// ... and 3 more files (download for full content)
About this extraction
This page contains the full source code of the JoeanAmier/XHS_Downloader GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 75 files (615.4 KB), approximately 219.3k tokens, and a symbol index with 342 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.