利用爬蟲建立自己的圖片軟件(一)

vue文檔已經(jīng)看了一遍又一遍,一直想拿個項目練練手。想了又想不如就寫個圖片站吧,復(fù)雜的也搞不來~~嘿嘿。圖片站需要圖片支撐,自己上傳怕是不可能,直接爬吧。百度了幾個圖片網(wǎng)站然后開干。

預(yù)期功能

  • 圖組瀏覽
  • 圖組收藏,點贊
  • 用戶個人中心
  • 圖組自動更新
  • 圖組管理

技術(shù)棧

后端:

  • blade:輕量級微服務(wù)web框架
  • jsoup:Java Html文檔解析框架
  • anima:輕量級ActionRecord模式的數(shù)據(jù)庫框架

前端:

  • vue
  • vuex:vue狀態(tài)管理插件
  • vue-router:vue路由組件
  • axios:js網(wǎng)絡(luò)請求庫
  • muse-ui:vue前端框架

數(shù)據(jù)庫:

  • sqlite

項目開發(fā)(后端部分)

一、數(shù)據(jù)庫設(shè)計

根據(jù)圖片站結(jié)構(gòu),定義一下三個數(shù)據(jù)表:

  • 圖片類型表:
@EqualsAndHashCode(callSuper = false)
@Data
@ToString
@Table(name = "img_type")
public class ImgType extends Model {
    private Integer id;
    @JsonIgnore
    private String host;   // 分類所屬網(wǎng)站 *
    @JsonIgnore
    private String uri;    // 分類標(biāo)題 英文 *
    private String name;   // 分類名 *

    @JsonIgnore
    private String  pageUriReg; // 分頁打開規(guī)則如: "list_1_$.html" $: 頁碼 *
    private Integer totalPages; // 網(wǎng)站上的 該分類下總頁數(shù) *
    private Integer pageSize;   // 網(wǎng)站上的 分頁尺寸
    private Integer pageNum;    // 已經(jīng)加載的頁碼 默認(rèn)0 *
    private String  time;

    /**
     * 獲取本頁url
     * @param page 頁碼
     */
    public String getUrl(int page) {
        if (page > totalPages) return null;
        if (page > 1) {
            return host + uri + pageUriReg.replace("$", page + "");
        }
        return host + uri;
    }
}
  • 圖組表
@EqualsAndHashCode(callSuper = false)
@Data
@Table(name = "img_group", pk = "uuid")
@ToString
public class ImgGroup extends Model {
    private String  uuid;  // not null
    @JsonIgnore
    private String  host;   // 來源網(wǎng)站
    @JsonIgnore
    private String  uri;    // 網(wǎng)絡(luò)圖片組路徑
    private Integer typeId; // 圖片組類型id
    private String  type;   // 圖片組類型名
    private String  title;  // 圖片組標(biāo)題

    @JsonIgnore
    private String  coverNetPath;  // 圖片組封面網(wǎng)絡(luò)地址 not null
    private String  coverUuid;// 封面圖片的uuid
    private String  time;     // 圖片組添加時間
    private Integer views;    // 圖片組查看次數(shù)
    private Integer total;    // 該圖片組圖片數(shù)

    public String getUrl() {
        return host + uri;
    }
}
  • 圖片表
@EqualsAndHashCode(callSuper = false)
@Data
@Table(name = "img_info", pk = "uuid")
public class ImgInfo extends Model {
    private String uuid;
    private String groupId;  // 圖片組id,圖片組的uuid

    @Ignore
    private ImgGroup group;
    private String   name;   // 圖片名
    @JsonIgnore
    private String netPath;  // 圖片網(wǎng)絡(luò)地址

    @JsonIgnore
    private String  path;    // 圖片本地地址
    private String  time;
    private Integer views;
}

二、確定爬取目標(biāo),編寫爬蟲接口

之前就爬過妹子圖的圖片,這次并不打算只爬一個網(wǎng)站,這樣圖片量太少。因此就要有一個統(tǒng)一的爬蟲接口,以便于將不同網(wǎng)站整合在一起。

/**
 * @AUTHOR soft
 * @DATE 2018/9/20 23:01
 * @DESCRIBE 圖片爬取器基礎(chǔ)接口
 */
public interface ImgCrawlerBase {

    /**
     * 獲取目標(biāo)網(wǎng)站地址
     */
    String getHost();
    
    /**
     * 獲取網(wǎng)站下所有圖片分類
     */
    List<ImgType> getTypes();

    /**
     * 獲取圖片組
     * 頁碼要是對應(yīng)網(wǎng)站的分頁形式的頁碼
     * @param type 圖片組類型
     * @param page 頁碼
     */
    List<ImgGroup> getGroups(@NonNull ImgType type, int page);

