Jsoup线程池分页爬取IP

Jsoup线程池分页爬取IP

最近写个邮件群发工具,要设置代理发送,然后就写了这个玩意。。。。。。

  1. 爬取目标:免费代理ip_服务器http代理_最新ip代理_免费ip提取网站_国内外代理_66免费代理ip (66ip.cn)
  2. 准备好创建线程池 和 IP对象
  3. 解析页面获取数据,并将数据存入到集合中
  4. 遍历测试IP是否可用
  5. 通过ObjectOutputStream将ip存集合作为对象存入到文件中
  6. 通过ObjectInputStream读取ip集合对象文件

1、准备好创建连接池工具类 和 IP对象(不用这个线程池也无所谓)

线程池:

/**
 * 线程池工具类
 */
public class PoolSend {

    BlockingQueue<Runnable> workQueue;//任务队列
    ExecutorService es;//线程池的接口

    /**
     * @param corePoolSize 初始线程
     * @param maximumPoolSize 最大线程
     */
    public PoolSend(Integer corePoolSize,Integer maximumPoolSize) {
        startTime = System.currentTimeMillis();
        workQueue = new LinkedBlockingQueue<>();//构造无界的任务队列,资源足够,理论可以支持无线个任务
        es = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, //2 core; 4 max
                3000, TimeUnit.SECONDS, workQueue, //30s keep alive
                new ThreadPoolExecutor.CallerRunsPolicy()); //任务失败重试
    }

    public void send(Runnable task) {
        System.out.println("PoolSend start sending mail...");
        es.execute(task);//将任务放入线程池
    }

    public void close() {// 关闭
        es.shutdown();
        while (true) {//让主线程等待所有任务都执行结束
            if (es.isTerminated()) {//所有的子线程都结束了
                break;
            }
        }
    }

}

IP对象,这个ip对象是我数据库中生成出来的bean,我们只需要往里面存IP和端口就行了,其他子网掩码、ip编号不用管:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Ipconfig implements Serializable {
    private static final long serialVersionUID = -60500851059312504L;
    /**
    * IP编号
    */
    private Integer id;
    /**
    * IP地址
    */
    private String ip;
    /**
    * 子网掩码
    */
    private String dns;
    /**
    * 端口
    */
    private String port;

    public Ipconfig(String ip, String port) {
        this.ip = ip;
        this.port = port;
    }
}

2、解析页面获取数据

对于这个代理ip网站的数据爬虫我写了两个方法:

  • 一个是爬取该网站的所有IP
      /**
       * 爬取该网站所有免费提供的代理IP 并将IP存入到文件中
       *
       * @return 返回IP集合
       * @throws Exception
       */
      public static List crawl() throws Exception {
          Integer index = 0;//每个线程爬取的起始页
          Integer index2 = 50;//每个爬取的结束页
          //这个集合就是我们所要返回的对象了
          ArrayList ipConfigList = new ArrayList<Ipconfig>();
          //这个是干嘛的?偶偶,这个就是用来搞一下随机线程睡眠时间,不设置也无所谓
          Random random = new Random();
          /*创建线程池*/
          PoolSend poolSend = new PoolSend(46, 46);
          /*使用for循环开启46个线程,每个线程爬取50个页面,一共爬取2300页面的IP*/
          for (int i = 0; i < 46; i++) {
              Integer finalIndex = index;
              Integer finalIndex1 = index2;
              //使用线程池
              poolSend.send(() -> {
                  /*要返回的对象*/
                  for (int j = finalIndex; j <= finalIndex1; j++) {
                      try {
                          URL url = null;
                          if (j == 1) { //是第一页的时候
                              url = new URL("http://www.66ip.cn/index.html");
                          } else {
                              url = new URL("http://www.66ip.cn/" + j + ".html");
                          }
                          //随机一个休眠时间
                          Long s = (long) (random.nextInt(5000 - 1000 + 1) + 1000);
                          //当前线程休眠,休眠过后就开始解析网页
                          Thread.currentThread().sleep(s);
                          /*传入爬取的url和超时等待时间*/
                          Document document = Jsoup.parse(url, 1000000);
                          //通过css选择器来获取对应的标签
                          Elements table = document.select("table tr:not(tr:nth-child(1))");
                          //遍历获取IP地址
                          for (Element element : table) {
                              //ip
                              String ip = element.child(0).text();
                              //端口
                              String port = element.child(1).text();
                              //然后将ip和port封装成我们前面的那个对象
                              Ipconfig ipconfig = new Ipconfig(ip, port);
                              /**
                               * isValid?啊你tm这个方法是个啥?
                               * 这个方法是用来判断该ip是否可用的,可以看下面文章,有给出代码
                               * 返回true就表示这个ip得行,放心用
                               * 返回false的话。。。懂得都懂
                               */
                              boolean valid = isValid(ipconfig);
                              if (valid) {
                                  //可以用的ip就放入我们要返回的对象中
                                  ipConfigList.add(ipconfig);
                                  System.out.println("====爬取成功" + ipConfigList.size());
                              } else {
                                  System.out.println("很遗憾,这个ip不合格:" + ipconfig.getIp() + "\t port:" + ipconfig.getPort());
                              }
                          }
                      } catch (Exception e) {
                          e.printStackTrace();
                      }
                      System.out.println("====爬取成功:一共爬取了" + ipConfigList.size() + "可用ip");
                      /*爬取1000条ip后停止,这段代码看个人吧,我这边不想爬取太多的ip*/
                      if (ipConfigList.size() > 1000) {
                          break;
                      }
                  }
              });
              index += 50;
              index2 += 50;
          }
          /*关闭线程池*/
          poolSend.close();
          System.out.println("执行玩球了");
          /*输出我们爬取成功可用的ip集合*/
          for (Object o : ipConfigList) {
              System.out.println(o);
          }
  
          /**
           * 这个方法又是干球的?
           * 咳咳,这个方法是用来将这个对象存入到某个文件中的,
           * 参数就是存入要存的对象
           */
          accessObject(ipConfigList);
          return ipConfigList;
      }
  • 另外一个是爬取该网站的所有国内IP
