init commit

This commit is contained in:
徐微
2025-12-08 15:27:00 +08:00
commit 20171bd31a
24 changed files with 921 additions and 0 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

55
app.py Normal file
View File

@@ -0,0 +1,55 @@
from flask import Flask, render_template, request, session
from playwright.sync_api import sync_playwright
import os
import time
app = Flask(__name__)
app.secret_key = 'your_secret_key'
@app.route('/')
def index():
return render_template('login.html')
@app.route('/send_code', methods=['POST'])
def send_code():
phone = request.form['phone']
session['phone'] = phone
# 用 Playwright 打开页面并点击发送验证码
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
context = browser.new_context()
page = context.new_page()
page.goto("https://creator.xiaohongshu.com/login?source=&redirectReason=401&lastUrl=%252Fnew%252Fhome")
page.get_by_role("textbox", name="手机号").click()
page.get_by_role("textbox", name="手机号").fill(phone)
page.wait_for_timeout(15000) # 等待用户手动输入验证码
page.get_by_text("发送验证码").click()
# 保存 context 到磁盘,后续用
time.sleep(20) # 确保操作完成
context.storage_state(path="state.json")
# browser.close()
return "验证码已发送,请查收手机短信!<a href='/'>返回</a>"
@app.route('/login', methods=['POST'])
def login():
code = request.form['code']
phone = session.get('phone')
# 用 Playwright继续登录
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
context = p.chromium.launch_persistent_context(user_data_dir=".", storage_state="state.json")
page = context.pages[0] if context.pages else context.new_page()
page.goto("https://creator.xiaohongshu.com/login?source=&redirectReason=401&lastUrl=%252Fnew%252Fhome")
# 填写验证码并登录
page.wait_for_selector('input[placeholder*="验证码"]', timeout=15000)
vcode_box = page.locator('input[placeholder*="验证码"]')
vcode_box.fill(code)
page.get_by_role("button", name="登 录").click()
# 等待跳转
page.wait_for_url("**/new/home", timeout=20000)
# 保存 cookies
context.storage_state(path="cookies.json")
browser.close()
return "登录成功cookie已保存<a href='/'>返回</a>"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)

162
cookies.json Normal file
View File

@@ -0,0 +1,162 @@
[
{
"name": "acw_tc",
"value": "0a0d0ad817647684166947926e745d544464c3eaa8c03b5c1daaf7727ce7ff",
"domain": "creator.xiaohongshu.com",
"path": "/",
"expires": 1764770219.611397,
"httpOnly": true,
"secure": false,
"sameSite": "Lax"
},
{
"name": "xsecappid",
"value": "ugc",
"domain": ".xiaohongshu.com",
"path": "/",
"expires": 1796304431,
"httpOnly": false,
"secure": false,
"sameSite": "Lax"
},
{
"name": "a1",
"value": "19ae46510a243heinye4i1n8fbktbb3sjw9vp3uz950000223112",
"domain": ".xiaohongshu.com",
"path": "/",
"expires": 1796304420,
"httpOnly": false,
"secure": false,
"sameSite": "Lax"
},
{
"name": "webId",
"value": "374bfec4808d4b49918e4f704bf9fde6",
"domain": ".xiaohongshu.com",
"path": "/",
"expires": 1796304420,
"httpOnly": false,
"secure": false,
"sameSite": "Lax"
},
{
"name": "acw_tc",
"value": "0a4a65a917647684172526611e22e34304a2cc5b000e71927ba9fa334c4ca7",
"domain": "edith.xiaohongshu.com",
"path": "/",
"expires": 1764770220.163317,
"httpOnly": true,
"secure": false,
"sameSite": "Lax"
},
{
"name": "acw_tc",
"value": "0a00074117647684176224664e82e886141fe61870fff4ae1ea35c0940faa6",
"domain": "customer.xiaohongshu.com",
"path": "/",
"expires": 1764770220.557782,
"httpOnly": true,
"secure": false,
"sameSite": "Lax"
},
{
"name": "websectiga",
"value": "59d3ef1e60c4aa37a7df3c23467bd46d7f1da0c1918cf335ee7f2e9e52ac04cf",
"domain": ".xiaohongshu.com",
"path": "/",
"expires": 1765027620,
"httpOnly": false,
"secure": false,
"sameSite": "Lax"
},
{
"name": "sec_poison_id",
"value": "b47ab11d-6876-4ed6-a53d-2192a935782b",
"domain": ".xiaohongshu.com",
"path": "/",
"expires": 1764769025,
"httpOnly": false,
"secure": false,
"sameSite": "Lax"
},
{
"name": "gid",
"value": "yj0d4K2yJSVSyj0d4K2y8Eq70J4qxd3Ivd43jujyIYiDTh289DDqMU888JJqyyJ880ySdS84",
"domain": ".xiaohongshu.com",
"path": "/",
"expires": 1799328434.79468,
"httpOnly": false,
"secure": false,
"sameSite": "Lax"
},
{
"name": "customer-sso-sid",
"value": "68c517579622683438383105kvqpbrqnjhxcuozw",
"domain": ".xiaohongshu.com",
"path": "/",
"expires": 1765373230.597087,
"httpOnly": true,
"secure": false,
"sameSite": "Lax"
},
{
"name": "x-user-id-creator.xiaohongshu.com",
"value": "68763bd9000000001b019a5c",
"domain": ".xiaohongshu.com",
"path": "/",
"expires": 1799328431.597156,
"httpOnly": true,
"secure": false,
"sameSite": "Lax"
},
{
"name": "customerClientId",
"value": "091245717510800",
"domain": ".xiaohongshu.com",
"path": "/",
"expires": 1799328431.597244,
"httpOnly": true,
"secure": false,
"sameSite": "Lax"
},
{
"name": "access-token-creator.xiaohongshu.com",
"value": "customer.creator.AT-68c517579622683438383106wtbhklklncen9nms",
"domain": ".xiaohongshu.com",
"path": "/",
"expires": 1767360430.59727,
"httpOnly": true,
"secure": false,
"sameSite": "Lax"
},
{
"name": "galaxy_creator_session_id",
"value": "jxq51QjFn05XJyW41Uz1ASvOSreHs5r2ImYX",
"domain": ".xiaohongshu.com",
"path": "/",
"expires": 1767360431.597285,
"httpOnly": true,
"secure": false,
"sameSite": "Lax"
},
{
"name": "galaxy.creator.beaker.session.id",
"value": "1764768428620029392110",
"domain": ".xiaohongshu.com",
"path": "/",
"expires": 1767360431.597294,
"httpOnly": true,
"secure": false,
"sameSite": "Lax"
},
{
"name": "loadts",
"value": "1764768431906",
"domain": ".xiaohongshu.com",
"path": "/",
"expires": 1796304431,
"httpOnly": false,
"secure": false,
"sameSite": "Lax"
}
]

