Сервер сканера Java заблокирован, не паникуйте, давайте поменяем сервер

Java

Это четвертая статья из серии статей о сканере Java, предыдущаяИскатели Java сталкиваются с асинхронной загрузкой данных, попробуйте эти два метода!В статье мы кратко рассказали об обработке данных, асинхронной загрузке с точки зрения встроенного ядра браузера и обратного парсинга. В этой статье мы кратко расскажем о сканерах, веб-сайтах ресурсов, блокирующих сканеры, и соответствующих решениях, основанных на поведении пользователей.

Блокирование поисковых роботов — это мера защиты ресурсных веб-сайтов, и наиболее часто используемая стратегия защиты от сканеров должна основываться на поведении пользователей при доступе. Например, к каждому серверу можно получить доступ только X раз в течение определенного периода времени. Если количество раз превышено, считается, что это программа-сканер. только на основе количества посещений, но также на основе количества посещений Запрошенный заголовок запроса User Agent, интервал времени каждого доступа и т. д. В целом он определяется рядом факторов, среди которых количество посещений является основным.

Антисканирование — это мера самозащиты для каждого веб-сайта ресурса, предназначенная для защиты ресурсов от захвата программами-краулерами. Например, Douban, который мы использовали ранее, будет блокировать сканеры на основе поведения пользователя при доступе.После того, как каждый IP достигнет определенного количества посещений в минуту, запрос в следующий период будет возвращать ошибку 403 напрямую, думая, что у вас нет разрешение на доступ к этой странице. Итак, давайте сегодня снова возьмем Douban в качестве примера.Мы моделируем это явление с помощью программы.Ниже представлена ​​программа, которую я написал для сбора фильмов Douban.

/**
 * 采集豆瓣电影
 */
public class CrawlerMovie {

