Crawler4j 是一个开源的 Java 类库提供一个用于抓取 Web 页面的简单接口。可以利用它来构建一个多线程的 Web 爬虫。

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<dependency>
<groupId>edu.uci.ics</groupId>
<artifactId>crawler4j</artifactId>
<version>4.2</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.8.2</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.12</version>
</dependency>

资源下载工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import java.io.File;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.impl.client.HttpClientBuilder;
/**
* 资源下载工具
*
* @author fanlychie
*/
public class HttpClientUtils {
private static Logger log = LoggerFactory.getLogger(HttpClientUtils.class);
/**
* 下载资源文件
*
* @param url
* 资源文件地址
* @param storeFolder
* 下载的文件存放的目录
*/
public static void download(String url, String storeFolder) {
String filename = null;
if (StringUtils.isNotBlank(url)) {
filename = url.substring(url.lastIndexOf("/") + 1, url.length());
}
download(url, storeFolder, filename);
}
/**
* 下载资源文件
*
* @param url
* 资源文件地址
* @param storeFolder
* 下载的文件存放的目录
* @param filename
* 下载的文件的名称
*/
public static void download(String url, String storeFolder, String filename) {
try {
if (StringUtils.isBlank(url)) {
throw new IllegalArgumentException("外部链接地址为空");
}
// HttpClient
HttpClient httpClient = HttpClientBuilder.create().build();
// GET 方法请求
HttpUriRequest request = RequestBuilder.get().setUri(url).build();
// 执行 GET 请求
HttpResponse response = httpClient.execute(request);
// 状态码
int statusCode = response.getStatusLine().getStatusCode();
// 200 OK
if (statusCode == HttpURLConnection.HTTP_OK) {
log.info("downloading {} ( {} KB )", url, response.getEntity().getContentLength() / 1024);
// 下载资源的临时文件名称
String fileTempName = filename + ".temp";
File file = new File(storeFolder, fileTempName);
downloadFile(response.getEntity().getContent(), new FileOutputStream(file));
// 下载成功后重命名文件
file.renameTo(new File(storeFolder, filename));
}
} catch (Throwable e) {
log.error("下载外部资源失败!", e);
}
}
/**
* 下载文件
*
* @param in
* 输入流
* @param out
* 输入流
* @throws Throwable
*/
private static void downloadFile(InputStream in, OutputStream out) throws Throwable {
byte bytes[] = new byte[1024 * 1024 / 4];
int read;
while ((read = in.read(bytes)) != -1) {
out.write(bytes, 0, read);
}
out.close();
}
}

log4j

1
2
3
4
5
6
log4j.rootLogger = INFO, console
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.target = System.out
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.conversionPattern = %p %d{yyyy-MM-dd HH:mm:ss} %c : %L %n%m%n%n
log4j.logger.org.apache.http = WARN

