pageUrl = $pageUrl; $this->username = $username; $this->password = $password; $parsedUrl = parse_url($pageUrl); $scheme = isset($parsedUrl['scheme']) ? $parsedUrl['scheme'] . '://' : ''; $host = isset($parsedUrl['host']) ? $parsedUrl['host'] : ''; $baseUrl = $scheme . $host; $this->baseUrl = $baseUrl; $this->unifiedData = new UnifiedScrmData(); } public function run() { // 1. 创建临时 Cookie 文件 $this->cookieFile = tempnam(sys_get_temp_dir(), 'spider_cookie_'); if ($this->cookieFile === false) { throw new \RuntimeException("无法在系统临时目录创建 Cookie 文件"); } // 2. 初始化 cURL 并设置全局参数 $this->ch = curl_init(); $apiUrl = '/share/share/api_yinliu_count.html?page=1&limit=10&id=&class_id=&is_repet=1&start_time=&end_time='; $defaultOptions = [ CURLOPT_RETURNTRANSFER => true, // 将结果返回为字符串 CURLOPT_HEADER => false, // 不输出响应头 CURLOPT_TIMEOUT => 15, // 设置超时时间(秒) CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36', // 核心优化:同时指定读写 Cookie 为同一个文件,cURL 会自动维护会话 CURLOPT_COOKIEJAR => $this->cookieFile, CURLOPT_COOKIEFILE => $this->cookieFile, ]; curl_setopt_array($this->ch, $defaultOptions); $this->authenticate(); $first_page_data = $this->fetchApiData($apiUrl); // 首页数据 $this->unifiedData->total = $first_page_data['count']; // 总在线人数 $this->unifiedData->todayNewCount = $first_page_data['totalRow']['day_sum']; // 今日新增在线人数 $total_pages = ceil($this->unifiedData->total/10); // 总页数 $allListPagesData = [$first_page_data['data']]; // 初始化列表容器,装入第一页数据 for ($page = 2; $page <= $total_pages; $page++) { $apiUrl = '/share/share/api_yinliu_count.html?page=' . $page . '&limit=10&id=&class_id=&is_repet=1&start_time=&end_time='; $page_data = $this->fetchApiData($apiUrl); $allListPagesData[] = $page_data['data']; } return $this->parseToUnifiedData($first_page_data, $allListPagesData); } // 清爽至极的数据清洗:详情是详情,列表是列表 protected function parseToUnifiedData($detailData, $allListPagesData) { $unifiedData = $this->unifiedData; // 循环合并了所有页数的 List 数组 foreach ($allListPagesData as $records) { foreach ($records as $item) { if(!empty($item['user'])) { $number = $item['user'] ?? null; $isOnline = (isset($item['online']) && $item['online'] == 1); $unifiedData->addNumber($number, $isOnline, $item['day_sum']); } } } return $unifiedData; } /** * 析构函数:释放资源,清理垃圾 */ public function __destruct() { // 关闭 cURL 会话 if (is_resource($this->ch) || $this->ch instanceof \CurlHandle) { curl_close($this->ch); } // 删除临时 Cookie 文件 if (file_exists($this->cookieFile)) { unlink($this->cookieFile); } } /** * 第一步:访问授权页面,建立会话 * * @param string $authUrl 包含 token 的授权地址 * @return bool 授权请求是否成功 * @throws \Exception */ public function authenticate() { $this->sendRequest($this->pageUrl); return true; // 如果没有抛出异常,则认为请求成功 } /** * 第二步:请求 API 接口获取数据 * * @param string $apiUrl 接口数据地址 * @return array|null 解析后的数组数据,如果解析失败返回 null * @throws \Exception */ public function fetchApiData($apiUrl) { $response = $this->sendRequest($this->baseUrl . $apiUrl); // 尝试解析 JSON 数据 $data = json_decode($response, true); if (json_last_error() !== JSON_ERROR_NONE) { throw new \RuntimeException("JSON 解析失败: " . json_last_error_msg() . "。原始响应: " . $response); } return $data; } /** * 发送 cURL 请求的底层私有方法 * * @param string $url 目标地址 * @return string 服务器响应内容 * @throws \Exception */ private function sendRequest($url) { curl_setopt($this->ch, CURLOPT_URL, $url); $response = curl_exec($this->ch); // 检查是否有网络或 cURL 底层错误 if ($response === false) { $error = curl_error($this->ch); throw new \RuntimeException("请求失败 [{$url}]: {$error}"); } // 检查 HTTP 状态码 $httpCode = curl_getinfo($this->ch, CURLINFO_HTTP_CODE); if ($httpCode >= 400) { throw new \RuntimeException("HTTP 请求异常 [{$url}],状态码: {$httpCode}"); } return (string)$response; } }