• Java爬虫系列(五)


    Java爬虫内容补充和ElasticSearch

    1. 案例扩展

    定时任务

    在案例中我们使用的是Spring内置的Spring Task,这是Spring3.0加入的定时任务功能。我们使用注解的方式定时启动爬虫进行数据爬取。

    我们使用的是@Scheduled注解,其属性如下:

    1)**cron:**cron表达式,指定任务在特定时间执行;

    2)fixedDelay:上一次任务执行完后多久再执行,参数类型为long,单位ms

    3)**fixedDelayString:**与fixedDelay含义一样,只是参数类型变为String

    4)**fixedRate:**按一定的频率执行任务,参数类型为long,单位ms

    5)fixedRateString: 与fixedRate的含义一样,只是将参数类型变为String

    6)**initialDelay:**延迟多久再第一次执行任务,参数类型为long,单位ms

    7)**initialDelayString:**与initialDelay的含义一样,只是将参数类型变为String

    8)**zone:**时区,默认为当前时区,一般没有用到

    我们这里的使用比较简单,固定的间隔时间来启动爬虫。例如可以实现项目启动后,每隔一小时启动一次爬虫。

    但是有可能业务要求更高,并不是定时定期处理,而是在特定的时间进行处理,这个时候我们之前的使用方式就不能满足需求了。例如我要在工作日(周一到周五)的晚上八点执行。这时我们就需要Cron表达式了。

    1. Cron表达式

    cron的表达式是字符串,实际上是由七子表达式,描述个别细节的时间表。这些子表达式是分开的空白,代

    1. Seconds

    2. Minutes

    3. Hours

    4. Day-of-Month

    5. Month

    6. Day-of-Week

    7. Year (可选字段)

    例 "0 0 12 ? * WED" 在每星期三下午12:00 执行,

    “*” 代表整个时间段

    每一个字段都有一套可以指定有效值,如

    Seconds (秒) :可以用数字0-59 表示,

    Minutes(分) :可以用数字0-59 表示,

    Hours(时) :可以用数字0-23表示,

    Day-of-Month(天) :可以用数字1-31 中的任一一个值,但要注意一些特别的月份

    Month(月) :可以用0-11 或用字符串:

    JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC

    Day-of-Week(天) :可以用数字1-7表示(1 = 星期日)或用字符口串:

    SUN, MON, TUE, WED, THU, FRI, SAT

    “/”:为特别单位,表示为“每”如“0/15”表示每隔15分钟执行一次,“0”表示为从“0”分开始, “3/20”表示表示每隔20分钟执行一次,“3”表示从第3分钟开始执行

    “?”:表示每月的某一天,或第周的某一天

    “L”:用于每月,或每周,表示为每月的最后一天,或每个月的最后星期几如“6L”表示“每月的最后一个星期五”

    可以使用CronExpBuilder (表达式生成器)生成表达式

    http://image.ayulong.cn/public/data/2020/md/webmagic/2020-10-11_110733.png

    2. Cron测试

    先把之前爬虫的@Component注解取消,避免干扰测试

    //@Component
    public class JobProcessor implements PageProcessor {

    编写使用Cron表达式的测试用例:

    package cn.ayulong.job.task;
    
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;
    
    @Component
    public class TaskTest {
    
        @Scheduled(cron = "0/5 * * * * *")
        public void test() {
            System.out.println("定时任务执行了");
        }
    }

    网页去重

    之前我们对下载的url地址进行了去重操作,避免同样的url下载多次。其实不光url需要去重,我们对下载的内容也需要去重。

    在网上我们可以找到许多内容相似的文章。但是实际我们只需要其中一个即可,同样的内容没有必要下载多次,那么如何进行去重就需要进行处理了

    去重方案介绍

    • 指纹码对比

    最常见的去重方案是生成文档的指纹门。例如对一篇文章进行MD5加密生成一个字符串,我们可以认为这是文章的指纹码,再和其他的文章指纹码对比,一致则说明文章重复。

    但是这种方式是完全一致则是重复的,如果文章只是多了几个标点符号,那仍旧被认为是不重复的,这种方式并不合理

    • BloomFilter

    这种方式就是我们之前对url进行去重的方式,使用在这里的话,也是对文章进行计算得到一个数,再进行对比,缺点和方法1是一样的,如果只有一点点不一样,也会认为不重复,这种方式不合理。

    • KMP算法

    KMP算法是一种改进的字符串匹配算法。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。能够找到两个文章有哪些是一样的,哪些不一样。

    这种方式能够解决前面两个方式的“只要一点不一样就是不重复”的问题。但是它的时空复杂度太高了,不适合大数据量的重复比对。

    还有一些其他的去重方式:最长公共子串、后缀数组、字典树、DFA等等,但是这些方式的空复杂度并不适合数据量较大的工业应用场景。我们需要找到一款性能高速度快,能够进行相似度对比的去重方案

    Google 的 simhash 算法产生的签名,可以满足上述要求。这个算法并不深奥,比较容易理解。这种算法也是目前Google搜索引擎所目前所使用的网页去重算法。

    SimHash

    1. 流程介绍

    simhash是由 Charikar 在2002年提出来的,为了便于理解尽量不使用数学公式,分为这几步:

    1、分词,把需要判断文本分词形成这个文章的特征单词。

    2、hash,通过hash算法把每个词变成hash值,比如“美国”通过hash算法计算为 100101,“51区”通过hash算法计算为 101011。这样我们的字符串就变成了一串串数字。

    3、加权,通过 2步骤的hash生成结果,需要按照单词的权重形成加权数字串,“美国”的hash值为“100101”,通过加权计算为“4 -4 -4 4 -4 4”

    “51区”计算为 “ 5 -5 5 -5 5 5”。

    4、合并,把上面各个单词算出来的序列值累加,变成只有一个序列串。

    “美国”的 “4 -4 -4 4 -4 4”,“51区”的 “ 5 -5 5 -5 5 5”

    把每一位进行累加, “4+5 -4+-5 -4+5 4+-5 -4+5 4+5”à“9 -9 1 -1 1 9”

    5、降维,把算出来的 “9 -9 1 -1 1 9”变成 0 1 串,形成最终的simhash签名。

    2. 签名距离计算

    我们把库里的文本都转换为simhash签名,并转换为long类型存储,空间大大减少。现在我们虽然解决了空间,但是如何计算两个simhash的相似度呢?

    我们通过海明距离(Hamming distance)就可以计算出两个simhash到底相似不相似。两个simhash对应二进制(01串)取值不同的数量称为这两个simhash的海明距离。

    举例如下: 10101 和 00110 从第一位开始依次有第一位、第四、第五位不同,则海明距离为3。对于二进制字符串的a和b,海明距离为等于在a XOR b运算结果中1的个数(普遍算法)。

    3. 导入 simhash 工程

    参考项目

    这个项目不能直接使用,因为jar包的问题,需要进行改造。这里使用已经改造好的 simhash项目

    在idea中导入工程simhash,将其安装到maven仓库即可导入使用, 导入方法参考 我的网站, 导入方法类似

    按照测试用例的要求,准备两个文件,就是需要进行对比的文章

    在上述安装项目到本地仓库后在之前的案例中导入依赖

    <!--simhash网页去重-->
    <dependency>
        <groupId>com.lou</groupId>
        <artifactId>simhasher</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>

    修改代码

    package cn.ayulong.job.task;
    
    import com.lou.simhasher.SimHasher;
    import org.apache.commons.io.IOUtils;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;
    
    import java.io.FileInputStream;
    import java.io.IOException;
    
    @Component
    public class TaskTest {
    
        @Scheduled(cron = "0/5 * * * * *")
        public void test() {
            String str1 = readAllFile("D:/test/testin.txt");
            SimHasher hash1 = new SimHasher(str1);
            //打印simhash签名
            System.out.println("第一个文章的simhash签名" + hash1.getSignature());
            System.out.println("============================");
    
            String str2 = readAllFile("D:/test/testin2.txt");
            //打印simhash签名
            SimHasher hash2 = new SimHasher(str2);
            System.out.println("第二个文章的simhash签名" + hash2.getSignature());
            System.out.println("============================");
    
            //打印海明距离, 越小越相似
            System.out.println("海明距离: " + hash1.getHammingDistance(hash2.getSignature()));
        }
    
        /**
         * 测试用
         * @param filename 名字
         * @return
         */
        public static String readAllFile(String filename) {
            String everything = "";
            try {
                FileInputStream inputStream = new FileInputStream(filename);
                everything = IOUtils.toString(inputStream);
                inputStream.close();
            } catch (IOException e) {
            }
    
            return everything;
        }
    }

    代理的使用

    有些网站不允许爬虫进行数据爬取,因为会加大服务器的压力。其中一种最有效的方式是通过ip+时间进行鉴别,因为正常人不可能短时间开启太多的页面,发起太多的请求。

    我们使用的WebMagic可以很方便的设置爬取数据的时间, 但是这样会大大降低我们爬取数据的效率,如果不小心ip被禁了,会让我们无法爬去数据,那么我们就有必要使用代理服务器来爬取数据。

    1. 代理服务器

    代理(英语:Proxy),也称网络代理,是一种特殊的网络服务,允许一个网络终端(一般为客户端)通过这个服务与另一个网络终端(一般为服务器)进行非直接的连接。

    提供代理服务的电脑系统或其它类型的网络终端称为代理服务器(英文:Proxy Server)。一个完整的代理请求过程为:客户端首先与代理服务器创建连接,接着根据代理服务器所使用的代理协议,请求对目标服务器创建连接、或者获得目标服务器的指定资源。

    我们就需要知道代理服务器在哪里(ip和端口号)才可以使用。网上有很多代理服务器的提供商,但是大多是免费的不好用,付费的还行。

    提供免费代理ip的服务商网站:

    米扑代理

    https://proxy.mimvp.com/free.php

    (需要尝试 有的ip无法使用会报错, 还有一个西刺代理似乎用不了了)

    2. 使用代理

    WebMagic使用的代理APIProxyProvider。因为相对于Site的“配置”,ProxyProvider定位更多是一个“组件”,所以代理不再从Site设置,而是由HttpClientDownloader设置。

    API说明
    HttpClientDownloader.setProxyProvider(ProxyProvider proxyProvider) 设置代理

    ProxyProvider有一个默认实现:SimpleProxyProvider。它是一个基于简单Round-Robin的、没有失败检查的ProxyProvider。可以配置任意个候选代理,每次会按顺序挑选一个代理使用。它适合用在自己搭建的比较稳定的代理的场景。

    如果需要根据实际使用情况对代理服务器进行管理(例如校验是否可用,定期清理、添加代理服务器等),只需要自己实现APIProxyProvider即可。

    请求能返回地址的api

    http://ip.chinaz.com/getip.aspx用不了了, 换成 https://api.myip.com/ 将就一下

    为了避免干扰,先把之前项目中的其他任务的@Component注释掉,再在案例中加入编写以下逻辑:

    package cn.ayulong.job.task;
    
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;
    import us.codecraft.webmagic.Page;
    import us.codecraft.webmagic.Site;
    import us.codecraft.webmagic.Spider;
    import us.codecraft.webmagic.downloader.HttpClientDownloader;
    import us.codecraft.webmagic.processor.PageProcessor;
    import us.codecraft.webmagic.proxy.Proxy;
    import us.codecraft.webmagic.proxy.SimpleProxyProvider;
    
    @Component
    public class ProxyTest implements PageProcessor {
    
        @Scheduled(fixedDelay = 1000)
        public void process() {
            // 创建下载器 Downloader
            HttpClientDownloader httpClientDownloader = new HttpClientDownloader();
            // 给下载器设置代理服务器信息
            httpClientDownloader.setProxyProvider(SimpleProxyProvider.from(new Proxy("221.122.91.65", 80)));
            Spider.create(new ProxyTest())
                    .addUrl("https://api.myip.com/")
                    .setDownloader(httpClientDownloader)// 设置下载器
                    .run();
        }
    
        @Override
        public void process(Page page) {
            System.out.println(page.getHtml().toString());
        }
    
        private Site site = Site.me();
    
        @Override
        public Site getSite() {
            return site;
        }
    }

    2. ElasticSearch环境准备

    安装ElasticSearch服务

     
  • 相关阅读:
    04. SpringCloud实战项目-安装Docker
    03. SpringCloud实战项目-配置虚拟机网络
    02. SpringCloud实战项目-快速搭建Linux环境-运维必备
    01. SpringCloud实战项目-五分钟搞懂分布式基础概念
    docker安装redis
    docker 安装mysql
    安装docker
    配置虚拟机网络
    Vagrant快速搭建Ubuntu虚拟机环境
    5分钟搞懂分布式基础概念
  • 原文地址:https://www.cnblogs.com/zouhong/p/14243549.html
Copyright © 2020-2023  润新知