ACodingDayBlog

Java 爬虫框架 WebMagic

Java 爬虫框架 WebMagic
2021-12-24 · 8 min read
爬虫 Java 折腾 状态码

前言

网上冲浪🏂,经常会遇到想要批量保存的沙雕表情包、Coser 套图、二次元图片等素材资源......那什么能实现快捷有效的批量下载呢?爬虫程序。
说起爬虫,我的第一反应就是 Python 语言,然后想到它的相关工具:Scrapy、Selenium......但是我没有系统学习过 Python学过一点皮毛),而且对它也不怎么感兴趣啊😟。

插件 — 图片助手

对于不会 Python 爬虫的我,难不成只能靠鼠标在目标网页上点击右键 -> “将图像另存为”?效率低下。
幸好我找到了一款优秀的浏览器扩展插件——图片助手,商店中显示它的用户高达 30W+,而且评分不低!
ImageAssistant
简单来说,它可以快速提取网页图片并批量下载,标签页中将整个页面的图片都以缩略图的形式展示出来,并且可以对缩略图添加筛选条件(过滤一些尺寸较小的图片)。
使用的方法很简单,启用后,在目标网页上点击扩展的“提取网页图片”功能即可。
使用简单
以哔哩哔哩首页为例,提取后可以看到图片的格式类型、分辨率大小、筛选条件等设置,点击选中下载即可。
提取图片结果

框架 — WebMagic

既然我不会 Python 的爬虫,那就选 Java 的吧(大聪明)。
WebMagic 是一个简单灵活的 Java 爬虫框架,官方中文文档:WebMagic in Action

添加依赖

使用 Maven 来安装 WebMagic,在 Spring Boot 项目中引入。

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

目标网站

作为一个 SP(我不老),爬取的网站一般都是那些写真套图网站,例如:美之图
例行分析这个网站,每次按 F12 打开浏览器的控制台,都显示一个“已在调试程序中暂停”的提示,这个有点碍眼,有两种方法可以解决。

  1. 每次都要重复步骤:先点击“停用断点”(Ctrl + F8),再点击“前进”(F8)。
    重复步骤
  2. 这种设置一劳永逸,但要记得在需要的时候取消。
    一次设置

点击“写真馆”,进入“https://mmzztt.com/photo/”,点击“检查元素”,开始寻找规律。
查找规律

分析规律

查看多张封面图的 src 属性后,就可以得知链接都是如下的形式:

  • https://t.iimzt.com/thumb/51490/960.jpg
  • https://t.iimzt.com/thumb/51322/960.jpg
  • https://t.iimzt.com/thumb/51400/960.jpg
  • https://t.iimzt.com/thumb/51249/960.jpg
  • https://t.iimzt.com/thumb/51156/960.jpg

域名地址就不用说了,封面图的文件名都是 960.jpg,而变的只是 thumb 后面的参数,这个变化的参数就是对应的专辑。
也就是说,在“https://mmzztt.com/photo/”后加上对应的专辑数字,就可以进入对应的专辑套图中去。举个例子,点击“https://mmzztt.com/photo/51490”就会进入专辑的内容页面,而这个数字“51490”可以从封面图中获取。

遗憾的是,并不能直接看出具体图片的命名方式,因为不可能采取从“960.jpg”递减到“001.jpg”这么直白朴素(愚蠢)的命名方案。
点击进入专辑“51490”,继续检查图片的来源,分析规律:

  • 第 1 张:https://p.iimzt.com/2021/12/13b01i.jpg
  • 第 2 张:https://p.iimzt.com/2021/12/13b02e.jpg
  • 第 3 张:https://p.iimzt.com/2021/12/13b03d.jpg
  • 省略......
  • 第 13 张:https://p.iimzt.com/2021/12/13b13e.jpg

从这个能看出来:前面的域名地址不变,中间是变化的有规律的日期格式——“2021/12/13”,后面的是变化的图片命名——可能固定的首字母 + “当前的图片数(第 X 张)” + 随机的末字母。
一开始我以为是首字母固定是 b,直到另一套是首字母是 a,但随机又看了十几套,基本都是首字母 ab,还没看到首字母为 c 的(可能是我没发现,或者就是没有)。
首末的随机字母是不是有某种对应的规律(算法生成),暂时看不出来,有可能是真的随机——外层循环随机使用字母 ab,内层循环从字母 a 到字母 z 随机挑选。

