本文主要是介绍备份CSDN博客正文到本地存档,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
大哥有了新想法,然而没有技术,令人欣慰的是大哥想到了我,于是我便答应免费帮个忙,这是一个基于云的项目,具体细节也就不透露了,然而在实现的过程中,其中一个模块我觉得可以自用,于是我就想把这个模块抽出来,该模块的功能就是将CSDN博客上的文章下载到本地。假期只完成了一个模板,虽然很垃圾,但是却能满足自用的需求,一直以来,我都很害怕自己喝懵了写的一些感悟放在网上会在某一天再也打不开,事实上,这种事 情确实也发生过很多次。忘记用户,文章被管理员删除,博客被封闭,网站不再维护等都会导致这样的问题,于是我就不得不定期将自己在各个网站注册的博客复制 到一个本地的文档上,包括网易的,百度的,CSDN的,51CTO的,以及老婆的QQ空间的(我总是将内容发布在老婆的QQ上,因为那上面可以畅所欲 言),这么多的网站,如此多的日志,工作量真的不少,久而久之,本地的存档也越来越乱,渐渐的,有很多文章都被遗漏了。特别是CSDN的博客,一直以来, 我都想将其完整的dump到本地,一篇一篇的复制,简直不可能,因为太多了,也找过整站下载器,但是效果不理想。趁此机会,别人委托我做的这个小玩意正好 可以用于此目的,而且比较满意的一点就是dump下来的每一篇文章都裁掉了不相关的内容,比如友情链接,博客访问量以及广告等,唯一被保留的就是正文和正 文的图片资源。
起初我是使用C++手工实现的,然而却需要自行解析HTML文档的各个标签,落实下来就是复杂的字符串解析,其实字符串解析可以堪称是编程的精髓,但是对 于实际做项目,这种工作还是直接使用现有的解析库比较好,后来我发现使用脚本语言更简单,比如使用python,perl,甚至grep/awk/sed 都可以,然而字符编码却始终是一个大问题。通过咨询一个超猛的同事,我认识了htmlparser这个java库,实在是太方便了,它将html文件元素 抽象成了各个类,这样可以很方便的实现过滤,更可贵的是,这种过滤甚至都不用自己实现,htmlparser中自带了过滤功能,你要做的只是重载一些方法 即可,这样就是使用很简单的代码实现这个博客下载功能了,下载完了之后最好将其保存成一个单独的PDF文档,虽然java也可以实现这个功能,然而目前已 经有了很多这样的工具,有现成工具的就不编程实现,这永远是一个真理。
首先看一下效果,然后看一下代码。
保存在本地的存档拥有下面的目录结构,首先是一个顶级目录,以我博客的标题来命名,内部是一个按月份存档的目录集合以及一个index.html索引文件,如下图所示:

展开一个月份存档目录,你将看到本月的文章集合,每一篇文章包含一个目录,如下图所示:

随意打开一篇文章,你将看到该篇文章的标题以及正文,所有的图片也被包含,链接到了该文章的_files目录中的对应图片,如下图:

