Skip to content

Commit

Permalink
发布 1.8 版本
Browse files Browse the repository at this point in the history
  • Loading branch information
JoeanAmier committed Mar 2, 2024
1 parent 2c8efd0 commit 18f9213
Show file tree
Hide file tree
Showing 23 changed files with 284 additions and 78 deletions.
99 changes: 56 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
<li>✅ 持久化储存作品信息至文件</li>
<li>✅ 作品文件储存至单独文件夹</li>
<li>✅ 后台监听剪贴板下载作品</li>
<li>✅ 记录已下载作品 ID</li>
<li>☑️ 支持 API 调用功能</li>
<li>☑️ 支持命令行参数下载作品文件</li>
</ul>
<ul><b>脚本功能</b>
<li>✅ 下载小红书无水印作品文件</li>
Expand All @@ -38,11 +40,11 @@
</ul>
<h1>📸 程序截图</h1>
<p><b>🎥 点击图片观看演示视频</b></p>
<a href="https://www.bilibili.com/video/BV1nQ4y137it/"><img src="static/screenshot/程序运行截图CN1.png" alt=""></a>
<a href="https://www.bilibili.com/video/BV1PJ4m1Y7Jt/"><img src="static/screenshot/程序运行截图CN1.png" alt=""></a>
<hr>
<a href="https://www.bilibili.com/video/BV1nQ4y137it/"><img src="static/screenshot/程序运行截图CN2.png" alt=""></a>
<a href="https://www.bilibili.com/video/BV1PJ4m1Y7Jt/"><img src="static/screenshot/程序运行截图CN2.png" alt=""></a>
<hr>
<a href="https://www.bilibili.com/video/BV1nQ4y137it/"><img src="static/screenshot/程序运行截图CN3.png" alt=""></a>
<a href="https://www.bilibili.com/video/BV1PJ4m1Y7Jt/"><img src="static/screenshot/程序运行截图CN3.png" alt=""></a>
<h1>🔗 支持链接</h1>
<ul>
<li><code>https://www.xiaohongshu.com/explore/作品ID</code></li>
Expand Down Expand Up @@ -74,41 +76,44 @@
<h1>💻 二次开发</h1>
<p>如果有其他需求,可以根据 <code>main.py</code> 的注释提示进行代码调用或修改!</p>
<pre>
# 示例链接
error_link = "https://github.com/JoeanAmier/XHS_Downloader"
demo_link = "https://www.xiaohongshu.com/explore/xxxxxxxxxx"
multiple_links = f"{demo_link} {demo_link} {demo_link}"
# 实例对象
work_path = "D:\\" # 作品数据/文件保存根路径,默认值:项目根路径
folder_name = "Download" # 作品文件储存文件夹名称(自动创建),默认值:Download
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" # 图文作品文件下载格式,支持:PNG、WEBP
folder_mode = False # 是否将每个作品的文件储存至单独的文件夹
async with XHS() as xhs:
pass # 使用默认参数
async with XHS(work_path=work_path,
folder_name=folder_name,
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,
) as xhs: # 使用自定义参数
download = True # 是否下载作品文件,默认值:False
# 返回作品详细信息,包括下载地址
print(await xhs.extract(error_link, download)) # 获取数据失败时返回空字典
print(await xhs.extract(demo_link, download))
print(await xhs.extract(multiple_links, download)) # 支持传入多个作品链接
async def example():
"""通过代码设置参数,适合二次开发"""
# 示例链接
error_link = "https://github.com/JoeanAmier/XHS_Downloader"
demo_link = "https://www.xiaohongshu.com/explore/xxxxxxxxxx"
multiple_links = f"{demo_link} {demo_link} {demo_link}"
# 实例对象
work_path = "D:\\" # 作品数据/文件保存根路径,默认值:项目根路径
folder_name = "Download" # 作品文件储存文件夹名称(自动创建),默认值:Download
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" # 图文作品文件下载格式,支持:PNG、WEBP
folder_mode = False # 是否将每个作品的文件储存至单独的文件夹
async with XHS() as xhs:
pass # 使用默认参数
async with XHS(work_path=work_path,
folder_name=folder_name,
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,
) as xhs: # 使用自定义参数
download = True # 是否下载作品文件,默认值:False
efficient = True # 高效模式,禁用请求延时
# 返回作品详细信息,包括下载地址
print(await xhs.extract(error_link, download, efficient)) # 获取数据失败时返回空字典
print(await xhs.extract(demo_link, download, efficient))
print(await xhs.extract(multiple_links, download, efficient)) # 支持传入多个作品链接
</pre>
<h1>⚙️ 配置文件</h1>
<p>项目根目录下的 <code>settings.json</code> 文件,首次运行自动生成,可以自定义部分运行参数。</p>
Expand Down Expand Up @@ -199,14 +204,19 @@ async with XHS(work_path=work_path,
</table>
<h1>🌐 Cookie</h1>
<ol>
<li>打开浏览器(可选无痕模式启动),访问小红书任意网页</li>
<li>按 <code>F12</code> 打开开发人员工具</li>
<li>选择 <code>控制台</code> 选项卡</li>
<li>输入 <code>document.cookie</code> 后回车确认</li>
<li>输出内容即为所需 Cookie</li>
<li>打开浏览器(可选无痕模式启动),访问 <code>https://www.xiaohongshu.com/explore</code></li>
<li>按下 <code>F12</code> 打开开发人员工具</li>
<li>选择 <code>网络</code> 选项卡</li>
<li>选择 <code>Fetch/XHR</code> 筛选器</li>
<li>点击小红书页面任意作品</li>
<li>在 <code>网络</code> 选项卡挑选包含 Cookie 的数据包</li>
<li>检查 Cookie 是否包含 <code>web_session</code> 字段</li>
<li>全选复制包含 <code>web_session</code> 字段的 Cookie</li>
</ol>
<br>
<img src="static/screenshot/获取Cookie示意图.png" alt="">
<h1>🗳 下载记录</h1>
<p>XHS-Downloader 会将下载过的作品 ID 储存至数据库,当重复下载相同的作品时,XHS-Downloader 会自动跳过该作品的文件下载(即使作品文件不存在),如果想要重新下载作品文件,请先删除数据库中对应的作品 ID,再使用 XHS-Downloader 下载作品文件!</p>
<h1>♥️ 支持项目</h1>
<p>如果 <b>XHS-Downloader</b> 对您有帮助,请考虑为它点个 <b>Star</b> ⭐,感谢您的支持!</p>
<table>
Expand All @@ -230,6 +240,9 @@ async with XHS(work_path=work_path,
<li>Email: [email protected]</li>
</ul>
<p>
<b>如果您在使用 XHS-Downloader 的时候遇到问题,请先阅读<a href="https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md">《提问的智慧》</a>,然后加入 QQ 群聊寻求帮助!</b>
</p>
<p>
<b>如果您通过 Email 联系我,我可能无法及时查看并回复信息,我会尽力在七天内回复您的邮件;如果有紧急事项或需要更快的回复,请通过其他方式与我联系,谢谢理解!</b>
</p>
<p><b>如果您对抖音 / TikTok 感兴趣,可以了解一下我的另一个开源项目 <a href="https://github.com/JoeanAmier/TikTokDownloader">TikTokDownloader</a></b></p>
Expand Down
11 changes: 6 additions & 5 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ async def example():
# 实例对象
work_path = "D:\\" # 作品数据/文件保存根路径,默认值:项目根路径
folder_name = "Download" # 作品文件储存文件夹名称(自动创建),默认值:Download
user_agent = "" # 请求头 User-Agent
cookie = "" # 小红书网页版 Cookie,无需登录
user_agent = "" # 请求头 User-Agent,可选参数
cookie = "" # 小红书网页版 Cookie,无需登录,必需参数
proxy = None # 网络代理
timeout = 5 # 请求数据超时限制,单位:秒,默认值:10
chunk = 1024 * 1024 * 10 # 下载文件时,每次从服务器获取的数据块大小,单位:字节
Expand All @@ -37,10 +37,11 @@ async def example():
folder_mode=folder_mode,
) as xhs: # 使用自定义参数
download = True # 是否下载作品文件,默认值:False
efficient = True # 高效模式,禁用请求延时
# 返回作品详细信息,包括下载地址
print(await xhs.extract(error_link, download)) # 获取数据失败时返回空字典
print(await xhs.extract(demo_link, download))
print(await xhs.extract(multiple_links, download)) # 支持传入多个作品链接
print(await xhs.extract(error_link, download, efficient)) # 获取数据失败时返回空字典
print(await xhs.extract(demo_link, download, efficient))
print(await xhs.extract(multiple_links, download, efficient)) # 支持传入多个作品链接


async def main():
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ textual>=0.47.1
pyperclip>=1.8.2
lxml>=5.1.0
PyYAML>=6.0.1
aiosqlite>=0.19.0
aiosqlite>=0.20.0
45 changes: 45 additions & 0 deletions source/TUI/about.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from textual.app import ComposeResult
from textual.binding import Binding
from textual.screen import Screen
from textual.widgets import Footer
from textual.widgets import Header
from textual.widgets import Label

from source.module import (
PROJECT,
)
from source.translator import (
Chinese,
English,
)

__all__ = ["About"]


class About(Screen):
BINDINGS = [
Binding(
key="q",
action="quit",
description="退出程序/Quit"),
Binding(
key="u",
action="check_update_about",
description="检查更新/Update"),
Binding(
key="b",
action="index",
description="返回首页/Back"),
]

def __init__(self, language: Chinese | English):
super().__init__()
self.prompt = language

def compose(self) -> ComposeResult:
yield Header()
yield Label()
yield Footer()

def on_mount(self) -> None:
self.title = PROJECT
27 changes: 25 additions & 2 deletions source/TUI/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@
Chinese,
English,
)
# from .about import About
from .index import Index
from .loading import Loading
from .monitor import Monitor
from .record import Record
from .setting import Setting
from .update import Update

