最近使用jsoup扒了几个网站,感觉bug改的差不多了,于是写出来与大家分享.
首先我会把爬虫基础的爬取思路与部分重要方法展示出来,最后我会把全部代码贴出来.并且我会写一个Main类,里面就是爬虫的模板,改一下目标网站的url和一些有注释的参数就行了
maven依赖:
jsoup爬虫需要maven的依赖包:
<!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.10.3</version>
</dependency>
爬取思路:
1.使用爬虫爬取一个连接的html代码对于我们jsoup很容易:
private Element getElement(String url) {
try {
return Jsoup.connect(url)
.userAgent("Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)")
.timeout(3000)
.get();
} catch (Exception e) {
}
return null;
}
这样,就可以把url连接的全部代码爬下来,然后返回一个Element类2.然后就是分析目标中的<a>标签了,a标签的href属性就是我们要继续爬取的敲门砖:
Elements linksElements = element.select("a[href]");//得到所有的a标签的链接
for (Element oneLink : linksElements) {
}
这里,可以直接得到一个可以循环执行的linksElements对象,从循环体中的oneLink提取出有效的url:
String url = oneLink.attr("abs:href");
这就得到了目标网站有效的url.
然后需要做的就是判断这个链接是否被扒过,所以我使用了一个HashMap来检测,把url作为key,这样就可以判断是否遇到相同的key.
private HashMap<String, Integer> links;//用来记录所有的链接与层次,防止出现重复
那么总体思路说完了,我们要怎么拿到最后的运行结果呢?
答案 : 我仍旧使用了HashMap<String, HashMap<Integer, String>>,爬取结束后,所有的结果都会保存在这里,这个的含义是< 域名 , < 层数 , HTML代码 > >.
域名就是目标url啦,层数是指我们递归爬取的时候在第几次递归时找到它的,HTML代码就是它的目标代码.在Main函数里我还会把它们都提取出来,让大家可以直接使用就好了.
重要方法:
public Spider2(String url,int level)
这是构造函数,这里的url就是指定网站的url,这里的level是指递归爬取的层数,如果确定要爬取整站代码,不但要设置level为一个很大的数,而且还有一点需要注意,后面会有警告,请认真阅读,防止目标网站有外站连接,这样就尴尬了
public Spider2(String url, int level) {
if (!url.equals("") && level >= 0) {
this.links = new HashMap<String, Integer>();
this.url = url;
this.level = level;
this.allHtml = new HashMap<String, HashMap<Integer, String>>();
this.onlyGetDown = false;
this.topUrl = "";
}
}
HashMap< String , HashMap < Integer , String > > run()
这是最核心的代码,没有特殊需求,直接运行这个代码就可以拿到整站代码了.
如果有任何错误,比如目标网站某些网页有403或404等问题,都会记录在工作目录的一个文件"exception.txt"下,方便爬取完成后看到错误反馈
public HashMap<String, HashMap<Integer, String>> run() {
try {
Element element = getElement(url);
links.put(url, 0);
getLevelLinks(element, 0, url);
return allHtml;
} catch (Exception e) {
/*将错误的信息打印到当前目录的 exception.txt 文件中*/
try {
FileOutputStream os = new FileOutputStream("exception.txt", true);
os.write(e.toString().getBytes("utf-8"));
os.write("\r\n".getBytes());
} catch (Exception e1) {
System.err.println(e1);
}
}
return null;
}
void setOnlyGetDown()
void setOnlyGetDown(String topUrl)
这是为了有针对性的爬取指定规模的代码而定制的: /*只从指定的部分url路径往下爬去*/
/*例如url为 : http://www.??.com/a/b/xxxx.html
* 所有的爬去网页都会是 : http://www.??.com/a/......*/
public void setOnlyGetDown() {
this.setOnlyGetDown("", true);
}
public void setOnlyGetDown(String topUrl) {
this.setOnlyGetDown(topUrl, false);
}
private void setOnlyGetDown(String topUrl, boolean auto) {
this.onlyGetDown = true;
if (auto) {
String[] strings = url.split("/");
{
System.out.println((strings[strings.length - 1]).length());//12
System.out.println((strings[strings.length - 2]).length());//2
}
int length = url.length() - strings[strings.length - 1].length() - strings[strings.length - 2].length() - 1;
this.topUrl = url.substring(0, length);
} else {
this.topUrl = topUrl;
}
}
注意:
如果要爬取整站代码:
- 构造函数中 level 为一个较大值
- setOnlyGetDown(String topUrl) 最好使用这个方法,设置一个目标网站的顶级域名,防止爬到外站的连接
Main:
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
/*1.爬取一大堆html网页或是目标网站的一些网页*/
String[] urlLists = new String[]{//这里是所有需要爬取的网页
"http://blog.csdn.net/experts.html",
};
for (String anUrl : urlLists) {
Spider2 spider = new Spider2(anUrl, 3);//会向下寻找3层
/*下面就可以得到 allHtml : < url , < 当前url所处的层 , HTML代码 > >*/
spider.setOnlyGetDown();
HashMap<String, HashMap<Integer, String>> allHtml = spider.run();//存放着N层的全部html代码
for (Object firstMap : allHtml.entrySet()) {
Map.Entry entry = (Map.Entry) firstMap;
String key = (String) entry.getKey();//得到url
/*得到 levHtml 与*/
Map<Integer, String> levHtml = allHtml.get(key);
for (Object scendMap : levHtml.entrySet()) {
Map.Entry entry1 = (Map.Entry) scendMap;
int level = (Integer) entry1.getKey(); //得到层数level
String html = (String) entry1.getValue();//得到html
}
}
}
/*2.爬取整站代码*/
Spider2 spider = new Spider2(urlLists[0], 888888);
spider.setOnlyGetDown("http://blog.csdn.net/");//爬取目标网站的顶级,防止外部的链接
HashMap<String, HashMap<Integer, String>> allHtml = spider.run();//存放着目标网站所有能够找到的html代码
//然后就可以做一些事情了,吼吼吼~~~~
}
}
真正的爬虫类
Splider2:
import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
public class Spider2 {
private HashMap<String, HashMap<Integer, String>> allHtml;//最终要得到的东西<url,<层级,html>>
private String url;//传入的url
private HashMap<String, Integer> links;//用来记录所有的链接与层次,防止出现重复
private int level;//递归的遍历多少层
private boolean onlyGetDown;//遍历层次时是否只选取指定域名以下的域名
private String topUrl;//遍历时的顶级域名
public HashMap<String, HashMap<Integer, String>> run() {
try {
Element element = getElement(url);
links.put(url, 0);
getLevelLinks(element, 0, url);
return allHtml;
} catch (Exception e) {
/*将错误的信息打印到当前目录的 exception.txt 文件中*/
try {
FileOutputStream os = new FileOutputStream("exception.txt", true);
os.write(e.toString().getBytes("utf-8"));
os.write("\r\n".getBytes());
} catch (Exception e1) {
System.err.println(e1);
}
}
return null;
}
/*递归的得到所有目标代码,并且将他们保存在 allHtml 中*/
private void getLevelLinks(final Element element, final int level, final String majorUrl) throws IOException {
// < url , < 层级 , html > >
if (element != null) {
allHtml.put(majorUrl, new HashMap<Integer, String>() {
{
put(level, element.html());
}
});
if (level < this.level) {
Elements linksElements = element.select("a[href]");//得到所有的a标签的链接
for (Element oneLink : linksElements) {
String url = oneLink.attr("abs:href");//抽取得到url
if (!onlyGetDown || (onlyGetDown && url.contains(topUrl))) {
if (!links.containsKey(url)) {//如果url没有重复,就要爬去
links.put(url, level);
getLevelLinks(getElement(url), level + 1, url);
}
}
}
//得到 links <String 超链接 , String 超链接的文字>
}
}
}
/*只从指定的部分url路径往下爬去*/
/*例如url为 : http://www.??.com/a/b/xxxx.html
* 所有的爬去网页都会是 : http://www.??.com/a/......*/
public void setOnlyGetDown() {
this.setOnlyGetDown("", true);
}
public void setOnlyGetDown(String topUrl) {
this.setOnlyGetDown(topUrl, false);
}
private void setOnlyGetDown(String topUrl, boolean auto) {
this.onlyGetDown = true;
if (auto) {
String[] strings = url.split("/");
{
System.out.println((strings[strings.length - 1]).length());//12
System.out.println((strings[strings.length - 2]).length());//2
}
int length = url.length() - strings[strings.length - 1].length() - strings[strings.length - 2].length() - 1;
this.topUrl = url.substring(0, length);
} else {
this.topUrl = topUrl;
}
}
/*唯一一个需要联网爬代码的方法*/
private Element getElement(String url) {
try {
return Jsoup.connect(url)
.userAgent("Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)")
.timeout(3000)
.get();
} catch (Exception e) {
}
return null;
}
public Spider2(String url, int level) {
if (!url.equals("") && level >= 0) {
this.links = new HashMap<String, Integer>();
this.url = url;
this.level = level;
this.allHtml = new HashMap<String, HashMap<Integer, String>>();
this.onlyGetDown = false;
this.topUrl = "";
}
}
}
为什么这里叫做Splider2呢?因为Splider1是调试专用类呀.......
注意爬取代码的时候要适可而止哟~~