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

本贴最后更新于 1230 天前,其中的信息可能已经时移世改

1. 前景提要

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

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

2. 进行开发

2.1 开发环境

  • Maven
  • Java 17 (为了顺行 Spring Framework 6 之后的潮流,所以打算之后的开发都用 Zulu JDK17)
  • WebMagic(Java 的爬虫框架)

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; } }

以上代码的逻辑大概是:

  • 属性配置一个 Site,作为爬虫请求网页时的配置。

  • 实现接口 PageProcessor,需要重写方法 process

  • 在方法 process 中,先请求总览页面,通过 if 进行了判断,如果进来的请求和总览页一样,则跳到详情页处理,就是在 else 中。为什么可以这样进行操作,因为在 if(true) 的条件处理中,可以通过以下代码直接在当前 Processor 再请求一次放入的 link,就不必重复编写 Processor,节省开发量。

    page.addTargetRequest("https://apod.nasa.gov/apod/" + link);
  • 其中在 if 条件==true 的时候,会先拿取指定保存文件夹中的所有文件的文件名,当应用中途停止在此启动时,不用把之前的网页再跑一遍。然后 getRawText() 获取到网页文本内容,通过正则进行过滤,拿到所有详情页的 link

  • 所以 else 中的就是详情页的代码处理过程,例如 Astronomy Picture of the Day 界面获取到网页内容后的处理过程。其中 getRawText() 获取到网页文本内容,通过正则进行过滤,page.putField("date", date); 放入 page 变量中,当中放入的值会被传入启动类 StartApplication 指定的 NasaStarsPipeline 中的方法。

编写 NasaStarsPipeline

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

其中大致的逻辑是:

  • 获取到详情页的具体信息,比如 name,date 以及图片的下载链接,然后对图片进行下载。这里需要注意的是,有时候打开的详情页并不一定是图片,还有可能是视频,当出现无法以图片的方式进行解析的时候,这里的处理方式是直接进行跳过。保存错误日志。
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),是一种按照一定的规则,自动地抓取万维网信息的程序。

    106 引用 • 275 回帖
  • Java

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

    3198 引用 • 8215 回帖
  • Web
    119 引用 • 433 回帖 • 8 关注
1 操作
MingGH 在 2021-12-12 09:35:58 更新了该帖

相关帖子

欢迎来到这里!

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

注册 关于
请输入回帖内容 ...
  • PeterChu via Redmi Note 7

    哇,又学到了,之前只知道用 Java 做爬虫是用另一个

    1 回复
  • 其他回帖
  • MingGH 1 评论

    嘿嘿,顺便问下你用的另一个是什么

    嘿嘿,Jsoup,查了下发现还有好几个其他的框架,不过都没听过用过
    PeterChu