当前位置: 首页 > news >正文

怎么做北京赛网站/怎么创建网站快捷方式

怎么做北京赛网站,怎么创建网站快捷方式,网站建设报价套餐,网站页面设计合同背景 注意: 下文关于小说的所有的地址都为虚假的 网上很多小说只能在线观看,没有下载功能,正好最近在学习node.js,就想着是否能用node写个脚本,批量获取每一章节的内容然后整合成txt并输出 网页结构分析 章节列表结构分析 我的…

背景

注意: 下文关于小说的所有的地址都为虚假的

网上很多小说只能在线观看,没有下载功能,正好最近在学习node.js,就想着是否能用node写个脚本,批量获取每一章节的内容然后整合成txt并输出

网页结构分析

章节列表结构分析

我的请求地址是http://m.biquge.net/book/107056/chapterlist,返回的是html,章节列表内容如下

<div class="bd"><ul class="list" id="listsss"><div data-id="0"><li id="chapter"><a href="http://m.biquge.net/book/107056/49982046.html">序 穿越四顾心茫然</a></li><li id="chapter"><a href="http://m.biquge.net/book/107056/49982047.html">第一话 骄傲无知的现代人</a></li><li id="chapterimg.alicdn.com"><a href="http://m.biquge.net/book/107056/49982048.html">第二话 十里坡剑神</a></li></div><div data-id="1"></div></ul>
</div>

章节详情结构分析

下面的每一章节的最后一页的html地址的内容,

  • 需要递归获取每一页的内容
  • 正常情况下页面内容里面是有“下一页”,但是“下一页”的按钮是个图片,难以作为判断依据
  • 非最后一页的情况下无下一章的按钮,所以可以根据页面内容里面是否有“下一章”的文案来作为递归的条件
    <div class="pager"><a href="49982048_3.html">上一页</a><a href="http://m.biquge.net/book/107056/">简介</a><a href="49982049.html">下一章</a></div><div class="content" id="txt"><dd data-id="6"><p>“叫我的名字?”尹泽愣住,受宠若惊,连忙拒绝,“我知道你和川村小哥很重视我,但这未免太过浮夸,我背不住呀?”</p><p>“……电影的名称是‘你的名字’。明白吗?”新渡诚无语,比划着手势。</p><p>“哦,原来这是标题啊,小弟我愿意陪你上映。”尹泽打着哈哈掩饰尴尬。</p><p>“话说怎么电脑一直开着PS的全屏模式啊?”新渡诚歪着身子,绕过去看,有些皱眉。</p><p>“这不显得更宽敞吗。”尹泽移动两步,挡住导演探究的视线。</p><p>新渡诚思索两秒,视线左右移动,旋即恍然,露出富含深意的迷之微笑,“哎,老师不必紧张,这里又没有外人,都是搞正经艺术的,不用觉得害羞。”</p><p>“这是何意啊?”尹泽不解。</p><p>“还装还装。都是画画的,研究人体还遮遮掩掩,生怕被发现了似的,真不爽利。咋还偷看呢。”新渡诚潇洒一笑,强行伸过去摸住鼠标,“让我瞧瞧,是伯里曼还是金政基呀,又或者是寺田克也——啊啊!这些是什么?!”</p><p>「如章节缺失请退#出#阅#读#模#式」</p></dd></div>

根据上述内容,写一个node脚本,获取每一章节的每一页内容最并整合,最终输出一个txt文件,注意要携带章节名称

编码实战

js

