如何用PHP编写代码巧妙抓取特定小说网站内容?

更新于
2026-08-15 03:01:21
6阅读来源:SEO教程
  • 内容介绍
  • 文章标签
  • 相关推荐

在实际项目中。很多开发者都会遇到以下痛点:

  • 抓取速度慢,频繁超时导致使用者体验下降。
  • 网站频繁更改页面结构。正则表达式失效,需要频繁维护代码。
  • IP 被封或被网站检测到爬虫行为,导致抓取中断。
  • 中文乱码、字符集不统一,导致采集的章节内容出现乱码。
  • 采集后数据保存不规范,后期检索和展示困难。

一、整体思路概览

如何用PHP编写代码巧妙抓取特定小说网站内容?

二、准备工作

1. PHP 环境要求

  • PHP>= 7.4
  • 已开启 allow_url_fopen或安装 cURL
  • (或直接使用原生 DOMDocument/XPath)
  • Mysql/MariaDB或本地文件程序

2. 常用工具库安装

# 使用 Composer 安装 phpQuery
composer require electrolinux/phpquery
# 若想使用 Guzzle 替代 cURL,可额外安装
composer require guzzlehttp/guzzle

3、获取页面内容的四种方式及对比

方式优点缺点 / 注意点
file_get_contents代码最简洁;怎么说呢,无需额外 不支持复杂请求头;对大文件易超时,
fopen/fread可自定义流上下文,灵活控制超时。PHP 配置需要允许 URL fopen;一样受限于单线程,
CURL(curl_exec) 支持自定义 Header、Cookie、代理;性能最佳,LAMP 环境需安装 curl 代码略显冗长。
Guzzle HTTP 客户端 Psr-7 标准;处理友好,支持并发请求。Larger dependency;对新手有学习成本,

CURL 示例

$url = 'https://www.example.com/book/12345.html';$ch = curl_init;curl_setopt_array($ch,,]);$html = curl_exec;if ) {
// 记录日志,便于后期排查
error_log);}
curl_close;>

四、解析页面结构

a) 使用 phpQuery 简化 DOM 操作

// 引入 phpQuery 自动加载文件
require_once __DIR__.'/vendor/autoload.php';use phpQuery;// 将获取到的 HTML 转为 phpQuery 对象
$doc = phpQuery::newDocumentHTML;// 示例:获取章节标题 & 内容
$title = $doc->find->text;$content = $doc->find->html;// 去除多余的换行与空格
$content = preg_replace;>

b) 原生 DOMDocument + XPath

$dom = new DOMDocument;话说回来,// 抑制因非法 HTML 导致的警告
libxml_use_internal_errors;$dom->loadHTML);libxml_clear_errors;$xpath = new DOMXPath;// 获取章节标题
$titleNode = $xpath->query;$title = $titleNode->length?trim->nodeValue) : '未知标题';// 获取正文内容
$contentNode = $xpath->query;$contentHtml = '';foreach {
$contentHtml .= $dom->saveHTML;}
>

五、正则匹配细节

虽然推荐使用 DOM 解析,但在某些仅有纯文本的页面仍需要正则抽取。下面给出常见的章节标题和正文匹配模式:

// 匹配类似 “第12章 奇怪的旅程”
$chapterPattern = '/第\s*\s*章\s*\s*/u';// 匹配正文块:去除广告或脚本标签
$contentPattern = '/
<\/div>/si';>

TIPS:Curl 抓取后若出现乱码,可在正则前先统一字符集:

$html = mb_convert_encoding;>

六、数据持久化方案

a) 写入本地 TXT 文件

$filename = __DIR__.'/novel/'.$title.'.txt';file_put_contents;echo "已写入文件:{$filename}";>

b) 保存至 MySQL 数据库


// PDO 示例,确保使用预处理防止注入
$pdo = new PDO('mysql:host=127.0.0.1;dbname=novel;charset=utf8mb4'。'root','password',);$sql = "INSERT INTO chapters VALUES )
ON DUPLICATE KEY UPDATE content=:content";$stmt = $pdo->prepare;$stmt->execute();>

七、定时任务实现自动更新

TIPS:Cron 是最常用且可靠的方式。怎么说呢,若服务器不支持 Cron。可考虑 Laravel Scheduler 或 Symfony Console 定时执行。

# 在 Linux 主机上编辑 crontab -e

30 2 * * * /usr/bin/php /var/www/html/novelcrawler.php>> /var/log/novelcrawler.log 2>&1

php // novel_crawler.php 简易入口文件

requireonce DIR.'/vendor/autoload.php';requireonce DIR.'/crawl_functions.php';// 包含所有业务函数

// 假设已有书籍列表存于 DB 中,需要遍历每本书进行增量更新 $books = getAllBooks;// 返回数组,... ]

