ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

PHP+MySQL从零搭建影视资源收藏导航,用curl批量检测网站失效状态

PHP+MySQL从零搭建影视资源收藏导航,用curl批量检测网站失效状态 前阵子整理本地收藏夹发现自己攒了不少影视资源相关的站点。收藏的时候一家一个链接真要找起来才知道什么叫乱得离谱——有的站点一个月没登就失效了有的换了域名有的是在手机上收藏的电脑上根本没同步。我当时正好有台闲置的服务器装了套PHP环境干脆自己写了个自用的影视资源收藏导航页把这些影视资源网站按分类、优先级、可用状态统一管起来。于是就有了这个项目php收藏影视十个值得收藏的影视资源网站。这篇文章不只是给你看十个网站的清单更是一份“用PHP从零搭一个收藏导航工具”的实操记录。适合这么几类人看一是没事折腾PHP、想练手数据库和curl的人二是收藏夹常年堆积几百条链接但找不到工具的影迷三是想快速验证“一张表 几个页面”能不能解决真实问题的同学。整个项目只用了PHP自带的PDO、curl、json没有引入任何框架部署到宝塔或者phpStudy里都能跑。1. 项目需求与整体设计思路1.1 为什么用PHP而不是别的方案收藏导航这类小工具本质就是“一张表几个页面”PHP天然适合这种轻量CRUD场景。我的理由很直接PHP部署太省事了传统LAMP环境到处都是不挑机器内置的PDO、curl、json系列函数齐全不需要额外装模块改个页面模板就能随时加功能从写代码到跑起来半小时内完成。拿它跟其他方案对比过各有取舍方案优点缺点适合场景纯静态HTML零依赖、打开快没法筛选排序、没法自动查失效纯粹当书架用不折腾PHP MySQL轻量、部署方便、生态成熟并发性能一般个人收藏、家庭小站Python Flask写起来舒服、思路现代服务器上要单独装Python环境本来就有Python环境的人Node Express前后端一体、ESM生态好内存占用偏高配置略繁琐想在工具里加实时推送玩法我当时的需求其实很简单收藏、分类、搜索、检测站点有没有失效、数据能备份、一键跳转。PHPMySQL全部覆盖而且这些功能全部用原生写法就能完成没必要为一个个人工具上框架。数据量常年也就几百条收藏MySQL性能完全够用不需要上ES不需要Redis更不需要微服务那一套。1.2 需求拆解收藏工具到底要做什么我开工前把需求写在了纸上避免写着写着往“大而全”跑。最终落地到功能层面就是下面这六块收藏新增把标题、URL、分类、一句话描述、优先级入库。分类展示按影视资源类型筛选比如官方平台、数据库、素材、字幕、社区。关键词搜索搜站点名称、URL和描述快速定位。批量状态检测定期探测收藏的站点是否还能访问。导入导出收藏数据导成JSON方便备份和换设备迁移。安全跳转点击跳转外站不暴露多余信息顺便记录点击次数。也就是说这不是一个“网址导航”项目而是一个“带健康监控的个人收藏夹”。当时最吸引我的其实是第六个功能站点失效检测。收藏夹里躺着一堆不知道还活不活的链接手动一个个点开太蠢了用curl批量探测能自动标出失效和不失效这一块我在后面详细讲。2. 核心功能实现收藏与数据管理2.1 数据库设计与字段取舍表结构我一开始就按最朴素的单表来做没有搞分类表、标签表那套复杂关系。影视收藏的数据量小硬拆多张表只会让代码徒增JOIN没有任何收益。最终表结构是这样的CREATE TABLE fav_sites ( id int(11) NOT NULL AUTO_INCREMENT, category varchar(50) NOT NULL DEFAULT other COMMENT 分类official/database/trailer等, name varchar(100) NOT NULL COMMENT 站点名称, url varchar(255) NOT NULL COMMENT 站点URL, desc text COMMENT 一句话描述, priority tinyint(3) NOT NULL DEFAULT 5 COMMENT 优先级1-10数字越大越靠前, status tinyint(1) NOT NULL DEFAULT 1 COMMENT 状态1正常0失效, click_count int(10) NOT NULL DEFAULT 0 COMMENT 点击次数, last_check datetime DEFAULT NULL COMMENT 最后检测时间, created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_url (url), KEY idx_category (category), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT收藏的影视资源站点;几个细节说说我的设计意图。url加了唯一索引这样数据库层面就杜绝了重复收藏后面代码里查重只是多一道保险。category用varchar直接存英文枚举值比存数字代号再关联字典表直观得多——我在PHP里写代码时直接写trailer比写3好理解。click_count是后来加的不用额外建日志表就能看哪些站点用得最多。status默认为1也就是新增的收藏默认视为正常检测任务跑过之后再把失效的改成0。2.2 PHP连接MySQL的坑与正确姿势数据库连接这一步看着简单实际坑不少。最大的坑就是中文乱码。很多人建库时用了utf8插入中文后查询乱码折腾半天其实是字符集不一致。正确的做法是DSN里显式带上charsetutf8mb4建表语句里也指定相同的字符集PHP文件保存为UTF-8无BOM三层统一中文描述就稳了。?php // db.php 数据库连接 $host 127.0.0.1; $port 3306; $dbname media_shelf; $user root; $pass your_password; $dsn mysql:host$host;port$port;dbname$dbname;charsetutf8mb4; $options [ PDO::ATTR_ERRMODE PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES false, ]; try { $pdo new PDO($dsn, $user, $pass, $options); } catch (PDOException $e) { // 生产环境建议记录日志不要直接把异常信息输出给用户 error_log($e-getMessage()); exit(数据库连接失败请检查配置。); }PDO::ATTR_EMULATE_PREPARES一定要设成false。这个选项控制是否模拟预处理设成false以后参数会真正以预处理方式发送给MySQL能有效防止SQL注入。个人工具虽然不追求高安全等级但“不要让你的SQL变成用户输入的一部分”这个习惯在什么时候都不多余。连接异常时我用了error_log记录详细原因页面上只给用户一个模糊提示避免把账号密码泄露到页面里。2.3 收藏入库的完整流程收藏功能是所有操作的核心入口。完整流程是表单提交 - CSRF Token验证 - URL规范化 - 查重 - 入库。每一个步骤都有原因。CSRF验证其实非常轻量页面加载时生成一个Token存Session提交时比对表单Token和Session值。个人工具虽然不太可能被打CSRF但收藏操作如果接口随便能被第三方页面触发你莫名其妙就会多出一堆收藏垃圾。防范成本只有几行代码值得做。// 收藏新增入口 add.php session_start(); if ($_SERVER[REQUEST_METHOD] POST) { if (!isset($_POST[token]) || $_POST[token] ! ($_SESSION[token] ?? )) { exit(请求来源异常请刷新页面重试); } $name trim($_POST[name] ?? ); $url trim($_POST[url] ?? ); $desc trim($_POST[desc] ?? ); $category trim($_POST[category] ?? other); $priority (int)($_POST[priority] ?? 5); if ($name || $url ) { exit(站点名称和地址不能为空); } $result add_site($name, $url, $desc, $category, $priority); // 根据$result跳转或输出提示 }add_site函数是收藏逻辑的核心URL规范化这步容易被忽略。用户很可能会输入“www.example.com”或直接粘贴“example.com/x/y”不处理直接入库会导致批量检测时curl报错。我统一补全协议头顺手用strtolower对域名部分做小写处理避免同一站点输入大小写不同重复入库。function add_site($name, $url, $desc , $category other, $priority 5) { global $pdo; $url trim($url); // 补全协议头 if (!preg_match(#^https?://#i, $url)) { $url http:// . $url; } // 查重 $stmt $pdo-prepare(SELECT id FROM fav_sites WHERE url ? LIMIT 1); $stmt-execute([$url]); if ($stmt-fetch()) { return [code 0, msg 这个地址已经在收藏夹里了]; } $stmt $pdo-prepare( INSERT INTO fav_sites (name, url, desc, category, priority, status, last_check) VALUES (?, ?, ?, ?, ?, 1, NULL) ); $stmt-execute([$name, $url, $desc, $category, $priority]); return [code 1, msg 收藏成功]; }这里有个PHP写法的细节desc是MySQL的保留字INSERT时要用反引号包裹否则语法报错。我在SQL里写了desc这个反引号在视觉上很容易被忽略但少了它SQL直接跑不起来。还有一个点是优先级priority我为它定的语义是“数字越大越靠前”后面列表排序时直接ORDER BY priority DESC就行不用额外转换逻辑。3. 批量检测、搜索与前端交互3.1 站点失效检测curl_multi并发探测这是整个工具里技术含量最高的一块也是最实用的。收藏夹里一两百个链接如果用人肉点的办法去看哪个失效了下午的时间就全搭进去了。PHP的curl_multi_*系列函数可以并发发包一次请求多个站点速度非常快。// check.php 批量检测站点状态 function check_sites($items) { $mh curl_multi_init(); $handles []; foreach ($items as $id $url) { $ch curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER true, CURLOPT_HEADER false, CURLOPT_NOBODY true, // 只拿响应头不下载正文 CURLOPT_FOLLOWLOCATION true, // 跟随跳转 CURLOPT_TIMEOUT 5, CURLOPT_CONNECTTIMEOUT 3, CURLOPT_USERAGENT Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36 ]); curl_multi_add_handle($mh, $ch); $handles[$id] $ch; } do { $status curl_multi_exec($mh, $active); if ($active) { curl_multi_select($mh); } } while ($active $status CURLM_OK); $results []; foreach ($handles as $id $ch) { $httpCode curl_getinfo($ch, CURLINFO_HTTP_CODE); $errno curl_errno($ch); // errno等于0说明请求无传输错误此时HTTP_CODE才有参考意义 $results[$id] $errno 0 ? $httpCode : 0; curl_multi_remove_handle($mh, $ch); curl_close($ch); } curl_multi_close($mh); return $results; }关于为什么要设CURLOPT_NOBODY true用GET方法访问一个页面会把整个HTML都下载下来如果某个站点资源很大探测就会又慢又耗流量。NOBODY改成HEAD请求只获取响应头服务器返回的状态码足以判断站点是否正常。实测下来一次检测70个站点并发模式下总耗时在3到5秒换成串行循环的话可能等到一分钟开外。关于检测频率血的教训是不要跑得太勤。第一次上线时我先设了每10分钟自动探测一次结果第二天那几个影视站全部把我的IP封了页面直接403。后来我改成每天凌晨3点跑一次检测结果只更新last_check和status两个字段服务器压力小站点的反爬阈值也摸清楚了。个人工具的核心思路是“低调、低频、高效”不要让你的收藏夹变成捅马蜂窝的那根棍子。3.2 状态码怎么判定有效还是失效拿到HTTP状态码后不能简单认为“200就是正常其他全部失效”。实际跑了两轮就发现很多站点返回30x跳转特别是官方平台域名经常换但从www.example.com跳到www.example.tv页面依然能用这是正常的短跳转curl已经按FOLLOWLOCATION自动追进去了。真正要标记为失效的是这样几类情况常见HTTP状态码处理方式正常访问200状态正常临时/永久跳转301、302视为正常已跟随跳转资源被删除410标记失效访问被拒绝403标记为“疑似反爬”下次换UA再试找不到页面404先标记失效手动确认网络层错误0标记失效稍后再补测所以更新状态的逻辑我做了两层如果返回200或者跟随跳转后得到200直接标记正常如果返回403这类“服务器拒绝”的结果不要急着标失效因为可能是本次探测UA被拦了我会在第二天检测时换个浏览器UA再试一次。只有连续两次都拿不到有效响应才把status改成0。3.3 搜索与分类筛选列表页的搜索函数是整个工具使用频率最高的功能。它支持三个筛选维度分类、关键词、状态。关键词匹配范围覆盖站点名称、URL和描述。这里容易踩的坑是用户输入的搜索词可能包含LIKE通配符比如搜“100%”时%会被当成通配符导致全部匹配。正确的做法是先把通配符转义掉。function get_sites($category , $keyword , $status ) { global $pdo; $sql SELECT * FROM fav_sites WHERE 11; $params []; if ($category ! $category ! all) { $sql . AND category ?; $params[] $category; } if ($keyword ! ) { $kw % . str_replace([%, _], [\%, \_], $keyword) . %; $sql . AND (name LIKE ? OR url LIKE ? OR desc LIKE ?); $params[] $kw; $params[] $kw; $params[] $kw; } if ($status ! $status ! all) { $sql . AND status ?; $params[] (int)$status; } $sql . ORDER BY priority DESC, id DESC; $stmt $pdo-prepare($sql); $stmt-execute($params); return $stmt-fetchAll(); }这个函数写在functions.php里列表页、搜索页共用同一份逻辑。用PDO预处理动态拼接的参数全程走?占位符传递不直接拼字符串安全性有保障。str_replace转义通配符那行代码短但是关键没有它搜索功能会在特殊字符上翻车。3.4 列表页视图与跳转统计列表页面我用最简单的PHP模板输出每一行数据卡片包含名称、描述、分类标签、状态角标、优先级数字和操作按钮。输出时要统一做HTML转义防止收藏数据里混入脚本代码这是XSS防护的基本功。收藏工具虽然是自己用但如果页面被分享出去转义习惯就能避免很多问题。点击跳转我做了个小小的中间层列表里的链接不直接指向外站而指向go.php?idxxx在这个跳转页面里做两件事——先给对应记录的click_count字段加1然后通过header(Location: . $url)完成跳转。这样做的好处有两点一是能统计哪些站点真的在用二是以后如果想加“访问前提示确认”之类的功能只需要改中间层一处。3.5 数据备份JSON导出与导入备份功能是我第三个版本才加上的但它是整个工具里存活价值最高的功能。为什么因为收藏夹内容一旦积累到几百个链接全部手动重新输入一遍是非常痛苦的事。JSON导出导入方案把迁移时间从一小时压缩到一分钟。function export_json() { global $pdo; $sites $pdo-query(SELECT name, url, desc, category, priority FROM fav_sites)-fetchAll(); // JSON_UNESCAPED_UNICODE 防止中文被转成 \uXXXX 编码 return json_encode($sites, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); } function import_json($json) { global $pdo; $data json_decode($json, true); if (!is_array($data)) { return 0; } $count 0; $stmt $pdo-prepare(INSERT IGNORE INTO fav_sites (name, url, desc, category, priority, status) VALUES (?, ?, ?, ?, ?, 1)); foreach ($data as $item) { $stmt-execute([ $item[name], $item[url], $item[desc] ?? , $item[category] ?? other, (int)($item[priority] ?? 5) ]); $count $stmt-rowCount(); } return $count; }导出时JSON_UNESCAPED_UNICODE这个选项很关键。不传的话PHP的json_encode会把中文描述转成\uXXXX形式导出的文件能看但没法编辑传了之后保留中文原样备份文件打开就是可读文本。导入用的是INSERT IGNORE配合数据库的uk_url唯一索引重复收藏的链接自动跳过不会产生脏数据。我实际把这份JSON存在网盘和本地两个地方换机器部署新环境时建好表跑一次导入脚本所有收藏原样恢复。3.6 顺便说说接口跨域如果你的列表页和PHP接口不在一台服务器上就会遇到跨域问题。PHP处理起来很简单接口返回前加上CORS头就行。header(Content-Type: application/json; charsetutf-8); header(Access-Control-Allow-Origin: *); // 如果是JSONP场景老接口兼容写法 $callback $_GET[callback] ?? ; if ($callback) { echo $callback . ( . json_encode($data, JSON_UNESCAPED_UNICODE) . ); } else { echo json_encode($data, JSON_UNESCAPED_UNICODE); }Access-Control-Allow-Origin: *适合公开接口如果只给自家前端页面用更稳的是指定具体域名避免跨站请求恶意调用你的接口。JSONP算是老方案了现在项目里有CORS基本用不上但如果你维护一些别人写的传统PHP接口还是会看到这种callback参数写法知道是怎么回事就行。4. 十个值得收藏的影视资源网站分类参考4.1 我实际收藏了什么以及为什么值得收藏工具搭好了接下来要喂数据。标题里说“十个值得收藏的影视资源网站”我在实际采集时并不是看到一个收藏一个而是按“类目”去收录的。因为影视相关内容实在太多只收藏站点不分类的话和以前的浏览器收藏夹没有区别。我最后沉淀出这十个类目每个类目对应一个“方向”经过筛选后留存下来的站点才是值得长期跟踪的分类标识类目方向为什么值得收藏我设置的分级official大型正版平台官网内容更新稳定、域名变动少、最可靠10database影视数据库与评分站查资料、看评分、作品关联信息最全9trailer官方预告片与物料站新片信息第一手来源素材正规可用8material公开样片与测试素材开发者测试播放器、CDN时需要无版权争议的视频7subtitle字幕资源站配合本地播放器使用解决外文片字幕问题7public公版影片与老片修复库版权过期作品可放心收藏和引用6static剧照海报素材库写文章配图、做海报墙需要高清剧照6news影视资讯站行业动态、档期变动、票房数据集中5community影迷论坛与讨论区淘片线索、版本对比、幕后信息都在讨论里5tool本地媒体管理工具官网服务端媒体库、下载整理工具配合自建家庭影院8这里要强调一个原则收录站点时优先选官方、公开、无版权争议的资源这类站在长期使用中稳定性最高。比如公版电影资源站因为版权过期域名被投诉关停的概率低而随意转码的盗版资源站换个域名是家常便饭今天收藏明天失效维护成本极高。我第一版收藏夹里就混了不少来路不明的站检测结果一片飘红后来按这个原则清洗了一遍健康率大幅提升。4.2 收藏一批数据后的筛选思路收藏进去只是第一步真正有价值的是定期清洗。我的做法是每个季度跑一次完整检测把status0的失效站点拉出来人工确认。确认时重点看三件事这个站是不是真没了只是临时扛不住流量挂了有没有备用域名有的话顺手更新URL如果彻底消失果断删除记录。这十个类目扫码下来你会发现它们覆盖的不只是“看片”这一个环节而是从“发现新片”到“确认资料”再到“观看收藏”的完整链路。这也是我觉得收藏影视资源网站最有价值的地方你收藏的不是一个个孤立的链接而是一套信息获取的渠道组合。工具只是帮你管理这套组合真正的筛选判断还是得靠自己的使用习惯来沉淀。5. 常见问题与排查技巧5.1 实际运行中遇到的典型问题项目跑起来之后各种小问题接踵而至。我把几个最常见的问题整理成了一张速查表照着排查基本都能解决现象可能原因解决办法中文保存后变乱码字符集不一致PHP文件、DSN、数据库表全部统一为utf8mb4curl检测全部返回200但站实际打不开站点有防盗链静默返回200加Referer头配合浏览器UA再试检测结果全是403同一IP请求太频繁被封降低检测频率改成每日一次加随机间隔列表页空白但数据库有数据PHP报错被隐藏开启display_errors临时排查查看error_log重复收藏还能进库唯一索引没生效检查uk_url索引是否创建成功业务层也要查重页面导入JSON后分类全部变成other导入数据里没有category字段老备份文件补上默认分类字段再导入页面跳转提示“过多重定向”站点强制HTTPS但检测时走了HTTPURL规范化时优先保留https跟随跳转开启即可5.2 环境与版本问题有次在旧服务器上部署时发现PHP版本停留在5.6PDO的ERRMODE_EXCEPTION虽然能用但很多新特性缺失curl_multi_select在超长阻塞时表现也不稳。后来我直接把PHP版本升级到7.4以上语法写起来更顺手性能也有明显提升。如果你用的是宝塔面板或者phpStudy升级PHP版本就是点两下的事但升级后要把PHP的扩展开关确认一遍——pdo_mysql、curl、openssl这三个扩展缺一不可。5.3 个人PHP工具的安全底线虽然只是自己用的收藏工具但安全习惯还是要养成的。我的底线是三条一是不在页面上暴露PHP报错信息开发期开着display_errors方便排查上线后一定关掉错误记录全部走error_log二是不把SQL拼接当成常规操作任何外部数据都通过PDO预处理传入三是源码文件和备份的JSON不放公共目录部署时放到网站根目录外的子目录或者设置目录访问权限。这里多说一句如果你的机器部署过一段时间的PHP项目偶尔可以在日志里看到被扫描的痕迹。个人小工具更要把入口收敛好不需要对外暴露的页面都用Session校验拦上收藏操作也加了CSRF Token做到这两点基本能挡住绝大多数瞎试的请求。最后留个尾这个工具我实际用了小半年最深的体会是收藏工具千万别贪功能能用一张表解决的问题就不要上多表能本地跑完的检测就不要上队列。现在这版PHPMySQLcurl的组合已经稳定运行很久日常维护就是每季度跑一次检测、看看有没有失效站点剩下的时间它安安静静躺在服务器角落。最后再分享一个习惯每次往收藏夹里加站点时顺手补一句描述和分类这一下看似多余但在一个月之后整理时救命程度堪比备份。影视资源网站那么多真正值得长期跟着走的其实就那么几个类目认准方向再动手收藏比见链接就存要高效得多。我就是靠这套办法把之前满屏失效链接的收藏夹慢慢整顿成了现在这个一眼能看穿全局的小工具。
返回列表