今年十月份以来,跟朋友尝试导入一些图片到tensorflow来生成模型,这就需要大量的图片。刚开始我只写了一个简单的HttpClient程序来抓取图片,后来为了通用性索性写一个简单的图片爬虫程序。它可以用于抓取单张图片、多张图片、某个网页下的所有图片、多个网页下的所有图片。
github地址:https://github.com/fengzhizi715/PicCrawler
这个爬虫使用了HttpClient、RxJava2以及Java 8的一些特性。它支持一些简单的定制,比如定制User-Agent、Referer、Cookies等。
一.下载安装:
对于Java项目如果使用gradle构建,由于默认不是使用jcenter,需要在相应module的build.gradle中配置
repositories {
mavenCentral()
jcenter()
}
Gradle:
compile 'com.cv4j.piccrawler:crawler:0.2.1'
Maven:
<dependency>
<groupId>com.cv4j.piccrawler</groupId>
<artifactId>crawler</artifactId>
<version>0.2.1</version>
<type>pom</type>
</dependency>
二.使用方法:
2.1 下载单张图片
- 普通方式
String url = "..."; // 图片的地址
CrawlerClient.get()
.timeOut(6000)
.fileStrategy(new FileStrategy() {
@Override
public String filePath() {
return "temp";
}
@Override
public String picFormat() {
return "png";
}
@Override
public FileGenType genType() {
return FileGenType.AUTO_INCREMENT;
}
})
.repeat(200) // 重复200次
.build()
.downloadPic(url);
在这里,timeOut()表示网络请求的超时时间。fileStrategy()表示存放的目录、文件使用的格式、生成的文件时使用何种策略。repeat()表示对该图片请求重复的次数。
PicCrawler支持多种文件的生成策略,比如随机生成文件名、从1开始自增长地生成文件名、生成指定的文件名等等。
下图显示了使用该程序对某验证码的图片下载200次。
下载200张验证码的图片.png
- 使用RxJava的方式下载
String url = "..."; // 图片的地址
CrawlerClient.get()
.timeOut(6000)
.fileStrategy(new FileStrategy() {
@Override
public String filePath() {
return "temp";
}
@Override
public String picFormat() {
return "png";
}
@Override
public FileGenType genType() {
return FileGenType.AUTO_INCREMENT;
}
})
.repeat(200)
.build()
.downloadPicUseRx(url);
- 使用RxJava,下载之后的图片还能做后续的处理
String url = "..."; // 图片的地址
CrawlerClient.get()
.timeOut(6000)
.fileStrategy(new FileStrategy() {
@Override
public String filePath() {
return "temp";
}
@Override
public String picFormat() {
return "png";
}
@Override
public FileGenType genType() {
return FileGenType.AUTO_INCREMENT;
}
})
.repeat(200)
.build()
.downloadPicToFlowable(url)
.subscribe(new Consumer<File>() {
@Override
public void accept(File file) throws Exception {
// do something
}
});
在Consumer中,可以对文件做一些后续的处理。
2.2 下载多张图片
List<String> urls = ...; // 多张图片地址的集合
CrawlerClient.get()
.timeOut(6000)
.fileStrategy(new FileStrategy() {
@Override
public String filePath() {
return "temp";
}
@Override
public String picFormat() {
return "png";
}
@Override
public FileGenType genType() {
return FileGenType.AUTO_INCREMENT;
}
})
.build()
.downloadPics(urls);
2.3 下载某个网页的全部图片
String url = "http://www.jianshu.com/u/4f2c483c12d8"; // 针对某一网址
CrawlerClient.get()
.timeOut(6000)
.fileStrategy(new FileStrategy() {
@Override
public String filePath() {
return "temp";
}
@Override
public String picFormat() {
return "png";
}
@Override
public FileGenType genType() {
return FileGenType.AUTO_INCREMENT;
}
})
.build()
.downloadWebPageImages(url);
使用上面的程序,对我简书主页上的图片进行抓取。
简书的主页.png
2.4 下载多个网页的全部图片
List<String> urls = new ArrayList<>(); // 多个网页的集合
urls.add("http://www.jianshu.com/u/4f2c483c12d8");
urls.add("https://toutiao.io/");
CrawlerClient.get()
.timeOut(6000)
.fileStrategy(new FileStrategy() {
@Override
public String filePath() {
return "temp";
}
@Override
public String picFormat() {
return "png";
}
@Override
public FileGenType genType() {
return FileGenType.AUTO_INCREMENT;
}
})
.build()
.downloadWebPageImages(urls);
下载个人简书主页上的图以及开发者头条的图片。
多个网址的图片下载.png
三. 部分源码解析
3.1 下载某个网页的全部图片
downloadWebPageImages()方法表示下载某个url的全部图片。
/**
* 下载整个网页的全部图片
* @param url
*/
public void downloadWebPageImages(String url) {
Flowable.just(url)
.map(s->httpManager.createHttpWithGet(s))
.map(response->parseHtmlToImages(response))
.subscribe(urls -> downloadPics(urls),
throwable-> System.out.println(throwable.getMessage()));
}
downloadWebPageImages()分成三步:创建网络请求、解析出当前页面中包含的图片路径、下载这些图片。
第一步,创建网络请求使用了HttpClient。
public CloseableHttpResponse createHttpWithGet(String url) {
// 获取客户端连接对象
CloseableHttpClient httpClient = getHttpClient();
// 创建Get请求对象
HttpGet httpGet = new HttpGet(url);
if (Preconditions.isNotBlank(httpParam)) {
Map<String,String> header = httpParam.getHeader();
if (Preconditions.isNotBlank(header)) {
for (String key : header.keySet()) {
httpGet.setHeader(key,header.get(key));
}
}
}
CloseableHttpResponse response = null;
// 执行请求
try {
response = httpClient.execute(httpGet);
} catch (IOException e) {
e.printStackTrace();
}
return response;
}
第二步,将返回的response转换成String类型,使用jsoup将带有图片的链接全部过滤出来。
jsoup 是一款Java 的HTML解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,可通过DOM,CSS以及类似于jQuery的操作方法来取出和操作数据。
private List<String> parseHtmlToImages(CloseableHttpResponse response) {
// 获取响应实体
HttpEntity entity = response.getEntity();
InputStream is = null;
String html = null;
try {
is = entity.getContent();
html = IOUtils.inputStream2String(is);
} catch (IOException e) {
e.printStackTrace();
}
Document doc = Jsoup.parse(html);
Elements media = doc.select("[src]");
List<String> urls = new ArrayList<>();
if (Preconditions.isNotBlank(media)) {
for (Element src : media) {
if (src.tagName().equals("img")) {
if (Preconditions.isNotBlank(src.attr("abs:src"))) { // 图片的绝对路径不为空
String picUrl = src.attr("abs:src");
log.info(picUrl);
urls.add(picUrl);
} else if (Preconditions.isNotBlank(src.attr("src"))){ // 图片的相对路径不为空
String picUrl = src.attr("src").replace("//","");
picUrl = "http://"+Utils.tryToEscapeUrl(picUrl);
log.info(picUrl);
urls.add(picUrl);
}
}
}
}
if (response != null) {
try {
EntityUtils.consume(response.getEntity());
response.close();
} catch (IOException e) {
System.err.println("释放链接错误");
e.printStackTrace();
}
}
return urls;
}
第三步,下载这些图片使用了Java 8的CompletableFuture。CompletableFuture是Java 8新增的用于异步处理的类,而且CompletableFuture的性能也好于传统的Future。
/**
* 下载多张图片
* @param urls
*/
public void downloadPics(List<String> urls) {
if (Preconditions.isNotBlank(urls)) {
urls.stream().parallel().forEach(url->{
try {
CompletableFuture.runAsync(() -> downloadPic(url)).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
});
}
}
3.2 下载多个网页的全部图片
downloadWebPageImages()方法还支持传List集合,表示多个网页的地址。
/**
* 下载多个网页的全部图片
* @param urls
*/
public void downloadWebPageImages(List<String> urls) {
if (Preconditions.isNotBlank(urls)) {
Flowable.fromIterable(urls)
.parallel()
.map(url->httpManager.createHttpWithGet(url))
.map(response->parseHtmlToImages(response))
.sequential()
.subscribe(list -> downloadPics(list),
throwable-> System.out.println(throwable.getMessage()));
}
}
在这里其实用到了ParallelFlowable,因为parallel()可以把Flowable转成ParallelFlowable。
总结
PicCrawler 是一个简单的图片爬虫,目前基本可以满足我的需求。未来要是有新的需求,我会不断添加功能。
在做PicCrawler时,其实还做了一个ProxyPool用于获取可用代理池的库,它也是基于RxJava2实现的。
网友评论
欢迎订阅《Tony沈哲的独家号》https://toutiao.io/subjects/5688