前言
用过老版本UC看小说的同学都知道,当年版权问题比较松懈,我们可以再UC搜索不同来源的小说,并且阅读,那么它是怎么做的呢?下面让我们自己实现一个小说线上采集阅读。(说明:仅用于技术学习、研究)
看小说时,最烦的就是有各种广告,这些广告有些是站长放上去的盈利手段,有些是被人恶意注入。在我的上一篇博客中实现了小说采集并保存到本地TXT文件 HttpClients+Jsoup抓取笔趣阁小说,并保存到本地TXT文件,这样我们就可以导入手机用手机阅读软件看小说;那么我们这里实现一个可以在线看小说。
话不多说先看效果
首页:
页面很纯净,目前有三种来源

搜索结果页:
三个不同的来源,分页用的是layui的laypage,逻辑分页。(笔趣阁的搜索结果界面没有书本的图片)


翻页效果:


纵横网连简介等都帮我们分词,搞得数据量太大,速度太慢:books.size() < 888

书本详情页:


小说阅读页:


上、下一章:

代码与分析
项目是springboot项目,原理非常简单,就是用httpclient构造一个请求头去请求对应的来源链接,用jsoup去解析响应回来的response,
通过jsoup的选择器去找到我们想要的数据,存入实体,放到ModelAndView里面,前端页面用thymeleaf去取值、遍历数据。
但是有一些书是要会员才能看,这种情况下我们需要做模拟登陆才能继续采集,这里只是一个简单的采集,就不做模拟登陆了。
采集过程中碰到的问题:
1、起点中文网采集书本集合时,想要的数据不在页面源码里面
起点中文网很机智,他在html代码了没有直接展示page分页信息的链接

可以看到,httpClient请求回来的response里分页信息标签里面是空的,但用浏览器去请求里面有信息

这是因为httpClient去模拟我们的浏览器访问某个链接,直接响应回这个链接对应的内容,并不会去帮我们触发其他的ajax,而浏览器回去解析响应回来的html,当碰到img、script、link等标签它会帮我们去ajax请求对应的资源。
由此推测,page相关的信息,起点中文网是在js代码里面去获取并追加,最后通过network找到它的一些蛛丝马迹

既然他没有写在html里,那我们就自己去创建连接,可以看到html上有当前页跟最大页数

完美

2、笔趣阁查看书本详情,图片防盗链
笔趣阁有一个图片防盗,我们在自己的html引入图片路径时,但当我们把链接用浏览器访问时是可以的


对比一下两边的请求头


首先我们要知道什么事图片防盗链,猛戳这里 -->:图片防盗链原理及应对方法 ;我们直接用大佬的反防盗链方法,并且针对我们的项目改造一下:
/**
* 反防盗链
*/
function showImg(parentObj, url) {
//来一个随机数
var frameid = 'frameimg' + Math.random();
//放在(父页面)window里面 iframe的script标签里面绑定了window.onload,作用:设置iframe的高度、宽度 <script>window.onload = function() { parent.document.getElementById(\'' + frameid + '\').height = document.getElementById(\'img\').height+\'px\'; }<' script>
window.img = '
';
//iframe调用parent.img
$(parentObj).append('');
}
showImg($("#bookImg"), book.img);
效果最终:

3、采集书本详情时,起点网的目录并没有在html里
起点网的目录并没有在html里,也不是在另一个链接里

通过浏览器页面Elements的Break on打断点

查看调用栈发现,它在js ajax请求数据,进行tab切换,就连总共有多少章,它都是页面加载出来之后ajax请求回来的

看一下他的请求头跟参数

只要我们弄懂_csrfToken参数就可以构造一个get请求
https://book.qidian.com/ajax/book/category?_csrfToken=LosgUIe29G7LV04gdutbSqzKRb9XxoPyqtWBQ3hU&bookId=1209977
通过浏览器查看可知,第一章对应的链接:
https://read.qidian.com/chapter/2R9G_ziBVg41/MyEcwtk5i8Iex0RJOkJclQ2
这个就是我们想要的
https://read.qidian.com/chapter/ + cU章节链接
cN章节名称

