1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/mier520-simple-spder

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
Внести вклад в разработку кода
Синхронизировать код
Отмена
Подсказка: Поскольку Git не поддерживает пустые директории, создание директории приведёт к созданию пустого файла .keep.
Loading...
README.md

Simple-spider

Веб-паук для сети, упрощённый, быстрый в освоении.

Реализован на Jsoup, поддерживает синтаксис cssQuery. Поддерживает преобразование динамических и статических данных в объекты Bean. Поддерживает IP-прокси. Поддерживает мониторинг во время сбора и анализа данных.

Полный пример: простой пример использования, пример извлечения данных из романа, пример извлечения с использованием пула IP-адресов.

Технический чат: 1134935268

QQ-чат WeChat-чат

Практический проект

Simple-spider полностью обрабатывает основную часть работы паука в проекте. Адрес проекта: http://www.51fjing.com.

Проект новый, надеемся на активное использование: http://www.51fjing.com

Проект новый, надеемся на активное использование: http://www.51fjing.com

Использование

Очень просто. Посмотрите демо для начала работы, а затем исходный код для разработки.

Пример

  1. Упрощённый пример — синхронное извлечение данных и немедленное получение результата:
    /**
     * Синхронное извлечение
     * Ожидание и получение данных извлечения
     */
    private static void spiderNow() throws UnsupportedEncodingException {
        // Целевой URL
        String url = "http://www.530p.com/s/" + URLEncoder.encode("明天下" ,"UTF-8");
        // Динамически создаём содержимое страницы извлечения данных, конфигурацию полей можно посмотреть в com.qianxun.spider.bean.SpiderBean и com.qianxun.spider.bean.SpiderBeanField
        SpiderBean spiderBean = SpiderBeanBuilder.newly()
                .fieldBuilder().fieldName("bookName").collection(true).selector("body > div:nth-child(3) > div.conter > ul > li.conter1 > a").valueKey("text").buildFieldAndAdd()
                .fieldBuilder().fieldName("bookHref").collection(true).selector("body > div:nth-child(3) > div.conter > ul > li.conter1 > a").valueKey("href").buildFieldAndAdd()
                .fieldBuilder().fieldName("lastChapterName").collection(true).selector("body > div:nth-child(3) > div.conter > ul > li.conter2 > a").valueKey("text").buildFieldAndAdd()
                .fieldBuilder().fieldName("lastChapterHref").collection(true).selector("body > div:nth-child(3) > div.conter > ul > li.conter2 > a").valueKey("href").buildFieldAndAdd()
                .fieldBuilder().fieldName("author").collection(true).selector("body > div:nth-child(3) > div.conter > ul > li.conter4").valueKey("text").buildFieldAndAdd()
                .fieldBuilder().fieldName("lastUpdateTime").collection(true).selector("body > div:nth-child(3) > div.conter > ul > li.conter3").valueKey("text").buildFieldAndAdd()
                .build();
        
        // Извлекаем целевые данные
        Object obj = Spider.get(spiderBean ,url);
        System.out.println(obj);
    }
  1. Асинхронный пример извлечения:
    /**
     * Асинхронное извлечение
     * Мониторинг процесса извлечения через пользовательский SpiderMonitor
     * Обработка извлечённых и проанализированных данных через пользовательский SpiderPipeline
     */
    private static void syncSpider() {
        // Динамическое создание содержимого, можно игнорировать при меньшей сложности
        SpiderBean spiderBean = SpiderBeanBuilder.newly().beanClass(Book.class)
                .fieldBuilder().fieldName("name").selector("body > div:nth-child(3) > div.tna > a").valueKey("text").buildFieldAndAdd()
                .fieldBuilder().fieldName("author").selector("body > div:nth-child(3) > table > tbody > tr:nth-child(1) > td:nth-child(3)").valueKey("text").buildFieldAndAdd()
                .fieldBuilder().fieldName("lastChapterUpdateTime").selector("body > div:nth-child(3) > table > tbody > tr:nth-child(1) > td:nth-child(7)").valueKey("text").buildFieldAndAdd()
                .fieldBuilder().fieldName("lastChapterName").selector("body > div:nth-child(3) > table > tbody > tr:nth-child(2) > td > a").valueKey("text").buildFieldAndAdd()
                .fieldBuilder().fieldName("lastChapterHref").addTaskUrl().selector("body > div:nth-child(3) > table > tbody > tr:nth-child(2) > td > a").valueKey("href").buildFieldAndAdd()
                .fieldBuilder().fieldName("description").selector("body > div:nth-child(3) > table > tbody > tr:nth-child(4) > td").valueKey("text").buildFieldAndAdd()
                .fieldBuilder().fieldName("cover").addTaskUrl().selector("body > div:nth-child(3) > table > tbody > tr:nth-child(1) > td:nth-child(1) > img").valueKey("src").buildFieldAndAdd()
                .fieldBuilder().fieldName("chapterList").collection(true).spiderBean(SpiderBeanBuilder.newly().beanClass(Chapter.class)
                        .fieldBuilder().fieldName("chapterName").collection(true).selector("body >
``` ```
div:nth-child(3) > div.conter > div > a").valueKey("text").buildFieldAndAdd()
                        .fieldBuilder().fieldName("chapterHref").collection(true).selector("body > div:nth-child(3) > div.conter > div > a").valueKey("href").buildFieldAndAdd())
                        .build()).buildFieldAndAdd().build();

// 配置SpiderBean对应的解析url地址模板、爬取数据监听、解析数据监听、以及爬取数据管道输出
// SpiderBeanConfig.urlTemplate :设定SpiderBean解析的页面模板url地址,即爬取的页面URl地址与该地址匹配时,才运用该spiderBean进行内容解析;详情查看该字段的注释
// ConsoleSpiderMonitor : 数据监听
// ConsoleSpiderPipeline : 爬取数据输出管道
SpiderBeanConfig spiderBeanConfig = SpiderBeanConfig.newly()
    .spiderBean(spiderBean).urlTemplate("http://www.530p.com/qihuan/shinue-9534/")
    .pipeline(new ConsoleSpiderPipeline()).monitor(new ConsoleSpiderMonitor());

//异步爬取:setUrl 配置起始爬取地址
Spider.newly().debug(true).config(spiderBeanConfig).setUrl("http://www.530p.com/qihuan/shinue-9534/").syncStart();
}

