PHP抓取及分析网页的方法详解
前言用 PHP 抓网页这件事本身并不难发一个 HTTP 请求拿到 HTML再从 HTML 里把需要的字段抠出来。难的是把这件事做对——难点几乎全在细节上编码不对导致中文变乱码、XPath 写得过于绝对导致页面改版就失效、正则用错导致提取结果错一半、频率没控制导致被封 IP。先把合规的底线说在前面因为这是绕不过去的第一步抓取别人的网站之前要看该站的robots.txt有没有禁止你访问的路径要看服务条款有没有禁止自动化采集不要绕过任何技术保护措施验证码、登录墙、频率限制。同时抓下来的内容如果要再次发布会涉及著作权问题如果内容里含个人信息还要受《个人信息保护法》约束。本文讲的是技术方法但方法的使用边界由这几条决定。另外澄清一个常见的思路误区能用接口就不要抓页面。如果目标站点的数据是通过 JSON 接口返回的直接请求那个接口、解析 JSON比解析 HTML 稳定得多——页面结构天天变接口字段相对稳定而且返回的数据本来就是结构化的。很多抓取难的问题本质上是因为找错了入口。本文分三步讲怎么把页面抓回来、怎么把内容解析出来、怎么把数据洗干净落库。一、抓取阶段把请求发对抓取只有一个核心动作发 HTTP 请求。但这里有几个必须处理好的点。第一是编码声明。老站点常见 GBK/GB2312新站点是 UTF-8。拿到响应后不能直接按 UTF-8 解析要先看响应头里的Content-Type再从 HTML 的meta标签里读charset两者都不明确时才用合法性校验兜底。第二是限速与身份标识。请求之间留间隔User-Agent里写清楚自己是谁。这不只是礼貌——被对方识别为恶意爬虫之后你会失去整个数据源。第三是超时与重试退避。建连超时和总超时都要设失败要按指数退避重试并设上限。?php // 适用于 PHP 8.0需启用 curl 与 mbstring 扩展/*** 抓一个页面返回 UTF-8 编码的 HTML 字符串。** param arraystring,string $headers 额外请求头*/function fetchPage(string $url, array $headers [], float $minInterval 1.0): string{static $lastAt 0.0; // 进程内限速用$wait $minInterval - (microtime(true) - $lastAt);if ($wait 0) {usleep((int) ($wait * 1_000_000));}$lastAt microtime(true);$ch curl_init($url);curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER true,CURLOPT_ENCODING , // 自动协商并解压 gzipCURLOPT_USERAGENT MyAppCrawler/1.0 (contact: opsexample.invalid),CURLOPT_CONNECTTIMEOUT 3,CURLOPT_TIMEOUT 15,CURLOPT_FOLLOWLOCATION false, // 需要跟随时要先校验目标地址防 SSRFCURLOPT_HTTPHEADER $headers,]);$body curl_exec($ch);$code curl_getinfo($ch, CURLINFO_RESPONSE_CODE);$ctype (string) curl_getinfo($ch, CURLINFO_CONTENT_TYPE);$err curl_error($ch);curl_close($ch);if ($body false) {throw new RuntimeException(抓取失败 . $err);}if ($code 400) {throw new RuntimeException(目标返回 HTTP {$code});}return toUtf8((string) $body, detectCharset($ctype, (string) $body));}/** 从响应头的 charset 或 meta 标签里取出编码名取不到就返回 null */function detectCharset(string $contentType, string $html): ?string{if (preg_match(/charset\s*\s*?([\w-])?/i, $contentType, $m)) {return $m[1];}if (preg_match(/meta[^]charset\s*\s*[\]?([\w-])/i, substr($html, 0, 2048), $m)) {return $m[1];}return null;}/** 统一转成 UTF-8无声明时先按 UTF-8 校验不合法再按 GBK 处理 */function toUtf8(string $bytes, ?string $charset): string{if ($charset ! null !in_array(strtoupper($charset), [UTF-8, UTF8], true)) {return mb_convert_encoding($bytes, UTF-8, $charset);}if (mb_check_encoding($bytes, UTF-8)) {return $bytes;}return mb_convert_encoding($bytes, UTF-8, GBK);}mb_convert_encoding()的参数顺序是字符串、目标编码、源编码写反了不会报错只会得到乱码一定要看清。另外不要把字符集探测做得太聪明优先信任响应头和meta声明两者矛盾时以响应头为准HTTP 头优先级高于 HTML 内的声明兜底才用校验。二、解析阶段DOM 与 XPath 是首选把 HTML 解析成结构首选DOMDocument加DOMXPath。它是基于 libxml 的真正的 HTML 解析器能容忍不规范的标签闭合也能处理嵌套结构比正则可靠得多。有一个编码细节必须先处理DOMDocument::loadHTML()在遇到 UTF-8 内容时如果没有任何编码声明会按 Latin-1 解释导致中文变成乱码。经典解法是在 HTML 前面拼一段 XML 声明来告知编码?php // 适用于 PHP 8.0/*** 用 XPath 从页面里抽取一个表格返回键值对数组。* 选择器要按目标页面的实际结构调整。*/function extractTable(string $html, string $tableXPath //table[1]): array{$prev libxml_use_internal_errors(true); // 屏蔽不规范的 HTML 警告$doc new DOMDocument();// 拼上 XML 声明让 libxml 明确知道这是 UTF-8$doc-loadHTML(?xml encodingUTF-8 . $html, LIBXML_NOWARNING | LIBXML_NOERROR);$xpath new DOMXPath($doc);$result [];$tables $xpath-query($tableXPath);if ($tables false || $tables-length 0) {libxml_clear_errors();libxml_use_internal_errors($prev);return $result;}foreach ($xpath-query(.//tr, $tables-item(0)) as $tr) {$cells $xpath-query(./td|./th, $tr);if ($cells-length 2) {$key trim($cells-item(0)-textContent);$val trim($cells-item(1)-textContent);if ($key ! ) {$result[$key] preg_replace(/\s/u, , $val) ?? $val;}}}libxml_clear_errors();libxml_use_internal_errors($prev); // 一定要还原否则影响后续解析return $result;}几个值得记住的用法DOMNodeList::item()返回DOMNode或null嵌套取值前要确认上一层的length取文字用-textContent属性取属性值用-getAttribute(href)libxml_use_internal_errors()用完必须还原因为它改的是全局状态不还原会让同一个请求里后续的 XML 操作行为变化。XPath 的选择器写法上有一条经验尽量用相对路径和属性匹配少用/html/body/div[3]/div[2]/table这种绝对路径。绝对路径对页面结构变化零容忍对方在中间插一个div你的抓取就全部失效。相对路径配合contains(class, xxx)这类条件容错性高得多。顺便说一句PHP 8.4 提供了基于 HTML5 规范的新 DOM APIDom\HTMLDocument等对真实页面的解析比老的DOMDocument::loadHTML()更符合浏览器行为。如果你的项目能升到 8.4可以优先用它低版本上仍然沿用上面这套经典做法。如果目标返回的是 XML 或 RSSsimplexml_load_string()会更顺手它把节点映射成对象属性和子节点用-和[属性名]访问代码比 DOM 短很多。但要注意它同样受编码声明影响并且遇到命名空间时要显式处理。三、解析阶段JSON 接口与正则的适用边界JSON 接口永远优于 HTML 解析。现代网站的前端数据几乎都走 JSON 接口用开发者工具看一眼网络面板就能找到。接 JSON 时用json_decode($json, true, 512, JSON_THROW_ON_ERROR)第二个参数true让它返回关联数组默认返回stdClass对象第四个参数让解析失败时抛异常而不是安静地返回null?php // 适用于 PHP 8.0try {$data json_decode($json, true, 512, JSON_THROW_ON_ERROR);} catch (JsonException $e) {throw new RuntimeException(接口返回的不是合法 JSON . $e-getMessage());}// 逐层取值前先确认存在不要假设结构一定完整$items $data[data][list] ?? [];foreach ($items as $item) {printf(%s\t%s\n, $item[title] ?? , $item[url] ?? );}正则什么时候用只在从一整段文本里抠出某个固定形态的值时用比如从页面里取meta描述、取一段 JavaScript 变量里的 JSON 字符串、取某个特定格式的编号。它不适合解析嵌套的 HTML 结构——因为 HTML 不是正则语言没法用正则表达配对的标签。一旦你用正则去匹配某个 div 之间的内容就已经埋下了下次改版就崩的隐患。?php // 适用于 PHP 8.0$html meta namedescription content这是一个测试页面;// 从固定形态的单值里取值可以接受if (preg_match(/meta\snamedescription\scontent([^]*)/i, $html, $m)) {echo 页面描述, html_entity_decode($m[1], ENT_QUOTES | ENT_HTML5, UTF-8), PHP_EOL;}写正则时有三个坑要避开[^]*这类否定字符类比.*安全得多后者会贪婪地跨过多个标签正则里的中文要用/u修饰符否则按字节匹配会出问题抓到的文本往往还带着 HTML 实体要用html_entity_decode()转回来并且记住第二个参数要显式传默认值不处理单引号。四、数据清洗与落库抓下来、解析出来的数据通常还很脏有多余空白、有全角半角混用、有重复记录、有缺失字段。落库前必须清洗和校验。清洗的重点是空白归一化和字段校验。preg_replace(/\s/u, , $text)把连续空白压成一个空格trim()去掉首尾数字字段要转成真正的数字类型不要原样存字符串。校验的重点是关键字段非空和格式合法不通过的一律丢弃并记日志不要先存下来再说——脏数据一旦落库清理成本远高于当时丢弃。入库必须用参数化查询。抓来的内容属于完全不可信的外部输入拼接 SQL 会直接导致注入漏洞。用 PDO 预处理加参数绑定?php // 适用于 PHP 8.0需启用 pdo_mysql 扩展$pdo new PDO(mysql:host127.0.0.1;dbnamescrape;charsetutf8mb4, $user, $pass, [PDO::ATTR_ERRMODE PDO::ERRMODE_EXCEPTION,PDO::ATTR_DEFAULT_FETCH_MODE PDO::FETCH_ASSOC,PDO::ATTR_EMULATE_PREPARES false, // 用真正的预处理让类型也正确]);$sql INSERT INTO pages (url_hash, url, title, fetched_at)VALUES (:url_hash, :url, :title, :fetched_at)ON DUPLICATE KEY UPDATE title VALUES(title), fetched_at VALUES(fetched_at);$stmt $pdo-prepare($sql);// 用 URL 的哈希做唯一键实现幂等同一个页面重复抓取不会产生重复行$stmt-execute([:url_hash hash(sha256, $url),:url $url,:title $title,:fetched_at date(Y-m-d H:i:s),]);这里用 URL 的哈希做唯一键是为了实现幂等——抓取任务经常要重跑没有唯一约束的话每次重跑都会产生一批重复数据后续统计全是错的。另外PDO::ATTR_EMULATE_PREPARES false让 PDO 使用数据库端真正的预处理绑定参数的类型会被正确传递也能避免某些边缘的注入手法。出错要记日志但日志里不要落完整的 HTML体积大且可能含个人信息记 URL、状态码、错误信息就够了。常见坑点❌ 直接对抓回来的页面调loadHTML()中文全变乱码✅ 先确定编码并转成 UTF-8用loadHTML(?xml encodingUTF-8 . $html)让 libxml 知道编码。❌ 调了libxml_use_internal_errors(true)不还原✅ 它是全局状态用完必须libxml_use_internal_errors($prev)还原否则影响同一进程里的其他解析。❌ 用正则解析嵌套的 HTML 结构匹配两个 div 之间的内容✅ 用DOMDocument加DOMXPath正则只用于提取固定形态的单值。❌ XPath 写成/html/body/div[3]/table/tr[2]/td[1]这种绝对路径✅ 页面结构一变就全废用相对路径配合属性条件例如contains(class, ...)。❌json_decode()只用两个参数解析失败时安静地返回null✅ 加JSON_THROW_ON_ERROR并用try/catch捕获JsonException让错误显式暴露。❌ 抓取请求之间不留间隔多进程并发去刷同一个站点✅ 单目标并发保持 1请求之间留间隔并在 UA 里表明身份被封 IP 后整个数据源就断了。❌ 把抓来的内容直接拼进 SQL✅ 抓取内容是外部不可信输入必须用 PDO 预处理加参数绑定并设置ATTR_EMULATE_PREPARES false。❌ 抓取任务没有唯一约束重跑一次就多一批重复数据✅ 用 URL 哈希之类的业务唯一键配合ON DUPLICATE KEY UPDATE让重跑是幂等的。总结阶段关键做法常见错误请求设两个超时、自动解压、自报身份的 UA、请求间隔只设一个超时并发轰炸不留间隔编码优先响应头 charset其次 meta兜底做合法性校验直接按 UTF-8 解析 GBK 页面编码转换mb_convert_encoding(字符串, 目标编码, 源编码)目标/源写反静默得到乱码结构解析DOMDocumentDOMXPath用完还原 libxml 设置用正则解析嵌套 HTML数据接口优先找 JSON 接口json_decode加JSON_THROW_ON_ERROR假设返回结构一定完整单值提取正则只用于固定形态字符类用否定形式中文加/u用.*贪婪跨标签清洗空白归一化、类型转换、关键字段校验脏数据先存后清入库PDO 预处理、业务唯一键保证幂等拼接 SQL重跑产生重复数据把网页抓取做稳靠的不是更复杂的技巧而是三件最朴素的事请求发得有礼貌限速、标识、退避解析做得有冗余用 DOM 不用正则XPath 不要绝对路径数据洗得有纪律校验不过就丢入库用参数绑定。最后再强调一次边界能用官方接口就用官方接口能用有授权的数据源就用授权数据源robots.txt、服务条款、以及是否绕过了技术保护措施是判断一件事能不能做的标准技术上的可行性从来不是。

