批量导出某个简书用户的所有文章列表和文章超链接
简书改版后,根据文章标题搜索文章的功能就不见了。
虽然简书提供了批量下载文章的功能,但是下载到本地的文章都是markdown格式的,不包含文章的链接,这不满足我的需求。
既然我是程序员,没有这个功能我就自己实现一个。
打开简书首页,发现默认只显示8篇文章,用鼠标滑动到屏幕底部后,会触发一个懒加载事件,到后台读取更多的文章列表,所以文章读取在服务器端是采取的分页实现。
打开Chrome开发者工具,观察网络请求,请求url中99b8712e8850是我简书用户id,page=2,3,4这些是分页代码。
每页的文章内容以html格式包含在响应结构里:
我关心的只是文章标题和文章链接,如上图高亮字段所示。
最开始我写了一个nodejs应用,代码如下:
varrequest=require('request');varjsdom=require("jsdom");varJSDOM=jsdom.JSDOM;constPREFIX="https://www.jianshu.com";constPAGE="https://www.jianshu.com/u/99b8712e8850?order_by=shared_at&page=";constMAX=2;varmArticleResult=newMap();varpageNumber;/*agivenarticle:https://www.jianshu.com/p/963cd23fb092valuegotfromAPI:/p/5c1d0319dc42*/varlastPageReached=false;varurl="";varaHandlers=[];//uselimitedforlooptoeasetestingfor(vari=0;i<MAX;i++){pageNumber=i+1;varurl=PAGE+pageNumber;//console.log("currentpage:"+url);varpageOptions={url:url,method:"GET",headers:{"Accept":"text/html"}};aHandlers.push(getArticles(pageOptions,pageNumber));if(lastPageReached)break;}console.log("promisehandlersize:"+aHandlers.length);Promise.all(aHandlers).then(function(){vararticleIndex=0;for(var[key,value]ofmArticleResult){console.log("Article["+articleIndex+++"]:"+key+"="+value);}console.log("done");});functiongetArticles(pageOptions,pageNumber){returnnewPromise(function(resolve,reject){varrequestC=request.defaults({jar:true});requestC(pageOptions,function(error,response,body){if(error){console.log("error:"+error);resolve(error);}vardocument=newJSDOM(body).window.document;varcontent=document.getElementsByTagName("li");for(vari=0;i<content.length;i++){varli=content[i];varchildren=li.childNodes;for(varj=0;j<children.length;j++){vareachChild=children[j];if(eachChild.nodeName=="DIV"){vargrandChild=eachChild.childNodes;for(vark=0;k<grandChild.length;k++){vargrand=grandChild[k];if(grand.nodeName=="A"){varfragment=grand.getAttribute("href");if(fragment.indexOf("/p")<0)continue;console.log("title:"+grand.text);varwholeURL=PREFIX+fragment;console.log("url:"+wholeURL);if(mArticleResult.has(grand.text)){lastPageReached=true;console.log("articlesize:"+mArticleResult.size);resolve(pageNumber);}mArticleResult.set(grand.text,wholeURL);}}}}}//endofouterloopresolve(pageNumber);});});}
原理就是使用nodejs的request module,向简书网站同时发起多个请求,每个请求读取一页的简书文章。
后来发现这种方法在并发请求数大于10个的时候就无法工作,简书网站会拒绝该类请求,返回HTTP 429状态码。
所以最后我采用了最简单的同步请求实现,使用了nodejs提供的sync-request在循环里发起请求。
varrequest=require("sync-request");varjsdom=require("jsdom");varJSDOM=jsdom.JSDOM;vartextEncoding=require('text-encoding');vartextDecoder=textEncoding.TextDecoder;constPREFIX="https://www.jianshu.com";constPAGE="https://www.jianshu.com/u/99b8712e8850?order_by=shared_at&page=";constMAX=100;varmArticleResult=newMap();varlastPageReached=false;varpageNumber;/*agivenarticle:https://www.jianshu.com/p/963cd23fb092valuegotfromAPI:/p/5c1d0319dc42*/try{//uselimitedforlooptoeasetestingfor(vari=0;i<MAX;i++){if(lastPageReached)break;pageNumber=i+1;varurl=PAGE+pageNumber;console.log("currentpage:"+url);varresponse=request('GET',url);varhtml=newtextDecoder("utf-8").decode(response.body);handleResponseHTML(html);}}catch(e){}vararticleIndex=0;varresultHTML="<html>";constfs=require('fs');/*<HTML><p><ahref="https://www.baidu.com">eee</a></p><p><a>22</a></p><p><a>33</a></p></HTML>*/varindex=1;for(var[key,value]ofmArticleResult){vararticle="<p><ahref=\""+key+"\">"+index+++"."+value+"</a></p>"+"\n";resultHTML=resultHTML+article;console.log("Article["+articleIndex+++"]:"+value+"="+key);}resultHTML=resultHTML+"</html>";varpwd=process.cwd()+"/jianshu.html";fs.appendFileSync(pwd,resultHTML);console.log("done");functionhandleResponseHTML(html){vardocument=newJSDOM(html).window.document;varcontent=document.getElementsByTagName("li");for(vari=0;i<content.length;i++){varli=content[i];varchildren=li.childNodes;for(varj=0;j<children.length;j++){vareachChild=children[j];if(eachChild.nodeName=="DIV"){vargrandChild=eachChild.childNodes;for(vark=0;k<grandChild.length;k++){vargrand=grandChild[k];if(grand.nodeName=="A"){varfragment=grand.getAttribute("href");if(fragment.indexOf("/p")<0)continue;//console.log("title:"+grand.text);varwholeURL=PREFIX+fragment;//console.log("url:"+wholeURL);if(mArticleResult.has(wholeURL)){lastPageReached=true;console.log("articlesize:"+mArticleResult.size);return;}mArticleResult.set(wholeURL,grand.text);}}}}}}
这个nodejs应用执行后,会在本地生成一个html文件,包含每篇文章的标题和超链接。
要获取更多Jerry的原创文章,请关注公众号"汪子熙":
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。