Пример статического преобразования данных в Bean-объект — аннотация

private static void spiderBeanClassNow(){
    Object obj = Spider.get(SpiderBeanBuilder.newly().buildClass(Book.class) ,"http://www.530p.com/qihuan/shinue-9534/");
    System.out.println(obj);
}

@Data
@com.qianxun.spider.annote.SpiderBean
public static class Chapter {
    @SpiderBeanField(collection = true ,selector = "body > div:nth-child(3) > div.conter > div > a" ,valueKey = "text")
    private String chapterName;
    @SpiderBeanField(collection = true ,selector = "body > div:nth-child(3) > div.conter > div > a" ,valueKey = "href")
    private String chapterHref;
    public Chapter() {
    }
}

@Data
@Accessors(chain = true)
@com.qianxun.spider.annote.SpiderBean
public static class Book {
    @SpiderBeanField(selector = "body > div:nth-child(3) > div.tna > a" ,valueKey = "text")
    private String name;
    @SpiderBeanField(selector = "body > div:nth-child(3) > table > tbody > tr:nth-child(1) > td:nth-child(3)" ,valueKey = "text")
    private String author;
    @SpiderBeanField(selector = "body > div:nth-child(3) > table > tbody > tr:nth-child(1) > td:nth-child(1) > img" ,valueKey = "src")
    private String cover;
    @SpiderBeanField(selector = "body > div:nth-child(3) > table > tbody > tr:nth-child(4) > td" ,valueKey = "text")
    private String description;
    @SpiderBeanField(selector = "body > div:nth-child(3) > table > tbody > tr:nth-child(2) > td > a" ,valueKey = "text")
    private String lastChapterName;
    @SpiderBeanField(selector = "body > div:nth-child(3) > table > tbody > tr:nth-child(2) > td > a" ,valueKey = "text")
    private String lastChapterHref;
    @SpiderBeanField(selector = "body > div:nth-child(3) > table > tbody > tr:nth-child(1) > td:nth-child(7)" ,valueKey = "text")
    private String lastChapterUpdateTime;
    @SpiderBeanField(spiderBeanClass = Chapter.class ,collection = true)
    private List<Chapter> chapterList;
}

