java爬虫——爬取网站图片

目录

  • 问题:java能否实现爬虫,如何爬取
  • jsoup简介
    • 获取jsoup
      • Maven
      • Gradle
    • jsoup几个常用的类
      • Document
      • Element及Elements
  • 解决办法:jsoup实现爬虫功能
    • 获取目标网站的html
    • 解析html并获取图片url
    • 下载图片
    • 获取本页面所有链接中的所有图片
    • 实现图形化界面
  • 代码展示
  • 应用展示
  • 总结
  • 参考资料

问题:java能否实现爬虫,如何爬取

在学习爬虫的时候,我是从python入门的。爬虫的原理也不难,获取服务器返回的html文件,然后通过正则表达式对html字符串进行解析,至于想要获得什么信息,则是看自己的业务逻辑,比如获取网站上所有邮箱,所有QQ号等。而我则是想爬取网站上所有图片。
一开始学习爬虫,想要获取网站上一些元素就需要自己写正则表达式筛选了,比如获取所有链接元素,(".*")。但是随着学习的深入,了解到python有Beautiful Soup这个库,提供了很多方便的方法来获取网站的元素,因本人对python了解不多,浅尝辄止,就不再妄论。
此时我就想,难道java不能获取html进行解析吗,就没有实用的工具类进行调用吗?毕竟java是做网站开发的利器啊。果然,java提供了jsoup工具来做爬虫开发,而且个人觉得很好用。

jsoup简介

想要深入学习jsoup的同学可以去jsoup官网查看文档学习,使用非常简单,不难看懂。这里我先简单介绍一下。
jsoup是用于处理实际HTML的Java库。它提供了使用DOM,CSS和类似jquery的最好方法提取和处理数据的非常方便的API。
jsoup实现WHATWG HTML5规范,并将HTML解析为与现代浏览器相同的DOM。

  • 从URL,文件或字符串中抓取并解析 HTML
  • 使用DOM遍历或CSS选择器查找和提取数据
  • 处理 HTML元素,属性和文本
  • 根据安全的白名单清除用户提交的内容,以防止XSS攻击
  • 输出整洁的HTML

获取jsoup

Maven

<dependency>
  <!-- jsoup HTML parser library @ https://jsoup.org/ -->
  <groupId>org.jsoup</groupId>
  <artifactId>jsoup</artifactId>
  <version>1.12.1</version>
</dependency>

Gradle

// jsoup HTML parser library @ https://jsoup.org/
compile 'org.jsoup:jsoup:1.12.1'

当然你也可以通过自行下载jsoup-1.12.1.jar,并把它放入你的项目中。

jsoup几个常用的类

Document

Document是jsoup解析完html之后返回的一个对象,由Elements和TextNode组成,封装好了很多方法,可以通过它很方便地获取指定元素。获取Document对象也很简单。

  • 从URL加载Document
Document doc = Jsoup.connect("http://example.com")
  .data("query", "Java")
  .userAgent("Mozilla")
  .cookie("auth", "token")
  .timeout(3000)
  .post();//只需要connect()就行了,后面的方法指定http请求的一些属性,使用默认的亦可
  • 从字符串加载Document
String html = "First parse"
  + "

Parsed HTML into a doc.

"
; Document doc = Jsoup.parse(html);
  • 从文件加载Document
File input = new File("/tmp/input.html");
Document doc = Jsoup.parse(input, "UTF-8", "http://example.com/");

该parse(File in, String charsetName, String baseUri)方法加载并解析HTML文件。如果在加载文件时发生错误,它将抛出一个IOException,您应该适当地处理它。

baseUri解析器使用该参数在找到元素之前解析文档中的相对URL 。如果您不担心此问题,可以改为传递一个空字符串。

有一种姐妹方法parse(File in, String charsetName),该方法使用文件的位置作为baseUri。如果您在本地文件系统站点上工作,并且指向该站点的相对链接也在文件系统上,则此功能很有用。

Element及Elements

获得了Document之后,我们可以通过它做一些更有趣的事情,比如获取特定元素。Element就是这些元素在java的特定实现,而Elements可以理解为Element的一个容器。获取元素方法也很简单,使用了jQuery和css的选择器,对前端熟悉的很容易理解这一点,不熟悉前端技术的也不用害怕,因为这并不难。

  • 通过元素id获得Element
//获取id为content的元素
Element content = doc.getElementById("content");
  • 通过元素class获得Elements
//获取class为confirmButton的所有元素
Elements confirmButtons= doc.getElementsByClass("confirmButton");
  • 通过元素tag获得Elements
//获取所有链接
Elements links = doc.getElementsByTag("a");
  • 还有很多,不再赘述
    • getElementsByAttribute(String key)
    • 元素的兄弟姐妹:siblingElements(),firstElementSibling(),lastElementSibling(),nextElementSibling(),previousElementSibling()
    • parent(),children(),child(int index)

