mirror of
https://github.com/JoeanAmier/XHS-Downloader.git
synced 2026-03-22 06:57:16 +08:00
更新项目依赖库版本
This commit is contained in:
16
Dockerfile
Normal file
16
Dockerfile
Normal file
@@ -0,0 +1,16 @@
|
||||
FROM python:3.12.4-slim
|
||||
|
||||
LABEL name="XHS-Downloader" version="2.1 Beta" authors="JoeanAmier"
|
||||
|
||||
COPY locale /locale
|
||||
COPY source /source
|
||||
COPY static /static
|
||||
COPY LICENSE /LICENSE
|
||||
COPY main.py /main.py
|
||||
COPY README.md /README.md
|
||||
COPY README_EN.md /README_EN.md
|
||||
COPY requirements.txt /requirements.txt
|
||||
|
||||
RUN pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt
|
||||
|
||||
CMD ["python", "main.py"]
|
||||
41
README.md
41
README.md
@@ -40,6 +40,7 @@
|
||||
<li>✅ 提取搜索结果作品链接</li>
|
||||
<li>✅ 提取搜索结果用户链接</li>
|
||||
</ul>
|
||||
<p>⭐ XHS-Downloader 开发计划及进度可前往 <a href="https://github.com/users/JoeanAmier/projects/5">Projects</a> 查阅</p>
|
||||
<h1>📸 程序截图</h1>
|
||||
<p><b>🎥 点击图片观看演示视频</b></p>
|
||||
<a href="https://www.bilibili.com/video/BV1PJ4m1Y7Jt/"><img src="static/screenshot/程序运行截图CN1.png" alt=""></a>
|
||||
@@ -83,7 +84,8 @@
|
||||
<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><code>POST</code></p>
|
||||
<p><b>请求格式:</b><code>JSON</code></p>
|
||||
<p><b>请求参数:</b></p>
|
||||
<table>
|
||||
<thead>
|
||||
@@ -109,8 +111,8 @@
|
||||
</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">list[int]</td>
|
||||
<td align="center">下载指定序号的图片文件,仅对图文作品生效;<code>download</code> 参数设置为 <code>false</code> 时不生效</td>
|
||||
<td align="center">null</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -124,11 +126,17 @@
|
||||
<p><b>代码示例:</b></p>
|
||||
<pre>
|
||||
def api_demo():
|
||||
server = "http://127.0.0.1:8080"
|
||||
server = "http://127.0.0.1:8000/xhs/"
|
||||
data = {
|
||||
"url": "https://www.xiaohongshu.com/explore/123456789",
|
||||
"download": True,
|
||||
"index": [
|
||||
3,
|
||||
6,
|
||||
9,
|
||||
],
|
||||
}
|
||||
response = requests.post(server, data=data)
|
||||
response = requests.post(server, json=data)
|
||||
print(response.json())
|
||||
</pre>
|
||||
<h1>🕹 用户脚本</h1>
|
||||
@@ -152,6 +160,8 @@ async def example():
|
||||
work_path = "D:\\" # 作品数据/文件保存根路径,默认值:项目根路径
|
||||
folder_name = "Download" # 作品文件储存文件夹名称(自动创建),默认值:Download
|
||||
name_format = "作品标题 作品描述"
|
||||
sec_ch_ua = "" # 请求头 Sec-Ch-Ua
|
||||
sec_ch_ua_platform = "" # 请求头 Sec-Ch-Ua-Platform
|
||||
user_agent = "" # User-Agent
|
||||
cookie = "" # 小红书网页版 Cookie,无需登录,必需参数,登录状态对数据采集有影响
|
||||
proxy = None # 网络代理
|
||||
@@ -166,6 +176,8 @@ async def example():
|
||||
async with XHS(work_path=work_path,
|
||||
folder_name=folder_name,
|
||||
name_format=name_format,
|
||||
sec_ch_ua=sec_ch_ua,
|
||||
sec_ch_ua_platform=sec_ch_ua_platform,
|
||||
user_agent=user_agent,
|
||||
cookie=cookie,
|
||||
proxy=proxy,
|
||||
@@ -216,10 +228,22 @@ async def example():
|
||||
<td align="center"><code>发布时间 作者昵称 作品标题</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">sec_ch_ua</td>
|
||||
<td align="center">str</td>
|
||||
<td align="center">浏览器请求头 Sec-Ch-Ua</td>
|
||||
<td align="center">内置 Chrome Sec-Ch-Ua</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">sec_ch_ua_platform</td>
|
||||
<td align="center">str</td>
|
||||
<td align="center">浏览器请求头 Sec-Ch-Ua-Platform</td>
|
||||
<td align="center">内置 Chrome Sec-Ch-Ua-Platform</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>
|
||||
<td align="center">浏览器 User Agent</td>
|
||||
<td align="center">内置 Chrome User Agent</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">cookie</td>
|
||||
@@ -295,6 +319,8 @@ async def example():
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p><b>其他说明:<code>sec_ch_ua</code>、<code>sec_ch_ua_platform</code>、<code>user_agent</code>参数获取示例,仅当程序获取数据失败时需要自行设置!</b></p>
|
||||
<img src="static/screenshot/请求头示例图.png" alt="">
|
||||
<h1>🌐 Cookie</h1>
|
||||
<ol>
|
||||
<li>打开浏览器(可选无痕模式启动),访问 <code>https://www.xiaohongshu.com/explore</code></li>
|
||||
@@ -361,6 +387,7 @@ async def example():
|
||||
|
||||
* https://github.com/encode/httpx/
|
||||
* https://github.com/tiangolo/fastapi
|
||||
* https://github.com/textualize/textual/
|
||||
* https://textual.textualize.io/
|
||||
* https://aiosqlite.omnilib.dev/en/stable/
|
||||
* https://click.palletsprojects.com/en/8.1.x/
|
||||
|
||||
Binary file not shown.
@@ -271,7 +271,13 @@ msgstr "Web API server has been shut down!"
|
||||
msgid "服务器主机及端口: {0}"
|
||||
msgstr "Server host and port: {0}"
|
||||
|
||||
msgid "内置 Chrome User-Agent"
|
||||
msgid "内置 Chrome Sec-Ch-Ua"
|
||||
msgstr "Built in Chrome Sec-Ch-Ua"
|
||||
|
||||
msgid "内置 Chrome Sec-Ch-Ua-Platform"
|
||||
msgstr "Built in Chrome Sec-Ch-Ua-Platform"
|
||||
|
||||
msgid "内置 Chrome User Agent"
|
||||
msgstr "Built in Chrome User Agent"
|
||||
|
||||
msgid "proxy 参数 {0} 设置错误,程序将不会使用代理"
|
||||
|
||||
@@ -271,7 +271,13 @@ msgstr ""
|
||||
msgid "服务器主机及端口: {0}"
|
||||
msgstr ""
|
||||
|
||||
msgid "内置 Chrome User-Agent"
|
||||
msgid "内置 Chrome Sec-Ch-Ua"
|
||||
msgstr ""
|
||||
|
||||
msgid "内置 Chrome Sec-Ch-Ua-Platform"
|
||||
msgstr ""
|
||||
|
||||
msgid "内置 Chrome User Agent"
|
||||
msgstr ""
|
||||
|
||||
msgid "proxy 参数 {0} 设置错误,程序将不会使用代理"
|
||||
|
||||
27
main.py
27
main.py
@@ -1,4 +1,6 @@
|
||||
from asyncio import run
|
||||
from asyncio.exceptions import CancelledError
|
||||
from contextlib import suppress
|
||||
from sys import argv
|
||||
|
||||
from source import Settings
|
||||
@@ -17,6 +19,8 @@ async def example():
|
||||
work_path = "D:\\" # 作品数据/文件保存根路径,默认值:项目根路径
|
||||
folder_name = "Download" # 作品文件储存文件夹名称(自动创建),默认值:Download
|
||||
name_format = "作品标题 作品描述"
|
||||
sec_ch_ua = "" # 请求头 Sec-Ch-Ua
|
||||
sec_ch_ua_platform = "" # 请求头 Sec-Ch-Ua-Platform
|
||||
user_agent = "" # User-Agent
|
||||
cookie = "" # 小红书网页版 Cookie,无需登录,必需参数,登录状态对数据采集有影响
|
||||
proxy = None # 网络代理
|
||||
@@ -31,6 +35,8 @@ async def example():
|
||||
async with XHS(work_path=work_path,
|
||||
folder_name=folder_name,
|
||||
name_format=name_format,
|
||||
sec_ch_ua=sec_ch_ua,
|
||||
sec_ch_ua_platform=sec_ch_ua_platform,
|
||||
user_agent=user_agent,
|
||||
cookie=cookie,
|
||||
proxy=proxy,
|
||||
@@ -55,16 +61,19 @@ async def app():
|
||||
await xhs.run_async()
|
||||
|
||||
|
||||
async def server():
|
||||
async def server(host="127.0.0.1", port=8000, log_level="info", ):
|
||||
async with XHS(**Settings().run()) as xhs:
|
||||
await xhs.run_server()
|
||||
await xhs.run_server(host, port, log_level, )
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(argv) == 1:
|
||||
run(app())
|
||||
elif argv[1] == "server":
|
||||
print("该模式重构中!")
|
||||
# run(server())
|
||||
else:
|
||||
cli()
|
||||
with suppress(
|
||||
KeyboardInterrupt,
|
||||
CancelledError,
|
||||
):
|
||||
if len(argv) == 1:
|
||||
run(app())
|
||||
elif argv[1] == "server":
|
||||
run(server())
|
||||
else:
|
||||
cli()
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
textual>=0.47.1
|
||||
pyperclip>=1.8.2
|
||||
lxml>=5.1.0
|
||||
textual>=0.70.0
|
||||
pyperclip>=1.9.0
|
||||
lxml>=5.2.2
|
||||
PyYAML>=6.0.1
|
||||
aiosqlite>=0.20.0
|
||||
click>=8.1.7
|
||||
browser_cookie3>=0.19.1
|
||||
httpx>=0.27.0
|
||||
fastapi>=0.110.0
|
||||
uvicorn>=0.24.0
|
||||
fastapi>=0.111.0
|
||||
uvicorn>=0.30.1
|
||||
|
||||
@@ -123,6 +123,9 @@ class CLI:
|
||||
("--work_path", "-wp", "str", _("作品数据 / 文件保存根路径")),
|
||||
("--folder_name", "-fn", "str", _("作品文件储存文件夹名称")),
|
||||
("--name_format", "-nf", "str", _("作品文件名称格式")),
|
||||
("--sec_ch_ua", "-su", "str", _("Sec-Ch-Ua")),
|
||||
("--sec_ch_ua_platform", "-sp", "str", _("User-Agent")),
|
||||
("--user_agent", "-ua", "str", _("Sec-Ch-Ua-Platform")),
|
||||
("--cookie", "-ck", "str", _("小红书网页版 Cookie,无需登录")),
|
||||
("--proxy", "-p", "str", _("网络代理")),
|
||||
("--timeout", "-t", "int", _("请求数据超时限制,单位:秒")),
|
||||
@@ -163,6 +166,9 @@ class CLI:
|
||||
)
|
||||
@option("--folder_name", "-fn", )
|
||||
@option("--name_format", "-nf", )
|
||||
@option("--sec_ch_ua", "-su", )
|
||||
@option("--sec_ch_ua_platform", "-sp", )
|
||||
@option("--user_agent", "-ua", )
|
||||
@option("--cookie", "-ck", )
|
||||
@option("--proxy", "-p", )
|
||||
@option("--timeout", "-t", type=int, )
|
||||
|
||||
@@ -21,15 +21,15 @@ __all__ = ["About"]
|
||||
class About(Screen):
|
||||
BINDINGS = [
|
||||
Binding(
|
||||
key="q",
|
||||
key="Q",
|
||||
action="quit",
|
||||
description="退出程序/Quit"),
|
||||
Binding(
|
||||
key="u",
|
||||
action="check_update_about",
|
||||
key="U",
|
||||
action="check_update",
|
||||
description="检查更新/Update"),
|
||||
Binding(
|
||||
key="b",
|
||||
key="B",
|
||||
action="index",
|
||||
description="返回首页/Back"),
|
||||
]
|
||||
@@ -53,3 +53,12 @@ class About(Screen):
|
||||
|
||||
def on_mount(self) -> None:
|
||||
self.title = PROJECT
|
||||
|
||||
async def action_quit(self) -> None:
|
||||
await self.app.action_quit()
|
||||
|
||||
async def action_index(self):
|
||||
await self.app.push_screen("index")
|
||||
|
||||
async def action_check_update(self):
|
||||
await self.app.run_action("update_and_return")
|
||||
|
||||
@@ -14,7 +14,6 @@ from source.module import logging
|
||||
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
|
||||
@@ -78,15 +77,6 @@ class XHSDownloader(App):
|
||||
|
||||
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.close_database()
|
||||
@@ -117,13 +107,10 @@ class XHSDownloader(App):
|
||||
async def action_check_update(self):
|
||||
await self.push_screen(Update(self.APP, self.message), callback=self.update_result)
|
||||
|
||||
async def action_check_update_about(self):
|
||||
async def action_update_and_return(self):
|
||||
await self.push_screen("index")
|
||||
await self.action_check_update()
|
||||
|
||||
async def action_monitor(self):
|
||||
await self.push_screen(Monitor(self.APP, self.message))
|
||||
|
||||
async def close_database(self):
|
||||
await self.APP.id_recorder.cursor.close()
|
||||
await self.APP.id_recorder.database.close()
|
||||
|
||||
@@ -27,18 +27,19 @@ from source.module import (
|
||||
REPOSITORY,
|
||||
GENERAL,
|
||||
)
|
||||
from .monitor import Monitor
|
||||
|
||||
__all__ = ["Index"]
|
||||
|
||||
|
||||
class Index(Screen):
|
||||
BINDINGS = [
|
||||
Binding(key="q", action="quit", description="退出程序/Quit"),
|
||||
Binding(key="u", action="check_update", description="检查更新/Update"),
|
||||
Binding(key="s", action="settings", description="程序设置/Settings"),
|
||||
Binding(key="r", action="record", description="下载记录/Record"),
|
||||
Binding(key="m", action="monitor", description="开启监听/Monitor"),
|
||||
Binding(key="a", action="about", description="关于项目/About"),
|
||||
Binding(key="Q", action="quit", description="退出程序/Quit"),
|
||||
Binding(key="U", action="update", description="检查更新/Update"),
|
||||
Binding(key="S", action="settings", description="程序设置/Settings"),
|
||||
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, message: Callable[[str], str]):
|
||||
@@ -73,7 +74,7 @@ class Index(Screen):
|
||||
Button(self.message("清空输入框"), id="reset"),
|
||||
),
|
||||
)
|
||||
yield RichLog(markup=True, wrap=True)
|
||||
yield RichLog(markup=True, )
|
||||
yield Footer()
|
||||
|
||||
def on_mount(self) -> None:
|
||||
@@ -86,7 +87,8 @@ class Index(Screen):
|
||||
f"\n{
|
||||
">" *
|
||||
50}",
|
||||
style=MASTER), scroll_end=False)
|
||||
style=MASTER), scroll_end=False,
|
||||
)
|
||||
self.xhs.manager.print_proxy_tip(log=self.tip, )
|
||||
|
||||
@on(Button.Pressed, "#deal")
|
||||
@@ -114,3 +116,21 @@ class Index(Screen):
|
||||
self.tip.write(Text(self.message("下载小红书作品文件失败"), style=ERROR))
|
||||
self.tip.write(Text(">" * 50, style=GENERAL))
|
||||
self.app.pop_screen()
|
||||
|
||||
async def action_quit(self) -> None:
|
||||
await self.app.action_quit()
|
||||
|
||||
async def action_update(self) -> None:
|
||||
await self.app.run_action("check_update")
|
||||
|
||||
async def action_settings(self):
|
||||
await self.app.run_action("settings")
|
||||
|
||||
async def action_monitor(self):
|
||||
await self.app.push_screen(Monitor(self.xhs, self.message))
|
||||
|
||||
async def action_about(self):
|
||||
await self.app.push_screen("about")
|
||||
|
||||
async def action_record(self):
|
||||
await self.app.push_screen("record")
|
||||
|
||||
@@ -24,8 +24,8 @@ __all__ = ["Monitor"]
|
||||
|
||||
class Monitor(Screen):
|
||||
BINDINGS = [
|
||||
Binding(key="q", action="quit", description="退出程序/Quit"),
|
||||
Binding(key="c", action="close", description="关闭监听/Close"),
|
||||
Binding(key="Q", action="quit", description="退出程序/Quit"),
|
||||
Binding(key="C", action="close", description="关闭监听/Close"),
|
||||
]
|
||||
|
||||
def __init__(self, app: XHS, message: Callable[[str], str]):
|
||||
@@ -60,3 +60,7 @@ class Monitor(Screen):
|
||||
def action_close(self):
|
||||
self.xhs.stop_monitor()
|
||||
self.app.pop_screen()
|
||||
|
||||
async def action_quit(self) -> None:
|
||||
self.action_close()
|
||||
await self.app.action_quit()
|
||||
|
||||
@@ -15,7 +15,6 @@ __all__ = ["Record"]
|
||||
|
||||
|
||||
class Record(ModalScreen):
|
||||
|
||||
def __init__(self, app: XHS, message: Callable[[str], str]):
|
||||
super().__init__()
|
||||
self.xhs = app
|
||||
|
||||
@@ -19,8 +19,8 @@ __all__ = ["Setting"]
|
||||
|
||||
class Setting(Screen):
|
||||
BINDINGS = [
|
||||
Binding(key="q", action="quit", description="退出程序/Quit"),
|
||||
Binding(key="b", action="index", description="返回首页/Back"),
|
||||
Binding(key="Q", action="quit", description="退出程序/Quit"),
|
||||
Binding(key="B", action="index", description="返回首页/Back"),
|
||||
]
|
||||
|
||||
def __init__(self, data: dict, message: Callable[[str], str]):
|
||||
@@ -39,8 +39,15 @@ class Setting(Screen):
|
||||
Label(self.message("作品文件名称格式"), classes="params", ),
|
||||
Input(self.data["name_format"], placeholder=self.message("发布时间 作者昵称 作品标题"), valid_empty=True,
|
||||
id="name_format", ),
|
||||
Label(self.message("Sec-Ch-Ua"), classes="params", ),
|
||||
Input(self.data["sec_ch_ua"], placeholder=self.message("内置 Chrome Sec-Ch-Ua"), valid_empty=True,
|
||||
id="sec_ch_ua", ),
|
||||
Label(self.message("Sec-Ch-Ua-Platform"), classes="params", ),
|
||||
Input(self.data["sec_ch_ua_platform"], placeholder=self.message("内置 Chrome Sec-Ch-Ua-Platform"),
|
||||
valid_empty=True,
|
||||
id="sec_ch_ua_platform", ),
|
||||
Label(self.message("User-Agent"), classes="params", ),
|
||||
Input(self.data["user_agent"], placeholder=self.message("内置 Chrome User-Agent"), valid_empty=True,
|
||||
Input(self.data["user_agent"], placeholder=self.message("内置 Chrome User Agent"), valid_empty=True,
|
||||
id="user_agent", ),
|
||||
Label(self.message("小红书网页版 Cookie"), classes="params", ),
|
||||
Input(placeholder=self.__check_cookie(), valid_empty=True, id="cookie", ),
|
||||
@@ -101,6 +108,8 @@ class Setting(Screen):
|
||||
"work_path": self.query_one("#work_path").value,
|
||||
"folder_name": self.query_one("#folder_name").value,
|
||||
"name_format": self.query_one("#name_format").value,
|
||||
"sec_ch_ua": self.query_one("#sec_ch_ua").value,
|
||||
"sec_ch_ua_platform": self.query_one("#sec_ch_ua_platform").value,
|
||||
"user_agent": self.query_one("#user_agent").value,
|
||||
"cookie": self.query_one("#cookie").value or self.data["cookie"],
|
||||
"proxy": self.query_one("#proxy").value or None,
|
||||
@@ -120,3 +129,9 @@ class Setting(Screen):
|
||||
@on(Button.Pressed, "#abandon")
|
||||
def reset(self):
|
||||
self.dismiss(self.data)
|
||||
|
||||
async def action_quit(self) -> None:
|
||||
await self.app.action_quit()
|
||||
|
||||
async def action_index(self):
|
||||
await self.app.push_screen("index")
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# from asyncio import CancelledError
|
||||
from asyncio import Event
|
||||
from asyncio import Queue
|
||||
from asyncio import QueueEmpty
|
||||
@@ -10,13 +9,19 @@ from re import compile
|
||||
from typing import Callable
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.responses import RedirectResponse
|
||||
# from aiohttp import web
|
||||
from pyperclip import paste
|
||||
from uvicorn import Config
|
||||
from uvicorn import Server
|
||||
|
||||
from source.expansion import BrowserCookie
|
||||
from source.expansion import Converter
|
||||
from source.expansion import Namespace
|
||||
from source.module import DataRecorder
|
||||
from source.module import ExtractData
|
||||
from source.module import ExtractParams
|
||||
from source.module import IDRecorder
|
||||
from source.module import Manager
|
||||
from source.module import (
|
||||
@@ -24,7 +29,9 @@ from source.module import (
|
||||
ERROR,
|
||||
WARNING,
|
||||
MASTER,
|
||||
# REPOSITORY,
|
||||
REPOSITORY,
|
||||
VERSION_MAJOR,
|
||||
VERSION_MINOR,
|
||||
)
|
||||
from source.module import Translate
|
||||
from source.module import logging
|
||||
@@ -53,6 +60,8 @@ class XHS:
|
||||
work_path="",
|
||||
folder_name="Download",
|
||||
name_format="发布时间 作者昵称 作品标题",
|
||||
sec_ch_ua: str = "",
|
||||
sec_ch_ua_platform: str = "",
|
||||
user_agent: str = None,
|
||||
cookie: str = None,
|
||||
proxy: str | dict = None,
|
||||
@@ -80,6 +89,8 @@ class XHS:
|
||||
folder_name,
|
||||
name_format,
|
||||
chunk,
|
||||
sec_ch_ua,
|
||||
sec_ch_ua_platform,
|
||||
user_agent,
|
||||
self.read_browser_cookie(read_cookie) or cookie,
|
||||
proxy,
|
||||
@@ -108,6 +119,7 @@ class XHS:
|
||||
self.event = Event()
|
||||
# self.runner = self.init_server()
|
||||
# self.site = None
|
||||
self.server = None
|
||||
|
||||
def __extract_image(self, container: dict, data: Namespace):
|
||||
container["下载地址"], container["动图地址"] = self.image.get_image_link(
|
||||
@@ -339,3 +351,46 @@ class XHS:
|
||||
# async def close_server(self, log=None, ):
|
||||
# await self.runner.cleanup()
|
||||
# logging(log, self.message("Web API 服务器已关闭!"))
|
||||
|
||||
async def run_server(self, host="127.0.0.1", port=8000, log_level="info", ):
|
||||
self.server = FastAPI(
|
||||
title="XHS-Downloader",
|
||||
version=f"{VERSION_MAJOR}.{VERSION_MINOR}")
|
||||
self.setup_routes()
|
||||
config = Config(
|
||||
self.server,
|
||||
host=host,
|
||||
port=port,
|
||||
log_level=log_level,
|
||||
)
|
||||
server = Server(config)
|
||||
await server.serve()
|
||||
|
||||
def setup_routes(self):
|
||||
@self.server.get("/")
|
||||
async def index():
|
||||
return RedirectResponse(url=REPOSITORY)
|
||||
|
||||
@self.server.post("/xhs/", response_model=ExtractData, )
|
||||
async def handle(extract: ExtractParams):
|
||||
url = await self.__extract_links(extract.url, None)
|
||||
if not url:
|
||||
msg = self.message("提取小红书作品链接失败")
|
||||
data = None
|
||||
else:
|
||||
if data := await self.__deal_extract(
|
||||
url[0],
|
||||
extract.download,
|
||||
extract.index,
|
||||
None,
|
||||
None,
|
||||
not extract.skip,
|
||||
):
|
||||
msg = self.message("获取小红书作品数据成功")
|
||||
else:
|
||||
msg = self.message("获取小红书作品数据失败")
|
||||
data = None
|
||||
return ExtractData(
|
||||
message=msg,
|
||||
url=url[0] if url else extract.url,
|
||||
data=data)
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
from .extend import Account
|
||||
from .manager import Manager
|
||||
from .model import (
|
||||
ExtractData,
|
||||
ExtractParams,
|
||||
)
|
||||
from .recorder import DataRecorder
|
||||
from .recorder import IDRecorder
|
||||
from .settings import Settings
|
||||
@@ -22,38 +26,11 @@ from .static import (
|
||||
HEADERS,
|
||||
PROJECT,
|
||||
USERAGENT,
|
||||
SEC_CH_UA,
|
||||
SEC_CH_UA_PLATFORM,
|
||||
)
|
||||
from .tools import (
|
||||
retry,
|
||||
logging,
|
||||
)
|
||||
from .translator import Translate
|
||||
|
||||
__all__ = [
|
||||
"Account",
|
||||
"Settings",
|
||||
"IDRecorder",
|
||||
"Manager",
|
||||
"VERSION_MAJOR",
|
||||
"VERSION_MINOR",
|
||||
"VERSION_BETA",
|
||||
"ROOT",
|
||||
"REPOSITORY",
|
||||
"LICENCE",
|
||||
"RELEASES",
|
||||
"MASTER",
|
||||
"PROMPT",
|
||||
"GENERAL",
|
||||
"PROGRESS",
|
||||
"ERROR",
|
||||
"WARNING",
|
||||
"INFO",
|
||||
"USERSCRIPT",
|
||||
"HEADERS",
|
||||
"retry",
|
||||
"logging",
|
||||
"PROJECT",
|
||||
"Translate",
|
||||
"DataRecorder",
|
||||
"USERAGENT",
|
||||
]
|
||||
|
||||
@@ -11,6 +11,8 @@ from httpx import TimeoutException
|
||||
from httpx import get
|
||||
|
||||
from .static import HEADERS
|
||||
from .static import SEC_CH_UA
|
||||
from .static import SEC_CH_UA_PLATFORM
|
||||
from .static import USERAGENT
|
||||
from .static import WARNING
|
||||
from .tools import logging
|
||||
@@ -48,6 +50,8 @@ class Manager:
|
||||
folder: str,
|
||||
name_format: str,
|
||||
chunk: int,
|
||||
sec_ch_ua: str,
|
||||
sec_ch_ua_platform: str,
|
||||
user_agent: str,
|
||||
cookie: str,
|
||||
proxy: str | dict,
|
||||
@@ -68,7 +72,11 @@ class Manager:
|
||||
self.path = self.__check_path(path)
|
||||
self.folder = self.__check_folder(folder)
|
||||
self.message = transition
|
||||
self.blank_headers = HEADERS | {"User-Agent": user_agent or USERAGENT}
|
||||
self.blank_headers = HEADERS | {
|
||||
"User-Agent": user_agent or USERAGENT,
|
||||
"Sec-Ch-Ua": sec_ch_ua or SEC_CH_UA,
|
||||
"Sec-Ch-Ua-Platform": sec_ch_ua_platform or SEC_CH_UA_PLATFORM,
|
||||
}
|
||||
self.headers = self.blank_headers | {"Cookie": cookie}
|
||||
self.retry = retry
|
||||
self.chunk = chunk
|
||||
@@ -83,11 +91,13 @@ class Manager:
|
||||
headers=self.headers | {
|
||||
"Referer": "https://www.xiaohongshu.com/explore", },
|
||||
timeout=timeout,
|
||||
verify=False,
|
||||
**self.proxy,
|
||||
)
|
||||
self.download_client = AsyncClient(
|
||||
headers=self.blank_headers,
|
||||
timeout=timeout,
|
||||
verify=False,
|
||||
**self.proxy,
|
||||
)
|
||||
self.image_download = self.check_bool(image_download, True)
|
||||
|
||||
14
source/module/model.py
Normal file
14
source/module/model.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ExtractParams(BaseModel):
|
||||
url: str
|
||||
download: bool = False
|
||||
index: list = None
|
||||
skip: bool = False
|
||||
|
||||
|
||||
class ExtractData(BaseModel):
|
||||
message: str
|
||||
url: str
|
||||
data: dict | None
|
||||
@@ -4,6 +4,8 @@ from pathlib import Path
|
||||
from platform import system
|
||||
|
||||
from .static import ROOT
|
||||
from .static import SEC_CH_UA
|
||||
from .static import SEC_CH_UA_PLATFORM
|
||||
from .static import USERAGENT
|
||||
|
||||
__all__ = ['Settings']
|
||||
@@ -14,6 +16,8 @@ class Settings:
|
||||
"work_path": "",
|
||||
"folder_name": "Download",
|
||||
"name_format": "发布时间 作者昵称 作品标题",
|
||||
"sec_ch_ua": SEC_CH_UA,
|
||||
"sec_ch_ua_platform": SEC_CH_UA_PLATFORM,
|
||||
"user_agent": USERAGENT,
|
||||
"cookie": "",
|
||||
"proxy": None,
|
||||
|
||||
@@ -19,6 +19,8 @@ __all__ = [
|
||||
"HEADERS",
|
||||
"PROJECT",
|
||||
"USERAGENT",
|
||||
"SEC_CH_UA",
|
||||
"SEC_CH_UA_PLATFORM",
|
||||
]
|
||||
|
||||
VERSION_MAJOR = 2
|
||||
@@ -37,6 +39,8 @@ USERSCRIPT = "https://raw.githubusercontent.com/JoeanAmier/XHS-Downloader/master
|
||||
USERAGENT = (
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 "
|
||||
"Safari/537.36")
|
||||
SEC_CH_UA = "\"Not/A)Brand\";v=\"8\", \"Chromium\";v=\"126\", \"Google Chrome\";v=\"126\""
|
||||
SEC_CH_UA_PLATFORM = "\"Windows\""
|
||||
|
||||
HEADERS = {
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,"
|
||||
@@ -46,9 +50,9 @@ HEADERS = {
|
||||
"Cookie": "",
|
||||
"Dnt": "1",
|
||||
# "Priority": "u=0, i",
|
||||
# "Sec-Ch-Ua": "\"Not/A)Brand\";v=\"8\", \"Chromium\";v=\"126\", \"Google Chrome\";v=\"126\"",
|
||||
"Sec-Ch-Ua": SEC_CH_UA,
|
||||
"Sec-Ch-Ua-Mobile": "?0",
|
||||
# "Sec-Ch-Ua-Platform": "\"Windows\"",
|
||||
"Sec-Ch-Ua-Platform": SEC_CH_UA_PLATFORM,
|
||||
"Sec-Fetch-Dest": "document",
|
||||
"Sec-Fetch-Mode": "navigate",
|
||||
"Sec-Fetch-Site": "none",
|
||||
|
||||
BIN
static/screenshot/脚本安装教程.png
Normal file
BIN
static/screenshot/脚本安装教程.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
BIN
static/screenshot/请求头示例图.png
Normal file
BIN
static/screenshot/请求头示例图.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
Reference in New Issue
Block a user