你有多久没有看星星了呢?【爬取 NASA 的科普网站上的所有图片】

菠萝🍍的博客 有谁不喜欢吃菠萝呢 本文由博客端 https://www.runnable.run 主动推送

1. 前景提要

在网上冲浪的时候看到原来 NASA 航天局有科普网站,每天一张科普图片,而且是非常高清的那种,很想下载下来做壁纸。

所以打算写一个 Java 爬虫爬取所有的图片,也可以加入一些通知,每天晚上进行检查,当检查到有更新的时候,第二天早上起床推送到手机端。当然这个功能还没有实现。

2. 进行开发

2.1 开发环境

2.2 Maven pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.yeahvin</groupId>
    <artifactId>getStars</artifactId>
    <version>1.0.1-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>us.codecraft</groupId>
            <artifactId>webmagic-core</artifactId>
            <version>0.7.5</version>
        </dependency>
        <dependency>
            <groupId>us.codecraft</groupId>
            <artifactId>webmagic-extension</artifactId>
            <version>0.7.5</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>com.yeahvin.StartApplication</mainClass>
                    <layout>ZIP</layout>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

2.3 Java 爬虫 webmagic 的使用

这个框架也是非常容易上手和使用,官方手册在这里 WebMagic in Action

对于这个框架的使用,这里简单的介绍一下,如下图,我们开发者需要在整个流程中需要做的是:

  1. 编写 PageProceser
  2. 编写 Pipeline

就可以完成一个非常简单的爬虫。推荐大家先看看这个例子:第一个例子

image

在这个项目中,对于整个 nasa 网站的分析其实非常的简单,流程大概是

请求总览页面--> 获取已经存在所有链接,进行遍历--> 请求详情网页---> 下载图片

启动类

ok, 那么来进行开发,编写 StartApplication,这是启动爬虫的地方。

package com.yeahvin;

import com.yeahvin.pageProcessors.NasaStarsProcessor;
import com.yeahvin.pipelines.NasaStarsPipeline;
import lombok.extern.slf4j.Slf4j;
import us.codecraft.webmagic.Spider;

/**
 * @author Asher
 * on 2021/11/6
 */
@Slf4j
public class StartApplication {

    //下载图片存储路径
    public static final String DOWNLOAD_PATH = "/Users/asher/gitWorkspace/temp/";
    public static final String INDEX_PATH = "https://apod.nasa.gov/apod/archivepix.html";

    public static void main(String[] args) {
        Spider.create(new NasaStarsProcessor())
                .addUrl(INDEX_PATH)
                .addPipeline(new NasaStarsPipeline())
                .run();
    }
}

以上代码中,Spider.create(Processor) 可以非常快速的构建一个爬虫,然后 addUrl 配置请求的地址,addPipeline 配置当爬虫抓取到数据时应该怎么进行处理,最后就是 run 进行启动了

编写 NasaStarsProcessor

编写这个的原因是为了,请求到网页之后,怎么对网页上的内容进行一个分析和处理

package com.yeahvin.pageProcessors;

import com.yeahvin.StartApplication;
import com.yeahvin.utils.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.processor.PageProcessor;

import java.io.File;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.yeahvin.StartApplication.DOWNLOAD_PATH;

/**
 * @author Asher
 * on 2021/11/6
 */
@Slf4j
public class NasaStarsProcessor implements PageProcessor {

    private Site site = Site.me().setRetryTimes(5).setSleepTime(1000).setTimeOut(10000);

