小说合并插件第五篇 本来想着这一篇完成第一个可用的版本, 可惜这个脚本编写耗时超过我的预料,
这次我们主要要解决的问题是第一步;第二部第三步和第四步。
当初我们分析脚本的四大步骤, 这里重新提一下, 如果看不懂不要紧, 看代码就明白了, 当然你已经理解promise和事件的前提下。
获取第一页章节列表, 发出EVT_CHAPTER_MORE事件
获取更多章节列表, 继续发出EVT_CHAPTER_MORE事件, 没有更多发出evt_chapter_done事件
获取小说某个章节第一页内容 发出EVT_CONTENT_MORE事件
获取本章节更多内容 发出evt_content_more事件, 没有更多发出evt_content_done事件
这里我们要解决chapter_more; content_more和content_done事件, chapter_done留到下次在说, 此外还有一个novel_done事件所有工作完成后发出, 这个也是留到下次分解。
到这里我们可以发现整个脚本围绕着这五个事件来正常运行的, 五者缺一不可, 这一篇说三个。
这次基本上整个脚本的主体股价给实现出来, 从此脚本不会有什么大的变化。
此外这里解决了数据过滤的问题……
首先定义几个事件, 然后把下载按钮放到对象里, init函数没有变化。
function Novel ( ){ const EVT_CHAPTER_MORE = "chapterMore" ; const EVT_CONTENT_MORE = "contentMore" ; const EVT_CONTENT_DONE = "contentDone" ; this .chapters = new Map (); this .escapeChapters = new Array (); this .main = document .createElement ("div" ); this .table = document .createElement ("table" ); this .downloadButton = document .createElement ("button" ); this .main .dispatchEvent (new CustomEvent (EVT_CHAPTER_MORE , {detail : getTextURL (document , "ablum_read" , "正序" )})); }
我们在escapeChapters里存放那些没有URL的章节序号和标题, 等到合适的时候通过其他途径获取这些章节的URL从而获取他们的内容。
第一步第二部获取小说所有章节标题序号和URL 我们知道Novel对象的init函数最后触发了EVT_CHAPTER_MORE事件, 那么如下就是它的事件处理函数,
this .main .addEventListener (EVT_CHAPTER_MORE , (e ) => { download (e.detail ) .then ((htmlDoc ) => { let dom = document .createElement ("div" ); dom.innerHTML = htmlDoc; for (let el of dom.getElementsByClassName ("chapter" )[0 ].getElementsByTagName ("li" )) { let title = el.innerText ; let url = el.getElementsByTagName ("a" )[0 ]?.getAttribute ("href" ) ?? "/" let chapterNum = parseInt (new RegExp ("\\d+" ).exec (title)[0 ]); if (url === "/" ) { this .escapeChapters .push ({title, chapterNum}); } else { this .main .dispatchEvent (new CustomEvent (EVT_CONTENT_MORE , {detail : {url, chapterNum}})); break ; } } let url = getTextURL (dom, "page" , "下一页" ); if (url) { this .main .dispatchEvent (new CustomEvent (EVT_CHAPTER_MORE , {detail : url})); } }) .catch (alert); });
《?.和??》编写跟优雅的代码 首先看双问号
let name = getName () ?? getDefaultName ();
如果getName返回无效值就用getDefaultName的值, 这个特性在下面的代码里实际使用过。
?.那更是非常重要, 如果没有.?写这样的代码
let a = document .getElementsByTagName ("a" )[0 ];if (a){ result = a.innerHTML ; }
如果用?.的话可以这样写:
result = document .getElementsByTagName ("a" )[0 ]?.innerHTML ;
好像C#也有了这个特性, Java还没有。
一个?.不知道节约了多少if语句。
第一步和第二部算是这样轻描淡写的搞定了, 接下来看第三步和第四步, 和上面的逻辑相同。
第三步第四步获取小说内容 我们知道CHAPTER_MORE的循环里触发了CONTENT_MORE事件, 测试阶段每一页只触发一次, 那么这就是它的事件处理函数。
this .main .addEventListener (EVT_CONTENT_MORE , (e ) => { let url = e.detail .url ; download (url) .then ((htmlDoc ) => { let content = null ; if (this .chapters .has (e.detail .chapterNum )) { content = this .chapters .get (e.detail .chapterNum ); } else { content = {pages : []}; this .chapters .set (e.detail .chapterNum , content); } let dom = document .createElement ("div" ); dom.innerHTML = htmlDoc; let text = dom.getElementsByClassName ("nr_nr" )[0 ]?.innerText + "\n" ; for (let el of dom.getElementsByTagName ("script" )) { if (el.innerText .includes (new RegExp ("\\d+" ).exec (url)[0 ])) { text += el.innerText + "\n" ; break ; } } content.pages .push (this .filter (text)); url = getTextURL (dom, "nr_title" , "下一页" ); if (url) { this .main .dispatchEvent (new CustomEvent (EVT_CONTENT_MORE , {detail : {url, chapterNum : e.detail .chapterNum }})); } else if ((url = getTextURL (dom, "nr_title" , "下一章" ))) { content.nextChapterURL = url; content.title = dom.getElementsByTagName ("title" )[0 ]?.innerText .split ("-" )[0 ] ?? `第${e.detail.chapterNum} 张` ; this .main .dispatchEvent (new CustomEvent (EVT_CONTENT_DONE , {detail : e.detail .chapterNum })); } }) .catch (alert); });
如果你看懂CONTENT_MORE那么也能看懂CHAPTER_MORE, 反之亦然。
对方网站的反扒措施 实际上小说正文的部分内容不在nr_nr里, 而是在一个id为当前章节ID的div元素里, 更妙的是它是动态添加的, 也就是说通过js来添加的,
虽然动态添加的但问题真正的内容直接写在了某个script里了, 没有做更多的处理。 所以便宜了我们, 如果它做其他手段, 我们可能就傻眼了。
现在是铭文, 可以轻而易举的拿下, 那以后就不好说了, 对方网站哪天闲着没事升级一下, 估计我们的脚本玩玩了。
这算是一个小插曲。
后续步骤把小说章节和摘要添加到表格里 我们知道CONTENT_MORE发现没有更多页后, 触发了CONTENT_DONE事件, 如下就是它的事件处理函数, 不完整。
this .main .addEventListener (EVT_CONTENT_DONE , (e ) => { let chapterNum = e.detail ; let backChapter = this .chapters .get (chapterNum); let title = backChapter.title ; let pageCount = `共${backChapter.pages.length} 页` ; let summary = backChapter.pages [1 ].substring (0 ); this .updateTable ({title, pageCount, summary}) });
我们今天的任务算是差不多了, 下一次继续完善content_done事件, 还要处理CHAPTER_DONE和NOVEL_DONE事件, 如下是更新表格的函数。
this .updateTable = (item, index = -1 ) => { let row = null ; if (index === -1 ) { row = this .table .insertRow (); } else { row = this .table .insertRow (index); } row.insertCell ().appendChild (document .createTextNode (item.title )); row.insertCell ().appendChild (document .createTextNode (item.pageCount )); row.insertCell ().appendChild (document .createTextNode (item.summary )); }
过滤函数, 用正则表达式替换那些乱码和多余的内容, 直接拷贝粘贴就可以了, 我亲手写下的这玩意太让人嫌弃了, 啥时候有空优化一下。
this .filter = (str ) => { let reg = new RegExp ("<br/>| |www\\.dudu0\\.com|上一章|下一章|上一页|下一页|返回目录|最新网址|关闭+畅\\/读=,看完整内容。本章未完,请点击【|】继续阅读。|请关闭\\-畅\\*读\\/模式阅读。|关闭\\+畅\\/读=,看完整内容。本章未完,请点击【|document.getElementById.+=\\s" , "gi" ); return str.replace (reg, "\n" ).replace (/\n{2, }/g , "\n" ); }
到此为止我们还生下了如下几个工作, 这个留到下次继续了。
如何判断所有章节下载完成
如何下载那些缺失的章节
如何让用户方便的下载到本地