FEATURED · 精选文章

外链自动化提交:PHP+curl_multi实现9600条外链批量管理

发布时间 / 2026/9/14 5:34:54
来源 / 创域科博编辑部
栏目 / 资讯中心
外链自动化提交:PHP+curl_multi实现9600条外链批量管理 简介一款面向新站快速建设外链的PHP工具源码整套程序以清爽风格呈现自带进度条内置九千六百余条可用的外链提交网址适合站长在短期内为网站集中增加外部链接、提升收录效率。工具不依赖复杂环境部署到虚拟主机即可运行老站则可忽略。资源包共一百一十三个文件约七百一十八KB其中以gif图标、js交互脚本和css样式为主另包含php核心入口、html页面以及可编辑的urls.txt外链清单还配有woff、eot、svg等字体资源覆盖前端展示与后台逻辑的完整结构源码层次清楚、便于二次开发。已有一百三十一人学习下载。下载后可直接获得整套程序源码通过修改urls.txt即可增删外链来源也能按自身需求调整界面样式和提交调度适用于有一定PHP基础、希望自主掌控外链建设节奏的站长。1. 外链提交到了 9600 条这个量级靠手填表单已经不可能完成做内容站和 B2B 站的人迟早要面对一个事实外链是该准备量变到质变的资源而不是偶尔做一次的活动。手动去目录站、行业导航、资源页提交一次需要填域名、标题、描述还要等邮件确认平均三到五分钟一条。一天发二十条手就受不了9600 条这个数字意味着至少两个多月的机械劳动。于是“超级外链SEO工具源码”这类项目出现了核心不是黑帽技术而是把“提交”这个动作拆成数据库、任务队列、HTTP 客户端三层。你真正要解决的是让脚本知道什么时候发、发完怎么验证、被拒了怎么重试而不是简单写一个重复提交的死循环。这套方案适合手里已经有一批外链候选源想用自动化把建站初期的权重积累速度提起来的人。2. 外链源库建设用筛选参数定义“优质”并把候选存进 MySQL2.1 先定义“优质外链”再谈采集和入库很多人以为优质外链就是权重高的门户页面但在批量操作场景里“优质”必须可计算、可存储、可过滤。我一般会把优质外链拆成四个硬性指标目标域名的注册时间大于一年外链要落地的页面本身能被索引也就是没有 noindex页面上要放置的链接不是 relsponsored 或 nofollow提交入口的表单字段相对固定方便自动化。这四个指标里权重和收录状态是前提反而好验证难的是“提交入口是否友好”。目录站和资源页一般提供固定的“提交网站”表单字段通常是站点URL、标题、描述、邮箱这类入口适合自动化。论坛签名和纯评论区留言不适合放到这个工具里一方面搜索引擎对这类链接的认知已经非常保守另一方面它们需要人工参与写内容不适合跑 9600 条。下面是我在项目里常用的外链来源分类和对应门槛发布方式典型入口可用性优质量级目录提交网站目录的 submit 页面表单字段固定适合脚本数千条以上资源页链接工具聚合页、网址导航页需要先发文或注册数百条以内客座博客投稿博客的 Write for Us 页面需要人工审核脚本只负责收集几十条以内行业导航站同行业的网址列表表单简单收录较快数百条左右同一个来源站点也要用domain_age_days和page_pa做二次过滤。域名年龄不够的新站权重还没有稳定发过去容易成为链接农场的一部分页面对外链越开放越是要警惕对方是否在卖过量外链导致被惩罚。这类信息不能靠拍脑袋要落到数据库字段里。2.2 用建表语句把外链源固化下来我会把候选外链源放到一张独立的表里字段除了来源信息还包括表单字段和调度时间窗口。建表语句如下CREATE TABLE link_source ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, target_url VARCHAR(500) NOT NULL COMMENT 要发布的主站链接, form_url VARCHAR(1000) NOT NULL COMMENT 提交表单的 action 地址, domain VARCHAR(255) NOT NULL COMMENT 外链所在域名, publish_method TINYINT NOT NULL COMMENT 1目录提交,2客座投稿,3资源页, language VARCHAR(10) DEFAULT zh, domain_age_days INT UNSIGNED DEFAULT 0, page_pa SMALLINT UNSIGNED DEFAULT 0, site_pages INT UNSIGNED DEFAULT 0 COMMENT 该域名被收录的页面数, noindex TINYINT(1) DEFAULT 0, form_fields JSON NOT NULL COMMENT 按固定结构存储表单键名, min_interval_hours SMALLINT DEFAULT 24, status TINYINT DEFAULT 0 COMMENT 0待发送,1已发送,2失败,3已阻止, last_submitted_at DATETIME DEFAULT NULL, next_submit_at DATETIME DEFAULT NULL, fail_count TINYINT DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, KEY idx_status_next (status, next_submit_at), KEY idx_domain (domain) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT外链源库;这里最关键的字段是form_fields它用 JSON 存储每个来源站点不同的表单控件名。有的表单把主站链接字段叫url有的叫url_website同一个工具不可能用一套固定 POST 参数发遍所有站点所以必须在入库时把字段名差异记录下来。min_interval_hours用来控制同一个域名下的重复提交频率避免一天内多次打扰对方审核人。next_submit_at和status是任务队列的入口脚本读取数据时就依赖这两个字段。2.3 用 SQL 批量筛选符合“优质”条件的候选建好表之后筛选就不需要再靠 Excel 人工判断了。下面这个查询是每次发送前都要跑的SELECT id, target_url, form_url, form_fields FROM link_source WHERE status IN (0, 2) AND fail_count 3 AND language zh AND domain_age_days 365 AND site_pages 100 AND noindex 0 AND page_pa 10 AND (next_submit_at IS NULL OR next_submit_at NOW()) ORDER BY last_submitted_at ASC LIMIT 100;这个 SQL 需要注意的参数是site_pages和domain_age_days。site_pages代表该域名有收录价值的页面数量只有几十个页面的站说明还没被搜索引擎认真对待外链放过去大概率是无效的。noindex 0用来排除不允许索引的页面避免外链发过去的结果只是被爬虫抓取但没有收录。LIMIT 100是每次送入提交引擎的批次大小后续可以根据服务器负载调整成 200 或 500。查询结果里的form_fields需要解码成关联数组再配合form_url构造 POST 请求。注意不要用SELECT *直接把用不到的大字段排除否则一次取几百条记录时MySQL 和 PHP 之间的传输会占掉不少内存。3. 提交引擎用 PHP curl_multi 跑完 9600 条外链队列3.1 为什么选 PHP curl_multi 而不是纯异步框架提交外链本质是大量 HTTP POST 请求瓶颈在 IO 等待不在 CPU。这类源码通常都跑在虚拟主机或轻量服务器上要求部署越简单越好。纯异步框架如 Swoole 虽然并发性能更好但编译安装成本高。PHP 自带的curl_multi_*函数组天然支持并发不用额外装扩展兼容普通 Nginx PHP 环境所以我会优先把它作为提交引擎的内核。curl_multi 的思路是把多个 cURL 句柄交给一个多路复用器然后循环等待所有句柄完成。它和异步框架的差距在于每个连接依然占用一个进程内的 socket但并发 10 到 20 个外链请求已经足够跑满大多数服务器的带宽和连接数限制没必要为 9600 条任务引入复杂组件。3.2 最小可用的批量提交脚本下面这段代码是提交引擎的核心函数把一个批次的tasks数组并发提交并返回每个任务的结果function submit_external_links(array $tasks): array { $mh curl_multi_init(); $handles []; $results []; foreach ($tasks as $id $task) { $ch curl_init(); curl_setopt_array($ch, [ CURLOPT_URL $task[form_url], CURLOPT_POST true, CURLOPT_POSTFIELDS $task[form_fields], CURLOPT_RETURNTRANSFER true, CURLOPT_FOLLOWLOCATION true, CURLOPT_USERAGENT Mozilla/5.0 (compatible; LinkSubmitter/2.0), CURLOPT_TIMEOUT 30, CURLOPT_CONNECTTIMEOUT 10, CURLOPT_HTTPHEADER [Accept-Language: zh-CN,zh;q0.9], ]); curl_multi_add_handle($mh, $ch); $handles[$id] $ch; } $active null; do { $status curl_multi_exec($mh, $active); if ($status ! CURLM_OK) { break; } if ($active 0) { curl_multi_select($mh, 1); } while ($info curl_multi_info_read($mh)) { $id array_search($info[handle], $handles, true); if ($info[result] ! CURLE_OK) { $results[$id] [ ok false, error curl_error($info[handle]), errno $info[result], ]; } else { $resp curl_multi_getcontent($info[handle]); $httpCode (int) curl_getinfo($info[handle], CURLINFO_HTTP_CODE); $results[$id] [ ok $httpCode 200 $httpCode 300, http_code $httpCode, ]; } curl_multi_remove_handle($mh, $info[handle]); curl_close($info[handle]); } } while ($active 0); curl_multi_close($mh); return $results; }这段代码的关键在于curl_multi_info_read只处理当前已完成的任务不会阻塞等待所有任务都完成。array_search用来把完成句柄映射回原始的任务 ID这个 ID 对应数据库里的主键。如果某一方的表单字段里包含中文必须保证form_fields是已经通过http_build_query编码过的字符串否则 curl 默认的application/x-www-form-urlencoded可能生成错误格式。3.3 调度参数并发数与频率控制9600 条任务不能一次性全部放入内存需要分批调度。以下是我常用的参数表参数建议值说明max_concurrent10每个并发包里的任务数min_interval_per_domain15 秒同一域名两次提交的最小间隔retry_times2只对超时和 5xx 响应码重试timeout30 秒超过该时间放弃并标记失败batch_size100从数据库读取的任务数量max_concurrent不是越大越好目标服务器通常会根据 IP 或 User-Agent 做限流并发超过 15 容易触发防火墙。min_interval_per_domain是控制摩擦的手段比如某个域名有 12 条待发外链脚本必须在一个任务完成后至少等待 15 秒再发下一个。批次内任务按域名分组用 PHP 的usort把相同域名的任务排在一起。3.4 失败重试与状态回写提交完成后结果要立刻回写到数据库用于判断下一次该发哪些任务。失败重试逻辑如下$db new PDO(mysql:hostlocalhost;dbnameseo;charsetutf8mb4, $user, $pass); $update $db-prepare( UPDATE link_source SET status :status, fail_count fail_count :inc, next_submit_at DATE_ADD(NOW(), INTERVAL :delay HOUR), last_submitted_at NOW() WHERE id :id ); foreach ($results as $id $res) { if ($res[ok]) { $update-execute([:status 1, :inc 0, :delay 24, :id $id]); } else { $update-execute([:status 2, :inc 1, :delay 2, :id $id]); } }这里的状态 1 表示成功状态 2 表示失败。失败的链接会设置next_submit_at为两小时后成功链接设置为一整天以后。fail_count累计超过 3 次的链接会从status IN (0, 2)的筛选里消失避免对同一失效源反复浪费请求。4. 参数调优与排错从日志里判断是外链站拒了还是脚本发错了4.1 常见 curl 错误码和 HTTP 状态码的应对方向跑完一批次后日志里会同时出现 curl 错误码和 HTTP 状态码。先区分它们curl 错误码是底层网络层的异常HTTP 状态码是目标服务器业务逻辑的反应。下表是我排错时的参考错误码/状态码指代含义处理方向28连接超时延长 timeout 至 60 秒或者放到低峰时段跑35SSL 握手失败检查目标站证书链临时关闭证书校验不建议47重定向过多把 FOLLOWLOCATION 关闭单独检查跳转链403服务端明确拒绝检查 UA、Referer、请求频率503服务暂不可用不重试延后 4 小时再跑404提交入口已失效将status置为 3停止后续任务28错误在外链提交里很常见因为很多外链站点响应慢或者表单提交后要做过滤检查。如果 30 秒内没返回不确定是成功还是失败这时不要立刻重试而是把任务状态暂时标记为“待确认”等人工看一眼对方服务器反馈。4.2 处理 403/503调整 UA、Referer 与请求频率403 多数不是请求头不够标准而是频率或指纹问题。我一般会把 User-Agent 改为一种常见浏览器的完整 UI 字符串而不是默认的 PHP cURL 字符串同时设置CURLOPT_REFERER为表单页面同域名的一个正常页面地址。代码示例如下curl_setopt_array($ch, [ CURLOPT_USERAGENT Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 . (KHTML, like Gecko) Chrome/120.0 Safari/537.36, CURLOPT_REFERER https://example.com/contact, CURLOPT_HTTPHEADER [ Accept-Language: zh-CN,zh;q0.9, Cache-Control: no-cache, ], ]);CURLOPT_REFERER不能直接设为对方首页要设成表单页面所在栏目的一个普通页面否则很容易暴露这个请求来自站外脚本。503 则是目标站负载高有可能是它在按 IP 做限流最好的方案就是立刻停止当前域名下还没发送的任务把next_submit_at推迟到 6 小时以后。4.3 遇到验证码或动态字段跳过并标记不要写打码逻辑很多外链站会在表单里插入验证码、时间戳字段或极验验证。这类任务不适合强行处理因为每写一个验证码破解逻辑就多一个被封的风险。合理的做法是检测响应体里的特征关键词识别后直接标记为“需要人工”停止该域名下剩余任务。function is_captcha_page(string $html): bool { return stripos($html, captcha) ! false || stripos($html, verify) ! false || stripos($html, 验证) ! false || stripos($html, slide) ! false; }这个函数的参数就是目标站返回的 HTML 内容。如果命中会调用更新 SQL 把该来源站的status改成 3等后续人工在浏览器里看看是纯粹的验证码还是需要邮件激活。对于工具源码来说自动化提交的真正作用是处理那些能一次成功的正向表单遇到动态验证就及时止损而不是浪费整个批次的时间去重试。5. 验证外链真实性检查 nofollow、收录状态与链接存续5.1 用 Python 脚本检查目标页面是否保留我们的链接外链提交成功不等于外链真正挂在页面上很多目录站会延迟三天到一周才审核通过。所以我会在提交后第七天跑一遍验证脚本确认链接是否出现在目标页面上以及是否带relnofollow。下面是验证脚本的核心函数import requests from bs4 import BeautifulSoup def check_backlink(page_url, tracked_url): resp requests.get( page_url, timeout25, headers{User-Agent: Mozilla/5.0} ) if resp.status_code ! 200: return {status: link_page_unreachable, found: False, nofollow: None} soup BeautifulSoup(resp.text, html.parser) for a in soup.find_all(a, hrefTrue): if tracked_url in a[href]: rel a.get(rel, []) return { status: found, found: True, nofollow: nofollow in rel, } return {status: missing, found: False, nofollow: None}这个脚本对每个外链目标页面发起一次请求所以不能直接在 PHP 提交引擎里复用因为 PHP 进程会长时间占用。Python 的优势是BeautifulSoup解析 HTML 更方便而且单独跑脚本更容易控制节奏不会影响正在提交的 MySQL 连接。验证结果会更新到link_source表里新增一列verified_at用来区分“已提交”和“已收录”。5.2 把验证结果回填并安排二次提交验证脚本跑完后将found True的任务标记为成功不用再处理。将missing的任务回填为待发送但必须把min_interval_hours调整为 72避免连续多天重复骚扰同一来源。如果页面返回 404 或已删除直接将该来源站标记为失效。这样经过两轮“提交-验证”循环后最终保留下来的外链才是真的能带来权重的链接。5.3 保存一套长效筛选视图最后我会在数据库中创建一个视图把“可提交的外链”实时算出来CREATE VIEW v_link_pending AS SELECT * FROM link_source WHERE status IN (0, 2) AND fail_count 3 AND noindex 0 AND domain_age_days 365 AND (next_submit_at IS NULL OR next_submit_at NOW()) ORDER BY page_pa DESC;以后脚本只要查询这个视图即可无需再写复杂的筛选条件。这样9600 条外链的整个生命周期就变成了“入库筛选、批次提交、失败重试、定期验证”的闭环后续只需要把新增的外链源导入到link_source表并发调度和验证逻辑都可以运行很长时间不用改动。本文还有配套的精品资源点击获取
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