相关新闻

MySQL 5.7.30一键部署实战:从环境准备到主从复制避坑指南

MySQL 5.7.30一键部署实战:从环境准备到主从复制避坑指南

简介:面向Linux运维人员和后端开发者的MySQL 5.7.30一键安装部署包,针对CentOS 7服务器环境进行优化。该版本在InnoDB存储引擎、JSON数据类型支持和查询优化器方面均有改善,适合需要稳定数据读写与半结构化数据处理的业务场景。用户只需解压后…

2026/10/9 23:56:35 阅读更多 →
大模型智能识别验证码:LoRA微调与部署实战指南

大模型智能识别验证码:LoRA微调与部署实战指南

简介:这是一份面向网络安全爱好者的验证码智能识别实现资源,聚焦卷积神经网络在图像验证码破解场景的应用。资源以C#工程为主体,提供从图像预处理、模型训练到字符预测的完整代码流程,并配有可执行程序与效果演示动图,…

2026/10/9 23:56:35 阅读更多 →
archify:可交互式代码驱动架构图生成工具

archify:可交互式代码驱动架构图生成工具

1. 这不是又一个“画图工具”,而是架构师的实时协作者我第一次在 GitHub Trending 页面看到archify的时候,没点进去——因为标题里带“AI代理”四个字,我本能地划走了。过去两年,我见过太多挂着“AI自动生成架构图”的项目&#x…