Прокси-сервер для обхода — пул IP-адресов прокси-серверов

 private static void proxyTest(){
    //代理池配置
    DefaultHttpProxyPool proxyPool = new DefaultHttpProxyPool(3 ,true ,false);
    proxyPool.addLast(new HttpProxy("47.88.7.18" ,8088) ,15 * 60 * 60 * 1000);
    proxyPool.addLast(new HttpProxy("161.35.124.128" ,3128) ,15 * 60 * 60 * 1000);
    proxyPool.addLast(new HttpProxy("157.245.221.254" ,8080) ,15 * 60 * 60 * 1000);

    SpiderBean spiderBean = SpiderBeanBuilder.newly()
            .fieldBuilder().fieldName("ip").selector("title").valueKey("text").buildFieldAndAdd().build());

    SpiderBeanConfig spiderBeanConfig = SpiderBeanConfig.newly().urlTemplate("*")
            .pipeline(new ConsoleSpiderPipeline())
            .monitor(new ConsoleSpiderMonitor()).spiderBean(spiderBean);

    //爬虫相关配置,详情参见字段注释
    SpiderConfig spiderConfig = new SpiderConfig();
    spiderConfig.setConnTimeoutMillis(1000).setHttpProxyPool(proxyPool);
``` ```
Spider.newly().setUrl("http://202020.ip138.com/").config(spiderBeanConfig).config(spiderConfig.setDebug(false)).intervalMillis(1000).syncStart();
}
  1. Практический пример (можно сразу использовать в проекте) — динамическое создание и динамическое обновление пула IP-прокси
@Slf4j
public class ProxyIpSpiderDemo {
    private DefaultHttpProxyPool pool = new DefaultHttpProxyPool(10 ,false ,false);
    private AtomicBoolean spiderRunStatus = new AtomicBoolean(false);

    public void startSpider() {
        if(isActive()){
            return;
        }
        this.spiderRunStatus.set(true);
        try {
            SchedulerUtil.schedule(this::innerStartSpider);
        }catch (Throwable e){
            new Thread(this::innerStartSpider).start();
        }
    }

    public void stopSpider() {
        this.spiderRunStatus.set(false);
    }

    public boolean isActive() {
        return this.spiderRunStatus.get();
    }

    public HttpProxyPool getHttpProxyPool(int retry , boolean localRetry , boolean localPriority) {
        return new HttpProxyPool() {
            @Override
            public HttpProxy next() {
                if(isEmpty()){
                    //如果代理池数据为空,通知管理员
                    log.info("代理IP池数量为空" ,"代理IP池数量为空 ,代理爬虫状态:" + (isActive() ? "运行中" : "挂掉"));
                }
                return pool.next();
            }

            @Override
            public int retry() {
                return retry;
            }

            @Override
            public boolean localRetry() {
                return localRetry;
            }

            @Override
            public boolean localPriority() {
                return localPriority;
            }

            @Override
            public void makeFailure(HttpProxy httpProxy) {
                pool.makeFailure(httpProxy);
            }

            @Override
            public boolean isEmpty() {
                return pool.isEmpty();
            }

            @Override
            public int size() {
                return pool.size();
            }
        };
    }

