PHP项目统计SofaScore综合评分方案
SofaScore 的评分体系在足球数据分析中很有参考价值,下面我给你一套完整的 PHP 实现思路和代码。

SofaScore 评分机制说明
SofaScore 的综合评分(Rating)特点:
- 基础分 6.0,表现好加分,表现差减分
- 范围约 4.0 ~ 10.0
- 依据数据:进球、助攻、传球成功率、抢断、射门、关键传球、失误等
- 不同位置权重不同(前锋重进攻,后卫重防守)
数据库设计
-- 球员基础信息
CREATE TABLE players (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50),
position ENUM('GK','DF','MF','FW') COMMENT '门将/后卫/中场/前锋'
);
-- 单场统计数据
CREATE TABLE player_match_stats (
id INT PRIMARY KEY AUTO_INCREMENT,
player_id INT,
match_id INT,
minutes_played INT DEFAULT 0,
goals INT DEFAULT 0,
assists INT DEFAULT 0,
shots_on_target INT DEFAULT 0,
key_passes INT DEFAULT 0,
passes_total INT DEFAULT 0,
passes_accurate INT DEFAULT 0,
dribbles_success INT DEFAULT 0,
tackles INT DEFAULT 0,
interceptions INT DEFAULT 0,
clearances INT DEFAULT 0,
duels_won INT DEFAULT 0,
duels_total INT DEFAULT 0,
fouls_committed INT DEFAULT 0,
yellow_cards INT DEFAULT 0,
red_cards INT DEFAULT 0,
saves INT DEFAULT 0, -- 门将扑救
goals_conceded INT DEFAULT 0, -- 门将失球
rating DECIMAL(3,1) DEFAULT NULL,
INDEX idx_player (player_id),
INDEX idx_match (match_id)
);
核心评分算法(PHP 实现)
<?php
class SofaScoreRating
{
// 各位置权重系数
private array $weights = [
'GK' => [
'saves' => 0.35,
'goals_conceded' => -0.30,
'passes' => 0.10,
'duels' => 0.15,
],
'DF' => [
'tackles' => 0.30,
'interceptions' => 0.25,
'clearances' => 0.20,
'duels' => 0.15,
'passes' => 0.10,
'goals' => 0.50,
'assists' => 0.30,
],
'MF' => [
'passes' => 0.30,
'key_passes' => 0.25,
'dribbles' => 0.15,
'tackles' => 0.10,
'interceptions' => 0.10,
'goals' => 0.40,
'assists' => 0.35,
],
'FW' => [
'goals' => 0.60,
'assists' => 0.30,
'shots' => 0.20,
'key_passes' => 0.15,
'dribbles' => 0.15,
],
];
/**
* 计算单场评分
*/
public function calculate(array $stats, string $position): float
{
// 出场时间不足按比例折算(踢满90分钟算全场)
$minutes = $stats['minutes_played'] ?? 0;
if ($minutes <= 0) return 0.0;
$timeFactor = min($minutes / 90, 1.0);
// 基础分 6.0
$rating = 6.0;
// 1. 进球 & 助攻(位置加成)
$goalWeight = $this->weights[$position]['goals'] ?? 0.40;
$assistWeight = $this->weights[$position]['assists'] ?? 0.30;
$rating += ($stats['goals'] ?? 0) * $goalWeight;
$rating += ($stats['assists'] ?? 0) * $assistWeight;
// 2. 传球成功率
$passTotal = $stats['passes_total'] ?? 0;
$passAcc = $stats['passes_accurate'] ?? 0;
if ($passTotal > 0) {
$passRate = $passAcc / $passTotal; // 0~1
// 70% 为基准线
$rating += ($passRate - 0.70) * 3.0 * ($this->weights[$position]['passes'] ?? 0.2);
}
// 3. 防守数据
$rating += ($stats['tackles'] ?? 0) * ($this->weights[$position]['tackles'] ?? 0.2) * 0.5;
$rating += ($stats['interceptions'] ?? 0) * ($this->weights[$position]['interceptions'] ?? 0.15) * 0.5;
$rating += ($stats['clearances'] ?? 0) * ($this->weights[$position]['clearances'] ?? 0.1) * 0.3;
// 4. 关键传球 & 射正
$rating += ($stats['key_passes'] ?? 0) * 0.15;
$rating += ($stats['shots_on_target']?? 0) * 0.10;
// 5. 过人成功
$rating += ($stats['dribbles_success'] ?? 0) * 0.08;
// 6. 对抗成功率
$duelTotal = $stats['duels_total'] ?? 0;
if ($duelTotal >= 3) {
$duelRate = ($stats['duels_won'] ?? 0) / $duelTotal;
$rating += ($duelRate - 0.5) * 0.8;
}
// 7. 门将特殊处理
if ($position === 'GK') {
$rating += ($stats['saves'] ?? 0) * 0.20;
$rating -= ($stats['goals_conceded'] ?? 0) * 0.35;
if (($stats['saves'] ?? 0) >= 5) $rating += 0.5; // 神扑加成
}
// 8. 负面数据
$rating -= ($stats['fouls_committed'] ?? 0) * 0.05;
$rating -= ($stats['yellow_cards'] ?? 0) * 0.30;
$rating -= ($stats['red_cards'] ?? 0) * 1.50;
// 9. 时间折算(替补出场按比例)
$rating = 6.0 + ($rating - 6.0) * $timeFactor;
// 10. 边界限制 4.0 ~ 10.0
return round(max(4.0, min(10.0, $rating)), 1);
}
/**
* 批量计算并按位置排名(SofaScore 风格:同位置内比较)
*/
public function batchCalculate(array $playersStats): array
{
$result = [];
foreach ($playersStats as $p) {
$result[] = [
'player_id' => $p['player_id'],
'position' => $p['position'],
'rating' => $this->calculate($p, $p['position']),
];
}
return $result;
}
}
使用示例
$engine = new SofaScoreRating();
// 一场曼城比赛哈兰德的数据
$haaland = [
'player_id' => 1,
'position' => 'FW',
'minutes_played' => 90,
'goals' => 2,
'assists' => 1,
'shots_on_target' => 4,
'key_passes' => 2,
'passes_total' => 18,
'passes_accurate' => 15,
'dribbles_success'=> 3,
'tackles' => 0,
'interceptions' => 0,
'clearances' => 0,
'duels_won' => 6,
'duels_total' => 9,
'fouls_committed' => 1,
'yellow_cards' => 0,
'red_cards' => 0,
];
$rating = $engine->calculate($haaland, 'FW');
echo "哈兰德本场评分: {$rating}"; // 输出类似 8.7
进阶:抓取真实数据(爬虫)
如果需要从 SofaScore 实时抓取,需要注意官方无公开 API,采集方式:
<?php
class SofaScoreFetcher
{
private const BASE = 'https://api.sofascore.com/api/v1';
public function getMatchStatistics(int $matchId): array
{
$url = self::BASE . "/event/{$matchId}/statistics";
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'User-Agent: Mozilla/5.0',
'Accept: application/json',
'Referer: https://www.sofascore.com/',
],
CURLOPT_TIMEOUT => 10,
]);
$resp = curl_exec($ch);
curl_close($ch);
return json_decode($resp, true) ?? [];
}
}
⚠️ 注意:
- SofaScore 有 Cloudflare 反爬,直接 curl 常被 403,需配合
cloudscraper或 Puppeteer - 商业项目使用需遵守其 ToS 和版权,建议购买官方数据源(如 Sportmonks / API-Football)
- 调用频率限制、代理池
可选方案:接入第三方 API
如果不想维护爬虫,推荐:
| 服务 | 特点 |
|---|---|
| API-Football | 免费 100次/天,覆盖广 |
| Sportmonks | 数据全,付费 |
| Football-Data.org | 免费层可用 |
| 飞鲸/纳米数据 | 国内,中文,付费 |
项目架构建议
sofa-rating/
├── src/
│ ├── SofaScoreRating.php # 评分算法
│ ├── Repository/
│ │ └── MatchStatsRepository.php
│ ├── Fetcher/
│ │ └── SofaScoreFetcher.php # 数据抓取
│ └── Service/
│ └── RatingService.php # 业务编排
├── config/
├── public/
│ └── index.php
├── storage/
└── composer.json
RatingService 编排逻辑:抓取 → 入库 → 计算 → 缓存 → 输出排名。
几点经验提示:
- 权重需要自我校准:官方算法未公开,最好拿已抓取的真实 SofaScore 评分做回归拟合,用
sklearn/PHP-ML 反推权重,误差能压到 ±0.2 以内。 - 门将独立模型:门将与其他位置差异极大,建议抽离成
GoalkeeperRating类。 - 加入上下文修正:对手强弱、比赛重要性、比分(落后时追分表现)都是 SofaScore 会考虑的因子。
需要我进一步给出 权重拟合脚本 或 Laravel 集成版本 吗?