分布式爬虫系统实现 | 青训营笔记

325 阅读4分钟

这是我参与「第四届青训营 」笔记创作活动的第15天

系统架构

未命名文件 (1).png

  • redis:存储URL
  • Flink:流式计算管理url
  • 爬虫系统:网页爬取
  • Zookeeper:监控爬虫状态
  • 存储MYSQL和HBase:存储爬取网络后的结果

软件安装

MySQL安装与卸载

安装

MySQL官网下载 dev.mysql.com/downloads/m…

Snipaste_2022-08-18_17-37-49.jpg 下载完解压,将安装包放到任意位置即可

Snipaste_2022-08-18_17-39-35.jpg 以管理员身份打开cmd命令行,进入mysql文件夹的bin目录

Snipaste_2022-08-18_17-43-11.jpg

  1. 初始化MySQL,并记录生成的用户密码root的随机密码,输入mysqld --initialize --console
  2. 安装MySQL服务,命令窗口内输入:mysqld --install
  3. 启动MySQL服务,命令窗口内输入:net start mysql
  4. 输入mysql -u root -p 输入密码进入数据库,密码在第一步运行结果
  5. 在命令窗口内依次输入:ALTER USER 'root'@'localhost' IDENTIFIED BY 'password' PASSWORD EXPIRE NEVER;#修改加密规则
  6. ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';#更新一下用户的密码
  7. FLUSH PRIVILEGES; #刷新权限
  8. alter user 'root'@'localhost' identified by '123456';#更改root的密码为123456
  9. 配置环境变量

卸载

  1. 停止服务: 【win+R快捷键】》输入services.msc》进入服务窗口关闭mysql服务;

  2. 卸载程序: 【控制面板】》【程序和功能】》右键卸载程序;

  3. 删除项目根文件夹:进入mysql安装位置,删除mysql的解压文件夹;

  4. 检查服务是否完全删除:如果mysql服务还在,可以使用 sc delete mysql 来删除服务;

  5. 删除C盘隐藏文件夹:显示隐藏文件后,删除C盘下的“C:\ProgramData\MySQL ”所有文件;

  6. 删除注册表信息:【win+R快捷键】》输入regedit 命令打开注册表窗口,删除以下文件

    • HKEY_LOCAL_MACHINE/SYSTEM/ControlSet001/Services/Eventlog/Applications/MySQL
    • HKEY_LOCAL_MACHINE/SYSTEM/ControlSet002/Services/Eventlog/Applications/MySQL
    • HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Eventlog/Applications/MySQL
  7. 删除环境变量里的配置:如果有更改了环境变量,那么进入【计算机】》右键【属性】》【高级系统设置】》【环境变量】》删除系统变量中的MYSQL_HOME变量和删除Path变量中的mysql路径;

  8. 全盘搜索mysql关键字检查:这里使用everything全盘搜索mysql关键字,可以查看系统所有相关文件,以防遗漏。

redis 安装

windows版本readis下载(GitHub):

github.com/tporadowski… 
解压到文件夹

Snipaste_2022-08-18_17-51-56.jpg
配置环境变量

Snipaste_2022-08-18_17-55-35.jpg redis-server.exe redis.windows.conf启动redis服务

Snipaste_2022-08-18_17-54-25.jpg redis-cli.exe -h 127.0.0.1 -p 6379打开客户端

Snipaste_2022-08-18_17-57-03.jpg

Flink安装

archive.apache.org/dist/flink/…
flink1.9.3版本在widows系统下,下载解压,即可使用

Snipaste_2022-08-18_18-01-21.jpg

Snipaste_2022-08-18_18-01-55.jpg

系统实现

flink程序

一个 Flink 程序,其实就是对 DataStream 的各种转换。具体来说,代码基本上都由以下几 部分构成,如图 所示:

  • 获取执行环境(execution environment)

    getExecutionEnvironment

  • 读取数据源(source)

    DataStream stream = env.addSource(...);

    从集合、文件、socket、Kafka、自定义的Source

  • 定义基于数据的转换操作(transformations)

    基本转换算子:map、filter、flatmap

    聚合算子:按键分区、简单聚合、归约聚合

  • 定义计算结果的输出位置(sink)