Expand Down Expand Up @@ -53,6 +55,8 @@ async def on_mount(self) -> None:
name="setting")
self.install_screen(Index(self.APP, self.prompt), name="index")
self.install_screen(Loading(self.prompt), name="loading")
# self.install_screen(About(self.prompt), name="about")
self.install_screen(Record(self.APP, self.prompt), name="record")
await self.push_screen("index")

async def action_settings(self):
Expand All @@ -62,29 +66,48 @@ async def save_settings(data: dict) -> None:

await self.push_screen("setting", save_settings)

async def action_about(self):
await self.push_screen("about")

async def action_index(self):
await self.push_screen("index")

async def action_record(self):
await self.push_screen("record")

async def refresh_screen(self):
self.pop_screen()
await self.APP.recorder.database.close()
await self.APP.close()
self.__initialization()
await self.__aenter__()
self.uninstall_screen("index")
self.uninstall_screen("setting")
self.uninstall_screen("loading")
# self.uninstall_screen("about")
self.uninstall_screen("record")
self.install_screen(Index(self.APP, self.prompt), name="index")
self.install_screen(
Setting(
self.parameter,
self.prompt),
name="setting")
self.install_screen(Loading(self.prompt), name="loading")
# self.install_screen(About(self.prompt), name="about")
self.install_screen(Record(self.APP, self.prompt), name="record")
await self.push_screen("index")