foreach { try { updateBookChapters;} catch { error_log);} } >

八、防反爬技巧

  • User-Agent 随机化: 每次请求随机挑选一个常见浏览器 UA。
  • X-Forwarded-For & Proxy IP 池: 使用国内外多个代理 IP,降低单一 IP 的访问频率。
  • SLEEP 与随机间隔: 在每次请求之间加入 ,避免触发频率阈值。
  • Caching 本地 HTML: 同一章节多次访问时直接读取缓存,可显著降低请求次数。
  • DDoS 防护规避: 如发现返回验证码或 Cloudflare 挑战页。可改为使用 Selenium/Puppeteer 等无头浏览器,但这会增加资源开销,仅在必要时采用。

九、完整示例代码汇总


iniset;errorreporting;

require_once DIR.'/vendor/autoload.php';use phpQuery;

// ---------- 配置 ---------- $baseUrl = 'https://www.biquge.com.cn/book/12345/';$startPageUrl = $baseUrl . 'index.html';话说回来,// 起始目录页

$userAgents =;

// ---------- 数据库 ---------- $pdo = new PDO('mysql:host=127.0.0.1;dbname=novel;charset=utf8mb4'。'root','password',);

// ---------- 工具函数 ---------- function curlGet: string{ static $idx=0;$ch=curlinit;curlsetoptarray($ch。CURLOPTHTTPHEADER=>,]);$data=curlexec;if){ throw new RuntimeException);} curlclose;// 防止被封:随机延迟 500~1500ms usleep);怎么说呢,return mbconvertencoding;}

function fetchChapterList: array{ // 用 phpQuery 抽取章节链接与标题 phpQuery::newDocumentHTML;$list=,foreach as $a){ /** @var DOMElement $a */ $href=pq->attr;if{continue,} // 相对方法转绝对方法 if!==0){ global $baseUrl;$href=$baseUrl.ltrim;} $list=,} return $list;}

function parseChapter: string{ // 使用 XPath 更精准定位正文区域,兼容不同站点结构 libxmluseinternalerrors;$dom=new DOMDocument;@$dom->loadHTML);libxmlclear_errors;

// 常见正文容器 ID / CLASS 列表,可自行 : $candidates=;

foreach { if===0){ // ID 查询 @$node=$dom->getElementById);if{ break,说起来,} }else{ // Class 查询 @$xpath=new DOMXPath;@$nodes=$xpath->query . "')]");if { @$node=$nodes->item;break,} }

php } if){ throw new RuntimeException;} $contentHtml=$node->ownerDocument->saveHTML;

// 清理广告脚本等噪声: $content=pregreplace*?#is','',$contentHtml);$content=pregreplace;// 转换成纯文本并去除多余空行: $content=striptags));$content=pregreplace;

return trim;}

// ---------- 主流程 ---------- try{ // 第一步先:获取目录页并抽取所有章节链接 $catalogHtml=curlGet;$chapters=fetchChapterList;

foreach{ echo "正在处理第".."章:{$chapter} ";$html=curlGet;$text=parseChapter;

// 保存至数据库 $stmt=$pdo->prepare( "INSERT INTO chapters VALUES) ON DUPLICATE KEY UPDATE content=:cont" );$stmt->execute(,':url'   => $chapter。':cont' => $text,]);

// 同步写入本地 txt $filePath=DIR.'/output/'.pregreplace.'.txt';fileput_contents;不过,}

echo "全部完成!",

}catch{ error_log);} >

如何用PHP编写代码巧妙抓取特定小说网站内容?

十、常见问题 FAQ

  • A: "抓取到的章节全是空白" B: 检查是否因为页面采用了iframe 加载正文或 JS 动态渲染”。此类情况只能使用无头浏览器,或者寻找接口返回纯文本 JSON 的 Ajax 地址再抓取。
  • A: "出现中文乱码" B: 先通过 `mb_detect_encoding` 判断原始字符集,再统一转成 UTF‑8;在 MySQL 表字段设置为 `utf8mb4` 编码。怎么说呢,
  • A: "IP 被目标站点封禁" B: ① 引入代理池并轮询;② 设置合理的请求间隔与随机 User‑Agent;③ 如仍被拦截,可考虑切换目标站点或申请官方 API 授权。
  • A: "正则匹配不到章节标题" B: 优先使用 DOM/XPath。因为它们不依赖具体字符模式,只要结构不变就能稳定工作。如果必须用正则,请先打印 `$html` 并手动定位关键标记。再调试 `preg_match_all` 的模式修饰符 `u` 与 `s`。
  • A: "Cron 没有按计划执行" B: 确认程序时间与时区一致;在命令行手动运行一次脚本看是否报错;将日志输出重定向至文件以便排查,如 `/var/log/novel_crawler.log`。