网络爬虫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import org.jsoup.Jsoup;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.fanlychie.util.HttpClientUtils;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import edu.uci.ics.crawler4j.crawler.Page;
import edu.uci.ics.crawler4j.crawler.WebCrawler;
import edu.uci.ics.crawler4j.parser.HtmlParseData;
import edu.uci.ics.crawler4j.url.WebURL;
/**
* 网络爬虫
*
* @author fanlychie
*/
public class MyWebCrawler extends WebCrawler {
private final static Pattern FILTERS = Pattern.compile(".*(\\.(css|js|gif|jpg|png))$");
/**
* 哪些页面可以被爬取
*/
@Override
public boolean shouldVisit(Page page, WebURL url) {
String href = url.getURL().toLowerCase();
// 链接地址不是 css, js, 图片且链接地址必须以 http://www.hai360.com/shoes-clothes.html 开始的链接才爬取
return !FILTERS.matcher(href).matches() && href.startsWith("http://www.hai360.com/shoes-clothes.html");
}
/**
* 当某个页面被爬取时该方法被调用
*/
@Override
public void visit(Page page) {
if (page.getParseData() instanceof HtmlParseData) {
HtmlParseData htmlParseData = (HtmlParseData) page.getParseData();
// 爬取到的 HTML 文本内容
String html = htmlParseData.getHtml();
Document doc = Jsoup.parse(html);
// 解析 class="list-item" 节点下的 img 标签
// 其中某一个的 img 标签内容如下
// <img data-src="http://aimg.hai360.com/item/20151113/140613/617251.jpg@!250"
// width="250" height="250" alt="(秒杀国内价 国内同款1459)Kenneth Cole 凯尼斯·柯尔男士休闲皮鞋">
Elements es = doc.select(".list-item img");
// 迭代 class="list-item" 节点下的 img 标签
for (Element e : es) {
// 商品图片的地址
String url = e.attr("data-src");
// 商品图片的名称
String filename = e.attr("alt");
// 商品图片文件的后缀格式名
String suffix = url.substring(url.lastIndexOf("/") + 1, url.length());
suffix = suffix.substring(suffix.lastIndexOf("."), suffix.length());
filename += suffix.substring(0, 4);
// 下载商品图片
HttpClientUtils.download(url, "D:/crawl/hai360/images", filterStr(filename));
}
}
}
/**
* 过滤特殊字串
*
* @param str
* 源字串
* @return 返回过滤后的字串
*/
private static String filterStr(String str) {
String regex = "[`~!@#$%^&*()+=|{}':;',//[//].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?]";
Matcher m = Pattern.compile(regex).matcher(str);
return m.replaceAll("").trim();
}
}

启动爬虫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import edu.uci.ics.crawler4j.crawler.CrawlConfig;
import edu.uci.ics.crawler4j.crawler.CrawlController;
import edu.uci.ics.crawler4j.fetcher.PageFetcher;
import edu.uci.ics.crawler4j.robotstxt.RobotstxtConfig;
import edu.uci.ics.crawler4j.robotstxt.RobotstxtServer;
/**
* 启动爬虫
*
* @author fanlychie
*/
public class Controller {
public static void main(String[] args) throws Exception {
// 爬取过的 URL 记录目录
String crawlStorageFolder = "D:/crawl/hai360";
// 线程数
int numberOfCrawlers = 10;
CrawlConfig config = new CrawlConfig();
config.setCrawlStorageFolder(crawlStorageFolder);
config.setResumableCrawling(true);
PageFetcher pageFetcher = new PageFetcher(config);
RobotstxtConfig robotstxtConfig = new RobotstxtConfig();
RobotstxtServer robotstxtServer = new RobotstxtServer(robotstxtConfig, pageFetcher);
CrawlController controller = new CrawlController(config, pageFetcher, robotstxtServer);
// 种子地址, 可以添加多个
controller.addSeed("http://www.hai360.com/shoes-clothes.html");
// 启动爬虫
controller.start(MyWebCrawler.class, numberOfCrawlers);
}
}

以上为爬取海360网站的商品图片,种子地址为:http://www.hai360.com/shoes-clothes.html

CrawlConfig

配置项 描述
crawlStorageFolder 用于记录爬取过的 URL 目录
resumableCrawling 若设置为 true,重新启动程序可恢复爬取过程中意外发生终止的爬虫,默认为 false
maxDepthOfCrawling      设置爬取的深度。默认为 -1,即没有限制。第一个页面的深度为 0,第一个页面由超链接到达的第二个页面,则第二个页面的深度为 1,以此类推
maxPagesToFetch 设置页面抓取的最大数量。默认为 -1,即没有限制
userAgentString 向 Web 服务器表明你的爬虫。默认为 crawler4j (https://github.com/yasserg/crawler4j/)
politenessDelay 请求前等待的毫秒数。默认为 200 毫秒。由于 crawler4j 并发高效的抓取能力,会给 Web 服务器带来一定量的压力,这可能会使 Web 服务器阻断你的请求
maxTotalConnections 设置最大连接数,默认为 100
connectionTimeout 设置连接超时,单位毫秒,默认为 30000
socketTimeout 设置读取超时,单位毫秒,默认为 20000
proxyHost 代理相关,代理服务器
proxyPort 代理相关,代理服务器端口
proxyUsername 代理相关,代理服务器账户
proxyPassword 代理相关,代理服务器密码

爬虫结果