liufuhua007

add code

1 +# Byte-compiled / optimized / DLL files
2 +__pycache__/
3 +*.py[cod]
4 +*$py.class
5 +
6 +# C extensions
7 +*.so
8 +
9 +# Distribution / packaging
10 +.Python
11 +build/
12 +develop-eggs/
13 +dist/
14 +downloads/
15 +eggs/
16 +.eggs/
17 +lib/
18 +lib64/
19 +parts/
20 +sdist/
21 +var/
22 +wheels/
23 +share/python-wheels/
24 +*.egg-info/
25 +.installed.cfg
26 +*.egg
27 +MANIFEST
28 +
29 +# PyInstaller
30 +# Usually these files are written by a python script from a template
31 +# before PyInstaller builds the exe, so as to inject date/other infos into it.
32 +*.manifest
33 +*.spec
34 +
35 +# Installer logs
36 +pip-log.txt
37 +pip-delete-this-directory.txt
38 +
39 +# Unit test / coverage reports
40 +htmlcov/
41 +.tox/
42 +.nox/
43 +.coverage
44 +.coverage.*
45 +.cache
46 +nosetests.xml
47 +coverage.xml
48 +*.cover
49 +*.py,cover
50 +.hypothesis/
51 +.pytest_cache/
52 +cover/
53 +
54 +# Translations
55 +*.mo
56 +*.pot
57 +
58 +# Django stuff:
59 +*.log
60 +local_settings.py
61 +db.sqlite3
62 +db.sqlite3-journal
63 +
64 +# Flask stuff:
65 +instance/
66 +.webassets-cache
67 +
68 +# Scrapy stuff:
69 +.scrapy
70 +
71 +# Sphinx documentation
72 +docs/_build/
73 +
74 +# PyBuilder
75 +.pybuilder/
76 +target/
77 +
78 +# Jupyter Notebook
79 +.ipynb_checkpoints
80 +
81 +# IPython
82 +profile_default/
83 +ipython_config.py
84 +
85 +# pyenv
86 +# For a library or package, you might want to ignore these files since the code is
87 +# intended to run in multiple environments; otherwise, check them in:
88 +# .python-version
89 +
90 +# pipenv
91 +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 +# However, in case of collaboration, if having platform-specific dependencies or dependencies
93 +# having no cross-platform support, pipenv may install dependencies that don't work, or not
94 +# install all needed dependencies.
95 +#Pipfile.lock
96 +
97 +# poetry
98 +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99 +# This is especially recommended for binary packages to ensure reproducibility, and is more
100 +# commonly ignored for libraries.
101 +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102 +#poetry.lock
103 +
104 +# pdm
105 +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106 +#pdm.lock
107 +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108 +# in version control.
109 +# https://pdm.fming.dev/#use-with-ide
110 +.pdm.toml
111 +
112 +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
113 +__pypackages__/
114 +
115 +# Celery stuff
116 +celerybeat-schedule
117 +celerybeat.pid
118 +
119 +# SageMath parsed files
120 +*.sage.py
121 +
122 +# Environments
123 +.env
124 +.venv
125 +env/
126 +venv/
127 +ENV/
128 +env.bak/
129 +venv.bak/
130 +
131 +# Spyder project settings
132 +.spyderproject
133 +.spyproject
134 +
135 +# Rope project settings
136 +.ropeproject
137 +
138 +# mkdocs documentation
139 +/site
140 +
141 +# mypy
142 +.mypy_cache/
143 +.dmypy.json
144 +dmypy.json
145 +
146 +# Pyre type checker
147 +.pyre/
148 +
149 +# pytype static type analyzer
150 +.pytype/
151 +
152 +# Cython debug symbols
153 +cython_debug/
154 +
155 +# PyCharm
156 +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
157 +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
158 +# and can be added to the global gitignore or merged into this file. For a more nuclear
159 +# option (not recommended) you can uncomment the following to ignore the entire idea folder.
160 +.idea/
161 +
162 +
163 +
164 +# ignore cookie file
165 +tencent_uploader/*.json
166 +youtube_uploader/*.json
167 +douyin_uploader/*.json
168 +bilibili_uploader/*.json
169 +tk_uploader/*.json
170 +cookies
...\ No newline at end of file ...\ No newline at end of file
1 # xj-marketing 1 # xj-marketing
2 -social-auto-upload 该项目旨在自动化发布视频到各个社交媒体平台 2 +xj-marketing 该项目旨在自动化发布视频到各个社交媒体平台
3 3
4 -social-auto-upload This project aims to automate the posting of videos to various social media platforms. 4 +xj-marketing This project aims to automate the posting of videos to various social media platforms.
5 5
6 6
7 ## 💡Feature 7 ## 💡Feature
......
1 +import argparse
2 +import asyncio
3 +from datetime import datetime
4 +from os.path import exists
5 +from pathlib import Path
6 +
7 +from conf import BASE_DIR
8 +from uploader.douyin_uploader.main import douyin_setup, DouYinVideo
9 +from uploader.ks_uploader.main import ks_setup, KSVideo
10 +from uploader.tencent_uploader.main import weixin_setup, TencentVideo
11 +from uploader.tk_uploader.main_chrome import tiktok_setup, TiktokVideo
12 +from uploader.wyh_uploader.main import wyh_setup
13 +from utils.base_social_media import get_supported_social_media, get_cli_action, SOCIAL_MEDIA_DOUYIN, \
14 + SOCIAL_MEDIA_TENCENT, SOCIAL_MEDIA_TIKTOK, SOCIAL_MEDIA_KUAISHOU, SOCIAL_MEDIA_WANGYIHAO
15 +from utils.constant import TencentZoneTypes
16 +from utils.files_times import get_title_and_hashtags
17 +
18 +
19 +def parse_schedule(schedule_raw):
20 + if schedule_raw:
21 + schedule = datetime.strptime(schedule_raw, '%Y-%m-%d %H:%M')
22 + else:
23 + schedule = None
24 + return schedule
25 +
26 +
27 +async def main():
28 + # 主解析器
29 + parser = argparse.ArgumentParser(description="Upload video to multiple social-media.")
30 + parser.add_argument("platform", metavar='platform', choices=get_supported_social_media(), help="Choose social-media platform: douyin tencent tiktok kuaishou")
31 +
32 + parser.add_argument("account_name", type=str, help="Account name for the platform: xiaoA")
33 + subparsers = parser.add_subparsers(dest="action", metavar='action', help="Choose action", required=True)
34 +
35 + actions = get_cli_action()
36 + for action in actions:
37 + action_parser = subparsers.add_parser(action, help=f'{action} operation')
38 + if action == 'login':
39 + # Login 不需要额外参数
40 + continue
41 + elif action == 'upload':
42 + action_parser.add_argument("video_file", help="Path to the Video file")
43 + action_parser.add_argument("-pt", "--publish_type", type=int, choices=[0, 1],
44 + help="0 for immediate, 1 for scheduled", default=0)
45 + action_parser.add_argument('-t', '--schedule', help='Schedule UTC time in %Y-%m-%d %H:%M format')
46 +
47 + # 解析命令行参数
48 + args = parser.parse_args()
49 + # 参数校验
50 + if args.action == 'upload':
51 + if not exists(args.video_file):
52 + raise FileNotFoundError(f'Could not find the video file at {args["video_file"]}')
53 + if args.publish_type == 1 and not args.schedule:
54 + parser.error("The schedule must must be specified for scheduled publishing.")
55 +
56 + account_file = Path(BASE_DIR / "cookies" / f"{args.platform}_{args.account_name}.json")
57 + account_file.parent.mkdir(exist_ok=True)
58 +
59 + print(account_file)
60 +
61 + # 根据 action 处理不同的逻辑
62 + if args.action == 'login':
63 + print(f"Logging in with account {args.account_name} on platform {args.platform}")
64 + if args.platform == SOCIAL_MEDIA_DOUYIN:
65 + await douyin_setup(str(account_file), handle=True)
66 + elif args.platform == SOCIAL_MEDIA_TIKTOK:
67 + await tiktok_setup(str(account_file), handle=True)
68 + elif args.platform == SOCIAL_MEDIA_TENCENT:
69 + await weixin_setup(str(account_file), handle=True)
70 + elif args.platform == SOCIAL_MEDIA_KUAISHOU:
71 + await ks_setup(str(account_file), handle=True)
72 + elif args.platform == SOCIAL_MEDIA_WANGYIHAO:
73 + await wyh_setup(str(account_file), handle=True)
74 +
75 + elif args.action == 'upload':
76 + title, tags = get_title_and_hashtags(args.video_file)
77 + video_file = args.video_file
78 +
79 + if args.publish_type == 0:
80 + print("Uploading immediately...")
81 + publish_date = 0
82 + else:
83 + print("Scheduling videos...")
84 + publish_date = parse_schedule(args.schedule)
85 +
86 + if args.platform == SOCIAL_MEDIA_DOUYIN:
87 + await douyin_setup(account_file, handle=False)
88 + app = DouYinVideo(title, video_file, tags, publish_date, account_file)
89 + elif args.platform == SOCIAL_MEDIA_TIKTOK:
90 + await tiktok_setup(account_file, handle=True)
91 + app = TiktokVideo(title, video_file, tags, publish_date, account_file)
92 + elif args.platform == SOCIAL_MEDIA_TENCENT:
93 + await weixin_setup(account_file, handle=True)
94 + category = TencentZoneTypes.LIFESTYLE.value # 标记原创需要否则不需要传
95 + app = TencentVideo(title, video_file, tags, publish_date, account_file, category)
96 + elif args.platform == SOCIAL_MEDIA_KUAISHOU:
97 + await ks_setup(account_file, handle=True)
98 + app = KSVideo(title, video_file, tags, publish_date, account_file)
99 + else:
100 + print("Wrong platform, please check your input")
101 + exit()
102 +
103 + await app.main()
104 +
105 +
106 +if __name__ == "__main__":
107 + asyncio.run(main())
1 +from pathlib import Path
2 +
3 +BASE_DIR = Path(__file__).parent.resolve()
4 +XHS_SERVER = "http://127.0.0.1:11901"
5 +LOCAL_CHROME_PATH = "" # change me necessary! for example C:/Program Files/Google/Chrome/Application/chrome.exe

9.44 KB

1 +from pathlib import Path
2 +
3 +from conf import BASE_DIR
4 +
5 +Path(BASE_DIR / "cookies").mkdir(exist_ok=True)
...\ No newline at end of file ...\ No newline at end of file
1 +from pathlib import Path
2 +
3 +from conf import BASE_DIR
4 +
5 +Path(BASE_DIR / "cookies" / "bilibili_uploader").mkdir(exist_ok=True)
...\ No newline at end of file ...\ No newline at end of file
This file is too large to display.
1 +import json
2 +import pathlib
3 +import random
4 +from biliup.plugins.bili_webup import BiliBili, Data
5 +
6 +from utils.log import bilibili_logger
7 +
8 +
9 +def extract_keys_from_json(data):
10 + """Extract specified keys from the provided JSON data."""
11 + keys_to_extract = ["SESSDATA", "bili_jct", "DedeUserID__ckMd5", "DedeUserID", "access_token"]
12 + extracted_data = {}
13 +
14 + # Extracting cookie data
15 + for cookie in data['cookie_info']['cookies']:
16 + if cookie['name'] in keys_to_extract:
17 + extracted_data[cookie['name']] = cookie['value']
18 +
19 + # Extracting access_token
20 + if "access_token" in data['token_info']:
21 + extracted_data['access_token'] = data['token_info']['access_token']
22 +
23 + return extracted_data
24 +
25 +
26 +def read_cookie_json_file(filepath: pathlib.Path):
27 + with open(filepath, 'r', encoding='utf-8') as file:
28 + content = json.load(file)
29 + return content
30 +
31 +
32 +def random_emoji():
33 + emoji_list = ["🍏", "🍎", "🍊", "🍋", "🍌", "🍉", "🍇", "🍓", "🍈", "🍒", "🍑", "🍍", "🥭", "🥥", "🥝",
34 + "🍅", "🍆", "🥑", "🥦", "🥒", "🥬", "🌶", "🌽", "🥕", "🥔", "🍠", "🥐", "🍞", "🥖", "🥨", "🥯", "🧀", "🥚", "🍳", "🥞",
35 + "🥓", "🥩", "🍗", "🍖", "🌭", "🍔", "🍟", "🍕", "🥪", "🥙", "🌮", "🌯", "🥗", "🥘", "🥫", "🍝", "🍜", "🍲", "🍛", "🍣",
36 + "🍱", "🥟", "🍤", "🍙", "🍚", "🍘", "🍥", "🥮", "🥠", "🍢", "🍡", "🍧", "🍨", "🍦", "🥧", "🍰", "🎂", "🍮", "🍭", "🍬",
37 + "🍫", "🍿", "🧂", "🍩", "🍪", "🌰", "🥜", "🍯", "🥛", "🍼", "☕️", "🍵", "🥤", "🍶", "🍻", "🥂", "🍷", "🥃", "🍸", "🍹",
38 + "🍾", "🥄", "🍴", "🍽", "🥣", "🥡", "🥢"]
39 + return random.choice(emoji_list)
40 +
41 +
42 +class BilibiliUploader(object):
43 + def __init__(self, cookie_data, file: pathlib.Path, title, desc, tid, tags, dtime):
44 + self.upload_thread_num = 3
45 + self.copyright = 1
46 + self.lines = 'AUTO'
47 + self.cookie_data = cookie_data
48 + self.file = file
49 + self.title = title
50 + self.desc = desc
51 + self.tid = tid
52 + self.tags = tags
53 + self.dtime = dtime
54 + self._init_data()
55 +
56 + def _init_data(self):
57 + self.data = Data()
58 + self.data.copyright = self.copyright
59 + self.data.title = self.title
60 + self.data.desc = self.desc
61 + self.data.tid = self.tid
62 + self.data.set_tag(self.tags)
63 + self.data.dtime = self.dtime
64 +
65 + def upload(self):
66 + with BiliBili(self.data) as bili:
67 + bili.login_by_cookies(self.cookie_data)
68 + bili.access_token = self.cookie_data.get('access_token')
69 + video_part = bili.upload_file(str(self.file), lines=self.lines,
70 + tasks=self.upload_thread_num) # 上传视频,默认线路AUTO自动选择,线程数量3。
71 + video_part['title'] = self.title
72 + self.data.append(video_part)
73 + ret = bili.submit() # 提交视频
74 + if ret.get('code') == 0:
75 + bilibili_logger.success(f'[+] {self.file.name}上传 成功')
76 + return True
77 + else:
78 + bilibili_logger.error(f'[-] {self.file.name}上传 失败, error messge: {ret.get("message")}')
79 + return False
1 +from pathlib import Path
2 +
3 +from conf import BASE_DIR
4 +
5 +Path(BASE_DIR / "cookies" / "douyin_uploader").mkdir(exist_ok=True)
...\ No newline at end of file ...\ No newline at end of file
This diff is collapsed. Click to expand it.
1 +from pathlib import Path
2 +
3 +from conf import BASE_DIR
4 +
5 +Path(BASE_DIR / "cookies" / "ks_uploader").mkdir(exist_ok=True)
...\ No newline at end of file ...\ No newline at end of file
1 +# -*- coding: utf-8 -*-
2 +from datetime import datetime
3 +
4 +from playwright.async_api import Playwright, async_playwright
5 +import os
6 +import asyncio
7 +
8 +from conf import LOCAL_CHROME_PATH
9 +from utils.base_social_media import set_init_script
10 +from utils.files_times import get_absolute_path
11 +from utils.log import kuaishou_logger
12 +
13 +
14 +async def cookie_auth(account_file):
15 + async with async_playwright() as playwright:
16 + browser = await playwright.chromium.launch(headless=True)
17 + context = await browser.new_context(storage_state=account_file)
18 + context = await set_init_script(context)
19 + # 创建一个新的页面
20 + page = await context.new_page()
21 + # 访问指定的 URL
22 + await page.goto("https://cp.kuaishou.com/article/publish/video")
23 + try:
24 + await page.wait_for_selector("div.names div.container div.name:text('机构服务')", timeout=5000) # 等待5秒
25 +
26 + kuaishou_logger.info("[+] 等待5秒 cookie 失效")
27 + return False
28 + except:
29 + kuaishou_logger.success("[+] cookie 有效")
30 + return True
31 +
32 +
33 +async def ks_setup(account_file, handle=False):
34 + account_file = get_absolute_path(account_file, "ks_uploader")
35 + if not os.path.exists(account_file) or not await cookie_auth(account_file):
36 + if not handle:
37 + return False
38 + kuaishou_logger.info('[+] cookie文件不存在或已失效,即将自动打开浏览器,请扫码登录,登陆后会自动生成cookie文件')
39 + await get_ks_cookie(account_file)
40 + return True
41 +
42 +
43 +async def get_ks_cookie(account_file):
44 + async with async_playwright() as playwright:
45 + options = {
46 + 'args': [
47 + '--lang en-GB'
48 + ],
49 + 'headless': False, # Set headless option here
50 + }
51 + # Make sure to run headed.
52 + browser = await playwright.chromium.launch(**options)
53 + # Setup context however you like.
54 + context = await browser.new_context() # Pass any options
55 + context = await set_init_script(context)
56 + # Pause the page, and start recording manually.
57 + page = await context.new_page()
58 + await page.goto("https://cp.kuaishou.com")
59 + await page.pause()
60 + # 点击调试器的继续,保存cookie
61 + await context.storage_state(path=account_file)
62 +
63 +
64 +class KSVideo(object):
65 + def __init__(self, title, file_path, tags, publish_date: datetime, account_file):
66 + self.title = title # 视频标题
67 + self.file_path = file_path
68 + self.tags = tags
69 + self.publish_date = publish_date
70 + self.account_file = account_file
71 + self.date_format = '%Y-%m-%d %H:%M'
72 + self.local_executable_path = LOCAL_CHROME_PATH
73 +
74 + async def handle_upload_error(self, page):
75 + kuaishou_logger.error("视频出错了,重新上传中")
76 + await page.locator('div.progress-div [class^="upload-btn-input"]').set_input_files(self.file_path)
77 +
78 + async def upload(self, playwright: Playwright) -> None:
79 + # 使用 Chromium 浏览器启动一个浏览器实例
80 + print(self.local_executable_path)
81 + if self.local_executable_path:
82 + browser = await playwright.chromium.launch(
83 + headless=False,
84 + executable_path=self.local_executable_path,
85 + )
86 + else:
87 + browser = await playwright.chromium.launch(
88 + headless=False
89 + ) # 创建一个浏览器上下文,使用指定的 cookie 文件
90 + context = await browser.new_context(storage_state=f"{self.account_file}")
91 + context = await set_init_script(context)
92 + context.on("close", lambda: context.storage_state(path=self.account_file))
93 +
94 + # 创建一个新的页面
95 + page = await context.new_page()
96 + # 访问指定的 URL
97 + await page.goto("https://cp.kuaishou.com/article/publish/video")
98 + kuaishou_logger.info('正在上传-------{}.mp4'.format(self.title))
99 + # 等待页面跳转到指定的 URL,没进入,则自动等待到超时
100 + kuaishou_logger.info('正在打开主页...')
101 + await page.wait_for_url("https://cp.kuaishou.com/article/publish/video")
102 + # 点击 "上传视频" 按钮
103 + upload_button = page.locator("button[class^='_upload-btn']")
104 + await upload_button.wait_for(state='visible') # 确保按钮可见
105 +
106 + async with page.expect_file_chooser() as fc_info:
107 + await upload_button.click()
108 + file_chooser = await fc_info.value
109 + await file_chooser.set_files(self.file_path)
110 +
111 + await asyncio.sleep(2)
112 +
113 + # if not await page.get_by_text("封面编辑").count():
114 + # raise Exception("似乎没有跳转到到编辑页面")
115 +
116 + await asyncio.sleep(1)
117 +
118 + # 等待按钮可交互
119 + new_feature_button = page.locator('button[type="button"] span:text("我知道了")')
120 + if await new_feature_button.count() > 0:
121 + await new_feature_button.click()
122 +
123 + kuaishou_logger.info("正在填充标题和话题...")
124 + await page.get_by_text("描述").locator("xpath=following-sibling::div").click()
125 + kuaishou_logger.info("clear existing title")
126 + await page.keyboard.press("Backspace")
127 + await page.keyboard.press("Control+KeyA")
128 + await page.keyboard.press("Delete")
129 + kuaishou_logger.info("filling new title")
130 + await page.keyboard.type(self.title)
131 + await page.keyboard.press("Enter")
132 +
133 + # 快手只能添加3个话题
134 + for index, tag in enumerate(self.tags[:3], start=1):
135 + kuaishou_logger.info("正在添加第%s个话题" % index)
136 + await page.keyboard.type(f"#{tag} ")
137 + await asyncio.sleep(2)
138 +
139 + max_retries = 60 # 设置最大重试次数,最大等待时间为 2 分钟
140 + retry_count = 0
141 +
142 + while retry_count < max_retries:
143 + try:
144 + # 获取包含 '上传中' 文本的元素数量
145 + number = await page.locator("text=上传中").count()
146 +
147 + if number == 0:
148 + kuaishou_logger.success("视频上传完毕")
149 + break
150 + else:
151 + if retry_count % 5 == 0:
152 + kuaishou_logger.info("正在上传视频中...")
153 + await asyncio.sleep(2)
154 + except Exception as e:
155 + kuaishou_logger.error(f"检查上传状态时发生错误: {e}")
156 + await asyncio.sleep(2) # 等待 2 秒后重试
157 + retry_count += 1
158 +
159 + if retry_count == max_retries:
160 + kuaishou_logger.warning("超过最大重试次数,视频上传可能未完成。")
161 +
162 + # 定时任务
163 + if self.publish_date != 0:
164 + await self.set_schedule_time(page, self.publish_date)
165 +
166 + # 判断视频是否发布成功
167 + while True:
168 + try:
169 + publish_button = page.get_by_text("发布", exact=True)
170 + if await publish_button.count() > 0:
171 + await publish_button.click()
172 +
173 + await asyncio.sleep(1)
174 + confirm_button = page.get_by_text("确认发布")
175 + if await confirm_button.count() > 0:
176 + await confirm_button.click()
177 +
178 + # 等待页面跳转,确认发布成功
179 + await page.wait_for_url(
180 + "https://cp.kuaishou.com/article/manage/video?status=2&from=publish",
181 + timeout=5000,
182 + )
183 + kuaishou_logger.success("视频发布成功")
184 + break
185 + except Exception as e:
186 + kuaishou_logger.info(f"视频正在发布中... 错误: {e}")
187 + await page.screenshot(full_page=True)
188 + await asyncio.sleep(1)
189 +
190 + await context.storage_state(path=self.account_file) # 保存cookie
191 + kuaishou_logger.info('cookie更新完毕!')
192 + await asyncio.sleep(2) # 这里延迟是为了方便眼睛直观的观看
193 + # 关闭浏览器上下文和浏览器实例
194 + await context.close()
195 + await browser.close()
196 +
197 + async def main(self):
198 + async with async_playwright() as playwright:
199 + await self.upload(playwright)
200 +
201 + async def set_schedule_time(self, page, publish_date):
202 + kuaishou_logger.info("click schedule")
203 + publish_date_hour = publish_date.strftime("%Y-%m-%d %H:%M:%S")
204 + await page.locator("label:text('发布时间')").locator('xpath=following-sibling::div').locator(
205 + '.ant-radio-input').nth(1).click()
206 + await asyncio.sleep(1)
207 +
208 + await page.locator('div.ant-picker-input input[placeholder="选择日期时间"]').click()
209 + await asyncio.sleep(1)
210 +
211 + await page.keyboard.press("Control+KeyA")
212 + await page.keyboard.type(str(publish_date_hour))
213 + await page.keyboard.press("Enter")
214 + await asyncio.sleep(1)
1 +from pathlib import Path
2 +
3 +from conf import BASE_DIR
4 +
5 +Path(BASE_DIR / "cookies" / "tencent_uploader").mkdir(exist_ok=True)
...\ No newline at end of file ...\ No newline at end of file
This diff is collapsed. Click to expand it.
1 +from pathlib import Path
2 +
3 +from conf import BASE_DIR
4 +
5 +Path(BASE_DIR / "cookies" / "tk_uploader").mkdir(exist_ok=True)
...\ No newline at end of file ...\ No newline at end of file
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
1 +
2 +class Tk_Locator(object):
3 + tk_iframe = '[data-tt="Upload_index_iframe"]'
4 + default = 'body'
1 +import os
2 +
3 +from utils.base_social_media import set_init_script
4 +from utils.files_times import get_absolute_path
5 +from utils.log import kuaishou_logger
6 +from playwright.async_api import Playwright, async_playwright
7 +
8 +
9 +async def wyh_setup(account_file, handle=False):
10 + print(account_file)
11 + account_file = get_absolute_path(account_file, "wyh_uploader")
12 + if not os.path.exists(account_file):
13 + if not handle:
14 + return False
15 + kuaishou_logger.info('[+] cookie文件不存在或已失效,即将自动打开浏览器,请扫码登录,登陆后会自动生成cookie文件')
16 + await get_wyh_cookie(account_file)
17 + else:
18 + await open_wyh_main_page(account_file)
19 + return True
20 +
21 +
22 +async def open_wyh_main_page(account_file):
23 + async with async_playwright() as playwright:
24 + options = {
25 + 'args': [
26 + '--lang en-GB'
27 + ],
28 + 'headless': False, # Set headless option here
29 + }
30 + # Make sure to run headed.
31 + browser = await playwright.chromium.launch(**options)
32 + # Setup context however you like.
33 + context = await browser.new_context(storage_state=account_file) # Pass any options
34 + context = await set_init_script(context)
35 + # Pause the page, and start recording manually.
36 + page = await context.new_page()
37 + await page.goto("https://mp.163.com")
38 +
39 + board = page.locator("//*[@class='homeV4__board__card__data__value']")
40 + # html_content = await board.inner_html()
41 + # print(html_content)
42 + content = await board.nth(0).inner_text() #总粉丝数
43 + print(content)
44 + content = await board.nth(1).inner_text() #总阅读数
45 + print(content)
46 + content = await board.nth(2).inner_text() #总收益
47 + print(content)
48 +
49 + await page.get_by_text("粉丝数据").click()
50 + await page.wait_for_timeout(5000)
51 + await page.get_by_text("内容数据").click()
52 + await page.wait_for_timeout(5000)
53 + await page.get_by_text("收益数据").click()
54 + await page.wait_for_timeout(5000)
55 + browser.close()
56 +
57 +
58 +async def get_wyh_cookie(account_file):
59 + print("get_wyh_cookie")
60 + async with async_playwright() as playwright:
61 + options = {
62 + 'args': [
63 + '--lang en-GB'
64 + ],
65 + 'headless': False, # Set headless option here
66 + }
67 + # Make sure to run headed.
68 + browser = await playwright.chromium.launch(**options)
69 + # Setup context however you like.
70 + context = await browser.new_context() # Pass any options
71 + context = await set_init_script(context)
72 + # Pause the page, and start recording manually.
73 + page = await context.new_page()
74 + await page.goto("https://mp.163.com")
75 + # await page.pause()
76 + # 点击调试器的继续,保存cookie
77 + # await context.storage_state(path=account_file)
78 +
79 + # 自动登陆
80 + await page.wait_for_timeout(20000)
81 + frame = page.frame_locator('//iframe[contains(@id, "x-URS-iframe")]')
82 + await frame.locator('[name="email"]').fill('liufuhua007@163.com')
83 + await frame.locator('[name="password"]').fill("Liuyihong1023@")
84 + await frame.locator('#dologin').click()
85 +
86 + # await page.pause()
87 + await page.wait_for_timeout(15000)
88 + # 点击调试器的继续,保存cookie
89 + await context.storage_state(path=account_file)
1 +[account1]
2 +cookies = changeme
1 +import configparser
2 +import json
3 +import pathlib
4 +from time import sleep
5 +
6 +import requests
7 +from playwright.sync_api import sync_playwright
8 +
9 +from conf import BASE_DIR, XHS_SERVER
10 +
11 +config = configparser.RawConfigParser()
12 +config.read('accounts.ini')
13 +
14 +
15 +def sign_local(uri, data=None, a1="", web_session=""):
16 + for _ in range(10):
17 + try:
18 + with sync_playwright() as playwright:
19 + stealth_js_path = pathlib.Path(BASE_DIR / "utils/stealth.min.js")
20 + chromium = playwright.chromium
21 +
22 + # 如果一直失败可尝试设置成 False 让其打开浏览器,适当添加 sleep 可查看浏览器状态
23 + browser = chromium.launch(headless=True)
24 +
25 + browser_context = browser.new_context()
26 + browser_context.add_init_script(path=stealth_js_path)
27 + context_page = browser_context.new_page()
28 + context_page.goto("https://www.xiaohongshu.com")
29 + browser_context.add_cookies([
30 + {'name': 'a1', 'value': a1, 'domain': ".xiaohongshu.com", 'path': "/"}]
31 + )
32 + context_page.reload()
33 + # 这个地方设置完浏览器 cookie 之后,如果这儿不 sleep 一下签名获取就失败了,如果经常失败请设置长一点试试
34 + sleep(2)
35 + encrypt_params = context_page.evaluate("([url, data]) => window._webmsxyw(url, data)", [uri, data])
36 + return {
37 + "x-s": encrypt_params["X-s"],
38 + "x-t": str(encrypt_params["X-t"])
39 + }
40 + except Exception:
41 + # 这儿有时会出现 window._webmsxyw is not a function 或未知跳转错误,因此加一个失败重试趴
42 + pass
43 + raise Exception("重试了这么多次还是无法签名成功,寄寄寄")
44 +
45 +
46 +def sign(uri, data=None, a1="", web_session=""):
47 + # 填写自己的 flask 签名服务端口地址
48 + res = requests.post(f"{XHS_SERVER}/sign",
49 + json={"uri": uri, "data": data, "a1": a1, "web_session": web_session})
50 + signs = res.json()
51 + return {
52 + "x-s": signs["x-s"],
53 + "x-t": signs["x-t"]
54 + }
55 +
56 +
57 +def beauty_print(data: dict):
58 + print(json.dumps(data, ensure_ascii=False, indent=2))
1 +import datetime
2 +import json
3 +import qrcode
4 +from time import sleep
5 +
6 +from xhs import XhsClient
7 +
8 +from uploader.xhs_uploader.main import sign
9 +
10 +# pip install qrcode
11 +if __name__ == '__main__':
12 + xhs_client = XhsClient(sign=sign, timeout=60)
13 + print(datetime.datetime.now())
14 + qr_res = xhs_client.get_qrcode()
15 + qr_id = qr_res["qr_id"]
16 + qr_code = qr_res["code"]
17 +
18 + qr = qrcode.QRCode(version=1, error_correction=qrcode.ERROR_CORRECT_L,
19 + box_size=50,
20 + border=1)
21 + qr.add_data(qr_res["url"])
22 + qr.make()
23 + qr.print_ascii()
24 +
25 + while True:
26 + check_qrcode = xhs_client.check_qrcode(qr_id, qr_code)
27 + print(check_qrcode)
28 + sleep(1)
29 + if check_qrcode["code_status"] == 2:
30 + print(json.dumps(check_qrcode["login_info"], indent=4))
31 + print("当前 cookie:" + xhs_client.cookie)
32 + break
33 +
34 + print(json.dumps(xhs_client.get_self_info(), indent=4))
...\ No newline at end of file ...\ No newline at end of file
File mode changed
1 +from pathlib import Path
2 +from typing import List
3 +
4 +from conf import BASE_DIR
5 +
6 +SOCIAL_MEDIA_DOUYIN = "douyin"
7 +SOCIAL_MEDIA_TENCENT = "tencent"
8 +SOCIAL_MEDIA_TIKTOK = "tiktok"
9 +SOCIAL_MEDIA_BILIBILI = "bilibili"
10 +SOCIAL_MEDIA_KUAISHOU = "kuaishou"
11 +SOCIAL_MEDIA_WANGYIHAO = 'wangyihao'
12 +
13 +
14 +def get_supported_social_media() -> List[str]:
15 + return [SOCIAL_MEDIA_DOUYIN, SOCIAL_MEDIA_TENCENT, SOCIAL_MEDIA_TIKTOK, SOCIAL_MEDIA_KUAISHOU,SOCIAL_MEDIA_WANGYIHAO]
16 +
17 +
18 +def get_cli_action() -> List[str]:
19 + return ["upload", "login", "watch"]
20 +
21 +
22 +async def set_init_script(context):
23 + stealth_js_path = Path(BASE_DIR / "utils/stealth.min.js")
24 + await context.add_init_script(path=stealth_js_path)
25 + return context
1 +import enum
2 +
3 +
4 +class TencentZoneTypes(enum.Enum):
5 + LIFESTYLE = '生活'
6 + CUTE_KIDS = '萌娃'
7 + MUSIC = '音乐'
8 + KNOWLEDGE = '知识'
9 + EMOTION = '情感'
10 + TRAVEL_SCENERY = '旅行风景'
11 + FASHION = '时尚'
12 + FOOD = '美食'
13 + LIFE_HACKS = '生活技巧'
14 + DANCE = '舞蹈'
15 + MOVIES_TV_SHOWS = '影视综艺'
16 + SPORTS = '运动'
17 + FUNNY = '搞笑'
18 + CELEBRITIES = '明星名人'
19 + NEWS_INFO = '新闻资讯'
20 + GAMING = '游戏'
21 + AUTOMOTIVE = '车'
22 + ANIME = '二次元'
23 + TALENT = '才艺'
24 + CUTE_PETS = '萌宠'
25 + INDUSTRY_MACHINERY_CONSTRUCTION = '机械'
26 + ANIMALS = '动物'
27 + PARENTING = '育儿'
28 + TECHNOLOGY = '科技'
29 +
30 +class VideoZoneTypes(enum.Enum):
31 + """
32 + 所有分区枚举
33 +
34 + - MAINPAGE: 主页
35 + - ANIME: 番剧
36 + - ANIME_SERIAL: 连载中番剧
37 + - ANIME_FINISH: 已完结番剧
38 + - ANIME_INFORMATION: 资讯
39 + - ANIME_OFFICAL: 官方延伸
40 + - MOVIE: 电影
41 + - GUOCHUANG: 国创
42 + - GUOCHUANG_CHINESE: 国产动画
43 + - GUOCHUANG_ORIGINAL: 国产原创相关
44 + - GUOCHUANG_PUPPETRY: 布袋戏
45 + - GUOCHUANG_MOTIONCOMIC: 动态漫·广播剧
46 + - GUOCHUANG_INFORMATION: 资讯
47 + - TELEPLAY: 电视剧
48 + - DOCUMENTARY: 纪录片
49 + - DOUGA: 动画
50 + - DOUGA_MAD: MAD·AMV
51 + - DOUGA_MMD: MMD·3D
52 + - DOUGA_VOICE: 短片·手书·配音
53 + - DOUGA_GARAGE_KIT: 手办·模玩
54 + - DOUGA_TOKUSATSU: 特摄
55 + - DOUGA_ACGNTALKS: 动漫杂谈
56 + - DOUGA_OTHER: 综合
57 + - GAME: 游戏
58 + - GAME_STAND_ALONE: 单机游戏
59 + - GAME_ESPORTS: 电子竞技
60 + - GAME_MOBILE: 手机游戏
61 + - GAME_ONLINE: 网络游戏
62 + - GAME_BOARD: 桌游棋牌
63 + - GAME_GMV: GMV
64 + - GAME_MUSIC: 音游
65 + - GAME_MUGEN: Mugen
66 + - KICHIKU: 鬼畜
67 + - KICHIKU_GUIDE: 鬼畜调教
68 + - KICHIKU_MAD: 音MAD
69 + - KICHIKU_MANUAL_VOCALOID: 人力VOCALOID
70 + - KICHIKU_THEATRE: 鬼畜剧场
71 + - KICHIKU_COURSE: 教程演示
72 + - MUSIC: 音乐
73 + - MUSIC_ORIGINAL: 原创音乐
74 + - MUSIC_COVER: 翻唱
75 + - MUSIC_PERFORM: 演奏
76 + - MUSIC_VOCALOID: VOCALOID·UTAU
77 + - MUSIC_LIVE: 音乐现场
78 + - MUSIC_MV: MV
79 + - MUSIC_COMMENTARY: 乐评盘点
80 + - MUSIC_TUTORIAL: 音乐教学
81 + - MUSIC_OTHER: 音乐综合
82 + - DANCE: 舞蹈
83 + - DANCE_OTAKU: 宅舞
84 + - DANCE_HIPHOP: 街舞
85 + - DANCE_STAR: 明星舞蹈
86 + - DANCE_CHINA: 中国舞
87 + - DANCE_THREE_D: 舞蹈综合
88 + - DANCE_DEMO: 舞蹈教程
89 + - CINEPHILE: 影视
90 + - CINEPHILE_CINECISM: 影视杂谈
91 + - CINEPHILE_MONTAGE: 影视剪辑
92 + - CINEPHILE_SHORTFILM: 小剧场
93 + - CINEPHILE_TRAILER_INFO: 预告·资讯
94 + - ENT: 娱乐
95 + - ENT_VARIETY: 综艺
96 + - ENT_TALKER: 娱乐杂谈
97 + - ENT_FANS: 粉丝创作
98 + - ENT_CELEBRITY: 明星综合
99 + - KNOWLEDGE: 知识
100 + - KNOWLEDGE_SCIENCE: 科学科普
101 + - KNOWLEDGE_SOCIAL_SCIENCE: 社科·法律·心理
102 + - KNOWLEDGE_HUMANITY_HISTORY: 人文历史
103 + - KNOWLEDGE_BUSINESS: 财经商业
104 + - KNOWLEDGE_CAMPUS: 校园学习
105 + - KNOWLEDGE_CAREER: 职业职场
106 + - KNOWLEDGE_DESIGN: 设计·创意
107 + - KNOWLEDGE_SKILL: 野生技能协会
108 + - TECH: 科技
109 + - TECH_DIGITAL: 数码
110 + - TECH_APPLICATION: 软件应用
111 + - TECH_COMPUTER_TECH: 计算机技术
112 + - TECH_INDUSTRY: 科工机械
113 + - INFORMATION: 资讯
114 + - INFORMATION_HOTSPOT: 热点
115 + - INFORMATION_GLOBAL: 环球
116 + - INFORMATION_SOCIAL: 社会
117 + - INFORMATION_MULTIPLE: 综合
118 + - FOOD: 美食
119 + - FOOD_MAKE: 美食制作
120 + - FOOD_DETECTIVE: 美食侦探
121 + - FOOD_MEASUREMENT: 美食测评
122 + - FOOD_RURAL: 田园美食
123 + - FOOD_RECORD: 美食记录
124 + - LIFE: 生活
125 + - LIFE_FUNNY: 搞笑
126 + - LIFE_TRAVEL: 出行
127 + - LIFE_RURALLIFE: 三农
128 + - LIFE_HOME: 家居房产
129 + - LIFE_HANDMAKE: 手工
130 + - LIFE_PAINTING: 绘画
131 + - LIFE_DAILY: 日常
132 + - CAR: 汽车
133 + - CAR_RACING: 赛车
134 + - CAR_MODIFIEDVEHICLE: 改装玩车
135 + - CAR_NEWENERGYVEHICLE: 新能源车
136 + - CAR_TOURINGCAR: 房车
137 + - CAR_MOTORCYCLE: 摩托车
138 + - CAR_STRATEGY: 购车攻略
139 + - CAR_LIFE: 汽车生活
140 + - FASHION: 时尚
141 + - FASHION_MAKEUP: 美妆护肤
142 + - FASHION_COS: 仿妆cos
143 + - FASHION_CLOTHING: 穿搭
144 + - FASHION_TREND: 时尚潮流
145 + - SPORTS: 运动
146 + - SPORTS_BASKETBALL: 篮球
147 + - SPORTS_FOOTBALL: 足球
148 + - SPORTS_AEROBICS: 健身
149 + - SPORTS_ATHLETIC: 竞技体育
150 + - SPORTS_CULTURE: 运动文化
151 + - SPORTS_COMPREHENSIVE: 运动综合
152 + - ANIMAL: 动物圈
153 + - ANIMAL_CAT: 喵星人
154 + - ANIMAL_DOG: 汪星人
155 + - ANIMAL_PANDA: 大熊猫
156 + - ANIMAL_WILD_ANIMAL: 野生动物
157 + - ANIMAL_REPTILES: 爬宠
158 + - ANIMAL_COMPOSITE: 动物综合
159 + - VLOG: VLOG
160 + """
161 +
162 + MAINPAGE = 0
163 +
164 + ANIME = 13
165 + ANIME_SERIAL = 33
166 + ANIME_FINISH = 32
167 + ANIME_INFORMATION = 51
168 + ANIME_OFFICAL = 152
169 +
170 + MOVIE = 23
171 +
172 + GUOCHUANG = 167
173 + GUOCHUANG_CHINESE = 153
174 + GUOCHUANG_ORIGINAL = 168
175 + GUOCHUANG_PUPPETRY = 169
176 + GUOCHUANG_MOTIONCOMIC = 195
177 + GUOCHUANG_INFORMATION = 170
178 +
179 + TELEPLAY = 11
180 +
181 + DOCUMENTARY = 177
182 +
183 + DOUGA = 1
184 + DOUGA_MAD = 24
185 + DOUGA_MMD = 25
186 + DOUGA_VOICE = 47
187 + DOUGA_GARAGE_KIT = 210
188 + DOUGA_TOKUSATSU = 86
189 + DOUGA_ACGNTALKS = 253
190 + DOUGA_OTHER = 27
191 +
192 + GAME = 4
193 + GAME_STAND_ALONE = 17
194 + GAME_ESPORTS = 171
195 + GAME_MOBILE = 172
196 + GAME_ONLINE = 65
197 + GAME_BOARD = 173
198 + GAME_GMV = 121
199 + GAME_MUSIC = 136
200 + GAME_MUGEN = 19
201 +
202 + KICHIKU = 119
203 + KICHIKU_GUIDE = 22
204 + KICHIKU_MAD = 26
205 + KICHIKU_MANUAL_VOCALOID = 126
206 + KICHIKU_THEATRE = 216
207 + KICHIKU_COURSE = 127
208 +
209 + MUSIC = 3
210 + MUSIC_ORIGINAL = 28
211 + MUSIC_COVER = 31
212 + MUSIC_PERFORM = 59
213 + MUSIC_VOCALOID = 30
214 + MUSIC_LIVE = 29
215 + MUSIC_MV = 193
216 + MUSIC_COMMENTARY = 243
217 + MUSIC_TUTORIAL = 244
218 + MUSIC_OTHER = 130
219 +
220 + DANCE = 129
221 + DANCE_OTAKU = 20
222 + DANCE_HIPHOP = 198
223 + DANCE_STAR = 199
224 + DANCE_CHINA = 200
225 + DANCE_THREE_D = 154
226 + DANCE_DEMO = 156
227 +
228 + CINEPHILE = 181
229 + CINEPHILE_CINECISM = 182
230 + CINEPHILE_MONTAGE = 183
231 + CINEPHILE_SHORTFILM = 85
232 + CINEPHILE_TRAILER_INFO = 184
233 +
234 + ENT = 5
235 + ENT_VARIETY = 71
236 + ENT_TALKER = 241
237 + ENT_FANS = 242
238 + ENT_CELEBRITY = 137
239 +
240 + KNOWLEDGE = 36
241 + KNOWLEDGE_SCIENCE = 201
242 + KNOWLEDGE_SOCIAL_SCIENCE = 124
243 + KNOWLEDGE_HUMANITY_HISTORY = 228
244 + KNOWLEDGE_BUSINESS = 207
245 + KNOWLEDGE_CAMPUS = 208
246 + KNOWLEDGE_CAREER = 209
247 + KNOWLEDGE_DESIGN = 229
248 + KNOWLEDGE_SKILL = 122
249 +
250 + TECH = 188
251 + TECH_DIGITAL = 95
252 + TECH_APPLICATION = 230
253 + TECH_COMPUTER_TECH = 231
254 + TECH_INDUSTRY = 232
255 +
256 + INFORMATION = 202
257 + INFORMATION_HOTSPOT = 203
258 + INFORMATION_GLOBAL = 204
259 + INFORMATION_SOCIAL = 205
260 + INFORMATION_MULTIPLE = 206
261 +
262 + FOOD = 211
263 + FOOD_MAKE = 76
264 + FOOD_DETECTIVE = 212
265 + FOOD_MEASUREMENT = 213
266 + FOOD_RURAL = 214
267 + FOOD_RECORD = 215
268 +
269 + LIFE = 160
270 + LIFE_FUNNY = 138
271 + LIFE_TRAVEL = 250
272 + LIFE_RURALLIFE = 251
273 + LIFE_HOME = 239
274 + LIFE_HANDMAKE = 161
275 + LIFE_PAINTING = 162
276 + LIFE_DAILY = 21
277 +
278 + CAR = 223
279 + CAR_RACING = 245
280 + CAR_MODIFIEDVEHICLE = 246
281 + CAR_NEWENERGYVEHICLE = 247
282 + CAR_TOURINGCAR = 248
283 + CAR_MOTORCYCLE = 240
284 + CAR_STRATEGY = 227
285 + CAR_LIFE = 176
286 +
287 + FASHION = 155
288 + FASHION_MAKEUP = 157
289 + FASHION_COS = 252
290 + FASHION_CLOTHING = 158
291 + FASHION_TREND = 159
292 +
293 + SPORTS = 234
294 + SPORTS_BASKETBALL = 235
295 + SPORTS_FOOTBALL = 249
296 + SPORTS_AEROBICS = 164
297 + SPORTS_ATHLETIC = 236
298 + SPORTS_CULTURE = 237
299 + SPORTS_COMPREHENSIVE = 238
300 +
301 + ANIMAL = 217
302 + ANIMAL_CAT = 218
303 + ANIMAL_DOG = 219
304 + ANIMAL_PANDA = 220
305 + ANIMAL_WILD_ANIMAL = 221
306 + ANIMAL_REPTILES = 222
307 + ANIMAL_COMPOSITE = 75
308 +
309 + VLOG = 19
1 +from datetime import timedelta
2 +
3 +from datetime import datetime
4 +from pathlib import Path
5 +
6 +from conf import BASE_DIR
7 +
8 +
9 +def get_absolute_path(relative_path: str, base_dir: str = None) -> str:
10 + # Convert the relative path to an absolute path
11 + absolute_path = Path(BASE_DIR) / base_dir / relative_path
12 + return str(absolute_path)
13 +
14 +
15 +def get_title_and_hashtags(filename):
16 + """
17 + 获取视频标题和 hashtag
18 +
19 + Args:
20 + filename: 视频文件名
21 +
22 + Returns:
23 + 视频标题和 hashtag 列表
24 + """
25 +
26 + # 获取视频标题和 hashtag txt 文件名
27 + txt_filename = filename.replace(".mp4", ".txt")
28 +
29 + # 读取 txt 文件
30 + with open(txt_filename, "r", encoding="utf-8") as f:
31 + content = f.read()
32 +
33 + # 获取标题和 hashtag
34 + splite_str = content.strip().split("\n")
35 + title = splite_str[0]
36 + hashtags = splite_str[1].replace("#", "").split(" ")
37 +
38 + return title, hashtags
39 +
40 +
41 +def generate_schedule_time_next_day(total_videos, videos_per_day, daily_times=None, timestamps=False, start_days=0):
42 + """
43 + Generate a schedule for video uploads, starting from the next day.
44 +
45 + Args:
46 + - total_videos: Total number of videos to be uploaded.
47 + - videos_per_day: Number of videos to be uploaded each day.
48 + - daily_times: Optional list of specific times of the day to publish the videos.
49 + - timestamps: Boolean to decide whether to return timestamps or datetime objects.
50 + - start_days: Start from after start_days.
51 +
52 + Returns:
53 + - A list of scheduling times for the videos, either as timestamps or datetime objects.
54 + """
55 + if videos_per_day <= 0:
56 + raise ValueError("videos_per_day should be a positive integer")
57 +
58 + if daily_times is None:
59 + # Default times to publish videos if not provided
60 + daily_times = [6, 11, 14, 16, 22]
61 +
62 + if videos_per_day > len(daily_times):
63 + raise ValueError("videos_per_day should not exceed the length of daily_times")
64 +
65 + # Generate timestamps
66 + schedule = []
67 + current_time = datetime.now()
68 +
69 + for video in range(total_videos):
70 + day = video // videos_per_day + start_days + 1 # +1 to start from the next day
71 + daily_video_index = video % videos_per_day
72 +
73 + # Calculate the time for the current video
74 + hour = daily_times[daily_video_index]
75 + time_offset = timedelta(days=day, hours=hour - current_time.hour, minutes=-current_time.minute,
76 + seconds=-current_time.second, microseconds=-current_time.microsecond)
77 + timestamp = current_time + time_offset
78 +
79 + schedule.append(timestamp)
80 +
81 + if timestamps:
82 + schedule = [int(time.timestamp()) for time in schedule]
83 + return schedule
1 +from pathlib import Path
2 +from sys import stdout
3 +from loguru import logger
4 +
5 +from conf import BASE_DIR
6 +
7 +
8 +def log_formatter(record: dict) -> str:
9 + """
10 + Formatter for log records.
11 + :param dict record: Log object containing log metadata & message.
12 + :returns: str
13 + """
14 + colors = {
15 + "TRACE": "#cfe2f3",
16 + "INFO": "#9cbfdd",
17 + "DEBUG": "#8598ea",
18 + "WARNING": "#dcad5a",
19 + "SUCCESS": "#3dd08d",
20 + "ERROR": "#ae2c2c"
21 + }
22 + color = colors.get(record["level"].name, "#b3cfe7")
23 + return f"<fg #70acde>{{time:YYYY-MM-DD HH:mm:ss}}</fg #70acde> | <fg {color}>{{level}}</fg {color}>: <light-white>{{message}}</light-white>\n"
24 +
25 +
26 +def create_logger(log_name: str, file_path: str):
27 + """
28 + Create custom logger for different business modules.
29 + :param str log_name: name of log
30 + :param str file_path: Optional path to log file
31 + :returns: Configured logger
32 + """
33 + def filter_record(record):
34 + return record["extra"].get("business_name") == log_name
35 +
36 + Path(BASE_DIR / file_path).parent.mkdir(exist_ok=True)
37 + logger.add(Path(BASE_DIR / file_path), filter=filter_record, level="INFO", rotation="10 MB", retention="10 days", backtrace=True, diagnose=True)
38 + return logger.bind(business_name=log_name)
39 +
40 +
41 +# Remove all existing handlers
42 +logger.remove()
43 +# Add a standard console handler
44 +logger.add(stdout, colorize=True, format=log_formatter)
45 +
46 +douyin_logger = create_logger('douyin', 'logs/douyin.log')
47 +tencent_logger = create_logger('tencent', 'logs/tencent.log')
48 +xhs_logger = create_logger('xhs', 'logs/xhs.log')
49 +tiktok_logger = create_logger('tiktok', 'logs/tiktok.log')
50 +bilibili_logger = create_logger('bilibili', 'logs/bilibili.log')
51 +kuaishou_logger = create_logger('kuaishou', 'logs/kuaishou.log')
This diff could not be displayed because it is too large.
No preview for this file type
1 +这位勇敢的男子为了心爱之人每天坚守 🥺❤️‍🩹
2 +#坚持不懈 #爱情执着 #奋斗使者 #短视频
...\ No newline at end of file ...\ No newline at end of file