标签:代码

在实际项目中。很多开发者都会遇到以下痛点:

  • 抓取速度慢,频繁超时导致使用者体验下降。
  • 网站频繁更改页面结构。正则表达式失效,需要频繁维护代码。
  • IP 被封或被网站检测到爬虫行为,导致抓取中断。
  • 中文乱码、字符集不统一,导致采集的章节内容出现乱码。
  • 采集后数据保存不规范,后期检索和展示困难。

一、整体思路概览

如何用PHP编写代码巧妙抓取特定小说网站内容?

二、准备工作

1. PHP 环境要求

  • PHP>= 7.4
  • 已开启 allow_url_fopen或安装 cURL
  • (或直接使用原生 DOMDocument/XPath)
  • Mysql/MariaDB或本地文件程序

2. 常用工具库安装

# 使用 Composer 安装 phpQuery
composer require electrolinux/phpquery
# 若想使用 Guzzle 替代 cURL,可额外安装
composer require guzzlehttp/guzzle

3、获取页面内容的四种方式及对比

方式优点缺点 / 注意点
file_get_contents代码最简洁;怎么说呢,无需额外 不支持复杂请求头;对大文件易超时,
fopen/fread可自定义流上下文,灵活控制超时。PHP 配置需要允许 URL fopen;一样受限于单线程,
CURL(curl_exec) 支持自定义 Header、Cookie、代理;性能最佳,LAMP 环境需安装 curl 代码略显冗长。
Guzzle HTTP 客户端 Psr-7 标准;处理友好,支持并发请求。Larger dependency;对新手有学习成本,

CURL 示例

$url = 'https://www.example.com/book/12345.html';$ch = curl_init;curl_setopt_array($ch,,]);$html = curl_exec;if ) {
// 记录日志,便于后期排查
error_log);}
curl_close;>

四、解析页面结构

a) 使用 phpQuery 简化 DOM 操作

// 引入 phpQuery 自动加载文件
require_once __DIR__.'/vendor/autoload.php';use phpQuery;// 将获取到的 HTML 转为 phpQuery 对象
$doc = phpQuery::newDocumentHTML;// 示例:获取章节标题 & 内容
$title = $doc->find->text;$content = $doc->find->html;// 去除多余的换行与空格
$content = preg_replace;>

b) 原生 DOMDocument + XPath

$dom = new DOMDocument;话说回来,// 抑制因非法 HTML 导致的警告
libxml_use_internal_errors;$dom->loadHTML);libxml_clear_errors;$xpath = new DOMXPath;// 获取章节标题
$titleNode = $xpath->query;$title = $titleNode->length?trim->nodeValue) : '未知标题';// 获取正文内容
$contentNode = $xpath->query;$contentHtml = '';foreach {
$contentHtml .= $dom->saveHTML;}
>

五、正则匹配细节

虽然推荐使用 DOM 解析,但在某些仅有纯文本的页面仍需要正则抽取。下面给出常见的章节标题和正文匹配模式:

// 匹配类似 “第12章 奇怪的旅程”
$chapterPattern = '/第\s*\s*章\s*\s*/u';// 匹配正文块:去除广告或脚本标签
$contentPattern = '/
<\/div>/si';>

TIPS:Curl 抓取后若出现乱码,可在正则前先统一字符集:

$html = mb_convert_encoding;>

六、数据持久化方案

a) 写入本地 TXT 文件

$filename = __DIR__.'/novel/'.$title.'.txt';file_put_contents;echo "已写入文件:{$filename}";>

b) 保存至 MySQL 数据库


// PDO 示例,确保使用预处理防止注入
$pdo = new PDO('mysql:host=127.0.0.1;dbname=novel;charset=utf8mb4'。'root','password',);$sql = "INSERT INTO chapters VALUES )
ON DUPLICATE KEY UPDATE content=:content";$stmt = $pdo->prepare;$stmt->execute();>

七、定时任务实现自动更新

TIPS:Cron 是最常用且可靠的方式。怎么说呢,若服务器不支持 Cron。可考虑 Laravel Scheduler 或 Symfony Console 定时执行。

# 在 Linux 主机上编辑 crontab -e

30 2 * * * /usr/bin/php /var/www/html/novelcrawler.php>> /var/log/novelcrawler.log 2>&1

php // novel_crawler.php 简易入口文件

requireonce DIR.'/vendor/autoload.php';requireonce DIR.'/crawl_functions.php';// 包含所有业务函数

// 假设已有书籍列表存于 DB 中,需要遍历每本书进行增量更新 $books = getAllBooks;// 返回数组,... ]

foreach { try { updateBookChapters;} catch { error_log);} } >

