因为要转到Java阵营,最近开始系统的看Java方面的书籍,正好自己需要大量的图片,所以就有了写一个爬虫,把自己设定的几个网站上所有的图片抓取下来,顺便练习Java。
对爬虫程序一直都比较好奇,但没有过任何经验,在参考前辈的理念基础上尽量自己发挥。
爬虫完成前都是边写边改,可能后期的设计和现在的不太一样,但是也反映了思维的变化,觉得也是不错的事情。
根据自己的需求,这个爬虫的基本流程暂设定为:
流程设定的比较简单,而且数据流不会过于庞大,所以也没下载下来保存成了文本文件,而没有存入数据库。
首先建立一个XML文件用来保存保存的路径等信息,这些东西不应该写死,config.xml:
<spider>
<sleeptime>
100</sleeptime>
<saveFilePath>E:/www/tmp/</saveFilePath>
<saveImagePath>E:/www/
img/</saveImagePath>
<
data>E:/www/
data/</
data>
</spider>
下面就是读取XML类Config.java:
package com.catcoder.config;
import java.io.File;
import java.io.IOException;
import javax.xml.parsers.
*;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
public
class Config {
public
static
int SLEEP_TIME
=
100;
public
static String FILE_PATH
=
"";
public
static String IMAGE_PATH
=
"";
public
static String DATA_PATH
=
"";
private Document document;
public Config()
throws ParserConfigurationException,
SAXException,
IOException{
DocumentBuilderFactory documentBF
= DocumentBuilderFactory.newInstance();
DocumentBuilder db
= documentBF.newDocumentBuilder();
document
= db.parse(
new File(
"src/com/catcoder/config/config.xml"));
}
public
void Load(){
NodeList nl1
= document.getElementsByTagName(
"sleeptime");
NodeList nl2
= document.getElementsByTagName(
"saveFilePath");
NodeList nl3
= document.getElementsByTagName(
"saveImagePath");
NodeList nl4
= document.getElementsByTagName(
"data");
if( nl1.getLength()
>
0
&&
nl2.getLength()
>
0
&&
nl3.getLength()
>
0
&&
nl4.getLength()
>
0){
SLEEP_TIME
= Integer.parseInt(nl1.item(
0).getTextContent());
FILE_PATH
= nl2.item(
0).getTextContent();
IMAGE_PATH
= nl3.item(
0).getTextContent();
DATA_PATH
= nl4.item(
0).getTextContent();
}
else{
System.out.println(
"配置文件不完整。");
}
}
}
这些数据应该是整个项目通用,所以几个参数使用static来修饰。
简单的配置之后,开始第一步,给出一个入口地址,然后下载页面。
在Java中使用HttpURLConnection来建立HTTP请求并下载页面,根据返回的状态,如果状态为200则说明请求成功,将请求到的数据进行处理并保存。
处理数据是,为了减少数据量,将网页中的script、link、style等标签全部去除,去除标签的style属性。
全部代码,DownPage.java:
package com.catcoder.down;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Date;
import java.text.SimpleDateFormat;
import com.catcoder.config.Config;
public
class DownPage {
private URL url;
private
int responseCode;
private HttpURLConnection urlConnection;
private BufferedReader reader;
private String line;
private String savePath
=
"";
private String fileName
=
"";
private SimpleDateFormat sdf;
private FileOutputStream fos;
private BufferedOutputStream bos;
public DownPage(String urls){
setURL(urls);
}
/**
* 下载页面
*/
public
void getPage(){
try {
System.out.println(
"---------------------------\n开始下载");
urlConnection
= (HttpURLConnection)url.openConnection();
responseCode
= urlConnection.getResponseCode();
if(responseCode
==
200){
reader
=
new BufferedReader(
new InputStreamReader(urlConnection.getInputStream(),
"UTF-8"));
StringBuilder sbPage
=
new StringBuilder();
while((line
=reader.readLine())
!=null){
sbPage.append(line);
}
System.out.println(
"下载结束\n保存页面");
saveDownPage(sbPage.toString());
}
else{
System.out.println(
"未找到网页。");
}
}
catch (IOException e) {
// TODO Auto-generated catch block
System.out.println(e.toString());
e.printStackTrace();
}
}
/**
* 根据String类型的URL下载页面
* @param urls
*/
public
void getPage(String urls){
setURL(urls);
getPage();
}
/**
* 根据URL下载页面
* @param urls
*/
public
void getPage(URL urls){
url
= urls;
getPage();
}
/**
* 保存下载下来的网页
*/
private
void saveDownPage(String code){
code
= removeExcess(code);
try{
savePath
= Config.FILE_PATH;
sdf
=
new SimpleDateFormat(
"yyyy-MM-dd-HH-mm-ss-SS");
fileName
= sdf.format(
new Date())
+
".txt";
fos
=
new FileOutputStream(
new File(savePath, fileName));
bos
=
new BufferedOutputStream(fos);
bos.write(code.getBytes());
bos.flush();
bos.close();
fos.close();
System.out.println(fileName
+
"保存完成\n---------------------");
}
catch(Exception e){
System.out.println(e);
}
}
/**
* 删除无用的标签
* @param code
* @return
*/
private String removeExcess(String code){
code
= code.replaceAll(
"<script[^>]*?>.*?</script>",
"");
code
= code.replaceAll(
"<style[^>]*?>.*?</style>",
"");
code
= code.replaceAll(
"<link(.*?)/>",
"");
code
= code.replaceAll(
"style=\"(.*?)\"",
"");
return code;
}
/**
* 设置将要下载的URL
*/
private
void setURL(String urls){
try{
url
=
new URL(urls);
}
catch (Exception e) {
// TODO: handle exception
System.out.println(e.toString());
}
}
}
去除标记选用正则匹配。在正则匹配的时候由于要匹配例如:<a href="#" style="border:0;">这样的标记中的style属性,最初用的正则是:style=\".*\"。
后来发现这样做,会把第一个style属性之后到最后一个引号之前的全部数据都清楚掉,不知道当时怎么脑袋想出这么XX的正则来,不过正则一直也算是短板,用万能的google得知了一个有用的方式:(.*?)。
这是正则中的分组,这样就把表达两个相邻引号中的所有数据了,就不用一直匹配到最后一个引号。
同样的,简单的匹配IP地址格式的数字可以用:(\d{1,3}\.){3}\d{1,3}。
这个正则前面的(\d{1,3}\.)就是一个分组,加上后面的{3}就表示这个分组重复3次。分组中的内容表示一到三位数字后面跟一个点。
当然,这种匹配对IP地址来说是有问题的,因为它可以匹配出999.999.999.999这样的数据,所以这里只是举例说明分组,并不是真正的IP匹配。真正的IP匹配正则是:((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)