rolling-reader 0.5.2__tar.gz → 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rolling-reader
3
- Version: 0.5.2
3
+ Version: 0.6.0
4
4
  Summary: Local-first web scraper that automatically rolls through HTTP → browser → JS state extraction
5
5
  License: MIT
6
6
  Requires-Python: >=3.11
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "rolling-reader"
7
- version = "0.5.2"
7
+ version = "0.6.0"
8
8
  description = "Local-first web scraper that automatically rolls through HTTP → browser → JS state extraction"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -261,14 +261,155 @@ async def _run_batch(
261
261
 
262
262
 
263
263
  # ---------------------------------------------------------------------------
264
- # 错误提示
264
+ # rr chrome — 启动 Chrome(带调试端口)
265
265
  # ---------------------------------------------------------------------------
266
266
 
267
+ @app.command(name="chrome")
268
+ def launch_chrome(
269
+ port: int = typer.Option(9222, "--port", "-p", help="Remote debugging port (default: 9222)"),
270
+ ) -> None:
271
+ """Launch Chrome with remote debugging enabled.
272
+
273
+ Finds Chrome automatically and starts it in the background.
274
+ After running this, Level 2/3 scraping works immediately.
275
+
276
+ Example:
277
+
278
+ rr chrome
279
+ rr https://app.example.com/dashboard
280
+ """
281
+ import subprocess
282
+ import platform
283
+
284
+ import asyncio
285
+ import time
286
+ import os
287
+ import tempfile
288
+
289
+ # 先检查端口是否已经在用(Chrome 已经以调试模式运行)
290
+ if asyncio.run(_check_cdp(port)):
291
+ typer.echo(f"Chrome is already running with remote debugging on port {port}.")
292
+ typer.echo("Ready — run: rr <url>")
293
+ return
294
+
295
+ exe = _find_chrome()
296
+ if exe is None:
297
+ typer.echo(
298
+ "Error: Chrome not found. Install Google Chrome and try again.\n"
299
+ "Download: https://www.google.com/chrome/",
300
+ err=True,
301
+ )
302
+ raise typer.Exit(code=1)
303
+
304
+ # 用独立的 user-data-dir,避免被已有 Chrome 进程吞掉
305
+ debug_profile = os.path.join(tempfile.gettempdir(), "rolling-reader-chrome")
306
+ os.makedirs(debug_profile, exist_ok=True)
307
+
308
+ args = [
309
+ exe,
310
+ f"--remote-debugging-port={port}",
311
+ f"--user-data-dir={debug_profile}",
312
+ "--remote-allow-origins=*",
313
+ "--no-first-run",
314
+ "--no-default-browser-check",
315
+ ]
316
+
317
+ system = platform.system()
318
+ try:
319
+ if system == "Windows":
320
+ subprocess.Popen(
321
+ args,
322
+ creationflags=subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP,
323
+ close_fds=True,
324
+ )
325
+ else:
326
+ subprocess.Popen(args, start_new_session=True, close_fds=True)
327
+ except Exception as e:
328
+ typer.echo(f"Error: failed to launch Chrome: {e}", err=True)
329
+ raise typer.Exit(code=1)
330
+
331
+ # 等待 Chrome 初始化调试端口(最多 8 秒)
332
+ typer.echo("Starting Chrome...", err=True)
333
+ deadline = time.time() + 8
334
+ while time.time() < deadline:
335
+ time.sleep(0.5)
336
+ if asyncio.run(_check_cdp(port)):
337
+ typer.echo(f"Chrome ready on port {port}.")
338
+ typer.echo("Now run: rr <url>")
339
+ return
340
+
341
+ typer.echo(
342
+ f"Warning: Chrome launched but port {port} is not responding yet.\n"
343
+ "Wait a moment and try your rr command — it may still be starting up.",
344
+ err=True,
345
+ )
346
+
347
+
348
+ async def _check_cdp(port: int) -> bool:
349
+ """检查 CDP 端口是否已经在响应。"""
350
+ import httpx
351
+ try:
352
+ async with httpx.AsyncClient(timeout=1.0) as client:
353
+ r = await client.get(f"http://localhost:{port}/json/version")
354
+ return r.status_code == 200
355
+ except Exception:
356
+ return False
357
+
358
+
359
+ def _find_chrome() -> Optional[str]:
360
+ """在各平台上自动定位 Chrome 可执行文件。"""
361
+ import platform
362
+ import shutil
363
+
364
+ system = platform.system()
365
+
366
+ if system == "Windows":
367
+ candidates = [
368
+ r"C:\Program Files\Google\Chrome\Application\chrome.exe",
369
+ r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe",
370
+ r"C:\Users\{}\AppData\Local\Google\Chrome\Application\chrome.exe".format(
371
+ __import__("os").environ.get("USERNAME", "")
372
+ ),
373
+ ]
374
+ for path in candidates:
375
+ if __import__("os.path", fromlist=["exists"]).exists(path):
376
+ return path
377
+ # 尝试 registry
378
+ try:
379
+ import winreg
380
+ key = winreg.OpenKey(
381
+ winreg.HKEY_LOCAL_MACHINE,
382
+ r"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe",
383
+ )
384
+ path, _ = winreg.QueryValueEx(key, "")
385
+ if path:
386
+ return path
387
+ except Exception:
388
+ pass
389
+
390
+ elif system == "Darwin":
391
+ candidates = [
392
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
393
+ "/Applications/Chromium.app/Contents/MacOS/Chromium",
394
+ ]
395
+ for path in candidates:
396
+ if __import__("os.path", fromlist=["exists"]).exists(path):
397
+ return path
398
+
399
+ else: # Linux
400
+ for name in ("google-chrome", "google-chrome-stable", "chromium", "chromium-browser"):
401
+ found = shutil.which(name)
402
+ if found:
403
+ return found
404
+
405
+ return None
406
+
407
+
267
408
  # ---------------------------------------------------------------------------
268
409
  # 真正的 CLI 入口
269
410
  # ---------------------------------------------------------------------------
270
411
 
271
- _SUBCOMMANDS = {"batch", "scrape", "--help", "-h", "--version"}
412
+ _SUBCOMMANDS = {"batch", "scrape", "chrome", "--help", "-h", "--version"}
272
413
 
273
414
 
274
415
  def main() -> None:
@@ -292,10 +433,8 @@ def _print_error(e: ExtractionError) -> None:
292
433
  if "Chrome is not available" in reason or "Cannot connect to Chrome" in reason:
293
434
  typer.echo(
294
435
  "\nError: Chrome is not running with remote debugging enabled.\n\n"
295
- "Start Chrome first:\n"
296
- " macOS: open -a 'Google Chrome' --args --remote-debugging-port=9222\n"
297
- " Windows: chrome --remote-debugging-port=9222\n"
298
- " Linux: google-chrome --remote-debugging-port=9222\n",
436
+ "Fix: run this first, then retry:\n"
437
+ " rr chrome\n",
299
438
  err=True,
300
439
  )
301
440
  return
File without changes