    /**
     * 分頁獲取圖片,降低響應(yīng)時間
     * 根據(jù)圖片組獲取指定頁碼的圖片
     * @param group 圖片組
     */
    List<ImgInfo> getImages(@NonNull ImgGroup group, int page);

    /**
     * 圖片搜索
     * @param keyword 搜索關(guān)鍵字
     */
    ImgSearch search(String keyword, int page);

    /**
     * 下載圖片
     * @param webPath 圖片網(wǎng)絡(luò)地址
     */
    static String downImage(String webPath) {
        Logger log = LoggerFactory.getLogger(ImgCrawlerBase.class);
        Optional<Connection.Response> execute = RequestKit.of().execute(webPath);
        if (execute.isPresent()) {
            Connection.Response response = execute.get();
            String filename = FileUtils.getFilenameByNetPath(webPath);
            byte[] bytes    = response.bodyAsBytes();
            try {
                return Files.write(new File(Const.TEMP, filename).toPath(), bytes)
                        .toAbsolutePath().toString();
            } catch (IOException e) {
                log.error("保存文件失敗! {}", e.getMessage());
            }
        }
        return null;
    }
}

定義一個爬蟲程序獲取工具

/**
 * @AUTHOR soft
 * @DATE 2018/9/21 1:08
 * @DESCRIBE 根據(jù)網(wǎng)址獲取對應(yīng)的爬取工具
 */
@Slf4j
public class CrawlerFactory {
    private static List<ImgCrawlerBase> crawlers = new ArrayList<>();

    static {
        crawlers = getImgCrawlerClasses().stream().map(cls -> {
            try {
                return (ImgCrawlerBase) cls.newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                log.error("獲取實例失敗! {}, 返回默認(rèn)實例: Mzitu", e.getMessage());
                return new Mzitu();
            }
        }).collect(Collectors.toList());
    }

    public static List<ImgCrawlerBase> getImgCrawlers() {
        return crawlers;
    }

    public static ImgCrawlerBase getImgCrawlerRandom() {
        int i = new Random().nextInt(3);
        return crawlers.get(i);
    }

    public static ImgCrawlerBase getImgCrawlerByRemove(List<ImgCrawlerBase> crawlers) {
        int i = new Random().nextInt(crawlers.size());
        return crawlers.remove(i);
    }

    /**
     * 獲取對應(yīng)網(wǎng)站的爬取工具
     * @param host 網(wǎng)址
     */
    public static ImgCrawlerBase getImgCrawler(String host) {
        Optional<ImgCrawlerBase> first = getImgCrawlers().stream().filter(crawler -> {
            return host.equals(crawler.getHost());
        }).findFirst();
        return first.orElse(null);
    }

    private static List<Class> getImgCrawlerClasses() { 
        return Arrays.asList(Mzitu.class, I99mm.class, Mm131.class);
    }
}

三、為app提供接口

提供json格式數(shù)據(jù),便于前端展示

/**
 * @AUTHOR soft
 * @DATE 2018/9/20 21:13
 * @DESCRIBE 接口
 */
@Path(value = "/api", restful = true)
public class ApiController {
    @Inject
    private ImgTypeService typeService;
    @Inject
    private ImgGroupService groupService;
    @Inject
    private ImgInfoService infoService;

    @Route("/titles")
    public RestResponse title(Request request) {
        List<ImgType> list = typeService.findList();
        if (list != null && !list.isEmpty()) {
            return RestResponse.ok(list);
        }
        return RestResponse.fail();
    }

    @Route("/group")
    public RestResponse group(@Param(defaultValue = "1") Integer page,
                              @Param(defaultValue = "10") Integer size,
                              @Param(defaultValue = "性感美女") String type) {
        Page<ImgGroup> page1 = groupService.page(page, size, type);
        if (page1 != null) {
            return RestResponse.ok(page1);
        }
        return RestResponse.fail();
    }

    @Route("/images")
    public RestResponse images(@Param(defaultValue = "1") Integer page,
                               @Param(defaultValue = "10") Integer size,
                               @Param String gid) {
        Page<ImgInfo> page1 = infoService.page(page, size, gid);
        if (page1 != null) {
            return RestResponse.ok(page1);
        }
        return RestResponse.fail();
    }

    @Route("/search")
    public RestResponse search(@Param String key,
                               @Param(defaultValue = "1") Integer page,
                               @Param(defaultValue = "10") Integer size) {
        Page<ImgGroup> search = groupService.search(key, page, size);
        if (search != null) {
            return RestResponse.ok(search);
        }
        return RestResponse.fail();
    }
}

后端基本框架就是這樣。未完待續(xù)...
最終效果圖

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容