/**
     * 爬取该网站中所有的国内IP
     *
     * @throws Exception
     */
    public static ArrayList<Ipconfig> crawl2() throws Exception {
        ArrayList ipConfigList = new ArrayList<Ipconfig>();//要返回的集合
        Random random = new Random();//用于生成随机线程休眠时间
        /*创建一个线程池*/
        PoolSend poolSend = new PoolSend(34, 34);
        /*创建34个线程执行爬虫,每个线程爬一个页面*/
        for (int i = 1; i <= 34; i++) {
            int finalI = i;
            //实现线程池
            poolSend.send(() -> {
                try {
                    //要爬取的url
                    URL url = new URL("http://www.66ip.cn/areaindex_" + finalI + "/1.html");
                    //当前线程休眠
                    Long s = (long) (random.nextInt(5000 - 1000 + 1) + 1000);
                    Thread.currentThread().sleep(s);
                    /*传入爬取的url和超时等待时间*/
                    Document document = Jsoup.parse(url, 1000000);
                    //通过css选择器来获取对应的标签
                    Elements table = document.select("table tr:not(tr:nth-child(1))");
                    //遍历获取IP地址
                    for (Element element : table) {
                        //ip
                        String ip = element.child(0).text();
                        //端口
                        String port = element.child(1).text();
                        //然后将ip和port封装成我们前面的那个对象
                        Ipconfig ipconfig = new Ipconfig(ip, port);
                        /**
                             * isValid?啊你tm这个方法是个啥?
                             * 这个方法是用来判断该ip是否可用的,可以看下面文章,有给出代码
                             * 返回true就表示这个ip得行,放心用
                             * 返回false的话。。。懂得都懂
                             */
                        boolean valid = isValid(ipconfig);
                        if (valid) {
                            //可以用的ip就放入我们要返回的对象中
                            ipConfigList.add(ipconfig);
                            System.out.println("====爬取成功" + ipConfigList.size());
                        } else {
                            System.out.println("很遗憾,这个ip不合格:" + ipconfig.getIp() + "\t port:" + ipconfig.getPort());
                        }
                    }
                    System.out.println(url.toString() + "爬取完毕");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
        //关闭线程池
        poolSend.close();
         /**
         * 这个方法又是干球的?
         * 咳咳,这个方法是用来将这个对象存入到某个文件中的,
         * 参数就是存入要存的对象
         */
        accessObject(ipConfigList);

        return ipConfigList;
    }

3、遍历测试IP是否可用

   /**
     * 检测代理ip是否有效
     *
     * @param ipBean
     * @return
     */
    public static boolean isValid(Ipconfig ipBean) {
        /*设置代理
        	参数1:http
        	参数2:ip
        	参数3:端口
        	*/
        Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(ipBean.getIp(), Integer.parseInt(ipBean.getPort())));
        try {
            /*访问百度*/
            URLConnection httpCon = new URL("https://www.baidu.com/").openConnection(proxy);
            httpCon.setConnectTimeout(10000);//连接超时
            httpCon.setReadTimeout(10000);//读取超时如果不设置,它就会一直阻塞着。
            //返回的一个响应code
            int code = ((HttpURLConnection) httpCon).getResponseCode();
            System.out.println(code);
            //如果code==200就说明我们访问成功了!
            return code == 200;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

4、通过ObjectOutputStream将ip存集合作为对象存入到文件中

    public static void accessObject(Object obj) throws Exception {
        /*将这个对象存入到文件中*/
        //1、创建ObjectOutputStream对象,构造方法中传递字节输出流
        System.out.println("开始存入");
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj\\ipconfig.txt"));
        //2、使用ObjectOutputStream对象中的方法writeObject,把对象写入文件中
        oos.writeObject(obj);
        //3、释放资源
        oos.close();
    }

5、通过ObjectInputStream读取ip集合对象文件

    /**
     * 读取文件对象
     *
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public static ArrayList<Ipconfig> duqvObject() throws IOException, ClassNotFoundException {
        //1、创建ObjectInputStream对象,构造方法中传递字节输入流
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj//ipconfig.txt"));
        //2、使用ObjectInputStream对象中的方法readObject读取保存对象的文件
        Object o = ois.readObject();
        //3、释放资源
        ois.close();
        //4、使用读取出来对的对象(打印)
        System.out.println(o);
        ArrayList<Ipconfig> p = (ArrayList<Ipconfig>) o;//转换
        return p;
    }

有啥问题,欢迎讨论…

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值