Snipaste_2022-08-09_21-05-50.jpg

  • 触发程序执行(execute)
    Snipaste_2022-08-09_20-45-40.jpg

Flink实现分布式爬虫

主程序

public class FlinkSpider {
    public static void main(String[] args) throws Exception {
        ISpider iSpider = ISpider.getInstance();

        // 创建流式执行环境
        StreamExecutionEnvironment env =
                StreamExecutionEnvironment.getExecutionEnvironment();

        env.setParallelism(1);
        // 添加数据源
        DataStreamSource<String> stream = env.addSource(new MyRedisSource());

        // 处理数据
        stream.map(new SpiderMapFunction(iSpider));

        env.execute();
    }
}

自定义redis数据源

public class MyRedisSource implements SourceFunction<String> {
  private boolean isRunning =true;
  private Jedis jedis=null;
  private final long SLEEP_MILLION=5000;

  @Override
  public void run(SourceContext<String> sourceContext) throws Exception {
      this.jedis = new Jedis("127.0.0.1", 6379);
      while(isRunning){
          String randomDomain = jedis.srandmember(SpiderConstants.SPIDER_WEBSITE_DOMAINS_KEY);    // jd.com
          String key = randomDomain + SpiderConstants.SPIDER_DOMAIN_HIGHER_SUFFIX;                // jd.com.higher
          String url = jedis.lpop(key);
          if(url == null) {   // 如果为null,则从低优先级中获取
              key = randomDomain + SpiderConstants.SPIDER_DOMAIN_LOWER_SUFFIX;    // jd.com.lower
              url = jedis.lpop(key);
          }
          //System.out.println("---Flink source url");
          sourceContext.collect(url);
          //JedisUtil.returnJedis(jedis);
      }
  }

  @Override
  public void cancel() {
      isRunning=false;
      while(jedis!=null){
          jedis.close();
      }
  }
}

自定义map函数

public class SpiderMapFunction implements MapFunction<String , String> {
  private  ISpider iSpider;
  SpiderMapFunction(ISpider iSpider){
      this.iSpider = iSpider;
  }
  @Override
  public String map(String s) throws Exception {
      // 6.启动爬虫
      // iSpider.start();

      iSpider.startSingle(s);
      return null;
  }
}

爬虫程序

public void startSingle(String url) {
 //while (true) {  // 要想开启循环爬取商品,则必须是执行一个死循环
     //String url = repository.poll();
     String domain = SpiderUtil.getTopDomain(url);   // 获取url对应的顶级域名
     System.out.println("-----flink url"+"    "+url);
     if (url != null) {  // 从url仓库中获取的url不为null
         // 下载网页
         Page page = download(url);
         // 解析网页
         if (page.getContent() != null) { // 只有content不为null时才进行后面的操作,否则没有意义
             parser(page, domain); // 如果该url为列表url,从这里有可能解析出很多的url
             for (String pUrl : page.getUrls()) { // 向url仓库中添加url
                 logger.info(pUrl);
                 String higherUrlMark = urlLevelMarker.get(domain).get("higher");
                 String lowerUrlMark = urlLevelMarker.get(domain).get("lower");
                 if (pUrl.startsWith(higherUrlMark)) {    // 高优先级
                     repository.offerHigher(pUrl);
                 } else if (pUrl.startsWith(lowerUrlMark)) { // 低优先级
                     repository.offerLower(pUrl);
                 }
             }
             if (page.getId() != null) {  // 当商品id不为null时,说明前面解析的url是商品url,而不是列表url,这时存储数据才有意义
                 // 存储解析数据
                 // store(page);
                 // System.out.println(page);
                 MysqlClient.insert(page);
             }
         }
         // 上面操作结束之后必须要休息一会,否则频率太高的话很有可能会被封ip
         SpiderUtil.sleep(1000);
     } else {    // 从url仓库中没有获取到url
         logger.info("没有url,请及时添加种子url");
         SpiderUtil.sleep(2000);
     }
 //}
}

参考:

zhuanlan.zhihu.com/p/150360780 blog.csdn.net/weixin_4489… www.cnblogs.com/darange/p/1…