baseUrl . '/tree/getPositionTree.htm'; $cookieString = $this->buildCookieString($cookies); // 构建Referer URL(包含完整参数) $refererUrl = "http://gzrsks.oumakspt.com:62/tyzpwb/stuchooseexam/selectPosition.htm?examstupid=1015&userid={$userid}&bmid={$bmid}&examid={$examid}&aa={$aa}"; // 构建POST参数 $postData = [ 'examid' => $examid, 'bmid' => $bmid, 'userid' => $userid, 'dsdm' => $dsdm ]; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_ENCODING, ''); // 自动处理gzip编码 curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Accept: text/plain, */*', 'Accept-Language: zh-CN,zh;q=0.9,zh-TW;q=0.8,zh-HK;q=0.7,en-US;q=0.6,en;q=0.5', 'Accept-Encoding: gzip, deflate', 'Pragma: no-cache', 'Cache-Control: no-cache', 'Connection: keep-alive', 'Content-Type: application/x-www-form-urlencoded', 'Cookie: ' . $cookieString, 'Origin: http://gzrsks.oumakspt.com:62', 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:147.0) Gecko/20100101 Firefox/147.0', 'Referer: ' . $refererUrl, 'X-Requested-With: XMLHttpRequest', ]); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData)); curl_setopt($ch, CURLOPT_TIMEOUT, 30); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $error = curl_error($ch); curl_close($ch); if ($error) { throw new \Exception('请求失败: ' . $error); } if ($httpCode !== 200) { throw new \Exception('请求失败,HTTP状态码: ' . $httpCode); } $data = json_decode($response, true); if (json_last_error() !== JSON_ERROR_NONE) { throw new \Exception('JSON解析失败: ' . json_last_error_msg()); } return $data ?: []; } /** * 获取职位详细信息 * @param string $zwdm 职位代码 * @param string $examid 考试ID * @param array $cookies Cookie数据 * @return array */ public function getPositionInfo(string $zwdm, string $examid, array $cookies): array { $url = $this->baseUrl . '/stuchooseexam/getPositionInfo.htm'; $cookieString = $this->buildCookieString($cookies); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_ENCODING, ''); // 自动处理gzip编码 curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Accept: text/plain, */*', 'Accept-Language: zh-CN,zh;q=0.9,zh-TW;q=0.8,zh-HK;q=0.7,en-US;q=0.6,en;q=0.5', 'Accept-Encoding: gzip, deflate', 'Cache-Control: no-cache', 'Connection: keep-alive', 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8', 'Cookie: ' . $cookieString, 'Origin: http://gzrsks.oumakspt.com:62', 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36', 'Referer: http://gzrsks.oumakspt.com:62/tyzpwb/stuchooseexam/selectPosition.htm', 'X-Requested-With: XMLHttpRequest', ]); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([ 'zwdm' => $zwdm, 'examid' => $examid ])); curl_setopt($ch, CURLOPT_TIMEOUT, 30); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $error = curl_error($ch); curl_close($ch); if ($error) { throw new \Exception('请求失败: ' . $error); } if ($httpCode !== 200) { throw new \Exception('请求失败,HTTP状态码: ' . $httpCode); } $data = json_decode($response, true); if (json_last_error() !== JSON_ERROR_NONE) { throw new \Exception('JSON解析失败: ' . json_last_error_msg()); } return $data ?: []; } /** * 批量获取职位信息 * @param array $zwdmList 职位代码列表 * @param string $examid 考试ID * @param array $cookies Cookie数据 * @return array */ public function batchGetPositionInfo(array $zwdmList, string $examid, array $cookies): array { $results = []; foreach ($zwdmList as $zwdm) { try { $info = $this->getPositionInfo($zwdm, $examid, $cookies); if (!empty($info)) { // 处理单条数据或数组数据 if (isset($info[0])) { $item = $info[0]; } else { $item = $info; } // 计算竞争比(格式:招聘人数:审核通过人数) $zprs = isset($item['zprs']) ? intval($item['zprs']) : 0; $bkrs = isset($item['bkrs']) ? intval($item['bkrs']) : 0; $competitionRatio = $zprs > 0 && $bkrs > 0 ? $zprs . ':' . $bkrs : ($zprs > 0 ? $zprs . ':0' : '0:0'); $results[] = [ 'sbmc' => $item['sbmc'] ?? '', // 省份 'dsmc' => $item['dsmc'] ?? '', // 地区 'zpdwmc' => $item['zpdwmc'] ?? '', // 招聘单位/用人司局 'zwmc' => $item['zwmc'] ?? '', // 职位名称 'zwdm' => $item['zwdm'] ?? $zwdm, // 职位代码 'zprs' => $zprs, // 招聘人数 'bkrs' => $bkrs, // 审核通过人数 'competition_ratio' => $competitionRatio, // 竞争比(格式:招聘人数:审核通过人数) ]; } // 避免请求过快,添加小延迟 usleep(200000); // 0.2秒 } catch (\Exception $e) { // 记录错误但继续处理其他数据 $results[] = [ 'zwdm' => $zwdm, 'error' => $e->getMessage(), ]; } } return $results; } /** * 构建Cookie字符串 * @param array $cookies Cookie数组 * @return string */ private function buildCookieString(array|string $cookies): string { // 如果直接传入原始Cookie字符串,优先使用 if (is_string($cookies)) { return trim($cookies); } // 只保留用户填写的Cookie,支持同名键多值(如双JSESSIONID) $cookieData = $cookies['请求 Cookie'] ?? $cookies; $normalized = []; foreach ($cookieData as $key => $value) { $k = trim((string)$key); if ($k === '') { continue; } $vals = is_array($value) ? $value : [$value]; foreach ($vals as $v) { $v = trim((string)$v); if ($v === '') { continue; } $normalized[$k][] = $v; } } // 将 JSESSIONID 放在最前(如果存在),其余按键名顺序,多值全部保留 $parts = []; if (isset($normalized['JSESSIONID'])) { foreach ($normalized['JSESSIONID'] as $v) { $parts[] = 'JSESSIONID=' . $v; } unset($normalized['JSESSIONID']); } foreach ($normalized as $k => $vArr) { foreach ($vArr as $v) { $parts[] = $k . '=' . $v; } } return implode('; ', $parts); } /** * 从HTML中提取地区选项 * @param string $html HTML内容 * @return array */ public function extractDsdmOptions(string $html): array { $options = []; // 使用正则表达式提取option标签 preg_match_all('/]*>([^<]+)<\/option>/i', $html, $matches); if (!empty($matches[1])) { foreach ($matches[1] as $index => $value) { if (!empty($value)) { // 跳过"请选择"等空值 $options[] = [ 'value' => $value, 'text' => $matches[2][$index] ?? $value, ]; } } } return $options; } }