camoufox_broswer.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. from pathlib import Path
  2. import subprocess
  3. import orjson
  4. import contextlib
  5. from camoufox import Camoufox
  6. from camoufox.server import launch_server,launch_options,get_nodejs,to_camel_case_dict,LAUNCH_SCRIPT
  7. from camoufox.async_api import AsyncCamoufox
  8. from browserforge.fingerprints import Screen
  9. from playwright.async_api import Browser, Page, BrowserContext, async_playwright
  10. import asyncio
  11. import signal
  12. import os
  13. import datetime
  14. from typing import Optional, Dict, Type, Protocol, Union,ClassVar
  15. import logging
  16. from camoufox import DefaultAddons
  17. from pydantic import BaseModel
  18. from config.settings import OUTPUT_DIR, WORK_DIR
  19. from mylib.logu import logger
  20. # ------------------- Core Implementation -------------------
  21. class BrowserConfig(BaseModel):
  22. """浏览器基础配置模型"""
  23. headless: bool = False
  24. geoip: bool = True
  25. proxy: Optional[Dict] = {'server': 'http://localhost:1881'}
  26. humanize: bool=True
  27. constrains: ClassVar[Screen] = Screen(max_width=1920, max_height=1200)
  28. # C:\Users\mg\AppData\Local\camoufox\camoufox\Cache\browser\features
  29. # addons: Optional[list]=[str(WORK_DIR / r"download\addon")]
  30. exclude_addons:Optional[list]=[DefaultAddons.UBO]
  31. class BrowserCore():
  32. """浏览器核心功能实现(支持上下文管理器)"""
  33. def __init__(self, config: BrowserConfig):
  34. self.config = config
  35. self.camoufox: Optional[AsyncCamoufox] = None
  36. self.browser: Union[Browser, BrowserContext] = None
  37. self.page: Page = None
  38. self.last_activity: datetime.datetime = None
  39. async def __aenter__(self):
  40. """异步上下文管理器入口"""
  41. await self.initialize()
  42. return self
  43. async def __aexit__(self, exc_type, exc_val, exc_tb):
  44. """异步上下文管理器出口"""
  45. await self.close()
  46. async def initialize(self):
  47. # 修改启动参数传递方式
  48. self.camoufox = AsyncCamoufox(
  49. headless=self.config.headless,
  50. geoip=self.config.geoip,
  51. proxy=self.config.proxy,
  52. screen=self.config.constrains,
  53. )
  54. self.browser = await self.camoufox.__aenter__()
  55. self.page = await self.browser.new_page()
  56. self.last_activity = datetime.datetime.now()
  57. logger.info(f"Browser session initialized | URL: {self.page.url}")
  58. async def close(self):
  59. """关闭浏览器实例"""
  60. if self.camoufox:
  61. await self.camoufox.__aexit__(None, None, None)
  62. logger.info("Browser session closed")
  63. async def goto(self, url: str):
  64. """导航到指定URL"""
  65. await self.page.goto(url)
  66. self.last_activity = datetime.datetime.now()
  67. async def take_screenshot(self, filename: str) -> str:
  68. """截图操作"""
  69. os.makedirs(self.screenshot_dir, exist_ok=True)
  70. timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
  71. path = os.path.join(self.screenshot_dir, f"{timestamp}_{filename}")
  72. await self.page.screenshot(path=path, full_page=True)
  73. return path
  74. async def get_page_info(self) -> dict:
  75. pass
  76. def launch_server(self):
  77. file = OUTPUT_DIR / "ws.txt"
  78. logger.info(f"launch_server {file}")
  79. """
  80. Launch a Playwright server. Takes the same arguments as `Camoufox()`.
  81. Prints the websocket endpoint to the console.
  82. """
  83. config = launch_options(
  84. headless=self.config.headless,
  85. geoip=self.config.geoip,
  86. proxy=self.config.proxy,
  87. )
  88. nodejs = get_nodejs()
  89. data = orjson.dumps(to_camel_case_dict(config)).decode()
  90. # 打开一个文件用于写入标准输出和标准错误
  91. with open(file, "w") as output_file:
  92. process = subprocess.Popen( # nosec
  93. [
  94. nodejs,
  95. str(LAUNCH_SCRIPT),
  96. ],
  97. cwd=Path(nodejs).parent / "package",
  98. stdin=subprocess.PIPE,
  99. stdout=output_file,
  100. stderr=subprocess.STDOUT,
  101. text=True,
  102. )
  103. if process.stdin:
  104. process.stdin.write(data)
  105. process.stdin.close()
  106. process.wait()
  107. raise RuntimeError("Server process terminated unexpectedly")
  108. # ------------------- API Service -------------------
  109. async def aio_main(config: BrowserConfig = BrowserConfig()):
  110. """API服务主循环"""
  111. async with BrowserCore(config) as core:
  112. try:
  113. logger.info(f"API服务已启动 | 初始页面: {core.page.url}")
  114. while True:
  115. await asyncio.sleep(5)
  116. except KeyboardInterrupt:
  117. logger.info("接收到终止信号,关闭浏览器...")
  118. except Exception as e:
  119. logger.error(f"API服务异常: {str(e)}")
  120. def main():
  121. asyncio.run(aio_main())
  122. if __name__ == "__main__":
  123. main()