获取Element后,我们就可以使用Element封装的一些方法获取元素中的信息,比如链接<\a>的地址href。

Elements links = content.getElementsByTag("a");
for (Element link : links) {
     
  String linkHref = link.attr("href");
  String linkText = link.text();
}

这些是Element的常用方法:

  • attr(String key)获取和attr(String key, String value)设置属性
  • attributes() 获取所有属性
  • id(),className()和classNames()
  • text()获取并text(String value)设置文本内容
    html()获取并html(String value)设置内部HTML内容
  • outerHtml() 获得外部HTML值
  • data()获取数据内容(例如script和style标签)
  • tag() 和 tagName()

解决办法:jsoup实现爬虫功能

熟悉了jsoup之后,我们就可以很方便的实现自己的爬虫工具了。

获取目标网站的html

//这里我们没有通过jsoup的方法获取,而是使用HttpClient,效果是一样的
public String getHtml(String myURL) {
     
       CloseableHttpClient httpClient = HttpClients.createDefault();
       CloseableHttpResponse response = null;
       String html="";
       HttpGet request = new HttpGet(myURL);
       request.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36");
       
       try {
     
           //3.执行get请求,相当于在输入地址栏后敲回车键
           response = httpClient.execute(request);
           
           //4.判断响应状态为200,进行处理
           if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
     
               //5.获取响应内容
               HttpEntity httpEntity = response.getEntity();
               html = EntityUtils.toString(httpEntity, "utf-8");
           } else {
     
               //如果返回状态不是200,比如404(页面不存在)等,根据情况做处理,这里略
               System.out.println("返回状态不是200");
               System.out.println(EntityUtils.toString(response.getEntity(), "utf-8"));
           }
       } catch (ParseException | IOException e) {
     
            e.printStackTrace();
       } finally {
     
           //6.关闭
           HttpClientUtils.closeQuietly(response);
           HttpClientUtils.closeQuietly(httpClient);
       }
       return html;
}

解析html并获取图片url

public void getImages(String html,String myURL) {
     
	Document document = Jsoup.parse(html);
	//像js一样,通过标签获取title
       //获取所有图片
       Elements imgs=document.getElementsByTag("img");
       for(Element img:imgs) {
     
       	String imgUrl = img.attr("src");
       	//无协议添加协议
       	if(imgUrl.startsWith("//")) {
     
       		imgUrl="http:"+imgUrl;
       	}
       	//相对地址转绝对地址,getHostName()是获得网站域名
       	else if(imgUrl.startsWith("/")){
     
			imgUrl=getHostName(myURL)+imgUrl;
		}
        //如果网址为空或者下载过这张图片(HashMap中已经有URL)就跳过
       	if(imgUrl==null||imgUrl.equals("")||imgURLMap.containsKey(imgUrl)) {
     
       		continue;
       	}
       	System.out.println(imgUrl);
       	//通过url下载图片
       	if(imgUrl.startsWith("http")) {
     
       		downImagesByHttp(imgUrl);
       	}
       	//通过base64解码下载图片
       	else {
     
			downImagesByBase64(imgUrl);
		}
       }
}
private String getHostName(String myURL) {
     
	return myURL.substring(0,myURL.indexOf("/",8));
}

下载图片

