做网络爬虫时,一般对代理IP的需求量比较大。因为在爬取网站信息的过程中,很多网站做了反爬虫策略,可能会对每个IP做频次控制。这样我们在爬取网站时就需要很多代理IP。

代理IP的获取,可以从以下几个途径得到:

  • 从免费的网站上获取,质量很低,能用的IP极少

  • 购买收费的代理服务,质量高很多

  • 自己搭建代理服务器,稳定,但需要大量的服务器资源。

本文的代理IP池是通过爬虫事先从多个免费网站上获取代理IP之后,再做检查判断IP是否可用,可用的话就存放到MongoDB中,最后展示到前端的页面上。

获取可用Proxy

获取代理的核心代码是ProxyManager,它采用RxJava2来实现,主要做了以下几件事:

第一,创建ParallelFlowable,针对每一个提供免费代理IP的页面并行地抓取。对于不了解ParallelFlowable的同学,可以看我之前的文章RxJava 之 ParallelFlowable

  
  1. Flowable.fromIterable(ProxyPool.proxyMap.keySet())

  2.                .parallel()


第二,针对每一个页面进行抓取,返回List

  
  1. map(new Function<String, List<Proxy>>() {

  2.                    @Override

  3.                    public List<Proxy> apply(String s) throws Exception {

  4.                        try {

  5.                            return new ProxyPageCallable(s).call();

  6.                        } catch (Exception e) {

  7.                            e.printStackTrace();

  8.                        }

  9.                        return null;

  10.                    }

  11.                })


第三,对每一个页面获取的代理IP列表进行校验,判断是否可用

  
  1. flatMap(new Function<List<Proxy>, Publisher<Proxy>>() {

  2.                    @Override

  3.                    public Publisher<Proxy> apply(List<Proxy> proxies) throws Exception {

  4.                        if (proxies == null) return null;

  5.                        List<Proxy> result = proxies

  6.                                .stream()

  7.                                .parallel()

  8.                                .filter(new Predicate<Proxy>() {

  9.                            @Override

  10.                            public boolean test(Proxy proxy) {

  11.                                HttpHost httpHost = new HttpHost(proxy.getIp(), proxy.getPort(), proxy.getType());

  12.                                return HttpManager.get().checkProxy(httpHost);

  13.                            }

  14.                        }).collect(Collectors.toList());

  15.                        return Flowable.fromIterable(result);

  16.                    }

  17.                })


第四,依次保存到proxyList

  
  1. subscribe(new Consumer<Proxy>() {

  2.                    @Override

  3.                    public void accept(Proxy proxy) throws Exception {

  4.                        log.debug("Result Proxy = "+proxy.getType()+"://"+proxy.getIp()+":"+proxy.getPort());

  5.                        proxy.setLastSuccessfulTime(new Date().getTime());

  6.                        ProxyPool.proxyList.add(proxy);

  7.                    }

  8.                });


附上完整的流程图

