Compare commits
106 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
8158292756 | ||
|
a790aad8df | ||
|
1e69ee8a14 | ||
|
ff72578a9c | ||
|
f7a3acc754 | ||
|
b38e210927 | ||
|
ce385d9608 | ||
|
d87cf4f837 | ||
|
336267fac4 | ||
|
2114bc925e | ||
|
54e50598de | ||
|
fc90897396 | ||
|
942bc35912 | ||
|
3b1f9e7b5c | ||
|
40b4462464 | ||
|
ee06c65269 | ||
|
ebf6671d33 | ||
|
87b48aec06 | ||
|
a140c2f3f8 | ||
|
1c48f64b75 | ||
|
321d4622a1 | ||
|
a984186542 | ||
|
72aca75960 | ||
|
064ae7dc63 | ||
|
48fcf902be | ||
|
ada1851e86 | ||
|
9aaab2f691 | ||
|
5c6dd6fb45 | ||
|
8f6c1ea70d | ||
|
59b24472c7 | ||
|
25614f9a67 | ||
|
6808355e9f | ||
|
1ef56da99b | ||
|
596a0fc633 | ||
|
2f13bd5122 | ||
|
d3d091d9dd | ||
|
372450b4d7 | ||
|
918d6c9eba | ||
|
2ea2ac07a8 | ||
|
aebc14429d | ||
|
72a57668c7 | ||
|
99c62922ca | ||
|
f2c1587e53 | ||
|
f0d8223f14 | ||
|
01fbd743f8 | ||
|
47b7662a66 | ||
|
0314e2520d | ||
|
8ce13c0d72 | ||
|
2496ccd90c | ||
|
f55dd004e9 | ||
|
cbfd137184 | ||
|
262c10495c | ||
|
e900d1d2ab | ||
|
a6c86629e6 | ||
|
ec559e2913 | ||
|
9fc37f1048 | ||
|
6b6978df49 | ||
|
c9ece0bf50 | ||
|
20350b8889 | ||
|
794632d6c5 | ||
|
38fc76826d | ||
|
0fb3739b47 | ||
|
ebb60c8ee7 | ||
|
def3dd77e7 | ||
|
8ea8871cc8 | ||
|
c5b9ec5faf | ||
|
f753c5c52e | ||
|
132ab70094 | ||
|
04b3279975 | ||
|
939a417654 | ||
|
f8ec5e3745 | ||
|
89d4c7cd42 | ||
|
f224557461 | ||
|
4c1a4ed950 | ||
|
e194a2818b | ||
|
eb570b183c | ||
|
d9c224bd2d | ||
|
e4a0ebba4f | ||
|
39f3920e54 | ||
|
14f51a62aa | ||
|
83e46322ee | ||
|
af63aadcfc | ||
|
85942f2ffc | ||
|
322f004e69 | ||
|
723da7640e | ||
|
8c5863d44f | ||
|
3fcacbd14c | ||
|
e608e5431b | ||
|
1b85b19891 | ||
|
3c2ed3cd78 | ||
|
50beb45228 | ||
|
7c44b9426d | ||
|
469fe2c521 | ||
|
98a9f74d4b | ||
|
80825b2fc2 | ||
|
ee4ec700b8 | ||
|
04ec0f1c40 | ||
|
42436948f7 | ||
|
44670186af | ||
|
fb82300a27 | ||
|
6f2908c116 | ||
|
90aa10515f | ||
|
f7fdcd141b | ||
|
2183ebdb41 | ||
|
5536380d53 | ||
|
a4328cd53b |
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: 报告出现的Bug
|
||||||
|
title: "[BUG]"
|
||||||
|
labels: bug
|
||||||
|
assignees: imgyh
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Bug 描述**
|
||||||
|
对bug的清晰而简洁的描述。
|
||||||
|
|
||||||
|
**Bug 复现**
|
||||||
|
复现这次行为的步骤:
|
||||||
|
1. 更改了 '...'
|
||||||
|
2. 点击了 '....'
|
||||||
|
3. '....'
|
||||||
|
|
||||||
|
**预期行为**
|
||||||
|
简单描述预期结果。
|
||||||
|
|
||||||
|
**截图**
|
||||||
|
如果适用,请添加屏幕截图以帮助解释您的问题。
|
||||||
|
|
||||||
|
**环境信息 (请填写以下信息):**
|
||||||
|
- 操作系统: [e.g. windows]
|
||||||
|
- 命令: [e.g. DouyinCommand.py, WebApi.py, DouyinCommand.exe, WebApi.exe]
|
||||||
|
- 版本 [e.g. v1.6.4]
|
||||||
|
|
||||||
|
**附文**
|
||||||
|
在此处添加有关该问题的其他文字。
|
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: 对该项目的建议
|
||||||
|
title: "[Feature]"
|
||||||
|
labels: enhancement
|
||||||
|
assignees: imgyh
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**描述您想要的功能**
|
||||||
|
对你想要加入的功能的清晰而简洁的描述。
|
||||||
|
|
||||||
|
**描述你想要替换的功能或需要修改的解决方案**
|
||||||
|
对您考虑过的任何替代解决方案或功能的清晰简洁的描述。
|
||||||
|
|
||||||
|
**附文**
|
||||||
|
在此处添加有关功能请求的任何其他文字或屏幕截图。
|
22
.github/ISSUE_TEMPLATE/help_want.md
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
name: Help Want
|
||||||
|
about: 想要得到帮助,非Bug问题,如程序不会用、部署相关等问题
|
||||||
|
title: "[Help]"
|
||||||
|
labels: help wanted
|
||||||
|
assignees: imgyh
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**问题描述**
|
||||||
|
对您的遇到的问题清晰而简洁的描述。
|
||||||
|
|
||||||
|
**截图**
|
||||||
|
如果适用,请添加屏幕截图以帮助解释您的问题。
|
||||||
|
|
||||||
|
**环境信息 (请填写以下信息):**
|
||||||
|
- 操作系统: [e.g. windows]
|
||||||
|
- 命令: [e.g. DouyinCommand.py, WebApi.py, DouyinCommand.exe, WebApi.exe]
|
||||||
|
- 版本 [e.g. v1.6.4]
|
||||||
|
|
||||||
|
**附文**
|
||||||
|
在此处添加有关该问题的其他文字。
|
1
.github/workflows/docker.yml
vendored
@ -51,6 +51,7 @@ jobs:
|
|||||||
platforms: |
|
platforms: |
|
||||||
linux/amd64
|
linux/amd64
|
||||||
linux/386
|
linux/386
|
||||||
|
linux/arm/v7
|
||||||
linux/arm64/v8
|
linux/arm64/v8
|
||||||
# docker build arg, 注入 APP_NAME/APP_VERSION
|
# docker build arg, 注入 APP_NAME/APP_VERSION
|
||||||
build-args: |
|
build-args: |
|
||||||
|
7
.github/workflows/pyinstaller.yml
vendored
@ -28,7 +28,8 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
python -m pip install -r requirements.txt
|
python -m pip install -r requirements.txt
|
||||||
pyinstaller -i ./static/img/favicon.ico -F TikTokCommand.py
|
pyinstaller -i ./static/img/favicon.ico -F DouYinCommand.py
|
||||||
|
pyinstaller -i ./static/img/favicon.ico -F WebApi.py --add-data "templates;templates" --add-data "static;static"
|
||||||
|
|
||||||
- name: Create Release and Upload Release Asset
|
- name: Create Release and Upload Release Asset
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
@ -45,6 +46,8 @@ jobs:
|
|||||||
# 如果指定了名称,将使用指定的名称;否则,将自动生成一个名称。
|
# 如果指定了名称,将使用指定的名称;否则,将自动生成一个名称。
|
||||||
# 如果指定了正文,正文将被添加到自动生成的注释中。
|
# 如果指定了正文,正文将被添加到自动生成的注释中。
|
||||||
files: | # 多个文件要加 |
|
files: | # 多个文件要加 |
|
||||||
dist/TikTokCommand.exe
|
dist/DouYinCommand.exe
|
||||||
|
dist/WebApi.exe
|
||||||
|
config.yml
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # 默认为${{ github.token }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # 默认为${{ github.token }}
|
||||||
|
321
.gitignore
vendored
@ -1,160 +1,161 @@
|
|||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
*$py.class
|
*$py.class
|
||||||
|
|
||||||
# C extensions
|
# C extensions
|
||||||
*.so
|
*.so
|
||||||
|
|
||||||
# Distribution / packaging
|
# Distribution / packaging
|
||||||
.Python
|
.Python
|
||||||
build/
|
build/
|
||||||
develop-eggs/
|
develop-eggs/
|
||||||
dist/
|
dist/
|
||||||
downloads/
|
downloads/
|
||||||
eggs/
|
eggs/
|
||||||
.eggs/
|
.eggs/
|
||||||
lib/
|
lib/
|
||||||
lib64/
|
lib64/
|
||||||
parts/
|
parts/
|
||||||
sdist/
|
sdist/
|
||||||
var/
|
var/
|
||||||
wheels/
|
wheels/
|
||||||
share/python-wheels/
|
share/python-wheels/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
.installed.cfg
|
.installed.cfg
|
||||||
*.egg
|
*.egg
|
||||||
MANIFEST
|
MANIFEST
|
||||||
|
|
||||||
# PyInstaller
|
# PyInstaller
|
||||||
# Usually these files are written by a python script from a template
|
# Usually these files are written by a python script from a template
|
||||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
*.manifest
|
*.manifest
|
||||||
*.spec
|
*.spec
|
||||||
|
|
||||||
# Installer logs
|
# Installer logs
|
||||||
pip-log.txt
|
pip-log.txt
|
||||||
pip-delete-this-directory.txt
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
# Unit test / coverage reports
|
# Unit test / coverage reports
|
||||||
htmlcov/
|
htmlcov/
|
||||||
.tox/
|
.tox/
|
||||||
.nox/
|
.nox/
|
||||||
.coverage
|
.coverage
|
||||||
.coverage.*
|
.coverage.*
|
||||||
.cache
|
.cache
|
||||||
nosetests.xml
|
nosetests.xml
|
||||||
coverage.xml
|
coverage.xml
|
||||||
*.cover
|
*.cover
|
||||||
*.py,cover
|
*.py,cover
|
||||||
.hypothesis/
|
.hypothesis/
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
cover/
|
cover/
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
*.mo
|
*.mo
|
||||||
*.pot
|
*.pot
|
||||||
|
|
||||||
# Django stuff:
|
# Django stuff:
|
||||||
*.log
|
*.log
|
||||||
local_settings.py
|
local_settings.py
|
||||||
db.sqlite3
|
db.sqlite3
|
||||||
db.sqlite3-journal
|
db.sqlite3-journal
|
||||||
|
|
||||||
# Flask stuff:
|
# Flask stuff:
|
||||||
instance/
|
instance/
|
||||||
.webassets-cache
|
.webassets-cache
|
||||||
|
|
||||||
# Scrapy stuff:
|
# Scrapy stuff:
|
||||||
.scrapy
|
.scrapy
|
||||||
|
|
||||||
# Sphinx documentation
|
# Sphinx documentation
|
||||||
docs/_build/
|
docs/_build/
|
||||||
|
|
||||||
# PyBuilder
|
# PyBuilder
|
||||||
.pybuilder/
|
.pybuilder/
|
||||||
target/
|
target/
|
||||||
|
|
||||||
# Jupyter Notebook
|
# Jupyter Notebook
|
||||||
.ipynb_checkpoints
|
.ipynb_checkpoints
|
||||||
|
|
||||||
# IPython
|
# IPython
|
||||||
profile_default/
|
profile_default/
|
||||||
ipython_config.py
|
ipython_config.py
|
||||||
|
|
||||||
# pyenv
|
# pyenv
|
||||||
# For a library or package, you might want to ignore these files since the code is
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
# intended to run in multiple environments; otherwise, check them in:
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
# .python-version
|
# .python-version
|
||||||
|
|
||||||
# pipenv
|
# pipenv
|
||||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
# install all needed dependencies.
|
# install all needed dependencies.
|
||||||
#Pipfile.lock
|
#Pipfile.lock
|
||||||
|
|
||||||
# poetry
|
# poetry
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
# commonly ignored for libraries.
|
# commonly ignored for libraries.
|
||||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||||
#poetry.lock
|
#poetry.lock
|
||||||
|
|
||||||
# pdm
|
# pdm
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||||
#pdm.lock
|
#pdm.lock
|
||||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||||
# in version control.
|
# in version control.
|
||||||
# https://pdm.fming.dev/#use-with-ide
|
# https://pdm.fming.dev/#use-with-ide
|
||||||
.pdm.toml
|
.pdm.toml
|
||||||
|
|
||||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
__pypackages__/
|
__pypackages__/
|
||||||
|
|
||||||
# Celery stuff
|
# Celery stuff
|
||||||
celerybeat-schedule
|
celerybeat-schedule
|
||||||
celerybeat.pid
|
celerybeat.pid
|
||||||
|
|
||||||
# SageMath parsed files
|
# SageMath parsed files
|
||||||
*.sage.py
|
*.sage.py
|
||||||
|
|
||||||
# Environments
|
# Environments
|
||||||
.env
|
.env
|
||||||
.venv
|
.venv
|
||||||
env/
|
env/
|
||||||
venv/
|
venv/
|
||||||
ENV/
|
ENV/
|
||||||
env.bak/
|
env.bak/
|
||||||
venv.bak/
|
venv.bak/
|
||||||
|
|
||||||
# Spyder project settings
|
# Spyder project settings
|
||||||
.spyderproject
|
.spyderproject
|
||||||
.spyproject
|
.spyproject
|
||||||
|
|
||||||
# Rope project settings
|
# Rope project settings
|
||||||
.ropeproject
|
.ropeproject
|
||||||
|
|
||||||
# mkdocs documentation
|
# mkdocs documentation
|
||||||
/site
|
/site
|
||||||
|
|
||||||
# mypy
|
# mypy
|
||||||
.mypy_cache/
|
.mypy_cache/
|
||||||
.dmypy.json
|
.dmypy.json
|
||||||
dmypy.json
|
dmypy.json
|
||||||
|
|
||||||
# Pyre type checker
|
# Pyre type checker
|
||||||
.pyre/
|
.pyre/
|
||||||
|
|
||||||
# pytype static type analyzer
|
# pytype static type analyzer
|
||||||
.pytype/
|
.pytype/
|
||||||
|
|
||||||
# Cython debug symbols
|
# Cython debug symbols
|
||||||
cython_debug/
|
cython_debug/
|
||||||
|
|
||||||
# PyCharm
|
# PyCharm
|
||||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
.idea/
|
.idea/
|
||||||
|
*.db
|
168
CHANGELOG.md
@ -1,3 +1,171 @@
|
|||||||
|
# [](https://github.com/imgyh/tiktok/compare/v1.6.4...v) (2023-05-28)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **webapi:** 修复作品列表为空时不返回cursor ([1e69ee8](https://github.com/imgyh/tiktok/commit/1e69ee8a140b7761f3ebdebf1b1d9e31af79bd2c)), closes [#40](https://github.com/imgyh/tiktok/issues/40)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **douyin:** 修改文件名命名风格 ([b38e210](https://github.com/imgyh/tiktok/commit/b38e2109271a07e408bbbad2454ebc99f48437fe))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [](https://github.com/imgyh/tiktok/compare/v1.6.3...v) (2023-05-21)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **tiktok:** 规范包目录结构 ([942bc35](https://github.com/imgyh/tiktok/commit/942bc359126a0ce9241b44576063bf42b161d02a))
|
||||||
|
* **tiktok:** 删除多余文件 ([2114bc9](https://github.com/imgyh/tiktok/commit/2114bc925edf8c050834f1ba2f3ddb4be22b6ed3))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [](https://github.com/imgyh/tiktok/compare/v1.6.2...v) (2023-04-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **tiktok:** 优化视频链接获取 ([321d462](https://github.com/imgyh/tiktok/commit/321d4622a16a26cc8587e16c0e5b9f8601c16f99))
|
||||||
|
* **web:** 解决浏览器提示不安全问题 ([87b48ae](https://github.com/imgyh/tiktok/commit/87b48aec06bce777c427d03ab02590fd7089bb9b))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **tiktok:** 增加数据库功能与增量更新功能 ([ebf6671](https://github.com/imgyh/tiktok/commit/ebf6671d336767595a01941562db397546ab2fe9)), closes [#24](https://github.com/imgyh/tiktok/issues/24)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [](https://github.com/imgyh/tiktok/compare/v1.6.1...v) (2023-04-21)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **result:** 修复1080p清晰度链接 ([ada1851](https://github.com/imgyh/tiktok/commit/ada1851e869ae4a65a43fb07eff3c22baeced087))
|
||||||
|
* **tiktok:** 修复接口失效问题 ([9aaab2f](https://github.com/imgyh/tiktok/commit/9aaab2f691adff5f43c0f40899b1eb37cd4665d5)), closes [#33](https://github.com/imgyh/tiktok/issues/33)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **web:** 前段静态文件使用国内cdn ([72aca75](https://github.com/imgyh/tiktok/commit/72aca75960b0f2fe4ef918682debec5c9af8cb59))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [](https://github.com/imgyh/tiktok/compare/v1.6.0...v) (2023-04-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **command:** 修复mode命令解析问题 ([aebc144](https://github.com/imgyh/tiktok/commit/aebc14429ded01203b2d3b3385d7f1bdcbafae1a))
|
||||||
|
* **tiktok:** 增加请求重试机制 ([8f6c1ea](https://github.com/imgyh/tiktok/commit/8f6c1ea70dd4da63c772f45f0326047deb3daef7))
|
||||||
|
* **tiktok:** 更改多作品接口请求逻辑, 不再调用单个作品的方法 ([2ea2ac0](https://github.com/imgyh/tiktok/commit/2ea2ac07a844421d665684e3273c3b6b8e7064a6))
|
||||||
|
* **tiktok:** 缩短文件名长度,只使用数字字母汉字作为文件名 ([99c6292](https://github.com/imgyh/tiktok/commit/99c62922ca701de2e7eccb68b3c3e67b98d9fcef)), closes [#19](https://github.com/imgyh/tiktok/issues/19)
|
||||||
|
* **web:** 前端适配新接口 ([2f13bd5](https://github.com/imgyh/tiktok/commit/2f13bd5122b25e507cf55a58aea24396016414da))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **command:** 未传入cookie则使用默认值 ([596a0fc](https://github.com/imgyh/tiktok/commit/596a0fc63308c2ea515c305161f115e6914f7504))
|
||||||
|
* **web:** 增加解析接口 ([918d6c9](https://github.com/imgyh/tiktok/commit/918d6c9ebaa47ce7259fc2f23efbd53b320095a9)), closes [#28](https://github.com/imgyh/tiktok/issues/28)
|
||||||
|
* **web:** 所有接口支持json与form两种格式, cookie不传使用默认值 ([d3d091d](https://github.com/imgyh/tiktok/commit/d3d091d9ddadf1f01588b7e273b40356ed09cfa2)), closes [#31](https://github.com/imgyh/tiktok/issues/31)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [](https://github.com/imgyh/tiktok/compare/v1.5.5...v) (2023-03-28)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **tiktok:** 使用tqdm进度条 ([f55dd00](https://github.com/imgyh/tiktok/commit/f55dd004e9eea039fea04ed9e8e1325bca62c363))
|
||||||
|
* **tiktok:** 修复主页作品获取失败 ([c5b9ec5](https://github.com/imgyh/tiktok/commit/c5b9ec5faf90c4d00c5cc24b48a774404344be19)), closes [#15](https://github.com/imgyh/tiktok/issues/15)
|
||||||
|
* **tiktok:** 修复获取单个合集id失败 ([f753c5c](https://github.com/imgyh/tiktok/commit/f753c5c52e4fa54f04f05d555d61b22b544a2169))
|
||||||
|
* **tiktok:** 修复配置文件路径获取问题 ([2496ccd](https://github.com/imgyh/tiktok/commit/2496ccd90ca5232c61a2ed213f85235fa26354b3))
|
||||||
|
* **tiktok:** 修改文件名匹配正则表达式 ([8ce13c0](https://github.com/imgyh/tiktok/commit/8ce13c0d72379accbfd38ba083728df87719d471))
|
||||||
|
* **tiktok:** 增加文件下载失败重试机制 ([794632d](https://github.com/imgyh/tiktok/commit/794632d6c5dce68b9eade8094e74231aa90421f0))
|
||||||
|
* **tiktok:** 缩短文件名长度 ([01fbd74](https://github.com/imgyh/tiktok/commit/01fbd743f82552e52099637871246ddc21949fc2))
|
||||||
|
* **tiktok:** 重试失败后返回已经获取到的数据, 而不是抛异常 ([9fc37f1](https://github.com/imgyh/tiktok/commit/9fc37f1048fbd182e00eea89b71ec644f7d9df56))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **tiktok:** 使用rich进度条 ([0fb3739](https://github.com/imgyh/tiktok/commit/0fb3739b4734910b6a0d35fdb0033921ef854adb))
|
||||||
|
* **tiktok:** 单个作品使用多线程下载 ([38fc768](https://github.com/imgyh/tiktok/commit/38fc76826d20257ec63bb7fcdea6eeca38a8aa6d))
|
||||||
|
* **tiktok:** 增加json数据是否保存的开关 ([8ea8871](https://github.com/imgyh/tiktok/commit/8ea8871cc88b1199bfcb6c2ff7aef16fd1f733c3))
|
||||||
|
* **tiktok:** 增加配置文件, 手动传入自己的cookie ([ec559e2](https://github.com/imgyh/tiktok/commit/ec559e2913c70836b97ea2634604ac6ca6734a60)), closes [#16](https://github.com/imgyh/tiktok/issues/16)
|
||||||
|
* **tiktok:** 支持电脑网页版url作为链接 ([c9ece0b](https://github.com/imgyh/tiktok/commit/c9ece0bf502c1a6a6e6b2e12c8ffcbce3303ce6a))
|
||||||
|
* **tiktok:** 进度条滚动显示 ([20350b8](https://github.com/imgyh/tiktok/commit/20350b8889343bb93ec60081e6369f96d868203b))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [](https://github.com/imgyh/tiktok/compare/v1.5.4...v) (2023-03-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **tiktok:** 修改命令行帮助提示信息 ([e194a28](https://github.com/imgyh/tiktok/commit/e194a2818b64aaa9b2a5ab172302c87a4f4a5790))
|
||||||
|
* **tiktok:** 修改文件夹名的正则匹配逻辑 ([4c1a4ed](https://github.com/imgyh/tiktok/commit/4c1a4ed950476c0945bf7986254d75d108d3019f)), closes [#11](https://github.com/imgyh/tiktok/issues/11)
|
||||||
|
* **tiktok:** 单个作品不使用多线程 ([939a417](https://github.com/imgyh/tiktok/commit/939a417654183a2ac2766a25bdecc13575752e61))
|
||||||
|
* **tiktok:** 获取x-bogus错误后重试, 单个作品不使用多线程 ([f8ec5e3](https://github.com/imgyh/tiktok/commit/f8ec5e3745d587d08dcba6464a0f990913f55ce5))
|
||||||
|
* **utils:** 优化获取x-bogus时出错的提示信息 ([f224557](https://github.com/imgyh/tiktok/commit/f2245574611f125f69be9bbe9a2d376c0241bc87))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **live:** 直播解析支持APP端分享链接 ([e4a0ebb](https://github.com/imgyh/tiktok/commit/e4a0ebba4f39cc55e8d070e12b6597ab5c3745d3))
|
||||||
|
* **tiktok:** 使用线程池代替手动创建线程 ([89d4c7c](https://github.com/imgyh/tiktok/commit/89d4c7cd4253cd3ac885b55774dff2853c0d6e4f))
|
||||||
|
* **web:** 前端提示信息优化 ([d9c224b](https://github.com/imgyh/tiktok/commit/d9c224bd2d5fd382fb22b37f5e68a85894bb9aef))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [](https://github.com/imgyh/tiktok/compare/v1.5.3...v) (2023-03-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **tiktok:** 修复接口未返回数据时重复请求的死循环(10s后还是未返回就抛异常) ([8c5863d](https://github.com/imgyh/tiktok/commit/8c5863d44f4ae4a5242c8191f98bc0f3936e8e84))
|
||||||
|
* **tiktok:** 修复无法获取图集id ([85942f2](https://github.com/imgyh/tiktok/commit/85942f2ffce97d853dab96da87737d98f450347e)), closes [#10](https://github.com/imgyh/tiktok/issues/10)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **tiktok:** 适配多线程下载 ([83e4632](https://github.com/imgyh/tiktok/commit/83e46322ee13bd14841332538fef22e27e2f0e59))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [](https://github.com/imgyh/tiktok/compare/v1.5.2...v) (2023-03-05)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **web:** 取消指定端口为必选项 ([1b85b19](https://github.com/imgyh/tiktok/commit/1b85b1989119cb35888e4e2d4f4018ed276f68d2))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **live:** 增加直播解析方法的提示信息开启或关闭选项 ([469fe2c](https://github.com/imgyh/tiktok/commit/469fe2c5217ff22e42c1523d089a119415266c23))
|
||||||
|
* **url:** 添加速度更快的X-Bogus接口 ([7c44b94](https://github.com/imgyh/tiktok/commit/7c44b9426dc30f3be97d6e5824bf628c47276a87))
|
||||||
|
* **web:** 增加命令行指定web端口 ([98a9f74](https://github.com/imgyh/tiktok/commit/98a9f74d4b0b319ea1aeb3883c105c0b805105f1))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [](https://github.com/imgyh/tiktok/compare/v1.5.1...v) (2023-03-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **live:** 修复无法播放http的直播地址 ([04ec0f1](https://github.com/imgyh/tiktok/commit/04ec0f1c400adb5bfacf74dca0114ec9d625e3cc))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **live:** 适配web端的直播解析 ([f7fdcd1](https://github.com/imgyh/tiktok/commit/f7fdcd141b3a9877f5fd888383bfa48875d023bf))
|
||||||
|
* **tiktok:** 支持本地生成X-Bogus ([#3](https://github.com/imgyh/tiktok/issues/3)) ([a4328cd](https://github.com/imgyh/tiktok/commit/a4328cd53bd8a0342cf053050a8066130e008cde))
|
||||||
|
* **utils:** 重新加入远程调用X-Bogus接口作为备用,防止本地没有JS环境 ([4467018](https://github.com/imgyh/tiktok/commit/44670186afdcb1314194b0c00f39d1baa9681985))
|
||||||
|
* **web:** 增加web端直播解析 ([90aa105](https://github.com/imgyh/tiktok/commit/90aa10515f7bc90ed35c9484e2993083a533d6cc))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [](https://github.com/imgyh/tiktok/compare/v1.5.0...v) (2023-03-02)
|
# [](https://github.com/imgyh/tiktok/compare/v1.5.0...v) (2023-03-02)
|
||||||
|
|
||||||
|
|
||||||
|
29
Dockerfile
@ -1,13 +1,16 @@
|
|||||||
# This Dockerfile is used to build an Python environment
|
# This Dockerfile is used to build an Python environment
|
||||||
FROM python:3.9-slim-bullseye
|
FROM python:3.9-slim-bullseye
|
||||||
|
|
||||||
LABEL maintainer="imgyh<admin@imgyh.com>"
|
LABEL maintainer="imgyh<admin@imgyh.com>"
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
ADD . $WORKDIR
|
ADD . $WORKDIR
|
||||||
|
|
||||||
RUN pip3 install -r requirements.txt
|
RUN sed -i s/deb.debian.org/mirrors.aliyun.com/g /etc/apt/sources.list
|
||||||
|
|
||||||
CMD ["python3", "TikTokWeb.py"]
|
RUN pip3 install -r requirements_docker.txt
|
||||||
|
|
||||||
|
ENV TZ=Asia/Shanghai
|
||||||
|
|
||||||
|
CMD ["python3", "WebApi.py"]
|
||||||
|
376
DouYinCommand.py
Normal file
@ -0,0 +1,376 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
'''
|
||||||
|
@FileName : DouYinCommand.py
|
||||||
|
@Project : apiproxy
|
||||||
|
@Description:
|
||||||
|
@Author : imgyh
|
||||||
|
@Mail : admin@imgyh.com
|
||||||
|
@Github : https://github.com/imgyh
|
||||||
|
@Site : https://www.imgyh.com
|
||||||
|
@Date : 2023/5/12 16:01
|
||||||
|
@Version : v1.0
|
||||||
|
@ChangeLog
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
------------------------------------------------
|
||||||
|
'''
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import yaml
|
||||||
|
import time
|
||||||
|
|
||||||
|
from apiproxy.douyin.douyin import Douyin
|
||||||
|
from apiproxy.douyin.download import Download
|
||||||
|
from apiproxy.douyin import douyin_headers
|
||||||
|
from apiproxy.common import utils
|
||||||
|
|
||||||
|
configModel = {
|
||||||
|
"link": [],
|
||||||
|
"path": os.getcwd(),
|
||||||
|
"music": True,
|
||||||
|
"cover": True,
|
||||||
|
"avatar": True,
|
||||||
|
"json": True,
|
||||||
|
"folderstyle": True,
|
||||||
|
"mode": ["post"],
|
||||||
|
"number": {
|
||||||
|
"post": 0,
|
||||||
|
"like": 0,
|
||||||
|
"allmix": 0,
|
||||||
|
"mix": 0,
|
||||||
|
"music": 0,
|
||||||
|
},
|
||||||
|
'database': True,
|
||||||
|
"increase": {
|
||||||
|
"post": False,
|
||||||
|
"like": False,
|
||||||
|
"allmix": False,
|
||||||
|
"mix": False,
|
||||||
|
"music": False,
|
||||||
|
},
|
||||||
|
"thread": 5,
|
||||||
|
"cookie": None
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def argument():
|
||||||
|
parser = argparse.ArgumentParser(description='抖音批量下载工具 使用帮助')
|
||||||
|
parser.add_argument("--cmd", "-C", help="使用命令行(True)或者配置文件(False), 默认为False",
|
||||||
|
type=utils.str2bool, required=False, default=False)
|
||||||
|
parser.add_argument("--link", "-l",
|
||||||
|
help="作品(视频或图集)、直播、合集、音乐集合、个人主页的分享链接或者电脑浏览器网址, 可以设置多个链接(删除文案, 保证只有URL, https://v.douyin.com/kcvMpuN/ 或者 https://www.douyin.com/开头的)",
|
||||||
|
type=str, required=False, default=[], action="append")
|
||||||
|
parser.add_argument("--path", "-p", help="下载保存位置, 默认当前文件位置",
|
||||||
|
type=str, required=False, default=os.getcwd())
|
||||||
|
parser.add_argument("--music", "-m", help="是否下载视频中的音乐(True/False), 默认为True",
|
||||||
|
type=utils.str2bool, required=False, default=True)
|
||||||
|
parser.add_argument("--cover", "-c", help="是否下载视频的封面(True/False), 默认为True, 当下载视频时有效",
|
||||||
|
type=utils.str2bool, required=False, default=True)
|
||||||
|
parser.add_argument("--avatar", "-a", help="是否下载作者的头像(True/False), 默认为True",
|
||||||
|
type=utils.str2bool, required=False, default=True)
|
||||||
|
parser.add_argument("--json", "-j", help="是否保存获取到的数据(True/False), 默认为True",
|
||||||
|
type=utils.str2bool, required=False, default=True)
|
||||||
|
parser.add_argument("--folderstyle", "-fs", help="文件保存风格, 默认为True",
|
||||||
|
type=utils.str2bool, required=False, default=True)
|
||||||
|
parser.add_argument("--mode", "-M", help="link是个人主页时, 设置下载发布的作品(post)或喜欢的作品(like)或者用户所有合集(mix), 默认为post, 可以设置多种模式",
|
||||||
|
type=str, required=False, default=[], action="append")
|
||||||
|
parser.add_argument("--postnumber", help="主页下作品下载个数设置, 默认为0 全部下载",
|
||||||
|
type=int, required=False, default=0)
|
||||||
|
parser.add_argument("--likenumber", help="主页下喜欢下载个数设置, 默认为0 全部下载",
|
||||||
|
type=int, required=False, default=0)
|
||||||
|
parser.add_argument("--allmixnumber", help="主页下合集下载个数设置, 默认为0 全部下载",
|
||||||
|
type=int, required=False, default=0)
|
||||||
|
parser.add_argument("--mixnumber", help="单个合集下作品下载个数设置, 默认为0 全部下载",
|
||||||
|
type=int, required=False, default=0)
|
||||||
|
parser.add_argument("--musicnumber", help="音乐(原声)下作品下载个数设置, 默认为0 全部下载",
|
||||||
|
type=int, required=False, default=0)
|
||||||
|
parser.add_argument("--database", "-d", help="是否使用数据库, 默认为True 使用数据库; 如果不使用数据库, 增量更新不可用",
|
||||||
|
type=utils.str2bool, required=False, default=True)
|
||||||
|
parser.add_argument("--postincrease", help="是否开启主页作品增量下载(True/False), 默认为False",
|
||||||
|
type=utils.str2bool, required=False, default=False)
|
||||||
|
parser.add_argument("--likeincrease", help="是否开启主页喜欢增量下载(True/False), 默认为False",
|
||||||
|
type=utils.str2bool, required=False, default=False)
|
||||||
|
parser.add_argument("--allmixincrease", help="是否开启主页合集增量下载(True/False), 默认为False",
|
||||||
|
type=utils.str2bool, required=False, default=False)
|
||||||
|
parser.add_argument("--mixincrease", help="是否开启单个合集下作品增量下载(True/False), 默认为False",
|
||||||
|
type=utils.str2bool, required=False, default=False)
|
||||||
|
parser.add_argument("--musicincrease", help="是否开启音乐(原声)下作品增量下载(True/False), 默认为False",
|
||||||
|
type=utils.str2bool, required=False, default=False)
|
||||||
|
parser.add_argument("--thread", "-t",
|
||||||
|
help="设置线程数, 默认5个线程",
|
||||||
|
type=int, required=False, default=5)
|
||||||
|
parser.add_argument("--cookie", help="设置cookie, 格式: \"name1=value1; name2=value2;\" 注意要加冒号",
|
||||||
|
type=str, required=False, default='')
|
||||||
|
args = parser.parse_args()
|
||||||
|
if args.thread <= 0:
|
||||||
|
args.thread = 5
|
||||||
|
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
def yamlConfig():
|
||||||
|
curPath = os.path.dirname(os.path.realpath(sys.argv[0]))
|
||||||
|
yamlPath = os.path.join(curPath, "config.yml")
|
||||||
|
f = open(yamlPath, 'r', encoding='utf-8')
|
||||||
|
cfg = f.read()
|
||||||
|
configDict = yaml.load(stream=cfg, Loader=yaml.FullLoader)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if configDict["link"] != None:
|
||||||
|
configModel["link"] = configDict["link"]
|
||||||
|
except Exception as e:
|
||||||
|
print("[ 警告 ]:link未设置, 程序退出...\r\n")
|
||||||
|
try:
|
||||||
|
if configDict["path"] != None:
|
||||||
|
configModel["path"] = configDict["path"]
|
||||||
|
except Exception as e:
|
||||||
|
print("[ 警告 ]:path未设置, 使用当前路径...\r\n")
|
||||||
|
try:
|
||||||
|
if configDict["music"] != None:
|
||||||
|
configModel["music"] = configDict["music"]
|
||||||
|
except Exception as e:
|
||||||
|
print("[ 警告 ]:music未设置, 使用默认值True...\r\n")
|
||||||
|
try:
|
||||||
|
if configDict["cover"] != None:
|
||||||
|
configModel["cover"] = configDict["cover"]
|
||||||
|
except Exception as e:
|
||||||
|
print("[ 警告 ]:cover未设置, 使用默认值True...\r\n")
|
||||||
|
try:
|
||||||
|
if configDict["avatar"] != None:
|
||||||
|
configModel["avatar"] = configDict["avatar"]
|
||||||
|
except Exception as e:
|
||||||
|
print("[ 警告 ]:avatar未设置, 使用默认值True...\r\n")
|
||||||
|
try:
|
||||||
|
if configDict["json"] != None:
|
||||||
|
configModel["json"] = configDict["json"]
|
||||||
|
except Exception as e:
|
||||||
|
print("[ 警告 ]:json未设置, 使用默认值True...\r\n")
|
||||||
|
try:
|
||||||
|
if configDict["folderstyle"] != None:
|
||||||
|
configModel["folderstyle"] = configDict["folderstyle"]
|
||||||
|
except Exception as e:
|
||||||
|
print("[ 警告 ]:folderstyle未设置, 使用默认值True...\r\n")
|
||||||
|
try:
|
||||||
|
if configDict["mode"] != None:
|
||||||
|
configModel["mode"] = configDict["mode"]
|
||||||
|
except Exception as e:
|
||||||
|
print("[ 警告 ]:mode未设置, 使用默认值post...\r\n")
|
||||||
|
try:
|
||||||
|
if configDict["number"]["post"] != None:
|
||||||
|
configModel["number"]["post"] = configDict["number"]["post"]
|
||||||
|
except Exception as e:
|
||||||
|
print("[ 警告 ]:post number未设置, 使用默认值0...\r\n")
|
||||||
|
try:
|
||||||
|
if configDict["number"]["like"] != None:
|
||||||
|
configModel["number"]["like"] = configDict["number"]["like"]
|
||||||
|
except Exception as e:
|
||||||
|
print("[ 警告 ]:like number未设置, 使用默认值0...\r\n")
|
||||||
|
try:
|
||||||
|
if configDict["number"]["allmix"] != None:
|
||||||
|
configModel["number"]["allmix"] = configDict["number"]["allmix"]
|
||||||
|
except Exception as e:
|
||||||
|
print("[ 警告 ]:allmix number未设置, 使用默认值0...\r\n")
|
||||||
|
try:
|
||||||
|
if configDict["number"]["mix"] != None:
|
||||||
|
configModel["number"]["mix"] = configDict["number"]["mix"]
|
||||||
|
except Exception as e:
|
||||||
|
print("[ 警告 ]:mix number未设置, 使用默认值0...\r\n")
|
||||||
|
try:
|
||||||
|
if configDict["number"]["music"] != None:
|
||||||
|
configModel["number"]["music"] = configDict["number"]["music"]
|
||||||
|
except Exception as e:
|
||||||
|
print("[ 警告 ]:music number未设置, 使用默认值0...\r\n")
|
||||||
|
try:
|
||||||
|
if configDict["database"] != None:
|
||||||
|
configModel["database"] = configDict["database"]
|
||||||
|
except Exception as e:
|
||||||
|
print("[ 警告 ]:database未设置, 使用默认值False...\r\n")
|
||||||
|
try:
|
||||||
|
if configDict["increase"]["post"] != None:
|
||||||
|
configModel["increase"]["post"] = configDict["increase"]["post"]
|
||||||
|
except Exception as e:
|
||||||
|
print("[ 警告 ]:post 增量更新未设置, 使用默认值False...\r\n")
|
||||||
|
try:
|
||||||
|
if configDict["increase"]["like"] != None:
|
||||||
|
configModel["increase"]["like"] = configDict["increase"]["like"]
|
||||||
|
except Exception as e:
|
||||||
|
print("[ 警告 ]:like 增量更新未设置, 使用默认值False...\r\n")
|
||||||
|
try:
|
||||||
|
if configDict["increase"]["allmix"] != None:
|
||||||
|
configModel["increase"]["allmix"] = configDict["increase"]["allmix"]
|
||||||
|
except Exception as e:
|
||||||
|
print("[ 警告 ]:allmix 增量更新未设置, 使用默认值False...\r\n")
|
||||||
|
try:
|
||||||
|
if configDict["increase"]["mix"] != None:
|
||||||
|
configModel["increase"]["mix"] = configDict["increase"]["mix"]
|
||||||
|
except Exception as e:
|
||||||
|
print("[ 警告 ]:mix 增量更新未设置, 使用默认值False...\r\n")
|
||||||
|
try:
|
||||||
|
if configDict["increase"]["music"] != None:
|
||||||
|
configModel["increase"]["music"] = configDict["increase"]["music"]
|
||||||
|
except Exception as e:
|
||||||
|
print("[ 警告 ]:music 增量更新未设置, 使用默认值False...\r\n")
|
||||||
|
try:
|
||||||
|
if configDict["thread"] != None:
|
||||||
|
configModel["thread"] = configDict["thread"]
|
||||||
|
except Exception as e:
|
||||||
|
print("[ 警告 ]:thread未设置, 使用默认值5...\r\n")
|
||||||
|
try:
|
||||||
|
if configDict["cookies"] != None:
|
||||||
|
cookiekey = configDict["cookies"].keys()
|
||||||
|
cookieStr = ""
|
||||||
|
for i in cookiekey:
|
||||||
|
cookieStr = cookieStr + i + "=" + configDict["cookies"][i] + "; "
|
||||||
|
configModel["cookie"] = cookieStr
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
if configDict["cookie"] != None:
|
||||||
|
configModel["cookie"] = configDict["cookie"]
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
start = time.time() # 开始时间
|
||||||
|
|
||||||
|
args = argument()
|
||||||
|
|
||||||
|
if args.cmd:
|
||||||
|
configModel["link"] = args.link
|
||||||
|
configModel["path"] = args.path
|
||||||
|
configModel["music"] = args.music
|
||||||
|
configModel["cover"] = args.cover
|
||||||
|
configModel["avatar"] = args.avatar
|
||||||
|
configModel["json"] = args.json
|
||||||
|
configModel["folderstyle"] = args.folderstyle
|
||||||
|
if args.mode == None or args.mode == []:
|
||||||
|
args.mode = []
|
||||||
|
args.mode.append("post")
|
||||||
|
configModel["mode"] = list(set(args.mode))
|
||||||
|
configModel["number"]["post"] = args.postnumber
|
||||||
|
configModel["number"]["like"] = args.likenumber
|
||||||
|
configModel["number"]["allmix"] = args.allmixnumber
|
||||||
|
configModel["number"]["mix"] = args.mixnumber
|
||||||
|
configModel["number"]["music"] = args.musicnumber
|
||||||
|
configModel["database"] = args.database
|
||||||
|
configModel["increase"]["post"] = args.postincrease
|
||||||
|
configModel["increase"]["like"] = args.likeincrease
|
||||||
|
configModel["increase"]["allmix"] = args.allmixincrease
|
||||||
|
configModel["increase"]["mix"] = args.mixincrease
|
||||||
|
configModel["increase"]["music"] = args.musicincrease
|
||||||
|
configModel["thread"] = args.thread
|
||||||
|
configModel["cookie"] = args.cookie
|
||||||
|
else:
|
||||||
|
yamlConfig()
|
||||||
|
|
||||||
|
if configModel["link"] == []:
|
||||||
|
return
|
||||||
|
|
||||||
|
if configModel["cookie"] is not None and configModel["cookie"] != "":
|
||||||
|
douyin_headers["Cookie"] = configModel["cookie"]
|
||||||
|
|
||||||
|
configModel["path"] = os.path.abspath(configModel["path"])
|
||||||
|
print("[ 提示 ]:数据保存路径 " + configModel["path"])
|
||||||
|
if not os.path.exists(configModel["path"]):
|
||||||
|
os.mkdir(configModel["path"])
|
||||||
|
|
||||||
|
dy = Douyin(database=configModel["database"])
|
||||||
|
dl = Download(thread=configModel["thread"], music=configModel["music"], cover=configModel["cover"],
|
||||||
|
avatar=configModel["avatar"], resjson=configModel["json"],
|
||||||
|
folderstyle=configModel["folderstyle"])
|
||||||
|
|
||||||
|
for link in configModel["link"]:
|
||||||
|
print("--------------------------------------------------------------------------------")
|
||||||
|
print("[ 提示 ]:正在请求的链接: " + link + "\r\n")
|
||||||
|
url = dy.getShareLink(link)
|
||||||
|
key_type, key = dy.getKey(url)
|
||||||
|
if key_type == "user":
|
||||||
|
print("[ 提示 ]:正在请求用户主页下作品\r\n")
|
||||||
|
data = dy.getUserDetailInfo(sec_uid=key)
|
||||||
|
nickname = ""
|
||||||
|
if data is not None and data != {}:
|
||||||
|
nickname = utils.replaceStr(data['user']['nickname'])
|
||||||
|
|
||||||
|
userPath = os.path.join(configModel["path"], "user_" + nickname + "_" + key)
|
||||||
|
if not os.path.exists(userPath):
|
||||||
|
os.mkdir(userPath)
|
||||||
|
|
||||||
|
for mode in configModel["mode"]:
|
||||||
|
print("--------------------------------------------------------------------------------")
|
||||||
|
print("[ 提示 ]:正在请求用户主页模式: " + mode + "\r\n")
|
||||||
|
if mode == 'post' or mode == 'like':
|
||||||
|
datalist = dy.getUserInfo(key, mode, 35, configModel["number"][mode], configModel["increase"][mode])
|
||||||
|
if datalist is not None and datalist != []:
|
||||||
|
modePath = os.path.join(userPath, mode)
|
||||||
|
if not os.path.exists(modePath):
|
||||||
|
os.mkdir(modePath)
|
||||||
|
dl.userDownload(awemeList=datalist, savePath=modePath)
|
||||||
|
elif mode == 'mix':
|
||||||
|
mixIdNameDict = dy.getUserAllMixInfo(key, 35, configModel["number"]["allmix"])
|
||||||
|
if mixIdNameDict is not None and mixIdNameDict != {}:
|
||||||
|
for mix_id in mixIdNameDict:
|
||||||
|
print(f'[ 提示 ]:正在下载合集 [{mixIdNameDict[mix_id]}] 中的作品\r\n')
|
||||||
|
mix_file_name = utils.replaceStr(mixIdNameDict[mix_id])
|
||||||
|
datalist = dy.getMixInfo(mix_id, 35, 0, configModel["increase"]["allmix"], key)
|
||||||
|
if datalist is not None and datalist != []:
|
||||||
|
modePath = os.path.join(userPath, mode)
|
||||||
|
if not os.path.exists(modePath):
|
||||||
|
os.mkdir(modePath)
|
||||||
|
dl.userDownload(awemeList=datalist, savePath=os.path.join(modePath, mix_file_name))
|
||||||
|
print(f'[ 提示 ]:合集 [{mixIdNameDict[mix_id]}] 中的作品下载完成\r\n')
|
||||||
|
elif key_type == "mix":
|
||||||
|
print("[ 提示 ]:正在请求单个合集下作品\r\n")
|
||||||
|
datalist = dy.getMixInfo(key, 35, configModel["number"]["mix"], configModel["increase"]["mix"], "")
|
||||||
|
if datalist is not None and datalist != []:
|
||||||
|
mixname = utils.replaceStr(datalist[0]["mix_info"]["mix_name"])
|
||||||
|
mixPath = os.path.join(configModel["path"], "mix_" + mixname + "_" + key)
|
||||||
|
if not os.path.exists(mixPath):
|
||||||
|
os.mkdir(mixPath)
|
||||||
|
dl.userDownload(awemeList=datalist, savePath=mixPath)
|
||||||
|
elif key_type == "music":
|
||||||
|
print("[ 提示 ]:正在请求音乐(原声)下作品\r\n")
|
||||||
|
datalist = dy.getMusicInfo(key, 35, configModel["number"]["music"], configModel["increase"]["music"])
|
||||||
|
|
||||||
|
if datalist is not None and datalist != []:
|
||||||
|
musicname = utils.replaceStr(datalist[0]["music"]["title"])
|
||||||
|
musicPath = os.path.join(configModel["path"], "music_" + musicname + "_" + key)
|
||||||
|
if not os.path.exists(musicPath):
|
||||||
|
os.mkdir(musicPath)
|
||||||
|
dl.userDownload(awemeList=datalist, savePath=musicPath)
|
||||||
|
elif key_type == "aweme":
|
||||||
|
print("[ 提示 ]:正在请求单个作品\r\n")
|
||||||
|
datanew, dataraw = dy.getAwemeInfo(key)
|
||||||
|
if datanew is not None and datanew != {}:
|
||||||
|
datalist = []
|
||||||
|
datalist.append(datanew)
|
||||||
|
awemePath = os.path.join(configModel["path"], "aweme")
|
||||||
|
if not os.path.exists(awemePath):
|
||||||
|
os.mkdir(awemePath)
|
||||||
|
dl.userDownload(awemeList=datalist, savePath=awemePath)
|
||||||
|
elif key_type == "live":
|
||||||
|
print("[ 提示 ]:正在进行直播解析\r\n")
|
||||||
|
live_json = dy.getLiveInfo(key)
|
||||||
|
if configModel["json"]:
|
||||||
|
livePath = os.path.join(configModel["path"], "live")
|
||||||
|
if not os.path.exists(livePath):
|
||||||
|
os.mkdir(livePath)
|
||||||
|
live_file_name = utils.replaceStr(key + live_json["nickname"])
|
||||||
|
# 保存获取到json
|
||||||
|
print("[ 提示 ]:正在保存获取到的信息到result.json\r\n")
|
||||||
|
with open(os.path.join(livePath, live_file_name + ".json"), "w", encoding='utf-8') as f:
|
||||||
|
f.write(json.dumps(live_json, ensure_ascii=False, indent=2))
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
end = time.time() # 结束时间
|
||||||
|
print('\n' + '[下载完成]:总耗时: %d分钟%d秒\n' % (int((end - start) / 60), ((end - start) % 60))) # 输出下载用时时间
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
@ -83,6 +83,7 @@ class TikTok(object):
|
|||||||
userVideoUrls.append(videoRealUrl)
|
userVideoUrls.append(videoRealUrl)
|
||||||
return userVideoUrls
|
return userVideoUrls
|
||||||
|
|
||||||
|
|
||||||
tk = TikTok()
|
tk = TikTok()
|
||||||
# tk.oneVideoInfo()
|
# tk.oneVideoInfo()
|
||||||
tk.userVideoInfo()
|
tk.userVideoInfo()
|
||||||
|
433
README.md
@ -8,42 +8,76 @@
|
|||||||
|
|
||||||
开源地址:https://github.com/imgyh/tiktok
|
开源地址:https://github.com/imgyh/tiktok
|
||||||
|
|
||||||
|
博客文档:https://www.imgyh.com/archives/41.html
|
||||||
|
|
||||||
抖音去水印工具Web demo:https://dy.gyh.im/
|
抖音去水印工具Web demo:https://dy.gyh.im/
|
||||||
|
|
||||||
|
**联系方式:**
|
||||||
|
|
||||||
|
> [TG](https://t.me/gyh9527)
|
||||||
|
|
||||||
|
> [TG群组](https://t.me/GYHgroup)
|
||||||
|
|
||||||
|
> [Email](mailto:admin@imgyh.com)
|
||||||
|
|
||||||
|
> [Blog](https://www.imgyh.com)
|
||||||
|
|
||||||
## 抖音去水印工具 Feature
|
## 抖音去水印工具 Feature
|
||||||
|
|
||||||
* 通过作品分享链接获取去水印作品、音乐、封面图、头像
|
* 通过作品分享链接获取去水印作品、音乐、封面图、头像
|
||||||
* 获取点赞数、评论数、收藏数、分享数、作品描述等信息
|
* 获取点赞数、评论数、收藏数、分享数、作品描述等信息
|
||||||
|
* 支持直播解析
|
||||||
* 基于Flask实现 Web 交互界面
|
* 基于Flask实现 Web 交互界面
|
||||||
|
* 提供相关接口,支持单个作品、直播、主页喜欢、主页作品、主页合集、合集、音乐(原声)通过接口获取
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
## 抖音批量下载工具 Feature
|
## 抖音批量下载工具 Feature
|
||||||
|
|
||||||
* 支持个人主页链接、作品分享链接、抖音直播Web链接、合集链接、音乐集合链接
|
* 支持个人主页链接、作品分享链接、抖音直播Web链接、合集链接、音乐(原声)集合链接
|
||||||
* 支持单个作品下载、主页作品下载、主页喜欢下载、单个合集下载、主页所有合集下载、音乐集合下载
|
* 支持单个作品下载、主页作品下载、主页喜欢下载、直播解析、单个合集下载、主页所有合集下载、音乐(原声)集合下载
|
||||||
* 下载视频、视频封面、音乐、头像
|
* 下载视频、视频封面、音乐、头像
|
||||||
* 去水印下载
|
* 去水印下载
|
||||||
* 自动跳过已下载
|
* 自动跳过已下载
|
||||||
* 支持指定下载作品数量
|
* 支持指定下载作品数量
|
||||||
|
* 多线程下载
|
||||||
|
* 支持多链接下载
|
||||||
|
* 增量更新与数据持久化到数据库, 保存每条作品信息到数据库, 并根据数据库是否存在来增量请求下载
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
# 使用方法
|
# 使用方法
|
||||||
|
|
||||||
|
- 支持的地址格式, 形如
|
||||||
|
|
||||||
|
```
|
||||||
|
抖音app分享链接:
|
||||||
|
1. 作品(视频或图集)、直播、合集、音乐集合、个人主页 https://v.douyin.com/BugmVVD/
|
||||||
|
抖音网页版浏览器URL:
|
||||||
|
2. 单个视频 https://www.douyin.com/video/6915675899241450760
|
||||||
|
3. 单个图集 https://www.douyin.com/note/7014363562642623777
|
||||||
|
4. 用户主页 https://www.douyin.com/user/MS4wLjABAAAA06y3Ctu8QmuefqvUSU7vr0c_ZQnCqB0eaglgkelLTek
|
||||||
|
5. 单个合集 https://www.douyin.com/collection/7208829743762769975
|
||||||
|
6. 音乐(原声)下的视频 https://www.douyin.com/music/7149936801028131598
|
||||||
|
7. 直播 https://live.douyin.com/759547612580
|
||||||
|
```
|
||||||
|
|
||||||
## 抖音去水印工具
|
## 抖音去水印工具
|
||||||
|
|
||||||
使用抖音去水印工具有三种方式
|
### 使用方式
|
||||||
|
|
||||||
|
使用抖音去水印工具有4种方式
|
||||||
|
|
||||||
1. (推荐)直接使用我搭建的抖音去水印工具:https://dy.gyh.im/
|
1. (推荐)直接使用我搭建的抖音去水印工具:https://dy.gyh.im/
|
||||||
|
|
||||||
2. 使用docker运行
|
2. 使用docker运行
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -55,14 +89,33 @@ docker run -d -p 5000:5000 --name tiktok --restart=always imgyh/tiktokweb
|
|||||||
```
|
```
|
||||||
cd /path/to/tiktok
|
cd /path/to/tiktok
|
||||||
python -m pip install -r requirements.txt
|
python -m pip install -r requirements.txt
|
||||||
python TikTokWeb.py
|
python WebApi.py
|
||||||
|
```
|
||||||
|
|
||||||
|
4. windows用户也可以下载 Releases 中的 [WebApi.exe](https://github.com/imgyh/tiktok/releases) 文件双击运行
|
||||||
|
|
||||||
|
5. 指定端口运行
|
||||||
|
|
||||||
|
```
|
||||||
|
# 指定端口运行
|
||||||
|
python WebApi.py -p 5001
|
||||||
|
.\WebApi.exe -p 5001
|
||||||
```
|
```
|
||||||
|
|
||||||
访问: http://localhost:5000
|
访问: http://localhost:5000
|
||||||
|
|
||||||
|
|
||||||
## 抖音批量下载工具
|
## 抖音批量下载工具
|
||||||
|
|
||||||
windows用户下载 Releases 中的 [TikTokCommand.exe](https://github.com/imgyh/tiktok/releases) 文件在cmd中运行
|
批量下载有两种方式运行, 配置文件和命令行
|
||||||
|
|
||||||
|
默认使用配置文件方式
|
||||||
|
|
||||||
|
> !!!!!! 请先获取cookie再使用
|
||||||
|
|
||||||
|
### 安装依赖
|
||||||
|
|
||||||
|
windows用户下载 Releases 中的 [DouYinCommand.exe](https://github.com/imgyh/tiktok/releases) 文件运行
|
||||||
windows用户本地有`python3.9`环境, 也可按照linux与mac用户的方式运行
|
windows用户本地有`python3.9`环境, 也可按照linux与mac用户的方式运行
|
||||||
|
|
||||||
linux与mac用户下载本项目, 在本地`python3.9`环境中运行, 首先需要安装依赖, 安装命令
|
linux与mac用户下载本项目, 在本地`python3.9`环境中运行, 首先需要安装依赖, 安装命令
|
||||||
@ -72,165 +125,409 @@ cd /path/to/tiktok
|
|||||||
python -m pip install -r requirements.txt
|
python -m pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
运行示例:
|
### 使用Docker
|
||||||
|
|
||||||
|
> !!!!!! 请先获取cookie再使用
|
||||||
|
|
||||||
|
请映射以下两个目录(三个位置需要修改), 根据实际情况修改目录地址
|
||||||
|
|
||||||
|
`/path/to/tiktok` 源代码目录
|
||||||
|
`/path/to/downloads` 下载位置
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run -d -p 5000:5000 --name tiktok --restart=always -v /path/to/tiktok:/app -v /path/to/downloads:/path/to/downloads imgyh/tiktokweb
|
||||||
|
```
|
||||||
|
|
||||||
|
将所有用到 `python DouYinCommand.py` 替换成 `docker exec -it tiktok python3 DouYinCommand.py`
|
||||||
|
|
||||||
|
### 配置文件方式
|
||||||
|
|
||||||
|
> !!!!!! 请自己登录网页版抖音后F12获取cookie
|
||||||
|
> !!!!!! 请仔细阅读配置示例[config.yml](./config.yml)中的描述
|
||||||
|
|
||||||
|
配置文件名必须叫 `config.yml`, 并将其放在DouYinCommand.py或者DouYinCommand.exe同一个目录下
|
||||||
|
|
||||||
|
直接运行DouYinCommand.py或者DouYinCommand.exe, 无需在命令中加入任何参数, 所有参数都从配置文件中读取
|
||||||
|
|
||||||
|
基本配置示例[config.yml](./config.yml)
|
||||||
|
|
||||||
|
|
||||||
|
### 命令行方式
|
||||||
|
|
||||||
|
> 以下为命令行的一些信息, 每个选项的详细说明参见配置示例[config.yml](./config.yml)中的描述
|
||||||
|
|
||||||
- 获取帮助信息
|
- 获取帮助信息
|
||||||
|
|
||||||
```
|
```
|
||||||
windows用户:
|
windows用户:
|
||||||
.\TikTokCommand.exe -h
|
.\DouYinCommand.exe -h
|
||||||
linux与mac用户:
|
linux与mac用户:
|
||||||
python TikTokCommand.py -h
|
python DouYinCommand.py -h
|
||||||
```
|
```
|
||||||
|
|
||||||
- 参数介绍
|
- 参数介绍
|
||||||
|
|
||||||
```
|
```
|
||||||
-h, --help 展示帮助页
|
-h, --help 展示帮助信息
|
||||||
--link LINK, -l LINK 1.作品(视频或图集)、合集、音乐集合、个人主页抖音分享链接(删除文案, 保证只有URL, https://v.douyin.com/kcvMpuN/)
|
--cmd CMD, -C CMD 使用命令行(True)或者配置文件(False), 默认为False
|
||||||
2.解析直播网页版网址(https://live.douyin.com/802939216127)
|
--link LINK, -l LINK 作品(视频或图集)、直播、合集、音乐集合、个人主页的分享链接或者电脑浏览器网址, 可以设置多个链接
|
||||||
--path PATH, -p PATH 下载保存位置
|
(删除文案, 保证只有URL, https://v.douyin.com/kcvMpuN/ 或者 https://www.douyin.com/开头的)
|
||||||
--music MUSIC, -m MUSIC 是否下载视频中的音乐(True/False), 默认为True
|
--path PATH, -p PATH 下载保存位置, 默认当前文件位置
|
||||||
--cover COVER, -c COVER 是否下载视频的封面(True/False), 默认为True, 当下载视频时有效
|
--music MUSIC, -m MUSIC 是否下载视频中的音乐(True/False), 默认为True
|
||||||
--avatar AVATAR, -a AVATAR 是否下载作者的头像(True/False), 默认为True
|
--cover COVER, -c COVER 是否下载视频的封面(True/False), 默认为True, 当下载视频时有效
|
||||||
--mode MODE, -M MODE link是个人主页时, 设置下载发布的作品(post)或喜欢的作品(like)或者用户所有合集(mix), 默认为post
|
--avatar AVATAR, -a AVATAR 是否下载作者的头像(True/False), 默认为True
|
||||||
--number NUMBER, -n NUMBER 1.当下载单个合集、音乐集合、主页作品(post模式)和喜欢(like模式)时, 可设置下载前n个作品, 默认为0全部下载
|
--json JSON, -j JSON 是否保存获取到的数据(True/False), 默认为True
|
||||||
2.当下载主页下所有合集(mix模式)时, 设置下载前n个合集下所有作品, 默认为0全部下载
|
--folderstyle FOLDERSTYLE, -fs FOLDERSTYLE 文件保存风格, 默认为True
|
||||||
|
--mode MODE, -M MODE link是个人主页时, 设置下载发布的作品(post)或喜欢的作品(like)或者用户所有合集(mix), 默认为post, 可以设置多种模式
|
||||||
|
--postnumber POSTNUMBER 主页下作品下载个数设置, 默认为0 全部下载
|
||||||
|
--likenumber LIKENUMBER 主页下喜欢下载个数设置, 默认为0 全部下载
|
||||||
|
--allmixnumber ALLMIXNUMBER 主页下合集下载个数设置, 默认为0 全部下载
|
||||||
|
--mixnumber MIXNUMBER 单个合集下作品下载个数设置, 默认为0 全部下载
|
||||||
|
--musicnumber MUSICNUMBER 音乐(原声)下作品下载个数设置, 默认为0 全部下载
|
||||||
|
--database DATABASE, -d DATABASE 是否使用数据库, 默认为True 使用数据库; 如果不使用数据库, 增量更新不可用
|
||||||
|
--postincrease POSTINCREASE 是否开启主页作品增量下载(True/False), 默认为False
|
||||||
|
--likeincrease LIKEINCREASE 是否开启主页喜欢增量下载(True/False), 默认为False
|
||||||
|
--allmixincrease ALLMIXINCREASE 是否开启主页合集增量下载(True/False), 默认为False
|
||||||
|
--mixincrease MIXINCREASE 是否开启单个合集下作品增量下载(True/False), 默认为False
|
||||||
|
--musicincrease MUSICINCREASE 是否开启音乐(原声)下作品增量下载(True/False), 默认为False
|
||||||
|
--thread THREAD, -t THREAD 设置线程数, 默认5个线程
|
||||||
|
--cookie COOKIE 设置cookie, 格式: "name1=value1; name2=value2;" 注意要加冒号
|
||||||
|
```
|
||||||
|
|
||||||
|
- 多链接多模式混合下载, 可以传入多个链接和多个模式(post、like、mix)
|
||||||
|
|
||||||
|
```
|
||||||
|
windows用户:
|
||||||
|
.\DouYinCommand.exe -C True `
|
||||||
|
-l https://live.douyin.com/759547612580 `
|
||||||
|
-l https://v.douyin.com/BugmVVD/ `
|
||||||
|
-l https://v.douyin.com/BugrFTN/ `
|
||||||
|
-l https://v.douyin.com/B72pdU5/ `
|
||||||
|
-l https://v.douyin.com/B72QgDw/ `
|
||||||
|
-l https://v.douyin.com/AJp8D3f/ `
|
||||||
|
-l https://v.douyin.com/B38oovu/ `
|
||||||
|
-l https://v.douyin.com/S6YMNXs/ `
|
||||||
|
-p C:\project\test `
|
||||||
|
-M post `
|
||||||
|
-M like `
|
||||||
|
-M mix `
|
||||||
|
--postnumber 5 `
|
||||||
|
--likenumber 5 `
|
||||||
|
--allmixnumber 1 `
|
||||||
|
--mixnumber 5 `
|
||||||
|
--musicnumber 5 `
|
||||||
|
--cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
|
|
||||||
|
linux与mac用户:
|
||||||
|
python DouYinCommand.py -C True \
|
||||||
|
-l https://live.douyin.com/759547612580 \
|
||||||
|
-l https://v.douyin.com/BugmVVD/ \
|
||||||
|
-l https://v.douyin.com/BugrFTN/ \
|
||||||
|
-l https://v.douyin.com/B72pdU5/ \
|
||||||
|
-l https://v.douyin.com/B72QgDw/ \
|
||||||
|
-l https://v.douyin.com/AJp8D3f/ \
|
||||||
|
-l https://v.douyin.com/B38oovu/ \
|
||||||
|
-l https://v.douyin.com/S6YMNXs/ \
|
||||||
|
-p /path/to/downdir \
|
||||||
|
-M post \
|
||||||
|
-M like \
|
||||||
|
-M mix \
|
||||||
|
--postnumber 5 \
|
||||||
|
--likenumber 5 \
|
||||||
|
--allmixnumber 1 \
|
||||||
|
--mixnumber 5 \
|
||||||
|
--musicnumber 5 \
|
||||||
|
--cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
```
|
```
|
||||||
|
|
||||||
- 下载单个作品
|
- 下载单个作品
|
||||||
|
|
||||||
```
|
```
|
||||||
windows用户:
|
windows用户:
|
||||||
.\TikTokCommand.exe -l https://v.douyin.com/kcvMpuN/ -p C:\project\test
|
.\DouYinCommand.exe -C True -l https://v.douyin.com/kcvMpuN/ -p C:\project\test --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
linux与mac用户:
|
linux与mac用户:
|
||||||
python TikTokCommand.py -l https://v.douyin.com/kcvMpuN/ -p /path/to/downdir
|
python DouYinCommand.py -C True -l https://v.douyin.com/kcvMpuN/ -p /path/to/downdir --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
```
|
```
|
||||||
|
|
||||||
- 下载主页全部作品
|
- 下载主页全部作品
|
||||||
|
|
||||||
```
|
```
|
||||||
windows用户:
|
windows用户:
|
||||||
.\TikTokCommand.exe -l https://v.douyin.com/kcvSCe9/ -p C:\project\test
|
.\DouYinCommand.exe -C True -l https://v.douyin.com/kcvSCe9/ -p C:\project\test --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
linux与mac用户:
|
linux与mac用户:
|
||||||
python TikTokCommand.py -l https://v.douyin.com/kcvSCe9/ -p /path/to/downdir
|
python DouYinCommand.py -C True -l https://v.douyin.com/kcvSCe9/ -p /path/to/downdir --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
```
|
```
|
||||||
|
|
||||||
- 下载主页前n个作品
|
- 增量更新主页下作品(postincrease 选项)
|
||||||
|
|
||||||
```
|
```
|
||||||
windows用户:
|
windows用户:
|
||||||
.\TikTokCommand.exe -l https://v.douyin.com/kcvSCe9/ -p C:\project\test -n 30
|
.\DouYinCommand.exe -C True -l https://v.douyin.com/kcvSCe9/ -p C:\project\test --postincrease True --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
linux与mac用户:
|
linux与mac用户:
|
||||||
python TikTokCommand.py -l https://v.douyin.com/kcvSCe9/ -p /path/to/downdir -n 30
|
python DouYinCommand.py -C True -l https://v.douyin.com/kcvSCe9/ -p /path/to/downdir --postincrease True --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
```
|
```
|
||||||
|
|
||||||
- 下载主页全部喜欢
|
- 关闭数据库, 增量更新不可用(database 选项)
|
||||||
|
|
||||||
```
|
```
|
||||||
windows用户:
|
windows用户:
|
||||||
.\TikTokCommand.exe -l https://v.douyin.com/kcvSCe9/ -p C:\project\test -M like
|
.\DouYinCommand.exe -C True -l https://v.douyin.com/kcvSCe9/ -p C:\project\test --database False --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
linux与mac用户:
|
linux与mac用户:
|
||||||
python TikTokCommand.py -l https://v.douyin.com/kcvSCe9/ -p /path/to/downdir -M like
|
python DouYinCommand.py -C True -l https://v.douyin.com/kcvSCe9/ -p /path/to/downdir --database False --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
```
|
```
|
||||||
|
|
||||||
- 下载主页前n个喜欢
|
- 所有视频在一个文件夹下(folderstyle 选项)
|
||||||
|
|
||||||
```
|
```
|
||||||
windows用户:
|
windows用户:
|
||||||
.\TikTokCommand.exe -l https://v.douyin.com/kcvSCe9/ -p C:\project\test -M like -n 30
|
.\DouYinCommand.exe -C True -l https://v.douyin.com/kcvSCe9/ -p C:\project\test --folderstyle False --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
linux与mac用户:
|
linux与mac用户:
|
||||||
python TikTokCommand.py -l https://v.douyin.com/kcvSCe9/ -p /path/to/downdir -M like -n 30
|
python DouYinCommand.py -C True -l https://v.douyin.com/kcvSCe9/ -p /path/to/downdir --folderstyle False --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
|
```
|
||||||
|
|
||||||
|
- 下载主页前n个作品(postnumber 选项)
|
||||||
|
|
||||||
|
```
|
||||||
|
windows用户:
|
||||||
|
.\DouYinCommand.exe -C True -l https://v.douyin.com/kcvSCe9/ -p C:\project\test --postnumber 30 --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
|
linux与mac用户:
|
||||||
|
python DouYinCommand.py -C True -l https://v.douyin.com/kcvSCe9/ -p /path/to/downdir --postnumber 30 --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
|
```
|
||||||
|
|
||||||
|
- 下载主页全部喜欢(-M like 选项)
|
||||||
|
|
||||||
|
```
|
||||||
|
windows用户:
|
||||||
|
.\DouYinCommand.exe -C True -l https://v.douyin.com/kcvSCe9/ -p C:\project\test -M like --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
|
linux与mac用户:
|
||||||
|
python DouYinCommand.py -C True -l https://v.douyin.com/kcvSCe9/ -p /path/to/downdir -M like --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
|
```
|
||||||
|
|
||||||
|
- 下载主页前n个喜欢(-M like --likenumber 选项)
|
||||||
|
|
||||||
|
```
|
||||||
|
windows用户:
|
||||||
|
.\DouYinCommand.exe -C True -l https://v.douyin.com/kcvSCe9/ -p C:\project\test -M like --likenumber 30 --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
|
linux与mac用户:
|
||||||
|
python DouYinCommand.py -C True -l https://v.douyin.com/kcvSCe9/ -p /path/to/downdir -M like --likenumber 30 --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
```
|
```
|
||||||
|
|
||||||
- 下载单个合集全部作品
|
- 下载单个合集全部作品
|
||||||
|
|
||||||
```
|
```
|
||||||
windows用户:
|
windows用户:
|
||||||
.\TikTokCommand.exe -l https://v.douyin.com/B3J63Le/ -p C:\project\test
|
.\DouYinCommand.exe -C True -l https://v.douyin.com/B3J63Le/ -p C:\project\test --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
linux与mac用户:
|
linux与mac用户:
|
||||||
python TikTokCommand.py -l https://v.douyin.com/B3J63Le/ -p /path/to/downdir
|
python DouYinCommand.py -C True -l https://v.douyin.com/B3J63Le/ -p /path/to/downdir --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
```
|
```
|
||||||
|
|
||||||
- 下载单个合集前n个作品
|
- 下载单个合集前n个作品(--mixnumber 选项)
|
||||||
|
|
||||||
```
|
```
|
||||||
windows用户:
|
windows用户:
|
||||||
.\TikTokCommand.exe -l https://v.douyin.com/B3J63Le/ -p C:\project\test -n 30
|
.\DouYinCommand.exe -C True -l https://v.douyin.com/B3J63Le/ -p C:\project\test --mixnumber 30 --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
linux与mac用户:
|
linux与mac用户:
|
||||||
python TikTokCommand.py -l https://v.douyin.com/B3J63Le/ -p /path/to/downdir -n 30
|
python DouYinCommand.py -C True -l https://v.douyin.com/B3J63Le/ -p /path/to/downdir --mixnumber 30 --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
```
|
```
|
||||||
|
|
||||||
- 下载主页全部合集下所有作品
|
- 下载主页全部合集下所有作品(-M mix 选项)
|
||||||
|
|
||||||
```
|
```
|
||||||
windows用户:
|
windows用户:
|
||||||
.\TikTokCommand.exe -l https://v.douyin.com/B38oovu/ -p C:\project\test -M mix
|
.\DouYinCommand.exe -C True -l https://v.douyin.com/B38oovu/ -p C:\project\test -M mix --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
linux与mac用户:
|
linux与mac用户:
|
||||||
python TikTokCommand.py -l https://v.douyin.com/B38oovu/ -p /path/to/downdir -M mix
|
python DouYinCommand.py -C True -l https://v.douyin.com/B38oovu/ -p /path/to/downdir -M mix --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
```
|
```
|
||||||
|
|
||||||
- 下载主页前n个合集下所有作品
|
- 下载主页前n个合集下所有作品(-M mix --allmixnumber 选项)
|
||||||
|
|
||||||
```
|
```
|
||||||
windows用户:
|
windows用户:
|
||||||
.\TikTokCommand.exe -l https://v.douyin.com/B38oovu/ -p C:\project\test -M mix -n 2
|
.\DouYinCommand.exe -C True -l https://v.douyin.com/B38oovu/ -p C:\project\test -M mix --allmixnumber 2 --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
linux与mac用户:
|
linux与mac用户:
|
||||||
python TikTokCommand.py -l https://v.douyin.com/B38oovu/ -p /path/to/downdir -M mix -n 2
|
python DouYinCommand.py -C True -l https://v.douyin.com/B38oovu/ -p /path/to/downdir -M mix --allmixnumber 2 --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
```
|
```
|
||||||
|
|
||||||
- 下载音乐集合下所有作品
|
- 下载音乐(原声)集合下所有作品
|
||||||
|
|
||||||
```
|
```
|
||||||
windows用户:
|
windows用户:
|
||||||
.\TikTokCommand.exe -l https://v.douyin.com/S6YMNXs/ -p C:\project\test
|
.\DouYinCommand.exe -C True -l https://v.douyin.com/S6YMNXs/ -p C:\project\test --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
linux与mac用户:
|
linux与mac用户:
|
||||||
python TikTokCommand.py -l https://v.douyin.com/S6YMNXs/ -p /path/to/downdir
|
python DouYinCommand.py -C True -l https://v.douyin.com/S6YMNXs/ -p /path/to/downdir --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
```
|
```
|
||||||
|
|
||||||
- 下载音乐集合下前n个作品
|
- 下载音乐(原声)集合下前n个作品(--musicnumber 选项)
|
||||||
|
|
||||||
```
|
```
|
||||||
windows用户:
|
windows用户:
|
||||||
.\TikTokCommand.exe -l https://v.douyin.com/S6YMNXs/ -p C:\project\test -n 30
|
.\DouYinCommand.exe -C True -l https://v.douyin.com/S6YMNXs/ -p C:\project\test --musicnumber 30 --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
linux与mac用户:
|
linux与mac用户:
|
||||||
python TikTokCommand.py -l https://v.douyin.com/S6YMNXs/ -p /path/to/downdir -n 30
|
python DouYinCommand.py -C True -l https://v.douyin.com/S6YMNXs/ -p /path/to/downdir --musicnumber 30 --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
```
|
```
|
||||||
|
|
||||||
- 关闭头像下载, cover, music 也是一样的设置对应选项为 False
|
- 关闭头像下载, cover, music json数据也是一样的设置对应选项为 False
|
||||||
|
|
||||||
```
|
```
|
||||||
windows用户:
|
windows用户:
|
||||||
.\TikTokCommand.exe -l https://v.douyin.com/kcvSCe9/ -p C:\project\test -a False
|
.\DouYinCommand.exe -C True -l https://v.douyin.com/kcvSCe9/ -p C:\project\test -a False --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
linux与mac用户:
|
linux与mac用户:
|
||||||
python TikTokCommand.py -l https://v.douyin.com/kcvSCe9/ -p /path/to/downdir -a False
|
python DouYinCommand.py -C True -l https://v.douyin.com/kcvSCe9/ -p /path/to/downdir -a False --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
|
```
|
||||||
|
|
||||||
|
- 多线程设置, 默认5个线程, 可以自己调节线程数
|
||||||
|
|
||||||
|
```
|
||||||
|
windows用户:
|
||||||
|
.\DouYinCommand.exe -C True -l https://v.douyin.com/kcvSCe9/ -p C:\project\test -t 8 --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
|
linux与mac用户:
|
||||||
|
python DouYinCommand.py -C True -l https://v.douyin.com/kcvSCe9/ -p /path/to/downdir -t 8 --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
```
|
```
|
||||||
|
|
||||||
- 直播推流地址解析
|
- 直播推流地址解析
|
||||||
|
|
||||||
```
|
```
|
||||||
windows用户:
|
windows用户:
|
||||||
.\TikTokCommand.exe -l https://live.douyin.com/802939216127 -p /path/to/downdir
|
.\DouYinCommand.exe -C True -l https://live.douyin.com/802939216127 -p /path/to/downdir --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
|
或者
|
||||||
|
.\DouYinCommand.exe -C True -l https://v.douyin.com/SnXMoh2/ -p /path/to/downdir --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
linux与mac用户:
|
linux与mac用户:
|
||||||
python TikTokCommand.py -l https://live.douyin.com/802939216127 -p /path/to/downdir
|
python DouYinCommand.py -C True -l https://live.douyin.com/802939216127 -p /path/to/downdir --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
|
或者
|
||||||
|
python DouYinCommand.py -C True -l https://v.douyin.com/SnXMoh2/ -p /path/to/downdir --cookie "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Web版接口
|
||||||
|
|
||||||
|
> !!!!!! 请传入cookie再使用
|
||||||
|
|
||||||
|
1. 单个作品、图集接口
|
||||||
|
|
||||||
|
```
|
||||||
|
接口地址: 127.0.0.1:5000/douyin/aweme
|
||||||
|
请求方式: POST
|
||||||
|
请求参数, JSON或form表单:
|
||||||
|
{
|
||||||
|
"share_link":"https://v.douyin.com/kcvMpuN/",
|
||||||
|
"cookie":"xxxx"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 直播解析接口
|
||||||
|
|
||||||
|
```
|
||||||
|
接口地址: 127.0.0.1:5000/douyin/live
|
||||||
|
请求方式: POST
|
||||||
|
请求参数, JSON或form表单:
|
||||||
|
{
|
||||||
|
"share_link":"https://v.douyin.com/DdWaSBd/",
|
||||||
|
"cookie":"xxxx"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
3. 主页作品
|
||||||
|
|
||||||
|
```
|
||||||
|
接口地址: 127.0.0.1:5000/douyin/user/post
|
||||||
|
请求方式: POST
|
||||||
|
请求参数, JSON或form表单:
|
||||||
|
{
|
||||||
|
"share_link":"https://v.douyin.com/B72pdU5/",
|
||||||
|
"cursor":0,
|
||||||
|
"cookie":"xxxx"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
4. 主页喜欢
|
||||||
|
|
||||||
|
```
|
||||||
|
接口地址: 127.0.0.1:5000/douyin/user/like
|
||||||
|
请求方式: POST
|
||||||
|
请求参数, JSON或form表单:
|
||||||
|
{
|
||||||
|
"share_link":"https://v.douyin.com/AoWVvYH/",
|
||||||
|
"cursor":0,
|
||||||
|
"cookie":"xxxx"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
5. 主页合集
|
||||||
|
|
||||||
|
```
|
||||||
|
接口地址: 127.0.0.1:5000/douyin/user/mix
|
||||||
|
请求方式: POST
|
||||||
|
请求参数, JSON或form表单:
|
||||||
|
{
|
||||||
|
"share_link":"https://v.douyin.com/B38oovu/",
|
||||||
|
"cursor":0,
|
||||||
|
"cookie":"xxxx"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
6. 单个合集
|
||||||
|
|
||||||
|
```
|
||||||
|
接口地址: 127.0.0.1:5000/douyin/mix
|
||||||
|
请求方式: POST
|
||||||
|
请求参数, JSON或form表单:
|
||||||
|
{
|
||||||
|
"share_link":"https://www.douyin.com/collection/7217644759668492345", // https://v.douyin.com 这种类型也可以
|
||||||
|
"cursor":0,
|
||||||
|
"cookie":"xxxx"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
7. 音乐(原声)
|
||||||
|
|
||||||
|
```
|
||||||
|
接口地址: 127.0.0.1:5000/douyin/music
|
||||||
|
请求方式: POST
|
||||||
|
请求参数, JSON或form表单:
|
||||||
|
{
|
||||||
|
"share_link":"https://v.douyin.com/S6YMNXs/",
|
||||||
|
"cursor":0,
|
||||||
|
"cookie":"xxxx"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ToDo
|
# ToDo
|
||||||
|
|
||||||
- [x] 单个合集下载
|
- [x] 单个合集下载
|
||||||
- [x] 主页所有合集下载
|
- [x] 主页所有合集下载
|
||||||
- [x] 获取分享的音乐链接下的所有作品
|
- [x] 获取分享的音乐(原声)链接下的所有作品
|
||||||
- [x] 指定下载作品数量
|
- [x] 指定下载作品数量
|
||||||
- [ ] 获取热搜榜数据
|
- [ ] 获取热搜榜数据
|
||||||
- [ ] 多主页链接批量下载
|
- [x] 多链接批量下载
|
||||||
- [ ] 多线程下载
|
- [x] 多线程下载
|
||||||
- [ ] 保存数据至数据库
|
- [x] 保存数据至数据库
|
||||||
- [ ] 制作成接口
|
- [x] 制作成接口
|
||||||
|
- [ ] 获取收藏与观看历史
|
||||||
|
- [ ] 直播间数据
|
||||||
|
|
||||||
# 鸣谢
|
# 鸣谢
|
||||||
|
|
||||||
本项目部分思路来自[TikTokDownload](https://github.com/Johnserf-Seed/TikTokDownload)
|
本项目部分思路来自[TikTokDownload](https://github.com/Johnserf-Seed/TikTokDownload)
|
||||||
|
|
||||||
|
# 赞赏
|
||||||
|
## 支付宝
|
||||||
|

|
||||||
|
## 微信
|
||||||
|

|
||||||
|
|
||||||
# 申明
|
# 申明
|
||||||
|
|
||||||
本项目只作为学习用途, 切勿他用
|
本项目只作为学习用途, 切勿他用
|
||||||
|
593
TikTok.py
@ -1,593 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- encoding: utf-8 -*-
|
|
||||||
|
|
||||||
'''
|
|
||||||
@Description:TikTok.py
|
|
||||||
@Date :2023/01/27 19:36:18
|
|
||||||
@Author :imgyh
|
|
||||||
@version :1.0
|
|
||||||
@Github :https://github.com/imgyh
|
|
||||||
@Mail :admin@imgyh.com
|
|
||||||
-------------------------------------------------
|
|
||||||
Change Log : 2023/02/11 修改接口
|
|
||||||
-------------------------------------------------
|
|
||||||
'''
|
|
||||||
|
|
||||||
import re
|
|
||||||
import requests
|
|
||||||
import json
|
|
||||||
import time
|
|
||||||
import os
|
|
||||||
import copy
|
|
||||||
|
|
||||||
from TikTokUtils import Utils
|
|
||||||
from TikTokUrls import Urls
|
|
||||||
from TikTokResult import Result
|
|
||||||
|
|
||||||
|
|
||||||
class TikTok(object):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.urls = Urls()
|
|
||||||
self.utils = Utils()
|
|
||||||
self.result = Result()
|
|
||||||
self.headers = {
|
|
||||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36',
|
|
||||||
'referer': 'https://www.douyin.com/',
|
|
||||||
'Cookie': f"msToken={self.utils.generate_random_str(107)}; ttwid={self.utils.getttwid()}; odin_tt=324fb4ea4a89c0c05827e18a1ed9cf9bf8a17f7705fcc793fec935b637867e2a5a9b8168c885554d029919117a18ba69; passport_csrf_token=f61602fc63757ae0e4fd9d6bdcee4810;"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# 从分享链接中提取网址
|
|
||||||
def getShareLink(self, string):
|
|
||||||
# findall() 查找匹配正则表达式的字符串
|
|
||||||
return re.findall('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', string)[0]
|
|
||||||
|
|
||||||
# 得到 作品id 或者 用户id
|
|
||||||
# 传入 url 支持 https://www.iesdouyin.com 与 https://v.douyin.com
|
|
||||||
def getKey(self, url):
|
|
||||||
key = None
|
|
||||||
key_type = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
r = requests.get(url=url, headers=self.headers)
|
|
||||||
except Exception as e:
|
|
||||||
print('[ 错误 ]:输入链接有误!\r')
|
|
||||||
return key_type, key
|
|
||||||
|
|
||||||
# 抖音把图集更新为note
|
|
||||||
# 作品 第一步解析出来的链接是share/video/{aweme_id}
|
|
||||||
# https://www.iesdouyin.com/share/video/7037827546599263488/?region=CN&mid=6939809470193126152&u_code=j8a5173b&did=MS4wLjABAAAA1DICF9-A9M_CiGqAJZdsnig5TInVeIyPdc2QQdGrq58xUgD2w6BqCHovtqdIDs2i&iid=MS4wLjABAAAAomGWi4n2T0H9Ab9x96cUZoJXaILk4qXOJlJMZFiK6b_aJbuHkjN_f0mBzfy91DX1&with_sec_did=1&titleType=title&schema_type=37&from_ssr=1&utm_source=copy&utm_campaign=client_share&utm_medium=android&app=aweme
|
|
||||||
# 用户 第一步解析出来的链接是share/user/{sec_uid}
|
|
||||||
# https://www.iesdouyin.com/share/user/MS4wLjABAAAA06y3Ctu8QmuefqvUSU7vr0c_ZQnCqB0eaglgkelLTek?did=MS4wLjABAAAA1DICF9-A9M_CiGqAJZdsnig5TInVeIyPdc2QQdGrq58xUgD2w6BqCHovtqdIDs2i&iid=MS4wLjABAAAAomGWi4n2T0H9Ab9x96cUZoJXaILk4qXOJlJMZFiK6b_aJbuHkjN_f0mBzfy91DX1&with_sec_did=1&sec_uid=MS4wLjABAAAA06y3Ctu8QmuefqvUSU7vr0c_ZQnCqB0eaglgkelLTek&from_ssr=1&u_code=j8a5173b×tamp=1674540164&ecom_share_track_params=%7B%22is_ec_shopping%22%3A%221%22%2C%22secuid%22%3A%22MS4wLjABAAAA-jD2lukp--I21BF8VQsmYUqJDbj3FmU-kGQTHl2y1Cw%22%2C%22enter_from%22%3A%22others_homepage%22%2C%22share_previous_page%22%3A%22others_homepage%22%7D&utm_source=copy&utm_campaign=client_share&utm_medium=android&app=aweme
|
|
||||||
# 合集
|
|
||||||
# https://www.douyin.com/collection/7093490319085307918
|
|
||||||
urlstr = str(r.request.path_url)
|
|
||||||
|
|
||||||
if "/share/user/" in urlstr:
|
|
||||||
# 获取用户 sec_uid
|
|
||||||
if '?' in r.request.path_url:
|
|
||||||
for one in re.finditer(r'user\/([\d\D]*)([?])', str(r.request.path_url)):
|
|
||||||
key = one.group(1)
|
|
||||||
else:
|
|
||||||
for one in re.finditer(r'user\/([\d\D]*)', str(r.request.path_url)):
|
|
||||||
key = one.group(1)
|
|
||||||
key_type = "user"
|
|
||||||
elif "/share/video/" in urlstr:
|
|
||||||
# 获取作品 aweme_id
|
|
||||||
key = re.findall('video/(\d+)?', urlstr)[0]
|
|
||||||
key_type = "aweme"
|
|
||||||
elif "/collection/" in urlstr:
|
|
||||||
# 获取作品 aweme_id
|
|
||||||
key = re.findall('collection/(\d+)?', urlstr)[0]
|
|
||||||
key_type = "mix"
|
|
||||||
elif "/music/" in urlstr:
|
|
||||||
# 获取作品 aweme_id
|
|
||||||
key = re.findall('music/(\d+)?', urlstr)[0]
|
|
||||||
key_type = "music"
|
|
||||||
elif "live.douyin.com" in r.url:
|
|
||||||
key = r.url.replace('https://live.douyin.com/', '')
|
|
||||||
key_type = "live"
|
|
||||||
|
|
||||||
if key is None or key_type is None:
|
|
||||||
print('[ 错误 ]:输入链接有误!无法获取 id\r')
|
|
||||||
return key_type, key
|
|
||||||
|
|
||||||
return key_type, key
|
|
||||||
|
|
||||||
# 传入 aweme_id
|
|
||||||
# 返回 数据 字典
|
|
||||||
def getAwemeInfo(self, aweme_id):
|
|
||||||
print('[ 提示 ]:正在请求的作品 id = %s\r' % aweme_id)
|
|
||||||
if aweme_id is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# 单作品接口返回 'aweme_detail'
|
|
||||||
# 主页作品接口返回 'aweme_list'->['aweme_detail']
|
|
||||||
jx_url = self.urls.POST_DETAIL + self.utils.getXbogus(
|
|
||||||
url=f'aweme_id={aweme_id}&aid=1128&version_name=23.5.0&device_platform=android&os_version=2333')
|
|
||||||
|
|
||||||
while True:
|
|
||||||
# 接口不稳定, 有时服务器不返回数据, 需要重新获取
|
|
||||||
try:
|
|
||||||
raw = requests.get(url=jx_url, headers=self.headers).text
|
|
||||||
datadict = json.loads(raw)
|
|
||||||
if datadict is not None and datadict['aweme_detail'] is not None and datadict["status_code"] == 0:
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
print("[ 警告 ]:接口未返回数据, 正在重新请求!\r")
|
|
||||||
|
|
||||||
# 清空self.awemeDict
|
|
||||||
self.result.clearDict(self.result.awemeDict)
|
|
||||||
|
|
||||||
# 默认为视频
|
|
||||||
awemeType = 0
|
|
||||||
try:
|
|
||||||
# datadict['aweme_detail']["images"] 不为 None 说明是图集
|
|
||||||
if datadict['aweme_detail']["images"] is not None:
|
|
||||||
awemeType = 1
|
|
||||||
except Exception as e:
|
|
||||||
print("[ 警告 ]:接口中未找到 images\r")
|
|
||||||
|
|
||||||
# 转换成我们自己的格式
|
|
||||||
self.result.dataConvert(awemeType, self.result.awemeDict, datadict['aweme_detail'])
|
|
||||||
|
|
||||||
return self.result.awemeDict, datadict
|
|
||||||
|
|
||||||
# 传入 url 支持 https://www.iesdouyin.com 与 https://v.douyin.com
|
|
||||||
# mode : post | like 模式选择 like为用户点赞 post为用户发布
|
|
||||||
def getUserInfo(self, sec_uid, mode="post", count=35, number=0):
|
|
||||||
print('[ 提示 ]:正在请求的用户 id = %s\r\n' % sec_uid)
|
|
||||||
if sec_uid is None:
|
|
||||||
return None
|
|
||||||
if number <= 0:
|
|
||||||
numflag = False
|
|
||||||
else:
|
|
||||||
numflag = True
|
|
||||||
|
|
||||||
max_cursor = 0
|
|
||||||
awemeList = []
|
|
||||||
|
|
||||||
print("[ 提示 ]:正在获取所有作品数据请稍后...\r")
|
|
||||||
print("[ 提示 ]:会进行多次请求,等待时间较长...\r\n")
|
|
||||||
times = 0
|
|
||||||
while True:
|
|
||||||
times = times + 1
|
|
||||||
print("[ 提示 ]:正在对 [主页] 进行第 " + str(times) + " 次请求...\r")
|
|
||||||
if mode == "post":
|
|
||||||
url = self.urls.USER_POST + self.utils.getXbogus(
|
|
||||||
url=f'device_platform=webapp&aid=6383&os_version=10&version_name=17.4.0&sec_user_id={sec_uid}&count={count}&max_cursor={max_cursor}')
|
|
||||||
elif mode == "like":
|
|
||||||
url = self.urls.USER_FAVORITE_A + self.utils.getXbogus(
|
|
||||||
url=f'sec_user_id={sec_uid}&count={count}&max_cursor={max_cursor}&aid=1128&version_name=23.5.0&device_platform=android&os_version=2333')
|
|
||||||
else:
|
|
||||||
print("[ 错误 ]:模式选择错误, 仅支持post、like、mix, 请检查后重新运行!\r")
|
|
||||||
return None
|
|
||||||
|
|
||||||
while True:
|
|
||||||
# 接口不稳定, 有时服务器不返回数据, 需要重新获取
|
|
||||||
try:
|
|
||||||
res = requests.get(url=url, headers=self.headers)
|
|
||||||
datadict = json.loads(res.text)
|
|
||||||
print('[ 提示 ]:本次请求返回 ' + str(len(datadict["aweme_list"])) + ' 条数据\r')
|
|
||||||
print('[ 提示 ]:开始对 ' + str(len(datadict["aweme_list"])) + ' 条数据请求作品详情\r\n')
|
|
||||||
if datadict is not None and datadict["status_code"] == 0:
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
print("[ 警告 ]:接口未返回数据, 正在重新请求!\r")
|
|
||||||
|
|
||||||
for aweme in datadict["aweme_list"]:
|
|
||||||
# 获取 aweme_id
|
|
||||||
aweme_id = aweme["aweme_id"]
|
|
||||||
# 深拷贝 dict 不然list里面全是同样的数据
|
|
||||||
datanew, dataraw = self.getAwemeInfo(aweme_id)
|
|
||||||
awemeList.append(copy.deepcopy(datanew))
|
|
||||||
if numflag:
|
|
||||||
number-=1
|
|
||||||
if number==0:
|
|
||||||
break
|
|
||||||
if numflag and number==0:
|
|
||||||
print("\r\n[ 提示 ]: [主页] 下指定数量作品数据获取完成...\r\n")
|
|
||||||
break
|
|
||||||
|
|
||||||
# 更新 max_cursor
|
|
||||||
max_cursor = datadict["max_cursor"]
|
|
||||||
|
|
||||||
# 退出条件
|
|
||||||
if datadict["has_more"] == 0 or datadict["has_more"] == False:
|
|
||||||
print("\r\n[ 提示 ]: [主页] 下所有作品数据获取完成...\r\n")
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
print("\r\n[ 提示 ]:[主页] 第 " + str(times) + " 次请求成功...\r\n")
|
|
||||||
|
|
||||||
return awemeList
|
|
||||||
|
|
||||||
def getLiveInfo(self, web_rid: str):
|
|
||||||
print('[ 提示 ]:正在请求的直播间 id = %s\r\n' % web_rid)
|
|
||||||
|
|
||||||
# web_rid = live_url.replace('https://live.douyin.com/', '')
|
|
||||||
|
|
||||||
live_api = self.urls.LIVE + self.utils.getXbogus(
|
|
||||||
url=f'aid=6383&device_platform=web&web_rid={web_rid}')
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = requests.get(live_api, headers=self.headers)
|
|
||||||
live_json = json.loads(response.text)
|
|
||||||
except Exception as e:
|
|
||||||
print("[ 错误 ]:接口未返回数据, 请检查后重新运行!\r")
|
|
||||||
return None
|
|
||||||
|
|
||||||
if live_json == {} or live_json['status_code'] != 0:
|
|
||||||
print("[ 错误 ]:接口未返回信息\r")
|
|
||||||
return None
|
|
||||||
|
|
||||||
# 清空字典
|
|
||||||
self.result.clearDict(self.result.liveDict)
|
|
||||||
|
|
||||||
# 是否在播
|
|
||||||
self.result.liveDict["status"] = live_json['data']['data'][0]['status']
|
|
||||||
|
|
||||||
if self.result.liveDict["status"] == 4:
|
|
||||||
print('[ 📺 ]:当前直播已结束,正在退出')
|
|
||||||
return self.result.liveDict
|
|
||||||
|
|
||||||
# 直播标题
|
|
||||||
self.result.liveDict["title"] = live_json['data']['data'][0]['title']
|
|
||||||
|
|
||||||
# 观看人数
|
|
||||||
self.result.liveDict["user_count"] = live_json['data']['data'][0]['user_count_str']
|
|
||||||
|
|
||||||
# 昵称
|
|
||||||
self.result.liveDict["nickname"] = live_json['data']['data'][0]['owner']['nickname']
|
|
||||||
|
|
||||||
# sec_uid
|
|
||||||
self.result.liveDict["sec_uid"] = live_json['data']['data'][0]['owner']['sec_uid']
|
|
||||||
|
|
||||||
# 直播间观看状态
|
|
||||||
self.result.liveDict["display_long"] = live_json['data']['data'][0]['room_view_stats']['display_long']
|
|
||||||
|
|
||||||
# 推流
|
|
||||||
self.result.liveDict["flv_pull_url"] = live_json['data']['data'][0]['stream_url']['flv_pull_url']
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 分区
|
|
||||||
self.result.liveDict["partition"] = live_json['data']['partition_road_map']['partition']['title']
|
|
||||||
self.result.liveDict["sub_partition"] = live_json['data']['partition_road_map']['sub_partition']['partition'][
|
|
||||||
'title']
|
|
||||||
except Exception as e:
|
|
||||||
self.result.liveDict["partition"] = '无'
|
|
||||||
self.result.liveDict["sub_partition"] = '无'
|
|
||||||
|
|
||||||
info = '[ 💻 ]:直播间:%s 当前%s 主播:%s 分区:%s-%s\r' % (
|
|
||||||
self.result.liveDict["title"], self.result.liveDict["display_long"], self.result.liveDict["nickname"],
|
|
||||||
self.result.liveDict["partition"], self.result.liveDict["sub_partition"])
|
|
||||||
print(info)
|
|
||||||
|
|
||||||
flv = []
|
|
||||||
print('[ 🎦 ]:直播间清晰度')
|
|
||||||
for i, f in enumerate(self.result.liveDict["flv_pull_url"].keys()):
|
|
||||||
print('[ %s ]: %s' % (i, f))
|
|
||||||
flv.append(f)
|
|
||||||
|
|
||||||
rate = int(input('[ 🎬 ]输入数字选择推流清晰度:'))
|
|
||||||
|
|
||||||
# 显示清晰度列表
|
|
||||||
print('[ %s ]:%s' % (flv[rate], self.result.liveDict["flv_pull_url"][flv[rate]]))
|
|
||||||
|
|
||||||
print('[ 📺 ]:复制链接使用下载工具下载')
|
|
||||||
return self.result.liveDict
|
|
||||||
|
|
||||||
def getMixInfo(self, mix_id: str, count=35, number=0):
|
|
||||||
print('[ 提示 ]:正在请求的合集 id = %s\r\n' % mix_id)
|
|
||||||
if mix_id is None:
|
|
||||||
return None
|
|
||||||
if number <= 0:
|
|
||||||
numflag = False
|
|
||||||
else:
|
|
||||||
numflag = True
|
|
||||||
|
|
||||||
cursor = 0
|
|
||||||
awemeList = []
|
|
||||||
|
|
||||||
print("[ 提示 ]:正在获取合集下的所有作品数据请稍后...\r")
|
|
||||||
print("[ 提示 ]:会进行多次请求,等待时间较长...\r\n")
|
|
||||||
times = 0
|
|
||||||
while True:
|
|
||||||
times = times + 1
|
|
||||||
print("[ 提示 ]:正在对 [合集] 进行第 " + str(times) + " 次请求...\r")
|
|
||||||
|
|
||||||
url = self.urls.USER_MIX + self.utils.getXbogus(
|
|
||||||
url=f'device_platform=webapp&aid=6383&os_version=10&version_name=17.4.0&mix_id={mix_id}&cursor={cursor}&count={count}')
|
|
||||||
|
|
||||||
while True:
|
|
||||||
# 接口不稳定, 有时服务器不返回数据, 需要重新获取
|
|
||||||
try:
|
|
||||||
res = requests.get(url=url, headers=self.headers)
|
|
||||||
datadict = json.loads(res.text)
|
|
||||||
print('[ 提示 ]:本次请求返回 ' + str(len(datadict["aweme_list"])) + ' 条数据\r')
|
|
||||||
print('[ 提示 ]:开始对 ' + str(len(datadict["aweme_list"])) + ' 条数据请求作品详情\r\n')
|
|
||||||
if datadict is not None:
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
print("[ 警告 ]:接口未返回数据, 正在重新请求!\r")
|
|
||||||
|
|
||||||
for aweme in datadict["aweme_list"]:
|
|
||||||
# 获取 aweme_id
|
|
||||||
aweme_id = aweme["aweme_id"]
|
|
||||||
# 深拷贝 dict 不然list里面全是同样的数据
|
|
||||||
datanew, dataraw = self.getAwemeInfo(aweme_id)
|
|
||||||
awemeList.append(copy.deepcopy(datanew))
|
|
||||||
if numflag:
|
|
||||||
number -= 1
|
|
||||||
if number == 0:
|
|
||||||
break
|
|
||||||
if numflag and number == 0:
|
|
||||||
print("\r\n[ 提示 ]:[合集] 下指定数量作品数据获取完成...\r\n")
|
|
||||||
break
|
|
||||||
|
|
||||||
# 更新 max_cursor
|
|
||||||
cursor = datadict["cursor"]
|
|
||||||
|
|
||||||
# 退出条件
|
|
||||||
if datadict["has_more"] == 0 or datadict["has_more"] == False:
|
|
||||||
print("\r\n[ 提示 ]:[合集] 下所有作品数据获取完成...\r\n")
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
print("\r\n[ 提示 ]:[合集] 第 " + str(times) + " 次请求成功...\r\n")
|
|
||||||
|
|
||||||
return awemeList
|
|
||||||
|
|
||||||
def getUserAllMixInfo(self, sec_uid, count=35, number=0):
|
|
||||||
print('[ 提示 ]:正在请求的用户 id = %s\r\n' % sec_uid)
|
|
||||||
if sec_uid is None:
|
|
||||||
return None
|
|
||||||
if number <= 0:
|
|
||||||
numflag = False
|
|
||||||
else:
|
|
||||||
numflag = True
|
|
||||||
|
|
||||||
cursor = 0
|
|
||||||
mixIdNameDict = {}
|
|
||||||
|
|
||||||
print("[ 提示 ]:正在获取主页下所有合集 id 数据请稍后...\r")
|
|
||||||
print("[ 提示 ]:会进行多次请求,等待时间较长...\r\n")
|
|
||||||
times = 0
|
|
||||||
while True:
|
|
||||||
times = times + 1
|
|
||||||
print("[ 提示 ]:正在对 [合集列表] 进行第 " + str(times) + " 次请求...\r")
|
|
||||||
|
|
||||||
url = self.urls.USER_MIX_LIST + self.utils.getXbogus(
|
|
||||||
url=f'device_platform=webapp&aid=6383&os_version=10&version_name=17.4.0&sec_user_id={sec_uid}&count={count}&cursor={cursor}')
|
|
||||||
|
|
||||||
while True:
|
|
||||||
# 接口不稳定, 有时服务器不返回数据, 需要重新获取
|
|
||||||
try:
|
|
||||||
res = requests.get(url=url, headers=self.headers)
|
|
||||||
datadict = json.loads(res.text)
|
|
||||||
print('[ 提示 ]:本次请求返回 ' + str(len(datadict["mix_infos"])) + ' 条数据\r')
|
|
||||||
print('[ 提示 ]:开始对 ' + str(len(datadict["mix_infos"])) + ' 条数据请求作品详情\r\n')
|
|
||||||
if datadict is not None and datadict["status_code"] == 0:
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
print("[ 警告 ]:接口未返回数据, 正在重新请求!\r")
|
|
||||||
|
|
||||||
for mix in datadict["mix_infos"]:
|
|
||||||
mixIdNameDict[mix["mix_id"]] = mix["mix_name"]
|
|
||||||
if numflag:
|
|
||||||
number -= 1
|
|
||||||
if number == 0:
|
|
||||||
break
|
|
||||||
if numflag and number == 0:
|
|
||||||
print("\r\n[ 提示 ]:[合集列表] 下指定数量合集数据获取完成...\r\n")
|
|
||||||
break
|
|
||||||
|
|
||||||
# 更新 max_cursor
|
|
||||||
cursor = datadict["cursor"]
|
|
||||||
|
|
||||||
# 退出条件
|
|
||||||
if datadict["has_more"] == 0 or datadict["has_more"] == False:
|
|
||||||
print("[ 提示 ]:[合集列表] 下所有合集 id 数据获取完成...\r\n")
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
print("\r\n[ 提示 ]:[合集列表] 第 " + str(times) + " 次请求成功...\r\n")
|
|
||||||
|
|
||||||
return mixIdNameDict
|
|
||||||
|
|
||||||
def getMusicInfo(self, music_id: str, count=35, number=0):
|
|
||||||
print('[ 提示 ]:正在请求的音乐集合 id = %s\r\n' % music_id)
|
|
||||||
if music_id is None:
|
|
||||||
return None
|
|
||||||
if number <= 0:
|
|
||||||
numflag = False
|
|
||||||
else:
|
|
||||||
numflag = True
|
|
||||||
|
|
||||||
cursor = 0
|
|
||||||
awemeList = []
|
|
||||||
|
|
||||||
print("[ 提示 ]:正在获取音乐集合下的所有作品数据请稍后...\r")
|
|
||||||
print("[ 提示 ]:会进行多次请求,等待时间较长...\r\n")
|
|
||||||
times = 0
|
|
||||||
while True:
|
|
||||||
times = times + 1
|
|
||||||
print("[ 提示 ]:正在对 [音乐集合] 进行第 " + str(times) + " 次请求...\r")
|
|
||||||
|
|
||||||
url = self.urls.MUSIC + self.utils.getXbogus(
|
|
||||||
url=f'device_platform=webapp&aid=6383&os_version=10&version_name=17.4.0&music_id={music_id}&cursor={cursor}&count={count}')
|
|
||||||
|
|
||||||
while True:
|
|
||||||
# 接口不稳定, 有时服务器不返回数据, 需要重新获取
|
|
||||||
try:
|
|
||||||
res = requests.get(url=url, headers=self.headers)
|
|
||||||
datadict = json.loads(res.text)
|
|
||||||
print('[ 提示 ]:本次请求返回 ' + str(len(datadict["aweme_list"])) + ' 条数据\r')
|
|
||||||
print('[ 提示 ]:开始对 ' + str(len(datadict["aweme_list"])) + ' 条数据请求作品详情\r\n')
|
|
||||||
if datadict is not None:
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
print("[ 警告 ]:接口未返回数据, 正在重新请求!\r")
|
|
||||||
|
|
||||||
for aweme in datadict["aweme_list"]:
|
|
||||||
# 获取 aweme_id
|
|
||||||
aweme_id = aweme["aweme_id"]
|
|
||||||
# 深拷贝 dict 不然list里面全是同样的数据
|
|
||||||
datanew, dataraw = self.getAwemeInfo(aweme_id)
|
|
||||||
awemeList.append(copy.deepcopy(datanew))
|
|
||||||
if numflag:
|
|
||||||
number -= 1
|
|
||||||
if number == 0:
|
|
||||||
break
|
|
||||||
if numflag and number == 0:
|
|
||||||
print("\r\n[ 提示 ]:[音乐集合] 下指定数量作品数据获取完成...\r\n")
|
|
||||||
break
|
|
||||||
|
|
||||||
# 更新 cursor
|
|
||||||
cursor = datadict["cursor"]
|
|
||||||
|
|
||||||
# 退出条件
|
|
||||||
if datadict["has_more"] == 0 or datadict["has_more"] == False:
|
|
||||||
print("\r\n[ 提示 ]:[音乐集合] 下所有作品数据获取完成...\r\n")
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
print("\r\n[ 提示 ]:[音乐集合] 第 " + str(times) + " 次请求成功...\r\n")
|
|
||||||
|
|
||||||
return awemeList
|
|
||||||
|
|
||||||
# 来自 https://blog.csdn.net/weixin_43347550/article/details/105248223
|
|
||||||
def progressBarDownload(self, url, filepath):
|
|
||||||
start = time.time() # 下载开始时间
|
|
||||||
response = requests.get(url, stream=True, headers=self.headers)
|
|
||||||
size = 0 # 初始化已下载大小
|
|
||||||
chunk_size = 1024 # 每次下载的数据大小
|
|
||||||
content_size = int(response.headers['content-length']) # 下载文件总大小
|
|
||||||
try:
|
|
||||||
if response.status_code == 200: # 判断是否响应成功
|
|
||||||
print('[开始下载]:文件大小:{size:.2f} MB'.format(
|
|
||||||
size=content_size / chunk_size / 1024)) # 开始下载,显示下载文件大小
|
|
||||||
with open(filepath, 'wb') as file: # 显示进度条
|
|
||||||
for data in response.iter_content(chunk_size=chunk_size):
|
|
||||||
file.write(data)
|
|
||||||
size += len(data)
|
|
||||||
print('\r' + '[下载进度]:%s%.2f%%' % (
|
|
||||||
'>' * int(size * 50 / content_size), float(size / content_size * 100)), end=' ')
|
|
||||||
end = time.time() # 下载结束时间
|
|
||||||
print('\n' + '[下载完成]:耗时: %.2f秒\n' % (
|
|
||||||
end - start)) # 输出下载用时时间
|
|
||||||
except Exception as e:
|
|
||||||
# 下载异常 删除原来下载的文件, 可能未下成功
|
|
||||||
if os.path.exists(filepath):
|
|
||||||
os.remove(filepath)
|
|
||||||
print("[ 错误 ]:下载出错\r")
|
|
||||||
|
|
||||||
def awemeDownload(self, awemeDict: dict, music=True, cover=True, avatar=True, savePath=os.getcwd()):
|
|
||||||
if awemeDict is None:
|
|
||||||
return
|
|
||||||
if not os.path.exists(savePath):
|
|
||||||
os.mkdir(savePath)
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 使用作品 创建时间+描述 当文件夹
|
|
||||||
file_name = self.utils.replaceStr(awemeDict["create_time"] + " " + awemeDict["desc"])
|
|
||||||
aweme_path = os.path.join(savePath, file_name)
|
|
||||||
if not os.path.exists(aweme_path):
|
|
||||||
os.mkdir(aweme_path)
|
|
||||||
|
|
||||||
# 保存获取到的字典信息
|
|
||||||
print("[ 提示 ]:正在保存获取到的信息到 result.json\r\n")
|
|
||||||
with open(os.path.join(aweme_path, "result.json"), "w", encoding='utf-8') as f:
|
|
||||||
f.write(json.dumps(awemeDict, ensure_ascii=False, indent=2))
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
# 下载 视频
|
|
||||||
if awemeDict["awemeType"] == 0:
|
|
||||||
print("[ 提示 ]:正在下载视频...\r")
|
|
||||||
video_path = os.path.join(aweme_path, file_name + ".mp4")
|
|
||||||
|
|
||||||
if os.path.exists(video_path):
|
|
||||||
print("[ 提示 ]:视频已存在为您跳过...\r\n")
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
url = awemeDict["video"]["play_addr"]["url_list"]
|
|
||||||
if url != "":
|
|
||||||
self.progressBarDownload(url, video_path)
|
|
||||||
except Exception as e:
|
|
||||||
print("[ 错误 ]:无法获取到视频url\r\n")
|
|
||||||
|
|
||||||
# 下载 图集
|
|
||||||
if awemeDict["awemeType"] == 1:
|
|
||||||
print("[ 提示 ]:正在下载图集...\r")
|
|
||||||
for ind, image in enumerate(awemeDict["images"]):
|
|
||||||
image_path = os.path.join(aweme_path, "image" + str(ind) + ".jpeg")
|
|
||||||
if os.path.exists(image_path):
|
|
||||||
print("[ 提示 ]:图片已存在为您跳过...\r\n")
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
url = image["url_list"][0]
|
|
||||||
if url != "":
|
|
||||||
self.progressBarDownload(url, image_path)
|
|
||||||
except Exception as e:
|
|
||||||
print("[ 错误 ]:无法获取到图片url\r\n")
|
|
||||||
|
|
||||||
# 下载 音乐
|
|
||||||
if music:
|
|
||||||
print("[ 提示 ]:正在下载音乐...\r")
|
|
||||||
music_name = self.utils.replaceStr(awemeDict["music"]["title"])
|
|
||||||
music_path = os.path.join(aweme_path, music_name + ".mp3")
|
|
||||||
|
|
||||||
if os.path.exists(music_path):
|
|
||||||
print("[ 提示 ]:音乐已存在为您跳过...\r\n")
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
url = awemeDict["music"]["play_url"]["url_list"][0]
|
|
||||||
if url != "":
|
|
||||||
self.progressBarDownload(url, music_path)
|
|
||||||
except Exception as e:
|
|
||||||
print("[ 错误 ]:无法获取到音乐url\r\n")
|
|
||||||
|
|
||||||
# 下载 cover
|
|
||||||
if cover and awemeDict["awemeType"] == 0:
|
|
||||||
print("[ 提示 ]:正在下载视频cover图...\r")
|
|
||||||
cover_path = os.path.join(aweme_path, "cover.jpeg")
|
|
||||||
|
|
||||||
if os.path.exists(cover_path):
|
|
||||||
print("[ 提示 ]:cover 已存在为您跳过...\r\n")
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
url = awemeDict["video"]["cover_original_scale"]["url_list"][0]
|
|
||||||
if url != "":
|
|
||||||
self.progressBarDownload(url, cover_path)
|
|
||||||
except Exception as e:
|
|
||||||
print("[ 错误 ]:无法获取到cover url\r\n")
|
|
||||||
|
|
||||||
# 下载 avatar
|
|
||||||
if avatar:
|
|
||||||
print("[ 提示 ]:正在下载用户头像...\r")
|
|
||||||
avatar_path = os.path.join(aweme_path, "avatar.jpeg")
|
|
||||||
|
|
||||||
if os.path.exists(avatar_path):
|
|
||||||
print("[ 提示 ]:avatar 已存在为您跳过...\r\n")
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
url = awemeDict["author"]["avatar"]["url_list"][0]
|
|
||||||
if url != "":
|
|
||||||
self.progressBarDownload(url, avatar_path)
|
|
||||||
except Exception as e:
|
|
||||||
print("[ 错误 ]:无法获取到avatar url\r\n")
|
|
||||||
except Exception as e:
|
|
||||||
print("[ 错误 ]:请检查json信息是否正确\r\n")
|
|
||||||
|
|
||||||
def userDownload(self, awemeList: list, music=True, cover=True, avatar=True, savePath=os.getcwd()):
|
|
||||||
if awemeList is None:
|
|
||||||
return
|
|
||||||
if not os.path.exists(savePath):
|
|
||||||
os.mkdir(savePath)
|
|
||||||
for ind, aweme in enumerate(awemeList):
|
|
||||||
print("[ 提示 ]:正在下载 [%s] 的作品 %s/%s\r\n"
|
|
||||||
% (aweme["author"]["nickname"], str(ind + 1), len(awemeList)))
|
|
||||||
|
|
||||||
self.awemeDownload(aweme, music, cover, avatar, savePath)
|
|
||||||
# time.sleep(0.5)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
pass
|
|
@ -1,97 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- encoding: utf-8 -*-
|
|
||||||
|
|
||||||
'''
|
|
||||||
@Description:TikTok.py
|
|
||||||
@Date :2023/01/27 19:36:18
|
|
||||||
@Author :imgyh
|
|
||||||
@version :1.0
|
|
||||||
@Github :https://github.com/imgyh
|
|
||||||
@Mail :admin@imgyh.com
|
|
||||||
-------------------------------------------------
|
|
||||||
Change Log :
|
|
||||||
-------------------------------------------------
|
|
||||||
'''
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import os
|
|
||||||
import json
|
|
||||||
from TikTok import TikTok
|
|
||||||
from TikTokUtils import Utils
|
|
||||||
|
|
||||||
|
|
||||||
def argument():
|
|
||||||
parser = argparse.ArgumentParser(description='抖音批量下载工具 使用帮助')
|
|
||||||
parser.add_argument("--link", "-l",
|
|
||||||
help="1.作品(视频或图集)、合集、音乐集合、个人主页抖音分享链接(删除文案, 保证只有URL, https://v.douyin.com/kcvMpuN/)\r\n" +
|
|
||||||
"2.解析直播网页版网址(https://live.douyin.com/802939216127)",
|
|
||||||
type=str, required=True)
|
|
||||||
parser.add_argument("--path", "-p", help="下载保存位置",
|
|
||||||
type=str, required=True)
|
|
||||||
parser.add_argument("--music", "-m", help="是否下载视频中的音乐(True/False), 默认为True",
|
|
||||||
type=Utils().str2bool, required=False, default=True)
|
|
||||||
parser.add_argument("--cover", "-c", help="是否下载视频的封面(True/False), 默认为True, 当下载视频时有效",
|
|
||||||
type=Utils().str2bool, required=False, default=True)
|
|
||||||
parser.add_argument("--avatar", "-a", help="是否下载作者的头像(True/False), 默认为True",
|
|
||||||
type=Utils().str2bool, required=False, default=True)
|
|
||||||
parser.add_argument("--mode", "-M", help="link是个人主页时, 设置下载发布的作品(post)或喜欢的作品(like)或者用户所有合集(mix), 默认为post",
|
|
||||||
type=str, required=False, default="post")
|
|
||||||
parser.add_argument("--number", "-n",
|
|
||||||
help="1.当下载单个合集、音乐集合、主页作品(post模式)和喜欢(like模式)时, 可设置下载前n个作品, 默认为0全部下载\r\n" +
|
|
||||||
"2.当下载主页下所有合集(mix模式)时, 设置下载前n个合集下所有作品, 默认为0全部下载",
|
|
||||||
type=int, required=False, default=0)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
return args
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
utils = Utils()
|
|
||||||
args = argument()
|
|
||||||
tk = TikTok()
|
|
||||||
url = tk.getShareLink(args.link)
|
|
||||||
key_type, key = tk.getKey(url)
|
|
||||||
if key is None or key_type is None:
|
|
||||||
return
|
|
||||||
elif key_type == "user" and args.mode != 'mix':
|
|
||||||
datalist = tk.getUserInfo(key, args.mode, 35, args.number)
|
|
||||||
tk.userDownload(awemeList=datalist, music=args.music, cover=args.cover, avatar=args.avatar,
|
|
||||||
savePath=args.path)
|
|
||||||
elif key_type == "user" and args.mode == 'mix':
|
|
||||||
if not os.path.exists(args.path):
|
|
||||||
os.mkdir(args.path)
|
|
||||||
mixIdNameDict = tk.getUserAllMixInfo(key, 35, args.number)
|
|
||||||
|
|
||||||
for mix_id in mixIdNameDict:
|
|
||||||
print(f'[ 提示 ]:正在下载合集 [{mixIdNameDict[mix_id]}] 中的作品\r\n')
|
|
||||||
mix_file_name = utils.replaceStr(mixIdNameDict[mix_id])
|
|
||||||
datalist = tk.getMixInfo(mix_id, 35)
|
|
||||||
tk.userDownload(awemeList=datalist, music=args.music, cover=args.cover, avatar=args.avatar,
|
|
||||||
savePath=os.path.join(args.path, mix_file_name))
|
|
||||||
print(f'[ 提示 ]:合集 [{mixIdNameDict[mix_id]}] 中的作品下载完成\r\n')
|
|
||||||
elif key_type == "mix":
|
|
||||||
datalist = tk.getMixInfo(key,35, args.number)
|
|
||||||
tk.userDownload(awemeList=datalist, music=args.music, cover=args.cover, avatar=args.avatar,
|
|
||||||
savePath=args.path)
|
|
||||||
elif key_type == "music":
|
|
||||||
datalist = tk.getMusicInfo(key,35, args.number)
|
|
||||||
tk.userDownload(awemeList=datalist, music=args.music, cover=args.cover, avatar=args.avatar,
|
|
||||||
savePath=args.path)
|
|
||||||
elif key_type == "aweme":
|
|
||||||
datanew, dataraw = tk.getAwemeInfo(key)
|
|
||||||
tk.awemeDownload(awemeDict=datanew, music=args.music, cover=args.cover, avatar=args.avatar,
|
|
||||||
savePath=args.path)
|
|
||||||
elif key_type == "live":
|
|
||||||
live_json = tk.getLiveInfo(key)
|
|
||||||
if not os.path.exists(args.path):
|
|
||||||
os.mkdir(args.path)
|
|
||||||
|
|
||||||
# 保存获取到json
|
|
||||||
print("[ 提示 ]:正在保存获取到的信息到result.json\r\n")
|
|
||||||
with open(os.path.join(args.path, "result.json"), "w", encoding='utf-8') as f:
|
|
||||||
f.write(json.dumps(live_json, ensure_ascii=False, indent=2))
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
130
TikTokTest.py
@ -1,130 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- encoding: utf-8 -*-
|
|
||||||
|
|
||||||
'''
|
|
||||||
@Description:TikTok.py
|
|
||||||
@Date :2023/02/11 13:06:23
|
|
||||||
@Author :imgyh
|
|
||||||
@version :1.0
|
|
||||||
@Github :https://github.com/imgyh
|
|
||||||
@Mail :admin@imgyh.com
|
|
||||||
-------------------------------------------------
|
|
||||||
Change Log :
|
|
||||||
-------------------------------------------------
|
|
||||||
'''
|
|
||||||
import TikTokUtils
|
|
||||||
from TikTok import TikTok
|
|
||||||
|
|
||||||
def getAwemeInfo():
|
|
||||||
share_link_video = "3.56 uSy:/ 复制打开抖音,看看【小透明的作品】没有女朋友就用我的吧哈哈哈哈 # 表情包锁屏 https://v.douyin.com/BugmVVD/"
|
|
||||||
share_link_pic = "8.20 MJI:/ 复制打开抖音,看看【舍溪的图文作品】我又来放图集啦~还有你们要的小可爱大图也放啦~# ... https://v.douyin.com/BugrFTN/"
|
|
||||||
tk = TikTok()
|
|
||||||
|
|
||||||
url = tk.getShareLink(share_link_pic)
|
|
||||||
key_type, key = tk.getKey(url)
|
|
||||||
datanew, dataraw = tk.getAwemeInfo(key)
|
|
||||||
print(datanew)
|
|
||||||
|
|
||||||
def getUserInfo():
|
|
||||||
share_link_post = "1- 长按复制此条消息,打开抖音搜索,查看TA的更多作品。 https://v.douyin.com/BupCppt/"
|
|
||||||
share_link_like = "2- 长按复制此条消息,打开抖音搜索,查看TA的更多作品。 https://v.douyin.com/BusJrfr/"
|
|
||||||
tk = TikTok()
|
|
||||||
|
|
||||||
url = tk.getShareLink(share_link_like)
|
|
||||||
key_type, key = tk.getKey(url)
|
|
||||||
awemeList = tk.getUserInfo(key, mode="like", count=35)
|
|
||||||
print(awemeList)
|
|
||||||
|
|
||||||
def getLiveInfo():
|
|
||||||
live_link = "https://live.douyin.com/40768897856"
|
|
||||||
tk = TikTok()
|
|
||||||
|
|
||||||
url = tk.getShareLink(live_link)
|
|
||||||
key_type, key = tk.getKey(url)
|
|
||||||
live_json = tk.getLiveInfo(key)
|
|
||||||
print(live_json)
|
|
||||||
|
|
||||||
def getMixInfo():
|
|
||||||
mix_link = 'https://v.douyin.com/B3J63Le/'
|
|
||||||
tk = TikTok()
|
|
||||||
|
|
||||||
url = tk.getShareLink(mix_link)
|
|
||||||
key_type, key = tk.getKey(url)
|
|
||||||
awemeList = tk.getMixInfo(key, count=35)
|
|
||||||
print(len(awemeList))
|
|
||||||
|
|
||||||
def getUserAllMixInfo():
|
|
||||||
user_all_mix_link = 'https://v.douyin.com/B38oovu/'
|
|
||||||
tk = TikTok()
|
|
||||||
|
|
||||||
url = tk.getShareLink(user_all_mix_link)
|
|
||||||
key_type, key = tk.getKey(url)
|
|
||||||
mixIdNameDict = tk.getUserAllMixInfo(key, count=35)
|
|
||||||
print(mixIdNameDict)
|
|
||||||
|
|
||||||
def getMusicInfo():
|
|
||||||
music_link = 'https://v.douyin.com/S6YMNXs/'
|
|
||||||
tk = TikTok()
|
|
||||||
|
|
||||||
url = tk.getShareLink(music_link)
|
|
||||||
key_type, key = tk.getKey(url)
|
|
||||||
awemeList = tk.getMusicInfo(key,count=35)
|
|
||||||
print(len(awemeList))
|
|
||||||
|
|
||||||
def test():
|
|
||||||
utils=TikTokUtils.Utils()
|
|
||||||
user_all_mix_link = 'https://www.douyin.com/aweme/v1/web/aweme/post/?'+\
|
|
||||||
utils.getXbogus(url='device_platform=webapp&aid=6383&os_version=10&version_name=17.4.0&sec_user_id=MS4wLjABAAAA7G3S983y0b0m_7bcTSK7UzY-3hFJdHvMv-aVbbz0kIg&max_cursor=1626149119000&count=10')
|
|
||||||
headers1 = {
|
|
||||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36',
|
|
||||||
'referer': 'https://www.douyin.com/',
|
|
||||||
'Cookie': 'passport_csrf_token=f61602fc63757ae0e4fd9d6bdcee4810; ttwid=1|S3B70hT91P08fJIZJfOQETOCRyfySRp95cXKBpdd2KA|1677745253|be2b0a6c96f4400315e013b22e949756d3028d25434bd028f415b50f2fc9d98d; msToken=58OWeEpSkfMK4IAYaYYLEGdsYU8ZZONYG4Y1iUTayG1P6s0PxSoQsfhT5qn0vTem7kSyk6U-M4a0P0o2ttjY_N2TaID0AcPCQEuTcucnWEkKUQe7MABFmaP9WLGpnA==;' }
|
|
||||||
headers = {
|
|
||||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36',
|
|
||||||
'referer': 'https://www.douyin.com/',
|
|
||||||
'Cookie': 'ttwid=1|sGp2L-Krm46cXHcK7BsKghavVeVQIIOYtQInA1LV0-w|1676899557|3e483426230c481bd34f4d6529d6252372c154b75be7d4a2baec8edbfd0a742c; __ac_signature=_02B4Z6wo00f01CEKaogAAIDBqkHxaCCYIyghKm4AAGu9c3; s_v_web_id=verify_ledo1j1t_0NwhDQFJ_nLca_42o5_8tAA_T8CWm5E2M6LF; msToken=%s;odin_tt=324fb4ea4a89c0c05827e18a1ed9cf9bf8a17f7705fcc793fec935b637867e2a5a9b8168c885554d029919117a18ba69;' % utils.generate_random_str(
|
|
||||||
107)
|
|
||||||
}
|
|
||||||
import requests
|
|
||||||
|
|
||||||
res = requests.get(user_all_mix_link,headers=headers1)
|
|
||||||
print(res.text)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# test()
|
|
||||||
# getMusicInfo()
|
|
||||||
# getUserAllMixInfo()
|
|
||||||
# getMixInfo()
|
|
||||||
# getAwemeInfo()
|
|
||||||
# getUserInfo()
|
|
||||||
# getLiveInfo()
|
|
||||||
pass
|
|
||||||
################################# 测试命令 ######################################################
|
|
||||||
# 直播
|
|
||||||
# python TikTokCommand.py -l https://live.douyin.com/759547612580 -p /mnt/c/project/test0
|
|
||||||
# .\TikTokCommand.exe -l https://live.douyin.com/759547612580 -p .\test0
|
|
||||||
# 视频
|
|
||||||
# python TikTokCommand.py -l https://v.douyin.com/BugmVVD/ -p /mnt/c/project/test1
|
|
||||||
# .\TikTokCommand.exe -l https://v.douyin.com/BugmVVD/ -p .\test1
|
|
||||||
# 图集
|
|
||||||
# python TikTokCommand.py -l https://v.douyin.com/BugrFTN/ -p /mnt/c/project/test2
|
|
||||||
# .\TikTokCommand.exe -l https://v.douyin.com/BugrFTN/ -p .\test2
|
|
||||||
# 主页作品(视频)
|
|
||||||
# python TikTokCommand.py -l https://v.douyin.com/BupCppt/ -p /mnt/c/project/test3
|
|
||||||
# .\TikTokCommand.exe -l https://v.douyin.com/BupCppt/ -p .\test3
|
|
||||||
# 主页作品(视频与图集混合)
|
|
||||||
# python TikTokCommand.py -l https://v.douyin.com/B72pdU5/ -p /mnt/c/project/test4
|
|
||||||
# .\TikTokCommand.exe -l https://v.douyin.com/B72pdU5/ -p .\test4
|
|
||||||
# 主页喜欢(视频)
|
|
||||||
# python TikTokCommand.py -l https://v.douyin.com/B72QgDw/ -p /mnt/c/project/test5 -M like
|
|
||||||
# .\TikTokCommand.exe -l https://v.douyin.com/B72QgDw/ -p .\test5 -M like
|
|
||||||
# 单个合集
|
|
||||||
# python TikTokCommand.py -l https://v.douyin.com/B3J63Le/ -p /mnt/c/project/test6
|
|
||||||
# .\TikTokCommand.exe -l https://v.douyin.com/B3J63Le/ -p .\test6
|
|
||||||
# 用户主页下所有合集
|
|
||||||
# python TikTokCommand.py -l https://v.douyin.com/B38oovu/ -p /mnt/c/project/test7 -M mix
|
|
||||||
# .\TikTokCommand.exe -l https://v.douyin.com/B38oovu/ -p .\test7 -M mix
|
|
||||||
# 音乐集合
|
|
||||||
# python TikTokCommand.py -l https://v.douyin.com/S6YMNXs/ -p /mnt/c/project/test8
|
|
||||||
# .\TikTokCommand.exe -l https://v.douyin.com/S6YMNXs/ -p .\test8
|
|
||||||
#################################################################################################
|
|
@ -1,87 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- encoding: utf-8 -*-
|
|
||||||
|
|
||||||
'''
|
|
||||||
@Description:TikTok.py
|
|
||||||
@Date :2023/01/27 19:36:18
|
|
||||||
@Author :imgyh
|
|
||||||
@version :1.0
|
|
||||||
@Github :https://github.com/imgyh
|
|
||||||
@Mail :admin@imgyh.com
|
|
||||||
-------------------------------------------------
|
|
||||||
Change Log :
|
|
||||||
-------------------------------------------------
|
|
||||||
'''
|
|
||||||
|
|
||||||
import random
|
|
||||||
import re
|
|
||||||
import json
|
|
||||||
import requests
|
|
||||||
from TikTokUrls import Urls
|
|
||||||
|
|
||||||
|
|
||||||
class Utils(object):
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def generate_random_str(self, randomlength=16):
|
|
||||||
"""
|
|
||||||
根据传入长度产生随机字符串
|
|
||||||
"""
|
|
||||||
random_str = ''
|
|
||||||
base_str = 'ABCDEFGHIGKLMNOPQRSTUVWXYZabcdefghigklmnopqrstuvwxyz0123456789='
|
|
||||||
length = len(base_str) - 1
|
|
||||||
for _ in range(randomlength):
|
|
||||||
random_str += base_str[random.randint(0, length)]
|
|
||||||
return random_str
|
|
||||||
|
|
||||||
def replaceStr(self, filenamestr: str):
|
|
||||||
"""
|
|
||||||
替换非法字符,缩短字符长度,使其能成为文件名
|
|
||||||
"""
|
|
||||||
# 匹配 汉字 字母 数字 空格
|
|
||||||
match = "([0-9A-Za-z\u4e00-\u9fa5 -._]+)"
|
|
||||||
|
|
||||||
result = re.findall(match, filenamestr)
|
|
||||||
|
|
||||||
result = "".join(result).strip()
|
|
||||||
if len(result) > 80:
|
|
||||||
result = result[:80]
|
|
||||||
# 去除前后空格
|
|
||||||
return result
|
|
||||||
|
|
||||||
def getXbogus(self, url, headers=None):
|
|
||||||
urls = Urls()
|
|
||||||
try:
|
|
||||||
response = json.loads(requests.post(
|
|
||||||
url=urls.GET_XB_PATH, data={"param": url}, headers=headers).text)
|
|
||||||
params = response["param"]
|
|
||||||
xb = response["X-Bogus"]
|
|
||||||
except Exception as e:
|
|
||||||
print('[ 错误 ]:X-Bogus接口异常, 可能是访问流量高, 接口限流请稍等几分钟再次尝试')
|
|
||||||
return
|
|
||||||
|
|
||||||
return params # , xb
|
|
||||||
|
|
||||||
def str2bool(self, v):
|
|
||||||
if isinstance(v, bool):
|
|
||||||
return v
|
|
||||||
if v.lower() in ('yes', 'true', 't', 'y', '1'):
|
|
||||||
return True
|
|
||||||
elif v.lower() in ('no', 'false', 'f', 'n', '0'):
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
# https://www.52pojie.cn/thread-1589242-1-1.html
|
|
||||||
def getttwid(self):
|
|
||||||
url = 'https://ttwid.bytedance.com/ttwid/union/register/'
|
|
||||||
data = '{"region":"cn","aid":1768,"needFid":false,"service":"www.ixigua.com","migrate_info":{"ticket":"","source":"node"},"cbUrlProtocol":"https","union":true}'
|
|
||||||
res = requests.post(url=url, data=data)
|
|
||||||
|
|
||||||
for i,j in res.cookies.items():
|
|
||||||
return j
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
pass
|
|
57
TikTokWeb.py
@ -1,57 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- encoding: utf-8 -*-
|
|
||||||
|
|
||||||
'''
|
|
||||||
@Description:TikTok.py
|
|
||||||
@Date :2023/01/27 19:36:18
|
|
||||||
@Author :imgyh
|
|
||||||
@version :1.0
|
|
||||||
@Github :https://github.com/imgyh
|
|
||||||
@Mail :admin@imgyh.com
|
|
||||||
-------------------------------------------------
|
|
||||||
Change Log :
|
|
||||||
-------------------------------------------------
|
|
||||||
'''
|
|
||||||
|
|
||||||
from flask import *
|
|
||||||
from TikTok import TikTok
|
|
||||||
|
|
||||||
|
|
||||||
def work(share_link):
|
|
||||||
tk = TikTok()
|
|
||||||
|
|
||||||
url = tk.getShareLink(share_link)
|
|
||||||
key_type, key = tk.getKey(url)
|
|
||||||
datanew, dataraw = tk.getAwemeInfo(key)
|
|
||||||
return datanew
|
|
||||||
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
|
||||||
# 设置编码
|
|
||||||
app.config['JSON_AS_ASCII'] = False
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/douyin", methods=["POST"])
|
|
||||||
def douyin():
|
|
||||||
usefuldict = {}
|
|
||||||
if request.method == "POST":
|
|
||||||
result = request.form
|
|
||||||
else:
|
|
||||||
usefuldict["status_code"] = 500
|
|
||||||
return jsonify(usefuldict)
|
|
||||||
|
|
||||||
try:
|
|
||||||
usefuldict = work(result["share_link"])
|
|
||||||
usefuldict["status_code"] = 200
|
|
||||||
except Exception as error:
|
|
||||||
usefuldict["status_code"] = 500
|
|
||||||
return jsonify(usefuldict)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/", methods=["GET"])
|
|
||||||
def index():
|
|
||||||
return render_template("index.html")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
app.run(debug=False, host="0.0.0.0", port=5000)
|
|
159
WebApi.py
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
'''
|
||||||
|
@FileName : WebApi.py
|
||||||
|
@Project : apiproxy
|
||||||
|
@Description:
|
||||||
|
@Author : imgyh
|
||||||
|
@Mail : admin@imgyh.com
|
||||||
|
@Github : https://github.com/imgyh
|
||||||
|
@Site : https://www.imgyh.com
|
||||||
|
@Date : 2023/5/12 18:52
|
||||||
|
@Version : v1.0
|
||||||
|
@ChangeLog
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
------------------------------------------------
|
||||||
|
'''
|
||||||
|
|
||||||
|
from flask import *
|
||||||
|
from apiproxy.douyin.douyinapi import DouyinApi
|
||||||
|
from apiproxy.douyin import douyin_headers
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
|
||||||
|
def douyinwork(share_link, max_cursor, mode, cookie):
|
||||||
|
dy = DouyinApi()
|
||||||
|
|
||||||
|
if cookie is not None and cookie != "":
|
||||||
|
douyin_headers["Cookie"] = cookie
|
||||||
|
|
||||||
|
url = dy.getShareLink(share_link)
|
||||||
|
key_type, key = dy.getKey(url)
|
||||||
|
|
||||||
|
data = None
|
||||||
|
rawdata = None
|
||||||
|
cursor = None
|
||||||
|
has_more = None
|
||||||
|
if key_type == "user":
|
||||||
|
if mode == 'post' or mode == 'like':
|
||||||
|
data, rawdata, cursor, has_more = dy.getUserInfoApi(sec_uid=key, mode=mode, count=35,
|
||||||
|
max_cursor=max_cursor)
|
||||||
|
elif mode == 'mix':
|
||||||
|
data, rawdata, cursor, has_more = dy.getUserAllMixInfoApi(sec_uid=key, count=35, cursor=max_cursor)
|
||||||
|
elif mode == 'detail':
|
||||||
|
rawdata = dy.getUserDetailInfoApi(sec_uid=key)
|
||||||
|
data = rawdata
|
||||||
|
elif key_type == "mix":
|
||||||
|
data, rawdata, cursor, has_more = dy.getMixInfoApi(mix_id=key, count=35, cursor=max_cursor)
|
||||||
|
elif key_type == "music":
|
||||||
|
data, rawdata, cursor, has_more = dy.getMusicInfoApi(music_id=key, count=35, cursor=max_cursor)
|
||||||
|
elif key_type == "aweme":
|
||||||
|
data, rawdata = dy.getAwemeInfoApi(aweme_id=key)
|
||||||
|
elif key_type == "live":
|
||||||
|
data, rawdata = dy.getLiveInfoApi(web_rid=key)
|
||||||
|
|
||||||
|
datadict = {}
|
||||||
|
|
||||||
|
|
||||||
|
datadict["data"] = data
|
||||||
|
datadict["rawdata"] = rawdata
|
||||||
|
datadict["cursor"] = cursor
|
||||||
|
datadict["has_more"] = has_more
|
||||||
|
|
||||||
|
return datadict
|
||||||
|
|
||||||
|
|
||||||
|
def deal(mode=None):
|
||||||
|
usefuldict = {}
|
||||||
|
if request.headers.get("content_type") == "application/json":
|
||||||
|
result = request.get_json(force=True)
|
||||||
|
else:
|
||||||
|
result = request.form
|
||||||
|
|
||||||
|
share_link = None
|
||||||
|
cursor = 0
|
||||||
|
cookie = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
share_link = result["share_link"]
|
||||||
|
cursor = result["cursor"]
|
||||||
|
cookie = result["cookie"]
|
||||||
|
except Exception as e:
|
||||||
|
usefuldict["status_code"] = 500
|
||||||
|
|
||||||
|
try:
|
||||||
|
if share_link is not None and share_link != "":
|
||||||
|
usefuldict = douyinwork(share_link, cursor, mode, cookie)
|
||||||
|
usefuldict["status_code"] = 200
|
||||||
|
except Exception as e:
|
||||||
|
usefuldict["status_code"] = 500
|
||||||
|
return jsonify(usefuldict)
|
||||||
|
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
# 设置编码
|
||||||
|
app.config['JSON_AS_ASCII'] = False
|
||||||
|
|
||||||
|
|
||||||
|
def argument():
|
||||||
|
parser = argparse.ArgumentParser(description='抖音去水印工具 使用帮助')
|
||||||
|
parser.add_argument("--port", "-p", help="Web端口",
|
||||||
|
type=int, required=False, default=5000)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/douyin/music", methods=["POST"])
|
||||||
|
def douyinMusic():
|
||||||
|
return deal()
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/douyin/mix", methods=["POST"])
|
||||||
|
def douyinMix():
|
||||||
|
return deal()
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/douyin/user/mix", methods=["POST"])
|
||||||
|
def douyinUserMix():
|
||||||
|
return deal(mode="mix")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/douyin/user/like", methods=["POST"])
|
||||||
|
def douyinUserLike():
|
||||||
|
return deal(mode="like")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/douyin/user/post", methods=["POST"])
|
||||||
|
def douyinUserPost():
|
||||||
|
return deal(mode="post")
|
||||||
|
|
||||||
|
@app.route("/douyin/user/detail", methods=["POST"])
|
||||||
|
def douyinUserDetail():
|
||||||
|
return deal(mode="detail")
|
||||||
|
|
||||||
|
@app.route("/douyin/aweme", methods=["POST"])
|
||||||
|
def douyinAweme():
|
||||||
|
return deal()
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/douyin/live", methods=["POST"])
|
||||||
|
def douyinLive():
|
||||||
|
return deal()
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/douyin", methods=["POST"])
|
||||||
|
def douyin():
|
||||||
|
return deal()
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/", methods=["GET"])
|
||||||
|
def index():
|
||||||
|
return render_template("index.html")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
args = argument()
|
||||||
|
app.run(debug=False, host="0.0.0.0", port=args.port)
|
20
apiproxy/__init__.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
'''
|
||||||
|
@FileName : __init__.py
|
||||||
|
@Project : apiproxy
|
||||||
|
@Description:
|
||||||
|
@Author : imgyh
|
||||||
|
@Mail : admin@imgyh.com
|
||||||
|
@Github : https://github.com/imgyh
|
||||||
|
@Site : https://www.imgyh.com
|
||||||
|
@Date : 2023/5/12 14:32
|
||||||
|
@Version : v1.0
|
||||||
|
@ChangeLog
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
------------------------------------------------
|
||||||
|
'''
|
||||||
|
|
||||||
|
ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'
|
21
apiproxy/common/__init__.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
'''
|
||||||
|
@FileName : __init__.py.py
|
||||||
|
@Project : apiproxy
|
||||||
|
@Description:
|
||||||
|
@Author : imgyh
|
||||||
|
@Mail : admin@imgyh.com
|
||||||
|
@Github : https://github.com/imgyh
|
||||||
|
@Site : https://www.imgyh.com
|
||||||
|
@Date : 2023/5/12 16:10
|
||||||
|
@Version : v1.0
|
||||||
|
@ChangeLog
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
------------------------------------------------
|
||||||
|
'''
|
||||||
|
from .utils import Utils
|
||||||
|
|
||||||
|
utils = Utils()
|
201
apiproxy/common/utils.py
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
'''
|
||||||
|
@FileName : utils.py
|
||||||
|
@Project : apiproxy
|
||||||
|
@Description:
|
||||||
|
@Author : imgyh
|
||||||
|
@Mail : admin@imgyh.com
|
||||||
|
@Github : https://github.com/imgyh
|
||||||
|
@Site : https://www.imgyh.com
|
||||||
|
@Date : 2023/5/12 15:18
|
||||||
|
@Version : v1.0
|
||||||
|
@ChangeLog
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
------------------------------------------------
|
||||||
|
'''
|
||||||
|
|
||||||
|
import random
|
||||||
|
import requests
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import hashlib
|
||||||
|
import base64
|
||||||
|
import time
|
||||||
|
|
||||||
|
import apiproxy
|
||||||
|
|
||||||
|
|
||||||
|
class Utils(object):
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def replaceStr(self, filenamestr: str):
|
||||||
|
"""
|
||||||
|
替换非法字符,缩短字符长度,使其能成为文件名
|
||||||
|
"""
|
||||||
|
# 匹配 汉字 字母 数字 空格
|
||||||
|
match = "([0-9A-Za-z\u4e00-\u9fa5]+)"
|
||||||
|
|
||||||
|
result = re.findall(match, filenamestr)
|
||||||
|
|
||||||
|
result = "".join(result).strip()
|
||||||
|
if len(result) > 20:
|
||||||
|
result = result[:20]
|
||||||
|
# 去除前后空格
|
||||||
|
return result
|
||||||
|
|
||||||
|
def resource_path(self, relative_path):
|
||||||
|
if getattr(sys, 'frozen', False): # 是否Bundle Resource
|
||||||
|
base_path = sys._MEIPASS
|
||||||
|
else:
|
||||||
|
base_path = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
return os.path.join(base_path, relative_path)
|
||||||
|
|
||||||
|
def str2bool(self, v):
|
||||||
|
if isinstance(v, bool):
|
||||||
|
return v
|
||||||
|
if v.lower() in ('yes', 'true', 't', 'y', '1'):
|
||||||
|
return True
|
||||||
|
elif v.lower() in ('no', 'false', 'f', 'n', '0'):
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def generate_random_str(self, randomlength=16):
|
||||||
|
"""
|
||||||
|
根据传入长度产生随机字符串
|
||||||
|
"""
|
||||||
|
random_str = ''
|
||||||
|
base_str = 'ABCDEFGHIGKLMNOPQRSTUVWXYZabcdefghigklmnopqrstuvwxyz0123456789='
|
||||||
|
length = len(base_str) - 1
|
||||||
|
for _ in range(randomlength):
|
||||||
|
random_str += base_str[random.randint(0, length)]
|
||||||
|
return random_str
|
||||||
|
|
||||||
|
# https://www.52pojie.cn/thread-1589242-1-1.html
|
||||||
|
def getttwid(self):
|
||||||
|
url = 'https://ttwid.bytedance.com/ttwid/union/register/'
|
||||||
|
data = '{"region":"cn","aid":1768,"needFid":false,"service":"www.ixigua.com","migrate_info":{"ticket":"","source":"node"},"cbUrlProtocol":"https","union":true}'
|
||||||
|
res = requests.post(url=url, data=data)
|
||||||
|
|
||||||
|
for i, j in res.cookies.items():
|
||||||
|
return j
|
||||||
|
|
||||||
|
def getXbogus(self, payload, form='', ua=apiproxy.ua):
|
||||||
|
xbogus = self.get_xbogus(payload, ua, form)
|
||||||
|
params = payload + "&X-Bogus=" + xbogus
|
||||||
|
return params
|
||||||
|
|
||||||
|
def get_xbogus(self, payload, ua, form):
|
||||||
|
short_str = "Dkdpgh4ZKsQB80/Mfvw36XI1R25-WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe="
|
||||||
|
arr2 = self.get_arr2(payload, ua, form)
|
||||||
|
|
||||||
|
garbled_string = self.get_garbled_string(arr2)
|
||||||
|
|
||||||
|
xbogus = ""
|
||||||
|
|
||||||
|
for i in range(0, 21, 3):
|
||||||
|
char_code_num0 = garbled_string[i]
|
||||||
|
char_code_num1 = garbled_string[i + 1]
|
||||||
|
char_code_num2 = garbled_string[i + 2]
|
||||||
|
base_num = char_code_num2 | char_code_num1 << 8 | char_code_num0 << 16
|
||||||
|
str1 = short_str[(base_num & 16515072) >> 18]
|
||||||
|
str2 = short_str[(base_num & 258048) >> 12]
|
||||||
|
str3 = short_str[(base_num & 4032) >> 6]
|
||||||
|
str4 = short_str[base_num & 63]
|
||||||
|
xbogus += str1 + str2 + str3 + str4
|
||||||
|
|
||||||
|
return xbogus
|
||||||
|
|
||||||
|
def get_garbled_string(self, arr2):
|
||||||
|
p = [
|
||||||
|
arr2[0], arr2[10], arr2[1], arr2[11], arr2[2], arr2[12], arr2[3], arr2[13], arr2[4], arr2[14],
|
||||||
|
arr2[5], arr2[15], arr2[6], arr2[16], arr2[7], arr2[17], arr2[8], arr2[18], arr2[9]
|
||||||
|
]
|
||||||
|
|
||||||
|
char_array = [chr(i) for i in p]
|
||||||
|
f = []
|
||||||
|
f.extend([2, 255])
|
||||||
|
tmp = ['ÿ']
|
||||||
|
bytes_ = self._0x30492c(tmp, "".join(char_array))
|
||||||
|
|
||||||
|
for i in range(len(bytes_)):
|
||||||
|
f.append(bytes_[i])
|
||||||
|
|
||||||
|
return f
|
||||||
|
|
||||||
|
def get_arr2(self, payload, ua, form):
|
||||||
|
salt_payload_bytes = hashlib.md5(hashlib.md5(payload.encode()).digest()).digest()
|
||||||
|
salt_payload = [byte for byte in salt_payload_bytes]
|
||||||
|
|
||||||
|
salt_form_bytes = hashlib.md5(hashlib.md5(form.encode()).digest()).digest()
|
||||||
|
salt_form = [byte for byte in salt_form_bytes]
|
||||||
|
|
||||||
|
ua_key = ['\u0000', '\u0001', '\u000e']
|
||||||
|
salt_ua_bytes = hashlib.md5(base64.b64encode(self._0x30492c(ua_key, ua))).digest()
|
||||||
|
salt_ua = [byte for byte in salt_ua_bytes]
|
||||||
|
|
||||||
|
timestamp = int(time.time())
|
||||||
|
canvas = 1489154074
|
||||||
|
|
||||||
|
arr1 = [
|
||||||
|
64, # 固定
|
||||||
|
0, # 固定
|
||||||
|
1, # 固定
|
||||||
|
14, # 固定 这个还要再看一下,14,12,0都出现过
|
||||||
|
salt_payload[14], # payload 相关
|
||||||
|
salt_payload[15],
|
||||||
|
salt_form[14], # form 相关
|
||||||
|
salt_form[15],
|
||||||
|
salt_ua[14], # ua 相关
|
||||||
|
salt_ua[15],
|
||||||
|
(timestamp >> 24) & 255,
|
||||||
|
(timestamp >> 16) & 255,
|
||||||
|
(timestamp >> 8) & 255,
|
||||||
|
(timestamp >> 0) & 255,
|
||||||
|
(canvas >> 24) & 255,
|
||||||
|
(canvas >> 16) & 255,
|
||||||
|
(canvas >> 8) & 255,
|
||||||
|
(canvas >> 0) & 255,
|
||||||
|
64, # 校验位
|
||||||
|
]
|
||||||
|
|
||||||
|
for i in range(1, len(arr1) - 1):
|
||||||
|
arr1[18] ^= arr1[i]
|
||||||
|
|
||||||
|
arr2 = [arr1[0], arr1[2], arr1[4], arr1[6], arr1[8], arr1[10], arr1[12], arr1[14], arr1[16], arr1[18], arr1[1],
|
||||||
|
arr1[3], arr1[5], arr1[7], arr1[9], arr1[11], arr1[13], arr1[15], arr1[17]]
|
||||||
|
|
||||||
|
return arr2
|
||||||
|
|
||||||
|
def _0x30492c(self, a, b):
|
||||||
|
d = [i for i in range(256)]
|
||||||
|
c = 0
|
||||||
|
result = bytearray(len(b))
|
||||||
|
|
||||||
|
for i in range(256):
|
||||||
|
c = (c + d[i] + ord(a[i % len(a)])) % 256
|
||||||
|
e = d[i]
|
||||||
|
d[i] = d[c]
|
||||||
|
d[c] = e
|
||||||
|
|
||||||
|
t = 0
|
||||||
|
c = 0
|
||||||
|
|
||||||
|
for i in range(len(b)):
|
||||||
|
t = (t + 1) % 256
|
||||||
|
c = (c + d[t]) % 256
|
||||||
|
e = d[t]
|
||||||
|
d[t] = d[c]
|
||||||
|
d[c] = e
|
||||||
|
result[i] = ord(b[i]) ^ d[(d[t] + d[c]) % 256]
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
pass
|
27
apiproxy/douyin/__init__.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
'''
|
||||||
|
@FileName : __init__.py.py
|
||||||
|
@Project : apiproxy
|
||||||
|
@Description:
|
||||||
|
@Author : imgyh
|
||||||
|
@Mail : admin@imgyh.com
|
||||||
|
@Github : https://github.com/imgyh
|
||||||
|
@Site : https://www.imgyh.com
|
||||||
|
@Date : 2023/5/12 14:44
|
||||||
|
@Version : v1.0
|
||||||
|
@ChangeLog
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
------------------------------------------------
|
||||||
|
'''
|
||||||
|
import apiproxy
|
||||||
|
from apiproxy.common import utils
|
||||||
|
|
||||||
|
douyin_headers = {
|
||||||
|
'User-Agent': apiproxy.ua,
|
||||||
|
'referer': 'https://www.douyin.com/',
|
||||||
|
'accept-encoding': None,
|
||||||
|
'Cookie': f"msToken={utils.generate_random_str(107)}; ttwid={utils.getttwid()}; odin_tt=324fb4ea4a89c0c05827e18a1ed9cf9bf8a17f7705fcc793fec935b637867e2a5a9b8168c885554d029919117a18ba69; passport_csrf_token=f61602fc63757ae0e4fd9d6bdcee4810;"
|
||||||
|
}
|
172
apiproxy/douyin/database.py
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
'''
|
||||||
|
@FileName : database.py
|
||||||
|
@Project : apiproxy
|
||||||
|
@Description:
|
||||||
|
@Author : imgyh
|
||||||
|
@Mail : admin@imgyh.com
|
||||||
|
@Github : https://github.com/imgyh
|
||||||
|
@Site : https://www.imgyh.com
|
||||||
|
@Date : 2023/5/12 15:15
|
||||||
|
@Version : v1.0
|
||||||
|
@ChangeLog
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
------------------------------------------------
|
||||||
|
'''
|
||||||
|
|
||||||
|
import sqlite3
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
class DataBase(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.conn = sqlite3.connect('data.db')
|
||||||
|
self.cursor = self.conn.cursor()
|
||||||
|
self.create_user_post_table()
|
||||||
|
self.create_user_like_table()
|
||||||
|
self.create_mix_table()
|
||||||
|
self.create_music_table()
|
||||||
|
|
||||||
|
def create_user_post_table(self):
|
||||||
|
sql = """CREATE TABLE if not exists t_user_post (
|
||||||
|
id integer primary key autoincrement,
|
||||||
|
sec_uid varchar(200),
|
||||||
|
aweme_id integer unique,
|
||||||
|
rawdata json
|
||||||
|
);"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.cursor.execute(sql)
|
||||||
|
self.conn.commit()
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_user_post(self, sec_uid: str, aweme_id: int):
|
||||||
|
sql = """select id, sec_uid, aweme_id, rawdata from t_user_post where sec_uid=? and aweme_id=?;"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.cursor.execute(sql, (sec_uid, aweme_id))
|
||||||
|
self.conn.commit()
|
||||||
|
res = self.cursor.fetchone()
|
||||||
|
return res
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def insert_user_post(self, sec_uid: str, aweme_id: int, data: dict):
|
||||||
|
insertsql = """insert into t_user_post (sec_uid, aweme_id, rawdata) values(?,?,?);"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.cursor.execute(insertsql, (sec_uid, aweme_id, json.dumps(data)))
|
||||||
|
self.conn.commit()
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def create_user_like_table(self):
|
||||||
|
sql = """CREATE TABLE if not exists t_user_like (
|
||||||
|
id integer primary key autoincrement,
|
||||||
|
sec_uid varchar(200),
|
||||||
|
aweme_id integer unique,
|
||||||
|
rawdata json
|
||||||
|
);"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.cursor.execute(sql)
|
||||||
|
self.conn.commit()
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_user_like(self, sec_uid: str, aweme_id: int):
|
||||||
|
sql = """select id, sec_uid, aweme_id, rawdata from t_user_like where sec_uid=? and aweme_id=?;"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.cursor.execute(sql, (sec_uid, aweme_id))
|
||||||
|
self.conn.commit()
|
||||||
|
res = self.cursor.fetchone()
|
||||||
|
return res
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def insert_user_like(self, sec_uid: str, aweme_id: int, data: dict):
|
||||||
|
insertsql = """insert into t_user_like (sec_uid, aweme_id, rawdata) values(?,?,?);"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.cursor.execute(insertsql, (sec_uid, aweme_id, json.dumps(data)))
|
||||||
|
self.conn.commit()
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def create_mix_table(self):
|
||||||
|
sql = """CREATE TABLE if not exists t_mix (
|
||||||
|
id integer primary key autoincrement,
|
||||||
|
sec_uid varchar(200),
|
||||||
|
mix_id varchar(200),
|
||||||
|
aweme_id integer,
|
||||||
|
rawdata json
|
||||||
|
);"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.cursor.execute(sql)
|
||||||
|
self.conn.commit()
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_mix(self, sec_uid: str, mix_id: str, aweme_id: int):
|
||||||
|
sql = """select id, sec_uid, mix_id, aweme_id, rawdata from t_mix where sec_uid=? and mix_id=? and aweme_id=?;"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.cursor.execute(sql, (sec_uid, mix_id, aweme_id))
|
||||||
|
self.conn.commit()
|
||||||
|
res = self.cursor.fetchone()
|
||||||
|
return res
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def insert_mix(self, sec_uid: str, mix_id: str, aweme_id: int, data: dict):
|
||||||
|
insertsql = """insert into t_mix (sec_uid, mix_id, aweme_id, rawdata) values(?,?,?,?);"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.cursor.execute(insertsql, (sec_uid, mix_id, aweme_id, json.dumps(data)))
|
||||||
|
self.conn.commit()
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def create_music_table(self):
|
||||||
|
sql = """CREATE TABLE if not exists t_music (
|
||||||
|
id integer primary key autoincrement,
|
||||||
|
music_id varchar(200),
|
||||||
|
aweme_id integer unique,
|
||||||
|
rawdata json
|
||||||
|
);"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.cursor.execute(sql)
|
||||||
|
self.conn.commit()
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_music(self, music_id: str, aweme_id: int):
|
||||||
|
sql = """select id, music_id, aweme_id, rawdata from t_music where music_id=? and aweme_id=?;"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.cursor.execute(sql, (music_id, aweme_id))
|
||||||
|
self.conn.commit()
|
||||||
|
res = self.cursor.fetchone()
|
||||||
|
return res
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def insert_music(self, music_id: str, aweme_id: int, data: dict):
|
||||||
|
insertsql = """insert into t_music (music_id, aweme_id, rawdata) values(?,?,?);"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.cursor.execute(insertsql, (music_id, aweme_id, json.dumps(data)))
|
||||||
|
self.conn.commit()
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
pass
|
688
apiproxy/douyin/douyin.py
Normal file
@ -0,0 +1,688 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
'''
|
||||||
|
@FileName : douyin.py
|
||||||
|
@Project : apiproxy
|
||||||
|
@Description:
|
||||||
|
@Author : imgyh
|
||||||
|
@Mail : admin@imgyh.com
|
||||||
|
@Github : https://github.com/imgyh
|
||||||
|
@Site : https://www.imgyh.com
|
||||||
|
@Date : 2023/5/12 14:52
|
||||||
|
@Version : v1.0
|
||||||
|
@ChangeLog
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
------------------------------------------------
|
||||||
|
'''
|
||||||
|
|
||||||
|
import re
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import copy
|
||||||
|
|
||||||
|
from apiproxy.douyin import douyin_headers
|
||||||
|
from apiproxy.douyin.urls import Urls
|
||||||
|
from apiproxy.douyin.result import Result
|
||||||
|
from apiproxy.douyin.database import DataBase
|
||||||
|
from apiproxy.common import utils
|
||||||
|
|
||||||
|
|
||||||
|
class Douyin(object):
|
||||||
|
|
||||||
|
def __init__(self, database=False):
|
||||||
|
self.urls = Urls()
|
||||||
|
self.result = Result()
|
||||||
|
self.database = database
|
||||||
|
if database:
|
||||||
|
self.db = DataBase()
|
||||||
|
# 用于设置重复请求某个接口的最大时间
|
||||||
|
self.timeout = 10
|
||||||
|
|
||||||
|
# 从分享链接中提取网址
|
||||||
|
def getShareLink(self, string):
|
||||||
|
# findall() 查找匹配正则表达式的字符串
|
||||||
|
return re.findall('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', string)[0]
|
||||||
|
|
||||||
|
# 得到 作品id 或者 用户id
|
||||||
|
# 传入 url 支持 https://www.iesdouyin.com 与 https://v.douyin.com
|
||||||
|
def getKey(self, url):
|
||||||
|
key = None
|
||||||
|
key_type = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = requests.get(url=url, headers=douyin_headers)
|
||||||
|
except Exception as e:
|
||||||
|
print('[ 错误 ]:输入链接有误!\r')
|
||||||
|
return key_type, key
|
||||||
|
|
||||||
|
# 抖音把图集更新为note
|
||||||
|
# 作品 第一步解析出来的链接是share/video/{aweme_id}
|
||||||
|
# https://www.iesdouyin.com/share/video/7037827546599263488/?region=CN&mid=6939809470193126152&u_code=j8a5173b&did=MS4wLjABAAAA1DICF9-A9M_CiGqAJZdsnig5TInVeIyPdc2QQdGrq58xUgD2w6BqCHovtqdIDs2i&iid=MS4wLjABAAAAomGWi4n2T0H9Ab9x96cUZoJXaILk4qXOJlJMZFiK6b_aJbuHkjN_f0mBzfy91DX1&with_sec_did=1&titleType=title&schema_type=37&from_ssr=1&utm_source=copy&utm_campaign=client_share&utm_medium=android&app=aweme
|
||||||
|
# 用户 第一步解析出来的链接是share/user/{sec_uid}
|
||||||
|
# https://www.iesdouyin.com/share/user/MS4wLjABAAAA06y3Ctu8QmuefqvUSU7vr0c_ZQnCqB0eaglgkelLTek?did=MS4wLjABAAAA1DICF9-A9M_CiGqAJZdsnig5TInVeIyPdc2QQdGrq58xUgD2w6BqCHovtqdIDs2i&iid=MS4wLjABAAAAomGWi4n2T0H9Ab9x96cUZoJXaILk4qXOJlJMZFiK6b_aJbuHkjN_f0mBzfy91DX1&with_sec_did=1&sec_uid=MS4wLjABAAAA06y3Ctu8QmuefqvUSU7vr0c_ZQnCqB0eaglgkelLTek&from_ssr=1&u_code=j8a5173b×tamp=1674540164&ecom_share_track_params=%7B%22is_ec_shopping%22%3A%221%22%2C%22secuid%22%3A%22MS4wLjABAAAA-jD2lukp--I21BF8VQsmYUqJDbj3FmU-kGQTHl2y1Cw%22%2C%22enter_from%22%3A%22others_homepage%22%2C%22share_previous_page%22%3A%22others_homepage%22%7D&utm_source=copy&utm_campaign=client_share&utm_medium=android&app=aweme
|
||||||
|
# 合集
|
||||||
|
# https://www.douyin.com/collection/7093490319085307918
|
||||||
|
urlstr = str(r.request.path_url)
|
||||||
|
|
||||||
|
if "/user/" in urlstr:
|
||||||
|
# 获取用户 sec_uid
|
||||||
|
if '?' in r.request.path_url:
|
||||||
|
for one in re.finditer(r'user\/([\d\D]*)([?])', str(r.request.path_url)):
|
||||||
|
key = one.group(1)
|
||||||
|
else:
|
||||||
|
for one in re.finditer(r'user\/([\d\D]*)', str(r.request.path_url)):
|
||||||
|
key = one.group(1)
|
||||||
|
key_type = "user"
|
||||||
|
elif "/video/" in urlstr:
|
||||||
|
# 获取作品 aweme_id
|
||||||
|
key = re.findall('video/(\d+)?', urlstr)[0]
|
||||||
|
key_type = "aweme"
|
||||||
|
elif "/note/" in urlstr:
|
||||||
|
# 获取note aweme_id
|
||||||
|
key = re.findall('note/(\d+)?', urlstr)[0]
|
||||||
|
key_type = "aweme"
|
||||||
|
elif "/mix/detail/" in urlstr:
|
||||||
|
# 获取合集 id
|
||||||
|
key = re.findall('/mix/detail/(\d+)?', urlstr)[0]
|
||||||
|
key_type = "mix"
|
||||||
|
elif "/collection/" in urlstr:
|
||||||
|
# 获取合集 id
|
||||||
|
key = re.findall('/collection/(\d+)?', urlstr)[0]
|
||||||
|
key_type = "mix"
|
||||||
|
elif "/music/" in urlstr:
|
||||||
|
# 获取原声 id
|
||||||
|
key = re.findall('music/(\d+)?', urlstr)[0]
|
||||||
|
key_type = "music"
|
||||||
|
elif "/webcast/reflow/" in urlstr:
|
||||||
|
key1 = re.findall('reflow/(\d+)?', urlstr)[0]
|
||||||
|
url = self.urls.LIVE2 + utils.getXbogus(
|
||||||
|
f'live_id=1&room_id={key1}&app_id=1128')
|
||||||
|
res = requests.get(url, headers=douyin_headers)
|
||||||
|
resjson = json.loads(res.text)
|
||||||
|
key = resjson['data']['room']['owner']['web_rid']
|
||||||
|
key_type = "live"
|
||||||
|
elif "live.douyin.com" in r.url:
|
||||||
|
key = r.url.replace('https://live.douyin.com/', '')
|
||||||
|
key_type = "live"
|
||||||
|
|
||||||
|
if key is None or key_type is None:
|
||||||
|
print('[ 错误 ]:输入链接有误!无法获取 id\r')
|
||||||
|
return key_type, key
|
||||||
|
|
||||||
|
return key_type, key
|
||||||
|
|
||||||
|
# 传入 aweme_id
|
||||||
|
# 返回 数据 字典
|
||||||
|
def getAwemeInfo(self, aweme_id):
|
||||||
|
print('[ 提示 ]:正在请求的作品 id = %s\r' % aweme_id)
|
||||||
|
if aweme_id is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
start = time.time() # 开始时间
|
||||||
|
while True:
|
||||||
|
# 接口不稳定, 有时服务器不返回数据, 需要重新获取
|
||||||
|
try:
|
||||||
|
# 单作品接口返回 'aweme_detail'
|
||||||
|
# 主页作品接口返回 'aweme_list'->['aweme_detail']
|
||||||
|
jx_url = self.urls.POST_DETAIL + utils.getXbogus(
|
||||||
|
f'aweme_id={aweme_id}&device_platform=webapp&aid=6383')
|
||||||
|
|
||||||
|
raw = requests.get(url=jx_url, headers=douyin_headers).text
|
||||||
|
datadict = json.loads(raw)
|
||||||
|
if datadict is not None and datadict["status_code"] == 0:
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
end = time.time() # 结束时间
|
||||||
|
if end - start > self.timeout:
|
||||||
|
print("[ 提示 ]:重复请求该接口" + str(self.timeout) + "s, 仍然未获取到数据")
|
||||||
|
return {}, {}
|
||||||
|
|
||||||
|
|
||||||
|
# 清空self.awemeDict
|
||||||
|
self.result.clearDict(self.result.awemeDict)
|
||||||
|
|
||||||
|
# 默认为视频
|
||||||
|
awemeType = 0
|
||||||
|
try:
|
||||||
|
# datadict['aweme_detail']["images"] 不为 None 说明是图集
|
||||||
|
if datadict['aweme_detail']["images"] is not None:
|
||||||
|
awemeType = 1
|
||||||
|
except Exception as e:
|
||||||
|
print("[ 警告 ]:接口中未找到 images\r")
|
||||||
|
|
||||||
|
# 转换成我们自己的格式
|
||||||
|
self.result.dataConvert(awemeType, self.result.awemeDict, datadict['aweme_detail'])
|
||||||
|
|
||||||
|
return self.result.awemeDict, datadict
|
||||||
|
|
||||||
|
# 传入 url 支持 https://www.iesdouyin.com 与 https://v.douyin.com
|
||||||
|
# mode : post | like 模式选择 like为用户点赞 post为用户发布
|
||||||
|
def getUserInfo(self, sec_uid, mode="post", count=35, number=0, increase=False):
|
||||||
|
print('[ 提示 ]:正在请求的用户 id = %s\r\n' % sec_uid)
|
||||||
|
if sec_uid is None:
|
||||||
|
return None
|
||||||
|
if number <= 0:
|
||||||
|
numflag = False
|
||||||
|
else:
|
||||||
|
numflag = True
|
||||||
|
|
||||||
|
max_cursor = 0
|
||||||
|
awemeList = []
|
||||||
|
increaseflag = False
|
||||||
|
numberis0 = False
|
||||||
|
|
||||||
|
print("[ 提示 ]:正在获取所有作品数据请稍后...\r")
|
||||||
|
print("[ 提示 ]:会进行多次请求,等待时间较长...\r\n")
|
||||||
|
times = 0
|
||||||
|
while True:
|
||||||
|
times = times + 1
|
||||||
|
print("[ 提示 ]:正在对 [主页] 进行第 " + str(times) + " 次请求...\r")
|
||||||
|
|
||||||
|
start = time.time() # 开始时间
|
||||||
|
while True:
|
||||||
|
# 接口不稳定, 有时服务器不返回数据, 需要重新获取
|
||||||
|
try:
|
||||||
|
if mode == "post":
|
||||||
|
url = self.urls.USER_POST + utils.getXbogus(
|
||||||
|
f'sec_user_id={sec_uid}&count={count}&max_cursor={max_cursor}&device_platform=webapp&aid=6383')
|
||||||
|
elif mode == "like":
|
||||||
|
url = self.urls.USER_FAVORITE_A + utils.getXbogus(
|
||||||
|
f'sec_user_id={sec_uid}&count={count}&max_cursor={max_cursor}&device_platform=webapp&aid=6383')
|
||||||
|
else:
|
||||||
|
print("[ 错误 ]:模式选择错误, 仅支持post、like、mix, 请检查后重新运行!\r")
|
||||||
|
return None
|
||||||
|
|
||||||
|
res = requests.get(url=url, headers=douyin_headers)
|
||||||
|
datadict = json.loads(res.text)
|
||||||
|
print('[ 提示 ]:本次请求返回 ' + str(len(datadict["aweme_list"])) + ' 条数据\r')
|
||||||
|
|
||||||
|
if datadict is not None and datadict["status_code"] == 0:
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
end = time.time() # 结束时间
|
||||||
|
if end - start > self.timeout:
|
||||||
|
print("[ 提示 ]:重复请求该接口" + str(self.timeout) + "s, 仍然未获取到数据")
|
||||||
|
return awemeList
|
||||||
|
|
||||||
|
|
||||||
|
for aweme in datadict["aweme_list"]:
|
||||||
|
if self.database:
|
||||||
|
# 退出条件
|
||||||
|
if increase is False and numflag and numberis0:
|
||||||
|
break
|
||||||
|
if increase and numflag and numberis0 and increaseflag:
|
||||||
|
break
|
||||||
|
# 增量更新, 找到非置顶的最新的作品发布时间
|
||||||
|
if mode == "post":
|
||||||
|
if self.db.get_user_post(sec_uid=sec_uid, aweme_id=aweme['aweme_id']) is not None:
|
||||||
|
if increase and aweme['is_top'] == 0:
|
||||||
|
increaseflag = True
|
||||||
|
else:
|
||||||
|
self.db.insert_user_post(sec_uid=sec_uid, aweme_id=aweme['aweme_id'], data=aweme)
|
||||||
|
elif mode == "like":
|
||||||
|
if self.db.get_user_like(sec_uid=sec_uid, aweme_id=aweme['aweme_id']) is not None:
|
||||||
|
if increase and aweme['is_top'] == 0:
|
||||||
|
increaseflag = True
|
||||||
|
else:
|
||||||
|
self.db.insert_user_like(sec_uid=sec_uid, aweme_id=aweme['aweme_id'], data=aweme)
|
||||||
|
|
||||||
|
# 退出条件
|
||||||
|
if increase and numflag is False and increaseflag:
|
||||||
|
break
|
||||||
|
if increase and numflag and numberis0 and increaseflag:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
if numflag and numberis0:
|
||||||
|
break
|
||||||
|
|
||||||
|
if numflag:
|
||||||
|
number -= 1
|
||||||
|
if number == 0:
|
||||||
|
numberis0 = True
|
||||||
|
|
||||||
|
# 清空self.awemeDict
|
||||||
|
self.result.clearDict(self.result.awemeDict)
|
||||||
|
|
||||||
|
# 默认为视频
|
||||||
|
awemeType = 0
|
||||||
|
try:
|
||||||
|
if aweme["images"] is not None:
|
||||||
|
awemeType = 1
|
||||||
|
except Exception as e:
|
||||||
|
print("[ 警告 ]:接口中未找到 images\r")
|
||||||
|
|
||||||
|
# 转换成我们自己的格式
|
||||||
|
self.result.dataConvert(awemeType, self.result.awemeDict, aweme)
|
||||||
|
|
||||||
|
if self.result.awemeDict is not None and self.result.awemeDict != {}:
|
||||||
|
awemeList.append(copy.deepcopy(self.result.awemeDict))
|
||||||
|
|
||||||
|
if self.database:
|
||||||
|
if increase and numflag is False and increaseflag:
|
||||||
|
print("\r\n[ 提示 ]: [主页] 下作品增量更新数据获取完成...\r\n")
|
||||||
|
break
|
||||||
|
elif increase is False and numflag and numberis0:
|
||||||
|
print("\r\n[ 提示 ]: [主页] 下指定数量作品数据获取完成...\r\n")
|
||||||
|
break
|
||||||
|
elif increase and numflag and numberis0 and increaseflag:
|
||||||
|
print("\r\n[ 提示 ]: [主页] 下指定数量作品数据获取完成, 增量更新数据获取完成...\r\n")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
if numflag and numberis0:
|
||||||
|
print("\r\n[ 提示 ]: [主页] 下指定数量作品数据获取完成...\r\n")
|
||||||
|
break
|
||||||
|
|
||||||
|
# 更新 max_cursor
|
||||||
|
max_cursor = datadict["max_cursor"]
|
||||||
|
|
||||||
|
# 退出条件
|
||||||
|
if datadict["has_more"] == 0 or datadict["has_more"] == False:
|
||||||
|
print("\r\n[ 提示 ]: [主页] 下所有作品数据获取完成...\r\n")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("\r\n[ 提示 ]:[主页] 第 " + str(times) + " 次请求成功...\r\n")
|
||||||
|
|
||||||
|
return awemeList
|
||||||
|
|
||||||
|
def getLiveInfo(self, web_rid: str):
|
||||||
|
print('[ 提示 ]:正在请求的直播间 id = %s\r\n' % web_rid)
|
||||||
|
|
||||||
|
start = time.time() # 开始时间
|
||||||
|
while True:
|
||||||
|
# 接口不稳定, 有时服务器不返回数据, 需要重新获取
|
||||||
|
try:
|
||||||
|
live_api = self.urls.LIVE + utils.getXbogus(
|
||||||
|
f'aid=6383&device_platform=web&web_rid={web_rid}')
|
||||||
|
|
||||||
|
response = requests.get(live_api, headers=douyin_headers)
|
||||||
|
live_json = json.loads(response.text)
|
||||||
|
if live_json != {} and live_json['status_code'] == 0:
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
end = time.time() # 结束时间
|
||||||
|
if end - start > self.timeout:
|
||||||
|
print("[ 提示 ]:重复请求该接口" + str(self.timeout) + "s, 仍然未获取到数据")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# 清空字典
|
||||||
|
self.result.clearDict(self.result.liveDict)
|
||||||
|
|
||||||
|
# 类型
|
||||||
|
self.result.liveDict["awemeType"] = 2
|
||||||
|
# 是否在播
|
||||||
|
self.result.liveDict["status"] = live_json['data']['data'][0]['status']
|
||||||
|
|
||||||
|
if self.result.liveDict["status"] == 4:
|
||||||
|
print('[ 📺 ]:当前直播已结束,正在退出')
|
||||||
|
return self.result.liveDict
|
||||||
|
|
||||||
|
# 直播标题
|
||||||
|
self.result.liveDict["title"] = live_json['data']['data'][0]['title']
|
||||||
|
|
||||||
|
# 直播cover
|
||||||
|
self.result.liveDict["cover"] = live_json['data']['data'][0]['cover']['url_list'][0]
|
||||||
|
|
||||||
|
# 头像
|
||||||
|
self.result.liveDict["avatar"] = live_json['data']['data'][0]['owner']['avatar_thumb']['url_list'][0].replace(
|
||||||
|
"100x100", "1080x1080")
|
||||||
|
|
||||||
|
# 观看人数
|
||||||
|
self.result.liveDict["user_count"] = live_json['data']['data'][0]['user_count_str']
|
||||||
|
|
||||||
|
# 昵称
|
||||||
|
self.result.liveDict["nickname"] = live_json['data']['data'][0]['owner']['nickname']
|
||||||
|
|
||||||
|
# sec_uid
|
||||||
|
self.result.liveDict["sec_uid"] = live_json['data']['data'][0]['owner']['sec_uid']
|
||||||
|
|
||||||
|
# 直播间观看状态
|
||||||
|
self.result.liveDict["display_long"] = live_json['data']['data'][0]['room_view_stats']['display_long']
|
||||||
|
|
||||||
|
# 推流
|
||||||
|
self.result.liveDict["flv_pull_url"] = live_json['data']['data'][0]['stream_url']['flv_pull_url']
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 分区
|
||||||
|
self.result.liveDict["partition"] = live_json['data']['partition_road_map']['partition']['title']
|
||||||
|
self.result.liveDict["sub_partition"] = \
|
||||||
|
live_json['data']['partition_road_map']['sub_partition']['partition']['title']
|
||||||
|
except Exception as e:
|
||||||
|
self.result.liveDict["partition"] = '无'
|
||||||
|
self.result.liveDict["sub_partition"] = '无'
|
||||||
|
|
||||||
|
info = '[ 💻 ]:直播间:%s 当前%s 主播:%s 分区:%s-%s\r' % (
|
||||||
|
self.result.liveDict["title"], self.result.liveDict["display_long"], self.result.liveDict["nickname"],
|
||||||
|
self.result.liveDict["partition"], self.result.liveDict["sub_partition"])
|
||||||
|
print(info)
|
||||||
|
|
||||||
|
flv = []
|
||||||
|
print('[ 🎦 ]:直播间清晰度')
|
||||||
|
for i, f in enumerate(self.result.liveDict["flv_pull_url"].keys()):
|
||||||
|
print('[ %s ]: %s' % (i, f))
|
||||||
|
flv.append(f)
|
||||||
|
|
||||||
|
rate = int(input('[ 🎬 ]输入数字选择推流清晰度:'))
|
||||||
|
|
||||||
|
self.result.liveDict["flv_pull_url0"] = self.result.liveDict["flv_pull_url"][flv[rate]]
|
||||||
|
|
||||||
|
# 显示清晰度列表
|
||||||
|
print('[ %s ]:%s' % (flv[rate], self.result.liveDict["flv_pull_url"][flv[rate]]))
|
||||||
|
print('[ 📺 ]:复制链接使用下载工具下载')
|
||||||
|
return self.result.liveDict
|
||||||
|
|
||||||
|
def getMixInfo(self, mix_id: str, count=35, number=0, increase=False, sec_uid=''):
|
||||||
|
print('[ 提示 ]:正在请求的合集 id = %s\r\n' % mix_id)
|
||||||
|
if mix_id is None:
|
||||||
|
return None
|
||||||
|
if number <= 0:
|
||||||
|
numflag = False
|
||||||
|
else:
|
||||||
|
numflag = True
|
||||||
|
|
||||||
|
cursor = 0
|
||||||
|
awemeList = []
|
||||||
|
increaseflag = False
|
||||||
|
numberis0 = False
|
||||||
|
|
||||||
|
print("[ 提示 ]:正在获取合集下的所有作品数据请稍后...\r")
|
||||||
|
print("[ 提示 ]:会进行多次请求,等待时间较长...\r\n")
|
||||||
|
times = 0
|
||||||
|
while True:
|
||||||
|
times = times + 1
|
||||||
|
print("[ 提示 ]:正在对 [合集] 进行第 " + str(times) + " 次请求...\r")
|
||||||
|
|
||||||
|
start = time.time() # 开始时间
|
||||||
|
while True:
|
||||||
|
# 接口不稳定, 有时服务器不返回数据, 需要重新获取
|
||||||
|
try:
|
||||||
|
url = self.urls.USER_MIX + utils.getXbogus(
|
||||||
|
f'mix_id={mix_id}&cursor={cursor}&count={count}&device_platform=webapp&aid=6383')
|
||||||
|
|
||||||
|
res = requests.get(url=url, headers=douyin_headers)
|
||||||
|
datadict = json.loads(res.text)
|
||||||
|
print('[ 提示 ]:本次请求返回 ' + str(len(datadict["aweme_list"])) + ' 条数据\r')
|
||||||
|
|
||||||
|
if datadict is not None:
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
end = time.time() # 结束时间
|
||||||
|
if end - start > self.timeout:
|
||||||
|
print("[ 提示 ]:重复请求该接口" + str(self.timeout) + "s, 仍然未获取到数据")
|
||||||
|
return awemeList
|
||||||
|
|
||||||
|
|
||||||
|
for aweme in datadict["aweme_list"]:
|
||||||
|
if self.database:
|
||||||
|
# 退出条件
|
||||||
|
if increase is False and numflag and numberis0:
|
||||||
|
break
|
||||||
|
if increase and numflag and numberis0 and increaseflag:
|
||||||
|
break
|
||||||
|
# 增量更新, 找到非置顶的最新的作品发布时间
|
||||||
|
if self.db.get_mix(sec_uid=sec_uid, mix_id=mix_id, aweme_id=aweme['aweme_id']) is not None:
|
||||||
|
if increase and aweme['is_top'] == 0:
|
||||||
|
increaseflag = True
|
||||||
|
else:
|
||||||
|
self.db.insert_mix(sec_uid=sec_uid, mix_id=mix_id, aweme_id=aweme['aweme_id'], data=aweme)
|
||||||
|
|
||||||
|
# 退出条件
|
||||||
|
if increase and numflag is False and increaseflag:
|
||||||
|
break
|
||||||
|
if increase and numflag and numberis0 and increaseflag:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
if numflag and numberis0:
|
||||||
|
break
|
||||||
|
|
||||||
|
if numflag:
|
||||||
|
number -= 1
|
||||||
|
if number == 0:
|
||||||
|
numberis0 = True
|
||||||
|
|
||||||
|
# 清空self.awemeDict
|
||||||
|
self.result.clearDict(self.result.awemeDict)
|
||||||
|
|
||||||
|
# 默认为视频
|
||||||
|
awemeType = 0
|
||||||
|
try:
|
||||||
|
if aweme["images"] is not None:
|
||||||
|
awemeType = 1
|
||||||
|
except Exception as e:
|
||||||
|
print("[ 警告 ]:接口中未找到 images\r")
|
||||||
|
|
||||||
|
# 转换成我们自己的格式
|
||||||
|
self.result.dataConvert(awemeType, self.result.awemeDict, aweme)
|
||||||
|
|
||||||
|
if self.result.awemeDict is not None and self.result.awemeDict != {}:
|
||||||
|
awemeList.append(copy.deepcopy(self.result.awemeDict))
|
||||||
|
|
||||||
|
if self.database:
|
||||||
|
if increase and numflag is False and increaseflag:
|
||||||
|
print("\r\n[ 提示 ]: [合集] 下作品增量更新数据获取完成...\r\n")
|
||||||
|
break
|
||||||
|
elif increase is False and numflag and numberis0:
|
||||||
|
print("\r\n[ 提示 ]: [合集] 下指定数量作品数据获取完成...\r\n")
|
||||||
|
break
|
||||||
|
elif increase and numflag and numberis0 and increaseflag:
|
||||||
|
print("\r\n[ 提示 ]: [合集] 下指定数量作品数据获取完成, 增量更新数据获取完成...\r\n")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
if numflag and numberis0:
|
||||||
|
print("\r\n[ 提示 ]: [合集] 下指定数量作品数据获取完成...\r\n")
|
||||||
|
break
|
||||||
|
|
||||||
|
# 更新 max_cursor
|
||||||
|
cursor = datadict["cursor"]
|
||||||
|
|
||||||
|
# 退出条件
|
||||||
|
if datadict["has_more"] == 0 or datadict["has_more"] == False:
|
||||||
|
print("\r\n[ 提示 ]:[合集] 下所有作品数据获取完成...\r\n")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("\r\n[ 提示 ]:[合集] 第 " + str(times) + " 次请求成功...\r\n")
|
||||||
|
|
||||||
|
return awemeList
|
||||||
|
|
||||||
|
def getUserAllMixInfo(self, sec_uid, count=35, number=0):
|
||||||
|
print('[ 提示 ]:正在请求的用户 id = %s\r\n' % sec_uid)
|
||||||
|
if sec_uid is None:
|
||||||
|
return None
|
||||||
|
if number <= 0:
|
||||||
|
numflag = False
|
||||||
|
else:
|
||||||
|
numflag = True
|
||||||
|
|
||||||
|
cursor = 0
|
||||||
|
mixIdNameDict = {}
|
||||||
|
|
||||||
|
print("[ 提示 ]:正在获取主页下所有合集 id 数据请稍后...\r")
|
||||||
|
print("[ 提示 ]:会进行多次请求,等待时间较长...\r\n")
|
||||||
|
times = 0
|
||||||
|
while True:
|
||||||
|
times = times + 1
|
||||||
|
print("[ 提示 ]:正在对 [合集列表] 进行第 " + str(times) + " 次请求...\r")
|
||||||
|
|
||||||
|
start = time.time() # 开始时间
|
||||||
|
while True:
|
||||||
|
# 接口不稳定, 有时服务器不返回数据, 需要重新获取
|
||||||
|
try:
|
||||||
|
url = self.urls.USER_MIX_LIST + utils.getXbogus(
|
||||||
|
f'sec_user_id={sec_uid}&count={count}&cursor={cursor}&device_platform=webapp&aid=6383')
|
||||||
|
|
||||||
|
res = requests.get(url=url, headers=douyin_headers)
|
||||||
|
datadict = json.loads(res.text)
|
||||||
|
print('[ 提示 ]:本次请求返回 ' + str(len(datadict["mix_infos"])) + ' 条数据\r')
|
||||||
|
|
||||||
|
if datadict is not None and datadict["status_code"] == 0:
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
end = time.time() # 结束时间
|
||||||
|
if end - start > self.timeout:
|
||||||
|
print("[ 提示 ]:重复请求该接口" + str(self.timeout) + "s, 仍然未获取到数据")
|
||||||
|
return mixIdNameDict
|
||||||
|
|
||||||
|
|
||||||
|
for mix in datadict["mix_infos"]:
|
||||||
|
mixIdNameDict[mix["mix_id"]] = mix["mix_name"]
|
||||||
|
if numflag:
|
||||||
|
number -= 1
|
||||||
|
if number == 0:
|
||||||
|
break
|
||||||
|
if numflag and number == 0:
|
||||||
|
print("\r\n[ 提示 ]:[合集列表] 下指定数量合集数据获取完成...\r\n")
|
||||||
|
break
|
||||||
|
|
||||||
|
# 更新 max_cursor
|
||||||
|
cursor = datadict["cursor"]
|
||||||
|
|
||||||
|
# 退出条件
|
||||||
|
if datadict["has_more"] == 0 or datadict["has_more"] == False:
|
||||||
|
print("[ 提示 ]:[合集列表] 下所有合集 id 数据获取完成...\r\n")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("\r\n[ 提示 ]:[合集列表] 第 " + str(times) + " 次请求成功...\r\n")
|
||||||
|
|
||||||
|
return mixIdNameDict
|
||||||
|
|
||||||
|
def getMusicInfo(self, music_id: str, count=35, number=0, increase=False):
|
||||||
|
print('[ 提示 ]:正在请求的音乐集合 id = %s\r\n' % music_id)
|
||||||
|
if music_id is None:
|
||||||
|
return None
|
||||||
|
if number <= 0:
|
||||||
|
numflag = False
|
||||||
|
else:
|
||||||
|
numflag = True
|
||||||
|
|
||||||
|
cursor = 0
|
||||||
|
awemeList = []
|
||||||
|
increaseflag = False
|
||||||
|
numberis0 = False
|
||||||
|
|
||||||
|
print("[ 提示 ]:正在获取音乐集合下的所有作品数据请稍后...\r")
|
||||||
|
print("[ 提示 ]:会进行多次请求,等待时间较长...\r\n")
|
||||||
|
times = 0
|
||||||
|
while True:
|
||||||
|
times = times + 1
|
||||||
|
print("[ 提示 ]:正在对 [音乐集合] 进行第 " + str(times) + " 次请求...\r")
|
||||||
|
|
||||||
|
start = time.time() # 开始时间
|
||||||
|
while True:
|
||||||
|
# 接口不稳定, 有时服务器不返回数据, 需要重新获取
|
||||||
|
try:
|
||||||
|
url = self.urls.MUSIC + utils.getXbogus(
|
||||||
|
f'music_id={music_id}&cursor={cursor}&count={count}&device_platform=webapp&aid=6383')
|
||||||
|
|
||||||
|
res = requests.get(url=url, headers=douyin_headers)
|
||||||
|
datadict = json.loads(res.text)
|
||||||
|
print('[ 提示 ]:本次请求返回 ' + str(len(datadict["aweme_list"])) + ' 条数据\r')
|
||||||
|
|
||||||
|
if datadict is not None and datadict["status_code"] == 0:
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
end = time.time() # 结束时间
|
||||||
|
if end - start > self.timeout:
|
||||||
|
print("[ 提示 ]:重复请求该接口" + str(self.timeout) + "s, 仍然未获取到数据")
|
||||||
|
return awemeList
|
||||||
|
|
||||||
|
|
||||||
|
for aweme in datadict["aweme_list"]:
|
||||||
|
if self.database:
|
||||||
|
# 退出条件
|
||||||
|
if increase is False and numflag and numberis0:
|
||||||
|
break
|
||||||
|
if increase and numflag and numberis0 and increaseflag:
|
||||||
|
break
|
||||||
|
# 增量更新, 找到非置顶的最新的作品发布时间
|
||||||
|
if self.db.get_music(music_id=music_id, aweme_id=aweme['aweme_id']) is not None:
|
||||||
|
if increase and aweme['is_top'] == 0:
|
||||||
|
increaseflag = True
|
||||||
|
else:
|
||||||
|
self.db.insert_music(music_id=music_id, aweme_id=aweme['aweme_id'], data=aweme)
|
||||||
|
|
||||||
|
# 退出条件
|
||||||
|
if increase and numflag is False and increaseflag:
|
||||||
|
break
|
||||||
|
if increase and numflag and numberis0 and increaseflag:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
if numflag and numberis0:
|
||||||
|
break
|
||||||
|
|
||||||
|
if numflag:
|
||||||
|
number -= 1
|
||||||
|
if number == 0:
|
||||||
|
numberis0 = True
|
||||||
|
|
||||||
|
# 清空self.awemeDict
|
||||||
|
self.result.clearDict(self.result.awemeDict)
|
||||||
|
|
||||||
|
# 默认为视频
|
||||||
|
awemeType = 0
|
||||||
|
try:
|
||||||
|
if aweme["images"] is not None:
|
||||||
|
awemeType = 1
|
||||||
|
except Exception as e:
|
||||||
|
print("[ 警告 ]:接口中未找到 images\r")
|
||||||
|
|
||||||
|
# 转换成我们自己的格式
|
||||||
|
self.result.dataConvert(awemeType, self.result.awemeDict, aweme)
|
||||||
|
|
||||||
|
if self.result.awemeDict is not None and self.result.awemeDict != {}:
|
||||||
|
awemeList.append(copy.deepcopy(self.result.awemeDict))
|
||||||
|
|
||||||
|
if self.database:
|
||||||
|
if increase and numflag is False and increaseflag:
|
||||||
|
print("\r\n[ 提示 ]: [音乐集合] 下作品增量更新数据获取完成...\r\n")
|
||||||
|
break
|
||||||
|
elif increase is False and numflag and numberis0:
|
||||||
|
print("\r\n[ 提示 ]: [音乐集合] 下指定数量作品数据获取完成...\r\n")
|
||||||
|
break
|
||||||
|
elif increase and numflag and numberis0 and increaseflag:
|
||||||
|
print("\r\n[ 提示 ]: [音乐集合] 下指定数量作品数据获取完成, 增量更新数据获取完成...\r\n")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
if numflag and numberis0:
|
||||||
|
print("\r\n[ 提示 ]: [音乐集合] 下指定数量作品数据获取完成...\r\n")
|
||||||
|
break
|
||||||
|
|
||||||
|
# 更新 cursor
|
||||||
|
cursor = datadict["cursor"]
|
||||||
|
|
||||||
|
# 退出条件
|
||||||
|
if datadict["has_more"] == 0 or datadict["has_more"] == False:
|
||||||
|
print("\r\n[ 提示 ]:[音乐集合] 下所有作品数据获取完成...\r\n")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("\r\n[ 提示 ]:[音乐集合] 第 " + str(times) + " 次请求成功...\r\n")
|
||||||
|
|
||||||
|
return awemeList
|
||||||
|
|
||||||
|
def getUserDetailInfo(self, sec_uid):
|
||||||
|
if sec_uid is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
datadict = {}
|
||||||
|
start = time.time() # 开始时间
|
||||||
|
while True:
|
||||||
|
# 接口不稳定, 有时服务器不返回数据, 需要重新获取
|
||||||
|
try:
|
||||||
|
url = self.urls.USER_DETAIL + utils.getXbogus(
|
||||||
|
f'sec_user_id={sec_uid}&device_platform=webapp&aid=6383')
|
||||||
|
|
||||||
|
res = requests.get(url=url, headers=douyin_headers)
|
||||||
|
datadict = json.loads(res.text)
|
||||||
|
|
||||||
|
if datadict is not None and datadict["status_code"] == 0:
|
||||||
|
return datadict
|
||||||
|
except Exception as e:
|
||||||
|
end = time.time() # 结束时间
|
||||||
|
if end - start > self.timeout:
|
||||||
|
print("[ 提示 ]:重复请求该接口" + str(self.timeout) + "s, 仍然未获取到数据")
|
||||||
|
return datadict
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
pass
|
398
apiproxy/douyin/douyinapi.py
Normal file
@ -0,0 +1,398 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
'''
|
||||||
|
@FileName : douyinapi.py
|
||||||
|
@Project : apiproxy
|
||||||
|
@Description:
|
||||||
|
@Author : imgyh
|
||||||
|
@Mail : admin@imgyh.com
|
||||||
|
@Github : https://github.com/imgyh
|
||||||
|
@Site : https://www.imgyh.com
|
||||||
|
@Date : 2023/5/20 22:13
|
||||||
|
@Version : v1.0
|
||||||
|
@ChangeLog
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
------------------------------------------------
|
||||||
|
'''
|
||||||
|
|
||||||
|
import re
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import copy
|
||||||
|
|
||||||
|
from apiproxy.douyin import douyin_headers
|
||||||
|
from apiproxy.douyin.urls import Urls
|
||||||
|
from apiproxy.douyin.result import Result
|
||||||
|
from apiproxy.common import utils
|
||||||
|
|
||||||
|
class DouyinApi(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.urls = Urls()
|
||||||
|
self.result = Result()
|
||||||
|
# 用于设置重复请求某个接口的最大时间
|
||||||
|
self.timeout = 10
|
||||||
|
|
||||||
|
# 从分享链接中提取网址
|
||||||
|
def getShareLink(self, string):
|
||||||
|
# findall() 查找匹配正则表达式的字符串
|
||||||
|
return re.findall('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', string)[0]
|
||||||
|
|
||||||
|
# 得到 作品id 或者 用户id
|
||||||
|
# 传入 url 支持 https://www.iesdouyin.com 与 https://v.douyin.com
|
||||||
|
def getKey(self, url):
|
||||||
|
key = None
|
||||||
|
key_type = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = requests.get(url=url, headers=douyin_headers)
|
||||||
|
except Exception as e:
|
||||||
|
print('[ 错误 ]:输入链接有误!\r')
|
||||||
|
return key_type, key
|
||||||
|
|
||||||
|
# 抖音把图集更新为note
|
||||||
|
# 作品 第一步解析出来的链接是share/video/{aweme_id}
|
||||||
|
# https://www.iesdouyin.com/share/video/7037827546599263488/?region=CN&mid=6939809470193126152&u_code=j8a5173b&did=MS4wLjABAAAA1DICF9-A9M_CiGqAJZdsnig5TInVeIyPdc2QQdGrq58xUgD2w6BqCHovtqdIDs2i&iid=MS4wLjABAAAAomGWi4n2T0H9Ab9x96cUZoJXaILk4qXOJlJMZFiK6b_aJbuHkjN_f0mBzfy91DX1&with_sec_did=1&titleType=title&schema_type=37&from_ssr=1&utm_source=copy&utm_campaign=client_share&utm_medium=android&app=aweme
|
||||||
|
# 用户 第一步解析出来的链接是share/user/{sec_uid}
|
||||||
|
# https://www.iesdouyin.com/share/user/MS4wLjABAAAA06y3Ctu8QmuefqvUSU7vr0c_ZQnCqB0eaglgkelLTek?did=MS4wLjABAAAA1DICF9-A9M_CiGqAJZdsnig5TInVeIyPdc2QQdGrq58xUgD2w6BqCHovtqdIDs2i&iid=MS4wLjABAAAAomGWi4n2T0H9Ab9x96cUZoJXaILk4qXOJlJMZFiK6b_aJbuHkjN_f0mBzfy91DX1&with_sec_did=1&sec_uid=MS4wLjABAAAA06y3Ctu8QmuefqvUSU7vr0c_ZQnCqB0eaglgkelLTek&from_ssr=1&u_code=j8a5173b×tamp=1674540164&ecom_share_track_params=%7B%22is_ec_shopping%22%3A%221%22%2C%22secuid%22%3A%22MS4wLjABAAAA-jD2lukp--I21BF8VQsmYUqJDbj3FmU-kGQTHl2y1Cw%22%2C%22enter_from%22%3A%22others_homepage%22%2C%22share_previous_page%22%3A%22others_homepage%22%7D&utm_source=copy&utm_campaign=client_share&utm_medium=android&app=aweme
|
||||||
|
# 合集
|
||||||
|
# https://www.douyin.com/collection/7093490319085307918
|
||||||
|
urlstr = str(r.request.path_url)
|
||||||
|
|
||||||
|
if "/user/" in urlstr:
|
||||||
|
# 获取用户 sec_uid
|
||||||
|
if '?' in r.request.path_url:
|
||||||
|
for one in re.finditer(r'user\/([\d\D]*)([?])', str(r.request.path_url)):
|
||||||
|
key = one.group(1)
|
||||||
|
else:
|
||||||
|
for one in re.finditer(r'user\/([\d\D]*)', str(r.request.path_url)):
|
||||||
|
key = one.group(1)
|
||||||
|
key_type = "user"
|
||||||
|
elif "/video/" in urlstr:
|
||||||
|
# 获取作品 aweme_id
|
||||||
|
key = re.findall('video/(\d+)?', urlstr)[0]
|
||||||
|
key_type = "aweme"
|
||||||
|
elif "/note/" in urlstr:
|
||||||
|
# 获取note aweme_id
|
||||||
|
key = re.findall('note/(\d+)?', urlstr)[0]
|
||||||
|
key_type = "aweme"
|
||||||
|
elif "/mix/detail/" in urlstr:
|
||||||
|
# 获取合集 id
|
||||||
|
key = re.findall('/mix/detail/(\d+)?', urlstr)[0]
|
||||||
|
key_type = "mix"
|
||||||
|
elif "/collection/" in urlstr:
|
||||||
|
# 获取合集 id
|
||||||
|
key = re.findall('/collection/(\d+)?', urlstr)[0]
|
||||||
|
key_type = "mix"
|
||||||
|
elif "/music/" in urlstr:
|
||||||
|
# 获取原声 id
|
||||||
|
key = re.findall('music/(\d+)?', urlstr)[0]
|
||||||
|
key_type = "music"
|
||||||
|
elif "/webcast/reflow/" in urlstr:
|
||||||
|
key1 = re.findall('reflow/(\d+)?', urlstr)[0]
|
||||||
|
url = self.urls.LIVE2 + utils.getXbogus(
|
||||||
|
f'live_id=1&room_id={key1}&app_id=1128')
|
||||||
|
res = requests.get(url, headers=douyin_headers)
|
||||||
|
resjson = json.loads(res.text)
|
||||||
|
key = resjson['data']['room']['owner']['web_rid']
|
||||||
|
key_type = "live"
|
||||||
|
elif "live.douyin.com" in r.url:
|
||||||
|
key = r.url.replace('https://live.douyin.com/', '')
|
||||||
|
key_type = "live"
|
||||||
|
|
||||||
|
if key is None or key_type is None:
|
||||||
|
print('[ 错误 ]:输入链接有误!无法获取 id\r')
|
||||||
|
return key_type, key
|
||||||
|
|
||||||
|
return key_type, key
|
||||||
|
|
||||||
|
def getAwemeInfoApi(self, aweme_id):
|
||||||
|
if aweme_id is None:
|
||||||
|
return None
|
||||||
|
start = time.time() # 开始时间
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
jx_url = self.urls.POST_DETAIL + utils.getXbogus(
|
||||||
|
f'aweme_id={aweme_id}&device_platform=webapp&aid=6383')
|
||||||
|
|
||||||
|
raw = requests.get(url=jx_url, headers=douyin_headers).text
|
||||||
|
datadict = json.loads(raw)
|
||||||
|
if datadict is not None and datadict["status_code"] == 0:
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
end = time.time() # 结束时间
|
||||||
|
if end - start > self.timeout:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 清空self.awemeDict
|
||||||
|
self.result.clearDict(self.result.awemeDict)
|
||||||
|
|
||||||
|
# 默认为视频
|
||||||
|
awemeType = 0
|
||||||
|
try:
|
||||||
|
if datadict['aweme_detail']["images"] is not None:
|
||||||
|
awemeType = 1
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 转换成我们自己的格式
|
||||||
|
self.result.dataConvert(awemeType, self.result.awemeDict, datadict['aweme_detail'])
|
||||||
|
|
||||||
|
return self.result.awemeDict, datadict
|
||||||
|
|
||||||
|
def getUserInfoApi(self, sec_uid, mode="post", count=35, max_cursor=0):
|
||||||
|
if sec_uid is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
awemeList = []
|
||||||
|
|
||||||
|
start = time.time() # 开始时间
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
if mode == "post":
|
||||||
|
url = self.urls.USER_POST + utils.getXbogus(
|
||||||
|
f'sec_user_id={sec_uid}&count={count}&max_cursor={max_cursor}&device_platform=webapp&aid=6383')
|
||||||
|
elif mode == "like":
|
||||||
|
url = self.urls.USER_FAVORITE_A + utils.getXbogus(
|
||||||
|
f'sec_user_id={sec_uid}&count={count}&max_cursor={max_cursor}&device_platform=webapp&aid=6383')
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
res = requests.get(url=url, headers=douyin_headers)
|
||||||
|
datadict = json.loads(res.text)
|
||||||
|
if datadict is not None and datadict["status_code"] == 0:
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
end = time.time() # 结束时间
|
||||||
|
if end - start > self.timeout:
|
||||||
|
return None
|
||||||
|
|
||||||
|
for aweme in datadict["aweme_list"]:
|
||||||
|
# 清空self.awemeDict
|
||||||
|
self.result.clearDict(self.result.awemeDict)
|
||||||
|
|
||||||
|
# 默认为视频
|
||||||
|
awemeType = 0
|
||||||
|
try:
|
||||||
|
if aweme["images"] is not None:
|
||||||
|
awemeType = 1
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 转换成我们自己的格式
|
||||||
|
self.result.dataConvert(awemeType, self.result.awemeDict, aweme)
|
||||||
|
|
||||||
|
if self.result.awemeDict is not None and self.result.awemeDict != {}:
|
||||||
|
awemeList.append(copy.deepcopy(self.result.awemeDict))
|
||||||
|
|
||||||
|
return awemeList, datadict, datadict["max_cursor"], datadict["has_more"]
|
||||||
|
|
||||||
|
def getLiveInfoApi(self, web_rid: str):
|
||||||
|
start = time.time() # 开始时间
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
live_api = self.urls.LIVE + utils.getXbogus(
|
||||||
|
f'aid=6383&device_platform=web&web_rid={web_rid}')
|
||||||
|
|
||||||
|
response = requests.get(live_api, headers=douyin_headers)
|
||||||
|
live_json = json.loads(response.text)
|
||||||
|
if live_json != {} and live_json['status_code'] == 0:
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
end = time.time() # 结束时间
|
||||||
|
if end - start > self.timeout:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 清空字典
|
||||||
|
self.result.clearDict(self.result.liveDict)
|
||||||
|
|
||||||
|
# 类型
|
||||||
|
self.result.liveDict["awemeType"] = 2
|
||||||
|
# 是否在播
|
||||||
|
self.result.liveDict["status"] = live_json['data']['data'][0]['status']
|
||||||
|
|
||||||
|
if self.result.liveDict["status"] == 4:
|
||||||
|
return self.result.liveDict, live_json
|
||||||
|
|
||||||
|
# 直播标题
|
||||||
|
self.result.liveDict["title"] = live_json['data']['data'][0]['title']
|
||||||
|
|
||||||
|
# 直播cover
|
||||||
|
self.result.liveDict["cover"] = live_json['data']['data'][0]['cover']['url_list'][0]
|
||||||
|
|
||||||
|
# 头像
|
||||||
|
self.result.liveDict["avatar"] = live_json['data']['data'][0]['owner']['avatar_thumb']['url_list'][0].replace(
|
||||||
|
"100x100", "1080x1080")
|
||||||
|
|
||||||
|
# 观看人数
|
||||||
|
self.result.liveDict["user_count"] = live_json['data']['data'][0]['user_count_str']
|
||||||
|
|
||||||
|
# 昵称
|
||||||
|
self.result.liveDict["nickname"] = live_json['data']['data'][0]['owner']['nickname']
|
||||||
|
|
||||||
|
# sec_uid
|
||||||
|
self.result.liveDict["sec_uid"] = live_json['data']['data'][0]['owner']['sec_uid']
|
||||||
|
|
||||||
|
# 直播间观看状态
|
||||||
|
self.result.liveDict["display_long"] = live_json['data']['data'][0]['room_view_stats']['display_long']
|
||||||
|
|
||||||
|
# 推流
|
||||||
|
self.result.liveDict["flv_pull_url"] = live_json['data']['data'][0]['stream_url']['flv_pull_url']
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 分区
|
||||||
|
self.result.liveDict["partition"] = live_json['data']['partition_road_map']['partition']['title']
|
||||||
|
self.result.liveDict["sub_partition"] = \
|
||||||
|
live_json['data']['partition_road_map']['sub_partition']['partition']['title']
|
||||||
|
except Exception as e:
|
||||||
|
self.result.liveDict["partition"] = '无'
|
||||||
|
self.result.liveDict["sub_partition"] = '无'
|
||||||
|
|
||||||
|
flv = []
|
||||||
|
|
||||||
|
for i, f in enumerate(self.result.liveDict["flv_pull_url"].keys()):
|
||||||
|
flv.append(f)
|
||||||
|
|
||||||
|
self.result.liveDict["flv_pull_url0"] = self.result.liveDict["flv_pull_url"][flv[0]]
|
||||||
|
|
||||||
|
return self.result.liveDict, live_json
|
||||||
|
|
||||||
|
def getMixInfoApi(self, mix_id: str, count=35, cursor=0):
|
||||||
|
if mix_id is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
awemeList = []
|
||||||
|
|
||||||
|
start = time.time() # 开始时间
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
url = self.urls.USER_MIX + utils.getXbogus(
|
||||||
|
f'mix_id={mix_id}&cursor={cursor}&count={count}&device_platform=webapp&aid=6383')
|
||||||
|
|
||||||
|
res = requests.get(url=url, headers=douyin_headers)
|
||||||
|
datadict = json.loads(res.text)
|
||||||
|
if datadict is not None:
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
end = time.time() # 结束时间
|
||||||
|
if end - start > self.timeout:
|
||||||
|
return None
|
||||||
|
|
||||||
|
for aweme in datadict["aweme_list"]:
|
||||||
|
|
||||||
|
# 清空self.awemeDict
|
||||||
|
self.result.clearDict(self.result.awemeDict)
|
||||||
|
|
||||||
|
# 默认为视频
|
||||||
|
awemeType = 0
|
||||||
|
try:
|
||||||
|
if aweme["images"] is not None:
|
||||||
|
awemeType = 1
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 转换成我们自己的格式
|
||||||
|
self.result.dataConvert(awemeType, self.result.awemeDict, aweme)
|
||||||
|
|
||||||
|
if self.result.awemeDict is not None and self.result.awemeDict != {}:
|
||||||
|
awemeList.append(copy.deepcopy(self.result.awemeDict))
|
||||||
|
|
||||||
|
return awemeList, datadict, datadict["cursor"], datadict["has_more"]
|
||||||
|
|
||||||
|
def getUserAllMixInfoApi(self, sec_uid, count=35, cursor=0):
|
||||||
|
|
||||||
|
if sec_uid is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
mixIdlist = []
|
||||||
|
|
||||||
|
start = time.time() # 开始时间
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
url = self.urls.USER_MIX_LIST + utils.getXbogus(
|
||||||
|
f'sec_user_id={sec_uid}&count={count}&cursor={cursor}&device_platform=webapp&aid=6383')
|
||||||
|
|
||||||
|
res = requests.get(url=url, headers=douyin_headers)
|
||||||
|
datadict = json.loads(res.text)
|
||||||
|
if datadict is not None and datadict["status_code"] == 0:
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
end = time.time() # 结束时间
|
||||||
|
if end - start > self.timeout:
|
||||||
|
return None
|
||||||
|
|
||||||
|
for mix in datadict["mix_infos"]:
|
||||||
|
mixIdNameDict = {}
|
||||||
|
mixIdNameDict["https://www.douyin.com/collection/" + mix["mix_id"]] = mix["mix_name"]
|
||||||
|
mixIdlist.append(mixIdNameDict)
|
||||||
|
|
||||||
|
return mixIdlist, datadict, datadict["cursor"], datadict["has_more"]
|
||||||
|
|
||||||
|
def getMusicInfoApi(self, music_id: str, count=35, cursor=0):
|
||||||
|
if music_id is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
awemeList = []
|
||||||
|
|
||||||
|
start = time.time() # 开始时间
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
url = self.urls.MUSIC + utils.getXbogus(
|
||||||
|
f'music_id={music_id}&cursor={cursor}&count={count}&device_platform=webapp&aid=6383')
|
||||||
|
|
||||||
|
res = requests.get(url=url, headers=douyin_headers)
|
||||||
|
datadict = json.loads(res.text)
|
||||||
|
if datadict is not None and datadict["status_code"] == 0:
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
end = time.time() # 结束时间
|
||||||
|
if end - start > self.timeout:
|
||||||
|
return None
|
||||||
|
|
||||||
|
for aweme in datadict["aweme_list"]:
|
||||||
|
# 清空self.awemeDict
|
||||||
|
self.result.clearDict(self.result.awemeDict)
|
||||||
|
|
||||||
|
# 默认为视频
|
||||||
|
awemeType = 0
|
||||||
|
try:
|
||||||
|
if aweme["images"] is not None:
|
||||||
|
awemeType = 1
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 转换成我们自己的格式
|
||||||
|
self.result.dataConvert(awemeType, self.result.awemeDict, aweme)
|
||||||
|
|
||||||
|
if self.result.awemeDict is not None and self.result.awemeDict != {}:
|
||||||
|
awemeList.append(copy.deepcopy(self.result.awemeDict))
|
||||||
|
|
||||||
|
return awemeList, datadict, datadict["cursor"], datadict["has_more"]
|
||||||
|
|
||||||
|
def getUserDetailInfoApi(self, sec_uid):
|
||||||
|
if sec_uid is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
start = time.time() # 开始时间
|
||||||
|
while True:
|
||||||
|
# 接口不稳定, 有时服务器不返回数据, 需要重新获取
|
||||||
|
try:
|
||||||
|
url = self.urls.USER_DETAIL + utils.getXbogus(
|
||||||
|
f'sec_user_id={sec_uid}&device_platform=webapp&aid=6383')
|
||||||
|
|
||||||
|
res = requests.get(url=url, headers=douyin_headers)
|
||||||
|
datadict = json.loads(res.text)
|
||||||
|
|
||||||
|
if datadict is not None and datadict["status_code"] == 0:
|
||||||
|
return datadict
|
||||||
|
except Exception as e:
|
||||||
|
end = time.time() # 结束时间
|
||||||
|
if end - start > self.timeout:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
pass
|
205
apiproxy/douyin/download.py
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
'''
|
||||||
|
@FileName : download.py
|
||||||
|
@Project : apiproxy
|
||||||
|
@Description:
|
||||||
|
@Author : imgyh
|
||||||
|
@Mail : admin@imgyh.com
|
||||||
|
@Github : https://github.com/imgyh
|
||||||
|
@Site : https://www.imgyh.com
|
||||||
|
@Date : 2023/5/12 15:18
|
||||||
|
@Version : v1.0
|
||||||
|
@ChangeLog
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
------------------------------------------------
|
||||||
|
'''
|
||||||
|
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import requests
|
||||||
|
from tqdm import tqdm
|
||||||
|
from concurrent.futures import ThreadPoolExecutor, wait, ALL_COMPLETED
|
||||||
|
|
||||||
|
from apiproxy.douyin import douyin_headers
|
||||||
|
from apiproxy.common import utils
|
||||||
|
|
||||||
|
|
||||||
|
class Download(object):
|
||||||
|
def __init__(self, thread=5, music=True, cover=True, avatar=True, resjson=True, folderstyle=True):
|
||||||
|
self.thread = thread
|
||||||
|
self.music = music
|
||||||
|
self.cover = cover
|
||||||
|
self.avatar = avatar
|
||||||
|
self.resjson = resjson
|
||||||
|
self.folderstyle = folderstyle
|
||||||
|
|
||||||
|
def progressBarDownload(self, url, filepath, desc):
|
||||||
|
response = requests.get(url, stream=True, headers=douyin_headers)
|
||||||
|
chunk_size = 1024 # 每次下载的数据大小
|
||||||
|
content_size = int(response.headers['content-length']) # 下载文件总大小
|
||||||
|
try:
|
||||||
|
if response.status_code == 200: # 判断是否响应成功
|
||||||
|
with open(filepath, 'wb') as file, tqdm(total=content_size,
|
||||||
|
unit="iB",
|
||||||
|
desc=desc,
|
||||||
|
unit_scale=True,
|
||||||
|
unit_divisor=1024,
|
||||||
|
|
||||||
|
) as bar: # 显示进度条
|
||||||
|
for data in response.iter_content(chunk_size=chunk_size):
|
||||||
|
size = file.write(data)
|
||||||
|
bar.update(size)
|
||||||
|
except Exception as e:
|
||||||
|
# 下载异常 删除原来下载的文件, 可能未下成功
|
||||||
|
if os.path.exists(filepath):
|
||||||
|
os.remove(filepath)
|
||||||
|
print("[ 错误 ]:下载出错\r")
|
||||||
|
|
||||||
|
def awemeDownload(self, awemeDict: dict, savePath=os.getcwd()):
|
||||||
|
if awemeDict is None:
|
||||||
|
return
|
||||||
|
if not os.path.exists(savePath):
|
||||||
|
os.mkdir(savePath)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 使用作品 创建时间+描述 当文件夹
|
||||||
|
file_name = awemeDict["create_time"] + "_" + utils.replaceStr(awemeDict["desc"])
|
||||||
|
if self.folderstyle:
|
||||||
|
aweme_path = os.path.join(savePath, file_name)
|
||||||
|
if not os.path.exists(aweme_path):
|
||||||
|
os.mkdir(aweme_path)
|
||||||
|
else:
|
||||||
|
aweme_path = savePath
|
||||||
|
|
||||||
|
# 保存获取到的字典信息
|
||||||
|
if self.resjson:
|
||||||
|
try:
|
||||||
|
with open(os.path.join(aweme_path, file_name + "_result.json"), "w", encoding='utf-8') as f:
|
||||||
|
f.write(json.dumps(awemeDict, ensure_ascii=False, indent=2))
|
||||||
|
f.close()
|
||||||
|
except Exception as e:
|
||||||
|
print("[ 错误 ]:保存 result.json 失败... 作品名: " + file_name + "\r\n")
|
||||||
|
|
||||||
|
desc = file_name[:30]
|
||||||
|
# 下载 视频
|
||||||
|
if awemeDict["awemeType"] == 0:
|
||||||
|
video_path = os.path.join(aweme_path, file_name + "_video.mp4")
|
||||||
|
|
||||||
|
if os.path.exists(video_path):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
url = awemeDict["video"]["play_addr"]["url_list"][0]
|
||||||
|
if url != "":
|
||||||
|
self.isdwownload = False
|
||||||
|
self.alltask.append(
|
||||||
|
self.pool.submit(self.progressBarDownload, url, video_path, "[ 视频 ]:" + desc))
|
||||||
|
except Exception as e:
|
||||||
|
print("[ 警告 ]:视频下载失败,请重试... 作品名: " + file_name + "\r\n")
|
||||||
|
|
||||||
|
# 下载 图集
|
||||||
|
if awemeDict["awemeType"] == 1:
|
||||||
|
for ind, image in enumerate(awemeDict["images"]):
|
||||||
|
image_path = os.path.join(aweme_path, file_name + "_image_" + str(ind) + ".jpeg")
|
||||||
|
if os.path.exists(image_path):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
url = image["url_list"][0]
|
||||||
|
if url != "":
|
||||||
|
self.isdwownload = False
|
||||||
|
self.alltask.append(
|
||||||
|
self.pool.submit(self.progressBarDownload, url, image_path, "[ 图集 ]:" + desc))
|
||||||
|
except Exception as e:
|
||||||
|
print("[ 警告 ]:图片下载失败,请重试... 作品名: " + file_name + "\r\n")
|
||||||
|
|
||||||
|
# 下载 音乐
|
||||||
|
if self.music:
|
||||||
|
music_name = utils.replaceStr(awemeDict["music"]["title"])
|
||||||
|
music_path = os.path.join(aweme_path, file_name + "_music_" + music_name + ".mp3")
|
||||||
|
|
||||||
|
if os.path.exists(music_path):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
url = awemeDict["music"]["play_url"]["url_list"][0]
|
||||||
|
if url != "":
|
||||||
|
self.isdwownload = False
|
||||||
|
self.alltask.append(
|
||||||
|
self.pool.submit(self.progressBarDownload, url, music_path, "[ 原声 ]:" + desc))
|
||||||
|
except Exception as e:
|
||||||
|
print("[ 警告 ]:音乐(原声)下载失败,请重试... 作品名: " + file_name + "\r\n")
|
||||||
|
|
||||||
|
# 下载 cover
|
||||||
|
if self.cover and awemeDict["awemeType"] == 0:
|
||||||
|
cover_path = os.path.join(aweme_path, file_name + "_cover.jpeg")
|
||||||
|
|
||||||
|
if os.path.exists(cover_path):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
url = awemeDict["video"]["cover"]["url_list"][0]
|
||||||
|
if url != "":
|
||||||
|
self.isdwownload = False
|
||||||
|
self.alltask.append(
|
||||||
|
self.pool.submit(self.progressBarDownload, url, cover_path, "[ 封面 ]:" + desc))
|
||||||
|
except Exception as e:
|
||||||
|
print("[ 警告 ]:cover下载失败,请重试... 作品名: " + file_name + "\r\n")
|
||||||
|
|
||||||
|
# 下载 avatar
|
||||||
|
if self.avatar:
|
||||||
|
avatar_path = os.path.join(aweme_path, file_name + "_avatar.jpeg")
|
||||||
|
|
||||||
|
if os.path.exists(avatar_path):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
url = awemeDict["author"]["avatar"]["url_list"][0]
|
||||||
|
if url != "":
|
||||||
|
self.isdwownload = False
|
||||||
|
self.alltask.append(
|
||||||
|
self.pool.submit(self.progressBarDownload, url, avatar_path, "[ 头像 ]:" + desc))
|
||||||
|
except Exception as e:
|
||||||
|
print("[ 警告 ]:avatar下载失败,请重试... 作品名: " + file_name + "\r\n")
|
||||||
|
except Exception as e:
|
||||||
|
print("[ 错误 ]:下载作品时出错\r\n")
|
||||||
|
|
||||||
|
def userDownload(self, awemeList: list, savePath=os.getcwd()):
|
||||||
|
if awemeList is None:
|
||||||
|
return
|
||||||
|
if not os.path.exists(savePath):
|
||||||
|
os.mkdir(savePath)
|
||||||
|
|
||||||
|
self.alltask = []
|
||||||
|
self.pool = ThreadPoolExecutor(max_workers=self.thread)
|
||||||
|
|
||||||
|
start = time.time() # 开始时间
|
||||||
|
|
||||||
|
for aweme in awemeList:
|
||||||
|
self.awemeDownload(awemeDict=aweme, savePath=savePath)
|
||||||
|
|
||||||
|
wait(self.alltask, return_when=ALL_COMPLETED)
|
||||||
|
|
||||||
|
# 检查下载是否完成
|
||||||
|
while True:
|
||||||
|
print("[ 提示 ]:正在检查下载是否完成...")
|
||||||
|
self.isdwownload = True
|
||||||
|
# 下载上一步失败的
|
||||||
|
for aweme in awemeList:
|
||||||
|
self.awemeDownload(awemeDict=aweme, savePath=savePath)
|
||||||
|
|
||||||
|
wait(self.alltask, return_when=ALL_COMPLETED)
|
||||||
|
|
||||||
|
if self.isdwownload:
|
||||||
|
break
|
||||||
|
|
||||||
|
end = time.time() # 结束时间
|
||||||
|
print('\n' + '[下载完成]:耗时: %d分钟%d秒\n' % (int((end - start) / 60), ((end - start) % 60))) # 输出下载用时时间
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
pass
|
@ -1,274 +1,315 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- encoding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
'''
|
'''
|
||||||
@Description:TikTok.py
|
@FileName : result.py
|
||||||
@Date :2023/02/11 13:06:23
|
@Project : apiproxy
|
||||||
@Author :imgyh
|
@Description:
|
||||||
@version :1.0
|
@Author : imgyh
|
||||||
@Github :https://github.com/imgyh
|
@Mail : admin@imgyh.com
|
||||||
@Mail :admin@imgyh.com
|
@Github : https://github.com/imgyh
|
||||||
-------------------------------------------------
|
@Site : https://www.imgyh.com
|
||||||
Change Log :
|
@Date : 2023/5/12 15:16
|
||||||
-------------------------------------------------
|
@Version : v1.0
|
||||||
'''
|
@ChangeLog
|
||||||
|
------------------------------------------------
|
||||||
import time
|
|
||||||
import copy
|
------------------------------------------------
|
||||||
|
'''
|
||||||
class Result(object):
|
|
||||||
def __init__(self):
|
import time
|
||||||
# 作者信息
|
import copy
|
||||||
self.authorDict = {
|
|
||||||
"avatar_thumb": {
|
|
||||||
"height": "",
|
class Result(object):
|
||||||
"uri": "",
|
def __init__(self):
|
||||||
"url_list": [],
|
# 作者信息
|
||||||
"width": ""
|
self.authorDict = {
|
||||||
},
|
"avatar_thumb": {
|
||||||
"avatar": {
|
"height": "",
|
||||||
"height": "",
|
"uri": "",
|
||||||
"uri": "",
|
"url_list": [],
|
||||||
"url_list": [],
|
"width": ""
|
||||||
"width": ""
|
},
|
||||||
},
|
"avatar": {
|
||||||
"cover_url": {
|
"height": "",
|
||||||
"height": "",
|
"uri": "",
|
||||||
"uri": "",
|
"url_list": [],
|
||||||
"url_list": [],
|
"width": ""
|
||||||
"width": ""
|
},
|
||||||
},
|
"cover_url": {
|
||||||
# 喜欢的作品数
|
"height": "",
|
||||||
"favoriting_count": "",
|
"uri": "",
|
||||||
# 粉丝数
|
"url_list": [],
|
||||||
"follower_count": "",
|
"width": ""
|
||||||
# 关注数
|
},
|
||||||
"following_count": "",
|
# 喜欢的作品数
|
||||||
# 昵称
|
"favoriting_count": "",
|
||||||
"nickname": "",
|
# 粉丝数
|
||||||
# 是否允许下载
|
"follower_count": "",
|
||||||
"prevent_download": "",
|
# 关注数
|
||||||
# 用户 url id
|
"following_count": "",
|
||||||
"sec_uid": "",
|
# 昵称
|
||||||
# 是否私密账号
|
"nickname": "",
|
||||||
"secret": "",
|
# 是否允许下载
|
||||||
# 短id
|
"prevent_download": "",
|
||||||
"short_id": "",
|
# 用户 url id
|
||||||
# 签名
|
"sec_uid": "",
|
||||||
"signature": "",
|
# 是否私密账号
|
||||||
# 总获赞数
|
"secret": "",
|
||||||
"total_favorited": "",
|
# 短id
|
||||||
# 用户id
|
"short_id": "",
|
||||||
"uid": "",
|
# 签名
|
||||||
# 用户自定义唯一id 抖音号
|
"signature": "",
|
||||||
"unique_id": "",
|
# 总获赞数
|
||||||
# 年龄
|
"total_favorited": "",
|
||||||
"user_age": "",
|
# 用户id
|
||||||
|
"uid": "",
|
||||||
}
|
# 用户自定义唯一id 抖音号
|
||||||
# 图片信息
|
"unique_id": "",
|
||||||
self.picDict = {
|
# 年龄
|
||||||
"height": "",
|
"user_age": "",
|
||||||
"mask_url_list": "",
|
|
||||||
"uri": "",
|
}
|
||||||
"url_list": [],
|
# 图片信息
|
||||||
"width": ""
|
self.picDict = {
|
||||||
}
|
"height": "",
|
||||||
# 音乐信息
|
"mask_url_list": "",
|
||||||
self.musicDict = {
|
"uri": "",
|
||||||
"cover_hd": {
|
"url_list": [],
|
||||||
"height": "",
|
"width": ""
|
||||||
"uri": "",
|
}
|
||||||
"url_list": [],
|
# 音乐信息
|
||||||
"width": ""
|
self.musicDict = {
|
||||||
},
|
"cover_hd": {
|
||||||
"cover_large": {
|
"height": "",
|
||||||
"height": "",
|
"uri": "",
|
||||||
"uri": "",
|
"url_list": [],
|
||||||
"url_list": [],
|
"width": ""
|
||||||
"width": ""
|
},
|
||||||
},
|
"cover_large": {
|
||||||
"cover_medium": {
|
"height": "",
|
||||||
"height": "",
|
"uri": "",
|
||||||
"uri": "",
|
"url_list": [],
|
||||||
"url_list": [],
|
"width": ""
|
||||||
"width": ""
|
},
|
||||||
},
|
"cover_medium": {
|
||||||
"cover_thumb": {
|
"height": "",
|
||||||
"height": "",
|
"uri": "",
|
||||||
"uri": "",
|
"url_list": [],
|
||||||
"url_list": [],
|
"width": ""
|
||||||
"width": ""
|
},
|
||||||
},
|
"cover_thumb": {
|
||||||
# 音乐作者抖音号
|
"height": "",
|
||||||
"owner_handle": "",
|
"uri": "",
|
||||||
# 音乐作者id
|
"url_list": [],
|
||||||
"owner_id": "",
|
"width": ""
|
||||||
# 音乐作者昵称
|
},
|
||||||
"owner_nickname": "",
|
# 音乐作者抖音号
|
||||||
"play_url": {
|
"owner_handle": "",
|
||||||
"height": "",
|
# 音乐作者id
|
||||||
"uri": "",
|
"owner_id": "",
|
||||||
"url_key": "",
|
# 音乐作者昵称
|
||||||
"url_list": [],
|
"owner_nickname": "",
|
||||||
"width": ""
|
"play_url": {
|
||||||
},
|
"height": "",
|
||||||
# 音乐名字
|
"uri": "",
|
||||||
"title": "",
|
"url_key": "",
|
||||||
}
|
"url_list": [],
|
||||||
# 视频信息
|
"width": ""
|
||||||
self.videoDict = {
|
},
|
||||||
"play_addr": {
|
# 音乐名字
|
||||||
"uri": "",
|
"title": "",
|
||||||
"url_list": "",
|
}
|
||||||
},
|
# 视频信息
|
||||||
"cover_original_scale": {
|
self.videoDict = {
|
||||||
"height": "",
|
"play_addr": {
|
||||||
"uri": "",
|
"uri": "",
|
||||||
"url_list": [],
|
"url_list": [],
|
||||||
"width": ""
|
},
|
||||||
},
|
"cover_original_scale": {
|
||||||
"dynamic_cover": {
|
"height": "",
|
||||||
"height": "",
|
"uri": "",
|
||||||
"uri": "",
|
"url_list": [],
|
||||||
"url_list": [],
|
"width": ""
|
||||||
"width": ""
|
},
|
||||||
},
|
"dynamic_cover": {
|
||||||
"origin_cover": {
|
"height": "",
|
||||||
"height": "",
|
"uri": "",
|
||||||
"uri": "",
|
"url_list": [],
|
||||||
"url_list": [],
|
"width": ""
|
||||||
"width": ""
|
},
|
||||||
},
|
"origin_cover": {
|
||||||
"cover": {
|
"height": "",
|
||||||
"height": "",
|
"uri": "",
|
||||||
"uri": "",
|
"url_list": [],
|
||||||
"url_list": [],
|
"width": ""
|
||||||
"width": ""
|
},
|
||||||
}
|
"cover": {
|
||||||
}
|
"height": "",
|
||||||
# 作品信息
|
"uri": "",
|
||||||
self.awemeDict = {
|
"url_list": [],
|
||||||
# 作品创建时间
|
"width": ""
|
||||||
"create_time": "",
|
}
|
||||||
# awemeType=0 视频, awemeType=1 图集
|
}
|
||||||
"awemeType": "",
|
# mix信息
|
||||||
# 作品 id
|
self.mixInfo = {
|
||||||
"aweme_id": "",
|
"cover_url": {
|
||||||
# 作者信息
|
"height": "",
|
||||||
"author": self.authorDict,
|
"uri": "",
|
||||||
# 作品描述
|
"url_list": [],
|
||||||
"desc": "",
|
"width": 720
|
||||||
# 图片
|
},
|
||||||
"images": [],
|
"ids": "",
|
||||||
# 音乐
|
"is_serial_mix": "",
|
||||||
"music": self.musicDict,
|
"mix_id": "",
|
||||||
# 视频
|
"mix_name": "",
|
||||||
"video": self.videoDict,
|
"mix_pic_type": "",
|
||||||
# 作品信息统计
|
"mix_type": "",
|
||||||
"statistics": {
|
"statis": {
|
||||||
"admire_count": "",
|
"current_episode": "",
|
||||||
"collect_count": "",
|
"updated_to_episode": ""
|
||||||
"comment_count": "",
|
}
|
||||||
"digg_count": "",
|
}
|
||||||
"play_count": "",
|
# 作品信息
|
||||||
"share_count": ""
|
self.awemeDict = {
|
||||||
}
|
# 作品创建时间
|
||||||
}
|
"create_time": "",
|
||||||
# 用户作品信息
|
# awemeType=0 视频, awemeType=1 图集, awemeType=2 直播
|
||||||
self.awemeList = []
|
"awemeType": "",
|
||||||
# 直播信息
|
# 作品 id
|
||||||
self.liveDict = {
|
"aweme_id": "",
|
||||||
# 是否在播
|
# 作者信息
|
||||||
"status": "",
|
"author": self.authorDict,
|
||||||
# 直播标题
|
# 作品描述
|
||||||
"title": "",
|
"desc": "",
|
||||||
# 观看人数
|
# 图片
|
||||||
"user_count": "",
|
"images": [],
|
||||||
# 昵称
|
# 音乐
|
||||||
"nickname": "",
|
"music": self.musicDict,
|
||||||
# sec_uid
|
# 合集
|
||||||
"sec_uid": "",
|
"mix_info": self.mixInfo,
|
||||||
# 直播间观看状态
|
# 视频
|
||||||
"display_long": "",
|
"video": self.videoDict,
|
||||||
# 推流
|
# 作品信息统计
|
||||||
"flv_pull_url": "",
|
"statistics": {
|
||||||
# 分区
|
"admire_count": "",
|
||||||
"partition": "",
|
"collect_count": "",
|
||||||
"sub_partition": ""
|
"comment_count": "",
|
||||||
}
|
"digg_count": "",
|
||||||
|
"play_count": "",
|
||||||
# 将得到的json数据(dataRaw)精简成自己定义的数据(dataNew)
|
"share_count": ""
|
||||||
# 转换得到的数据
|
}
|
||||||
def dataConvert(self, awemeType, dataNew, dataRaw):
|
}
|
||||||
for item in dataNew:
|
# 用户作品信息
|
||||||
try:
|
self.awemeList = []
|
||||||
# 作品创建时间
|
# 直播信息
|
||||||
if item == "create_time":
|
self.liveDict = {
|
||||||
dataNew['create_time'] = time.strftime(
|
# awemeType=0 视频, awemeType=1 图集, awemeType=2 直播
|
||||||
"%Y-%m-%d %H.%M.%S", time.localtime(dataRaw['create_time']))
|
"awemeType": "",
|
||||||
continue
|
# 是否在播
|
||||||
# 设置 awemeType
|
"status": "",
|
||||||
if item == "awemeType":
|
# 直播标题
|
||||||
dataNew["awemeType"] = awemeType
|
"title": "",
|
||||||
continue
|
# 直播cover
|
||||||
# 当 解析的链接 是图片时
|
"cover": "",
|
||||||
if item == "images":
|
# 头像
|
||||||
if awemeType == 1:
|
"avatar": "",
|
||||||
for image in dataRaw[item]:
|
# 观看人数
|
||||||
for i in image:
|
"user_count": "",
|
||||||
self.picDict[i] = image[i]
|
# 昵称
|
||||||
# 字典要深拷贝
|
"nickname": "",
|
||||||
self.awemeDict["images"].append(copy.deepcopy(self.picDict))
|
# sec_uid
|
||||||
continue
|
"sec_uid": "",
|
||||||
# 当 解析的链接 是视频时
|
# 直播间观看状态
|
||||||
if item == "video":
|
"display_long": "",
|
||||||
if awemeType == 0:
|
# 推流
|
||||||
self.dataConvert(awemeType, dataNew[item], dataRaw[item])
|
"flv_pull_url": "",
|
||||||
continue
|
# 分区
|
||||||
# 将小头像放大
|
"partition": "",
|
||||||
if item == "avatar":
|
"sub_partition": "",
|
||||||
for i in dataNew[item]:
|
# 最清晰的地址
|
||||||
if i == "url_list":
|
"flv_pull_url0": "",
|
||||||
for j in self.awemeDict["author"]["avatar_thumb"]["url_list"]:
|
}
|
||||||
dataNew[item][i].append(j.replace("100x100", "1080x1080"))
|
|
||||||
elif i == "uri":
|
|
||||||
dataNew[item][i] = self.awemeDict["author"]["avatar_thumb"][i].replace("100x100",
|
|
||||||
"1080x1080")
|
# 将得到的json数据(dataRaw)精简成自己定义的数据(dataNew)
|
||||||
else:
|
# 转换得到的数据
|
||||||
dataNew[item][i] = self.awemeDict["author"]["avatar_thumb"][i]
|
def dataConvert(self, awemeType, dataNew, dataRaw):
|
||||||
continue
|
for item in dataNew:
|
||||||
|
try:
|
||||||
# 原来的json是[{}] 而我们的是 {}
|
# 作品创建时间
|
||||||
if item == "cover_url":
|
if item == "create_time":
|
||||||
self.dataConvert(awemeType, dataNew[item], dataRaw[item][0])
|
dataNew['create_time'] = time.strftime(
|
||||||
continue
|
"%Y-%m-%d %H.%M.%S", time.localtime(dataRaw['create_time']))
|
||||||
|
continue
|
||||||
# 根据 uri 获取 1080p 视频
|
# 设置 awemeType
|
||||||
if item == "play_addr":
|
if item == "awemeType":
|
||||||
dataNew[item]["uri"] = dataRaw["bit_rate"][0]["play_addr"]["uri"]
|
dataNew["awemeType"] = awemeType
|
||||||
# 使用 这个api 可以获得1080p
|
continue
|
||||||
dataNew[item]["url_list"] = "https://aweme.snssdk.com/aweme/v1/play/?video_id=%s&ratio=1080p&line=0" \
|
# 当 解析的链接 是图片时
|
||||||
% dataNew[item]["uri"]
|
if item == "images":
|
||||||
continue
|
if awemeType == 1:
|
||||||
|
for image in dataRaw[item]:
|
||||||
# 常规 递归遍历 字典
|
for i in image:
|
||||||
if isinstance(dataNew[item], dict):
|
self.picDict[i] = image[i]
|
||||||
self.dataConvert(awemeType, dataNew[item], dataRaw[item])
|
# 字典要深拷贝
|
||||||
else:
|
self.awemeDict["images"].append(copy.deepcopy(self.picDict))
|
||||||
# 赋值
|
continue
|
||||||
dataNew[item] = dataRaw[item]
|
# 当 解析的链接 是视频时
|
||||||
except Exception as e:
|
if item == "video":
|
||||||
# 删除这个警告, 总是让人误会出错了
|
if awemeType == 0:
|
||||||
# print("[ 警告 ]:转换数据时在接口中未找到 %s\r" % (item))
|
self.dataConvert(awemeType, dataNew[item], dataRaw[item])
|
||||||
pass
|
continue
|
||||||
|
# 将小头像放大
|
||||||
def clearDict(self, data):
|
if item == "avatar":
|
||||||
for item in data:
|
for i in dataNew[item]:
|
||||||
# 常规 递归遍历 字典
|
if i == "url_list":
|
||||||
if isinstance(data[item], dict):
|
for j in self.awemeDict["author"]["avatar_thumb"]["url_list"]:
|
||||||
self.clearDict(data[item])
|
dataNew[item][i].append(j.replace("100x100", "1080x1080"))
|
||||||
elif isinstance(data[item], list):
|
elif i == "uri":
|
||||||
data[item] = []
|
dataNew[item][i] = self.awemeDict["author"]["avatar_thumb"][i].replace("100x100",
|
||||||
else:
|
"1080x1080")
|
||||||
data[item] = ""
|
else:
|
||||||
|
dataNew[item][i] = self.awemeDict["author"]["avatar_thumb"][i]
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 原来的json是[{}] 而我们的是 {}
|
||||||
|
if item == "cover_url":
|
||||||
|
self.dataConvert(awemeType, dataNew[item], dataRaw[item][0])
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 根据 uri 获取 1080p 视频
|
||||||
|
if item == "play_addr":
|
||||||
|
dataNew[item]["uri"] = dataRaw["bit_rate"][0]["play_addr"]["uri"]
|
||||||
|
# 使用 这个api 可以获得1080p
|
||||||
|
# dataNew[item]["url_list"] = "https://aweme.snssdk.com/aweme/v1/play/?video_id=%s&ratio=1080p&line=0" \
|
||||||
|
# % dataNew[item]["uri"]
|
||||||
|
dataNew[item]["url_list"] = copy.deepcopy(dataRaw["bit_rate"][0]["play_addr"]["url_list"])
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 常规 递归遍历 字典
|
||||||
|
if isinstance(dataNew[item], dict):
|
||||||
|
self.dataConvert(awemeType, dataNew[item], dataRaw[item])
|
||||||
|
else:
|
||||||
|
# 赋值
|
||||||
|
dataNew[item] = dataRaw[item]
|
||||||
|
except Exception as e:
|
||||||
|
# 删除这个警告, 总是让人误会出错了
|
||||||
|
# print("[ 警告 ]:转换数据时在接口中未找到 %s\r" % (item))
|
||||||
|
pass
|
||||||
|
|
||||||
|
def clearDict(self, data):
|
||||||
|
for item in data:
|
||||||
|
# 常规 递归遍历 字典
|
||||||
|
if isinstance(data[item], dict):
|
||||||
|
self.clearDict(data[item])
|
||||||
|
elif isinstance(data[item], list):
|
||||||
|
data[item] = []
|
||||||
|
else:
|
||||||
|
data[item] = ""
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
pass
|
@ -1,84 +1,80 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- encoding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
'''
|
'''
|
||||||
@Description:TikTok.py
|
@FileName : urls.py
|
||||||
@Date :2023/02/11 13:06:23
|
@Project : apiproxy
|
||||||
@Author :imgyh
|
@Description:
|
||||||
@version :1.0
|
@Author : imgyh
|
||||||
@Github :https://github.com/imgyh
|
@Mail : admin@imgyh.com
|
||||||
@Mail :admin@imgyh.com
|
@Github : https://github.com/imgyh
|
||||||
-------------------------------------------------
|
@Site : https://www.imgyh.com
|
||||||
Change Log :
|
@Date : 2023/5/12 15:04
|
||||||
-------------------------------------------------
|
@Version : v1.0
|
||||||
'''
|
@ChangeLog
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
class Urls(object):
|
------------------------------------------------
|
||||||
def __init__(self):
|
'''
|
||||||
# https://langyue.cc/APIdocV1.0.html
|
|
||||||
######################################### WEB #########################################
|
|
||||||
# 首页推荐
|
class Urls(object):
|
||||||
self.TAB_FEED = 'https://www.douyin.com/aweme/v1/web/tab/feed/?'
|
def __init__(self):
|
||||||
|
######################################### WEB #########################################
|
||||||
# 用户短信息(给多少个用户secid就返回多少的用户信息)
|
# 首页推荐
|
||||||
self.USER_SHORT_INFO = 'https://www.douyin.com/aweme/v1/web/im/user/info/?'
|
self.TAB_FEED = 'https://www.douyin.com/aweme/v1/web/tab/feed/?'
|
||||||
|
|
||||||
# 用户详细信息
|
# 用户短信息(给多少个用户secid就返回多少的用户信息)
|
||||||
self.USER_DETAIL = 'https://www.douyin.com/aweme/v1/web/user/profile/other/?'
|
self.USER_SHORT_INFO = 'https://www.douyin.com/aweme/v1/web/im/user/info/?'
|
||||||
|
|
||||||
# 用户作品
|
# 用户详细信息
|
||||||
# cookies 暂时只需要 __ac_signature, s_v_web_id两个参数, 好像会过期
|
self.USER_DETAIL = 'https://www.douyin.com/aweme/v1/web/user/profile/other/?'
|
||||||
# url 暂时不需要携带 msToken, X-Bogus, _signature
|
|
||||||
# 每次返回数据很少
|
# 用户作品
|
||||||
# self.USER_POST = 'https://m.douyin.com/web/api/v2/aweme/post/?'
|
self.USER_POST = 'https://www.douyin.com/aweme/v1/web/aweme/post/?'
|
||||||
# 2023/02/19 失效
|
|
||||||
self.USER_POST = 'https://www.douyin.com/aweme/v1/web/aweme/post/?'
|
# 作品信息
|
||||||
|
self.POST_DETAIL = 'https://www.douyin.com/aweme/v1/web/aweme/detail/?'
|
||||||
# 作品信息
|
|
||||||
self.POST_DETAIL = 'https://www.douyin.com/aweme/v1/web/aweme/detail/?'
|
# 用户喜欢A
|
||||||
|
# 需要 odin_tt
|
||||||
# 用户喜欢A
|
self.USER_FAVORITE_A = 'https://www.douyin.com/aweme/v1/web/aweme/favorite/?'
|
||||||
# 需要 odin_tt
|
|
||||||
self.USER_FAVORITE_A = 'https://www.douyin.com/aweme/v1/web/aweme/favorite/?'
|
# 用户喜欢B
|
||||||
|
self.USER_FAVORITE_B = 'https://www.iesdouyin.com/web/api/v2/aweme/like/?'
|
||||||
# 用户喜欢B
|
|
||||||
self.USER_FAVORITE_B = 'https://www.iesdouyin.com/web/api/v2/aweme/like/?'
|
# 用户历史
|
||||||
|
self.USER_HISTORY = 'https://www.douyin.com/aweme/v1/web/history/read/?'
|
||||||
# 用户历史
|
|
||||||
self.USER_HISTORY = 'https://www.douyin.com/aweme/v1/web/history/read/?'
|
# 用户收藏
|
||||||
|
self.USER_COLLECTION = 'https://www.douyin.com/aweme/v1/web/aweme/listcollection/?'
|
||||||
# 用户收藏
|
|
||||||
self.USER_COLLECTION = 'https://www.douyin.com/aweme/v1/web/aweme/listcollection/?'
|
# 用户评论
|
||||||
|
self.COMMENT = 'https://www.douyin.com/aweme/v1/web/comment/list/?'
|
||||||
# 用户评论
|
|
||||||
self.COMMENT = 'https://www.douyin.com/aweme/v1/web/comment/list/?'
|
# 首页朋友作品
|
||||||
|
self.FRIEND_FEED = 'https://www.douyin.com/aweme/v1/web/familiar/feed/?'
|
||||||
# 首页朋友作品
|
|
||||||
self.FRIEND_FEED = 'https://www.douyin.com/aweme/v1/web/familiar/feed/?'
|
# 关注用户作品
|
||||||
|
self.FOLLOW_FEED = 'https://www.douyin.com/aweme/v1/web/follow/feed/?'
|
||||||
# 关注用户作品
|
|
||||||
self.FOLLOW_FEED = 'https://www.douyin.com/aweme/v1/web/follow/feed/?'
|
# 合集下所有作品
|
||||||
|
# 只需要X-Bogus
|
||||||
# 合集下所有作品
|
self.USER_MIX = 'https://www.douyin.com/aweme/v1/web/mix/aweme/?'
|
||||||
# 只需要X-Bogus
|
|
||||||
self.USER_MIX = 'https://www.douyin.com/aweme/v1/web/mix/aweme/?'
|
# 用户所有合集列表
|
||||||
|
# 需要 ttwid
|
||||||
# 用户所有合集列表
|
self.USER_MIX_LIST = 'https://www.douyin.com/aweme/v1/web/mix/list/?'
|
||||||
# 需要 ttwid
|
|
||||||
self.USER_MIX_LIST = 'https://www.douyin.com/aweme/v1/web/mix/list/?'
|
# 直播
|
||||||
|
self.LIVE = 'https://live.douyin.com/webcast/room/web/enter/?'
|
||||||
# 直播
|
self.LIVE2 = 'https://webcast.amemv.com/webcast/room/reflow/info/?'
|
||||||
self.LIVE = 'https://live.douyin.com/webcast/room/web/enter/?'
|
|
||||||
|
# 音乐
|
||||||
# 音乐
|
self.MUSIC = 'https://www.douyin.com/aweme/v1/web/music/aweme/?'
|
||||||
self.MUSIC = 'https://www.douyin.com/aweme/v1/web/music/aweme/?'
|
|
||||||
|
#######################################################################################
|
||||||
# X-Bogus Path
|
|
||||||
# 60 秒内,请求同一URI累计超过 600 次,封锁IP 300 秒
|
|
||||||
self.GET_XB_PATH = 'https://tiktok.199933.xyz/xb'
|
if __name__ == '__main__':
|
||||||
|
pass
|
||||||
#######################################################################################
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
Urls()
|
|
18
apiproxy/tiktok/__init__.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
'''
|
||||||
|
@FileName : __init__.py.py
|
||||||
|
@Project : apiproxy
|
||||||
|
@Description:
|
||||||
|
@Author : imgyh
|
||||||
|
@Mail : admin@imgyh.com
|
||||||
|
@Github : https://github.com/imgyh
|
||||||
|
@Site : https://www.imgyh.com
|
||||||
|
@Date : 2023/5/12 14:43
|
||||||
|
@Version : v1.0
|
||||||
|
@ChangeLog
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
------------------------------------------------
|
||||||
|
'''
|
111
config.yml
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
#######################################
|
||||||
|
# 说明:
|
||||||
|
# 1. 井号(#)为注释
|
||||||
|
# 2. 缩进严格对齐,使用空格缩进, 注意有些冒号后面有一个空格, 有些没有空格
|
||||||
|
# 3. 请使用英文字符
|
||||||
|
# 4. 更多yaml语法请上网查看
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
|
||||||
|
# 作品(视频或图集)、直播、合集、音乐集合、个人主页的分享链接或者电脑浏览器网址
|
||||||
|
# (删除文案, 保证只有URL, https://v.douyin.com/kcvMpuN/ 或者 https://www.douyin.com/开头的)
|
||||||
|
# 可以设置多个链接, 确保至少一个链接
|
||||||
|
# 必选
|
||||||
|
link:
|
||||||
|
- https://live.douyin.com/759547612580
|
||||||
|
- https://v.douyin.com/BugmVVD/
|
||||||
|
- https://v.douyin.com/BugrFTN/
|
||||||
|
- https://v.douyin.com/B38oovu/
|
||||||
|
- https://v.douyin.com/S6YMNXs/
|
||||||
|
|
||||||
|
# 下载保存位置, 默认当前文件位置
|
||||||
|
# 必选
|
||||||
|
path: C:\project\test333
|
||||||
|
|
||||||
|
# 是否下载视频中的音乐(True/False), 默认为True
|
||||||
|
# 可选
|
||||||
|
music: True
|
||||||
|
|
||||||
|
# 是否下载视频的封面(True/False), 默认为True, 当下载视频时有效
|
||||||
|
# 可选
|
||||||
|
cover: True
|
||||||
|
|
||||||
|
# 是否下载作者的头像(True/False), 默认为True
|
||||||
|
# 可选
|
||||||
|
avatar: True
|
||||||
|
|
||||||
|
# 是否保存获取到的数据(True/False), 默认为True
|
||||||
|
# 可选
|
||||||
|
json: True
|
||||||
|
|
||||||
|
folderstyle: True # True -> 每个视频是一个单独的文件夹; False -> 所有视频共用一个文件夹
|
||||||
|
# True
|
||||||
|
# user_xxx_xxx
|
||||||
|
# - like/post/mix
|
||||||
|
# - 2022-11-28 13.09.56_xxx
|
||||||
|
# - 2022-11-28 13.09.56_xxx.mp4
|
||||||
|
# - 2022-11-29 12.09.56_xxx
|
||||||
|
# - 2022-11-29 12.09.56_xxx.mp4
|
||||||
|
|
||||||
|
# False
|
||||||
|
# user_xxx_xxx
|
||||||
|
# - like/post/mix
|
||||||
|
# - 2022-11-28 13.09.56_xxx.mp4
|
||||||
|
# - 2022-11-29 12.09.56_xxx.mp4
|
||||||
|
|
||||||
|
# link是个人主页时, 设置下载发布的作品(post)或喜欢的作品(like)或者用户所有合集(mix), 默认为post, 可以设置多种模式
|
||||||
|
# 可选
|
||||||
|
mode:
|
||||||
|
- post
|
||||||
|
- like
|
||||||
|
- mix
|
||||||
|
|
||||||
|
# 下载作品个数设置
|
||||||
|
# 可选
|
||||||
|
number:
|
||||||
|
post: 5 # 主页下作品下载个数设置, 默认为0 全部下载
|
||||||
|
like: 5 # 主页下喜欢下载个数设置, 默认为0 全部下载
|
||||||
|
allmix: 1 # 主页下合集下载个数设置, 默认为0 全部下载
|
||||||
|
mix: 5 # 单个合集下作品下载个数设置, 默认为0 全部下载
|
||||||
|
music: 5 # 音乐(原声)下作品下载个数设置, 默认为0 全部下载
|
||||||
|
|
||||||
|
database: True # 如果不使用数据库, 增量更新将不可用
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# 增量下载, 下载作品范围: 抖音最新作品到本地的最新作品之间的作品, 如果本地没有该链接的任何视频则全部下载
|
||||||
|
# 可配合 number 选项一起使用
|
||||||
|
# 情况1: number(假如设置5) 和 increase(假如抖音博主更新了3条作品,本地并未下载) 则会获取5条数据并下载
|
||||||
|
# 情况2: number(假如设置5) 和 increase(假如抖音博主更新了6条作品,本地并未下载) 则会获取6条数据并下载
|
||||||
|
# 情况3: number(假如设置5) 和 increase(假如本地并未下载该博主视频) 则会获取所有的视频
|
||||||
|
# 情况4: 当获取主页所有mix时(mode是mix模式)比较特殊, number(allmix) 控制下载多少个合集, increase(allmix) 对每个合集进行增量更新
|
||||||
|
# 可选
|
||||||
|
increase:
|
||||||
|
post: False # 是否开启主页作品增量下载(True/False), 默认为False
|
||||||
|
like: False # 是否开启主页喜欢增量下载(True/False), 默认为False
|
||||||
|
allmix: False # 是否开启主页合集增量下载(True/False), 默认为False
|
||||||
|
mix: False # 是否开启单个合集下作品增量下载(True/False), 默认为False
|
||||||
|
music: False # 是否开启音乐(原声)下作品增量下载(True/False), 默认为False
|
||||||
|
|
||||||
|
# 设置线程数, 默认5个线程
|
||||||
|
# 可选
|
||||||
|
thread: 5
|
||||||
|
|
||||||
|
# cookie 请登录网页抖音后F12查看
|
||||||
|
# cookies 和 cookie 二选一, 要使用这种形式, 请注释下面的cookie
|
||||||
|
# 目前只需要msToken、ttwid、odin_tt、passport_csrf_token、sid_guard
|
||||||
|
# 可以动态添加, 程序会根据填的键查找,并没有写死, 如果抖音需要更多的cookie自己加上就行了
|
||||||
|
cookies:
|
||||||
|
msToken: xxx
|
||||||
|
ttwid: xxx
|
||||||
|
odin_tt: xxx
|
||||||
|
passport_csrf_token: xxx
|
||||||
|
sid_guard: xxx
|
||||||
|
|
||||||
|
# cookie 请登录网页抖音后F12查看
|
||||||
|
# cookies 和 cookie 二选一, 要使用这种形式, 请注释上面的cookies及包含的所有键值对
|
||||||
|
# 设置了这个后上面的cookies选项自动失效, 这个优先级更高
|
||||||
|
# 格式: "name1=value1; name2=value2;" 注意要加冒号
|
||||||
|
# 冒号中的内容包括不限于以下键值对, 如果抖音需要更多的cookie自己加上就行了
|
||||||
|
#cookie: "msToken=xxx; ttwid=xxx; odin_tt=xxx; passport_csrf_token=xxx; sid_guard=xxx;"
|
||||||
|
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 148 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 132 KiB |
Before Width: | Height: | Size: 157 KiB After Width: | Height: | Size: 157 KiB |
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 140 KiB |
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 147 KiB After Width: | Height: | Size: 147 KiB |
BIN
img/alipay.jpg
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
img/wechat.jpg
Normal file
After Width: | Height: | Size: 29 KiB |
@ -1,3 +1,5 @@
|
|||||||
requests==2.28.2
|
requests==2.28.2
|
||||||
flask==2.2.2
|
flask==2.2.2
|
||||||
pyinstaller==5.7.0
|
pyinstaller==5.7.0
|
||||||
|
tqdm==4.65.0
|
||||||
|
PyYAML==6.0
|
4
requirements_docker.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
requests==2.28.2
|
||||||
|
flask==2.2.2
|
||||||
|
tqdm==4.65.0
|
||||||
|
PyYAML==6.0
|
4
static/css/font-awesome.css
vendored
@ -6,8 +6,8 @@
|
|||||||
* -------------------------- */
|
* -------------------------- */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'FontAwesome';
|
font-family: 'FontAwesome';
|
||||||
src: url('https://cdn.jsdelivr.net/gh/imgyh/tiktok/static/fonts/fontawesome-webfont.eot?v=4.7.0');
|
src: url('../fonts/fontawesome-webfont.eot?v=4.7.0');
|
||||||
src: url('https://cdn.jsdelivr.net/gh/imgyh/tiktok/static/fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'), url('https://cdn.jsdelivr.net/gh/imgyh/tiktok/static/fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'), url('https://cdn.jsdelivr.net/gh/imgyh/tiktok/static/fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'), url('https://cdn.jsdelivr.net/gh/imgyh/tiktok/static/fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'), url('https://cdn.jsdelivr.net/gh/imgyh/tiktok/static/fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');
|
src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
1264
static/css/style.css
@ -1,163 +1,237 @@
|
|||||||
// 发 post 请求
|
// 发 post 请求
|
||||||
function SendAjax() {
|
function SendAjax() {
|
||||||
var data = {};
|
var data = {};
|
||||||
data = $('#form1').serialize();
|
data = $('#form1').serialize();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
url: "/douyin",
|
url: "/douyin",
|
||||||
data: data,
|
data: data,
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
beforeSend: function () {
|
beforeSend: function () {
|
||||||
$("#loading").attr("style", "display:block;");//在请求后台数据之前显示loading图标
|
$("#loading").attr("style", "display:block;");//在请求后台数据之前显示loading图标
|
||||||
$("#download").attr("style", "display:none;");//隐藏 download
|
$("#download").attr("style", "display:none;");//隐藏 download
|
||||||
},
|
},
|
||||||
success: function (result) {
|
success: function (result) {
|
||||||
// console.log(result);//打印服务端返回的数据(调试用)
|
// console.log(result);//打印服务端返回的数据(调试用)
|
||||||
if (result.status_code === 200) {
|
if (result.status_code === 200) {
|
||||||
if (result.awemeType === 0) {
|
result = result.data
|
||||||
$("#awemeType").html("预览视频");
|
if (result.awemeType === 0) {
|
||||||
$("#video").attr("href", result.video.play_addr.url_list);
|
$("#awemeType").html("预览视频");
|
||||||
$("#pre_video").attr("src", result.video.play_addr.url_list);
|
$("#AwemeOrLive").html("下载视频");
|
||||||
$("#video").attr("style", "display:inline;");//显示 video
|
$("#video").attr("href", removeHttp(result.video.play_addr.url_list[0]));
|
||||||
}
|
$("#pre_video").attr("src", removeHttp(result.video.play_addr.url_list[0]));
|
||||||
if (result.awemeType === 1) {
|
$("#video").attr("style", "display:inline;");//显示 video
|
||||||
$("#awemeType").html("预览图集");
|
}
|
||||||
var images = result.images;
|
if (result.awemeType === 1) {
|
||||||
var licontent = ""; // 拼接输入的 li 标签的字符串
|
$("#awemeType").html("预览图集");
|
||||||
for (var i = 0; i < images.length; i++) {
|
var images = result.images;
|
||||||
licontent += "<li><img src= " + images[i].url_list[0] + "></li>"
|
var licontent = ""; // 拼接输入的 li 标签的字符串
|
||||||
}
|
for (var i = 0; i < images.length; i++) {
|
||||||
document.getElementById("images").innerHTML = licontent;
|
licontent += "<li><img src= " + removeHttp(images[i].url_list[0]) + "></li>"
|
||||||
$("#video").attr("style", "display:none;");//隐藏 video
|
}
|
||||||
}
|
document.getElementById("images").innerHTML = licontent;
|
||||||
$("#cover").attr("href", result.video.cover_original_scale.url_list[0]);
|
$("#video").attr("style", "display:none;");//隐藏 video
|
||||||
$("#pre_video").attr("poster", result.video.dynamic_cover.url_list[0]);
|
}
|
||||||
$("#music").attr("href", result.music.play_url.url_list[0]);
|
if (result.awemeType === 0 || result.awemeType === 1) {
|
||||||
|
$("#cover").attr("href", removeHttp(result.video.cover_original_scale.url_list[0]));
|
||||||
$("#avatar").attr("src", result.author.avatar.url_list[0]);
|
$("#pre_video").attr("poster", removeHttp(result.video.dynamic_cover.url_list[0]));
|
||||||
$("#avatar").attr("alt", result.author.nickname);
|
$("#music").attr("href", removeHttp(result.music.play_url.url_list[0]));
|
||||||
$("#nickname").html(result.author.nickname);
|
|
||||||
$("#desc").html(result.desc);
|
$("#avatar").attr("src", removeHttp(result.author.avatar.url_list[0]));
|
||||||
|
$("#avatar").attr("alt", result.author.nickname);
|
||||||
|
$("#nickname").html(result.author.nickname);
|
||||||
var count = result.statistics.digg_count;
|
$("#desc").html(result.desc);
|
||||||
var digg_count;
|
|
||||||
if (count < 1000) {
|
|
||||||
digg_count = count
|
var count = result.statistics.digg_count;
|
||||||
} else if (count >= 1000 && count < 10000) {
|
var digg_count;
|
||||||
digg_count = (count / 1000).toFixed(1) + "K"
|
if (count < 1000) {
|
||||||
} else {
|
digg_count = count
|
||||||
digg_count = (count / 10000).toFixed(1) + "W"
|
} else if (count >= 1000 && count < 10000) {
|
||||||
}
|
digg_count = (count / 1000).toFixed(1) + "K"
|
||||||
$("#aweme_digg_count").html(digg_count);
|
} else {
|
||||||
count = result.statistics.comment_count;
|
digg_count = (count / 10000).toFixed(1) + "W"
|
||||||
var comment_count;
|
}
|
||||||
if (count < 1000) {
|
$("#aweme_digg_count").html(digg_count);
|
||||||
comment_count = count
|
count = result.statistics.comment_count;
|
||||||
} else if (count >= 1000 && count < 10000) {
|
var comment_count;
|
||||||
comment_count = (count / 1000).toFixed(1) + "K"
|
if (count < 1000) {
|
||||||
} else {
|
comment_count = count
|
||||||
comment_count = (count / 10000).toFixed(1) + "W"
|
} else if (count >= 1000 && count < 10000) {
|
||||||
}
|
comment_count = (count / 1000).toFixed(1) + "K"
|
||||||
$("#aweme_comment_count").html(comment_count);
|
} else {
|
||||||
count = result.statistics.collect_count;
|
comment_count = (count / 10000).toFixed(1) + "W"
|
||||||
var collect_count;
|
}
|
||||||
if (count < 1000) {
|
$("#aweme_comment_count").html(comment_count);
|
||||||
collect_count = count
|
count = result.statistics.collect_count;
|
||||||
} else if (count >= 1000 && count < 10000) {
|
var collect_count;
|
||||||
collect_count = (count / 1000).toFixed(1) + "K"
|
if (count < 1000) {
|
||||||
} else {
|
collect_count = count
|
||||||
collect_count = (count / 10000).toFixed(1) + "W"
|
} else if (count >= 1000 && count < 10000) {
|
||||||
}
|
collect_count = (count / 1000).toFixed(1) + "K"
|
||||||
$("#aweme_collect_count").html(collect_count);
|
} else {
|
||||||
count = result.statistics.share_count;
|
collect_count = (count / 10000).toFixed(1) + "W"
|
||||||
var share_count;
|
}
|
||||||
if (count < 1000) {
|
$("#aweme_collect_count").html(collect_count);
|
||||||
share_count = count
|
count = result.statistics.share_count;
|
||||||
} else if (count >= 1000 && count < 10000) {
|
var share_count;
|
||||||
share_count = (count / 1000).toFixed(1) + "K"
|
if (count < 1000) {
|
||||||
} else {
|
share_count = count
|
||||||
share_count = (count / 10000).toFixed(1) + "W"
|
} else if (count >= 1000 && count < 10000) {
|
||||||
}
|
share_count = (count / 1000).toFixed(1) + "K"
|
||||||
$("#aweme_share_count").html(share_count);
|
} else {
|
||||||
|
share_count = (count / 10000).toFixed(1) + "W"
|
||||||
$("#loading").attr("style", "display:none;");//隐藏 loading
|
}
|
||||||
$("#download").attr("style", "display:block;");//显示 download
|
$("#aweme_share_count").html(share_count);
|
||||||
// alert("SUCCESS");
|
|
||||||
// 执行弹框
|
$("#icons").attr("style", "display:flex;");//显示 icons
|
||||||
narnSuccess();
|
$("#icon").attr("style", "display:table-row;");//显示 icon
|
||||||
} else {
|
$("#music").attr("style", "display:inline;");//显示 music
|
||||||
$("#loading").attr("style", "display:none;");//隐藏 loading
|
|
||||||
$("#download").attr("style", "display:none;");//隐藏 download
|
$("#loading").attr("style", "display:none;");//隐藏 loading
|
||||||
|
$("#download").attr("style", "display:block;");//显示 download
|
||||||
// 执行弹框
|
// alert("SUCCESS");
|
||||||
narnFail();
|
// 执行弹框
|
||||||
}
|
narnSuccess();
|
||||||
;
|
}
|
||||||
},
|
|
||||||
error: function (xhr, type) {
|
if (result.awemeType === 2) {
|
||||||
$("#loading").attr("style", "display:none;");//隐藏 loading
|
if (result.status === 4) {
|
||||||
$("#download").attr("style", "display:none;");//隐藏 download
|
$("#loading").attr("style", "display:none;");//隐藏 loading
|
||||||
// alert("异常!");
|
$("#download").attr("style", "display:none;");//隐藏 download
|
||||||
|
// 执行弹框
|
||||||
// 执行弹框
|
narnWarn()
|
||||||
narnFail();
|
} else {
|
||||||
}
|
$("#AwemeOrLive").html("下载直播");
|
||||||
});
|
$("#awemeType").html("预览直播");
|
||||||
}
|
$("#video").attr("href", removeHttp(result.flv_pull_url0));
|
||||||
|
$("#pre_video").attr("src", removeHttp(result.flv_pull_url0));
|
||||||
// 右上角弹框
|
|
||||||
function narnSuccess() {
|
$("#cover").attr("href", removeHttp(result.cover));
|
||||||
naranja().success({
|
$("#pre_video").attr("poster", result.cover);
|
||||||
title: '解析成功',
|
$("#avatar").attr("src", removeHttp(result.avatar));
|
||||||
text: '请及时下载音视频',
|
$("#avatar").attr("alt", result.nickname);
|
||||||
icon: true,
|
$("#nickname").html(result.nickname);
|
||||||
timeout: 5000,
|
$("#desc").html(result.title);
|
||||||
buttons: []
|
|
||||||
})
|
$("#video").attr("style", "display:inline;");//显示 video
|
||||||
}
|
$("#icons").attr("style", "display:none;");//隐藏 icons
|
||||||
|
$("#icon").attr("style", "display:none;");//隐藏 icon
|
||||||
function narnFail() {
|
$("#music").attr("style", "display:none;");//隐藏 music
|
||||||
naranja().error({
|
|
||||||
title: '解析失败',
|
$("#loading").attr("style", "display:none;");//隐藏 loading
|
||||||
text: '视频不存在或接口失效',
|
$("#download").attr("style", "display:block;");//显示 download
|
||||||
icon: true,
|
// alert("SUCCESS");
|
||||||
timeout: 5000,
|
// 执行弹框
|
||||||
buttons: []
|
narnSuccess();
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
$("#loading").attr("style", "display:none;");//隐藏 loading
|
||||||
window.addEventListener('DOMContentLoaded', function () {
|
$("#download").attr("style", "display:none;");//隐藏 download
|
||||||
document.getElementById('view_aweme').addEventListener('click', function () {
|
|
||||||
var awemeType = document.getElementById("awemeType").innerText;
|
// 执行弹框
|
||||||
|
narnFail();
|
||||||
if (awemeType === "预览视频") {
|
}
|
||||||
// 调小音量
|
;
|
||||||
var videoElement = document.getElementById("pre_video");
|
},
|
||||||
videoElement.volume = 0.6
|
error: function (xhr, type) {
|
||||||
/*弹出视频播放层*/
|
$("#loading").attr("style", "display:none;");//隐藏 loading
|
||||||
$("#show-video").show();
|
$("#download").attr("style", "display:none;");//隐藏 download
|
||||||
}
|
// alert("异常!");
|
||||||
// 图片查看器
|
|
||||||
if (awemeType === "预览图集") {
|
// 执行弹框
|
||||||
|
narnFail();
|
||||||
var viewer = new Viewer(document.getElementById('images'), {
|
}
|
||||||
hidden: function () {
|
});
|
||||||
viewer.destroy();
|
}
|
||||||
},
|
|
||||||
});
|
// 右上角弹框
|
||||||
|
function narnSuccess() {
|
||||||
// image.click();
|
naranja().success({
|
||||||
viewer.show();
|
title: '解析成功',
|
||||||
}
|
text: '请及时下载音视频',
|
||||||
|
icon: true,
|
||||||
|
timeout: 5000,
|
||||||
});
|
buttons: []
|
||||||
/*关闭视频播放层*/
|
})
|
||||||
$(".video-close").click(function () {
|
}
|
||||||
var videoElement = document.getElementById("pre_video");
|
|
||||||
videoElement.pause()
|
function narnFail() {
|
||||||
$("#show-video").hide();
|
naranja().error({
|
||||||
})
|
title: '解析失败',
|
||||||
});
|
text: '直播/视频/图集不存在或接口失效',
|
||||||
|
icon: true,
|
||||||
|
timeout: 5000,
|
||||||
|
buttons: []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function narnWarn() {
|
||||||
|
naranja().warn({
|
||||||
|
title: '提示',
|
||||||
|
text: '直播未开始',
|
||||||
|
icon: true,
|
||||||
|
timeout: 5000,
|
||||||
|
buttons: []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
window.addEventListener('DOMContentLoaded', function () {
|
||||||
|
document.getElementById('view_aweme').addEventListener('click', function () {
|
||||||
|
var awemeType = document.getElementById("awemeType").innerText;
|
||||||
|
|
||||||
|
if (awemeType === "预览视频") {
|
||||||
|
// 调小音量
|
||||||
|
var videoElement = document.getElementById("pre_video");
|
||||||
|
videoElement.volume = 0.6
|
||||||
|
/*弹出视频播放层*/
|
||||||
|
$("#show-video").show();
|
||||||
|
}
|
||||||
|
// 图片查看器
|
||||||
|
if (awemeType === "预览图集") {
|
||||||
|
|
||||||
|
var viewer = new Viewer(document.getElementById('images'), {
|
||||||
|
hidden: function () {
|
||||||
|
viewer.destroy();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// image.click();
|
||||||
|
viewer.show();
|
||||||
|
}
|
||||||
|
// 预览直播
|
||||||
|
if (awemeType === "预览直播") {
|
||||||
|
if (flvjs.isSupported()) {//检查flvjs能否正常使用
|
||||||
|
var videoElement = document.getElementById('pre_video');//使用id选择器找到第二步设置的dom元素
|
||||||
|
var flvPlayer = flvjs.createPlayer({//创建一个新的flv播放器对象
|
||||||
|
type: 'flv',//类型flv
|
||||||
|
url: $("#video").attr("href")//flv文件地址
|
||||||
|
});
|
||||||
|
flvPlayer.attachMediaElement(videoElement);//将flv视频装载进video元素内
|
||||||
|
flvPlayer.load();//载入视频
|
||||||
|
flvPlayer.play();//播放视频,如果不想要自动播放,去掉本行
|
||||||
|
|
||||||
|
/*弹出视频播放层*/
|
||||||
|
$("#show-video").show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
/*关闭视频播放层*/
|
||||||
|
$(".video-close").click(function () {
|
||||||
|
var videoElement = document.getElementById("pre_video");
|
||||||
|
videoElement.pause()
|
||||||
|
$("#show-video").hide();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function removeHttp(url) {
|
||||||
|
if (typeof (url) == 'string') {
|
||||||
|
url = url.replace(/^https?:/, '');
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
10
static/js/flv.min.js
vendored
Normal file
@ -1,170 +1,175 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="zhxx">
|
<html lang="zhxx">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>抖音去水印工具</title>
|
<title>抖音去水印工具</title>
|
||||||
<!-- Meta tag Keywords -->
|
<!-- Meta tag Keywords -->
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta charset="UTF-8"/>
|
<meta charset="UTF-8"/>
|
||||||
<meta name="keywords" content="抖音去水印工具"/>
|
<meta name="keywords" content="抖音去水印工具"/>
|
||||||
<meta name="referrer" content="never">
|
<meta name="referrer" content="never">
|
||||||
<script>
|
<script>
|
||||||
addEventListener("load", function () {
|
addEventListener("load", function () {
|
||||||
setTimeout(hideURLbar, 0);
|
setTimeout(hideURLbar, 0);
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
function hideURLbar() {
|
function hideURLbar() {
|
||||||
window.scrollTo(0, 1);
|
window.scrollTo(0, 1);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<!-- //Meta tag Keywords -->
|
<!-- //Meta tag Keywords -->
|
||||||
<!-- /Favicons -->
|
<!-- /Favicons -->
|
||||||
<link href="https://cdn.jsdelivr.net/gh/imgyh/tiktok/static/img/favicon.ico" rel="shortcut icon" type="image/x-icon">
|
<link href="../static/img/favicon.ico" rel="shortcut icon"
|
||||||
<!-- //Favicons -->
|
type="image/x-icon">
|
||||||
<!--/Style-CSS -->
|
<!-- //Favicons -->
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/imgyh/tiktok/static/css/style.css" type="text/css" media="all"/>
|
<!--/Style-CSS -->
|
||||||
<!--//Style-CSS -->
|
<link rel="stylesheet" href="../static/css/style.css" type="text/css"
|
||||||
<!-- font-awesome-icons -->
|
media="all"/>
|
||||||
<link href="https://cdn.jsdelivr.net/gh/imgyh/tiktok/static/css/font-awesome.css" rel="stylesheet">
|
<!--//Style-CSS -->
|
||||||
<!-- //font-awesome-icons -->
|
<!-- font-awesome-icons -->
|
||||||
<!-- naranja 右下角弹框提示 https://github.com/e1016/naranja-->
|
<link href="//lib.baomitu.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/imgyh/tiktok/static/css/naranja.min.css" type="text/css"/>
|
<!-- //font-awesome-icons -->
|
||||||
<!-- //naranja -->
|
<!-- naranja 右下角弹框提示 https://github.com/e1016/naranja-->
|
||||||
|
<link rel="stylesheet" href="//unpkg.com/naranja@1.0.1/lib/naranja.min.css" type="text/css"/>
|
||||||
<!-- viewerjs 图片查看器 https://github.com/fengyuanchen/viewerjs-->
|
<!-- //naranja -->
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/imgyh/tiktok/static/css/viewer.min.css" type="text/css"/>
|
|
||||||
<!-- //viewerjs -->
|
<!-- viewerjs 图片查看器 https://github.com/fengyuanchen/viewerjs-->
|
||||||
</head>
|
<link rel="stylesheet" href="//lib.baomitu.com/viewerjs/1.11.2/viewer.min.css" type="text/css"/>
|
||||||
|
<!-- //viewerjs -->
|
||||||
<body>
|
</head>
|
||||||
|
|
||||||
<div class="error-61-mian">
|
<body>
|
||||||
<div class="wrapper">
|
|
||||||
<div class="errors-16-top">
|
<div class="error-61-mian">
|
||||||
|
<div class="wrapper">
|
||||||
<p style="color:#00c4b6;font-size:32px;">抖音去水印工具</p>
|
<div class="errors-16-top">
|
||||||
<br>
|
|
||||||
<p>温馨提示: 粘贴分享链接时 无需删除文案 但如果链接正确但解析失败请删掉文案后重试 https://v.douyin.com/kcvMpuN/</p>
|
<p style="color:#00c4b6;font-size:32px;">抖音去水印工具</p>
|
||||||
<p>关于抖音批量下载与去水印工具的更多实现细节请点击: <a href="https://www.imgyh.com/archives/41.html" target="_blank" style="color:#00c4b6;">抖音批量下载与去水印工具</a></p>
|
<br>
|
||||||
<form id="form1" onsubmit="return false" action="#" method="post" class="d-flex error-page-form">
|
<p>支持视频/图集/直播解析,粘贴视频/图集/直播分享链接时无需删除文案,但如果链接正确但解析失败请删掉文案后重试 https://v.douyin.com/kcvMpuN/</p>
|
||||||
{# 以前需要手动选择 图片 或者 视频 现在加了自动判断#}
|
{# <p>2.支持直播解析,需要网页版直播链接 https://live.douyin.com/343806013144</p>#}
|
||||||
{# <div class="select">#}
|
<p>关于抖音批量下载与去水印工具的更多实现细节请点击: <a href="https://www.imgyh.com/archives/41.html" target="_blank"
|
||||||
{# <select name="awemeType" required="required">#}
|
style="color:#00c4b6;">抖音批量下载与去水印工具</a></p>
|
||||||
{# <option value="0" selected="selected">视频</option>#}
|
<form id="form1" onsubmit="return false" action="#" method="post" class="d-flex error-page-form">
|
||||||
{# <option value="1">图集</option>#}
|
{# 以前需要手动选择 图片 或者 视频 现在加了自动判断#}
|
||||||
{# </select>#}
|
{# <div class="select">#}
|
||||||
{# </div>#}
|
{# <select name="awemeType" required="required">#}
|
||||||
|
{# <option value="0" selected="selected">视频</option>#}
|
||||||
<input type="text" placeholder="抖音视频分享地址,复制后粘贴到此处" name="share_link" required="required">
|
{# <option value="1">图集</option>#}
|
||||||
<button type="reset" onclick="SendAjax()">解析</button>
|
{# </select>#}
|
||||||
</form>
|
{# </div>#}
|
||||||
{# <div class="social-coming-icons">#}
|
|
||||||
{# <a href="#" title="Facebook" class="footer-fb"><span class="fa fa-facebook"#}
|
<input type="text" placeholder="粘贴视频/图集/直播分享地址" name="share_link" required="required">
|
||||||
{# aria-hidden="true"></span></a>#}
|
<button type="reset" onclick="SendAjax()">解析</button>
|
||||||
{# <a href="#" title="Twitter" class="footer-tw"><span class="fa fa-twitter"#}
|
</form>
|
||||||
{# aria-hidden="true"></span></a>#}
|
{# <div class="social-coming-icons">#}
|
||||||
{# <a href="#" title="Google Plus" class="footer-gg"><span class="fa fa-google-plus"#}
|
{# <a href="#" title="Facebook" class="footer-fb"><span class="fa fa-facebook"#}
|
||||||
{# aria-hidden="true"></span></a>#}
|
{# aria-hidden="true"></span></a>#}
|
||||||
{# <a href="#" title="Linkedin" class="footer-lin"><span class="fa fa-linkedin"#}
|
{# <a href="#" title="Twitter" class="footer-tw"><span class="fa fa-twitter"#}
|
||||||
{# aria-hidden="true"></span></a>#}
|
{# aria-hidden="true"></span></a>#}
|
||||||
{# </div>#}
|
{# <a href="#" title="Google Plus" class="footer-gg"><span class="fa fa-google-plus"#}
|
||||||
|
{# aria-hidden="true"></span></a>#}
|
||||||
</div>
|
{# <a href="#" title="Linkedin" class="footer-lin"><span class="fa fa-linkedin"#}
|
||||||
<div class="errors-16-mid">
|
{# aria-hidden="true"></span></a>#}
|
||||||
<div class="loading" id="loading">
|
{# </div>#}
|
||||||
<div class="shape shape-1"></div>
|
|
||||||
<div class="shape shape-2"></div>
|
</div>
|
||||||
<div class="shape shape-3"></div>
|
<div class="errors-16-mid">
|
||||||
<div class="shape shape-4"></div>
|
<div class="loading" id="loading">
|
||||||
</div>
|
<div class="shape shape-1"></div>
|
||||||
|
<div class="shape shape-2"></div>
|
||||||
<div id="download" style="display: none;">
|
<div class="shape shape-3"></div>
|
||||||
<div class="photo">
|
<div class="shape shape-4"></div>
|
||||||
<img id="avatar" src="#" class="avatar" alt="avatar">
|
</div>
|
||||||
<br>
|
|
||||||
<p id="nickname" class="nickname"></p>
|
<div id="download" style="display: none;">
|
||||||
</div>
|
<div class="photo">
|
||||||
<div class="info">
|
<img id="avatar" src="#" class="avatar" alt="avatar">
|
||||||
<div class="icons">
|
<br>
|
||||||
<div class="icon">
|
<p id="nickname" class="nickname"></p>
|
||||||
<i class="fa fa-heart" style="color:#fd325c;" aria-hidden="true"></i>
|
</div>
|
||||||
<p id="aweme_digg_count"></p>
|
<div class="info">
|
||||||
</div>
|
<div id="icons" class="icons">
|
||||||
<div class="icon">
|
<div id="icon" class="icon">
|
||||||
<i class="fa fa-comment" style="color:#efeeec;" aria-hidden="true"></i>
|
<i class="fa fa-heart" style="color:#fd325c;" aria-hidden="true"></i>
|
||||||
<p id="aweme_comment_count"></p>
|
<p id="aweme_digg_count"></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="icon">
|
<div id="icon" class="icon">
|
||||||
<i class="fa fa-star" style="color:#fcb505;" aria-hidden="true"></i>
|
<i class="fa fa-comment" style="color:#efeeec;" aria-hidden="true"></i>
|
||||||
<p id="aweme_collect_count"></p>
|
<p id="aweme_comment_count"></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="icon">
|
<div id="icon" class="icon">
|
||||||
<i class="fa fa-share" style="color:#e7e8e6;" aria-hidden="true"></i>
|
<i class="fa fa-star" style="color:#fcb505;" aria-hidden="true"></i>
|
||||||
<p id="aweme_share_count"></p>
|
<p id="aweme_collect_count"></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div id="icon" class="icon">
|
||||||
<br>
|
<i class="fa fa-share" style="color:#e7e8e6;" aria-hidden="true"></i>
|
||||||
<p id="desc"></p>
|
<p id="aweme_share_count"></p>
|
||||||
<br>
|
</div>
|
||||||
<a id="cover" href="#" target="_blank">
|
</div>
|
||||||
<button type="button" class="btn btn1 ">
|
<br>
|
||||||
<span>下载封面</span>
|
<p id="desc"></p>
|
||||||
</button>
|
<br>
|
||||||
</a>
|
<a id="cover" href="#" target="_blank">
|
||||||
<a id="video" href="#" target="_blank">
|
<button type="button" class="btn btn1 ">
|
||||||
<button type="button" class="btn btn1">
|
<span>下载封面</span>
|
||||||
<span>下载视频</span>
|
</button>
|
||||||
</button>
|
</a>
|
||||||
</a>
|
<a id="video" href="#" target="_blank">
|
||||||
<a id="music" href="#" target="_blank">
|
<button type="button" class="btn btn1">
|
||||||
<button type="button" class="btn btn1">
|
<span id="AwemeOrLive"></span>
|
||||||
<span>下载音乐</span>
|
</button>
|
||||||
</button>
|
</a>
|
||||||
</a>
|
<a id="music" href="#" target="_blank">
|
||||||
<button id="view_aweme" type="button" class="btn btn1">
|
<button type="button" class="btn btn1">
|
||||||
<span id="awemeType"></span>
|
<span>下载音乐</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</a>
|
||||||
</div>
|
<button id="view_aweme" type="button" class="btn btn1">
|
||||||
|
<span id="awemeType"></span>
|
||||||
</div>
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="copy-right">
|
|
||||||
<p>Copyright © 2021-2023.我的博客 <a href="https://www.imgyh.com/" target="_blank">GYH's Blog</a> && 项目地址 <a
|
</div>
|
||||||
href="https://github.com/imgyh/douyin" target="_blank">Github</a>
|
|
||||||
All rights reserved.</p>
|
</div>
|
||||||
</div>
|
<div class="copy-right">
|
||||||
</div>
|
<p>Copyright © 2021-2023.我的博客 <a href="https://www.imgyh.com/" target="_blank">GYH's Blog</a> && 项目地址 <a
|
||||||
|
href="https://github.com/imgyh/douyin" target="_blank">Github</a>
|
||||||
{# 视频预览效果 https://blog.csdn.net/qq_45140694/article/details/115266928 #}
|
All rights reserved.</p>
|
||||||
<div id="show-video">
|
</div>
|
||||||
<a class="video-close">
|
</div>
|
||||||
<span>
|
|
||||||
<svg t="1614676844098" class="icon" viewBox="0 0 1024 1024"
|
{# 视频预览效果 https://blog.csdn.net/qq_45140694/article/details/115266928 #}
|
||||||
xmlns="http://www.w3.org/2000/svg" p-id="2082"
|
<div id="show-video">
|
||||||
width="30" height="30">
|
<a class="video-close">
|
||||||
<path d="M591.506286 511.853714l417.133714-416.914285a54.601143 54.601143 0 0 0 0-76.8l-2.267429-2.267429a54.601143 54.601143 0 0 0-76.8 0L512.438857 433.481143 95.305143 15.798857a54.601143 54.601143 0 0 0-76.8 0L16.237714 18.066286a53.577143 53.577143 0 0 0 0 76.8l417.097143 416.987428L16.201143 929.097143a54.601143 54.601143 0 0 0 0 76.8l2.267428 2.267428a54.601143 54.601143 0 0 0 76.8 0l417.170286-417.060571 417.097143 417.097143a54.601143 54.601143 0 0 0 76.8 0l2.267429-2.267429a54.601143 54.601143 0 0 0 0-76.8z"
|
<span>
|
||||||
p-id="2083" fill="#e6e6e6"></path>
|
<svg t="1614676844098" class="icon" viewBox="0 0 1024 1024"
|
||||||
</svg>
|
xmlns="http://www.w3.org/2000/svg" p-id="2082"
|
||||||
</span>
|
width="30" height="30">
|
||||||
</a>
|
<path d="M591.506286 511.853714l417.133714-416.914285a54.601143 54.601143 0 0 0 0-76.8l-2.267429-2.267429a54.601143 54.601143 0 0 0-76.8 0L512.438857 433.481143 95.305143 15.798857a54.601143 54.601143 0 0 0-76.8 0L16.237714 18.066286a53.577143 53.577143 0 0 0 0 76.8l417.097143 416.987428L16.201143 929.097143a54.601143 54.601143 0 0 0 0 76.8l2.267428 2.267428a54.601143 54.601143 0 0 0 76.8 0l417.170286-417.060571 417.097143 417.097143a54.601143 54.601143 0 0 0 76.8 0l2.267429-2.267429a54.601143 54.601143 0 0 0 0-76.8z"
|
||||||
{# https://blog.csdn.net/seeeeeeeeeee/article/details/119981594 #}
|
p-id="2083" fill="#e6e6e6"></path>
|
||||||
<video src="" id="pre_video" controls="controls" poster=""></video>
|
</svg>
|
||||||
</div>
|
</span>
|
||||||
|
</a>
|
||||||
<div style="display: none">
|
{# https://blog.csdn.net/seeeeeeeeeee/article/details/119981594 #}
|
||||||
<ul id="images">
|
<video src="" id="pre_video" controls="controls" poster=""></video>
|
||||||
{# <li><img src="picture-1.jpg" alt="Picture 1"></li>#}
|
</div>
|
||||||
{# <li><img src="picture-2.jpg" alt="Picture 2"></li>#}
|
|
||||||
{# <li><img src="picture-3.jpg" alt="Picture 3"></li>#}
|
<div style="display: none">
|
||||||
</ul>
|
<ul id="images">
|
||||||
</div>
|
{# <li><img src="picture-1.jpg" alt="Picture 1"></li>#}
|
||||||
|
{# <li><img src="picture-2.jpg" alt="Picture 2"></li>#}
|
||||||
<script src="https://cdn.jsdelivr.net/gh/imgyh/tiktok/static/js/jquery-1.8.2.min.js" type="text/javascript"></script>
|
{# <li><img src="picture-3.jpg" alt="Picture 3"></li>#}
|
||||||
<script src="https://cdn.jsdelivr.net/gh/imgyh/tiktok/static/js/naranja.min.js" type="text/javascript"></script>
|
</ul>
|
||||||
<script src="https://cdn.jsdelivr.net/gh/imgyh/tiktok/static/js/viewer.min.js" type="text/javascript"></script>
|
</div>
|
||||||
<script src="https://cdn.jsdelivr.net/gh/imgyh/tiktok/static/js/custom.js" type="text/javascript"></script>
|
|
||||||
</body>
|
<script src="//lib.baomitu.com/jquery/1.8.2/jquery.min.js" type="text/javascript"></script>
|
||||||
|
<script src="//unpkg.com/naranja@1.0.1/lib/naranja.min.js" type="text/javascript"></script>
|
||||||
|
<script src="//lib.baomitu.com/viewerjs/1.11.2/viewer.min.js" type="text/javascript"></script>
|
||||||
|
<script src="../static/js/custom.js" type="text/javascript"></script>
|
||||||
|
<script src="//lib.baomitu.com/flv.js/0.0.2/flv.min.js" type="text/javascript"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|