def update_result(self, tip: str) -> None:
self.query_one(RichLog).write(tip)
log = self.query_one(RichLog)
log.write(tip)
log.write(">" * 50)

async def action_check_update(self):
await self.push_screen(Update(self.APP, self.prompt), callback=self.update_result)

async def action_clipboard(self):
async def action_check_update_about(self):
await self.push_screen("index")
await self.action_check_update()

async def action_monitor(self):
await self.push_screen(Monitor(self.APP, self.prompt))
19 changes: 7 additions & 12 deletions source/TUI/index.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
from asyncio import create_task
from webbrowser import open

from pyperclip import paste
from rich.text import Text
from textual import on
from textual import work
from textual.app import ComposeResult
from textual.binding import Binding
from textual.containers import HorizontalScroll
Expand All @@ -26,7 +24,6 @@
LICENCE,
REPOSITORY,
GENERAL,
USERSCRIPT,
)
from source.translator import (
English,
Expand All @@ -40,9 +37,10 @@ class Index(Screen):
BINDINGS = [
Binding(key="q", action="quit", description="退出程序/Quit"),
Binding(key="u", action="check_update", description="检查更新/Update"),
Binding(key="m", action="user_script", description="获取脚本/Script"),
Binding(key="s", action="settings", description="程序设置/Settings"),
Binding(key="c", action="clipboard", description="监听链接/ClipBoard"),
Binding(key="r", action="record", description="下载记录/Record"),
Binding(key="m", action="monitor", description="开启监听/Monitor"),
# Binding(key="a", action="about", description="关于项目/About"),
]

def __init__(self, app: XHS, language: Chinese | English):
Expand All @@ -68,7 +66,7 @@ def compose(self) -> ComposeResult:
Label(
Text(
self.prompt.input_box_title,
style=PROMPT), id="prompt",
style=PROMPT), classes="prompt",
),
Input(placeholder=self.prompt.input_prompt),
HorizontalScroll(
Expand All @@ -89,7 +87,7 @@ def on_mount(self) -> None:
@on(Button.Pressed, "#deal")
async def deal_button(self):
if self.url.value:
await create_task(self.deal())
self.deal()
else:
self.tip.write(Text(self.prompt.invalid_link, style=WARNING))
self.tip.write(Text(">" * 50, style=GENERAL))
Expand All @@ -102,14 +100,11 @@ def reset_button(self):
def paste_button(self):
self.query_one(Input).value = paste()

@work()
async def deal(self):
await self.app.push_screen("loading")
if any(await self.xhs.extract(self.url.value, True, log=self.tip)):
self.url.value = ""
else:
self.tip.write(Text(self.prompt.download_failure, style=ERROR))
self.app.pop_screen()

@staticmethod
def action_user_script():
open(USERSCRIPT)
2 changes: 1 addition & 1 deletion source/TUI/monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def __init__(self, app: XHS, language: Chinese | English):

def compose(self) -> ComposeResult:
yield Header()
yield Label(Text(self.prompt.monitor_mode, style=INFO), id="monitor")
yield Label(Text(self.prompt.monitor_mode, style=INFO), classes="prompt")
yield RichLog(markup=True, wrap=True)
yield Button(self.prompt.close_monitor, id="close")
yield Footer()
Expand Down
Loading

0 comments on commit 18f9213

Please sign in to comment.