八、防反爬技巧

  • User-Agent 随机化: 每次请求随机挑选一个常见浏览器 UA。
  • X-Forwarded-For & Proxy IP 池: 使用国内外多个代理 IP,降低单一 IP 的访问频率。
  • SLEEP 与随机间隔: 在每次请求之间加入 ,避免触发频率阈值。
  • Caching 本地 HTML: 同一章节多次访问时直接读取缓存,可显著降低请求次数。
  • DDoS 防护规避: 如发现返回验证码或 Cloudflare 挑战页。可改为使用 Selenium/Puppeteer 等无头浏览器,但这会增加资源开销,仅在必要时采用。

九、完整示例代码汇总


iniset;errorreporting;

require_once DIR.'/vendor/autoload.php';use phpQuery;

// ---------- 配置 ---------- $baseUrl = 'https://www.biquge.com.cn/book/12345/';$startPageUrl = $baseUrl . 'index.html';话说回来,// 起始目录页

$userAgents =;

// ---------- 数据库 ---------- $pdo = new PDO('mysql:host=127.0.0.1;dbname=novel;charset=utf8mb4'。'root','password',);

// ---------- 工具函数 ---------- function curlGet: string{ static $idx=0;$ch=curlinit;curlsetoptarray($ch。CURLOPTHTTPHEADER=>,]);$data=curlexec;if){ throw new RuntimeException);} curlclose;// 防止被封:随机延迟 500~1500ms usleep);怎么说呢,return mbconvertencoding;}

function fetchChapterList: array{ // 用 phpQuery 抽取章节链接与标题 phpQuery::newDocumentHTML;$list=,foreach as $a){ /** @var DOMElement $a */ $href=pq->attr;if{continue,} // 相对方法转绝对方法 if!==0){ global $baseUrl;$href=$baseUrl.ltrim;} $list=,} return $list;}

function parseChapter: string{ // 使用 XPath 更精准定位正文区域,兼容不同站点结构 libxmluseinternalerrors;$dom=new DOMDocument;@$dom->loadHTML);libxmlclear_errors;

// 常见正文容器 ID / CLASS 列表,可自行 : $candidates=;

foreach { if===0){ // ID 查询 @$node=$dom->getElementById);if{ break,说起来,} }else{ // Class 查询 @$xpath=new DOMXPath;@$nodes=$xpath->query . "')]");if { @$node=$nodes->item;break,} }

php } if){ throw new RuntimeException;} $contentHtml=$node->ownerDocument->saveHTML;

// 清理广告脚本等噪声: $content=pregreplace*?#is','',$contentHtml);$content=pregreplace;// 转换成纯文本并去除多余空行: $content=striptags));$content=pregreplace;

return trim;}

// ---------- 主流程 ---------- try{ // 第一步先:获取目录页并抽取所有章节链接 $catalogHtml=curlGet;$chapters=fetchChapterList;

foreach{ echo "正在处理第".."章:{$chapter} ";$html=curlGet;$text=parseChapter;

// 保存至数据库 $stmt=$pdo->prepare( "INSERT INTO chapters VALUES) ON DUPLICATE KEY UPDATE content=:cont" );$stmt->execute(,':url'   => $chapter。':cont' => $text,]);

// 同步写入本地 txt $filePath=DIR.'/output/'.pregreplace.'.txt';fileput_contents;不过,}

echo "全部完成!",

}catch{ error_log);} >

如何用PHP编写代码巧妙抓取特定小说网站内容?

十、常见问题 FAQ

  • A: "抓取到的章节全是空白" B: 检查是否因为页面采用了iframe 加载正文或 JS 动态渲染”。此类情况只能使用无头浏览器,或者寻找接口返回纯文本 JSON 的 Ajax 地址再抓取。
  • A: "出现中文乱码" B: 先通过 `mb_detect_encoding` 判断原始字符集,再统一转成 UTF‑8;在 MySQL 表字段设置为 `utf8mb4` 编码。怎么说呢,
  • A: "IP 被目标站点封禁" B: ① 引入代理池并轮询;② 设置合理的请求间隔与随机 User‑Agent;③ 如仍被拦截,可考虑切换目标站点或申请官方 API 授权。
  • A: "正则匹配不到章节标题" B: 优先使用 DOM/XPath。因为它们不依赖具体字符模式,只要结构不变就能稳定工作。如果必须用正则,请先打印 `$html` 并手动定位关键标记。再调试 `preg_match_all` 的模式修饰符 `u` 与 `s`。
  • A: "Cron 没有按计划执行" B: 确认程序时间与时区一致;在命令行手动运行一次脚本看是否报错;将日志输出重定向至文件以便排查,如 `/var/log/novel_crawler.log`。

标签:代码