    private void innerStartSpider() {
        System.out.println("---代理IP爬虫启动,准备爬取...");
        //данные не удалось получить
        int failedCount = 0;
        //максимальное количество неудачных попыток, после достижения которого необходимо уведомить администратора, чтобы избежать аномалий в работе из-за недоступности прокси-IP
        final int failedGrace = 100;

        while (this.spiderRunStatus.get()){
            try {
                long sleepTimeMillis;
                ProxyIpInfoBean proxyIpInfoBean = spiderProxyIp();
                if (proxyIpInfoBean == null) {
                    sleepTimeMillis = TimeUnit.SECONDS.toMillis(5);
                    if(++failedCount > failedGrace){
                        failedCount = 0;
                        log.error("代理IP爬取失败");
                        System.err.println("代理IP爬取失败");
                    }
                }else{
                    sleepTimeMillis = sleepTimeMillis(proxyIpInfoBean.getUpdateTimeMillis());
                    //IP-фильтрация
                    //адрес тестового URL
                    final String testUrl = "http://www.baidu.com";
                    //максимальный допустимый период ожидания
                    final int timeout = 5000;

                    //флаг для удаления устаревших прокси-IP, используется для очистки ранее добавленных прокси-IP, так как большинство из них больше не доступны
                    AtomicBoolean clearBeforeFlag = new AtomicBoolean(false);

                    proxyIpInfoBean.getIpList().forEach(proxyIpBean -> {
                        try {
                            int statusCode = Jsoup.connect(testUrl).timeout(timeout).proxy(proxyIpBean.getHost() ,proxyIpBean.getPort()).execute().statusCode();
                            if(statusCode >= 200 && statusCode < 300){
                                if(!clearBeforeFlag.get()){
                                    System.err.println("--- older proxy ip was clear");
                                    clearBeforeFlag.set(true);
                                    this.pool.clear();
                                }
                                this.pool.addLast(new HttpProxy(proxyIpBean.getHost() ,proxyIpBean.getPort()) ,sleepTimeMillis);
                                System.err.println("--- spider available proxy ip: " + proxyIpBean.getHost());
                            }
``` ```
} catch (IOException e) {
    // 请求异常,该代理IP不可用
}
});
}
log.info("-- 代理IP краулер скоро перейдёт в спящий режим, ожидается сон: " + sleepTimeMillis + " миллисекунд");
System.out.println("-- 代理IP краулер скоро перейдет в спящий режим, ожидается сон: " + sleepTimeMillis + " миллисекунды");
// Краулер не смог получить данные, будет спать 5 секунд и повторит попытку
synchronized (Thread.currentThread()){
    Thread.currentThread().wait(sleepTimeMillis);
}
} catch (InterruptedException e){
    // Прерывание потока
    break;
}
private long sleepTimeMillis(long updateTimeMillis){
    // Список прокси обновляется каждые 15 минут, сейчас основной IP взят с этого сайта
    return TimeUnit.MINUTES.toMillis(15) - (System.currentTimeMillis() - updateTimeMillis);
}
private ProxyIpInfoBean spiderProxyIp(){
    // список прокси обновляется на сайте каждые 15 минут
    SpiderBean spiderBean = SpiderBeanBuilder.newly()
        .fieldBuilder().fieldName("ipInfoJsonList").collection(true)
        .selector("table.highlight.tab-size.js-file-line-container > tbody > tr > td:nth-child(2)").valueKey("text").buildFieldAndAdd()
        .fieldBuilder().fieldName("updateTime").selector("body > div.application-main > div > main > div.container-xl.clearfix.new-discussion-timeline.px-3.px-md-4.px-lg-5 > div > div.Box.d-flex.flex-column.flex-shrink-0.mb-3 > div.Box-header.Box-header--blue.Details.js-details-container > div > div.flex-1.d-flex.flex-items-center.ml-3.min-width-0 > div > span:nth-child(2) > a")
        .valueKey("text").buildFieldAndAdd().build();
    Object o = Spider.get(spiderBean,"https://github.com/fate0/proxylist/blob/master/proxy.list");
    if(o != null){
        try {
            Map<String, Object> objectMap = ((Map<String, Object>) o);
            List<Object> ipInfoList = (List<Object>) objectMap.get("ipInfoJsonList");
            List<ProxyIpBean> proxyIpBeans = new ArrayList<>(ipInfoList.size());
            ipInfoList.forEach(info -> {
                JSONObject jsonObject = JSON.parseObject(info.toString());
                ProxyIpBean proxyIpBean = new ProxyIpBean();
                proxyIpBean.setHost(jsonObject.getString("host")).setPort(jsonObject.getInteger("port"))
                    .setResponseTimeMillis((long) (jsonObject.getDouble("response_time") * 1000));
                proxyIpBeans.add(proxyIpBean);
            });
            Collections.sort(proxyIpBeans, (o1, o2) -> o1.responseTimeMillis > o2.responseTimeMillis ? 1 : (o1.responseTimeMillis == o2.responseTimeMillis ? 0 : -1));

            String updateTimeStr = objectMap.get("updateTime").toString();
            long updateTimeMillis = ObjectUtil.isBlank(updateTimeStr) ? System.currentTimeMillis() : new Date(updateTimeStr).getTime();

            ProxyIpInfoBean proxyIpInfoBean = new ProxyIpInfoBean();
            proxyIpInfoBean.setUpdateTimeMillis(updateTimeMillis).setIpList(proxyIpBeans);
            return proxyIpInfoBean;
        }catch (Throwable throwable){
            // Предотвратить сбой при получении данных из-за изменений на целевом сайте
            log.error("Ошибка при получении прокси IP, целевой адрес: https://github.com/fate0/proxylist/blob/master/proxy.list",throwable);
        }
    }
    return null;
}
@Data
@Accessors(chain = true)
private static class ProxyIpInfoBean {
    private long updateTimeMillis;
    private List<ProxyIpBean> ipList;
}
@Data
@Accessors(chain = true)
private static class ProxyIpBean {
    private String host;
    private int port;
    private long responseTimeMillis;
}

Комментарии ( 0 )

Вы можете оставить комментарий после Вход в систему

Введение

Описание недоступно Развернуть Свернуть
AGPL-3.0
Отмена

Обновления

Пока нет обновлений

Участники

все

Недавние действия

Загрузить больше
Больше нет результатов для загрузки
1
https://api.gitlife.ru/oschina-mirror/mier520-simple-spder.git
git@api.gitlife.ru:oschina-mirror/mier520-simple-spder.git
oschina-mirror
mier520-simple-spder
mier520-simple-spder
master