前言
看视频看的累了 写写博文~
很久以前就想有个自己的博客。csdn很好,可是我不是专家啊,还是功底不够, 没有权限,也就不能实现自己的一些想法。所以就百度了一个静态模版。有了模版,问题来了。是个空壳子啊,没有任何内容,所以就想着爬取自己csdn上面的所有文章并生成相应的jsp页面保存到本地,而且为了实时保证自己的博文新鲜度所以就用spring-quartz定时器定时执行抓取页面的任务。博客地址(http://www.susulovefreedom.cn)
分析网页
分析完网页,我终于发现为啥我的博文总是那么轻易的被别人爬走了 , 百度搜索排名还在我的前面。原来那么简单
页码分析
http://blog.youkuaiyun.com/su20145104009?viewmode=contents
在这个页面我们可以看到最短的分页信息。
然后通过审查元素定位到这里。
仔细查看,最大页码为12.在id为papelist标签下的第一个span元素。所以最大页码就可以这样获得
HtmlPage page=wc.getPage("http://blog.youkuaiyun.com/su20145104009?viewmode=contents");
DomElement pageList = page.getElementById("papelist");
//最大页码
int MaxPage=Integer.parseInt(pageList.getFirstChild().asText().substring(6, 8));
文章链接分析
在上一步,我们得到了分页的最大页码。
通过查看url可以发现http://blog.youkuaiyun.com/su20145104009/article/list/2
最后一位
即为你要请求的页码。
那么就可以通过get请求来获得某一页的数据。
page=wc.getPage(“http://blog.youkuaiyun.com/su20145104009/article/list/“+MaxPage);
而在进入某一页后。继续查看源码
可以发现所有的文章都在article_list标签内。
所以我们可以首先获得article_list标签。
DomElement article_list = page.getElementById(“article_list”);
在article_list 标签内我们要继续获得文章的信息。
首先获得所有的article_list 子标签(并不包括子标签的子标签)。即上述图片的
DomNodeList<DomNode> childNodes = article_list .getChildNodes();
然后通过for循环对每一篇的文章细节进行分析:
for (DomNode htmlElement : childNodes)
文章的所有信息一览无余。
由于里面只有两个a标签。
- 在第一个a标签里面有文章的地址,标题信息。
- 在第二个a标签里面有文章的阅读次数信息。然后第二个a标签的父标签的父标签下的第一个标签内有文章的创建日期信息。
- 在获得题目的同时,使用md5码对文章的标题进行加密作为数据库中的主键
所以通过标签名字获取两个a标签:
DomNodeList<HtmlElement> links = ((DomElement) htmlElement).getElementsByTagName("a");
String address=links.get(0).getAttribute("href");
String title=links.get(0).asText();
String readCounts=links.get(1).getParentNode().asText();
String time=links.get(1).getParentNode().getParentNode().getFirstChild().asText();
到了这一步仅仅获得了所有文章的题目信息。
那么应该如何获得文章的具体内容呢?
文章内容分析
定位到某一篇文章。通过审查元素,我们可以找到所有的文章内容都在article_details标签里。
剩下的就是把article_details标签的所有内容存到自己的网页了。
在这里遇到了一问题。
起初我使用的是htmlunit工具直接通过getElementById的方法获取article_details的源码。可以生成后的网页分析后发现。所有的文章是正常显示了,可是代码出现严重的换行现象。通过对比源码后发现,htmlunit的axXml方法会自动对所有的标签进行格式化操作。google许久也没有找到适当的解决办法。灵光一闪,想到了URLConnection。
URLConnection的get请求获得的源码和浏览器源码一致。
于是定义了一个方法。对获得的源码进行字符串截取即可。正则表达式不是我不用,是爆栈啦~字符串太长了。。。。
/**
*
* @Title: getArticle
* @Description: (通过URLConnection的get请求获得文章的源码信息)
* @param link 文章的链接
* @return String 文章的源码
*/
public static String getArticle(String link){
URL url=null;
URLConnection conn=null;
InputStream is = null;
//字符流
InputStreamReader isr = null;
//缓冲流
BufferedReader br = null;
try {
url=new URL(link);
conn= url.openConnection();
conn.setRequestProperty("User-Agent","Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
conn.connect();
//网页编码
String context = null;
//获得网页的字节输入流
is = conn.getInputStream();
//将字节输入流转换为字符输入流 并设置转码格式为utf-8
isr = new InputStreamReader(is, "utf-8");
//将字符流转换为缓冲流
br = new BufferedReader(isr);
//一行一行读取网页内容
context = br.readLine();
String txt="";
while ((txt=br.readLine())!=null) {
context +=txt+"\n";
}
int st=context.indexOf("<div id=\"article_content\"");
int ed=context.indexOf("<!-- Baidu Button BEGIN -->");
return context.substring(st, ed);
} catch (Exception e) {
if(is!=null){
try {
is.close();
} catch (IOException e1) {
throw new RuntimeException(e1);
}
}
if(isr!=null){
try {
isr.close();
} catch (IOException e1) {
throw new RuntimeException(e1);
}
}
if(br!=null){
try {
br.close();
} catch (IOException e1) {
throw new RuntimeException(e1);
}
}
throw new RuntimeException(e);
}
到了这里基本就快完成了~
剩下的就是使用io流写入网页了
io流生成jsp页面
我们的jsp页面肯定不能只有个文章的信息。当然也需要一些其它的元素。所以这些其它元素就需要我们自己准备了,保存到web项目里,然后使用io流读取即可。由于至少需要读取两个部分。所以我新建了一个方法
/**
*
* @Title: getHeadHtml
* @Description: (通过文件的路径读取相应的文件信息并返回)
* @param path 文件路径
* @return String 文件内的信息
* @throws
*/
public static String getHeadHtml(String path) {
String res="";
BufferedReader br=null;
String data=null;
try {
br=new BufferedReader(new InputStreamReader(new FileInputStream(path),"utf-8"));
while((data=br.readLine())!=null){
res+=data+"\n";
}
} catch (IOException e) {
throw new RuntimeException(e);
}finally{
if(br!=null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return res;
}
然后就是写入jsp信息了。如果当前数据库中没有这篇文章,那么就生成这个jsp页面。jsp的名称我使用的是文章题目md5码的后8位
if(!articleService.findArticleById(id)){
//将源码写入到文件
OutputStreamWriter bw=new OutputStreamWriter(new FileOutputStream(filePath+"articles/"+htmlname+".jsp"),"utf-8");
//jsp的头部信息
bw.write(MyStringUtil.getHeadHtml(filePath+"headHtml.txt"));
//head标签中的title标签
bw.write("<title>"+title+"</title>");
//head标签中的标题信息
bw.write("<meta name=\"description\" content=\"");
bw.write(content+"\" />");
bw.write("<base href=\"<%=basePath%>articleShow_"+htmlname+".html\"/>");
//文章内容前的信息 一些js文件css文件啦
bw.write(MyStringUtil.getHeadHtml(filePath+"titlePreHtml.txt"));
bw.write(title+"</a></h3>");
//通过文章的链接 调用getArticle方法获得文章的源码 并写入jsp页面
bw.write(MyStringUtil.getArticle(article.getLink()));
//文章底部的信息
bw.write(MyStringUtil.getHeadHtml(filePath+"endHtml.txt"));
bw.flush();
bw.close();
System.out.println(title+"制作网页完成");
}
到了这里就大功告成了。
squarz设置定时抓取
配置如下:每12个小时执行一次抓取任务
如果对squarz定时器不懂,请转http://blog.youkuaiyun.com/su20145104009/article/details/72842526
<!-- 任务bean
由于我要写入数据库 所有注入了articleService
-->
<bean id="hourWork" class="com.scx.hourwork.HourWork">
<property name="articleService" ref="articleService"></property>
</bean>
<!-- 使用MethodInvokingJobDetailFactoryBean,
任务类可以不实现Job接口,
targetObject:调用类
targetMethod:调用方法
-->
<bean id="methodInvokingBean" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="hourWork"></property>
<property name="targetMethod" value="execute"></property>
</bean>
<!-- 任务触发器 -->
<bean id="myTriggers" class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail" ref="methodInvokingBean"></property>
<!-- 每隔12个小时更新一次 -->
<property name="cronExpression" value="00 00 0/12 * * ?"></property>
</bean>
<!-- 任务调度工厂 -->
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<!-- 可以使用list配置多个,第一种方式已经演示 -->
<property name="triggers" ref="myTriggers"></property>
</bean>
执行后可以在文件夹中查看
当点击自己文章的链接时,去文章id的后8位转向这个jsp页面。
写了一个多小时 也该回宿舍了~~希望暑期能找到个实习工作 唉