java 爬虫爬取糗事百科热图图片

最近几天突然想学习一下爬虫技术,首先想到的是用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指向一个网页时的异常。

猜你喜欢

转载自blog.csdn.net/jiangxin792/article/details/80556615