Use shared Chrome debug port

This commit is contained in:
wangshaoqing 2026-05-27 15:36:22 +08:00
parent 12c2009950
commit 9788647ede
6 changed files with 18 additions and 14 deletions

View File

@ -36,11 +36,15 @@ pip install requests DrissionPage
### 步骤 1启动 Chrome 并手动登录
如果你已经通过抖音项目启动了调试端口为 `9223` 的 Chrome可以直接在那个 Chrome 里打开并登录小红书,不需要再运行 `login_xhs.py`
如果还没有可复用的 Chrome再运行
```bash
./.venv/bin/python login_xhs.py
```
脚本会打开 `https://www.xiaohongshu.com/explore`。请在打开的浏览器里完成登录;如果出现验证码,也需要手动处理。
脚本会用默认端口 `9223` 打开 `https://www.xiaohongshu.com/explore`。请在打开的浏览器里完成登录;如果出现验证码,也需要手动处理。
### 步骤 2下载发现页视频

4
XHS.py
View File

@ -13,7 +13,7 @@ from typing import Any
from urllib.parse import urljoin
DEFAULT_EXPLORE_URL = "https://www.xiaohongshu.com/explore"
DEFAULT_BROWSER_PORT = 9224
DEFAULT_BROWSER_PORT = 9223
DEFAULT_OUTPUT_DIR = Path("video")
LISTEN_TARGET = "/api/sns/web/v1/feed"
MAX_FILENAME_BYTES = 240
@ -533,7 +533,7 @@ def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="附着到已登录小红书 Chrome监听 feed 响应并下载视频")
parser.add_argument("--max-videos", type=int, default=10, help="最多下载视频数量,默认 10")
parser.add_argument("--output-dir", default=str(DEFAULT_OUTPUT_DIR), help="视频保存目录,默认 video")
parser.add_argument("--browser-port", type=int, default=DEFAULT_BROWSER_PORT, help="Chrome 调试端口,默认 9224")
parser.add_argument("--browser-port", type=int, default=DEFAULT_BROWSER_PORT, help="Chrome 调试端口,默认 9223")
parser.add_argument("--timeout", type=int, default=20, help="等待单次 feed 响应的秒数,默认 20")
parser.add_argument("--start-url", default=DEFAULT_EXPLORE_URL, help="打开或刷新使用的小红书页面")
parser.add_argument("--use-current-page", action="store_true", help="使用浏览器当前页面,不强制打开发现页")

View File

@ -31,7 +31,7 @@ The tool mirrors the existing Douyin project pattern:
```bash
python3 login_xhs.py
python3 XHS.py --max-videos 10
python3 XHS.py --browser-port 9224 --max-videos 20 --output-dir video
python3 XHS.py --browser-port 9334 --max-videos 20 --output-dir video
```
## Error Handling

View File

@ -9,7 +9,7 @@ from pathlib import Path
DEFAULT_START_URL = "https://www.xiaohongshu.com/explore"
DEFAULT_CHROME_PATH = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
DEFAULT_BROWSER_PORT = 9224
DEFAULT_BROWSER_PORT = 9223
DEFAULT_PROFILE_DIR = Path(".xhs-chrome-profile")
@ -50,7 +50,7 @@ def build_parser() -> argparse.ArgumentParser:
"--browser-port",
type=int,
default=DEFAULT_BROWSER_PORT,
help="Chrome 调试端口,默认 9224",
help="Chrome 调试端口,默认 9223",
)
parser.add_argument("--start-url", default=DEFAULT_START_URL, help="启动后打开的小红书页面 URL")
return parser

View File

@ -13,7 +13,7 @@ class LoginXhsModuleTests(unittest.TestCase):
command = module.build_login_command(
chrome_path="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
profile_dir=Path("/tmp/xhs-profile"),
browser_port=9224,
browser_port=9223,
start_url="https://www.xiaohongshu.com/explore",
)
self.assertEqual(
@ -24,7 +24,7 @@ class LoginXhsModuleTests(unittest.TestCase):
"/Applications/Google Chrome.app",
"--args",
"--user-data-dir=/tmp/xhs-profile",
"--remote-debugging-port=9224",
"--remote-debugging-port=9223",
"https://www.xiaohongshu.com/explore",
],
)
@ -32,7 +32,7 @@ class LoginXhsModuleTests(unittest.TestCase):
def test_build_parser_uses_expected_defaults(self) -> None:
module = importlib.import_module("login_xhs")
args = module.build_parser().parse_args([])
self.assertEqual(args.browser_port, 9224)
self.assertEqual(args.browser_port, 9223)
self.assertEqual(args.chrome_path, module.DEFAULT_CHROME_PATH)
self.assertEqual(args.start_url, module.DEFAULT_START_URL)
@ -79,7 +79,7 @@ class LoginXhsModuleTests(unittest.TestCase):
)
self.assertEqual(exit_code, 0)
self.assertIn("./.venv/bin/python XHS.py", stdout.getvalue())
self.assertNotIn("--browser-port 9224", stdout.getvalue())
self.assertNotIn("--browser-port 9223", stdout.getvalue())
def test_main_returns_error_when_chrome_path_missing(self) -> None:
module = importlib.import_module("login_xhs")

View File

@ -153,7 +153,7 @@ class XhsModuleTests(unittest.TestCase):
def test_build_browser_address_from_port(self) -> None:
module = importlib.import_module("XHS")
self.assertEqual(module.build_browser_address(9224), "127.0.0.1:9224")
self.assertEqual(module.build_browser_address(9223), "127.0.0.1:9223")
self.assertIsNone(module.build_browser_address(None))
def test_ensure_browser_debug_port_ready_accepts_open_port(self) -> None:
@ -162,14 +162,14 @@ class XhsModuleTests(unittest.TestCase):
connection.__enter__.return_value = connection
connection.__exit__.return_value = False
with mock.patch.object(module.socket, "create_connection", return_value=connection) as mocked_connect:
module.ensure_browser_debug_port_ready(9224)
module.ensure_browser_debug_port_ready(9223)
mocked_connect.assert_called_once()
def test_ensure_browser_debug_port_ready_rejects_closed_port(self) -> None:
module = importlib.import_module("XHS")
with mock.patch.object(module.socket, "create_connection", side_effect=OSError("boom")):
with self.assertRaisesRegex(RuntimeError, "login_xhs.py"):
module.ensure_browser_debug_port_ready(9224)
module.ensure_browser_debug_port_ready(9223)
def test_extract_feed_payload_uses_dict_body(self) -> None:
module = importlib.import_module("XHS")
@ -189,7 +189,7 @@ class XhsModuleTests(unittest.TestCase):
args = module.build_parser().parse_args([])
self.assertEqual(args.max_videos, 10)
self.assertEqual(args.output_dir, "video")
self.assertEqual(args.browser_port, 9224)
self.assertEqual(args.browser_port, 9223)
self.assertEqual(args.timeout, 20)
self.assertEqual(args.start_url, module.DEFAULT_EXPLORE_URL)
self.assertFalse(args.use_current_page)