Showing
5 changed files
with
232 additions
and
55 deletions
| ... | @@ -9,6 +9,7 @@ from uploader.douyin_uploader.main import douyin_setup, DouYinVideo | ... | @@ -9,6 +9,7 @@ from uploader.douyin_uploader.main import douyin_setup, DouYinVideo |
| 9 | from uploader.ks_uploader.main import ks_setup, KSVideo | 9 | from uploader.ks_uploader.main import ks_setup, KSVideo |
| 10 | from uploader.tencent_uploader.main import weixin_setup, TencentVideo | 10 | from uploader.tencent_uploader.main import weixin_setup, TencentVideo |
| 11 | from uploader.tk_uploader.main_chrome import tiktok_setup, TiktokVideo | 11 | from uploader.tk_uploader.main_chrome import tiktok_setup, TiktokVideo |
| 12 | +from uploader.xhs_uploader.main import xhs_setup, XHSVideo | ||
| 12 | from uploader.wyh_uploader.main import wyh_setup | 13 | from uploader.wyh_uploader.main import wyh_setup |
| 13 | from uploader.toutiao_uploader.main import toutiao_setup | 14 | from uploader.toutiao_uploader.main import toutiao_setup |
| 14 | from utils.base_social_media import get_supported_social_media, get_cli_action, SOCIAL_MEDIA_DOUYIN, \ | 15 | from utils.base_social_media import get_supported_social_media, get_cli_action, SOCIAL_MEDIA_DOUYIN, \ |
| ... | @@ -73,8 +74,8 @@ async def main(): | ... | @@ -73,8 +74,8 @@ async def main(): |
| 73 | await ks_setup(str(account_file), handle=True) | 74 | await ks_setup(str(account_file), handle=True) |
| 74 | elif args.platform == SOCIAL_MEDIA_WANGYIHAO: | 75 | elif args.platform == SOCIAL_MEDIA_WANGYIHAO: |
| 75 | await wyh_setup(str(account_file), handle=True) | 76 | await wyh_setup(str(account_file), handle=True) |
| 76 | - # elif args.platform == SOCIAL_MEDIA_XHS: | 77 | + elif args.platform == SOCIAL_MEDIA_XHS: |
| 77 | - # await xhs_setup(str(account_file), handle=True) | 78 | + await xhs_setup(str(account_file), handle=True) |
| 78 | elif args.platform == SOCIAL_MEDIA_TOUTIAO: | 79 | elif args.platform == SOCIAL_MEDIA_TOUTIAO: |
| 79 | await toutiao_setup(str(account_file), handle=True) | 80 | await toutiao_setup(str(account_file), handle=True) |
| 80 | elif args.platform == SOCIAL_MEDIA_BAIJIAHAO: | 81 | elif args.platform == SOCIAL_MEDIA_BAIJIAHAO: |
| ... | @@ -104,6 +105,9 @@ async def main(): | ... | @@ -104,6 +105,9 @@ async def main(): |
| 104 | elif args.platform == SOCIAL_MEDIA_KUAISHOU: | 105 | elif args.platform == SOCIAL_MEDIA_KUAISHOU: |
| 105 | await ks_setup(account_file, handle=True) | 106 | await ks_setup(account_file, handle=True) |
| 106 | app = KSVideo(title, video_file, tags, publish_date, account_file) | 107 | app = KSVideo(title, video_file, tags, publish_date, account_file) |
| 108 | + elif args.platform == SOCIAL_MEDIA_XHS: | ||
| 109 | + await xhs_setup(account_file, handle=True) | ||
| 110 | + app = XHSVideo(title, video_file, tags, publish_date, account_file) | ||
| 107 | else: | 111 | else: |
| 108 | print("Wrong platform, please check your input") | 112 | print("Wrong platform, please check your input") |
| 109 | exit() | 113 | exit() | ... | ... |
| 1 | -import configparser | 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 | ||
| 2 | import json | 7 | import json |
| 3 | -import pathlib | 8 | +from conf import LOCAL_CHROME_PATH |
| 4 | -from time import sleep | 9 | +from utils.base_social_media import set_init_script |
| 10 | +from utils.files_times import get_absolute_path | ||
| 11 | +from utils.log import xhs_logger | ||
| 12 | + | ||
| 5 | 13 | ||
| 6 | -import requests | 14 | +async def write_cookies_to_file(cookies, file_path): |
| 7 | -from playwright.sync_api import sync_playwright | ||
| 8 | 15 | ||
| 9 | -from conf import BASE_DIR, XHS_SERVER | 16 | + with open(file_path, 'w') as f: |
| 17 | + json.dump(cookies, f) | ||
| 10 | 18 | ||
| 11 | -config = configparser.RawConfigParser() | ||
| 12 | -config.read('accounts.ini') | ||
| 13 | 19 | ||
| 20 | +async def read_cookies_from_file(file_path): | ||
| 21 | + try: | ||
| 22 | + with open(file_path, 'r') as f: | ||
| 23 | + return json.load(f) | ||
| 24 | + except FileNotFoundError: | ||
| 25 | + return [] | ||
| 14 | 26 | ||
| 15 | -def sign_local(uri, data=None, a1="", web_session=""): | 27 | +async def cookie_auth(account_file): |
| 16 | - for _ in range(10): | 28 | + print(account_file) |
| 29 | + async with async_playwright() as playwright: | ||
| 30 | + browser = await playwright.chromium.launch(headless=True) | ||
| 31 | + # context = await browser.new_context(storage_state=account_file) | ||
| 32 | + context = await browser.new_context() | ||
| 33 | + page = await context.new_page() | ||
| 34 | + saved_cookies = await read_cookies_from_file(account_file) | ||
| 35 | + await context.add_cookies(saved_cookies) | ||
| 36 | + await page.goto("https://www.xiaohongshu.com") | ||
| 17 | try: | 37 | try: |
| 18 | - with sync_playwright() as playwright: | 38 | + print(await page.title()) |
| 19 | - stealth_js_path = pathlib.Path(BASE_DIR / "utils/stealth.min.js") | 39 | + await page.wait_for_selector("div.link-wrapper a.link-wrapper span.channel", timeout=5000) # 等待5秒 |
| 20 | - chromium = playwright.chromium | 40 | + xhs_logger.success("[+] cookie 有效") |
| 21 | - | 41 | + return True |
| 22 | - # 如果一直失败可尝试设置成 False 让其打开浏览器,适当添加 sleep 可查看浏览器状态 | 42 | + except: |
| 23 | - browser = chromium.launch(headless=True) | 43 | + xhs_logger.info("[+] 等待5秒 cookie 失效") |
| 24 | - | 44 | + return False |
| 25 | - browser_context = browser.new_context() | 45 | + |
| 26 | - browser_context.add_init_script(path=stealth_js_path) | 46 | + |
| 27 | - context_page = browser_context.new_page() | 47 | +async def xhs_setup(account_file, handle=False): |
| 28 | - context_page.goto("https://www.xiaohongshu.com") | 48 | + account_file = get_absolute_path(account_file, "xhs_uploader") |
| 29 | - browser_context.add_cookies([ | 49 | + if not os.path.exists(account_file) or not await cookie_auth(account_file): |
| 30 | - {'name': 'a1', 'value': a1, 'domain': ".xiaohongshu.com", 'path': "/"}] | 50 | + if not handle: |
| 31 | - ) | 51 | + return False |
| 32 | - context_page.reload() | 52 | + xhs_logger.info('[+] cookie文件不存在或已失效,即将自动打开浏览器,请扫码登录,登陆后会自动生成cookie文件') |
| 33 | - # 这个地方设置完浏览器 cookie 之后,如果这儿不 sleep 一下签名获取就失败了,如果经常失败请设置长一点试试 | 53 | + await get_xhs_cookie(account_file) |
| 34 | - sleep(2) | 54 | + return True |
| 35 | - encrypt_params = context_page.evaluate("([url, data]) => window._webmsxyw(url, data)", [uri, data]) | 55 | + |
| 36 | - return { | 56 | + |
| 37 | - "x-s": encrypt_params["X-s"], | 57 | +async def get_xhs_cookie(account_file): |
| 38 | - "x-t": str(encrypt_params["X-t"]) | 58 | + async with async_playwright() as playwright: |
| 39 | - } | 59 | + options = { |
| 40 | - except Exception: | 60 | + 'args': [ |
| 41 | - # 这儿有时会出现 window._webmsxyw is not a function 或未知跳转错误,因此加一个失败重试趴 | 61 | + '--lang en-GB' |
| 42 | - pass | 62 | + ], |
| 43 | - raise Exception("重试了这么多次还是无法签名成功,寄寄寄") | 63 | + 'headless': False, # Set headless option here |
| 44 | - | 64 | + } |
| 45 | - | 65 | + # Make sure to run headed. |
| 46 | -def sign(uri, data=None, a1="", web_session=""): | 66 | + browser = await playwright.chromium.launch(**options) |
| 47 | - # 填写自己的 flask 签名服务端口地址 | 67 | + # Setup context however you like. |
| 48 | - res = requests.post(f"{XHS_SERVER}/sign", | 68 | + context = await browser.new_context() # Pass any options |
| 49 | - json={"uri": uri, "data": data, "a1": a1, "web_session": web_session}) | 69 | + context = await set_init_script(context) |
| 50 | - signs = res.json() | 70 | + # Pause the page, and start recording manually. |
| 51 | - return { | 71 | + page = await context.new_page() |
| 52 | - "x-s": signs["x-s"], | 72 | + await page.goto("https://www.xiaohongshu.com") |
| 53 | - "x-t": signs["x-t"] | 73 | + await asyncio.sleep(1) |
| 54 | - } | 74 | + await page.wait_for_selector("div.link-wrapper a.link-wrapper span.channel", timeout=50000) |
| 55 | - | 75 | + print("小红书登录成功") |
| 56 | - | 76 | + await asyncio.sleep(3) |
| 57 | -def beauty_print(data: dict): | 77 | + |
| 58 | - print(json.dumps(data, ensure_ascii=False, indent=2)) | 78 | + # await page.pause() |
| 79 | + # 点击调试器的继续,保存cookie | ||
| 80 | + await asyncio.sleep(3) | ||
| 81 | + cookies = await context.cookies() | ||
| 82 | + await write_cookies_to_file(cookies, account_file) | ||
| 83 | + | ||
| 84 | + | ||
| 85 | +class XHSVideo(object): | ||
| 86 | + def __init__(self, title, file_path, tags, publish_date: datetime, account_file): | ||
| 87 | + self.title = title # 视频标题 | ||
| 88 | + self.file_path = file_path | ||
| 89 | + self.tags = tags | ||
| 90 | + self.publish_date = publish_date | ||
| 91 | + self.account_file = account_file | ||
| 92 | + self.date_format = '%Y-%m-%d %H:%M' | ||
| 93 | + self.local_executable_path = LOCAL_CHROME_PATH | ||
| 94 | + | ||
| 95 | + async def handle_upload_error(self, page): | ||
| 96 | + xhs_logger.error("视频出错了,重新上传中") | ||
| 97 | + await page.locator('div.progress-div [class^="upload-btn-input"]').set_input_files(self.file_path) | ||
| 98 | + | ||
| 99 | + async def upload(self, playwright: Playwright) -> None: | ||
| 100 | + # 使用 Chromium 浏览器启动一个浏览器实例 | ||
| 101 | + if self.local_executable_path: | ||
| 102 | + browser = await playwright.chromium.launch( | ||
| 103 | + headless=False, | ||
| 104 | + executable_path=self.local_executable_path, | ||
| 105 | + ) | ||
| 106 | + else: | ||
| 107 | + browser = await playwright.chromium.launch( | ||
| 108 | + headless=True | ||
| 109 | + ) # 创建一个浏览器上下文,使用指定的 cookie 文件 | ||
| 110 | + context = await browser.new_context() | ||
| 111 | + page = await context.new_page() | ||
| 112 | + saved_cookies = await read_cookies_from_file(self.account_file) | ||
| 113 | + await context.add_cookies(saved_cookies) | ||
| 114 | + # context = await set_init_script(context) | ||
| 115 | + # context.on("close", lambda: ) | ||
| 116 | + | ||
| 117 | + # 创建一个新的页面 | ||
| 118 | + # 访问指定的 URL | ||
| 119 | + await page.goto("https://creator.xiaohongshu.com/publish/publish?source=official") | ||
| 120 | + xhs_logger.info('正在上传-------{}.mp4'.format(self.title)) | ||
| 121 | + # 等待页面跳转到指定的 URL,没进入,则自动等待到超时 | ||
| 122 | + # 点击 "上传视频" 按钮 | ||
| 123 | + | ||
| 124 | + await page.locator("input.upload-input").set_input_files( | ||
| 125 | + self.file_path) | ||
| 126 | + | ||
| 127 | + upload_button = page.locator('button div span:has-text("发布")') | ||
| 128 | + await upload_button.wait_for(state='visible') # 确保按钮可见 | ||
| 129 | + | ||
| 130 | + | ||
| 131 | + xhs_logger.info("正在填充标题和话题...") | ||
| 132 | + await page.locator("div.titleInput div div input").fill("花花") | ||
| 133 | + | ||
| 134 | + inputTag = page.locator('id=quillEditor').locator('div p') | ||
| 135 | + await inputTag.click() | ||
| 136 | + for index, tag in enumerate(self.tags, start=1): | ||
| 137 | + await page.keyboard.type(f"#{tag} ") | ||
| 138 | + await asyncio.sleep(2) | ||
| 139 | + | ||
| 140 | + xhs_logger.info("已写完tag") | ||
| 141 | + await asyncio.sleep(1) | ||
| 142 | + | ||
| 143 | + # 定时任务 | ||
| 144 | + if self.publish_date != 0: | ||
| 145 | + await self.set_schedule_time(page, self.publish_date) | ||
| 146 | + upload_button = page.locator('button div span:has-text("定时发布")') | ||
| 147 | + | ||
| 148 | + await upload_button.click() | ||
| 149 | + await page.wait_for_selector('p:has-text("发布成功")') | ||
| 150 | + await write_cookies_to_file(await context.cookies(), self.account_file) | ||
| 151 | + xhs_logger.info('cookie更新完毕!') | ||
| 152 | + await asyncio.sleep(2) # 这里延迟是为了方便眼睛直观的观看 | ||
| 153 | + # 关闭浏览器上下文和浏览器实例 | ||
| 154 | + await context.close() | ||
| 155 | + await browser.close() | ||
| 156 | + | ||
| 157 | + async def main(self): | ||
| 158 | + async with async_playwright() as playwright: | ||
| 159 | + await self.upload(playwright) | ||
| 160 | + | ||
| 161 | + async def set_schedule_time(self, page, publish_date): | ||
| 162 | + xhs_logger.info("click schedule") | ||
| 163 | + publish_date_hour = publish_date.strftime("%Y-%m-%d %H:%M:%S") | ||
| 164 | + await page.locator("span.el-radio__input").nth(1).click() | ||
| 165 | + await asyncio.sleep(1) | ||
| 166 | + | ||
| 167 | + await page.locator('div.date-picker div div input').click() | ||
| 168 | + await asyncio.sleep(1) | ||
| 169 | + | ||
| 170 | + await page.keyboard.press("Control+KeyA") | ||
| 171 | + await page.keyboard.type(str(publish_date_hour)) | ||
| 172 | + await page.keyboard.press("Enter") | ||
| 173 | + await asyncio.sleep(1) | ... | ... |
uploader/xhs_uploader/main_old.py
0 → 100644
| 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)) |
| ... | @@ -5,7 +5,7 @@ from time import sleep | ... | @@ -5,7 +5,7 @@ from time import sleep |
| 5 | 5 | ||
| 6 | from xhs import XhsClient | 6 | from xhs import XhsClient |
| 7 | 7 | ||
| 8 | -from uploader.xhs_uploader.main import sign | 8 | +from uploader.xhs_uploader.main_old import sign |
| 9 | 9 | ||
| 10 | # pip install qrcode | 10 | # pip install qrcode |
| 11 | if __name__ == '__main__': | 11 | if __name__ == '__main__': | ... | ... |
| ... | @@ -16,7 +16,7 @@ SOCIAL_MEDIA_BAIJIAHAO = 'baijiahao' | ... | @@ -16,7 +16,7 @@ SOCIAL_MEDIA_BAIJIAHAO = 'baijiahao' |
| 16 | 16 | ||
| 17 | def get_supported_social_media() -> List[str]: | 17 | def get_supported_social_media() -> List[str]: |
| 18 | return [SOCIAL_MEDIA_DOUYIN, SOCIAL_MEDIA_TENCENT, SOCIAL_MEDIA_TIKTOK, SOCIAL_MEDIA_KUAISHOU, | 18 | return [SOCIAL_MEDIA_DOUYIN, SOCIAL_MEDIA_TENCENT, SOCIAL_MEDIA_TIKTOK, SOCIAL_MEDIA_KUAISHOU, |
| 19 | - SOCIAL_MEDIA_WANGYIHAO, SOCIAL_MEDIA_TOUTIAO, SOCIAL_MEDIA_BAIJIAHAO] | 19 | + SOCIAL_MEDIA_WANGYIHAO, SOCIAL_MEDIA_TOUTIAO, SOCIAL_MEDIA_BAIJIAHAO, SOCIAL_MEDIA_XHS] |
| 20 | 20 | ||
| 21 | 21 | ||
| 22 | def get_cli_action() -> List[str]: | 22 | def get_cli_action() -> List[str]: | ... | ... |
-
Please register or login to post a comment