新增 Web API 模式

This commit is contained in:
JoeanAmier 2024-06-03 22:26:11 +08:00
parent e26d4875e3
commit bd0780c344
12 changed files with 166 additions and 18 deletions

View File

@ -28,7 +28,7 @@
<li>✅ 支持命令行下载作品文件</li>
<li>✅ 从浏览器读取 Cookie</li>
<li>✅ 自定义文件名称格式</li>
<li>☑️ 支持 API 调用功能</li>
<li> 支持 API 调用功能</li>
</ul>
<ul><b>脚本功能</b>
<li>✅ 下载小红书无水印作品文件</li>
@ -70,14 +70,66 @@
<li>运行 <code>main.py</code> 即可使用</li>
</ol>
<h1>🛠 命令行模式</h1>
<p>项目支持命令行运行模式,若想要下载图文作品的部分图片,可以使用此模式传入需要下载的图片序号!</p>
<p>可以使用命令行从浏览器读取 Cookie 并写入配置文件!注意需要关闭对应浏览器才能读取数据!</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>
<p>项目支持命令行运行模式,若想要下载图文作品的部分图片,可以使用此模式设置需要下载的图片序号!</p>
<p>可以使用命令行<b>从浏览器读取 Cookie 并写入配置文件</b>!注意需要关闭浏览器才能读取数据!</p>
<p>命令示例:<code>python .\main.py --browser_cookie Chrome --update_settings</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>
<hr>
<img src="static/screenshot/命令行模式截图1.png" alt="">
<hr>
<img src="static/screenshot/命令行模式截图2.png" alt="">
<h1>🖥 服务器模式</h1>
<p><b>启动:</b>运行命令:<code>python .\main.py server</code></p>
<p><b>关闭:</b>按下 <code>Ctrl</code> + <code>C</code> 关闭服务器</p>
<p><b>请求接口:</b><code>/xhs/</code></p>
<p><b>请求类型:</b><code>POST</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">str</td>
<td align="center">下载指定序号的图片文件,仅对图文作品生效;多个序号之间使用空格分隔;<code>download</code> 参数设置为 <code>false</code> 时不生效</td>
<td align="center">null</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>
def api_demo():
server = "http://127.0.0.1:8080"
data = {
"url": "https://www.xiaohongshu.com/explore/123456789",
}
response = requests.post(server, data=data)
print(response.json())
</pre>
<h1>🕹 用户脚本</h1>
<img src="static/screenshot/用户脚本截图1.png" alt="">
<hr>
@ -235,6 +287,7 @@ async def example():
<h1>🌐 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>

Binary file not shown.

View File

@ -255,3 +255,18 @@ msgstr "Format of works file name"
msgid "邀请链接:"
msgstr "Invitation link: "
msgid "获取小红书作品数据成功"
msgstr "Successfully obtained data on Xiaohongshu's works"
msgid "获取小红书作品数据失败"
msgstr "Failed to obtain data on Xiaohongshu's works"
msgid "Web API 服务器已启动!"
msgstr "Web API server started!"
msgid "Web API 服务器已关闭!"
msgstr "Web API server has been shut down!"
msgid "服务器主机及端口: {0}"
msgstr "Server host and port: {0}"

View File

@ -255,3 +255,18 @@ msgstr ""
msgid "邀请链接:"
msgstr ""
msgid "获取小红书作品数据成功"
msgstr ""
msgid "获取小红书作品数据失败"
msgstr ""
msgid "Web API 服务器已启动!"
msgstr ""
msgid "Web API 服务器已关闭!"
msgstr ""
msgid "服务器主机及端口: {0}"
msgstr ""

16
main.py
View File

@ -1,6 +1,7 @@
from asyncio import run
from sys import argv
from source import Settings
from source import XHS
from source import XHSDownloader
from source import cli
@ -47,13 +48,20 @@ async def example():
print(await xhs.extract(multiple_links, download, ))
async def main():
async def app():
async with XHSDownloader() as xhs:
await xhs.run_async()
async def server():
async with XHS(**Settings().run()) as xhs:
await xhs.run_server()
if __name__ == '__main__':
if len(argv) > 1:
cli()
if len(argv) == 1:
run(app())
elif argv[1] == "server":
run(server())
else:
run(main())
cli()

View File

@ -107,7 +107,7 @@ class Setting(Screen):
"language": self.query_one("#language").value,
"image_download": self.query_one("#image_download").value,
"video_download": self.query_one("#video_download").value,
"server": False,
# "server": False,
})
@on(Button.Pressed, "#abandon")

View File

@ -1,5 +1,6 @@
from .CLI import cli
from .TUI import XHSDownloader
from .application import XHS
from .module import Settings
__all__ = ['XHS', 'XHSDownloader', 'cli']
__all__ = ['XHS', 'XHSDownloader', 'cli', 'Settings', ]

View File