45
demo_ip.py Normal file
View File

@@ -0,0 +1,45 @@
import requests
import time
if __name__ == '__main__':
# 客户ip提取链接,每次提取1个提取链接可以换成自己购买的
url = 'http://api.tianqiip.com/getip?secret=6o09i8io&num=2&type=txt&port=1&mr=1&sign=5451e454a54b9f1f06222606c418e12f'
# 访问的目标地址
targeturl = 'http://47.99.184.230/info.php'
response = requests.get(url)
content =response.content.decode("utf-8").strip()
print('提取IP' + content)
nowtime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
print('提取IP时间' + nowtime)
sj = content.strip().split(":", 1)
sj1 = sj[0]
print("IP", sj1)
sj2 = sj[1]
print("端口:", sj2)
try:
#proxyMeta = "http://nfd0p2:bHQAp5iW@%(host)s:%(port)s" % { # 账密验证,需要购买的代理套餐开通才可使用账密验证,此种情况无需加白名单
proxyMeta = "http://%(host)s:%(port)s" % {#白名单验证
"host": sj1,
"port": sj2,
}
print("代理1", proxyMeta)
proxysdata = {
'http': proxyMeta,
'https': proxyMeta
}
print("代理2", proxysdata)
headers = {
"user-agent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.'
}
start = int(round(time.time() * 1000))
resp = requests.get(targeturl, proxies=proxysdata, headers=headers, timeout=20)
costTime = int(round(time.time() * 1000)) - start
print("耗时:" + str(costTime) + "ms")
print("返回:",resp.text)
s = requests.session()
s.keep_alive = False
except Exception as e:
print(e)

30
main.py Normal file
View File

@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
"""
main.py - 支持扫码登录和 cookie 登录模式选择
"""
import os
def main():
print("请选择登录模式:")
print("1. 扫码登录(首次登录/刷新 cookie")
print("2. 使用已保存 cookie 自动发文")
choice = input("请输入模式编号1或2").strip()
if choice == '1':
from scan_qrcode import scan_qrcode
scan_qrcode()
elif choice == '2':
cookie_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'cookies.json')
if not os.path.exists(cookie_path):
print("未检测到 cookie 文件,请先扫码登录!")
return
from use_cookie import get_test_content, auto_publish_xhs
title, desc, image_list = get_test_content()
if not image_list:
print('未找到图片文件请将图片放入test文件夹')
else:
auto_publish_xhs(title, desc, image_list)
else:
print("输入无效,请输入 1 或 2")
if __name__ == "__main__":
main()

103
phone_num.py Normal file
View File

