透过妹子看本质:爬虫小问题,并发大学问-1爬虫小问题

     前几天我寂寞难耐,在网上看到了不少python的教程,都在谈python实现爬虫如何的快,如何的好。我大略浏览了几篇文章,爬虫怎么弄没怎么记住(我不会python),可是美女图片网站记住了好几个,而后就养分跟不上了。这几天精力跟上来后,我又开始手痒,我就掏出springboot,准备糊弄一个爬虫,java虽然是门啰嗦的语言,但架不住社区强大,开源工具众多,爬虫还不是小case。html

    爬虫的核心其实很简单,就两个,一是解析到内容,二是快速的把内容下载下来。咱们分两段来说这个事,先讲第一段。java

一、爬虫小问题,jsoup

爬虫的第一个核心问题其实就是解析须要抓取的内容,若是连内容的信息都整理不出来,那么抓取天然是无从下手。特别是抓图这种程序猿喜闻乐见的事,大部分都是从网站抓取,对于html而言,这方面java有个至关给力的工具,那就是jsoup。python

      jsoup 是一款 Java 的HTML 解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套很是省力的API,可经过DOM,CSS以及相似于JQuery的操做方法来取出和操做数据。jquery

jsoup的主要功能以下:git

  1. 从一个URL,文件或字符串中解析HTML;web

  2. 使用DOM或CSS选择器来查找、取出数据;spring

  3. 可操做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几个核心类的应用,包括

  • Jsoup.connect(url),须要解析的目标url,而且生成一个Document。
  • Document对象就是目标url的内容啦,对网页而言就是html代码,咱们经过简单的方法就可以筛选出咱们须要的内容。
  • Elements 就是html的各个元素,这里咱们须要的是每条记录的连接,经过document.getElementsByTag("a")就能轻松获取。和jquery同样,什么title,div,form这些dom元素均可以这样获取。
  • Element对应的dom标签会有不少属性,经过element.attr(attribute)就能轻松获取,例子中咱们就是经过title属性的内容来判断哪些连接才是咱们须要的连接。

收集完了具体图片页面的连接,咱们继续分析具体图片页面的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,开始下载的时候,出问题了。

二、403咋回事

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());
				}
            ......

再次运行,我就能愉快的下载图片了。

诸位若是是对妹子图片有大量需求的话,就会发现下的好慢啊,缘由很简单,这是一张一张下的,下一课,咱们讲怎么用多线程下图,速度杠杠的。

教程源码地址(妹子图虽好,看多了也伤身)

相关文章
相关标签/搜索