private void downImagesByHttp(String imgUrl){
     
	imgURLMap.put(imgUrl, mapValue);
	String fileName = imgUrl.substring(imgUrl.lastIndexOf("."));
	HttpURLConnection connection=null;
	InputStream is = null;
	File file=null;
	FileOutputStream out = null;
	
	try {
     
		URL url = new URL(imgUrl);
		connection = (HttpURLConnection)url.openConnection();
		if(connection.getContentLength()>imgSize) {
     
			is = connection.getInputStream();
			if(fileName.matches(".+?((png)|(jpg)|(jpeg)|(gif)|(svg))$")) {
     
				file=new File(filePath+"zsy"+UUID.randomUUID().toString().substring(28)+fileName);
			}
			else {
     
				file=File.createTempFile("zsy", ".png",new File(filePath));
			}
			out = new FileOutputStream(file);
			int i = 0;
			while((i = is.read()) != -1){
     
				out.write(i);
			}
		}
	} catch (Exception e) {
     
		 e.printStackTrace();
	}
	finally {
     
		try {
     
			connection.disconnect();
			out.close();
			is.close();
		} catch (Exception e) {
     
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
//这里的Base64类来自Apache Commons Codec,可以通过Maven获取,用于解码Base64
/*
这个是它的gav


    commons-codec
    commons-codec
    1.12

*/
private void downImagesByBase64(String imgUrl) {
     
	// TODO Auto-generated method stub
	String fileName="."+imgUrl.substring(imgUrl.indexOf('/')+1, imgUrl.indexOf(';'));
	String fileBase64=imgUrl.substring(imgUrl.indexOf(',')+1);
	File file=null;
	FileOutputStream out = null;
	try {
     
		file=new File(filePath+"zsy"+UUID.randomUUID().toString().substring(28)+fileName);
		out = new FileOutputStream(file);
		byte[] b=Base64.decodeBase64(fileBase64);
		out.write(b);
	} catch (Exception e) {
     
		 e.printStackTrace();
	}
	finally {
     
		try {
     
			out.close();
		} catch (Exception e) {
     
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

获取本页面所有链接中的所有图片

如果只是获得当前URL的图片,那程序就结束了。但是爬虫的厉害之处在于应当能够通过链接爬取其他网页的内容,所以应该获取当前页面的所有链接,但是注意不能重复,同样通过HashMap来存储已经访问过的URL。

public void getAllImages(String myURL) {
     
	System.out.println(myURL);
	String html=getHtml(myURL);
	getImages(html,myURL);
	URLMap.put(myURL,mapValue);
	String hostName=getHostName(myURL);
	LinkedList<String> curURLs=new LinkedList<>();
	Document document = Jsoup.parse(html);
	Elements links=document.getElementsByTag("a");
	for(Element link:links) {
     
		String nextLink=link.attr("href");
		if(nextLink.startsWith("http")||nextLink.startsWith("/")) {
     
			if(nextLink.startsWith("/")) {
     
				nextLink=hostName+nextLink;
			}
			if(!URLMap.containsKey(nextLink)) {
     
				curURLs.add(nextLink);
			}
		}
	}
	while(!curURLs.isEmpty()) {
     
		String curURL=curURLs.pollFirst();
		pool.submit(()->{
     
			getAllImages(curURL);
		});
	}
}

实现图形化界面

通过调用getAllImage(URL),即可把图片全部下载下来,我们实现GUI界面更友好的接受数据(URL,文件存储位置)。

public void init() {
     
		JFrame uiFrame=new JFrame("世缘科技");
    	JPanel panelURL=new JPanel();
    	JTextField urlField=new JTextField(40);
    	JButton start=new JButton("开始");
    	JButton stop=new JButton("结束");
    	panelURL.add(urlField);
    	panelURL.add(start);
    	panelURL.add(stop);
    	
    	//urlField.setText("");
    	
    	start.addActionListener(startEve->{
     
    		pool=Executors.newFixedThreadPool(threadNum);
    		pool.submit(()->{
     
    			if(!urlField.getText().startsWith("http")) {
     
    				urlField.setText("http://"+urlField.getText());
    			}
    			getAllImages(urlField.getText());
    		});
    	});
    	
    	stop.addActionListener(stopEve->{
     
    		if(!pool.isShutdown()) {
     
    			pool.shutdownNow();
    		}
    		URLMap.clear();
    		imgURLMap.clear();
    	});
    	
    	JPanel panelFilePath=new JPanel();
    	JTextField filePathField=new JTextField(44);
    	JButton choose=new JButton("选择文件");
    	panelFilePath.add(filePathField);
    	panelFilePath.add(choose);
    	
    	filePathField.setText(filePath);
    	choose.addActionListener(chooseEve->{
     
    		JFileChooser fileChooser=new JFileChooser(".");
    		fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
    		int result=fileChooser.showDialog(uiFrame,"选择存储路径");
			if(result==JFileChooser.APPROVE_OPTION){
     
				filePath=fileChooser.getSelectedFile().getPath()+"\\";
				filePathField.setText(filePath);
			}
    	});
    	
    	uiFrame.add(panelURL);
    	uiFrame.add(panelFilePath,BorderLayout.SOUTH);
    	uiFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    	uiFrame.pack();
    	uiFrame.setLocation(500, 200);
    	uiFrame.setVisible(true);
	}

代码展示

可以从我的git仓库中获取源码https://github.com/noblegongzi/imgSpider.git

应用展示

  • 主页面
    java爬虫——爬取网站图片_第1张图片
  • 选择存储路径
    java爬虫——爬取网站图片_第2张图片
  • 填写网址选择文件夹后开始下载java爬虫——爬取网站图片_第3张图片
  • 成果展示

总结

整体而言,爬虫并不是很难,但是需要处理的细节很多,因为每种元素属性会出现很多种情况,比如URL有//开头,有/开头,也有完整URL,需要分别处理,还要记录已经访问过的URL,防止重复访问。img的src也是如此,而且还有通过base64传输的图片,也需要另外处理。

参考资料

《jsoup使用说明》–作者:jsoup开发人员
《Java爬虫系列三:使用Jsoup解析HTML》–作者:JAVA开发老菜鸟

你可能感兴趣的:(java成长之路,java,爬虫,爬取图片)