先申明下,这个只是用来作为采集的一个样本,请大家还是尊重知识产权,看正版的书籍。
一、简要说明:
主要用到:jQuery, PHP
主要思路:
1. 通过js来循环访问本地的php文件,并传输书本网址,编号等;
2. php获取小说站相应页面的内容,存入本地文件夹,并返回结果给js
3. js根据收到的结果来进行下一步处理,如果当前书本已经采集完,则采集下一本
注意点:只能采集指定网址小说,并该网站网页结构固定且简单,否则可能会失败
同时目前只能每次都从第一章开始采集,虽然会跳过已保存过的,但是还需要进一步优化,比如:
通过最新更新的章节,获取到还有多少章需要采集,然后只采集对应的章节比如从书本的首页获取到所有章节对应的链接,而不是单纯的从0开始循环,避免有的章节是跳过某个数字的,导致后面的中断
二、HTML js代码部分(记得要有jQuery文件)
异步循环部分代码来自ai
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="jquery-3.6.0.min.js" charset="utf-8"></script>
<title>实时数据采集与保存</title>
<style>
#output, #output1 {
display: inline-block;
margin-top: 20px;
padding: 10px;
border: 1px solid #ddd;
max-height: 600px;
overflow-y: auto;
width:400px;
}
</style>
</head>
<body>
<h1>实时数据采集与无刷输出</h1>
<div id="output1"><h4>采集中的书名(最底部的为正在采集的)</h4></div>
<div id="output">正在等待数据...</div>
<script>
let url = new URL(window.location.href);
// 使用URLSearchParams获取查询参数,这个主要是用来执行从第几本书开始
let params = new URLSearchParams(url.search);
//api_id是用来指定那本书用,这里是数字
var api_id = params.get('id')
if(api_id !=null && api_id !=""){
//定义书籍网站地址,注意书籍对应的网址需要是有规律的数字,比如从1开始到99999
const api = "https://想要采集的看书站网址/book/"
// 调用函数开始数据采集
collectDataFromApi(api,api_id)
.then(() => {
console.log('数据采集结束');
})
.catch(error => {
console.error('数据采集失败', error);
});
}else{
displayData("未提供书本id");
}
// 定义一个异步函数来获取数据
async function fetchData(api,api_id,idx) {
try {
//通过ajax向后端php文件传输api并获取返回的数据
const response = await $.ajax({
url:'get_book.php',
method:'POST',
data:{'api':api,'api_id':api_id,'idx':idx},
dataType:'JSON',
});
console.log('res',response)
//通过返回的code,输出不同的结果
if(response.code==0){
//这个不知道什么原因,无法获取
displayData(response.msg,"书名")
}
if(response.code==1){
if(response.bookname!=null){
//当返回的数据中包含书名时,在前端输出当前采集的书名(只有第一章的时候才会返回)
displayData(response.bookname,"书名")
}
//输出刚采集的章节名
displayData(response.msg,"章节名");
}
//返回给函数来执行相应操作
return response
} catch (error) {
//输出错误信息
//这里不知道什么原因,第一次获取书籍信息的时候会报错误,第二次循环获取第一章的时候就不会
console.error('错误:', error);
throw error; // 重新抛出错误以便上层调用者可以处理
}
}
// 定义一个异步函数来循环访问 API
async function collectDataFromApi(api,api_id) {
//循环传输章节页码并传给后端php
for (var i=0;i<99999;i++) {
try {
const data = await fetchData(api,api_id,i);
// 处理获取到的数据
//console.log('获取数据成功:', data);
if(data.code ==2){
//当php访问对应页面为空(即没有该页面时)则表示这本书采集完了,采集下一本书
to_next(api,api_id)
return;
}
// 你可以在这里添加其他逻辑,比如存储数据到数据库等
} catch (error) {
// 处理错误,例如记录日志或重试
console.error(`错误,跳转到下一轮`, error);
// 如果需要,可以在这里添加重试逻辑
}
}
}
//这个是重新开始新一本书籍的循环获取
//只适用于书本网页id是有序通过+1的形式
function to_next(api,api_id){
collectDataFromApi(api,parseInt(api_id)+1)
}
//用于在前端网页显示当前采集的结果
function displayData(data,type) {
// 在页面中追加显示采集的数据
if(type=="书名"){
const output = document.getElementById('output1');
const newEntry = document.createElement('div');
newEntry.innerHTML = `正在采集:<strong>`+data+`</strong> `;
output.appendChild(newEntry);
// 保持滚动条在底部,方便查看最新数据
output.scrollTop = output.scrollHeight;
}else{
const output = document.getElementById('output');
const newEntry = document.createElement('div');
newEntry.innerHTML = `<strong>`+data+`</strong> -- 已保存`;
output.appendChild(newEntry);
output.scrollTop = output.scrollHeight;
}
}
</script>
</body>
</html>
三、php部分
其中cut裁切获取内容部分,需要修改成你需要采集的网页内对应的标签字符串
<?php
$api=$_POST['api'];
$api_id = $_POST['api_id'];
$idx =$_POST['idx'];
/**
* 先分析是不是第一页
* 是的话,就先获取书本基础信息,并将数据存入text
*
* 注意:当前cut截取只适用于代码中起止标签和要采集的网页标签对应的,需要根据自己找的网站自定义
**/
if($idx==0){
$content = get($api.$api_id.'/'); //获取书籍首页全部内容
$info = cut($content,'<div class="info">','<div class="link wap_none">'); //从上面的内容中截取介绍部分
$cover = cut($info,'src="','"'); //封面
$book_name = cut($info,'<h1>','</h1>'); //书名
$author = cut($info,'作者:','</span>'); //作者
$status = cut($info,'状态:','</span>'); //状态
$last_update = cut($info,'更新:','</span>'); //最后更新时间
$new_chapter = cut($info,'最新:','</span>'); //最新章节
$intro = cut($info,'<dd>','<span class="noshow"'); //简介
$up_time = date('Y-m-d H:i'); //保存时间,用于记录你采集保存的时间
$book_dir = "books/".$api_id.'_'.$book_name; //书本存放目录
//先创建book_id对应的文件夹,用于存放书内容文件
if(!file_exists($book_dir)){
mkdir($book_dir,0777,true);
}
//下载封面到本地
$localPath = $book_dir.'/cover.jpg';
$imgContent = file_get_contents($cover);
file_put_contents($localPath, $imgContent);
//拼接书籍基本信息,用来存到本地,方便后面获取
$book_desc_info =
"书名:".$book_name.chr(10).
"作者:".$author.chr(10).
"状态:".$status.chr(10).
"最后更新时间:".$last_update.chr(10).
"最新章节:".$new_chapter.chr(10).
"简介:".$intro.chr(10).
"上传时间:".$up_time.chr(10).
"封面:".$cover.chr(10);
//保存到本地,存成txt文件
$book_desc= fopen($book_dir."/book_desc.txt", "w") or die("Unable to open file!");
fwrite($book_desc, $book_desc_info);
fclose($this_chapter_txt);
echo ('{"code":0,"msg":"'.$book_name.'"}');
}else{
/**
* 然后再进行获取章节数据
**/
//拼接上书本id和当前需要获取的章节id
$url = $api.$api_id.'/'.$idx.".html";
//逻辑同上面,获取内容,裁切获取需要的内容
//同样需要根据网页调整裁切的起止标签
$chapter_content = get($url);
if($chapter_content !=""){
$book_name = cut($chapter_content,'<a href="/book/'.$api_id.'/">','</a>');
$book_content = get_content($chapter_content);
$this_chapter = cut($chapter_content,'<h1 class="wap_none">','</h1>');
$this_chapter = str_replace("!",'!',$this_chapter);
$this_chapter = str_replace("'",'"',$this_chapter);
$this_chapter = str_replace("(",'(',$this_chapter);
$this_chapter = str_replace(")",')',$this_chapter);
$this_chapter = str_replace("?",'',$this_chapter);
$this_chapter = str_replace("@",'',$this_chapter);
$book_dir = "books/".$api_id.'_'.$book_name;
//先创建book_id对应的文件夹,用于存放书内容文件
if(!file_exists($book_dir)){
mkdir($book_dir,0777,true);
}
//将章节名存入章节列表
file_put_contents($book_dir."/chapter_list.txt",$idx."._.".$this_chapter.chr(10),FILE_APPEND);
if(!file_exists($book_dir."/".$this_chapter.".txt") || filesize($book_dir."/".$this_chapter.".txt") ==0){
$this_chapter_txt = fopen($book_dir."/".$this_chapter.".txt", "w") ;//or die("Unable to open file!");
if($this_chapter_txt){
//如果创建并打开当前章节的txt文件
fwrite($this_chapter_txt, $book_content);
//关闭该章节文档
fclose($this_chapter_txt);
if($idx==1){
echo '{"code":1,"msg":"'.$this_chapter.'","bookname":"'.$api_id.'_'.$book_name.'"}';
}else{
echo '{"code":1,"msg":"'.$this_chapter.'"}';
}
}else{
file_put_contents($book_dir."/wrong_chapter.txt",$this_chapter.chr(10),FILE_APPEND);
echo '{"code":1,"msg":"未成功保存当前章节:'.$this_chapter.'"}';
}
}else{
if($idx==1){
echo '{"code":1,"msg":"该章节已存在:'.$this_chapter.'","bookname":"'.$api_id.'_'.$book_name.'"}';
}else{
echo '{"code":1,"msg":"该章节已存在:'.$this_chapter.'"}';
}
}
}else{
echo '{"code":2,"msg":"获取章节结束"}';
}
}
//获取文件详细内容
function get_content($content){
//设置截取的开始和结束
$start1 = '<div id="chaptercontent" class="Readarea ReadAjax_content" style="font-size: 20px;">';
$start2 = '<div id="chaptercontent" class="Readarea ReadAjax_content">';
if(strpos($content,$start1)){
$start=$start1;
}elseif(strpos($content,$start2)){
$start=$start2;
}else{
$start = '<div id="chaptercontent">';
}
$end = '<p class="readinline">';
//进行截取
$res = cut($content,$start,$end);
//判断处理双换行,转成数组
if(strpos($res,'<br /><br />')){
$temp_res = explode("<br /><br />",$res);
}elseif(strpos($res,'<br />')){
$temp_res = explode("<br />",$res);
}
//获取数组长度
$rows = count($temp_res);
//判断出包含qu70.cc的值,删除
if(strpos(end($temp_res),'qu70.cc')){
$temp_res = array_pop($temp_res);
}elseif(strpos($temp_res[$rows-2],'qu70.cc')){
unset($temp_res[$rows-2]);
}
//再转成分行的字符串
$res = implode(chr(10),$temp_res);
return ($res);
}
//根据起止位置,裁切获取指定区间内容
function cut($content,$start,$end) {
$r = explode($start, $content);
if (isset($r[1])) {
$r = explode($end, $r[1]);
return $r[0];
}
return '';
}
//Curl get请求
function get($url) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.7 (KHTML, like Gecko) Chrome/20.0.1099.0 Safari/536.7 QQBrowser/6.14.15493.201');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
这样,就会从你指定书籍id开始,不断循环获取,并保存到服务器books文件夹内了
采集中的前端页面效果如图
再次申明下,这个只是一个思路,只用于学习,再次希望大家尊重知识产权
评论前必须登录!
注册