    public static void main(String[] args) {
        try {
            CrawlerMovie crawlerMovie = new CrawlerMovie();
            // 豆瓣电影链接
            List<String> movies = crawlerMovie.movieList();
            //创建10个线程的线程池
            ExecutorService exec = Executors.newFixedThreadPool(10);
            for (String url : movies) {
                //执行线程
                exec.execute(new CrawlMovieThread(url));
            }
            //线程关闭
            exec.shutdown();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 豆瓣电影列表链接
     * 采用反向解析法
     *
     * @return
     */
    public List<String> movieList() throws Exception {
        // 获取100条电影链接
        String url = "https://movie.douban.com/j/search_subjects?type=movie&tag=热门&sort=recommend&page_limit=200&page_start=0";
        CloseableHttpClient client = HttpClients.createDefault();
        List<String> movies = new ArrayList<>(100);
        try {
            HttpGet httpGet = new HttpGet(url);
            CloseableHttpResponse response = client.execute(httpGet);
            System.out.println("获取豆瓣电影列表,返回验证码:" + response.getStatusLine().getStatusCode());
            if (response.getStatusLine().getStatusCode() == 200) {
                HttpEntity entity = response.getEntity();
                String body = EntityUtils.toString(entity, "utf-8");
                // 将请求结果格式化成json
                JSONObject jsonObject = JSON.parseObject(body);
                JSONArray data = jsonObject.getJSONArray("subjects");
                for (int i = 0; i < data.size(); i++) {
                    JSONObject movie = data.getJSONObject(i);
                    movies.add(movie.getString("url"));
                }
            }
            response.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            client.close();
        }
        return movies;
    }
}
/**
 * 采集豆瓣电影线程
 */
class CrawlMovieThread extends Thread {
    // 待采集链接
    String url;

    public CrawlMovieThread(String url) {
        this.url = url;
    }
    public void run() {
        try {
            Connection connection = Jsoup.connect(url)
                    .method(Connection.Method.GET)
                    .timeout(50000);
            Connection.Response Response = connection.execute();
            System.out.println("采集豆瓣电影,返回状态码:" + Response.statusCode());
        } catch (Exception e) {
            System.out.println("采集豆瓣电影,采集出异常:" + e.getMessage());
        }
    }
}

Логика этой программы относительно проста: сначала собираются популярные фильмы Douban, здесь используется прямой доступ к Ajax для получения ссылок на популярные фильмы Douban, а затем анализируются ссылки на страницы фильмов, и детали ссылки на страницы доступны в нескольких потоках, потому что только в случае нескольких потоков для удовлетворения требований доступа Douban. Страница популярного фильма Дубана выглядит следующим образом:

Запустите приведенную выше программу несколько раз, вы получите результат, показанный на рисунке ниже.

На приведенном выше рисунке мы видим, что код состояния, возвращаемый httpclient access, равен 403, что указывает на то, что у нас больше нет разрешения на доступ к странице, то есть Douban уже думает, что мы краулер, и отказывается принимать наш доступ. запрос. Давайте проанализируем нашу текущую архитектуру доступа, поскольку мы напрямую обращаемся к Douban, поэтому архитектура доступа на данный момент показана на следующем рисунке:

Если мы хотим прорваться через этот слой ограничений, мы не можем получить прямой доступ к серверу Douban. Нам нужно подключить третью сторону и позволить другим посещать нас вместо нас. Мы находим разных людей каждый раз, когда посещаем, так что мы не будем ограниченный., это так называемый IP-прокси.Архитектура доступа в это время приобретает следующую картину:

Для IP-прокси, который мы используем, нам нужен пул IP-прокси.Далее поговорим о пуле IP-прокси.

Пул IP-прокси

Proxy Есть много производителей, которые делают это, я не буду говорить конкретно, что он IP-агенты Baidu могут перерыть много этих IP-агентов, доступных за плату и бесплатные IP-агенты, высокая доступность IP-агентов, высокая скорость, высокая скорость, on- среду линии, если вам нужно использовать прокси, рекомендуется использовать плату прокси-IP. Если только их собственное исследование, мы можем пойти собирать эти компании бесплатный общедоступный IP-прокси, производительность и доступность IP, чем бедные, но не влияет на наше использование.

Поскольку мы являемся демонстрационным проектом, мы создадим собственный пул IP-прокси. Как нам спроектировать пул IP-прокси? На следующем рисунке показана простая схема архитектуры пула IP-прокси, которую я нарисовал.

Из приведенной выше схемы архитектуры видно, что система IP-прокси включает в себя четыре модуля, а именно: модуль сбора IP-адресов, модуль хранения IP-адресов, модуль обнаружения IP-адресов и модуль интерфейса API.

  • Модуль захвата IP

Отвечает за сбор IP-адресов прокси-серверов от основных производителей прокси-серверов. Чем больше веб-сайтов вы собираете, тем выше доступность IP-адресов прокси.

  • Модуль хранения IP

Для хранения собранного IP-адреса прокси-сервера обычно используется высокопроизводительная база данных, такая как Redis.Что касается хранилища, нам необходимо хранить два типа данных: один для обнаружения доступного IP-адреса прокси-сервера, а другой-для сбора прокси-сервера. IP, который еще не обнаружен.

  • Модуль обнаружения IP

Определите, доступен ли собранный IP-адрес, что может сделать IP-адрес, который мы предоставляем, более доступным, и мы сначала отфильтруем недоступный IP-адрес.

  • Модуль интерфейса API

Предоставлять доступные IP-адреса прокси в виде интерфейсов

Выше приведен соответствующий дизайн пула IP-прокси. Нам нужно только краткое понимание этого, потому что нам в принципе не нужно писать службу пула IP-прокси. На GitHub уже есть много отличных проектов с открытым исходным кодом, и нет нужды их повторять.Делайте колеса. Я выбрал проект пула IP-прокси с открытым исходным кодом proxy_pool со звездой 8K на GitHub для всех, и мы будем использовать его в качестве нашего пула IP-прокси. Для proxy_pool посетите:https://github.com/jhao104/proxy_pool

развернуть proxy_pool

proxy_pool написан на питоне, но это тоже не беда, так как теперь все ОК развертывание контейнера, использование развертывания контейнера может заблокировать часть среды установки, вам нужно только запустить зеркало, может запустить службу, и не нужно знать его внутри конкретной реализации, так что этот проект не понимает программистов Python Java также может быть использован. proxy_pool Redis используется для хранения полученного IP-адреса, поэтому перед запуском proxy_pool необходимо запустить службу Redis. Вот процедура запуска докера proxy_pool.

  • вытащить изображение

docker pull jhao104/proxy_pool

  • запустить изображение

docker run --env db_type=REDIS --env db_host=127.0.0.1 --env db_port=6379 --env db_password=pwd_str -p 5010:5010 jhao104/proxy_pool

После запуска образа мы некоторое время ждем, потому что сбор и обработка данных в первый раз занимает некоторое время. доступ после ожиданияhttp://{your_host}:5010/get_all/, если вы получите результат, показанный на рисунке ниже, это означает, что вы успешно развернули проект proxy_pool.

Использование IP-прокси

После создания пула IP-прокси мы можем использовать IP-адрес прокси-сервера для сбора фильмов Douban. Мы уже знаем, что в дополнение к IP заголовок запроса агента пользователя также будет фактором для Douban, чтобы определить, является ли доступ сканером, поэтому мы также Подделка заголовка запроса агента пользователя, мы используем другой заголовок запроса агента пользователя для каждого посещения.

Мы представляем IP-прокси-серверный и случайный запрос пользовательского агента для процедуры сбора фильма Dumban. Конкретный код выглядит следующим образом:

public class CrawlerMovieProxy {

    /**
     * 常用 user agent 列表
     */
    static List<String> USER_AGENT = new ArrayList<String>(10) {
        {
            add("Mozilla/5.0 (Linux; Android 4.1.1; Nexus 7 Build/JRO03D) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Safari/535.19");
            add("Mozilla/5.0 (Linux; U; Android 4.0.4; en-gb; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30");
            add("Mozilla/5.0 (Linux; U; Android 2.2; en-gb; GT-P1000 Build/FROYO) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1");
            add("Mozilla/5.0 (Windows NT 6.2; WOW64; rv:21.0) Gecko/20100101 Firefox/21.0");
            add("Mozilla/5.0 (Android; Mobile; rv:14.0) Gecko/14.0 Firefox/14.0");
            add("Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36");
            add("Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19");
            add("Mozilla/5.0 (iPad; CPU OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3");
            add("Mozilla/5.0 (iPod; U; CPU like Mac OS X; en) AppleWebKit/420.1 (KHTML, like Gecko) Version/3.0 Mobile/3A101a Safari/419.3");
        }
    };

    /**
     * 随机获取 user agent
     *
     * @return
     */
    public String randomUserAgent() {
        Random random = new Random();
        int num = random.nextInt(USER_AGENT.size());
        return USER_AGENT.get(num);
    }

    /**
     * 设置代理ip池
     *
     * @param queue 队列
     * @throws IOException
     */
    public void proxyIpPool(LinkedBlockingQueue<String> queue) throws IOException {


        // 每次能随机获取一个代理ip
        String proxyUrl = "http://192.168.99.100:5010/get_all/";

        CloseableHttpClient httpclient = HttpClients.createDefault();

        HttpGet httpGet = new HttpGet(proxyUrl);
        CloseableHttpResponse response = httpclient.execute(httpGet);
        if (response.getStatusLine().getStatusCode() == 200) {
            HttpEntity entity = response.getEntity();
            String body = EntityUtils.toString(entity, "utf-8");

            JSONArray jsonArray = JSON.parseArray(body);
            int size = Math.min(100, jsonArray.size());
            for (int i = 0; i < size; i++) {
                // 将请求结果格式化成json
                JSONObject data = jsonArray.getJSONObject(i);
                String proxy = data.getString("proxy");
                queue.add(proxy);
            }
        }
        response.close();
        httpclient.close();
        return;
    }


    /**
     * 随机获取一个代理ip
     *
     * @return
     * @throws IOException
     */
    public String randomProxyIp() throws IOException {

        // 每次能随机获取一个代理ip
        String proxyUrl = "http://192.168.99.100:5010/get/";

        String proxy = "";

        CloseableHttpClient httpclient = HttpClients.createDefault();

        HttpGet httpGet = new HttpGet(proxyUrl);
        CloseableHttpResponse response = httpclient.execute(httpGet);
        if (response.getStatusLine().getStatusCode() == 200) {
            HttpEntity entity = response.getEntity();
            String body = EntityUtils.toString(entity, "utf-8");
            // 将请求结果格式化成json
            JSONObject data = JSON.parseObject(body);
            proxy = data.getString("proxy");
        }
        return proxy;
    }

    /**
     * 豆瓣电影链接列表
     *
     * @return
     */
    public List<String> movieList(LinkedBlockingQueue<String> queue) {
        // 获取60条电影链接
        String url = "https://movie.douban.com/j/search_subjects?type=movie&tag=热门&sort=recommend&page_limit=40&page_start=0";
        List<String> movies = new ArrayList<>(40);
        try {
            CloseableHttpClient client = HttpClients.createDefault();
            HttpGet httpGet = new HttpGet(url);
            // 设置 ip 代理
            HttpHost proxy = null;
            // 随机获取一个代理IP
            String proxy_ip = randomProxyIp();
            if (StringUtils.isNotBlank(proxy_ip)) {
                String[] proxyList = proxy_ip.split(":");
                System.out.println(proxyList[0]);
                proxy = new HttpHost(proxyList[0], Integer.parseInt(proxyList[1]));
            }
            // 随机获取一个请求头
            httpGet.setHeader("User-Agent", randomUserAgent());
            RequestConfig requestConfig = RequestConfig.custom()
                    .setProxy(proxy)
                    .setConnectTimeout(10000)
                    .setSocketTimeout(10000)
                    .setConnectionRequestTimeout(3000)
                    .build();
            httpGet.setConfig(requestConfig);
            CloseableHttpResponse response = client.execute(httpGet);
            System.out.println("获取豆瓣电影列表,返回验证码:" + response.getStatusLine().getStatusCode());
            if (response.getStatusLine().getStatusCode() == 200) {
                HttpEntity entity = response.getEntity();
                String body = EntityUtils.toString(entity, "utf-8");
                // 将请求结果格式化成json
                JSONObject jsonObject = JSON.parseObject(body);
                JSONArray data = jsonObject.getJSONArray("subjects");
                for (int i = 0; i < data.size(); i++) {
                    JSONObject movie = data.getJSONObject(i);
                    movies.add(movie.getString("url"));
                }
            }
            response.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {

        }
        return movies;
    }


    public static void main(String[] args) {
        // 存放代理ip的队列
        LinkedBlockingQueue<String> queue = new LinkedBlockingQueue(100);

        try {
            CrawlerMovieProxy crawlerProxy = new CrawlerMovieProxy();
            // 初始化ip代理队列
            crawlerProxy.proxyIpPool(queue);
            // 获取豆瓣电影列表
            List<String> movies = crawlerProxy.movieList(queue);

            //创建固定大小的线程池
            ExecutorService exec = Executors.newFixedThreadPool(5);
            for (String url : movies) {
                //执行线程
                exec.execute(new CrawlMovieProxyThread(url, queue, crawlerProxy));
            }
            //线程关闭
            exec.shutdown();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

/**
 * 采集豆瓣电影线程
 */
class CrawlMovieProxyThread extends Thread {
    // 待采集链接
    String url;
    // 代理ip队列
    LinkedBlockingQueue<String> queue;
    // 代理类
    CrawlerMovieProxy crawlerProxy;

    public CrawlMovieProxyThread(String url, LinkedBlockingQueue<String> queue, CrawlerMovieProxy crawlerProxy) {
        this.url = url;
        this.queue = queue;
        this.crawlerProxy = crawlerProxy;
    }

    public void run() {
        String proxy;
        String[] proxys = new String[2];
        try {
            Connection connection = Jsoup.connect(url)
                    .method(Connection.Method.GET)
                    .timeout(50000);

            // 如果代理ip队列为空,则重新获取ip代理
            if (queue.size() == 0) crawlerProxy.proxyIpPool(queue);
            // 从队列中获取代理ip
            proxy = queue.poll();
            // 解析代理ip
            proxys = proxy.split(":");
            // 设置代理ip
            connection.proxy(proxys[0], Integer.parseInt(proxys[1]));
            // 设置 user agent
            connection.header("User-Agent", crawlerProxy.randomUserAgent());
            Connection.Response Response = connection.execute();
            System.out.println("采集豆瓣电影,返回状态码:" + Response.statusCode() + " ,请求ip:" + proxys[0]);
        } catch (Exception e) {
            System.out.println("采集豆瓣电影,采集出异常:" + e.getMessage() + " ,请求ip:" + proxys[0]);
        }
    }
}

Для запуска измененной программы захвата может потребоваться несколько запусков, поскольку ваш IP-адрес прокси-сервера может быть недействительным каждый раз. Если IP-адрес прокси-сервера действителен, вы получите следующий результат

Из результатов видно, что за 40 посещений страницы сведений о фильме большое количество IP-адресов прокси-серверов недействительны, и только небольшое количество IP-адресов прокси-серверов являются действительными. Результаты прямо доказывают, что доступность бесплатных IP-адресов прокси невысока, поэтому, если вам нужно использовать IP-адреса прокси в Интернете, лучше всего использовать платные IP-адреса прокси. Хотя доступность нашего собственного пула IP-прокси не слишком высока, IP-прокси, который мы настроили для доступа к фильмам Douban, оказался успешным, и IP-прокси успешно обошел ограничения Douban.

Есть много причин, по которым серверы сканера блокируются, в этой статье в основном рассказывается, как обойти ограничения доступа Douban, установив IP-прокси и подделав заголовки запросов агента пользователя. Как сделать так, чтобы наша программа не рассматривалась сайтом ресурса как программа-краулер? Необходимо сделать три вещи:

  • Поддельный заголовок запроса агента пользователя
  • Использование IP-прокси
  • Нефиксированный интервал сбора данных

希望这篇文章对你有所帮助,下一篇是关于多线程爬虫的探索。 Если вас интересуют рептилии, вы можете обратить внимание на волну, учиться друг у друга и добиваться прогресса друг с другом.

Что касается недостатков статьи, я надеюсь, что вы можете дать больше указаний, учиться вместе и вместе добиваться прогресса.

Наконец

Сделайте небольшую рекламу, добро пожаловать, чтобы отсканировать код и подпишитесь на общедоступную учетную запись WeChat: «Технический блог брата Пинтоу», давайте вместе добьемся прогресса.

平头哥的技术博文