简介:这是一套面向PHP开发者与建站爱好者的网站采集程序源码,核心为VIVI万能小偷程序,只需填入目标站地址即可自动抓取内容,据描述可兼容约98%的单级域名站点,适合想研究采集逻辑、搭建聚合类站点或做数据抓取练习的中初级开发者参考。资源以zip压缩包形式提供,整体约1.37MB,包内以PHP源码文件为主,涵盖采集入口、页面解析与内容输出等核心逻辑,便于直接阅读与二次修改。目前已有289人学习下载,属于有一定关注度的实用型源码。读者可从中了解小偷程序如何解析目标页面、提取正文与列表、处理单级域名适配,并据此搭建自己的采集流程或改造为定制化爬虫工具,对理解PHP网络请求与HTML解析思路有直接帮助。
1. 从一份 PHP 采集源码说起:网站小偷程序到底解决什么问题
手里这份vivi网站小偷程序5.5(PHP版).zip,名字听着像黑产工具,其实拆开看就是一套用 PHP 写的目标站点内容采集与本地化重建脚本。它的核心场景很明确:你有一个已经上线的 PHP 站点,想把另一个站点的文章、列表、详情页结构批量搬到自己的库里,又不想从零写爬虫、解析、入库、模板替换这一整条链路。这套东西把「抓取—解析—清洗—入库—伪静态输出」串成了一条流水线,适合做内容聚合站、行业资讯站、资源导航站的从业者快速搭原型。
我第一次拿到这类源码时也犯嘀咕:PHP 做采集,并发和稳定性扛得住吗?实际拆完发现,它的定位不是工业级爬虫框架,而是「单机小规模、规则驱动、开箱即用」的轻量方案。你给它一个目标栏目地址和几条 CSS/XPath 规则,它就能把列表页翻页、详情页正文、缩略图一起拉下来,按你预设的模板生成静态页。对预算有限、又需要快速验证内容模型的小团队来说,这比上重型框架省事得多。下面我按「它怎么跑起来 → 规则怎么配 → 哪里会翻车 → 怎么稳住」的顺序,把这份源码拆给你看。
2. 环境搭建与目录结构:把 5.5 版跑起来的第一公里
2.1 运行环境与依赖确认
这套程序是纯 PHP 实现,没有 Composer 依赖,理论上 PHP 5.6 到 7.4 都能跑,但 5.5 这个版本号对应的代码风格偏老,我建议用 PHP 7.2 配 Apache 或 Nginx,别直接上 PHP 8,否则一些ereg、each之类的老函数会直接报致命错误。数据库用 MySQL 5.7 就够,字符集统一utf8mb4,不然后面抓 emoji 或生僻字会乱码。
先把压缩包解到 Web 根目录下的一个子目录,比如/var/www/html/collector,然后确认三件事:allow_url_fopen打开、curl扩展启用、mbstring扩展启用。这三个是采集和编码转换的命脉,缺一个都会在运行到一半时给你脸色看。
# 检查 PHP 扩展是否齐全 php -m | grep -E 'curl|mbstring|pdo_mysql' # 预期输出类似: # curl # mbstring # pdo_mysql上面这条命令是排查环境的第一步。如果curl没出现,去php.ini里把;extension=curl前面的分号去掉再重启服务。mbstring负责把 GBK 目标站转成 UTF-8,少了它,抓回来的中文全是问号,这个坑后面还会细说。
2.2 目录结构与核心文件职责
解压后你会看到大致这么几个部分:config/放数据库和采集参数,lib/是抓取和解析的类库,rule/存每个目标站的规则文件,template/是生成静态页的模板,admin/是后台管理入口。别急着改代码,先摸清哪个文件管哪件事,能省掉大量瞎找的时间。
| 目录/文件 | 职责 | 改动频率 |
|---|---|---|
config/db.php | 数据库连接与表前缀 | 部署时改一次 |
config/setting.php | 采集间隔、超时、编码 | 按目标站调 |
lib/Fetcher.class.php | HTTP 请求与重试 | 基本不动 |
lib/Parser.class.php | 规则解析与字段提取 | 按需扩展 |
rule/*.php | 单站采集规则 | 每加一个站写一份 |
template/default/ | 静态页模板 | 按自己站点风格改 |
这张表是我拆包后整理的核心映射。新手最容易犯的错是直接去改lib/里的类,其实九成需求在rule/和template/里就能解决。Fetcher负责发请求,Parser负责按规则抠数据,两者解耦得还算干净,你换目标站只需要新增规则文件,不用动底层。
2.3 数据库初始化与首次运行
导入建表 SQL 通常在install/或根目录的install.sql里。用命令行导入比 phpMyAdmin 稳,尤其是表多的时候。
# 创建库并导入结构 mysql -u root -p -e "CREATE DATABASE collector DEFAULT CHARSET utf8mb4;" mysql -u root -p collector < install.sql # 确认表已建好 mysql -u root -p collector -e "SHOW TABLES;"导入完成后,改config/db.php里的主机、库名、账号密码,然后浏览器访问后台入口登录。首次运行建议先跑一个「测试采集」,只抓一页列表、不入库,看解析结果对不对。这一步能帮你把规则问题在污染数据库之前就暴露出来。很多人图快直接开全量,结果规则写错,几万条脏数据进了库,清理起来比重抓还麻烦。
3. 采集规则配置:列表页翻页与详情页字段提取
3.1 规则文件的基本结构
每个目标站对应rule/下一份 PHP 数组配置。它要回答四个问题:从哪个 URL 开始、列表页怎么翻、详情链接怎么定位、详情页每个字段从哪抠。下面是一份我常用的规则骨架,字段名按你实际表结构调整。
<?php // rule/example_site.php return [ 'name' => 'example_site', 'charset' => 'gbk', // 目标站编码,gbk 或 utf-8 'list_url' => 'https://target.com/news/list_1.html', 'page_rule' => 'https://target.com/news/list_{page}.html', // 翻页模板 'page_start'=> 1, 'page_end' => 20, 'list' => [ 'item' => 'div.news-list li', // 列表项选择器 'link' => 'a', // 详情链接所在标签 'title' => 'a', // 标题所在标签 ], 'detail' => [ 'title' => 'h1.title', 'content' => 'div.article-content', 'pubtime' => 'span.time', 'source' => 'span.source', ], ];这份配置里,charset决定要不要做编码转换,page_rule里的{page}是翻页占位符,程序会用它替换页码循环抓取。list.item是列表项的选择器,link和title是相对该项的子选择器。detail下每个键对应数据库一个字段,值就是 CSS 选择器。逻辑很直白:先定位列表项,再在每项里找链接和标题,进详情页后按字段选择器逐个抠。
3.2 翻页策略与去重逻辑
翻页有两种常见写法:一种是 URL 里带页码,用{page}占位;另一种是「下一页」链接跟随。这套程序默认支持第一种,第二种需要在规则里加next_rule字段。我一般优先用页码模板,因为可控、可断点续抓,翻到第几页心里有数。
去重靠的是详情页 URL 的哈希。程序入库前会算一次md5(url),在表里建唯一索引,重复的直接跳过。这里有个细节:有些站点的 URL 带随机参数,同一个页面每次抓到的 URL 都不一样,去重就失效了。解决办法是在规则里加一个url_clean正则,把无关参数剔掉再算哈希。
// 在规则里追加 URL 清洗 'url_clean' => '/\?.*$/', // 去掉问号及其后所有参数加上这行后,https://target.com/news/123.html?from=list和https://target.com/news/123.html会被视为同一条,避免重复入库。这个坑我在做行业资讯聚合时踩过,一个站因为带追踪参数,同一篇文章进了七遍库,后台列表惨不忍睹。
3.3 正文提取与图片本地化
正文提取最怕两件事:一是选择器抓到了广告或推荐位,二是正文里的图片是外链,目标站一改图就全挂。前者靠调选择器精度解决,后者需要在规则里开图片本地化开关。
'content_filter' => [ 'remove' => ['div.ad', 'div.related', 'script', 'style'], // 先删干扰节点 'img_local' => true, // 图片下载到本地 'img_dir' => '/uploads/collect/', 'img_url' => 'https://your-site.com/uploads/collect/', ],remove里的选择器会在提取正文前先把广告、相关阅读、脚本样式删掉,剩下的才是干净正文。img_local打开后,程序会把img标签的src换成你站点的地址,同时把图片文件下载到img_dir。注意img_url要和你站点的实际访问路径一致,否则页面能生成但图片 404。图片下载是同步的,量大时会拖慢采集速度,我一般把img_local在调试阶段关掉,确认正文没问题再打开跑全量。
4. 避坑与排查:采集跑不通时先看这几处
4.1 抓回来全是乱码
现象:入库的中文显示为??????或一串看不懂的字符。原因:目标站是 GBK 编码,但规则里charset写成了utf-8,或者服务器没装mbstring扩展,转换函数直接失效。解决:先用浏览器查看目标站源码里的meta charset,确认真实编码;再检查php -m里有没有mbstring;最后在规则里把charset改对。如果目标站是混合编码,可以在Fetcher里强制按mb_convert_encoding转一次。
4.2 列表页抓得到,详情页全空
现象:列表能翻页,标题也抓到了,但进详情页后正文字段为空。原因:详情页的选择器写错了,或者详情页结构和列表页不在同一个域名下,请求被拦。解决:把详情页 URL 单独拿出来,用浏览器的开发者工具复制正确的 CSS 选择器;如果是跨域,检查Fetcher里的Referer和User-Agent有没有带上,很多站会校验这两个头。
4.3 采集到一半进程卡死
现象:跑了十几页后脚本不动了,也不报错。原因:目标站响应变慢或触发了频率限制,而Fetcher的默认超时设得太长,或者没有重试上限。解决:在config/setting.php里把timeout调到 10 秒以内,retry设为 2 次,并在每次请求之间加sleep(1)到sleep(3)的随机间隔。别把间隔设成固定值,固定间隔反而容易被识别出是脚本。
4.4 图片下载了但页面不显示
现象:uploads/collect/里确实有图片文件,但生成的静态页里图片是裂的。原因:img_url配的是相对路径或域名不对,或者图片目录没有写权限。解决:确认img_url是完整的https://开头地址,且和站点实际访问路径一致;用chmod 755给图片目录写权限;如果用了 CDN,还要确认 CDN 回源路径能命中这个目录。
4.5 重复入库清不掉
现象:同一篇文章反复出现在后台列表里。原因:URL 带随机参数导致哈希不同,或者唯一索引没建在url_hash字段上。解决:按 3.2 节加url_clean正则清洗 URL;检查表结构,确认url_hash上有UNIQUE索引;如果历史脏数据已经进了库,写一条 SQL 按标题去重保留最早一条。
-- 按标题去重,保留 id 最小的一条 DELETE t1 FROM articles t1 INNER JOIN articles t2 WHERE t1.title = t2.title AND t1.id > t2.id;这条 SQL 执行前先备份,别问我怎么知道的。去重逻辑最好在入库前用代码拦,而不是事后用 SQL 擦屁股,后者在数据量大时又慢又危险。
5. 进阶技巧:把采集频率和内容质量同时稳住
5.1 用队列削峰,别让脚本裸奔
直接for循环翻页抓取,在目标站响应快的时候没问题,一旦对方变慢,你的脚本就卡在某个请求上,整批任务停摆。我后来改成「先入队、再消费」:列表页只负责把详情 URL 写进一张queue表,另一个常驻脚本按固定节奏从队列取任务、抓详情、入库。这样即使某个详情页超时,也只影响那一条,不会拖垮整批。
// 列表阶段:只收集 URL 入队 foreach ($items as $item) { $url = build_url($item['link']); $hash = md5(preg_replace($rule['url_clean'], '', $url)); $db->exec("INSERT IGNORE INTO queue (url, url_hash, status) VALUES (?, ?, 0)", [$url, $hash]); } // 消费阶段:单独脚本按批取任务 $tasks = $db->query("SELECT * FROM queue WHERE status = 0 LIMIT 20")->fetchAll(); foreach ($tasks as $task) { $html = $fetcher->get($task['url']); // 解析、入库、更新 status = 1 }队列方案的好处是可断点、可限速、可并行。INSERT IGNORE配合url_hash唯一索引,天然去重。消费脚本里把LIMIT调小,配合sleep,对目标站的压力就平缓了。这套改法是我从「跑一次挂一次」到「挂机跑一晚上」的分水岭。
5.2 内容质量校验:三道过滤网
采集来的内容不能直接上站,我一般加三道过滤。第一道是长度过滤,正文少于 200 字的直接丢弃,多半是抓到了列表页或错误页。第二道是关键词黑名单,命中「404」「验证码」「访问受限」的丢弃。第三道是重复度检测,用similar_text或简单的 SimHash 比对最近 1000 篇,相似度超阈值的不入库。
// 正文长度与黑名单校验 $len = mb_strlen(strip_tags($content), 'utf-8'); if ($len < 200) { return false; } $blacklist = ['404', '验证码', '访问受限', '页面不存在']; foreach ($blacklist as $word) { if (mb_strpos($content, $word) !== false) { return false; } }这三道网能挡掉八成脏数据。长度过滤最有效,因为错误页和列表页的正文通常很短。黑名单要按目标站的实际返回文案维护,不同站的拦截提示不一样。重复度检测开销稍大,我一般放在入库前异步跑,不阻塞主流程。
5.3 定时任务与日志留存
最后一步是让它自己跑。用crontab每 30 分钟触发一次消费脚本,每次只跑固定条数,跑完退出,避免常驻进程内存泄漏。
# 每 30 分钟消费一批,日志按天切 */30 * * * * /usr/bin/php /var/www/html/collector/consume.php >> /var/log/collector/$(date +\%Y\%m\%d).log 2>&1日志一定要留,而且要按天切。采集出问题时,日志里的 HTTP 状态码、超时记录、解析失败的行号,是你唯一能回溯的线索。我现在的习惯是:任何采集任务上线前,先把日志路径、轮转策略、失败重试次数这三样配好,再开跑。从那以后我每次部署采集脚本,都强制走一遍「小批量试跑 → 看日志 → 确认无异常 → 再放量」的流程,省下来的排错时间远比多花的那十分钟值。希望帮到你。
本文还有配套的精品资源,点击获取