2026/10/9 23:55:35 阅读更多 →

最新新闻

杨幂×Prada:顶奢代言背后的选人逻辑与商业价值拆解

杨幂×Prada:顶奢代言背后的选人逻辑与商业价值拆解

关于杨幂成为Prada代言人这件事,圈内讨论热度一直没停过。不管是时装周前排看秀的镜头,还是广告大片释放出的状态,都让“顶奢代言”这个概念在当下的内娱市场里有了更具体的参照物。借着这个热点,我想认真聊聊这背后的逻辑&#x…

2026/10/10 5:46:40 阅读更多 →
Unison 语言中 Term 声明禁止携带哈希限定名:语法规则、解析器实现与转写测试验证

Unison 语言中 Term 声明禁止携带哈希限定名:语法规则、解析器实现与转写测试验证

编程语言编译器语言运行时开发工具 【免费下载链接】unison A friendly programming language from the future 项目地址: https://gitcode.com/gh_mirrors/un/unison 点击查看 免费下载 本文以 Unison 开源仓库中的转写(transcript)测试文档…

2026/10/10 5:46:40 阅读更多 →
PCA9422+STM32F405RG电源管理实战:寄存器配置与调试全解析

PCA9422+STM32F405RG电源管理实战:寄存器配置与调试全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 5:46:40 阅读更多 →
有效信息是博文生成的核心要素