const axios = require("axios");
const cheerio = require("cheerio");
const fs = require("fs").promises;const MAX_DEPTH = 10; // 设定最大递归深度
const CHAPTER_LIST_URL = "http://m.biquge.net/book/107056/chapterlist"; // 列表目录
const DELAY_MS = 1; // 延迟时间
const LIMIT_CONCURRENT_REQUESTS = 5; // 设置并发请求的最大数量// 模拟延迟
function delay() {return new Promise((resolve) => setTimeout(resolve, DELAY_MS));
}async function fetchChapterList(url) {try {const response = await axios.get(url);const $ = cheerio.load(response.data);const chapterLinks = $("#listsss li a").map((_, elem) => ({chapterName: $(elem).text(),url: $(elem).attr("href")})).get();return chapterLinks;} catch (error) {console.error("Error fetching chapter list:", error);return [];}
}async function fetchChapterContentAndNext(chapterUrl,currentChapterName = "未知章节",depth = 0
) {if (depth >= MAX_DEPTH) {console.warn(`\n warn 达到最大递归深度 ${MAX_DEPTH}, 停止抓取后续章节.`);console.warn(`\n 错误章节名称:${currentChapterName}`);console.warn(`\n 错误章节地址:${chapterUrl}`);return "";}try {const response = await axios.get(chapterUrl);const $ = cheerio.load(response.data);// 获取当前章节当前页内容const content = $(".content dd").text();// 页面内是否存在下一章的按钮const withNextButton = !!$('.pager a:contains("下一章")').first().attr("href");const withEndButton = !!$('.pager a:contains("看完了")').first().attr("href");if (withNextButton || withEndButton) {// 下一章的按钮存在,说明当前章节已结束return `${currentChapterName}\n\n${content}`;} else {// 下一章的按钮不存在,继续请求下一页的内容var parts = chapterUrl.split(/(\/[^\/]+)\.html$/);const urlArr = parts[1].split("_");let nextPageKey = urlArr[0];if (urlArr.length > 1) {nextPageKey = `${urlArr[0]}_${Number(urlArr[1]) + 1}`; // 构造下一章URL} else {nextPageKey = `${urlArr[0]}_2`;}const nextPageLink = `${parts[0]}${nextPageKey}.html`;const nextContent = await fetchChapterContentAndNext(nextPageLink,currentChapterName,depth + 1);return `${content}\n\n---\n\n${nextContent}`;}} catch (error) {console.error("Error fetching chapter content:", error);return "";}
}async function saveToFile(chapters) {const totalChapters = chapters.length;const allContentPromises = [];// 预先创建所有章节内容的Promise数组for (let index = 0; index < totalChapters; index++) {const item = chapters[index];allContentPromises.push(fetchChapterContentAndNext(item.url, item.chapterName));}// 使用Promise.all分批次处理Promise数组,控制并发数量let allContent = "";for (let i = 0;i < allContentPromises.length;i += LIMIT_CONCURRENT_REQUESTS) {// 取出一批Promise进行并发处理const batchPromises = allContentPromises.slice(i,i + LIMIT_CONCURRENT_REQUESTS);await delay();const batchResults = await Promise.all(batchPromises.map((p) =>p.catch((err) => console.error(`Error in batch: ${err}`))));// 将这批结果合并到allContent中batchResults.forEach((content) => {allContent += content || ""; // 确保错误处理后的内容仍能合并});// 计算并打印进度const progress = ((i + LIMIT_CONCURRENT_REQUESTS) / totalChapters) * 100;process.stdout.write(`\r处理进度: ${progress.toFixed(2)}%`);}process.stdout.write("\n"); // 完成所有章节处理后换行// 清理并保存内容const cleanedStr = allContent.replace(/!「如章节缺失请退#出#阅#读#模#式」/g,"");await fs.writeFile("chapters.txt", cleanedStr, "utf8");
}async function main() {const startTime = Date.now(); // 开始时间记录const chapterLinks = await fetchChapterList(CHAPTER_LIST_URL);// const chapterLinksTest = chapterLinks.slice(-2); // 小范围测试// console.log("🚀 ~ main ~ chapterLinks_test:", chapterLinksTest);await saveToFile(chapterLinks);const endTime = Date.now(); // 结束时间记录const totalTimeInSeconds = (endTime - startTime) / 1000; // 总耗时(秒)console.log(`操作完成,总共耗时: ${totalTimeInSeconds.toFixed(2)}`);
}main().catch(console.error);

package.json

{"name": "tds-txt-download","version": "1.0.0","description": "","bin": {"my-tool": "./index.js"},"scripts": {"start": "node index.js"},"keywords": [],"author": "","license": "ISC","dependencies": {"axios": "^1.7.2","cheerio": "^1.0.0-rc.12","fs": "0.0.1-security"},"devDependencies": {"eslint": "^8.57.0",}
}

.eslintrc.json

{"env": {"browser": false, // 禁用浏览器环境的全局变量"es2022": true, // 启用ES2022特性"node": true // 启用Node.js环境的全局变量和语法检测},"parserOptions": {"ecmaVersion": 2022,"sourceType": "module","ecmaFeatures": {"jsx": false // 如果项目包含JSX,应改为true并安装相应的解析器和插件}},"plugins": [// 可以添加额外的插件,如"import"用于更严格的import/export语句检查// "import"],"rules": {// 基础规则"no-console": "warn","no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],// 代码风格"indent": ["error", 2],"quotes": ["warn", "double"],"semi": ["error", "always"],"comma-dangle": ["error", "never"], // 对象或数组最后一个元素后面不允许有逗号// 提高代码质量"eqeqeq": "error","no-debugger": "error","no-undef": "error","no-use-before-define": ["error", { "functions": false }],"no-shadow": "error", // 防止变量被意外阴影遮蔽// 可能的错误"no-unreachable": "error", // 禁止不可达代码// ES6+特性"prefer-template": "error","arrow-body-style": ["error", "as-needed"],"prefer-const": "error","prefer-destructuring": ["error", { "array": false, "object": true }],// 可读性和简洁性"max-len": ["warn", { "code": 120 }], // 限制行宽,提高可读性"camelcase": "error" // 变量和函数名使用驼峰命名法}
}

代码分析

代码分析来自“通义千问”

这段JavaScript代码使用了axioscheerio和Node.js的文件系统模块fs来实现了一个小说爬虫程序,其主要功能是从一个在线小说网站抓取章节列表及其内容,并将这些内容保存到本地文件中。下面是详细的流程分析与注释解释:

代码结构

初始化与配置

  • 引入必要的模块:axios用于发送HTTP请求,cheerio用于解析HTML文档,以及Node.js的异步文件系统模块fs.promises用于文件操作。
  • 定义几个关键常量:MAX_DEPTH限制递归深度以防止无限循环,CHAPTER_LIST_URL为目标小说的章节列表页面地址,DELAY_MS是请求之间的延迟时间(单位:毫秒),LIMIT_CONCURRENT_REQUESTS限制并发请求的数量以减轻服务器压力。

辅助函数

  • delay()函数:用于在请求之间引入延迟,以遵守网站的访问规则和避免被封禁。

数据获取

  • fetchChapterList(url):此函数负责获取章节列表页面的HTML内容,然后使用cheerio解析出每一章的标题和链接,最后以对象数组形式返回章节信息。

内容抓取与递归

  • fetchChapterContentAndNext(chapterUrl, currentChapterName, depth):这是核心函数,负责递归地获取单个章节的全部内容。它首先尝试获取章节的HTML内容,接着检查是否有“下一章”的链接。如果有,则根据URL结构递归调用自身以获取下一章的内容;如果没有,则返回当前章节的内容。递归深度由MAX_DEPTH限制,超出则停止抓取并给出警告。

文件保存

  • saveToFile(chapters):此函数遍历章节列表,为每个章节发起内容抓取请求。为了管理并发,使用LIMIT_CONCURRENT_REQUESTS限制每次处理的请求数量,并使用Promise.all来批量处理这些请求。章节内容经处理后(去除不需要的部分),最终写入到名为chapters.txt的文件中。

主函数与执行

  • main():程序的入口点,它首先记录开始时间,然后调用fetchChapterList获取章节列表,再调用saveToFile处理并保存章节内容,最后计算并打印整个操作的总耗时。

异常处理

  • 整个程序通过.catch(console.error)确保任何未捕获的异常都能被打印出来,以便于调试和监控。

流程总结

  1. 准备阶段:设置配置项,导入依赖。
  2. 获取章节列表:通过fetchChapterList获取小说的所有章节链接。
  3. 内容抓取:利用fetchChapterContentAndNext递归地抓取每个章节的详细内容,同时控制递归深度和并发请求量以优化性能和尊重网站规则。
  4. 内容保存:在saveToFile中整理抓取到的所有章节内容,清理不必要的信息,并将其保存到本地文件。
  5. 完成与统计:记录程序运行的总耗时,并通知用户操作已完成。

整个过程体现了对目标网站数据的自动化采集、处理与存储,展现了网络爬虫的基本工作原理。

深度思考

最初版本的内容里面请求是串行的,后来因为速度太慢,然后使用五个请求并行的方法,预计提升5倍的效率,但是实际上提升了七十倍,没搞懂为什么有这么大的提升,下面是串行的代码

async function saveToFile(chapters) {let allContent = "";const totalChapters = chapters.length;for (let index = 0; index < totalChapters; index++) {const item = chapters[index];const fullChapterContent = await fetchChapterContentAndNext(item.url,item.chapterName);allContent += fullChapterContent;// 计算并打印进度const progress = ((index + 1) / totalChapters) * 100;process.stdout.write(`\r处理进度: ${progress.toFixed(2)}%  当前处理--${item.chapterName}`);}process.stdout.write("\n"); // 在完成所有章节处理后换行const cleanedStr = allContent.replace(/!「如章节缺失请退#出#阅#读#模#式」/g,"");await fs.writeFile("chapters.txt", cleanedStr, "utf8");
}

并行方案耗时

处理进度: 100.79%
操作完成,总共耗时: 18.55

串行方案耗时

处理进度: 100.00%  当前处理--第九十一话 剑指下一届
操作完成,总共耗时: 1270.63
http://www.lryc.cn/news/581076.html

相关文章:

  • 在家有电脑怎么做网站/抖音推广平台
  • 潮阳网站开发/新闻头条最新消息今日头条
  • 做网站 深圳/公司营销策划方案案例
  • 网站幻灯片效果代码/网站建设的六个步骤
  • nas可以做网站服务器/网站站点查询
  • 北京seo供应商/宁波seo优化排名
  • wordpress4.7.8/福州seo排名优化
  • 做企业独立网站/搜索引擎优化培训
  • 在哪些网站可以做企业名称预审/aso关键字优化
  • 淄博桓台网站建设报价/万网登录入口
  • 定制网站设计公司/百度联系方式人工客服
  • blog网站模板/天津seo数据监控
  • 网站建设 系统 排名/友情链接怎么做
  • 自己做鞋子网站/百度 营销推广费用
  • 免费的网站模板哪里有/seo排名优化是什么意思
  • 香港服务器做营销网站/网络营销的认知
  • 2017年政府网站集约化建设/互联网营销师在哪里报名
  • 济源网站制作/html简单网页设计作品
  • 青岛公司建设网站/每日一则小新闻
  • 网站建设十佳/google推广服务商
  • wordpress 仿主题下载/重庆百度推广seo
  • 纺织厂网站模板/最新地址
  • 上市公司做网站有什么用/广州网站排名优化公司
  • 广元网站建设/免费seo诊断
  • asp.net开发移动网站模板下载/怎么制作网站?
  • 网站站点建立/免费发布软文广告推广平台
  • 做网站网站关键词是什么/seo工程师是做什么的
  • 最权威的做网站设计哪家好/促销活动推广语言
  • wordpress安装不上主题/seo入门基础教程
  • 成都网站外包优化/宁波正规seo快速排名公司