完整版V1 加入爬虫功能

This commit is contained in:
root
2026-06-09 03:36:30 +08:00
parent 34d76cce74
commit a68b83fcbd
69 changed files with 5058 additions and 56 deletions
+105
View File
@@ -0,0 +1,105 @@
<?php
// A2c云控
require_once __DIR__ . '/AbstractScrmSpider.class.php';
class A2c extends AbstractScrmSpider
{
const API_LIST = '/api/talk/counter/share/record/list'; // 列表API地址
const API_DETAILS = '/api/talk/counter/share/detail'; // 详情API地址
const API_COUNT = ''; // 总数API地址
const DEFAULT_PER_PAGE_COUNT = 20; // List默认每页显示的数量
private $pageUrl;
private $account;
private $password;
private $unifiedData;
// 实例化时动态传入账号和密码
public function __construct($pageUrl, $account, $password, $nodeHost = 'http://127.0.0.1:3001')
{
parent::__construct($nodeHost);
$this->account = $account;
$this->password = $password;
$this->pageUrl = $pageUrl;
$this->unifiedData = new UnifiedScrmData();
}
protected function getSpiderConfig()
{
return [
'pageUrl' => $this->pageUrl,
'apiUrls' => [self::API_LIST, self::API_DETAILS],
// 明确指派角色
'listApi' => self::API_LIST, // 必须
'detailApi' => self::API_DETAILS, // 选填
'listMethod' => 'POST',
'paginationMode' => self::MODE_UI,
'authActions' => [
// ['type' => 'type', 'selector' => 'input[type="password"]', 'value' => $this->password],
// ['type' => 'type', 'selector' => '#username_input', 'value' => $this->account],
// ['type' => 'press', 'key' => 'Enter'],
['type' => 'wait', 'ms' => 2000]
]
];
}
// 只负责解析 List 的总页数
protected function extractListTotalPages($listFirstPageData, $countData = null)
{
$default_per_page_count = self::DEFAULT_PER_PAGE_COUNT;
$this->unifiedData->total = $listFirstPageData['data']['total'];
if($listFirstPageData['data']['total'] <= $default_per_page_count) {
return 1;
}
return ceil($listFirstPageData['data']['total']/$default_per_page_count);
}
// 只负责组装 List 的翻页参数
protected function buildListPageParams($page)
{
return ['page' => $page, 'pageSize' => self::DEFAULT_PER_PAGE_COUNT];
}
// 提供 List 的下一页按钮信息
protected function getUiPaginationConfig()
{
return [
'nextBtnSelector' => '.btn-next',
'waitMs' => 2000
];
}
// 清爽至极的数据清洗:详情是详情,列表是列表
protected function parseToUnifiedData($detailData, $allListPagesData)
{
$unifiedData = $this->unifiedData;
// 1. 如果捕获到了详情数据,提取今日新增
if ($detailData) {
$unifiedData->todayNewCount = (int)($detailData['data']['newFollowersToday'] ?? 0);
}
// 2. 循环合并了所有页数的 List 数组
foreach ($allListPagesData as $pageRaw) {
$records = $pageRaw['data']['rows'] ?? [];
foreach ($records as $item) {
if(!empty($item['account'])) {
$number = $item['account'] ?? null;
$isOnline = (isset($item['numberStatus']) && $item['numberStatus'] == 1);
$unifiedData->addNumber($number, $isOnline, $item['newFollowersToday']);
}
}
}
return $unifiedData;
}
}
+246
View File
@@ -0,0 +1,246 @@
<?php
/**
* 美化打印变量(不终止程序)
* 支持传入多个变量,如:dump($var1, $var2);
*/
if (!function_exists('dump')) {
function dump(...$vars) {
// 定义外层容器的 CSS 样式,使其在页面中醒目且整洁
$containerStyle = "
background-color: #282c34;
color: #abb2bf;
padding: 15px;
margin: 10px 0;
border-left: 5px solid #61afef;
border-radius: 4px;
font-family: Consolas, Monaco, 'Courier New', monospace;
font-size: 14px;
line-height: 1.6;
overflow-x: auto;
text-align: left;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
z-index: 9999;
position: relative;
";
echo "<div style=\"{$containerStyle}\">";
foreach ($vars as $var) {
echo "<pre style=\"margin: 0; padding: 5px 0;\">";
// 针对布尔值和 null 的特殊处理,因为 print_r 打印这些不直观
if (is_bool($var)) {
echo "<span style=\"color: #d19a66;\">bool</span>(" . ($var ? 'true' : 'false') . ")";
} elseif (is_null($var)) {
echo "<span style=\"color: #d19a66;\">null</span>";
} else {
// 使用 print_r 获取格式化字符串,并用 htmlspecialchars 防止 HTML 标签被浏览器解析
$output = htmlspecialchars(print_r($var, true));
// 简单的高亮:给数组的键名加上颜色
$output = preg_replace('/\[(.*?)\]/', '[<span style="color: #98c379;">$1</span>]', $output);
echo $output;
}
echo "</pre>";
// 如果有多个参数,用分割线隔开
if (count($vars) > 1 && $var !== end($vars)) {
echo "<hr style=\"border: 0; border-bottom: 1px dashed #5c6370; margin: 10px 0;\">";
}
}
echo "</div>";
}
}
/**
* 美化打印变量并终止程序 (Dump and Die)
*/
if (!function_exists('dd')) {
function dd(...$vars) {
dump(...$vars);
die(1); // 终止程序执行
}
}
// 文件名: AbstractScrmSpider.php
require_once __DIR__ . '/UnifiedScrmData.class.php';
abstract class AbstractScrmSpider
{
const MODE_FETCH = 'fetch'; // 极速并发拉取 (推荐默认)
const MODE_UI = 'ui_click'; // 强制 UI 点击下一页
protected $nodeHost;
public function __construct($nodeHost = 'http://127.0.0.1:3001')
{
$this->nodeHost = $nodeHost;
}
/** ================= 子类必须实现的契约 ================= **/
abstract protected function getSpiderConfig();
// 以下方法的关注点,全部仅限于 List 接口!
abstract protected function extractListTotalPages($listFirstPageData, $countData = null);
abstract protected function buildListPageParams($page);
abstract protected function getUiPaginationConfig();
// 清洗方法直接接收两个明确的变量,告别繁杂的数组解析
abstract protected function parseToUnifiedData($detailData, $allListPagesData);
/** ================= 核心工作流 ================= **/
public function run()
{
$config = $this->getSpiderConfig();
$listApi = $config['listApi'];
$detailApi = $config['detailApi'] ?? null; // detail 接口是可选的
$countApi = $config['countApi'] ?? null; // 有些在线客服的总数通过单独调用接口获取
// 组装需要 Node 拦截的目标数组
$apiUrlsToIntercept = [$listApi];
if ($detailApi) { $apiUrlsToIntercept[] = $detailApi; }
if ($countApi) { $apiUrlsToIntercept[] = $countApi; }
// var_dump($apiUrlsToIntercept);
// 【阶段一】:初始化并首屏拦截
$initResult = $this->requestNode('/api/auth-and-intercept', [
'pageUrl' => $config['pageUrl'],
'apiUrls' => $apiUrlsToIntercept,
'authActions' => $config['authActions']
]);
if (empty($initResult['success'])) {
throw new Exception("初始化失败: " . ($initResult['error'] ?? '未知'));
}
$interceptedApis = $initResult['interceptedApis'];
$cookies = $initResult['cookies'];
// dd($interceptedApis);
// 必须拦截到 List 接口,否则无法继续
if (!isset($interceptedApis[$listApi])) {
throw new Exception("致命错误:未能拦截到必须的列表接口 [{$listApi}]");
}
// 分离 Detail 和 List 的首屏数据
$detailData = $detailApi && isset($interceptedApis[$detailApi]) ? $interceptedApis[$detailApi]['data'] : null;
$countData = $countApi && isset($interceptedApis[$countApi]) ? $interceptedApis[$countApi]['data'] : null;
// dd($detailData);
// dd($countData);
$listApiNode = $interceptedApis[$listApi];
$allListPagesData = [$listApiNode['data']]; // 初始化列表容器,装入第一页数据
$totalPages = $this->extractListTotalPages($listApiNode['data'], $countData);
$mode = $config['paginationMode'] ?? self::MODE_FETCH;
// 如果总页数 > 1,触发【阶段二】
if ($totalPages > 1 || $totalPages === null) {
// 策略 1:极速 Fetch
if ($mode === self::MODE_FETCH) {
$paramList = [];
for ($page = 2; $page <= $totalPages; $page++) {
$paramList[] = $this->buildListPageParams($page);
}
$fetchResult = $this->requestNode('/api/batch-fetch', [
'tasks' => [[
'apiPath' => $listApi,
'fullUrl' => $listApiNode['url'],
'headers' => $listApiNode['headers'] ?? "",
'paramList' => $paramList,
'method' => $config['listMethod'] ?? 'GET'
]],
'cookies' => $cookies
], 120);
if (!empty($fetchResult['success'])) {
foreach ($fetchResult['results'][$listApi] as $pResult) {
if ($pResult['success']) $allListPagesData[] = $pResult['data'];
}
}
}
// 策略 2:强制 UI 点击
elseif ($mode === self::MODE_UI) {
$uiConfig = $this->getUiPaginationConfig();
// 🔥 核心修改:直接将第一页的数组传给 Node,让 Node 引擎自己去剔除时间戳并生成统一哈希
$firstPageData = $listApiNode['data'];
// 🔥 核心重构 2:如果是 null 盲点模式,下发 9999 次点击任务;否则按实际计算次数
$clicksToPerform = ($totalPages === null) ? 9999 : ($totalPages - 1);
$uiResult = $this->requestNode('/api/ui-pagination', [
'apiUrl' => $listApi,
'pageUrl' => $config['pageUrl'],
'nextBtnSelector' => $uiConfig['nextBtnSelector'],
'waitMs' => $uiConfig['waitMs'] ?? 2000,
'clicksToPerform' => $clicksToPerform,
'cookies' => $cookies,
'firstPageData' => $firstPageData, // 传递原始数据源
// 🔥 核心补充:把登录剧本原封不动地传给翻页引擎!
'authActions' => $config['authActions']
], 1200); // 增加 PHP 端的 cURL 超时时间到 400 秒,以包容多次重试产生的耗时
if (!empty($uiResult['success']) && !empty($uiResult['data'])) {
foreach ($uiResult['data'] as $pageData) {
$allListPagesData[] = $pageData;
}
}
}
}
// 注:如果 $totalPages <= 1,直接自然跳过上述区块,进入第三阶段。
// 【阶段三】:移交数据进行最终清洗
// return $this->parseToUnifiedData($detailData, $allListPagesData);
// 【阶段三】:解析并打包数据
$unifiedData = $this->parseToUnifiedData($detailData, $allListPagesData);
// 🔥 核心修改:引入业务对账雷达
// if (!$this->validateDataIntegrity($unifiedData)) {
// 方案 A:直接返回 false(符合你的特殊要求)
// return false;
// 方案 B(工业级推荐):抛出自定义异常。
// 因为返回 false 会让你在外部不知道到底是“网络超时”失败的,还是“数据对账没对上”失败的。
// throw new Exception("抓取校验异常:数据未成功获取,或客服数量业务对账失败!");
// }
return $unifiedData;
}
private function requestNode($endpoint, $payload, $timeout = 60)
{
$ch = curl_init($this->nodeHost . $endpoint);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
$response = curl_exec($ch);
if (curl_errno($ch)) throw new Exception(curl_error($ch));
curl_close($ch);
return json_decode($response, true);
}
/**
* 🔥 新增:数据完整性与业务对账校验
* @param array $unifiedData 已经解析好的统一格式数据
* @return bool 校验成功返回 true,失败返回 false
*/
protected function validateDataIntegrity($unifiedData)
{
// 1. 底线校验:如果最核心的列表数据或详情数据根本就是空的,直接判定失败
if (empty($unifiedData) || !is_array($unifiedData)) {
return false;
}
// 2. 这里可以放一些全系统通用的基础校验(如果有的话)
return true;
}
}
+98
View File
@@ -0,0 +1,98 @@
<?php
// A2c云控
require_once __DIR__ . '/AbstractScrmSpider.class.php';
class Haiwang extends AbstractScrmSpider
{
const API_LIST = '/webApi/accountshow/list'; // 列表API地址
const API_DETAILS = ''; // 详情API地址
const API_COUNT = ''; // 总数API地址
const DEFAULT_PER_PAGE_COUNT = 10; // List默认每页显示的数量
private $pageUrl;
private $account;
private $password;
private $unifiedData;
// 实例化时动态传入账号和密码
public function __construct($pageUrl, $account, $password, $nodeHost = 'http://127.0.0.1:3001')
{
parent::__construct($nodeHost);
$this->account = $account;
$this->password = $password;
$this->pageUrl = $pageUrl;
$this->unifiedData = new UnifiedScrmData();
}
protected function getSpiderConfig()
{
return [
'pageUrl' => $this->pageUrl,
'apiUrls' => [self::API_LIST, self::API_DETAILS],
// 明确指派角色
'listApi' => self::API_LIST, // 必须
'detailApi' => self::API_DETAILS, // 选填
'listMethod' => 'POST',
'paginationMode' => self::MODE_UI,
'authActions' => [
['type' => 'type', 'selector' => 'input[type="password"]', 'value' => $this->password],
// ['type' => 'type', 'selector' => '#username_input', 'value' => $this->account],
['type' => 'press', 'key' => 'Enter'],
['type' => 'wait', 'ms' => 2000]
]
];
}
// 只负责解析 List 的总页数
protected function extractListTotalPages($listFirstPageData, $countData = null)
{
$default_per_page_count = self::DEFAULT_PER_PAGE_COUNT;
$this->unifiedData->total = $listFirstPageData['data']['total'];
if($this->unifiedData->total <= $default_per_page_count) {
return 1;
}
return ceil($this->unifiedData->total/$default_per_page_count);
}
// 只负责组装 List 的翻页参数
protected function buildListPageParams($page)
{
return ['page' => $page, 'limit' => self::DEFAULT_PER_PAGE_COUNT];
}
// 提供 List 的下一页按钮信息
protected function getUiPaginationConfig()
{
return [
'nextBtnSelector' => '.btn-next',
'waitMs' => 2000
];
}
// 清爽至极的数据清洗:详情是详情,列表是列表
protected function parseToUnifiedData($detailData, $allListPagesData)
{
$unifiedData = $this->unifiedData;
// 循环合并了所有页数的 List 数组
foreach ($allListPagesData as $pageRaw) {
$records = $pageRaw['data']['items'] ?? [];
foreach ($records as $item) {
if(!empty($item['acclist_account'])) {
$number = $item['acclist_account'] ?? null;
$isOnline = (isset($item['acclist_status']) && $item['acclist_status'] == 2);
$unifiedData->addNumber($number, $isOnline, $item['account_statistics_today_effective']);
}
}
}
$unifiedData->todayNewCount = $allListPagesData[0]['data']['shareStatistics']['sharecode_statistics_today_contact_effective'];
return $unifiedData;
}
}
+102
View File
@@ -0,0 +1,102 @@
<?php
// 火箭工单
require_once __DIR__ . '/AbstractScrmSpider.class.php';
class Huojian extends AbstractScrmSpider
{
const API_LIST = '/prod-api1/biz/counter/link/share/'; // 列表API地址
const API_DETAILS = ''; // 详情API地址
const API_COUNT = ''; // 总数API地址
const DEFAULT_PER_PAGE_COUNT = 20; // List默认每页显示的数量
private $pageUrl;
private $account;
private $password;
private $unifiedData;
// 实例化时动态传入账号和密码
public function __construct($pageUrl, $account, $password, $nodeHost = 'http://127.0.0.1:3001')
{
parent::__construct($nodeHost);
$this->account = $account;
$this->password = $password;
$this->pageUrl = $pageUrl;
$this->unifiedData = new UnifiedScrmData();
}
protected function getSpiderConfig()
{
return [
'pageUrl' => $this->pageUrl,
'apiUrls' => [self::API_LIST],
// 明确指派角色
'listApi' => self::API_LIST, // 必须
'listMethod' => 'POST',
'paginationMode' => self::MODE_UI,
'authActions' => [
// 1. 填入密码:寻找 class 包含 el-message-box__input 下面的任意 input
// Node.js 会自动扫描所有匹配项,并只把密码强行注入到那个“肉眼可见”的框里
['type' => 'vue_fill', 'selector' => '.el-message-box__input input', 'value' => $this->password],
// 2. 停顿 500ms,让 Vue 绑定的 v-model 彻底反应过来
['type' => 'wait', 'ms' => 500],
// 3. 点击确认:寻找 MessageBox 底部的蓝色 primary 确认按钮
// 同样利用 vue_click 的可见性过滤,无视隐藏的旧弹窗按钮
['type' => 'vue_click', 'selector' => '.el-message-box__btns .el-button--primary'],
// 4. 等待弹窗淡出,接口开始请求
['type' => 'wait', 'ms' => 2000]
]
];
}
// 只负责解析 List 的总页数
protected function extractListTotalPages($listFirstPageData, $countData = null)
{
// 只有一页
return 1;
}
// 没有分页返回空数组
protected function buildListPageParams($page)
{
return [];
}
// 提供 List 的下一页按钮信息
protected function getUiPaginationConfig()
{
return [];
}
// 清爽至极的数据清洗:详情是详情,列表是列表
protected function parseToUnifiedData($detailData, $allListPagesData)
{
$unifiedData = $this->unifiedData;
$unifiedData->todayNewCount = $allListPagesData[0]['data']['counterWorker']['newTodayFriend'];
// 2. 循环合并了所有页数的 List 数组
$count = 0;
foreach ($allListPagesData as $pageRaw) {
$records = $pageRaw['data']['counterCsAccountVo'] ?? [];
foreach ($records as $item) {
if(!empty($item['accountLogin'])) {
$number = $item['accountLogin'] ?? null;
$isOnline = (isset($item['accountStatus']) && $item['accountStatus'] == 1);
$count++;
$unifiedData->addNumber($number, $isOnline, $item['newTodayFriend']);
}
}
}
$unifiedData->total = $count;
return $unifiedData;
}
}
+131
View File
@@ -0,0 +1,131 @@
<?php
// SS云控(Customer)
require_once __DIR__ . '/AbstractScrmSpider.class.php';
// 可以获取每日具体进线数据
class SsCustomer extends AbstractScrmSpider
{
const API_LIST = '/sys/share/report/get-customer-analysis-dimension-list'; // 列表API地址
const API_DETAILS = '/sys/share/report/get-customer-analysis-statistics'; // 详情API地址
const API_COUNT = ''; // 总数API地址
const DEFAULT_PER_PAGE_COUNT = 20; // List默认每页显示的数量
private $pageUrl;
private $account;
private $password;
private $unifiedData;
// 实例化时动态传入账号和密码
public function __construct($pageUrl, $account, $password, $nodeHost = 'http://127.0.0.1:3001')
{
parent::__construct($nodeHost);
$this->account = $account;
$this->password = $password;
$this->pageUrl = $pageUrl;
$this->unifiedData = new UnifiedScrmData();
}
protected function getSpiderConfig()
{
return [
'pageUrl' => $this->pageUrl,
'apiUrls' => [self::API_LIST, self::API_DETAILS, self::API_COUNT],
// 明确指派角色
'listApi' => self::API_LIST, // 必须
'detailApi' => self::API_DETAILS, // 选填
'countApi' => self::API_COUNT,
'listMethod' => 'POST',
'paginationMode' => self::MODE_UI,
'authActions' => [
['type' => 'wait', 'ms' => 2000],
['type' => 'type', 'selector' => 'input[type="password"]', 'value' => $this->password],
['type' => 'press', 'key' => 'Enter'],
['type' => 'wait', 'ms' => 3000],
[
'type' => 'vue_click',
'selector' => 'button[class*="reports-customers__dimension"]',
'text' => 'social media accounts'
],
['type' => 'wait', 'ms' => 3000],
]
];
}
// 只负责解析 List 的总页数
protected function extractListTotalPages($listFirstPageData, $countData = null)
{
return null;
// $default_per_page_count = self::DEFAULT_PER_PAGE_COUNT;
// // 场景 A:如果单独的 Count 接口有数据返回
// if ($countData) {
// // 假设 count 接口返回的是 {"data": {"total": 312}}
// $totalRecords = $countData['data']['total'];
// } else {
// // 场景 B
// $totalRecords = $listFirstPageData['data']['total'];
// }
// $this->unifiedData->total = $totalRecords;
// if($totalRecords <= $default_per_page_count) {
// return 1;
// }
// return ceil($listFirstPageData['data']['total']/$default_per_page_count);
}
// 只负责组装 List 的翻页参数
protected function buildListPageParams($page)
{
return ['page' => $page, 'pageSize' => self::DEFAULT_PER_PAGE_COUNT];
}
// 提供 List 的下一页按钮信息
protected function getUiPaginationConfig()
{
return [
'nextBtnSelector' => '.arco-pagination-item-next',
'waitMs' => 2000
];
}
// 清爽至极的数据清洗:详情是详情,列表是列表
protected function parseToUnifiedData($detailData, $allListPagesData)
{
$unifiedData = $this->unifiedData;
// 1. 如果捕获到了详情数据,提取今日新增
if ($detailData) {
$unifiedData->todayNewCount = (int)($detailData['data']['distinct_contacts_total'] ?? 0);
}
// 2. 循环合并了所有页数的 List 数组
foreach ($allListPagesData as $pageRaw) {
$records = $pageRaw['data']['list'] ?? [];
foreach ($records as $item) {
if(!empty($item['channel_tag'])) {
$number = $item['channel_tag'] ?? null;
$isOnline = true; // 离线状态的账号同步不到
$unifiedData->addNumber($number, $isOnline, $item['distinct_contacts_total']);
}
}
}
// 🚀 3. 终极统计:所有翻页数据均已入库,此时再统计真实的 Total 总数
$unifiedData->total = count($unifiedData->numbers);
return $unifiedData;
}
}
+26
View File
@@ -0,0 +1,26 @@
<?php
// 文件名: UnifiedScrmData.php
class UnifiedScrmData
{
public $todayNewCount = 0;
public $totalOnline = 0;
public $totalOffline = 0;
public $numbers = []; // 号码列表
public $total = 0; // 号码总数
public function addNumber($number, $isOnline, $newFollowersToday)
{
$this->numbers[] = [
'number' => $number,
'status' => $isOnline ? 'online' : 'offline',
'newFollowersToday' => $newFollowersToday
];
if ($isOnline) {
$this->totalOnline++;
} else {
$this->totalOffline++;
}
}
}
+100
View File
@@ -0,0 +1,100 @@
<?php
// 星河工单
require_once __DIR__ . '/AbstractScrmSpider.class.php';
class Xinghe extends AbstractScrmSpider
{
const API_LIST = '/share/share/api_yinliu_count.html'; // 列表API地址
const API_DETAILS = ''; // 详情API地址
const API_COUNT = ''; // 总数API地址
const DEFAULT_PER_PAGE_COUNT = 10; // List默认每页显示的数量
private $pageUrl;
private $account;
private $password;
private $unifiedData;
// 实例化时动态传入账号和密码
public function __construct($pageUrl, $account, $password, $nodeHost = 'http://127.0.0.1:3001')
{
parent::__construct($nodeHost);
$this->account = $account;
$this->password = $password;
$this->pageUrl = $pageUrl;
$this->unifiedData = new UnifiedScrmData();
}
protected function getSpiderConfig()
{
return [
'pageUrl' => $this->pageUrl,
'apiUrls' => [self::API_LIST . "?page=1&limit=10"],
'listApi' => self::API_LIST, // 必须,第一页的列表数据
// 'detailApi' => self::API_DETAILS, // 选填
// 'countApi' => self::API_COUNT,
'listMethod' => 'GET',
'paginationMode' => self::MODE_FETCH,
'authActions' => [
// ['type' => 'type', 'selector' => 'input[type="password"]', 'value' => $this->password],
// ['type' => 'type', 'selector' => '#username_input', 'value' => $this->account],
// ['type' => 'press', 'key' => 'Enter'],
['type' => 'wait', 'ms' => 2000]
]
];
}
// 只负责解析 List 的总页数
protected function extractListTotalPages($listFirstPageData, $countData = null)
{
$default_per_page_count = self::DEFAULT_PER_PAGE_COUNT;
$this->unifiedData->total = $listFirstPageData['count'];
$this->unifiedData->todayNewCount = $listFirstPageData['totalRow']['day_sum'];
if($listFirstPageData['count'] <= $default_per_page_count) {
return 1;
}
return ceil($listFirstPageData['count']/$default_per_page_count);
}
// 只负责组装 List 的翻页参数
protected function buildListPageParams($page)
{
return ['page' => $page, 'limit' => self::DEFAULT_PER_PAGE_COUNT];
}
// 提供 List 的下一页按钮信息
protected function getUiPaginationConfig()
{
return [
'nextBtnSelector' => '.layui-laypage-next',
'waitMs' => 2000
];
}
// 清爽至极的数据清洗:详情是详情,列表是列表
protected function parseToUnifiedData($detailData, $allListPagesData)
{
$unifiedData = $this->unifiedData;
// 循环合并了所有页数的 List 数组
foreach ($allListPagesData as $pageRaw) {
$records = $pageRaw['data'] ?? [];
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;
}
}
+140
View File
@@ -0,0 +1,140 @@
<?php
// 文件名: run.php
require_once __DIR__ . '/A2c.php'; // A2c云控
require_once __DIR__ . '/Xinghe.php'; // 星河云控
require_once __DIR__ . '/Huojian.php'; // 火箭
require_once __DIR__ . '/SsCustomer.php'; // SS云控(Customer)
require_once __DIR__ . '/Haiwang.php'; // 海王
// try {
// echo "🚀 开始执行<A2c云控>抓取任务 (多引擎智能调度)...\n\r";
// $pageUrl = 'https://user.a2c.chat/visitors/counter/share?id=33e449dc83c24ee59275bf03a2d82234'; // PageUrl 入口授权页
// $username = ""; // 登录账号
// $password = ""; // 登录密码
// $spider = new A2c($pageUrl, $username, $password);
// $finalData = $spider->run();
// echo "✅ 任务完成!统一数据如下:\n\r";
// echo "----------------------------------------\n\r";
// echo "当日新增:{$finalData->todayNewCount} 人\n\r";
// echo "在线号码:{$finalData->totalOnline} 个\n\r";
// echo "离线号码:{$finalData->totalOffline} 个\n\r";
// echo "Total" . $finalData->total . " 个号码\n\r";
// echo "实际总共抓取:" . count($finalData->numbers) . " 个号码\n\r";
// echo "号码列表:\n\r";
// echo dd($finalData->numbers);
// } catch (Exception $e) {
// echo "🚨 抓取异常:" . $e->getMessage() . "\n\r";
// }
// try {
// echo "🚀 开始执行<星河云控>抓取任务 (多引擎智能调度)...\n\r";
// $pageUrl = 'http://103.251.112.35:10158/share/share/index.html?token=pds65jl202t2kjis5firb8epu4d8a83ptfhc63d89l3mv11nwa'; // PageUrl 入口授权页
// $username = ""; // 登录账号
// $password = ""; // 登录密码
// $spider = new Xinghe($pageUrl, $username, $password);
// $finalData = $spider->run();
// echo "✅ 任务完成!统一数据如下:\n\r";
// echo "----------------------------------------\n\r";
// echo "当日新增:{$finalData->todayNewCount} 人\n\r";
// echo "在线号码:{$finalData->totalOnline} 个\n\r";
// echo "离线号码:{$finalData->totalOffline} 个\n\r";
// echo "Total" . $finalData->total . " 个号码\n\r";
// echo "实际总共抓取:" . count($finalData->numbers) . " 个号码\n\r";
// echo "号码列表:\n\r";
// echo dd($finalData->numbers);
// } catch (Exception $e) {
// echo "🚨 抓取异常:" . $e->getMessage() . "\n\r";
// }
// try {
// echo "🚀 开始执行<火箭工单>抓取任务 (多引擎智能调度)...\n\r";
// $pageUrl = 'https://s.url99.me/ygn9zjr8'; // PageUrl 入口授权页
// $username = ""; // 登录账号
// $password = "123456"; // 登录密码
// $spider = new Huojian($pageUrl, $username, $password);
// $finalData = $spider->run();
// echo "✅ 任务完成!统一数据如下:\n\r";
// echo "----------------------------------------\n\r";
// echo "当日新增:{$finalData->todayNewCount} 人\n\r";
// echo "在线号码:{$finalData->totalOnline} 个\n\r";
// echo "离线号码:{$finalData->totalOffline} 个\n\r";
// echo "Total" . $finalData->total . " 个号码\n\r";
// echo "实际总共抓取:" . count($finalData->numbers) . " 个号码\n\r";
// echo "号码列表:\n\r";
// echo dd($finalData->numbers);
// } catch (Exception $e) {
// echo "🚨 抓取异常:" . $e->getMessage() . "\n\r";
// }
// try {
// echo "🚀 开始执行<SS云控(Customer)>抓取任务 (多引擎智能调度)...\n\r";
// $pageUrl = 'https://app.salesmartly.vip/next/share/reports/verify/customer/fwt7cg'; // PageUrl 入口授权页
// $username = ""; // 登录账号
// $password = "123456"; // 登录密码
// $spider = new SsCustomer($pageUrl, $username, $password);
// $finalData = $spider->run();
// echo "✅ 任务完成!统一数据如下:\n\r";
// echo "----------------------------------------\n\r";
// echo "当日新增:{$finalData->todayNewCount} 人\n\r";
// echo "在线号码:{$finalData->totalOnline} 个\n\r";
// echo "离线号码:{$finalData->totalOffline} 个\n\r";
// echo "Total" . $finalData->total . " 个号码\n\r";
// echo "实际总共抓取:" . count($finalData->numbers) . " 个号码\n\r";
// echo "号码列表:\n\r";
// echo dd($finalData->numbers);
// } catch (Exception $e) {
// echo "🚨 抓取异常:" . $e->getMessage() . "\n\r";
// }
try {
echo "🚀 开始执行<海王>抓取任务 (多引擎智能调度)...\n\r";
$pageUrl = 'https://admin.haiwangweb.com/web#/accountshow/pZsEulYrb'; // PageUrl 入口授权页
$username = ""; // 登录账号
$password = "9999"; // 登录密码
$spider = new Haiwang($pageUrl, $username, $password);
$finalData = $spider->run();
echo "✅ 任务完成!统一数据如下:\n\r";
echo "----------------------------------------\n\r";
echo "当日新增:{$finalData->todayNewCount}\n\r";
echo "在线号码:{$finalData->totalOnline}\n\r";
echo "离线号码:{$finalData->totalOffline}\n\r";
echo "Total" . $finalData->total . " 个号码\n\r";
echo "实际总共抓取:" . count($finalData->numbers) . " 个号码\n\r";
echo "号码列表:\n\r";
echo dd($finalData->numbers);
} catch (Exception $e) {
echo "🚨 抓取异常:" . $e->getMessage() . "\n\r";
}
// try {
// echo "🚀 开始执行<CEO SCRM>抓取任务 (多引擎智能调度)...\n\r";
// $pageUrl = 'https://admin.scrmceo.com/#/workShareDetail?code=XgGTK5yN'; // PageUrl 入口授权页
// $username = ""; // 登录账号
// $password = "a2222"; // 登录密码
// $spider = new SsCustomer($pageUrl, $username, $password);
// $finalData = $spider->run();
// echo "✅ 任务完成!统一数据如下:\n\r";
// echo "----------------------------------------\n\r";
// echo "当日新增:{$finalData->todayNewCount} 人\n\r";
// echo "在线号码:{$finalData->totalOnline} 个\n\r";
// echo "离线号码:{$finalData->totalOffline} 个\n\r";
// echo "Total" . $finalData->total . " 个号码\n\r";
// echo "实际总共抓取:" . count($finalData->numbers) . " 个号码\n\r";
// echo "号码列表:\n\r";
// echo dd($finalData->numbers);
// } catch (Exception $e) {
// echo "🚨 抓取异常:" . $e->getMessage() . "\n\r";
// }