Use shared Chrome debug port
This commit is contained in:
parent
12c2009950
commit
9788647ede
@ -36,11 +36,15 @@ pip install requests DrissionPage
|
|||||||
|
|
||||||
### 步骤 1:启动 Chrome 并手动登录
|
### 步骤 1:启动 Chrome 并手动登录
|
||||||
|
|
||||||
|
如果你已经通过抖音项目启动了调试端口为 `9223` 的 Chrome,可以直接在那个 Chrome 里打开并登录小红书,不需要再运行 `login_xhs.py`。
|
||||||
|
|
||||||
|
如果还没有可复用的 Chrome,再运行:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./.venv/bin/python login_xhs.py
|
./.venv/bin/python login_xhs.py
|
||||||
```
|
```
|
||||||
|
|
||||||
脚本会打开 `https://www.xiaohongshu.com/explore`。请在打开的浏览器里完成登录;如果出现验证码,也需要手动处理。
|
脚本会用默认端口 `9223` 打开 `https://www.xiaohongshu.com/explore`。请在打开的浏览器里完成登录;如果出现验证码,也需要手动处理。
|
||||||
|
|
||||||
### 步骤 2:下载发现页视频
|
### 步骤 2:下载发现页视频
|
||||||
|
|
||||||
|
|||||||
4
XHS.py
4
XHS.py
@ -13,7 +13,7 @@ from typing import Any
|
|||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
DEFAULT_EXPLORE_URL = "https://www.xiaohongshu.com/explore"
|
DEFAULT_EXPLORE_URL = "https://www.xiaohongshu.com/explore"
|
||||||
DEFAULT_BROWSER_PORT = 9224
|
DEFAULT_BROWSER_PORT = 9223
|
||||||
DEFAULT_OUTPUT_DIR = Path("video")
|
DEFAULT_OUTPUT_DIR = Path("video")
|
||||||
LISTEN_TARGET = "/api/sns/web/v1/feed"
|
LISTEN_TARGET = "/api/sns/web/v1/feed"
|
||||||
MAX_FILENAME_BYTES = 240
|
MAX_FILENAME_BYTES = 240
|
||||||
@ -533,7 +533,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|||||||
parser = argparse.ArgumentParser(description="附着到已登录小红书 Chrome,监听 feed 响应并下载视频")
|
parser = argparse.ArgumentParser(description="附着到已登录小红书 Chrome,监听 feed 响应并下载视频")
|
||||||
parser.add_argument("--max-videos", type=int, default=10, help="最多下载视频数量,默认 10")
|
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("--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("--timeout", type=int, default=20, help="等待单次 feed 响应的秒数,默认 20")
|
||||||
parser.add_argument("--start-url", default=DEFAULT_EXPLORE_URL, help="打开或刷新使用的小红书页面")
|
parser.add_argument("--start-url", default=DEFAULT_EXPLORE_URL, help="打开或刷新使用的小红书页面")
|
||||||
parser.add_argument("--use-current-page", action="store_true", help="使用浏览器当前页面,不强制打开发现页")
|
parser.add_argument("--use-current-page", action="store_true", help="使用浏览器当前页面,不强制打开发现页")
|
||||||
|
|||||||
@ -31,7 +31,7 @@ The tool mirrors the existing Douyin project pattern:
|
|||||||
```bash
|
```bash
|
||||||
python3 login_xhs.py
|
python3 login_xhs.py
|
||||||
python3 XHS.py --max-videos 10
|
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
|
## Error Handling
|
||||||
|
|||||||
@ -9,7 +9,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
DEFAULT_START_URL = "https://www.xiaohongshu.com/explore"
|
DEFAULT_START_URL = "https://www.xiaohongshu.com/explore"
|
||||||
DEFAULT_CHROME_PATH = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
|
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")
|
DEFAULT_PROFILE_DIR = Path(".xhs-chrome-profile")
|
||||||
|
|
||||||
|
|
||||||
@ -50,7 +50,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|||||||
"--browser-port",
|
"--browser-port",
|
||||||
type=int,
|
type=int,
|
||||||
default=DEFAULT_BROWSER_PORT,
|
default=DEFAULT_BROWSER_PORT,
|
||||||
help="Chrome 调试端口,默认 9224",
|
help="Chrome 调试端口,默认 9223",
|
||||||
)
|
)
|
||||||
parser.add_argument("--start-url", default=DEFAULT_START_URL, help="启动后打开的小红书页面 URL")
|
parser.add_argument("--start-url", default=DEFAULT_START_URL, help="启动后打开的小红书页面 URL")
|
||||||
return parser
|
return parser
|
||||||
|
|||||||
@ -13,7 +13,7 @@ class LoginXhsModuleTests(unittest.TestCase):
|
|||||||
command = module.build_login_command(
|
command = module.build_login_command(
|
||||||
chrome_path="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
chrome_path="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
||||||
profile_dir=Path("/tmp/xhs-profile"),
|
profile_dir=Path("/tmp/xhs-profile"),
|
||||||
browser_port=9224,
|
browser_port=9223,
|
||||||
start_url="https://www.xiaohongshu.com/explore",
|
start_url="https://www.xiaohongshu.com/explore",
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
@ -24,7 +24,7 @@ class LoginXhsModuleTests(unittest.TestCase):
|
|||||||
"/Applications/Google Chrome.app",
|
"/Applications/Google Chrome.app",
|
||||||
"--args",
|
"--args",
|
||||||
"--user-data-dir=/tmp/xhs-profile",
|
"--user-data-dir=/tmp/xhs-profile",
|
||||||
"--remote-debugging-port=9224",
|
"--remote-debugging-port=9223",
|
||||||
"https://www.xiaohongshu.com/explore",
|
"https://www.xiaohongshu.com/explore",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@ -32,7 +32,7 @@ class LoginXhsModuleTests(unittest.TestCase):
|
|||||||
def test_build_parser_uses_expected_defaults(self) -> None:
|
def test_build_parser_uses_expected_defaults(self) -> None:
|
||||||
module = importlib.import_module("login_xhs")
|
module = importlib.import_module("login_xhs")
|
||||||
args = module.build_parser().parse_args([])
|
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.chrome_path, module.DEFAULT_CHROME_PATH)
|
||||||
self.assertEqual(args.start_url, module.DEFAULT_START_URL)
|
self.assertEqual(args.start_url, module.DEFAULT_START_URL)
|
||||||
|
|
||||||
@ -79,7 +79,7 @@ class LoginXhsModuleTests(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
self.assertEqual(exit_code, 0)
|
self.assertEqual(exit_code, 0)
|
||||||
self.assertIn("./.venv/bin/python XHS.py", stdout.getvalue())
|
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:
|
def test_main_returns_error_when_chrome_path_missing(self) -> None:
|
||||||
module = importlib.import_module("login_xhs")
|
module = importlib.import_module("login_xhs")
|
||||||
|
|||||||
@ -153,7 +153,7 @@ class XhsModuleTests(unittest.TestCase):
|
|||||||
|
|
||||||
def test_build_browser_address_from_port(self) -> None:
|
def test_build_browser_address_from_port(self) -> None:
|
||||||
module = importlib.import_module("XHS")
|
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))
|
self.assertIsNone(module.build_browser_address(None))
|
||||||
|
|
||||||
def test_ensure_browser_debug_port_ready_accepts_open_port(self) -> 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.__enter__.return_value = connection
|
||||||
connection.__exit__.return_value = False
|
connection.__exit__.return_value = False
|
||||||
with mock.patch.object(module.socket, "create_connection", return_value=connection) as mocked_connect:
|
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()
|
mocked_connect.assert_called_once()
|
||||||
|
|
||||||
def test_ensure_browser_debug_port_ready_rejects_closed_port(self) -> None:
|
def test_ensure_browser_debug_port_ready_rejects_closed_port(self) -> None:
|
||||||
module = importlib.import_module("XHS")
|
module = importlib.import_module("XHS")
|
||||||
with mock.patch.object(module.socket, "create_connection", side_effect=OSError("boom")):
|
with mock.patch.object(module.socket, "create_connection", side_effect=OSError("boom")):
|
||||||
with self.assertRaisesRegex(RuntimeError, "login_xhs.py"):
|
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:
|
def test_extract_feed_payload_uses_dict_body(self) -> None:
|
||||||
module = importlib.import_module("XHS")
|
module = importlib.import_module("XHS")
|
||||||
@ -189,7 +189,7 @@ class XhsModuleTests(unittest.TestCase):
|
|||||||
args = module.build_parser().parse_args([])
|
args = module.build_parser().parse_args([])
|
||||||
self.assertEqual(args.max_videos, 10)
|
self.assertEqual(args.max_videos, 10)
|
||||||
self.assertEqual(args.output_dir, "video")
|
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.timeout, 20)
|
||||||
self.assertEqual(args.start_url, module.DEFAULT_EXPLORE_URL)
|
self.assertEqual(args.start_url, module.DEFAULT_EXPLORE_URL)
|
||||||
self.assertFalse(args.use_current_page)
|
self.assertFalse(args.use_current_page)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user