@@ -0,0 +1,103 @@
# -*- coding: utf-8 -*-
"""
手机号登录小红书创作者中心
功能:
- 自动打开登录页,等待用户手机号登录
- 登录成功后可获取 cookies 或提示
"""
import time
from playwright.sync_api import sync_playwright
def scan_qrcode():
with sync_playwright() as p:
import getpass
browser = p.chromium.launch(headless=False)
context = browser.new_context(viewport={"width": 1600, "height": 900})
page = context.new_page()
# 1. 让用户输入手机号和验证码
phone = input("请输入手机号: ")
page.goto("https://creator.xiaohongshu.com/login?source=&redirectReason=401&lastUrl=%252Fnew%252Fhome")
page.get_by_role("textbox", name="手机号").click()
page.get_by_role("textbox", name="手机号").fill(phone)
page.get_by_text("发送验证码").click()
code = input("请输入收到的验证码: ")
# 等待验证码输入框出现,兼容多种写法
try:
# 先尝试常规定位
page.wait_for_selector('input[placeholder*="验证码"], input[name*="code"], input[type="text"]', timeout=15000)
# 优先用 placeholder 包含“验证码”
if page.query_selector('input[placeholder*="验证码"]'):
vcode_box = page.locator('input[placeholder*="验证码"]')
elif page.query_selector('input[name*="code"]'):
vcode_box = page.locator('input[name*="code"]')
else:
# 兜底用第2个文本框
vcode_box = page.locator('input[type="text"]').nth(1)
vcode_box.click()
vcode_box.fill(code)
except Exception as e:
print("验证码输入框定位失败,请检查页面结构或手动调整定位方式。", e)
browser.close()
return
page.get_by_role("button", name="登 录").click()
# 等待跳转到主页
for i in range(30):
time.sleep(2)
if "new/home" in page.url:
print("手机号登录成功!")
break
else:
print("登录失败,请检查手机号和验证码。")
browser.close()
return
# 只有手机号登录成功后才执行发文流程
try:
import os
# 读取 test 文件夹内容
base_dir = os.path.dirname(os.path.abspath(__file__))
test_dir = os.path.join(base_dir, 'test')
title_path = os.path.join(test_dir, 'title.txt')
desc_path = os.path.join(test_dir, 'desc.txt')
# 查找所有图片,支持多图上传
exts = ['.jpg', '.jpeg', '.png', '.bmp', '.gif']
image_list = []
for fname in os.listdir(test_dir):
if any(fname.lower().endswith(ext) for ext in exts):
image_list.append(os.path.join(test_dir, fname))
if not image_list:
raise Exception("未找到图片文件,请在 test 文件夹中放入图片。")
with open(title_path, 'r', encoding='utf-8') as f:
title = f.read().strip()
with open(desc_path, 'r', encoding='utf-8') as f:
desc = f.read().strip()
page.get_by_text("发布图文笔记").click()
time.sleep(1)
# 上传多张图片,定位 input[type='file']
file_input = page.locator("input[type='file']")
file_input.set_input_files(image_list)
time.sleep(1)
# 填写标题
page.get_by_role("textbox", name="填写标题会有更多赞哦~").click()
page.get_by_role("textbox", name="填写标题会有更多赞哦~").fill(title)
time.sleep(1)
# 填写正文
page.get_by_role("textbox").nth(1).click()
page.get_by_role("textbox").nth(1).fill(desc)
time.sleep(1)
# 点击发布
page.get_by_role("button", name="发布").click()
print("已点击发布(多图模式)")
# 发完图文后保存 cookie
try:
from util import save_cookies
cookie_path = os.path.join(base_dir, "cookies.json")
save_cookies(context, cookie_path)
print(f"登录 cookie 已保存到 {cookie_path}")
except Exception as e:
print("保存 cookie 失败:", e)
except Exception as e:
print("自动发文流程异常:", e)
browser.close()
if __name__ == "__main__":
scan_qrcode()

30
readme.md Normal file
View File

@@ -0,0 +1,30 @@
1. 安装依赖
pip install -r requirements.txt
2. 安装 Playwright 浏览器驱动
playwright install chromium
3. 文件结构说明
├── main.py # 主入口,选择登录模式
├── scan_qrcode.py # 扫码登录并自动发文,首次登录/刷新 cookie 用
├── use_cookie.py # 使用已保存 cookie 自动发文
├── util.py # cookie 保存与加载 工具
├── requirements.txt # 依赖包列表
├── test/ # 发文内容文件夹
│ ├── title.txt # 标题
│ ├── desc.txt # 正文
│ └── *.jpg/png/... # 图片(支持多图)
4. 使用方法
- 首次登录或 cookie 失效时,运行 main.py选择【1 扫码登录】,扫码后自动保存 cookie 并发文。
- 后续可直接选择【2 使用已保存 cookie 自动发文】,无需再次扫码。
- 发文内容请放在 test 文件夹,支持多张图片。
5. 常见问题
- cookies.json 文件丢失或无效时,必须重新扫码登录。
- Playwright 导入失败请检查依赖安装。
6. 运行示例
python main.py

