前几天我寂寞难耐,在网上看到了不少python的教程,都在谈python实现爬虫如何的快,如何的好。我大略浏览了几篇文章,爬虫怎么弄没怎么记住(我不会python),可是美女图片网站记住了好几个,而后就养分跟不上了。这几天精力跟上来后,我又开始手痒,我就掏出springboot,准备糊弄一个爬虫,java虽然是门啰嗦的语言,但架不住社区强大,开源工具众多,爬虫还不是小case。html
爬虫的核心其实很简单,就两个,一是解析到内容,二是快速的把内容下载下来。咱们分两段来说这个事,先讲第一段。java
爬虫的第一个核心问题其实就是解析须要抓取的内容,若是连内容的信息都整理不出来,那么抓取天然是无从下手。特别是抓图这种程序猿喜闻乐见的事,大部分都是从网站抓取,对于html而言,这方面java有个至关给力的工具,那就是jsoup。python
jsoup 是一款 Java 的HTML 解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套很是省力的API,可经过DOM,CSS以及相似于JQuery的操做方法来取出和操做数据。jquery
jsoup的主要功能以下:git
从一个URL,文件或字符串中解析HTML;web
使用DOM或CSS选择器来查找、取出数据;spring
可操做HTML元素、属性、文本;chrome
简而言之一句话,jsoup就是java开源界的jquery。下面咱们开始看妹子抓图。浏览器
咱们先下一个springboot,里面有两样依赖:springboot
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.12.1</version> </dependency>
其实spring-boot-starter-web都不是必须的,但我我的爱好用controller来演示,因此你们迁就下我吧。
我用的目标网站是http://zhainanba.net/category/zhainanfuli,为了让你们可以体会到真实的爬虫效果,我特意找了这个有点坑的网站,个人用心良苦啊,看了多少妹子才找到这么个合适写教程的网站。
肯定好目标网站,第一件事固然是看图看网站结构咯。这个网站是下拉更新的,因此直接抓取这个网址,基本上就只能抓大第一屏的内容,对于妹子全都要的咱们,天然是不行的。在chrome中按F12呼出开发者工具,看Network的变化,很快就能找到规律
原来仍是简简单单的分页而已,只要不停的把page后面的参数变化,就能抓取不一样页数的内容。
解决了分页问题,咱们再看具体的每一条内容,这个只能翻看html代码了。仔细对照下每条信息的连接就能发现对应的html代码是这样的
<a target="_blank" href="http://zhainanba.net/20981.html" title="今日妹子图 20190422 好看的小姐姐 @卜卜龙_ 喜欢死了!-宅男吧">今日妹子图 20190422 好看的小姐姐 @卜卜龙_ 喜欢死了!</a>
咱们只要能抓取里面href的内容,就能看到具体图片的页面了。而后观察页面的图片就能发现妹子好好看html的规律是这样的
<img class="alignnone" src="http://ac.meijiecao.net/ac/img/znb/meizitu/20190422_meizitu_09.jpg" alt="小姐姐 小仙女 卜卜龙 " title="今日妹子图 20190422 好看的小姐姐 @卜卜龙 喜欢死了!" /><br />
哎呀,真简单,让咱们撸代码愉快的抓图吧。
public static String zhainanfuli_URL = "http://zhainanba.net/category/zhainanfuli/page/"; int start;//开始的页数 int end;//结束的页数 ... // 利用Jsoup得到链接 Connection connect; for (int i = start; i <= end; i++) { connect = Jsoup.connect(zhainanfuli_URL + i); try { // 获得Document对象 Document document = connect.get(); // 查找全部img标签 Elements hrefs = document.getElementsByTag("a"); System.out.println("开始查找"); // 遍历img标签并得到src的属性 for (Element element : hrefs) { // 获取每一个URL "abs:"表示绝对路径 String title = element.attr("title"); // 打印URL if(title.indexOf("今日妹子图")<0) {//不是妹子图片 continue; } String url = element.attr("href"); System.out.println(element.attr("href")); downloadMeizitu(url); } System.out.println("下载完成"); } catch (IOException e) { e.printStackTrace(); } }
这一段是抓取列表的内容,其实就是jsoup几个核心类的应用,包括
收集完了具体图片页面的连接,咱们继续分析具体图片页面的html,获取图片的地址:
private void downloadMeizitu(String url) { Connection connect = Jsoup.connect(url); try { // 获得Document对象 Document document = connect.get(); // 查找全部img标签 Elements imgs = document.getElementsByTag("img"); System.out.println("共检测到图片URL:"+imgs.size()); String filePath = "E:/youtube/images/zhainanfuli/"+DownloadUtils.getHtml(url); File dir = new File(filePath); if (dir.exists()) { logger.info("该连接的图片已经下载"); return; } System.out.println("开始下载"); // 遍历img标签并得到src的属性 for (Element element : imgs) { // 获取每一个URL "abs:"表示绝对路径 String title = element.attr("title"); // 打印URL if(title.indexOf("今日妹子图")<0) {//不是妹子图片 continue; } String imgSrc = element.attr("src"); Map<String,String> map = new HashMap<String,String>(); // TODO 这里后面要加内容,记住 downImages(filePath, imgSrc,map); //logger.info(future.get()); } System.out.println("下载完成"); } catch (IOException e) { e.printStackTrace(); } }
用的方法和上面相似,用document.getElementsByTag("img")找到图片url,根据element的属性来过滤掉不是妹子图片的url,而后就能够开心的下载了,下载代码以下:
public static void downImages(String filePath, String imgUrl,Map<String,String> requestPropMap) { // 若指定文件夹没有,则先建立 File dir = new File(filePath); if (!dir.exists()) { dir.mkdirs(); } // 截取图片文件名 String fileName = imgUrl.substring(imgUrl.lastIndexOf('/') + 1, imgUrl.length()); try { // 文件名里面可能有中文或者空格,因此这里要进行处理。但空格又会被URLEncoder转义为加号 String urlTail = URLEncoder.encode(fileName, "UTF-8"); // 所以要将加号转化为UTF-8格式的%20 imgUrl = imgUrl.substring(0, imgUrl.lastIndexOf('/') + 1) + urlTail.replaceAll("\\+", "\\%20"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } // 写出的路径 File file = new File(filePath + File.separator + fileName); try { // 获取图片URL URL url = new URL(imgUrl); // 得到链接 URLConnection connection = url.openConnection(); if(requestPropMap != null) { //假装成浏览器,不然会被403拒绝 for(String key:requestPropMap.keySet()){//keySet获取map集合key的集合 而后在遍历key便可 connection.setRequestProperty(key, requestPropMap.get(key)); } } connection.setDoInput(true); // 设置10秒的相应时间 connection.setConnectTimeout(10 * 1000); // 得到输入流 InputStream in = connection.getInputStream(); // 得到输出流 BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file)); // 构建缓冲区 byte[] buf = new byte[1024]; int size; // 写入到文件 while (-1 != (size = in.read(buf))) { out.write(buf, 0, size); } out.close(); in.close(); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
这就是个标准的下载文件的方法,就很少介绍了,而后咱们就能够开始下载了,当咱们开心的找到图片的url,开始下载的时候,出问题了。
java.io.IOException: Server returned HTTP response code: 403 for URL: http://ac.meijiecao.net/ac/img/znb/meizitu/20180628_meizitu_02.jpg at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(Unknown Source) at sun.net.www.protocol.http.HttpURLConnection.getInputStream(Unknown Source) at com.skyblue.crawel.utils.DownloadUtils.downImages(DownloadUtils.java:64) at com.skyblue.crawel.service.DownloadAsyncService.downloadImage(DownloadAsyncService.java:23) at com.skyblue.crawel.service.DownloadAsyncService$$FastClassBySpringCGLIB$$bda70fc.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:749) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) at org.springframework.aop.interceptor.AsyncExecutionInterceptor.lambda$invoke$0(AsyncExecutionInterceptor.java:115) at java.util.concurrent.FutureTask.run(Unknown Source) at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) at java.lang.Thread.run(Unknown Source)
这种错误其实不是每一个网站抓图都会遇到的,有些网站会对下载图片作一些限制,限制的具体状况各不相同,我在这个网站遇到了这个问题,咱们就针对性看一下吧。403错误简单的说就是没有权限访问此站,通常就是挡住那些不是规规矩矩访问的人,那咋办呢,通常的作法是伪装是浏览器,形成我是规矩人的假象。
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1")
通常加了这句后403就对付过去了,可是在这个网站还不行,继续403,这就有些蹊跷了,我只好直接到浏览器去看下,它的访问图片和我访问图片有啥不一样。结果我发现了一个很奇怪的现象,我把图片的url直接拍到浏览器中去,居然也不能访问。看到的也是403
居然不按套路出牌,我再次打开chrome的开发者工具,翻看http的内容。在html页面中图片的request headers是这样的:
而我直接把图片的url贴到浏览器中去访问是这样的:
果然没有对比就没有伤害,我敏锐(推眼镜)的发现,虽然下面的request headers一大堆,可是缺乏上面的同样东西,那就是
这个网站居然作了判断,只有从页面进来的图片才可以访问到,而没有referer(referer表示当前访问页面的前一页url,来源页面)的话就是直接403了。知道了问题在哪就好办了,既然我已经假装成了浏览器,再假装我是从页面来的又如何,我再次修改了代码,把referer也加了上去。
private void downloadMeizitu(String url) { ...... String imgSrc = element.attr("src"); Map<String,String> map = new HashMap<String,String>(); map.put("Referer", url); map.put("User-Agent", "Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1"); downloadAsyncService.downloadImage(filePath, imgSrc,map); //logger.info(future.get()); } ......
再次运行,我就能愉快的下载图片了。
诸位若是是对妹子图片有大量需求的话,就会发现下的好慢啊,缘由很简单,这是一张一张下的,下一课,咱们讲怎么用多线程下图,速度杠杠的。
教程源码地址(妹子图虽好,看多了也伤身)