_csrfToken是cookie,而且多次刷新都不变,大胆猜测:起点为我们生成cookie并且携带请求ajax,携带与起点给我们的cookie不一致的时候返回失败,
我们每次调用gather,都是一次新的httpclient对象,每次既然如此,那我们就先获取cookie,在用同一个httpclient去请求数据即可 (详情代码已经贴出来,在
BookHandler_qidian.book_details_qidian里面)
最终我们获得了返回值,是一个json

同样的,大部分逻辑都写在注释里面,相信大家都看得懂:
maven引包:
org.apache.httpcomponents
httpclient
4.5.4
org.apache.httpcomponents
httpcore
4.4.9
org.jsoup
jsoup
1.11.3
net.sf.json-lib
json-lib
2.4
jdk15
书实体类:
/**
* 书对象
*/
@Data
public class Book {
/**
* 链接
*/
private String bookUrl;
/**
* 书名
*/
private String bookName;
/**
* 作者
*/
private String author;
/**
* 简介
*/
private String synopsis;
/**
* 图片
*/
private String img;
/**
* 章节目录 chapterName、url
*/
private List<Map> chapters;
/**
* 状态
*/
private String status;
/**
* 类型
*/
private String type;
/**
* 更新时间
*/
private String updateDate;
/**
* 第一章
*/
private String firstChapter;
/**
* 第一章链接
*/
private String firstChapterUrl;
/**
* 上一章节
*/
private String prevChapter;
/**
* 上一章节链接
*/
private String prevChapterUrl;
/**
* 当前章节名称
*/
private String nowChapter;
/**
* 当前章节内容
*/
private String nowChapterValue;
/**
* 当前章节链接
*/
private String nowChapterUrl;
/**
* 下一章节
*/
private String nextChapter;
/**
* 下一章节链接
*/
private String nextChapterUrl;
/**
* 最新章节
*/
private String latestChapter;
/**
* 最新章节链接
*/
private String latestChapterUrl;
/**
* 大小
*/
private String magnitude;
/**
* 来源
*/
private Map source;
private String sourceKey;
}
小工具类:
/**
* 小工具类
*/
public class BookUtil {
/**
* 自动注入参数
* 例如:
*
* @param src http://search.zongheng.com/s?keyword=#1&pageNo=#2&sort=
* @param params "斗破苍穹","1"
* @return http://search.zongheng.com/s?keyword=斗破苍穹&pageNo=1&sort=
*/
public static String insertParams(String src, String... params) {
int i = 1;
for (String param : params) {
src = src.replaceAll("#" + i, param);
i++;
}
return src;
}
/**
* 采集当前url完整response实体.toString()
*
* @param url url
* @return response实体.toString()
*/
public static String gather(String url, String refererUrl) {
String result = null;
try {
//创建httpclient对象 (这里设置成全局变量,相对于同一个请求session、cookie会跟着携带过去)
CloseableHttpClient httpClient = HttpClients.createDefault();
//创建get方式请求对象
HttpGet httpGet = new HttpGet(url);
httpGet.addHeader("Content-type", "application/json");
//包装一下
httpGet.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36");
httpGet.addHeader("Referer", refererUrl);
httpGet.addHeader("Connection", "keep-alive");
//通过请求对象获取响应对象
CloseableHttpResponse response = httpClient.execute(httpGet);
//获取结果实体
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
result = EntityUtils.toString(response.getEntity(), "GBK");
}
//释放链接
response.close();
}
//这里还可以捕获超时异常,重新连接抓取
catch (Exception e) {
result = null;
System.err.println("采集操作出错");
e.printStackTrace();
}
return result;
}
}
Controller层:
/**
* Book Controller层
*/
@RestController
@RequestMapping("book")
public class BookContrller {
/**
* 来源集合
*/
private static Map<String, Map> source = new HashMap<>();
static {
//笔趣阁
source.put("biquge", BookHandler_biquge.biquge);
//纵横中文网
source.put("zongheng", BookHandler_zongheng.zongheng);
//起点中文网
source.put("qidian", BookHandler_qidian.qidian);
}
/**
* 访问首页
*/
@GetMapping("/index")
public ModelAndView index() {
return new ModelAndView("book_index.html");
}
/**
* 搜索书名
*/
@GetMapping("/search")
public ModelAndView search(Book book) {
//结果集
ArrayList books = new ArrayList<>();
//关键字
String keyWord = book.getBookName();
//来源
String sourceKey = book.getSourceKey();
//获取来源详情
Map src = source.get(sourceKey);
// 编码
try {
keyWord = URLEncoder.encode(keyWord, src.get("UrlEncode"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
//searchUrl
src.put("searchUrl", BookUtil.insertParams(src.get("searchUrl"), keyWord, "1"));
//调用不同的方法
switch (sourceKey) {
case "biquge":
BookHandler_biquge.book_search_biquge(books, src, keyWord);
break;
case "zongheng":
BookHandler_zongheng.book_search_zongheng(books, src, keyWord);
break;
case "qidian":
BookHandler_qidian.book_search_qidian(books, src, keyWord);
break;
default:
//默认所有都查
BookHandler_biquge.book_search_biquge(books, src, keyWord);
BookHandler_zongheng.book_search_zongheng(books, src, keyWord);
BookHandler_qidian.book_search_qidian(books, src, keyWord);
break;
}
System.out.println(books.size());
ModelAndView modelAndView = new ModelAndView("book_list.html", "books", books);
try {
modelAndView.addObject("keyWord", URLDecoder.decode(keyWord, src.get("UrlEncode")));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
modelAndView.addObject("sourceKey", sourceKey);
return modelAndView;
}
/**
* 访问书本详情
*/
@GetMapping("/details")
public ModelAndView details(String sourceKey,String bookUrl,String searchUrl) {
Map src = source.get(sourceKey);
src.put("searchUrl",searchUrl);
Book book = new Book();
//调用不同的方法
switch (sourceKey) {
case "biquge":
book = BookHandler_biquge.book_details_biquge(src, bookUrl);
break;
case "zongheng":
book = BookHandler_zongheng.book_details_zongheng(src, bookUrl);
break;
case "qidian":
book = BookHandler_qidian.book_details_qidian(src, bookUrl);
break;
default:
break;
}
return new ModelAndView("book_details.html", "book", book);
}
/**
* 访问书本章节
*/
@GetMapping("/read")
public ModelAndView read(String sourceKey,String chapterUrl,String refererUrl) {
Map src = source.get(sourceKey);
Book book = new Book();
//调用不同的方法
switch (sourceKey) {
case "biquge":
book = BookHandler_biquge.book_read_biquge(src, chapterUrl,refererUrl);
break;
case "zongheng":
book = BookHandler_zongheng.book_read_zongheng(src, chapterUrl,refererUrl);
break;
case "qidian":
book = BookHandler_qidian.book_read_qidian(src, chapterUrl,refererUrl);
break;
default:
break;
}
return new ModelAndView("book_read.html", "book", book);
}
}
三个不同来源的Handler处理器,每个来源都有不同的采集规则:
BookHandler_biquge
/**
* 笔趣阁采集规则
*/
public class BookHandler_biquge {
/**
* 来源信息
*/
public static HashMap biquge = new HashMap<>();
static {
//笔趣阁
biquge.put("name", "笔趣阁");
biquge.put("key", "biquge");
biquge.put("baseUrl", "http://www.biquge.com.tw");
biquge.put("baseSearchUrl", "http://www.biquge.com.tw/modules/article/soshu.php");
biquge.put("UrlEncode", "GB2312");
biquge.put("searchUrl", "http://www.biquge.com.tw/modules/article/soshu.php?searchkey=+#1&page=#2");
}
/**
* 获取search list 笔趣阁采集规则
*
* @param books 结果集合
* @param src 源目标
* @param keyWord 关键字
*/
public static void book_search_biquge(ArrayList books, Map src, String keyWord) {
//采集术
String html = BookUtil.gather(src.get("searchUrl"), src.get("baseUrl"));
try {
//解析html格式的字符串成一个Document
Document doc = Jsoup.parse(html);
//当前页集合
Elements resultList = doc.select("table.grid tr#nr");
for (Element result : resultList) {
Book book = new Book();
//书本链接
book.setBookUrl(result.child(0).select("a").attr("href"));
//书名
book.setBookName(result.child(0).select("a").text());
//作者
book.setAuthor(result.child(2).text());
//更新时间
book.setUpdateDate(result.child(4).text());
//最新章节
book.setLatestChapter(result.child(1).select("a").text());
book.setLatestChapterUrl(result.child(1).select("a").attr("href"));
//状态
book.setStatus(result.child(5).text());
//大小
book.setMagnitude(result.child(3).text());
//来源
book.setSource(src);
books.add(book);
}
//下一页
Elements searchNext = doc.select("div.pages > a.ngroup");
String href = searchNext.attr("href");
if (!StringUtils.isEmpty(href)) {
src.put("baseUrl", src.get("searchUrl"));
src.put("searchUrl", href.contains("http") ? href : (src.get("baseSearchUrl") + href));
book_search_biquge(books, src, keyWord);
}
} catch (Exception e) {
System.err.println("采集数据操作出错");
e.printStackTrace();
}
}
/**
* 获取书本详情 笔趣阁采集规则
* @param src 源目标
* @param bookUrl 书本链接
* @return Book对象
*/
public static Book book_details_biquge(Map src, String bookUrl) {
Book book = new Book();
//采集术
String html = BookUtil.gather(bookUrl, src.get("searchUrl"));
try {
//解析html格式的字符串成一个Document
Document doc = Jsoup.parse(html);
//书本链接
book.setBookUrl(doc.select("meta[property=og:url]").attr("content"));
//图片
book.setImg(doc.select("meta[property=og:image]").attr("content"));
//书名
book.setBookName(doc.select("div#info > h1").text());
//作者
book.setAuthor(doc.select("meta[property=og:novel:author]").attr("content"));
//更新时间
book.setUpdateDate(doc.select("meta[property=og:novel:update_time]").attr("content"));
//最新章节
book.setLatestChapter(doc.select("meta[property=og:novel:latest_chapter_name]").attr("content"));
book.setLatestChapterUrl(doc.select("meta[property=og:novel:latest_chapter_url]").attr("content"));
//类型
book.setType(doc.select("meta[property=og:novel:category]").attr("content"));
//简介
book.setSynopsis(doc.select("meta[property=og:description]").attr("content"));
//状态
book.setStatus(doc.select("meta[property=og:novel:status]").attr("content"));
//章节目录
ArrayList<Map> chapters = new ArrayList<>();
for (Element result : doc.select("div#list dd")) {
HashMap map = new HashMap<>();
map.put("chapterName", result.select("a").text());
map.put("url", result.select("a").attr("href"));
chapters.add(map);
}
book.setChapters(chapters);
//来源
book.setSource(src);
} catch (Exception e) {
System.err.println("采集数据操作出错");
e.printStackTrace();
}
return book;
}
/**
* 得到当前章节名以及完整内容跟上、下一章的链接地址 笔趣阁采集规则
* @param src 源目标
* @param chapterUrl 当前章节链接
* @param refererUrl 来源链接
* @return Book对象
*/
public static Book book_read_biquge(Map src,String chapterUrl,String refererUrl) {
Book book = new Book();
//当前章节链接
book.setNowChapterUrl(chapterUrl.contains("http") ? chapterUrl : (src.get("baseUrl") + chapterUrl));
//采集术
String html = BookUtil.gather(book.getNowChapterUrl(), refererUrl);
try {
//解析html格式的字符串成一个Document
Document doc = Jsoup.parse(html);
//当前章节名称
book.setNowChapter(doc.select("div.box_con > div.bookname > h1").text());
//删除图片广告
doc.select("div.box_con > div#content img").remove();
//当前章节内容
book.setNowChapterValue(doc.select("div.box_con > div#content").outerHtml());
//上、下一章
book.setPrevChapter(doc.select("div.bottem2 a:matches((?i)下一章)").text());
book.setPrevChapterUrl(doc.select("div.bottem2 a:matches((?i)下一章)").attr("href"));
book.setNextChapter(doc.select("div.bottem2 a:matches((?i)上一章)").text());
book.setNextChapterUrl(doc.select("div.bottem2 a:matches((?i)上一章)").attr("href"));
//来源
book.setSource(src);
} catch (Exception e) {
System.err.println("采集数据操作出错");
e.printStackTrace();
}
return book;
}
}
BookHandler_zongheng
/**
* 纵横中文网采集规则
*/
public class BookHandler_zongheng {
/**
* 来源信息
*/
public static HashMap zongheng = new HashMap<>();
static {
//纵横中文网
zongheng.put("name", "纵横中文网");
zongheng.put("key", "zongheng");
zongheng.put("baseUrl", "http://www.zongheng.com");
zongheng.put("baseSearchUrl", "http://search.zongheng.com/s");
zongheng.put("UrlEncode", "UTF-8");
zongheng.put("searchUrl", "http://search.zongheng.com/s?keyword=#1&pageNo=#2&sort=");
}
/**
* 获取search list 纵横中文网采集规则
*
* @param books 结果集合
* @param src 源目标
* @param keyWord 关键字
*/
public static void book_search_zongheng(ArrayList books, Map src, String keyWord) {
//采集术
String html = BookUtil.gather(src.get("searchUrl"), src.get("baseUrl"));
try {
//解析html格式的字符串成一个Document
Document doc = Jsoup.parse(html);
//当前页集合
Elements resultList = doc.select("div.search-tab > div.search-result-list");
for (Element result : resultList) {
Book book = new Book();
//书本链接
book.setBookUrl(result.select("div.imgbox a").attr("href"));
//图片
book.setImg(result.select("div.imgbox img").attr("src"));
//书名
book.setBookName(result.select("h2.tit").text());
//作者
book.setAuthor(result.select("div.bookinfo > a").first().text());
//类型
book.setType(result.select("div.bookinfo > a").last().text());
//简介
book.setSynopsis(result.select("p").text());
//状态
book.setStatus(result.select("div.bookinfo > span").first().text());
//大小
book.setMagnitude(result.select("div.bookinfo > span").last().text());
//来源
book.setSource(src);
books.add(book);
}
//下一页
Elements searchNext = doc.select("div.search_d_pagesize > a.search_d_next");
String href = searchNext.attr("href");
//最多只要888本,不然太慢了
if (books.size() < 888 && !StringUtils.isEmpty(href)) {
src.put("baseUrl", src.get("searchUrl"));
src.put("searchUrl", href.contains("http") ? href : (src.get("baseSearchUrl") + href));
book_search_zongheng(books, src, keyWord);
}
} catch (Exception e) {
System.err.println("采集数据操作出错");
e.printStackTrace();
}
}
/**
* 获取书本详情 纵横中文网采集规则
* @param src 源目标
* @param bookUrl 书本链接
* @return Book对象
*/
public static Book book_details_zongheng(Map src, String bookUrl) {
Book book = new Book();
//采集术
String html = BookUtil.gather(bookUrl, src.get("searchUrl"));
try {
//解析html格式的字符串成一个Document
Document doc = Jsoup.parse(html);
//书本链接
book.setBookUrl(bookUrl);
//图片
book.setImg(doc.select("div.book-img > img").attr("src"));
//书名
book.setBookName(doc.select("div.book-info > div.book-name").text());
//作者
book.setAuthor(doc.select("div.book-author div.au-name").text());
//更新时间
book.setUpdateDate(doc.select("div.book-new-chapter div.time").text());
//最新章节
book.setLatestChapter(doc.select("div.book-new-chapter div.tit a").text());
book.setLatestChapterUrl(doc.select("div.book-new-chapter div.tit a").attr("href"));
//类型
book.setType(doc.select("div.book-label > a").last().text());
//简介
book.setSynopsis(doc.select("div.book-dec > p").text());
//状态
book.setStatus(doc.select("div.book-label > a").first().text());
//章节目录
String chaptersUrl = doc.select("a.all-catalog").attr("href");
ArrayList<Map> chapters = new ArrayList<>();
//采集术
for (Element result : Jsoup.parse(BookUtil.gather(chaptersUrl, bookUrl)).select("ul.chapter-list li")) {
HashMap map = new HashMap<>();
map.put("chapterName", result.select("a").text());
map.put("url", result.select("a").attr("href"));
chapters.add(map);
}
book.setChapters(chapters);
//来源
book.setSource(src);
} catch (Exception e) {
System.err.println("采集数据操作出错");
e.printStackTrace();
}
return book;
}
/**
* 得到当前章节名以及完整内容跟上、下一章的链接地址 纵横中文网采集规则
* @param src 源目标
* @param chapterUrl 当前章节链接
* @param refererUrl 来源链接
* @return Book对象
*/
public static Book book_read_zongheng(Map src,String chapterUrl,String refererUrl) {
Book book = new Book();
//当前章节链接
book.setNowChapterUrl(chapterUrl.contains("http") ? chapterUrl : (src.get("baseUrl") + chapterUrl));
//采集术
String html = BookUtil.gather(book.getNowChapterUrl(), refererUrl);
try {
//解析html格式的字符串成一个Document
Document doc = Jsoup.parse(html);
//当前章节名称
book.setNowChapter(doc.select("div.title_txtbox").text());
//删除图片广告
doc.select("div.content img").remove();
//当前章节内容
book.setNowChapterValue(doc.select("div.content").outerHtml());
//上、下一章
book.setPrevChapter(doc.select("div.chap_btnbox a:matches((?i)下一章)").text());
book.setPrevChapterUrl(doc.select("div.chap_btnbox a:matches((?i)下一章)").attr("href"));
book.setNextChapter(doc.select("div.chap_btnbox a:matches((?i)上一章)").text());
book.setNextChapterUrl(doc.select("div.chap_btnbox a:matches((?i)上一章)").attr("href"));
//来源
book.setSource(src);
} catch (Exception e) {
System.err.println("采集数据操作出错");
e.printStackTrace();
}
return book;
}
}
BookHandler_qidian
/**
* 起点中文网采集规则
*/
public class BookHandler_qidian {
/**
* 来源信息
*/
public static HashMap qidian = new HashMap<>();
static {
//起点中文网
qidian.put("name", "起点中文网");
qidian.put("key", "qidian");
qidian.put("baseUrl", "http://www.qidian.com");
qidian.put("baseSearchUrl", "https://www.qidian.com/search");
qidian.put("UrlEncode", "UTF-8");
qidian.put("searchUrl", "https://www.qidian.com/search?kw=#1&page=#2");
}
/**
* 获取search list 起点中文网采集规则
*
* @param books 结果集合
* @param src 源目标
* @param keyWord 关键字
*/
public static void book_search_qidian(ArrayList books, Map src, String keyWord) {
//采集术
String html = BookUtil.gather(src.get("searchUrl"), src.get("baseUrl"));
try {
//解析html格式的字符串成一个Document
Document doc = Jsoup.parse(html);
//当前页集合
Elements resultList = doc.select("li.res-book-item");
for (Element result : resultList) {
Book book = new Book();
/*
如果大家打断点在这里的话就会发现,起点的链接是这样的
//book.qidian.com/info/1012786368
以两个斜杠开头,不过无所谓,httpClient照样可以请求
*/
//书本链接
book.setBookUrl(result.select("div.book-img-box a").attr("href"));
//图片
book.setImg(result.select("div.book-img-box img").attr("src"));
//书名
book.setBookName(result.select("div.book-mid-info > h4").text());
//作者
book.setAuthor(result.select("div.book-mid-info > p.author > a").first().text());
//类型
book.setType(result.select("div.book-mid-info > p.author > a").last().text());
//简介
book.setSynopsis(result.select("div.book-mid-info > p.intro").text());
//状态
book.setStatus(result.select("div.book-mid-info > p.author > span").first().text());
//更新时间
book.setUpdateDate(result.select("div.book-mid-info > p.update > span").text());
//最新章节
book.setLatestChapter(result.select("div.book-mid-info > p.update > a").text());
book.setLatestChapterUrl(result.select("div.book-mid-info > p.update > a").attr("href"));
//来源
book.setSource(src);
books.add(book);
}
//当前页
String page = doc.select("div#page-container").attr("data-page");
//最大页数
String pageMax = doc.select("div#page-container").attr("data-pageMax");
//当前页 < 最大页数
if (Integer.valueOf(page) < Integer.valueOf(pageMax)) {
src.put("baseUrl", src.get("searchUrl"));
//自己拼接下一页链接
src.put("searchUrl", src.get("searchUrl").replaceAll("page=" + Integer.valueOf(page), "page=" + (Integer.valueOf(page) + 1)));
book_search_qidian(books, src, keyWord);
}
} catch (Exception e) {
System.err.println("采集数据操作出错");
e.printStackTrace();
}
}
/**
* 获取书本详情 起点中文网采集规则
* @param src 源目标
* @param bookUrl 书本链接
* @return Book对象
*/
public static Book book_details_qidian(Map src, String bookUrl) {
Book book = new Book();
//https
bookUrl = "https:" + bookUrl;
//采集术
String html = BookUtil.gather(bookUrl, src.get("searchUrl"));
try {
//解析html格式的字符串成一个Document
Document doc = Jsoup.parse(html);
//书本链接
book.setBookUrl(bookUrl);
//图片
String img = doc.select("div.book-img > a#bookImg > img").attr("src");
img = "https:" + img;
book.setImg(img);
//书名
book.setBookName(doc.select("div.book-info > h1 > em").text());
//作者
book.setAuthor(doc.select("div.book-info > h1 a.writer").text());
//更新时间
book.setUpdateDate(doc.select("li.update em.time").text());
//最新章节
book.setLatestChapter(doc.select("li.update a").text());
book.setLatestChapterUrl(doc.select("li.update a").attr("href"));
//类型
book.setType(doc.select("p.tag > span").first().text());
//简介
book.setSynopsis(doc.select("div.book-intro > p").text());
//状态
book.setStatus(doc.select("p.tag > a").first().text());
//章节目录
//创建httpclient对象 (这里设置成全局变量,相对于同一个请求session、cookie会跟着携带过去)
BasicCookieStore cookieStore = new BasicCookieStore();
CloseableHttpClient httpClient = HttpClients.custom().setDefaultCookieStore(cookieStore).build();
//创建get方式请求对象
HttpGet httpGet = new HttpGet("https://book.qidian.com/");
httpGet.addHeader("Content-type", "application/json");
//包装一下
httpGet.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36");
httpGet.addHeader("Connection", "keep-alive");
//通过请求对象获取响应对象
CloseableHttpResponse response = httpClient.execute(httpGet);
//获得Cookies
String _csrfToken = "";
List cookies = cookieStore.getCookies();
for (int i = 0; i < cookies.size(); i++) {
if("_csrfToken".equals(cookies.get(i).getName())){
_csrfToken = cookies.get(i).getValue();
}
}
//构造post
String bookId = doc.select("div.book-img a#bookImg").attr("data-bid");
HttpPost httpPost = new HttpPost(BookUtil.insertParams("https://book.qidian.com/ajax/book/category?_csrfToken=#1&bookId=#2",_csrfToken,bookId));
httpPost.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36");
httpPost.addHeader("Connection", "keep-alive");
//通过请求对象获取响应对象
CloseableHttpResponse response1 = httpClient.execute(httpPost);
//获取结果实体(json格式字符串)
String chaptersJson = "";
if (response1.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
chaptersJson = EntityUtils.toString(response1.getEntity(), "UTF-8");
}
//java处理json
ArrayList<Map> chapters = new ArrayList<>();
JSONObject jsonArray = JSONObject.fromObject(chaptersJson);
Map objectMap = (Map) jsonArray;
Map objectMap_data = (Map) objectMap.get("data");
List<Map> objectMap_data_vs = (List<Map>) objectMap_data.get("vs");
for(Map vs : objectMap_data_vs){
List<Map> cs = (List<Map>) vs.get("cs");
for(Map chapter : cs){
Map map = new HashMap<>();
map.put("chapterName", (String) chapter.get("cN"));
map.put("url", "https://read.qidian.com/chapter/"+(String) chapter.get("cU"));
chapters.add(map);
}
}
book.setChapters(chapters);
//来源
book.setSource(src);
//释放链接
response.close();
} catch (Exception e) {
System.err.println("采集数据操作出错");
e.printStackTrace();
}
return book;
}
/**
* 得到当前章节名以及完整内容跟上、下一章的链接地址 起点中文网采集规则
* @param src 源目标
* @param chapterUrl 当前章节链接
* @param refererUrl 来源链接
* @return Book对象
*/
public static Book book_read_qidian(Map src,String chapterUrl,String refererUrl) {
Book book = new Book();
//当前章节链接
book.setNowChapterUrl(chapterUrl.contains("http") ? chapterUrl : (src.get("baseUrl") + chapterUrl));
//采集术
String html = BookUtil.gather(book.getNowChapterUrl(), refererUrl);
try {
//解析html格式的字符串成一个Document
Document doc = Jsoup.parse(html);
System.out.println(html);
//当前章节名称
book.setNowChapter(doc.select("h3.j_chapterName").text());
//删除图片广告
doc.select("div.read-content img").remove();
//当前章节内容
book.setNowChapterValue(doc.select("div.read-content").outerHtml());
//上、下一章
book.setPrevChapter(doc.select("div.chapter-control a:matches((?i)下一章)").text());
String prev = doc.select("div.chapter-control a:matches((?i)下一章)").attr("href");
prev = "https:"+prev;
book.setPrevChapterUrl(prev);
book.setNextChapter(doc.select("div.chapter-control a:matches((?i)上一章)").text());
String next = doc.select("div.chapter-control a:matches((?i)上一章)").attr("href");
next = "https:"+next;
book.setNextChapterUrl(next);
//来源
book.setSource(src);
} catch (Exception e) {
System.err.println("采集数据操作出错");
e.printStackTrace();
}
return book;
}
}
四个html页面:
book_index
MY BOOK
MY BOOK
BOOK LIST
<script src="http://libs.baidu.com/jquery/2.1.4/jquery.min.js"></script>
<script src="http://hanlei.online/Onlineaddress/layui/layui.js"></script>
<script th:inline="javascript">
var ctx = /*[[@{/}]]*/'';
var books = [[${books}]];//取出后台数据
var nums = 10; //每页出现的数量
var pages = books.length; //总数
/**
* 传入当前页,根据nums去计算,从books集合截取对应数据做展示
*/
var thisDate = function (curr) {
var str = "",//当前页需要展示的html
first = (curr * nums - nums),//展示的第一条数据的下标
last = curr * nums - 1;//展示的最后一条数据的下标
last = last >= books.length ? (books.length - 1) : last;
for (var i = first; i <= last; i++) {
var book = books[i];
str += "<div class='book'>" +
"
" +
"书名:" + book.bookName + "
" +
"作者:" + book.author + "
" +
"简介:" + book.synopsis + "
" +
"最新章节:" + book.latestChapter + "
" +
"更新时间:" + book.updateDate + "
" +
"大小:" + book.magnitude + "
" +
"状态:" + book.status + "
" +
"类型:" + book.type + "
" +
"来源:" + book.source.name + "
" +
"