有效信息是博文生成的核心要素

您提供的信息中没有有效的项目标题(当前显示为“无标题”),且相关热搜词和网络搜索内容均为空白。缺少核心输入,我无法生成围绕具体主题、场景和关键词展开的高质量原创博文。《无标题》不是一个可执行的项目主题,强行…

2026/10/10 5:46:40 阅读更多 →
colorlog 6.10.1 使用指南:为 Python 标准库 logging 接入 ANSI 彩色终端输出

colorlog 6.10.1 使用指南:为 Python 标准库 logging 接入 ANSI 彩色终端输出

【免费下载链接】context-hub 项目地址: https://gitcode.com/gh_mirrors/co/context-hub 点击查看 免费下载 导读 colorlog 是一个轻量的 Python 第三方库,它的作用是为 Python 标准库 logging 的处理器(handler)增加 ANSI 颜色…

2026/10/10 5:46:40 阅读更多 →
缩短招聘周期:从人才画像到Offer的11个高效策略

缩短招聘周期:从人才画像到Offer的11个高效策略

招聘周期拉长,用人部门催、候选人等不起、HR夹在中间两头受气——这是过去几年我在各类企业里反复看到的真实场面。尤其遇到急招岗位,从职位发布到人选入职动辄拖上三四十天,错过业务窗口不说,还经常出现“谈好的Offer被对手截胡”…

