Browser 架构文档
模块路径: FQBase.Crawler.browser源码: [browser.py](file:///Users/A.D.189/FQuant/FQuant.Server/FQBase/FQBase/Crawler/browser.py)
一、整体架构
┌─────────────────────────────────────────────────────────────────┐
│ 应用层代码 │
│ class MyCrawler(BaseCrawler): ... │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ BaseCrawler │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ HTTP 请求层 │ │
│ │ fetch_url() → requests.get/post │ │
│ │ ├─ 自动重试 (@retry) │ │
│ │ ├─ 代理支持 │ │
│ │ ├─ 请求头管理 │ │
│ │ └─ 随机延迟 │ │
│ └───────────────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 浏览器自动化层 │ │
│ │ fetch_url_with_browser() → Selenium │ │
│ │ ├─ 无头浏览器 │ │
│ │ ├─ 元素等待 │ │
│ │ ├─ 元素点击 │ │
│ │ └─ 滚动控制 │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ PageParser │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 解析方法 │ │
│ │ extract_by_regex() → 正则表达式 │ │
│ │ extract_by_css() → CSS 选择器 │ │
│ │ extract_tables() → 表格提取 │ │
│ │ extract_json() → JSON 解析 │ │
│ │ clean_html() → HTML 清理 │ │
│ │ extract_links() → 链接提取 │ │
│ │ extract_images() → 图片提取 │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘二、组件架构
browser.py
│
├── 常量
│ ├── TIMEOUT = 90 # 默认超时(秒)
│ └── POLL_FREQUENCY = 0.2 # 默认轮询频率(秒)
│
├── 函数
│ ├── make_headless_browser() # 创建无头 Chrome
│ └── make_headless_browser_with_auto_save_path() # 创建 Firefox(带下载)
│
├── BrowserPool (单例)
│ ├── _browsers: List[webdriver.Chrome] # 浏览器实例列表
│ ├── _max_browsers: int # 最大实例数
│ ├── _lock_count: int # 轮换计数
│ ├── get_browser() # 获取浏览器
│ └── close_all() # 关闭所有
│
├── BaseCrawler
│ ├── HTTP 请求
│ │ ├── _default_headers: Dict # 默认请求头
│ │ ├── _use_proxy: Optional[str] # 代理配置
│ │ ├── _delay: float # 请求间隔
│ │ └── fetch_url() # HTTP 请求
│ │
│ ├── 浏览器自动化
│ │ ├── _browser: webdriver.Chrome # 浏览器实例
│ │ ├── _init_browser() # 初始化浏览器
│ │ ├── fetch_url_with_browser() # 浏览器获取
│ │ ├── wait_and_click() # 等待点击
│ │ ├── get_element_text() # 获取元素文本
│ │ └── scroll_to_element() # 滚动到元素
│ │
│ └── 生命周期
│ ├── close() # 关闭浏览器
│ ├── __enter__() / __exit__() # 上下文管理
│ └── __del__() # 析构清理
│
└── PageParser (工具类)
├── extract_by_regex() # 正则提取
├── extract_by_css() # CSS 选择器
├── extract_tables() # 表格提取
├── extract_json() # JSON 提取
├── clean_html() # HTML 清理
├── extract_links() # 链接提取
└── extract_images() # 图片提取三、工作流程
3.1 HTTP 爬取流程
BaseCrawler.fetch_url(url)
│
▼
┌───────────────────────────────────────────────────────────────┐
│ 1. 准备请求 │
│ - 合并请求头 │
│ - 处理 URL 参数 │
│ - 配置代理 │
└───────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────┐
│ 2. 发送请求 (@retry 自动重试) │
│ - requests.get() / requests.post() │
│ - 设置超时 │
└───────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────┐
│ 3. 处理响应 │
│ - response.raise_for_status() │
│ - 解码返回 │
└───────────────────────────────────────────────────────────────┘3.2 浏览器爬取流程
BaseCrawler.fetch_url_with_browser(url, wait_for=None)
│
▼
┌───────────────────────────────────────────────────────────────┐
│ 1. 初始化浏览器(Lazy Init) │
│ - make_headless_browser() │
│ - 设置页面加载超时 │
└───────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────┐
│ 2. 加载页面 │
│ - browser.get(url) │
└───────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────┐
│ 3. 等待元素(可选) │
│ - WebDriverWait.until() │
│ - 超时处理 │
└───────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────┐
│ 4. 随机延迟 │
│ - time.sleep(_delay + random.uniform(0, 0.5)) │
└───────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────┐
│ 5. 返回页面源码 │
│ - return browser.page_source │
└───────────────────────────────────────────────────────────────┘3.3 浏览器池工作流程
BrowserPool.get_browser()
│
▼
┌───────────────────────────────────────────────────────────────┐
│ 检查可用数量 │
│ │
│ 数量 < 最大值? │
│ ├── 是 → 创建新浏览器,加入池 │
│ └── 否 → 轮换返回现有浏览器 │
└───────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────┐
│ 更新轮换计数 │
│ _lock_count = (_lock_count + 1) % _max_browsers │
└───────────────────────────────────────────────────────────────┘