    @Override
    public void process(Page page) {
        if (page.getUrl().regex(StartApplication.INDEX_PATH).match()){

            //获取文件夹下已经保存的文件
            File saveDir = new File(DOWNLOAD_PATH);
            List<String> fileNames = Arrays.stream(Optional.ofNullable(saveDir.listFiles()).orElse(new File[]{}))
                    .map(File::getName)
                    .collect(Collectors.toList());

            log.info("开始主页抓取");
            List<String> starsInfos = page.getRawText()
                    .lines()
                    .map(line -> {
                        List<String> infoList = StringUtil.getRegexString(line, "^\\d{4}.*<br>$");
                        boolean flag = infoList.size() > 0;
                        return flag ? infoList.get(0) : null;
                    })
                    .filter(StringUtils::isNoneBlank)
                    .collect(Collectors.toList());

            starsInfos.forEach(info -> {
                String link = StringUtil.getRegexString(info, "ap.*html").get(0);
                String name = StringUtil.getRegexString(info, ">.*</a>").get(0)
                        .replace("</a>", "")
                        .replace(">", "");

                //如果已经存在对应的文件,则进行跳过
                if (fileNames.stream().filter(fileName -> fileName.contains(name)).count() > 0) {
                    log.info("文件 [{}] 已经存在,进行跳过", name);
                    return;
                }
                page.addTargetRequest("https://apod.nasa.gov/apod/" + link);
            });
            log.info("完成主页抓取,一共有{}条数据", starsInfos.size());
        }else {
            page.getRawText()
                .lines()
                .filter(line -> line.contains("<title>")
                        || Pattern.compile("<a href=\"image.*jpg.*\">").matcher(line).find()
                        || Pattern.compile("<a href=\"image.*jpg.*\"").matcher(line).find()
                )
                .forEach(line -> {
                    if (line.contains("<title>")){
                        String date = StringUtil.getRegexString(line, "\\d{4}.*-").get(0)
                                .replace("-", "");
                        String name = StringUtil.getRegexString(line, "-.*").get(0)
                                .replace("-", "");
                        page.putField("date", date);
                        page.putField("name", name);
                    }else {
                        String imageLink = StringUtil.getRegexString(line, "image.*jpg").get(0);
                        page.putField("imageLink", imageLink);
                    }
                    page.putField("url", page.getUrl());
                });

        }
    }

    @Override
    public Site getSite() {
        return site;
    }
}

以上代码的逻辑大概是:

编写 NasaStarsPipeline

编写这个 NasaStarsPipeline 就是为了在上面的 Processor 放入 Field 之后,我们需要对 Field 的内容进行处理的过程。

其中大致的逻辑是:

package com.yeahvin.pipelines;

import com.yeahvin.utils.X509TrustManager;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import us.codecraft.webmagic.ResultItems;
import us.codecraft.webmagic.Task;
import us.codecraft.webmagic.pipeline.Pipeline;

import java.io.File;

import static com.yeahvin.StartApplication.DOWNLOAD_PATH;

/**
 * @author Asher
 * on 2021/11/6
 */
@Slf4j
public class NasaStarsPipeline implements Pipeline {

    @Override
    public void process(ResultItems resultItems, Task task) {
        if (resultItems.getAll()!=null && resultItems.getAll().size()>0){
            String date = resultItems.get("date");
            String name = resultItems.get("name");
            String imageLink = resultItems.get("imageLink");

            if (StringUtils.isBlank(imageLink)){
                log.error("获取图片下载链接失败,name:{}, date:{}, url:{}", name, date, resultItems.get("url"));
                return;
            }

            String fileName = date + name + ".jpg";
            if (new File(DOWNLOAD_PATH + fileName).exists()) {
                log.info("下载图片{}已经存在,跳过,name:{}, date:{}", imageLink, name, date);
                return;
            }
            try {
                X509TrustManager.downLoadFromUrlHttps("https://apod.nasa.gov/apod/" + imageLink, fileName, DOWNLOAD_PATH);
            } catch (Exception e) {
                log.error("图片下载失败:", e);
                e.printStackTrace();
            }
            log.info("下载图片{}完成,name:{}, date:{}", imageLink, name, date);
        }
    }

}

当你写完这么多的时候,整个代码其实就已经完成了,后面可以再做一些扩展。
比如图片多线程的下载,每天定时任务去执行,这里的话我是通过 shell 脚本去完成这个定时任务的,就不打算常挂这个应用在内存中。

3. 代码

地址:getStars

  • 爬虫

    网络爬虫(Spider、Crawler),是一种按照一定的规则,自动地抓取万维网信息的程序。

    105 引用 • 270 回帖
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    2991 引用 • 8144 回帖 • 592 关注
  • NASA
    3 引用 • 15 回帖

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...