2026/10/10 5:45:40 阅读更多 →

日新闻

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

1. 从“卫星轨道分类”这个标题说起:为什么值得花时间搞懂第一次接触“卫星轨道分类”这个概念,很多人会觉得它离自己很远——不就是天上的星星怎么转吗?但如果你正在做航天任务规划、遥感数据接收、星座设计,甚至只是准备一场航天…

2026/10/10 0:00:39 阅读更多 →
Spring AOP 核心原理与实战:从概念到日志切面落地

Spring AOP 核心原理与实战:从概念到日志切面落地

1. 从一个真实痛点说起:为什么你的代码里到处都是重复逻辑刚入行那会儿,我写过一个用户管理模块,注册、登录、改密码、注销四个接口。每个接口里都塞了几乎一样的日志打印、参数校验、事务开启和提交。当时觉得没什么,能跑就行。直…

2026/10/10 0:00:40 阅读更多 →
Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

简介:这是一套面向计算机相关专业学生与项目实战学习者的Python数据采集与分析可视化完整项目,以Boss直聘岗位数据为对象,适合用作毕业设计、课程设计或期末大作业。资源包共38个文件,约246KB,以13个py源码文件为核心&…

2026/10/10 0:00:40 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 1:36:08 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 10:11:06 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 5:23:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 6:17:20 阅读更多 →