当我连续点击套图的“下一张”,到第 16 张时,网站就会自动跳转到下载 APP 的页面。后来这个现象又没了。

实现 PageProcessor

根据官方文档的说明,PageProcessor 的定制分为三个部分,分别是爬虫的配置、页面元素的抽取和链接的发现。
针对“写真馆”界面的套图进行抽取元素,这部分需要懂一点 XPath 规则以及正则表达式。
为了简单起见,直接抽取套图专辑的文字标题以及首页图的链接。

@Override
    public void process(Page page) {
        // 列表页
        if (page.getUrl().regex(URL_LIST).match()) {
            /**
             * 运行时抛出异常:Links can not apply to plain text.
             * Please check whether you use a previous xpath with attribute select (/@href etc).
             * 原因:使用过正则匹配后就不能使用 XPath 抽取了,因为正则取出来的没有办法保证是 Html,当做是是纯文本处理了
             */
            page.addTargetRequests(page.getHtml().xpath("//a[@class='uk-inline']/@href").regex(URL_ARTICLE).all());
            page.addTargetRequests(page.getHtml().links().regex(URL_LIST).all());
        } else {
            // 标题名
            String imgTitle = page.getHtml().xpath("//h1[@class='uk-article-title uk-text-truncate']/text()").toString();
            // 套图第一张的链接
            String imgUrl = page.getHtml().xpath("//figure[@class='uk-inline']/img/@src").toString();
            MztImage image = new MztImage();
            image.setId(counter.get());
            atomicAdd();
            image.setName(imgTitle);
            image.setAvatar(imgUrl);
            page.putField("result", image);
        }
    }

自定义 Pipeline

Pipeline其实就是将 PageProcessor 抽取的结果,继续进行了处理,主要用于抽取结果的保存。WebMagic 中已经提供了将结果输出到控制台、保存到文件和 JSON 格式保存的几个 Pipeline ,我们也可以自定义 Pileline 实现想要的功能。
自定义实现:将文字标题作为 TXT 文本文件的文件名,首页图链接作为文本中的内容。

import com.example.common.MztImage;
import com.example.tool.HttpFileTool;
import us.codecraft.webmagic.ResultItems;
import us.codecraft.webmagic.Task;
import us.codecraft.webmagic.pipeline.Pipeline;

import java.io.File;
import java.io.FileWriter;
import java.io.Writer;

/**
 * @author yyt
 * @date 2021年12月21日 19:30
 */
public class DownloadImgPipeline implements Pipeline {
    /**
     * 图片保存的路径
     */
    private static final String BASE_IMG_FILE_PATH = "D:" + File.separator + "Desktop" + File.separator + "MZT" + File.separator;

    @Override
    public void process(ResultItems resultItems, Task task) {
        MztImage newImage = resultItems.get("result");
        if(newImage != null){
            // 根据标题创建对应的文件
            String dirStr = BASE_IMG_FILE_PATH + newImage.getName() + ".txt";
            File file = new File(dirStr);
            if (!file.getParentFile().exists()){
                // 包含父文件夹
                file.getParentFile().mkdirs();
            }
            // 根据链接下载图片
            String imageUrl = newImage.getAvatar();
            // 获取图片的文件名
            // String fileName = imageUrl.substring(imageUrl.lastIndexOf("/") + 1);
            try {
                // 下载图片
                // HttpFileTool.download(imageUrl, dirStr + imageUrl);
                Writer writer = new FileWriter(file, true);
                writer.write(imageUrl + "\r\n");
                writer.flush();
                writer.close();
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println("DownloadImgPipeline 运行出现异常...");
            }
        }
    }
}

Debug

因为下载保存图片会出现 403 错误,所以改为保存图片的链接。

java.io.IOException: Server returned HTTP response code: 403 for URL: https://p.iimzt.com/2021/12/20b01c.jpg

查看了一下网页元素,不出意外应该是“同源策略”导致的——<img> 标签中设置了独立的请求策略 referrerpolicy="origin"。而 WebMagic 也有对应的方案:通过设置 Site 对象来进行配置编码、HTTP 头、超时时间、重试策略等、代理等信息,那可以考虑使用 Fiddler 抓包,再使用 addHeader(String, String) 方法和 addCookie(String, String) 方法伪装。
但经过测试,依然不行(我太菜了)。😫

爬取结果

开始漫长的等待。⏳⏳⏳
运行完毕
打开文件夹一看,爬取成功了!!!🎉🎉🎉
保存结果