最近几天突然想学习一下爬虫技术,首先想到的是用python来做,于是安装了python3.6,装好之后又下了别人的python代码来研究,发现缺少各种模块,比如scrapy、requests、urlib2 …,报各种错误,比如需要安装MicrosoftVisualC++14.0什么的(关于安装MicrosoftVisualC++14见MicrosoftVisualC++14.0 安装 ),折腾了好几天才把各种模块安装好。这还不算,有些模块不兼容python3,或者被整合到另一个模块中,用了4天时间,竟然还没让下载的代码跑起来,于是就放弃了,还是回归我的老本行Java的怀抱。
稍微搜索了一下网上的Java爬虫demo,学习改写了一下,正好用上了前几天学习的多线程,用半天时间做了一个包含多线程的爬虫。主要使用的是一个爬虫工具Jsoup:
Jsoup是一款 Java 的 HTML 解析器,可直接解析某个 URL 地址、HTML 文本内容。它提供了一套非常省力的 API,可通过 DOM,CSS 以及类似于 jQuery 的操作方法来取出和操作数据。
jsoup 的主要功能如下:
1. 从一个 URL,文件或字符串中解析 HTML;
2. 使用 DOM 或 CSS 选择器来查找、取出数据;
3. 可操作 HTML 元素、属性、文本;
maven导入:
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.10.1</version>
</dependency><dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
简单版本:
@Test
public void dd() {String UA =”mozilla/5.0 (windows nt 10.0; wow64) applewebkit/537.36 (khtml, like gecko) chrome/65.0.3325.181 safari/537.36″;
Connection.Response res = null;
String src=”http://pic.qiushibaike.com/system/pictures/12050/120503978/medium/app120503978.jpeg”;
try {
res = Jsoup.connect(src)
.maxBodySize(3000000)
.userAgent(UA)
.validateTLSCertificates(false)
.ignoreContentType(true).timeout(30000).execute();
byte[] bytes = res.bodyAsBytes();
File file = new File(“d:\\1.png”);
if (!file.exists()) {
RandomAccessFile raf = new RandomAccessFile(file, “rw”);
raf.write(bytes);
raf.close();
}
} catch (IOException e1) {
e1.printStackTrace();
}
}
复杂版本代码:
package com.pachong2;
/**
* 糗百热图
* @author jiangxin
* @date 2018年6月2日
*/import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;import org.apache.commons.io.FileUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;public class QiubaiRetu {
//图片Src 的值,防止下重复了
private List<String> imgAllSrcVector=new Vector<>();
//图片下载的位置
String preDownloadFile =”D:\\下载\\pic\\qiubai\\”;
//本线程下载的位置
ThreadLocal<String> downloadFilePath=new ThreadLocal<>();
//需要爬取的路径,后面加数字就能获取相应页码的网页
String url=”https://www.qiushibaike.com/imgrank/page/”;
//浏览器请求头
final static String UA =”mozilla/5.0 (windows nt 10.0; wow64) applewebkit/537.36 (khtml, like gecko) chrome/65.0.3325.181 safari/537.36″;
//本线程下载的页码
private int page=0;
//总共下载页数,同时作为线程数量
static int N;
//成功下载图片数量
static volatile int successDownNum=0;
//门闩 ,用于所有线程结束后统计成功下载的数量
CountDownLatch latch;
public QiubaiRetu(int page,CountDownLatch latch) {
this.page=page;
this.latch=latch;
}
public void m1() {
try {
System.out.println(“准备获取 url —-“+url+page+”/”);
//获取第page页的网页
Document doc=Jsoup.connect(url+page+”/”).userAgent(UA).get();
//保持本页面的html
//saveDoc(doc, “D:\\下载\\pic\\qiubai\\qiushiretu”+page+”.html”);
//获取正文部分的图片路径
List<String> urlList=getUrl( doc);
//将路径转为 名称路径键值对 name–url
Map<String, String> nameUrl=getNameAndUrl(urlList);
System.out.println(“nameUrl—“);
downloadFilePath.set(preDownloadFile+page+”\\”);
makedir(downloadFilePath.get());
//遍历图片下载
Set<String> keys=nameUrl.keySet();
for (String key : keys) {
//通过路径和图片名称下载图片
downPic(nameUrl.get(key), key);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
latch.countDown();
}
}
/**
* 获取正文部分的图片路径
* @author jiangxin
* @date 2018年6月2日下午9:30:06
* @param doc
* @return
*/
public List<String> getUrl(Document doc) {
List<String> urlList=new ArrayList<>();
//获取图片
Elements contentEle=doc.select(“#content-left .article .thumb img”);
//循环,获取每个帖子的div
for (Element element : contentEle) {
//有图片
if(element!=null) {
//获取路径
// src= “//pic.qiushibaike.com/system/pictures/10399/103995772/medium/app103995772.jpg”
String src=element.attr(“src”) ;
//并且图片没有在所有图片集合中
if(!imgAllSrcVector.contains(src)) {
imgAllSrcVector.add(src);
urlList.add(src);
}
}
}
return urlList;
}
/**
* 将路径转为 名称路径键值对 name–url
* @author jiangxin
* @date 2018年6月2日下午9:56:57
* @param urlList
* @return
*/
public Map<String, String> getNameAndUrl(List<String> urlList) {
Map<String,String> nameAndUrl=new HashMap<>();
for (String url : urlList) {
//获取图片名称
String imageName =url.substring(url.lastIndexOf(“/”) + 1, url.length());
//如果没有图片名称的话,直接结束本循环
if (imageName == null || imageName.length() == 0) {
continue;
}
nameAndUrl.put(imageName, url);
}
return nameAndUrl;
}
/**
* 通过路径和图片名称下载图片
* @author jiangxin
* @date 2018年6月2日下午10:05:08
* @param url
* @param name
*/
public void downPic(String imgUrl,String name) {
InputStream is = null;
OutputStream os = null;
// 连接url
try {
URL url = new URL(“https:”+imgUrl);
URLConnection uri = url.openConnection();
// 获取数据流
is = uri.getInputStream();
// 写入数据流
os = new FileOutputStream(new File(downloadFilePath.get(), name));
byte[] buf = new byte[1024];
int length = 0;
while ((length = is.read(buf, 0, buf.length)) != -1) {
os.write(buf, 0, length);
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
System.out.println(“图片有问题,下载不了:”+imgUrl);
} catch (IOException e) {
System.out.println(“图片有问题,下载不了:”+imgUrl);
}finally {
try {
if (os != null) {
os.close();
}
if (is != null) {
is.close();
}
} catch (IOException e) {
System.out.println(“——关闭流发生异常—–“);
}
}
successDownNum++;
}
/**
* 创建文件夹
* @author jiangxin
* @date 2018年6月3日下午1:07:01
* @param filesName
* @return
*/
public String makedir(String filesName) {
// 定义文件夹路径
String filePath = filesName;
File file = new File(filePath);
if (!file.exists() && !file.isDirectory()) {
file.mkdirs(); // 创建文件夹 注意mkdirs()和mkdir()的区别
// 判断是否创建成功
if (file.exists() && file.isDirectory()) // 文件夹存在并且是文件夹
{
System.out.println(filesName + ” 文件夹创建成功!”);
return filePath;
} else {
System.out.println(filesName + ” 文件创建不成功!”);
return filesName;
}
} else {
System.out.println(filesName + ” 文件已经存在!”);
return filePath;
}
}
/**
* 保存页面html
* @author jiangxin
* @date 2018年6月3日下午1:05:43
* @param doc
* @param filePath
*/
public void saveDoc(Document doc,String filePath) {
try {
FileUtils.write(new File(filePath), doc.html());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
//设置线程个数,将爬取从1-N页的图片
N=20;
CountDownLatch latch=new CountDownLatch(N);
int pageInit=0;
//起10个线程,获取10个页面的
for (int i = 1; i <= N; i++) {
pageInit++;
QiubaiRetu qiubaiRetu=new QiubaiRetu( pageInit,latch);
new Thread(()->qiubaiRetu.m1()).start();
try {
//如果线程相隔太近,反爬虫机制会拒接服务,报503错误
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
latch.await();
System.out.println(“结束 –下载数量:”+successDownNum);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结果:
后记:我用这个方法去爬取1024的图片,发现总是报
java.net.SocketException: Software caused connection abort: recv failed
或者下载的图片只有4kb,打开提示无效图片,尝试了很多网上提供的方法也无法解决,应该是网站的发爬虫机制在起作用,以后继续看看有什么办法突破一下吧。
就酱,我的第一个爬虫。
—————————————————2018年6月3日18:16:20- 修改—————————–
1024中某些图片的地址并不是真实地址,进入此地址进一步查看,里面是一个网页(网页中只有一张图片)。再次获取地址后,用这个src能够正确下载,不报错。所以我认为SocketException 有可能是使用
URL url = new URL(src);
inputStream = uri.getInputStream();
获取内容,当src指向一个网页时的异常。