I'm Sham
一个在通往码农道路上走走停停的行政文员

通过JS + PHP实现简易小说采集

先申明下,这个只是用来作为采集的一个样本,请大家还是尊重知识产权,看正版的书籍。

一、简要说明:

主要用到: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文件夹内了

采集中的前端页面效果如图

再次申明下,这个只是一个思路,只用于学习,再次希望大家尊重知识产权

赞(0) 赏杯咖啡!
未经允许不得转载:Sham@双目瞿 » 通过JS + PHP实现简易小说采集

评论 抢沙发

评论前必须登录!

 

如果你觉得文章好,请赏1杯速溶咖啡给Sham吧!

微信扫一扫打赏