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ù)...
最終效果圖