你可能正在做一个书签管理工具、链接目录,或者一个会展示第三方网站的面板。你希望每个站点都能显示 favicon,让列表看起来更精致——但对每个 URL 都直接请求 /favicon.ico 其实很脆弱。相当多的站点用的是 SVG 图标、自定义路径,或者通过条件式的 <link> 标签来声明图标,而不是依赖根目录下的那个默认文件。
这篇教程会带你走完「用 PHP 从 URL 获取 favicon」的完整流程:解析 HTML 中所有 <link rel="icon"> 声明,回退到旧的 /favicon.ico 端点,校验响应内容,最后把二进制文件保存到本地。
前置条件
你需要 PHP 7.4 或更高版本,并启用以下两个扩展:
curl— 用于抓取远程页面并跟随重定向dom— 用于解析 HTML 并提取<link>标签
官方 PHP Docker 镜像和绝大多数 Linux 发行版都默认自带这两个扩展。开始写代码前可以先跑一下 php -m 确认它们在列表中。
步骤一:抓取页面 HTML
从一个基础的 cURL 请求开始。User-Agent 请求头比看起来更重要——有些站点对非浏览器客户端会返回精简版的 HTML,结果可能直接省略掉 favicon 的 <link> 标签。
<?php
function fetchHtml(string $url): string|false {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
CURLOPT_TIMEOUT => 10,
CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; FaviconFetcher/1.0)',
CURLOPT_SSL_VERIFYPEER => true,
]);
$html = curl_exec($ch);
curl_close($ch);
return $html;
}
?>
一定要对重定向次数和超时做限制。一个跑 30 秒都收不住的 cURL 请求,很快就会把你的 PHP-FPM worker 池全部占满。
步骤二:解析 HTML 中的 rel="icon" 标签
HTML 规范把 rel="icon" 定义为声明 favicon 的标准方式(可以参考 WHATWG 链接关系文档)。浏览器可以从任意一个匹配的 <link> 中挑选,所以我们扫描每一个出现的位置,优先取第一个 href 可用的。
<?php
function findIconUrl(string $baseUrl, string $html): ?string {
libxml_use_internal_errors(true);
$dom = new DOMDocument();
$dom->loadHTML($html);
libxml_clear_errors();
$xpath = new DOMXPath($dom);
$links = $xpath->query('//link[@rel="icon" or @rel="shortcut icon"]');
foreach ($links as $link) {
$href = $link->getAttribute('href');
if ($href === '') continue;
return resolveUrl($baseUrl, $href);
}
return null;
}
?>
注意我们也把 shortcut icon 加进来了 —— 那是微软 IE 时代留下的老古董,但出乎意料的是,不少老站点至今还在用这种方式声明 favicon。
步骤 3:处理相对路径
favicon 的 href 经常是相对路径,比如 /static/icon.png 或 icons/favicon.svg。可以用 parse_url 拆解,再写一个小辅助函数把它们转成绝对 URL,然后再下载。
<?php
function resolveUrl(string $base, string $ref): string {
if (parse_url($ref, PHP_URL_SCHEME) !== null) {
return $ref; // already absolute
}
$parts = parse_url($base);
$origin = ($parts['scheme'] ?? 'https') . '://' . ($parts['host'] ?? '');
if (str_starts_with($ref, '/')) {
return $origin . $ref;
}
$path = $parts['path'] ?? '/';
$dir = substr($path, 0, strrpos($path, '/') + 1);
return $origin . $dir . $ref;
}
?>
步骤 4:兜底请求 /favicon.ico
如果 HTML 里压根没有 <link rel="icon"> 标签,所有浏览器其实都会自动去请求 /favicon.ico。把这个 endpoint 当作兜底,免得在面对老式站点时返回一片空白图标。
<?php
function fallbackIconUrl(string $pageUrl): string {
$parts = parse_url($pageUrl);
return ($parts['scheme'] ?? 'https') . '://' . ($parts['host'] ?? '') . '/favicon.ico';
}
?>
步骤 5:下载并校验二进制
接下来就是下载图标、确认拿到的确实是图片,然后存到本地。务必用 cURL 重新发一次请求,不要复用抓 HTML 那个响应 —— favicon 的 URL 可能挂在另一个域名或者 CDN 上,缓存策略完全不同。
<?php
function downloadFavicon(string $iconUrl, string $savePath): bool {
$ch = curl_init($iconUrl);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
CURLOPT_TIMEOUT => 10,
CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; FaviconFetcher/1.0)',
CURLOPT_SSL_VERIFYPEER => true,
]);
$data = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE) ?? '';
curl_close($ch);
if ($data === false || $code !== 200 || $data === '') {
return false;
}
// Reject obvious non-image responses (HTML error pages, JSON, etc.)
if (stripos($type, 'image/') !== 0 && stripos($type, 'application/octet-stream') !== 0) {
return false;
}
return file_put_contents($savePath, $data) !== false;
}
?>
把整条 PHP Get Favicon from URL 流程串起来
完整流程是这样:抓取 HTML,解析 link 标签,找不到就回退到根目录的 icon,然后保存:
<?php
function getFaviconFromUrl(string $pageUrl, string $savePath): bool {
$html = fetchHtml($pageUrl);
if ($html === false) return false;
$iconUrl = findIconUrl($pageUrl, $html) ?? fallbackIconUrl($pageUrl);
return downloadFavicon($iconUrl, $savePath);
}
?>
调用方式如下:
<?php
$ok = getFaviconFromUrl('https://example.com', __DIR__ . '/example.ico');
echo $ok ? 'Saved.' : 'Failed.';
?>
常见坑
- HTML错误页面伪装成图标。服务器配置错误时,缺失的 favicon 请求会返回 200 状态码并附带 HTML 内容。务必检查 content-type header,把任何不以
image/开头的响应直接丢弃。 - CDN 速率限制。从你的服务器反复请求第三方 CDN 可能会导致 IP 被限流甚至封禁。把成功下载的结果缓存到磁盘,并保持礼貌的抓取间隔(每个域名间隔几秒通常是安全的)。
- 关闭 SSL 验证。调试时把
CURLOPT_SSL_VERIFYPEER设为false看起来很省事,但会让你的用户暴露在 MITM 攻击之下。生产环境务必保持开启。 - 编码边界情况。当 HTML 缺少 charset meta 标签时,DOMDocument 会回退到 ISO-8859-1,这会让 UTF-8 网站上的 href 值出现乱码。可以前置一个
<meta charset='UTF-8'>块,或者改用支持 UTF-8 的解析器,例如HTML5DomDocument。 - 同源策略不适用于服务端。PHP 可以直接抓取任意公开的 favicon,无需 CORS 检查——那套规则只对前端 JavaScript 生效。
何时切换到专用工具
对于几千个 URL 的规模,自己手写一个 PHP 函数完全够用。但一旦你需要多格式输出(ICO、PNG、SVG、Apple Touch)、深色模式适配、maskable manifest 图标,或者要稳定覆盖那些 <link> 结构古怪的站点,cURL 那套流程就会变成沉重的维护负担。像 Mzu favicondl 这类工具会帮你完成解析、回退、归一化和打包,覆盖各种浏览器和设备,你就可以丢掉这些样板代码,把精力放回产品本身。
你也可以把这套手动方案与目前流行的公开 favicon API 端点 做个对比,看看哪种取舍更适合你的技术栈——手动抓取给你完全的控制权,而 API 则用外部依赖换取了开发时间。