@ -1,3 +1,4 @@
from asyncio import CancelledError
from asyncio import Event
from asyncio import Queue
from asyncio import QueueEmpty
@ -9,6 +10,7 @@ from re import compile
from typing import Callable
from urllib.parse import urlparse
from aiohttp import web
from pyperclip import paste
from source.expansion import BrowserCookie
@ -22,6 +24,7 @@ from source.module import (
ERROR,
WARNING,
MASTER,
REPOSITORY,
)
from source.module import Translate
from source.module import logging
@ -62,7 +65,7 @@ class XHS:
video_download=True,
folder_mode=False,
language="zh_CN",
server=False,
# server=False,
transition: Callable[[str], str] = None,
read_cookie: int | str = None,
*args,
@ -85,6 +88,7 @@ class XHS:
image_download,
video_download,
folder_mode,
# server,
self.message,
)
self.html = Html(self.manager)
@ -98,7 +102,8 @@ class XHS:
self.clipboard_cache: str = ""
self.queue = Queue()
self.event = Event()
self.server = server
self.runner = self.init_server()
self.site = None
def __extract_image(self, container: dict, data: Namespace):
container["下载地址"] = self.image.get_image_link(
@ -279,3 +284,50 @@ class XHS:
def read_browser_cookie(value: str | int) -> str:
return BrowserCookie.get(
value, domain="xiaohongshu.com") if value else ""
@staticmethod
async def index(request):
return web.HTTPFound(REPOSITORY)
async def handle(self, request):
data = await request.post()
url = data.get("url")
download = data.get("download", False)
index = data.get("index")
skip = data.get("skip", False)
url = await self.__extract_links(url, None)
if not url:
msg = self.message("提取小红书作品链接失败")
data = None
else:
if data := await self.__deal_extract(url[0], download, index, None, None, not skip, ):
msg = self.message("获取小红书作品数据成功")
else:
msg = self.message("获取小红书作品数据失败")
data = None
return web.json_response(dict(message=msg, url=url[0], data=data))
def init_server(self, ):
app = web.Application(debug=True)
app.router.add_get('/', self.index)
app.router.add_post('/xhs/', self.handle)
return web.AppRunner(app)
async def run_server(self, log=None, ):
try:
await self.start_server(log)
while True:
await sleep(3600) # 保持服务器运行
except (CancelledError, KeyboardInterrupt):
await self.close_server(log)
async def start_server(self, log=None, ):
await self.runner.setup()
self.site = web.TCPSite(self.runner, "0.0.0.0")
await self.site.start()
logging(log, self.message("Web API 服务器已启动!"))
logging(log, self.message("服务器主机及端口: {0}".format(self.site.name, )))
async def close_server(self, log=None, ):
await self.runner.cleanup()
logging(log, self.message("Web API 服务器已关闭!"))

View File

@ -13,5 +13,5 @@ class Video:
@classmethod
def get_video_link(cls, data: Namespace) -> list:
return [Html.format_url(f"https://sns-video-hw.xhscdn.com/{t}")] if (
return [Html.format_url(f"https://sns-video-bd.xhscdn.com/{t}")] if (
t := data.safe_extract(".".join(cls.VIDEO_LINK))) else []

View File

@ -50,6 +50,7 @@ class Manager:
image_download: bool,
video_download: bool,
folder_mode: bool,
# server: bool,
transition: Callable[[str], str],
):
self.root = root
@ -77,6 +78,7 @@ class Manager:
self.message = transition
self.image_download = self.check_bool(image_download, True)
self.video_download = self.check_bool(video_download, True)
# self.server = self.check_bool(server, False)
def __check_path(self, path: str) -> Path:
if not path:

View File

@ -3,6 +3,8 @@ from json import load
from pathlib import Path
from platform import system
from .static import ROOT
__all__ = ['Settings']
@ -23,11 +25,11 @@ class Settings:
"video_download": True,
"folder_mode": False,
"language": "zh_CN",
"server": False,
# "server": False,
}
encode = "UTF-8-SIG" if system() == "Windows" else "UTF-8"
def __init__(self, root: Path):
def __init__(self, root: Path = ROOT):
self.file = root.joinpath("./settings.json")
def run(self):

View File

@ -1,7 +1,7 @@
// ==UserScript==
// @name XHS-Downloader
// @namespace https://github.com/JoeanAmier/XHS-Downloader
// @version 1.4.3
// @version 1.4.4
// @description 提取小红书作品/用户链接,下载小红书无水印图文/视频作品文件
// @author JoeanAmier
// @match http*://xhslink.com/*
@ -42,7 +42,7 @@
2. 提取账号发布收藏点赞作品链接时脚本会尝试自动滚动屏幕直至加载全部作品滚动检测间隔2.5
3. 提取搜索结果作品用户链接时脚本会自动滚动屏幕以尝试加载更多内容滚动屏幕次数10
4. 可以修改滚动检测间隔滚动屏幕次数修改后立即生效亦可关闭自动滚动屏幕功能手动滚动屏幕加载内容
5. XHS-Downloader 用户脚本仅实现可见即可得的数据采集功能无任何破解功能
5. XHS-Downloader 用户脚本仅实现可见即可得的数据采集功能无任何收费功能和破解功能
项目开源地址https://github.com/JoeanAmier/XHS-Downloader
`
@ -132,7 +132,7 @@
const generateVideoUrl = note => {
try {
return [`https://sns-video-hw.xhscdn.com/${note.video.consumer.originVideoKey}`];
return [`https://sns-video-bd.xhscdn.com/${note.video.consumer.originVideoKey}`];
} catch (error) {
console.error("Error generating video URL:", error);
return [];