BIN
requirements.txt Normal file

Binary file not shown.

80
scan_qrcode.py Normal file
View File

@@ -0,0 +1,80 @@
# -*- coding: utf-8 -*-
"""
扫码登录小红书创作者中心
功能:
- 自动打开登录页,展示二维码,等待用户扫码登录
- 登录成功后可获取 cookies 或提示
"""
import time
from playwright.sync_api import sync_playwright
def scan_qrcode():
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
context = browser.new_context(viewport={"width": 1600, "height": 900})
page = context.new_page()
login_success = False
for i in range(120):
page.goto("https://creator.xiaohongshu.com/new/home")
if "new/home" in page.url:
print("扫码登录成功!")
login_success = True
break
## 等待10s的扫码时间
time.sleep(10)
if not login_success:
print("扫码超时,请重试。")
browser.close()
return
# 只有扫码登录成功后才执行发文流程
try:
import os
# 读取 test 文件夹内容
base_dir = os.path.dirname(os.path.abspath(__file__))
test_dir = os.path.join(base_dir, 'test')
title_path = os.path.join(test_dir, 'title.txt')
desc_path = os.path.join(test_dir, 'desc.txt')
# 查找所有图片,支持多图上传
exts = ['.jpg', '.jpeg', '.png', '.bmp', '.gif']
image_list = []
for fname in os.listdir(test_dir):
if any(fname.lower().endswith(ext) for ext in exts):
image_list.append(os.path.join(test_dir, fname))
if not image_list:
raise Exception("未找到图片文件,请在 test 文件夹中放入图片。")
with open(title_path, 'r', encoding='utf-8') as f:
title = f.read().strip()
with open(desc_path, 'r', encoding='utf-8') as f:
desc = f.read().strip()
page.get_by_text("发布图文笔记").click()
time.sleep(1)
# 上传多张图片,定位 input[type='file']
file_input = page.locator("input[type='file']")
file_input.set_input_files(image_list)
time.sleep(1)
# 填写标题
page.get_by_role("textbox", name="填写标题会有更多赞哦~").click()
page.get_by_role("textbox", name="填写标题会有更多赞哦~").fill(title)
time.sleep(1)
# 填写正文
page.get_by_role("textbox").nth(1).click()
page.get_by_role("textbox").nth(1).fill(desc)
time.sleep(1)
# 点击发布
page.get_by_role("button", name="发布").click()
print("已点击发布(多图模式)")
# 发完图文后保存 cookie
try:
from util import save_cookies
cookie_path = os.path.join(base_dir, "cookies.json")
save_cookies(context, cookie_path)
print(f"登录 cookie 已保存到 {cookie_path}")
except Exception as e:
print("保存 cookie 失败:", e)
except Exception as e:
print("自动发文流程异常:", e)
browser.close()
if __name__ == "__main__":
scan_qrcode()

32
scrapy_proxy.py Normal file
View File

@@ -0,0 +1,32 @@
import scrapy
class MimvpSpider(scrapy.spiders.Spider):
name = "mimvp"
allowed_domains = ["mimvp.com"]
start_urls = [
"http://proxy.mimvp.com/exist.php",
"https://proxy.mimvp.com/exist.php",
]
## 代理设置方式1直接在代理里设置
def start_requests(self):
urls = [
"http://proxy.mimvp.com/exist.php",
"https://proxy.mimvp.com/exist.php",
]
for url in urls:
meta_proxy = ""
if url.startswith("http://"):
meta_proxy = "http://180.96.27.12:88" # http代理
elif url.startswith("https://"):
meta_proxy = "http://109.108.87.136:53281" # https代理
yield scrapy.Request(url=url, callback=self.parse, meta={'proxy': meta_proxy})
def parse(self, response):
mimvp_url = response.url # 爬取时请求的url
body = response.body # 返回网页内容
print("mimvp_url : " + str(mimvp_url))
print("body : " + str(body))

1
state.json Normal file
View File