index.html呈现处以下的样子:
如果你以文本编辑器或者xcode打开每一篇文章或者index.html文件,你将看到其中的大部分链接都被改成了本地的相对路径了,并且删除了大量的无关的内容,这种修改很简单,手工改其实很可以做到,使用程序来做当然更简便些,问题是当你写程序所带来的麻烦超过了手工修改的麻烦时,这种编程就很没有意义,幸运的是,htmlparser可以很简单的做到这一点,一点也不复杂,这样的话这种编程就显得很有意义了。
代码很简单,基本就是几大块:
1.几次遍历-按主页遍历月份信息,按月份存档遍历文章,按每一篇文章遍历图片;
2.解析关键信息,比如标题,文章中的图片等,并填充数据结构。这种事可以通过Filter来完成;
3.根据filter的副作用填充的信息生成目录。
需要说明的是,以下的代码完全是过程化的,没有使用Java语言的OO特性,因此它的数据以及方法完全是static的,没有生成任何对象,我只是想使用htmlparser的API以及java语言IDE的诸多良好的功能,比如方法以及方法参数的自动补全功能,老手或者科班高年级学生可能会较真地说,C/C++的IDE也可以支持这样的功能,如果碰到这样反驳的,我也可以说,其实嘛,汇编语言也是可以自动补全的…另外,代码中有很多的硬编码,其实应该将它们再抽象一下的,或者说定义成变量也可以,只是因为自用,以后也不准备维护,就这么着了。还有,那就是最大的问题,代码有一些bug,比如对于标题中含有奇怪字符的支持,以及错误日志(这很重要)的记录的缺失等等。不管怎么说,代码如下:
- <spanstyle="font-family:KaiTi_GB2312;font-size:18px;">importorg.htmlparser.Node;
- importorg.htmlparser.NodeFilter;
- importorg.htmlparser.Parser;
- importorg.htmlparser.filters.TagNameFilter;
- importorg.htmlparser.util.NodeList;
- importorg.htmlparser.tags.*;
- importjava.io.*;
- importjava.net.*;
- importjava.nio.*;
- importjava.util.List;
- importjavax.management.*;
- /*类名使用test很不规范,然而为了方面胡乱起的名字,可是不管怎么说,它确实是个test*/
- publicclasstest{
- /*月份文章的月份名称/月份存档URL对的列表*/
- finalstaticAttributeListindexList=newAttributeList();
- /*每月文章名称/每月文章的URL对的列表*/
- finalstaticAttributeListarticleList=newAttributeList();
- /*每篇文章图片本地存档地址/每篇文章图片URL对的列表*/
- finalstaticAttributeListresourceList=newAttributeList();
- /*保存月份以及该月文章本地存档的列表,用于生成目录*/
- staticAttributeListmonthList=newAttributeList();
- /*用于生成本地存档目录的writer*/
- staticOutputStreamWriterindex_handle=null;
- staticStringproxy_addr=null;
- staticintproxy_port=3128;
- /*
- *@paramurl网页的URL
- *@paramtype类型:1为文本,0为二进制
- *@return内容的字节数组
- */
- publicstaticbyte[]GetContent(Stringurl,inttype){
- byteret[]=null;
- try{
- HttpURLConnectionconn=null;
- InputStreamurlStream=null;;
- URLsurl=newURL(url);
- intj=-1;
- if(proxy_addr!=null){
- InetSocketAddresssoA=newInetSocketAddress(InetAddress.getByName(proxy_addr),proxy_port);
- Proxyproxy=newProxy(Proxy.Type.HTTP,soA);
- conn=(HttpURLConnection)surl.openConnection(proxy);
- }else{
- conn=(HttpURLConnection)surl.openConnection();
- }
- /*必须加上这一句伪装成Mozilla浏览器,否则CSDN会拒绝连接*/
- conn.setRequestProperty("User-Agent","Mozilla/4.0");
- conn.connect();
- urlStream=conn.getInputStream();
- if(type==1){
- StringsTotalString="";
- BufferedReaderreader=newBufferedReader(newInputStreamReader(urlStream,"UTF-8"));
- CharBuffervv=CharBuffer.allocate(1024);
- while((j=reader.read(vv.array()))!=-1){
- sTotalString+=newString(vv.array(),0,j);
- vv.clear();
- }
- sTotalString=sTotalString.replace('\n','');
- sTotalString=sTotalString.replace('\r','');
- ret=sTotalString.getBytes();
- }else{
- ByteBuffervv=ByteBuffer.allocate(1024);
- /*CSDN允许最大图片有上限*/
- ByteBufferbuffer=ByteBuffer.allocate(5000000);
- while((j=urlStream.read(vv.array()))!=-1){
- buffer.put(vv.array(),0,j);
- vv.clear();
- }
- ret=buffer.array();
- }
- }catch(Exceptione){
- e.printStackTrace();
- //追加出错日志
- }
- returnret;
- }
- /*
- *@parampath文件路径
- *@paramcontent文件内容的字节数组
- *@return成功或者失败
- */
- publicstaticbooleanWriteFile(Stringpath,byte[]content){
- try{
- FileOutputStreamosw=newFileOutputStream(path);
- osw.write(content);
- osw.close();
- }catch(Exceptione){
- e.printStackTrace();
- //追加出错日志
- returnfalse;
- }
- returntrue;
- }
- /*
- *@parampath目录路径
- *@return成功或者失败
- */
- publicstaticbooleanMKDir(Stringpath){
- try{
- Filefp=newFile(path);
- if(!fp.exists()){
- fp.mkdir();
- }
- }catch(Exceptione){
- e.printStackTrace();
- //追加出错日志
- returnfalse;
- }
- returntrue;
- }
- /*
- *@parampath文件路径
- *@paramurl文章在blog上的URL
- *@paramarticles保存本月存档的列表
- *@return无
- */
- publicstaticvoidHandleHtml(Stringpath,Stringurl,AttributeListarticles){
- try{
- StringBuffertext=newStringBuffer();
- NodeListnodes=HandleText(newString(GetContent(url,1)),3);
- Nodenode=nodes.elementAt(0);
- Stringtitle=(String)((List<Attribute>)resourceList.asList()).get(0).getValue();
- Stringfilepath=path+"/"+title;
- List<Attribute>li=resourceList.asList();
- /*加入meta信息*/
- text.append(newString("<metahttp-equiv=\"Content-Type\"content=\"text/html;chaset=utf-8\"/>"));
- text.append("<h1>"+title+"</h1>");
- if(node!=null){
- Divdv=(Div)node;
- text.append(newString(dv.toHtml().getBytes("UTF-8"),"UTF-8"));
- }else{
- text.append("<h3>Downloaderror</h3>");
- }
- test.MKDir(filepath+"_files");
- articles.add(newAttribute(filepath.split("/",2)[1],title));
- for(inti=1;i<li.size();i++){
- byte[]imgString=GetContent((String)li.get(i).getValue(),0);
- test.WriteFile(filepath+"_files/"+li.get(i).getName()+".gif",imgString);
- }
- resourceList.clear();
- test.WriteFile(filepath+".html",text.toString().getBytes());
- }catch(Exceptione){
- //追加出错日志
- e.printStackTrace();
- }
- }
- /*
- *@paramnlistHTML正文的子标签链表
- *@paramindex用于索引图片的个数以及当前的图片数
- *@return当前的图片数
- */
- publicstaticintparseImg(NodeListnlist,intindex){
- Nodeimg=null;
- intcount=nlist.size();
- for(inti=0;i<count;i++){
- img=nlist.elementAt(i);
- if(imginstanceofImageTag){
- ImageTagimgtag=(ImageTag)img;
- if(!imgtag.isEndTag()){
- Stringtitle=(String)((List<Attribute>)resourceList.asList()).get(0).getValue();
- /*将图片的URL映射成本地路径*/
- resourceList.add(newAttribute(""+index,newString(imgtag.extractImageLocn().getBytes())));
- title=title.trim();
- imgtag.setImageURL(title+"_files/"+index+".gif");
- /*递增本地路径序列*/
- index++;
- }
- }else{
- NodeListslist=img.getChildren();
- if(slist!=null&&slist.size()>0){
- index=test.parseImg(slist,index);
- }
- }
- }
- returnindex;
- }
- /*
- *@paramnlistHTML月份存档的子标签链表
- *@paramindex无用
- *@return无用
- */
- publicstaticintparseMonthArticle(NodeListnlist,intindex){
- Nodeatls=null;
- intcount=nlist.size();
- for(inti=0;i<count;i++){
- atls=nlist.elementAt(i);
- if(atlsinstanceofLinkTag){
- LinkTaglink=(LinkTag)atls;
- indexList.add(newAttribute(link.getLinkText(),link.extractLink()));
- }else{
- NodeListslist=atls.getChildren();
- if(slist!=null&&slist.size()>0){
- index=test.parseMonthArticle(slist,index);
- }
- }
- }
- returnindex;
- }
- /*
- *@paramnlistHTML标题的子标签链表
- *@paramindex无用
- *@return无用
- */
- publicstaticintparseTitle(NodeListnlist,intindex){
- Nodetit=null;
- intcount=nlist.size();
- for(inti=0;i<count;i++){
- tit=nlist.elementAt(i);
- if(titinstanceofSpan){
- Spanspan=(Span)tit;
- if(span.getAttribute("class")!=null&&span.getAttribute("class").equalsIgnoreCase("link_title")){
- LinkTaglink=(LinkTag)span.childAt(0);
- Stringtitle=link.getLinkText();
- /*将文件名中不允许的字符替换成允许的字符*/
- title=title.replace('/','-');
- title=title.trim();
- title=title.replace('','-');
- resourceList.add(newAttribute("title",title));
- }
- }else{
- NodeListslist=tit.getChildren();
- if(slist!=null&&slist.size()>0){
- index=test.parseTitle(slist,index);
- }
- }
- }
- returnindex;
- }
- /*
- *@paramnlistHTML每月份存档的子标签链表
- *@paramindex无用
- *@return无用
- */
- publicstaticintparsePerArticle(NodeListnlist,intindex){
- Nodeatl=null;
- intcount=nlist.size();
- for(inti=0;i<count;i++){
- atl=nlist.elementAt(i);
- if(atlinstanceofSpan){
- Spanspan=(Span)atl;
- if(span.getAttribute("class")!=null&&span.getAttribute("class").equalsIgnoreCase("link_title")){
- LinkTaglink=(LinkTag)span.childAt(0);
- articleList.add(newAttribute(link.getLinkText(),"http://blog.csdn.net"+link.extractLink()));
- }
- }else{
- NodeListslist=atl.getChildren();
- if(slist!=null&&slist.size()>0){
- index=test.parsePerArticle(slist,index);
- }
- }
- }
- returnindex;
- }
- /*
- *@paramnlistHTML分页显示标签的子标签链表
- *@paramindex无用
- *@return无用
- */
- publicstaticintparsePage(NodeListnlist,intindex){
- Nodepg=null;
- intcount=nlist.size();
- for(inti=0;i<count;i++){
- pg=nlist.elementAt(i);
- if(pginstanceofLinkTag){
- LinkTaglt=(LinkTag)pg;
- if(lt.getLinkText().equalsIgnoreCase("下一页")){
- try{
- test.HandleText(newString(test.GetContent("http://blog.csdn.net"+lt.extractLink(),1)),2);
- }catch(Exceptione){
- //追加出错日志
- }
- }
- }
- }
- returnindex;
- }
- /*
- *@paramnlistHTML作者信息标签的子标签链表
- *@paramindex无用
- *@return无用
- */
- publicstaticintparseAuthor(NodeListnlist,intindex){
- Nodeaut=null;
- intcount=nlist.size();
- for(inti=0;i<count;i++){
- aut=nlist.elementAt(i);
- if(autinstanceofLinkTag){
- LinkTaglink=(LinkTag)aut;
- resourceList.add(newAttribute("author",link.getLinkText()));
- }else{
- NodeListslist=aut.getChildren();
- if(slist!=null&&slist.size()>0){
- index=test.parseAuthor(slist,index);
- }
- }
- }
- returnindex;
- }
- /*
- *@paraminput输入的html文档字符串
- *@paramskip是否执行的类别
- *@return匹配的链表,很多类别通过副作用而起作用
- */
- publicstaticNodeListHandleText(Stringinput,finalintskip)throwsException{
- Parserparser=Parser.createParser(input,"UTF-8");
- NodeListnodes=parser.extractAllNodesThatMatch(newNodeFilter(){
- publicbooleanaccept(Nodenode){
- if(nodeinstanceofDiv){
- Divdv=(Div)node;
- NodeListnlist=dv.getChildren();
- if(dv.getAttribute("id")!=null&&nlist!=null){
- if(dv.getAttribute("id").equalsIgnoreCase("article_content")&&skip==3){
- parseImg(nlist,0);
- returntrue;
- }elseif(dv.getAttribute("id").equalsIgnoreCase("article_details")&&skip==3){
- parseTitle(nlist,0);
- }elseif(dv.getAttribute("id").equalsIgnoreCase("archive_list")&&(skip==1||skip==4)){
- parseMonthArticle(nlist,0);
- }elseif(dv.getAttribute("id").equalsIgnoreCase("papelist")&&skip==2){
- parsePage(nlist,0);
- }elseif(dv.getAttribute("id").equalsIgnoreCase("blog_title")&&skip==4){
- parseAuthor(nlist,0);
- }
- }
- if(dv.getAttribute("class")!=null&&nlist!=null){
- if(dv.getAttribute("class").equalsIgnoreCase("article_title")&&skip==2){
- parsePerArticle(nlist,0);
- }
- }
- }
- returnfalse;
- }
- });
- returnnodes;
- }
- /*
- *@paramfilepath本地存档的路径
- *@paramurl保存本月存档的网页的URL
- *@paramarticles保存本月存档的链表
- *@return无
- */
- publicstaticvoidparseMonth(Stringfilepath,Stringurl,AttributeListarticles){
- List<Attribute>li=articleList.asList();
- try{
- HandleText(newString(GetContent(url,1)),2);
- }catch(Exceptione){
- //追加出错日志
- }
- test.MKDir(filepath);
- for(inti=0;i<li.size();i++){
- HandleHtml(filepath,(String)li.get(i).getValue(),articles);
- try{
- /*慢一点,否则会被认为是恶意行为*/
- Thread.sleep(500);
- }catch(Exceptione){}
- }
- articleList.clear();
- }
- /*
- *@paramurlblog入口文章的URL
- *@return无
- */
- publicstaticvoidparseAll(Stringurl){
- try{
- Stringauthor=null;
- HandleText(newString(GetContent(url,1)),4);
- author=(String)((List<Attribute>)resourceList.asList()).get(0).getValue();
- resourceList.clear();
- test.MKDir(author);
- List<Attribute>li=indexList.asList();
- for(inti=0;i<li.size();i++){
- AttributeListarticles=newAttributeList();
- monthList.add(newAttribute(li.get(i).getName(),articles));
- parseMonth(author+"/"+li.get(i).getName(),(String)li.get(i).getValue(),articles);
- }
- HandleIndex(author);
- }catch(Exceptione){
- e.printStackTrace();
- }
- indexList.clear();
- }
- /*
- *@paramdir本地存档根路径名称
- *@return无
- */
- staticvoidHandleIndex(Stringdir){
- try{
- index_handle=newOutputStreamWriter(newFileOutputStream(dir+"/index.html"),"GB18030");
- Stringheader="<html><head><metahttp-equiv=\"Content-Type\"content=\"text/html;charset=utf-8\"><title>CSDN文章归档</title></head><bodybgcolor=\"white\"text=\"black\"link=\"#0000FF\"vlink=\"#840084\"alink=\"#0000FF\"><hr></div><div><h1class=\"title\"><aname=\"id2747881\"></a>"+dir+"CSDN文章归档</h1></div></div><hr></div><divclass=\"toc\"><p><b>目录</b></p><dl><dt><spanclass=\"preface\"><ahref=\"preface.html\">摘要</a></span></dt>";
- Stringtailer="</div></div><hr></body></html>";
- index_handle.write(header);
- List<Attribute>li=monthList.asList();
- for(inti=0;i<li.size();i++){
- Stringmindex="<dt><spanclass=\"part\"><h4>"+li.get(i).getName()+"</span></dt><dd><dl>";
- AttributeListarticles=(AttributeList)li.get(i).getValue();
- List<Attribute>al=articles.asList();
- index_handle.write(mindex);
- for(intj=0;j<al.size();j++){
- Stringper="<dt><spanclass=\"part\"><ahref=\""+al.get(j).getName()+".html\">"+al.get(j).getValue()+"</a></span></dt>";
- index_handle.write(per);
- }
- index_handle.write("</dl></dd>");
- }
- index_handle.write(tailer);
- index_handle.close();
- }catch(Exceptione){}
- }
- /*
- *@paramargsargs[0]:blog入口文章的URLargs[1]:代理地址args[2]:代理端口【用法:javaDownBloghttp://blog.csdn.net/dog250192.168.40.199808】
- *@return无
- */
- publicstaticvoidmain(String[]args)throwsException{
- parseAll("http://blog.csdn.net/dog250");
- /*booleanvalid=false;
- if(args.length==1){
- valid=true;
- }elseif(args.length==2){
- proxy_addr=args[1];
- valid=true;
- }elseif(args.length==3){
- proxy_addr=args[1];
- proxy_port=Integer.parseInt(args[2]);
- valid=true;
- }
- if(valid){
- parseAll(args[0]);
- }else{
- }*/
- }
- }</span>
那么,如果使用上面的代码备份你自己的CSDN博客呢?很简单,将dog250改成你的ID即可,我在main方法中注释了一大段的内容,你也可以将其展开,然后他就是通用的了,试试看。
这篇关于备份CSDN博客正文到本地存档的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!