欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

深入浅出爬虫(Java福利版)

程序员文章站 2022-05-04 15:52:58
...

福利在手,说走就走;

深入浅出爬虫(Java福利版)

深入浅出爬虫(Java福利版)

关键技术点

  • 生产消费者模式的应用;
  • 线程池的应用;
  • 网页解析技术(Jsoup)的应用;
  • Selenium的应用;
  • 乐观锁的简单实现;
  • 单例模式的应用;
  • 防反爬技术的应用;
  • 自定义应用池的实现;
  • Java语言实现,Maven编译;

关键代码说明

生产消费者模式的实现;

// 生产消费者模式
BlockingQueue<LMIndex> queue = new ArrayBlockingQueue<LMIndex>(100);
// 线程池管理任务;
ExecutorService es = Executors.newFixedThreadPool(3);
es.submit(new Producer(queue));
es.submit(new Producer(queue));
es.submit(new Consumer(queue));
es.submit(new Consumer(queue));

es.shutdown();

以上代码中,是应用的入口main方法的内容,在内容中使用队列来作为生产消费者的通道,使用线程池来管理任务线程,
在这里,为了避免拖累目标服务器,尽量少用那么多线程来操作。

消费者的重试机制

public void run() {

        AtomicInteger retry = new AtomicInteger(RETRY_COUNT);
        while (true) {
            LMIndex lmi = null;
            try {
                lmi = queue.poll(2, TimeUnit.SECONDS);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
                if (retry.decrementAndGet() == 0) {
                    break;
                }
            }
            System.out.println("消费:" + lmi);

            int retryCount = retry.decrementAndGet();
            if (lmi == null && retryCount == 0) {
                break;
            }
            if (lmi == null && retryCount > 0) {
                continue;
            }
            // 恢复初始状态
            retry.set(RETRY_COUNT);

            consume(lmi);

            //
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
                if (retry.decrementAndGet() == 0) {
                    break;
                }
            }
        }
    }

为了避免出现生产慢,消费快的情况,这里使用了重试机制;这里有个关键的地方,重试获取任务完成之后记得要恢复重试机制的初始状态,要不然会出现意外情况哦;

乐观锁的简单实现
    public static String getSource(String url) {
        WebDriver driver;
        while (true) {
            driver = DriverHolder.getDriver();
            if (driver != null) {
                break;
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        driver.get(url);
        String src = driver.getPageSource();
        DriverHolder.completeTask(driver);

        return src;
    }

从上面的代码中可以很明显的看到,当获取driver失败的时候,在进行不断的重试,直到成功为止。
除了重试机制,在后面的操作中,当操作完成的时候,需要重置driver的状态。
至于为什么要重置driver的状态,可以看后面的说明。

Driver工作类
/**
 * 
 */
package mk.lesmao.driver;

import java.io.IOException;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;

import org.openqa.selenium.Capabilities;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriverService;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.remote.service.DriverService;

import mk.lesmao.SeleniumUtil;

/**
 * @author MichaelKoo
 *
 *
 *         驱动工具类
 */
public class DriverHolder {

    static ConcurrentHashMap<Integer, DriverEntity> driverPool = new ConcurrentHashMap<Integer, DriverEntity>(5);
    static final int POOL_SIZE = 10;

    /**
     * 1、如果池为空,则添加;
     * 
     * 2、如果有空闲的则使用;
     * 
     * 3、如果池未满,则添加;
     * 
     * @return
     */
    public static WebDriver getDriver() {

        System.out.println("driver driverPool size=" + driverPool.size());
        //
        if (driverPool.size() == 0) {
            DriverEntity de = DriverEntity.create();
            if (de != null) {
                driverPool.put(de.driver.hashCode(), de);
                return de.driver;
            }
        }

        //
        Iterator<Integer> iter = driverPool.keySet().iterator();
        while (iter.hasNext()) {
            DriverEntity de = driverPool.get(iter.next());
            if (!de.hasTask) {
                de.hasTask = true;
                driverPool.put(de.driver.hashCode(), de);
                return de.driver;
            }
        }
        //
        if (driverPool.size() < POOL_SIZE) {
            DriverEntity de = DriverEntity.create();
            driverPool.put(de.driver.hashCode(), de);
            return de.driver;
        }

        return null;
    }

    /**
     * 完成操作进行状态恢复
     * 
     * @param driver
     */
    public static void completeTask(WebDriver driver) {
        DriverEntity de = driverPool.get(driver.hashCode());
        de.hasTask = false;
        driverPool.put(driver.hashCode(), de);
    }

    /**
     * 驱动实体
     * 
     * @author MichaelKoo
     *
     * 
     */
    static class DriverEntity {
        WebDriver driver;
        DriverService service;
        boolean hasTask = true;

        private void close() {
            if (driver != null) {
                driver.close();
            }
            if (service != null && service.isRunning()) {
                service.stop();
                service = null;
            }
        }

        static DriverEntity create() {
            DriverEntity entity = new DriverEntity();
            entity.service = new ChromeDriverService.Builder().usingAnyFreePort().build();
            try {
                entity.service.start();
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            }

            Capabilities options = SeleniumUtil.getCapabilities(SeleniumUtil.lesmaoHeader());
            entity.driver = new RemoteWebDriver(entity.service.getUrl(), options);

            return entity;
        }

    }

    /**
     * 释放驱动池
     */
    public static void release() {
        Iterator<Integer> iter = driverPool.keySet().iterator();
        while (iter.hasNext()) {
            DriverEntity de = driverPool.get(iter.next());
            de.close();
        }
        driverPool.clear();
    }
}

关键词有,池化思想,状态恢复,对象重复利用;
从completeTask方法可以看到恢复状态的操作,这里做个说明,如果不恢复driver状态,那么前面的getSource就一直会失败;
一直在重试。

声明

本文只做学习之用,请勿用于其他用途,谢谢。