@@ -0,0 +1 @@
{"cookies": [{"name": "acw_tc", "value": "0a00073f17648186622332701e43566b95b602a93cd549a88b024eee608633", "domain": "creator.xiaohongshu.com", "path": "/", "expires": 1764820461.74781, "httpOnly": true, "secure": false, "sameSite": "Lax"}, {"name": "xsecappid", "value": "ugc", "domain": ".xiaohongshu.com", "path": "/", "expires": 1796354663, "httpOnly": false, "secure": false, "sameSite": "Lax"}, {"name": "loadts", "value": "1764818663108", "domain": ".xiaohongshu.com", "path": "/", "expires": 1796354663, "httpOnly": false, "secure": false, "sameSite": "Lax"}, {"name": "a1", "value": "19ae763b6c588mpg6z68nwm5jhc3ndw0r6snpywrx50000382215", "domain": ".xiaohongshu.com", "path": "/", "expires": 1796354663, "httpOnly": false, "secure": false, "sameSite": "Lax"}, {"name": "webId", "value": "634abc526217352bc577c6c427da9e75", "domain": ".xiaohongshu.com", "path": "/", "expires": 1796354663, "httpOnly": false, "secure": false, "sameSite": "Lax"}, {"name": "acw_tc", "value": "0a50896717648186651941329eb01af570a20066b520e2ff1034dffa32d0b3", "domain": "edith.xiaohongshu.com", "path": "/", "expires": 1764820464.69803, "httpOnly": true, "secure": false, "sameSite": "Lax"}, {"name": "websectiga", "value": "88%3B6be45f388a1ee7bf611a69f3e174cae48f1ea02c0f8ec3256031b8be9c7ee", "domain": ".xiaohongshu.com", "path": "/", "expires": 1765077866, "httpOnly": false, "secure": false, "sameSite": "Lax"}, {"name": "sec_poison_id", "value": "ecf5ae6b-e74a-438b-8798-f95c3add5334", "domain": ".xiaohongshu.com", "path": "/", "expires": 1764819271, "httpOnly": false, "secure": false, "sameSite": "Lax"}, {"name": "acw_tc", "value": "0a00071517648186671184918e7d081a899a9aa951214f73a355f65e3832f0", "domain": "customer.xiaohongshu.com", "path": "/", "expires": 1764820466.772548, "httpOnly": true, "secure": false, "sameSite": "Lax"}], "origins": [{"origin": "https://creator.xiaohongshu.com", "localStorage": [{"name": "last_tiga_update_time", "value": "1764818666580"}, {"name": "sdt_source_storage_key", "value": "{\"extraInfo\":{},\"reportUrl\":\"/api/sec/v1/shield/webprofile\",\"xhsTokenUrl\":\"https://fe-static.xhscdn.com/as/v1/3e44/public/bf7d4e32677698655a5cadc581fd09b3.js\",\"validate\":false,\"commonPatch\":[\"/fe_api/burdock/v2/note/post\",\"/api/sns/web/v1/comment/post\",\"/api/sns/web/v1/note/like\",\"/api/sns/web/v1/note/collect\",\"/api/sns/web/v1/user/follow\",\"/api/sns/web/v1/feed\",\"/api/sns/web/v1/login/activate\",\"/api/sns/web/v1/note/metrics_report\",\"/api/redcaptcha\",\"/api/store/jpd/main\",\"/phoenix/api/strategy/getAppStrategy\",\"/web_api/sns/v2/note\"],\"signUrl\":\"https://fe-static.xhscdn.com/as/v1/f218/a15/public/04b29480233f4def5c875875b6bdc3b1.js\",\"signVersion\":\"1\",\"url\":\"https://fe-static.xhscdn.com/as/v2/fp/962356ead351e7f2422eb57edff6982d.js\",\"desVersion\":\"2\"}"}]}]}

37
templates/login.html Normal file
View File