再附上完整的ProxyManager代码:

  
  1. import com.cv4j.proxy.domain.Proxy;

  2. import com.cv4j.proxy.http.HttpManager;

  3. import com.cv4j.proxy.task.ProxyPageCallable;

  4. import io.reactivex.Flowable;

  5. import io.reactivex.functions.Consumer;

  6. import io.reactivex.functions.Function;

  7. import lombok.extern.slf4j.Slf4j;

  8. import org.apache.http.HttpHost;

  9. import org.reactivestreams.Publisher;

  10. import org.springframework.stereotype.Component;

  11. import java.util.Date;

  12. import java.util.List;

  13. import java.util.function.Predicate;

  14. import java.util.stream.Collectors;

  15. /**

  16. * Created by tony on 2017/10/25.

  17. */

  18. @Slf4j

  19. @Component

  20. public class ProxyManager {

  21.    /**

  22.     * 抓取代理,成功的代理存放到ProxyPool中

  23.     */

  24.    public void start() {

  25.        Flowable.fromIterable(ProxyPool.proxyMap.keySet())

  26.                .parallel()

  27.                .map(new Function<String, List<Proxy>>() {

  28.                    @Override

  29.                    public List<Proxy> apply(String s) throws Exception {

  30.                        try {

  31.                            return new ProxyPageCallable(s).call();

  32.                        } catch (Exception e) {

  33.                            e.printStackTrace();

  34.                        }

  35.                        return null;

  36.                    }

  37.                })

  38.                .flatMap(new Function<List<Proxy>, Publisher<Proxy>>() {

  39.                    @Override

  40.                    public Publisher<Proxy> apply(List<Proxy> proxies) throws Exception {

  41.                        if (proxies == null) return null;

  42.                        List<Proxy> result = proxies

  43.                                .stream()

  44.                                .parallel()

  45.                                .filter(new Predicate<Proxy>() {

  46.                            @Override

  47.                            public boolean test(Proxy proxy) {

  48.                                HttpHost httpHost = new HttpHost(proxy.getIp(), proxy.getPort(), proxy.getType());

  49.                                return HttpManager.get().checkProxy(httpHost);

  50.                            }

  51.                        }).collect(Collectors.toList());

  52.                        return Flowable.fromIterable(result);

  53.                    }

  54.                })

  55.                .sequential()

  56.                .subscribe(new Consumer<Proxy>() {

  57.                    @Override

  58.                    public void accept(Proxy proxy) throws Exception {

  59.                        log.debug("Result Proxy = "+proxy.getType()+"://"+proxy.getIp()+":"+proxy.getPort());

  60.                        proxy.setLastSuccessfulTime(new Date().getTime());

  61.                        ProxyPool.proxyList.add(proxy);

  62.                    }

  63.                });

  64.    }

  65. }

定时任务

每隔几个小时跑一次定时任务,在抓取完任务之后先删除旧的数据,然后再把新的数据插入到MongoDB中。

  
  1. import com.cv4j.proxy.ProxyManager;

  2. import com.cv4j.proxy.ProxyPool;

  3. import com.cv4j.proxy.dao.ProxyDao;

  4. import com.cv4j.proxy.domain.Proxy;

  5. import com.safframework.tony.common.utils.Preconditions;

  6. import org.springframework.beans.factory.annotation.Autowired;

  7. import org.springframework.scheduling.annotation.Scheduled;

  8. import org.springframework.stereotype.Component;

  9. import java.util.concurrent.CopyOnWriteArrayList;

  10. /**

  11. * Created by tony on 2017/11/22.

  12. */

  13. @Component

  14. public class ScheduleJobs {

  15.    @Autowired

  16.    ProxyDao proxyDao;

  17.    @Autowired

  18.    ProxyManager proxyManager;

  19.    /**

  20.     * 每六个小时跑一次任务

  21.     */

  22.    @Scheduled(cron = "0 0 */6 * * ?")

  23.    public void cronJob() {

  24.        System.out.println("Job Start...");

  25.        proxyManager.start();

  26.        CopyOnWriteArrayList<Proxy> list = ProxyPool.proxyList;

  27.        // 先删除旧的数据

  28.        proxyDao.deleteAll();

  29.        // 然后再进行插入新的proxy

  30.        if (Preconditions.isNotBlank(list)) {

  31.            for (Proxy p:list) {

  32.                proxyDao.saveProxy(p);

  33.            }

  34.        }

  35.        System.out.println("Job End...");

  36.    }

  37. }

展示到前端

整个项目使用Spring Boot搭建,运行起来之后本地访问地址: http://localhost:8080/load?pagename=proxy_list

预览效果如下:


在使用前,还可以再做一次检测,只要双击某个代理IP即可。


在第二次检测时,对于已经失效的IP会被ProxyPool删除。

总结

在做爬虫时,自己维护一个可用的代理IP池是很有必要的事情,当然想要追求更高稳定性的代理IP还是考虑购买比较好。

最后,附上github地址: https://github.com/fengzhizi715/ProxyPool


关注【Java与Android技术栈】

更多精彩内容请关注扫码


Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