@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>小红书创作者中心登录</title>
<style>
body { font-family: Arial, sans-serif; background: #f7f7f7; }
.container { max-width: 400px; margin: 80px auto; background: #fff; padding: 32px 24px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
h2 { text-align: center; margin-bottom: 24px; }
label { display: block; margin-bottom: 8px; }
input[type="text"] { width: 100%; padding: 8px; margin-bottom: 16px; border: 1px solid #ddd; border-radius: 4px; }
button { width: 100%; padding: 10px; background: #e34f4f; color: #fff; border: none; border-radius: 4px; font-size: 16px; cursor: pointer; }
.msg { color: #e34f4f; text-align: center; margin-bottom: 16px; }
</style>
</head>
<body>
<div class="container">
<h2>小红书创作者中心登录</h2>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="msg">{{ messages[0] }}</div>
{% endif %}
{% endwith %}
<form method="post" action="/send_code">
<label for="phone">手机号:</label>
<input type="text" id="phone" name="phone" placeholder="请输入手机号" required>
<button type="submit">发送验证码</button>
</form>
<form method="post" action="/login">
<label for="code">验证码:</label>
<input type="text" id="code" name="code" placeholder="请输入验证码" required>
<button type="submit">登录</button>
</form>
<p style="color:#888;font-size:13px;text-align:center;margin-top:18px;">请先在小红书页面点击“发送验证码”后再输入收到的验证码</p>
</div>
</body>
</html>

103
test.py Normal file
View File

@@ -0,0 +1,103 @@
# -*- coding: utf-8 -*-
"""
手机号登录小红书创作者中心
功能:
- 自动打开登录页,等待用户手机号登录
- 登录成功后可获取 cookies 或提示
"""
import time
from playwright.sync_api import sync_playwright
def scan_qrcode():
with sync_playwright() as p:
import getpass
browser = p.chromium.launch(headless=False)
context = browser.new_context(viewport={"width": 1600, "height": 900})
page = context.new_page()
# 1. 让用户输入手机号和验证码
phone = input("请输入手机号: ")
page.goto("https://creator.xiaohongshu.com/login?source=&redirectReason=401&lastUrl=%252Fnew%252Fhome")
page.get_by_role("textbox", name="手机号").click()
page.get_by_role("textbox", name="手机号").fill(phone)
page.get_by_text("发送验证码").click()
code = input("请输入收到的验证码: ")
# 等待验证码输入框出现,兼容多种写法
try:
# 先尝试常规定位
page.wait_for_selector('input[placeholder*="验证码"], input[name*="code"], input[type="text"]', timeout=15000)
# 优先用 placeholder 包含“验证码”
if page.query_selector('input[placeholder*="验证码"]'):
vcode_box = page.locator('input[placeholder*="验证码"]')
elif page.query_selector('input[name*="code"]'):
vcode_box = page.locator('input[name*="code"]')
else:
# 兜底用第2个文本框
vcode_box = page.locator('input[type="text"]').nth(1)
vcode_box.click()
vcode_box.fill(code)
except Exception as e:
print("验证码输入框定位失败,请检查页面结构或手动调整定位方式。", e)
browser.close()
return
page.get_by_role("button", name="登 录").click()
# 等待跳转到主页
for i in range(30):
time.sleep(2)
if "new/home" in page.url:
print("手机号登录成功!")
break
else:
print("登录失败,请检查手机号和验证码。")
browser.close()
return
# 只有手机号登录成功后才执行发文流程
try:
import os
# 读取 test 文件夹内容
base_dir = os.path.dirname(os.path.abspath(__file__))
test_dir = os.path.join(base_dir, 'test')
title_path = os.path.join(test_dir, 'title.txt')
desc_path = os.path.join(test_dir, 'desc.txt')
# 查找所有图片,支持多图上传
exts = ['.jpg', '.jpeg', '.png', '.bmp', '.gif']
image_list = []
for fname in os.listdir(test_dir):
if any(fname.lower().endswith(ext) for ext in exts):
image_list.append(os.path.join(test_dir, fname))
if not image_list:
raise Exception("未找到图片文件,请在 test 文件夹中放入图片。")
with open(title_path, 'r', encoding='utf-8') as f:
title = f.read().strip()
with open(desc_path, 'r', encoding='utf-8') as f:
desc = f.read().strip()
page.get_by_text("发布图文笔记").click()
time.sleep(1)
# 上传多张图片,定位 input[type='file']
file_input = page.locator("input[type='file']")
file_input.set_input_files(image_list)
time.sleep(1)
# 填写标题
page.get_by_role("textbox", name="填写标题会有更多赞哦~").click()
page.get_by_role("textbox", name="填写标题会有更多赞哦~").fill(title)
time.sleep(1)
# 填写正文
page.get_by_role("textbox").nth(1).click()
page.get_by_role("textbox").nth(1).fill(desc)
time.sleep(1)
# 点击发布
page.get_by_role("button", name="发布").click()
print("已点击发布(多图模式)")
# 发完图文后保存 cookie
try:
from util import save_cookies
cookie_path = os.path.join(base_dir, "cookies.json")
save_cookies(context, cookie_path)
print(f"登录 cookie 已保存到 {cookie_path}")
except Exception as e:
print("保存 cookie 失败:", e)
except Exception as e:
print("自动发文流程异常:", e)
browser.close()
if __name__ == "__main__":
scan_qrcode()

11
test/desc.txt Normal file
View File

@@ -0,0 +1,11 @@
从 17 岁的天才少年到 29 岁的六冠王Faker 的名字早就刻在了《英雄联盟》的神坛上🏆​
谁能想到,这个 1996 年出生的韩国少年,从韩服路人王出道,一脚踏入职业赛场就打出了 “双劫大战” 的封神操作,残血反杀 Ryu 的画面至今仍是电竞史上的名场面。2013 年第一次站在 S 赛舞台,他就带着 SKT 拿下 S3 冠军,出道即巅峰说的就是他吧!​
✨三冠王的统治时代​
2015-2016 年是 Faker 的绝对统治期,连续拿下 S5、S6 全球总决赛冠军,成为 LOL 史上第一个三冠王。那时候的他就像中路的 “大魔王”,瑞兹、发条、阿狸这些英雄在他手里仿佛有了灵魂,对手连兵线都不敢轻易靠近。​
🌧️低谷时的坚守最动人​
2017 年后 SKT 经历重组Faker 也曾跌入低谷,甚至坐过替补席。但他从没离开 T1这个从出道就效力的战队成了他唯一的执念。2022 年重返 S 赛决赛2023 年 27 岁的他带着新生代队友拿下 S13 冠军,四冠王的成就已经前无古人。​
🏆六冠王!传奇还在继续​
2024 年拿下 S14 冠军成为五冠王2025 年在成都东安湖体育馆,他又带领 T1 打满五局战胜 KT拿下史无前例的六冠王和三连冠当《Silver Scrapes》的战歌响起看着他站在领奖台上举起奖杯真的忍不住泪目 ——12 年了,他还在守着中路的塔,守着属于自己的传奇。​
从 S3 到 S15队友换了一批又一批挑战者来了又走只有 Faker 还在赛场。他是首位入选英雄联盟名人堂的选手,是拿下 10 次 LCK 冠军、500 次 S 赛击杀的传奇,更是把 “电竞精神” 刻进骨子里的李相赫。​
有人说他是电竞乔丹可我觉得Faker 就是 Faker是独一份的英雄联盟之神🙌
#Faker #T1 #英雄联盟 S15 #电竞传奇 #六冠王 #李相赫​

BIN
test/faker.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

1
test/title.txt Normal file
View File

@@ -0,0 +1 @@
Faker 六冠王封神12 年守塔的电竞传奇✨

86
use_cookie.py Normal file
View File

@@ -0,0 +1,86 @@
# -*- coding: utf-8 -*-
"""
自动发文脚本test.py
功能:
"""
import os
import json
from playwright.sync_api import sync_playwright
def get_test_content():
base_dir = os.path.dirname(os.path.abspath(__file__))
test_dir = os.path.join(base_dir, 'test')
title_path = os.path.join(test_dir, 'title.txt')
desc_path = os.path.join(test_dir, 'desc.txt')
with open(title_path, 'r', encoding='utf-8') as f:
title = f.read().strip()
with open(desc_path, 'r', encoding='utf-8') as f:
desc = f.read().strip()
# 查找所有图片,支持多图上传
exts = ['.jpg', '.jpeg', '.png', '.bmp', '.gif']
image_list = []
for fname in os.listdir(test_dir):
if any(fname.lower().endswith(ext) for ext in exts):
image_list.append(os.path.join(test_dir, fname))
return title, desc, image_list
def auto_publish_xhs(title, desc, image_path):
# 自动读取扫码登录保存的 cookie
base_dir = os.path.dirname(os.path.abspath(__file__))
cookie_path = os.path.join(base_dir, "cookies.json")
if not os.path.exists(cookie_path):
print("未找到 cookies.json请先扫码登录生成 cookie")
return
with open(cookie_path, 'r', encoding='utf-8') as f:
cookies = json.load(f)
import time
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
context = browser.new_context(viewport={"width": 1600, "height": 900})
context.add_cookies(cookies)
page = context.new_page()
# 进入主页cookie生效后自动登录
page.goto("https://creator.xiaohongshu.com/new/home")
time.sleep(2)
# 进入图文发布页面
page.get_by_text("发布图文笔记").click()
time.sleep(1)
# 上传多张图片,定位 input[type='file']
try:
file_input = page.locator("input[type='file']")
file_input.set_input_files(image_path)
print('图片已上传')
except Exception as e:
print('图片上传控件异常:', e)
time.sleep(1)
# 填写标题
try:
page.get_by_role("textbox", name="填写标题会有更多赞哦~").click()
page.get_by_role("textbox", name="填写标题会有更多赞哦~").fill(title)
except Exception as e:
print('标题控件异常:', e)
time.sleep(1)
# 填写正文
try:
page.get_by_role("textbox").nth(1).click()
page.get_by_role("textbox").nth(1).fill(desc)
except Exception as e:
print('正文控件异常:', e)
time.sleep(1)
# 点击发布
try:
page.get_by_role("button", name="发布").click()
print('已点击发布(多图模式)')
except Exception as e:
print('发布按钮异常:', e)
time.sleep(2)
print('发文流程结束,请检查小红书后台是否发布成功')
browser.close()
if __name__ == '__main__':
title, desc, image_list = get_test_content()
if not image_list:
print('未找到图片文件请将图片放入test文件夹')
else:
auto_publish_xhs(title, desc, image_list)

28
util.py Normal file
View File

@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
"""
util.py - 用于保存和读取 cookie 的工具函数
"""
import json
import os
def save_cookies(context, save_path):
"""
保存 Playwright 浏览器上下文中的 cookies 到指定路径
:param context: Playwright browser context
:param save_path: 保存 cookie 的文件路径
"""
cookies = context.cookies()
with open(save_path, 'w', encoding='utf-8') as f:
json.dump(cookies, f, ensure_ascii=False, indent=2)
def load_cookies(context, load_path):
"""
从指定路径读取 cookies 并设置到 Playwright 浏览器上下文
:param context: Playwright browser context
:param load_path: cookie 文件路径
"""
if not os.path.exists(load_path):
return
with open(load_path, 'r', encoding='utf-8') as f:
cookies = json.load(f)
context.add_cookies(cookies)

117
web_login.py Normal file
View File

@@ -0,0 +1,117 @@
# -*- coding: utf-8 -*-
"""
Flask Web 登录小红书创作者中心
- 前端输入手机号和验证码
- 后端用 Playwright 自动登录
"""
from flask import Flask, render_template, request, redirect, url_for, flash, session
import time
from playwright.sync_api import sync_playwright
import requests
def get_working_proxy(test_url="https://creator.xiaohongshu.com"):
proxy_url = 'http://api.tianqiip.com/getip?secret=ew9mj7j3yplbk3xb&num=1&type=txt&port=1&time=3&mr=1&sign=5451e454a54b9f1f06222606c418e12f'
try:
resp = requests.get(proxy_url, timeout=10)
proxy_content = resp.content.decode("utf-8").strip()
proxy_ip, proxy_port = proxy_content.split(":", 1)
proxy_server = f"http://{proxy_ip}:{proxy_port}"
proxies = {"http": proxy_server, "https": proxy_server}
# 检查代理可用性
try:
test = requests.get(test_url, proxies=proxies, timeout=8)
if test.status_code == 200:
print(f"代理可用: {proxy_server}")
return proxy_server
else:
print(f"代理返回非200: {proxy_server}")
except Exception as e:
print(f"代理不可用: {proxy_server}, {e}")
except Exception as e:
print("获取代理失败", e)
return None
app = Flask(__name__)
app.secret_key = 'xhs_secret_key'
# 步骤1输入手机号自动打开浏览器并填手机号等待用户手动点击发送验证码
@app.route('/', methods=['GET'])
def index():
return render_template('login.html')
@app.route('/send_code', methods=['POST'])
def send_code():
phone = request.form.get('phone')
if not phone:
flash('请输入手机号')
return redirect(url_for('index'))
session['phone'] = phone
# 获取可用代理
proxy_server = get_working_proxy()
if proxy_server:
print(f"使用代理: {proxy_server}")
else:
print("未获取到可用代理使用本地IP")
with sync_playwright() as p:
launch_args = {"headless": False}
if proxy_server:
launch_args["proxy"] = {"server": proxy_server}
browser = p.chromium.launch(**launch_args)
context = browser.new_context()
page = context.new_page()
page.goto("https://creator.xiaohongshu.com/login?source=&redirectReason=401&lastUrl=%252Fnew%252Fhome")
page.get_by_role("textbox", name="手机号").click()
page.get_by_role("textbox", name="手机号").fill(phone)
page.get_by_text("发送验证码").click()
# 等待用户收到验证码
input("验证码已自动发送,请查收手机短信并输入验证码,操作完成后回到命令行按回车...")
# 保存 context 到磁盘,后续用
context.storage_state(path="state.json")
browser.close()
flash('验证码已发送,请查收手机短信并输入验证码')
return redirect(url_for('index'))
# 步骤2输入验证码自动登录
@app.route('/login', methods=['POST'])
def login():
code = request.form.get('code')
phone = session.get('phone')
if not code or not phone:
flash('请先输入手机号并发送验证码')
return redirect(url_for('index'))
# 获取可用代理
proxy_server = get_working_proxy()
if proxy_server:
print(f"使用代理: {proxy_server}")
else:
print("未获取到可用代理使用本地IP")
with sync_playwright() as p:
launch_args = {"headless": False}
if proxy_server:
launch_args["proxy"] = {"server": proxy_server}
context = p.chromium.launch_persistent_context(user_data_dir=".", storage_state="state.json", **launch_args)
page = context.pages[0] if context.pages else context.new_page()
page.goto("https://creator.xiaohongshu.com/login?source=&redirectReason=401&lastUrl=%252Fnew%252Fhome")
# 填写验证码并登录
page.wait_for_selector('input[placeholder*="验证码"]', timeout=15000)
vcode_box = page.locator('input[placeholder*="验证码"]')
vcode_box.fill(code)
page.get_by_role("button", name="登 录").click()
# 等待跳转
try:
page.wait_for_url("**/new/home", timeout=20000)
flash('登录成功!')
# 保存 cookies
context.storage_state(path="cookies.json")
except Exception as e:
flash('登录失败,请检查验证码')
context.close()
return redirect(url_for('index'))
if __name__ == '__main__':
app.run(debug=True)