新四季網

小說網站爬蟲功能設計(jsoup實現小說線上採集閱讀)

2023-06-12 09:46:18

前言

  用過老版本UC看小說的同學都知道,當年版權問題比較鬆懈,我們可以再UC搜索不同來源的小說,並且閱讀,那麼它是怎麼做的呢?下面讓我們自己實現一個小說線上採集閱讀。(說明:僅用於技術學習、研究)

  看小說時,最煩的就是有各種廣告,這些廣告有些是站長放上去的盈利手段,有些是被人惡意注入。在我的上一篇博客中實現了小說採集並保存到本地TXT文件 HttpClients Jsoup抓取筆趣閣小說,並保存到本地TXT文件,這樣我們就可以導入手機用手機閱讀軟體看小說;那麼我們這裡實現一個可以在線看小說。

話不多說先看效果

  首頁:

  頁面很純淨,目前有三種來源

  搜索結果頁:

  三個不同的來源,分頁用的是layui的laypage,邏輯分頁。(筆趣閣的搜索結果界面沒有書本的圖片)

  翻頁效果:

  縱橫網連簡介等都幫我們分詞,搞得數據量太大,速度太慢:books.size :圖片防盜鏈原理及應對方法 ;我們直接用大佬的反防盜鏈方法,並且針對我們的項目改造一下:

/** * 反防盜鏈 */ function showImg(parentObj, url) { //來一個隨機數 var frameid = 'frameimg' Math.random; //放在(父頁面)window裡面 iframe的script標籤裡面綁定了window.onload,作用:設置iframe的高度、寬度 window.onload = function { parent.document.getElementById(\'' frameid '\').height = document.getElementById(\'img\').height \'px\'; } 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

  書實體類:

/** * 書對象 */@Datapublic 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 body{ background-color: antiquewhite; } .main{ margin: auto; width: 500px; margin-top: 150px; } #bookName{ width: 300px; } #title{ text-align: center; } MY BOOK 來源 所有 筆趣閣 縱橫網 起點網 搜索

BOOK LIST body { background-color: antiquewhite; } .main { margin: auto; width: 500px; margin-top: 50px; } .book { border-bottom: solid #428bca 1px; } .click-book-detail, .click-book-read { cursor: pointer; color: #428bca; } .click-book-detail:hover { color: rgba(150, 149, 162, 0.47); } .click-book-read:hover { color: rgba(150, 149, 162, 0.47); } 來源 所有 筆趣閣 縱橫網 起點網 搜索 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 = "" "" "書名:" book.bookName "" "作者:" book.author "" "簡介:" book.synopsis "" "最新章節:" book.latestChapter "" "更新時間:" book.updateDate "" "大小:" book.magnitude "" "狀態:" book.status "" "類型:" book.type "" "來源:" book.source.name "" ""; } return str; }; //獲取一個laypage實例 layui.use('laypage', function { var laypage = layui.laypage; //調用laypage 邏輯分頁 laypage.render({ elem: 'page', count: pages, limit: nums, jump: function (obj) { //obj包含了當前分頁的所有參數,比如: // console.log(obj.curr); //得到當前頁,以便向服務端請求對應頁的數據。 // console.log(obj.limit); //得到每頁顯示的條數 document.getElementById('books').innerHTML = thisDate(obj.curr); }, prev: '', theme: '#f9c357', }) }); $("body").on("click", ".click-book-detail", function (even) { var bookUrl = $(this).data("bookurl"); var searchUrl = $(this).data("searchurl"); var sourceKey = $(this).data("sourcekey"); window.location.href = ctx &searchUrl=" searchUrl "&bookUrl=" bookUrl; }); $("body").on("click", ".click-book-read", function (even) { var chapterUrl = $(this).data("chapterurl"); var refererUrl = $(this).data("refererurl"); var sourceKey = $(this).data("sourcekey"); window.location.href = ctx &refererUrl=" refererUrl "&chapterUrl=" chapterUrl; });

book_details

BOOK DETAILS body { background-color: antiquewhite; } .main { margin: auto; width: 500px; margin-top: 150px; } .book { border-bottom: solid #428bca 1px; } .click-book-detail, .click-book-read { cursor: pointer; color: #428bca; } .click-book-detail:hover { color: rgba(150, 149, 162, 0.47); } .click-book-read:hover { color: rgba(150, 149, 162, 0.47); } a { color: #428bca; } 書名: 作者: 簡介: 最新章節: 更新時間: 大小: 狀態: 類型: 來源: var ctx = /*[[@{/}]]*/''; var book = [[${book}]];//取出後臺數據 /** * 反防盜鏈 */ function showImg(parentObj, url) { //來一個隨機數 var frameid = 'frameimg' Math.random; //放在(父頁面)window裡面 iframe的script標籤裡面綁定了window.onload,作用:設置iframe的高度、寬度 window.onload = function { parent.document.getElementById(\'' frameid '\').height = document.getElementById(\'img\').height \'px\'; } window.img = ''; //iframe調用parent.img $(parentObj).append(''); } showImg($("#bookImg"), book.img); $("body").on("click", ".click-book-read", function (even) { var chapterUrl = $(this).data("chapterurl"); var refererUrl = $(this).data("refererurl"); var sourceKey = $(this).data("sourcekey"); window.location.href = ctx &refererUrl=" refererUrl "&chapterUrl=" chapterUrl; });

  book_read

BOOK READ body { background-color: antiquewhite; } .main { padding: 10px 20px; } .click-book-detail, .click-book-read { cursor: pointer; color: #428bca; } .click-book-detail:hover { color: rgba(150, 149, 162, 0.47); } .click-book-read:hover { color: rgba(150, 149, 162, 0.47); } .float-left{ float: left; margin-left: 70px; } var ctx = /*[[@{/}]]*/''; $("body").on("click", ".click-book-read", function (even) { var chapterUrl = $(this).data("chapterurl"); var refererUrl = $(this).data("refererurl"); var sourceKey = $(this).data("sourcekey"); window.location.href = ctx &refererUrl=" refererUrl "&chapterUrl=" chapterUrl; });

  補充

  2019-07-17補充:我們之前三個來源網站的baseUrl都是用http,但網站後面都升級成了https,例如筆趣閣:

  導致抓取數據時報錯

BOOK LIST body { background-color: antiquewhite; } .main { margin: auto; width: 500px; margin-top: 50px; } .book { border-bottom: solid #428bca 1px; } .click-book-detail, .click-book-read { cursor: pointer; color: #428bca; } .click-book-detail:hover { color: rgba(150, 149, 162, 0.47); } .click-book-read:hover { color: rgba(150, 149, 162, 0.47); } 來源 所有 筆趣閣 縱橫網 起點網 搜索 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 = "" "" "書名:" book.bookName "" "作者:" book.author "" "簡介:" book.synopsis "" "最新章節:" book.latestChapter "" "更新時間:" book.updateDate "" "大小:" book.magnitude "" "狀態:" book.status "" "類型:" book.type "" "來源:" book.source.name "" ""; } return str; }; //獲取一個laypage實例 layui.use('laypage', function { var laypage = layui.laypage; //調用laypage 邏輯分頁 laypage.render({ elem: 'page', count: pages, limit: nums, jump: function (obj) { //obj包含了當前分頁的所有參數,比如: // console.log(obj.curr); //得到當前頁,以便向服務端請求對應頁的數據。 // console.log(obj.limit); //得到每頁顯示的條數 document.getElementById('books').innerHTML = thisDate(obj.curr); }, prev: '', theme: '#f9c357', }) }); $("body").on("click", ".click-book-detail", function (even) { var bookUrl = $(this).data("bookurl"); var searchUrl = $(this).data("searchurl"); var sourceKey = $(this).data("sourcekey"); window.location.href = ctx &searchUrl=" searchUrl "&bookUrl=" bookUrl; }); $("body").on("click", ".click-book-read", function (even) { var chapterUrl = $(this).data("chapterurl"); var refererUrl = $(this).data("refererurl"); var sourceKey = $(this).data("sourcekey"); window.location.href = ctx &refererUrl=" refererUrl "&chapterUrl=" chapterUrl; });

  解決辦法:參考https://blog.csdn.net/xiaoxian8023/article/details/49865335,繞過證書驗證

  在BookUtil.java中新增方法

/** * 繞過SSL驗證 */ private static SSLContext createIgnoreVerifySSL throws NoSuchAlgorithmException, KeyManagementException { SSLContext sc = SSLContext.getInstance("SSLv3"); // 實現一個X509TrustManager接口,用於繞過驗證,不用修改裡面的方法 X509TrustManager trustManager = new X509TrustManager { @Override public void checkClientTrusted( java.security.cert.X509Certificate[] paramArrayOfX509Certificate, String paramString) throws CertificateException { } @Override public void checkServerTrusted( java.security.cert.X509Certificate[] paramArrayOfX509Certificate, String paramString) throws CertificateException { } @Override public java.security.cert.X509Certificate[] getAcceptedIssuers { return null; } }; sc.init(null, new TrustManager[]{trustManager}, null); return sc; }

  然後在gather方法中改成這樣獲取httpClient

/** * 採集當前url完整response實體.toString * * @param url url * @return response實體.toString */ public static String gather(String url, String refererUrl) { String result = null; try { //採用繞過驗證的方式處理https請求 SSLContext sslcontext = createIgnoreVerifySSL; // 設置協議http和https對應的處理socket連結工廠的對象 Registry socketFactoryRegistry = RegistryBuilder.create .register("http", PlainConnectionSocketFactory.INSTANCE) .register("https", new SSLConnectionSocketFactory(sslcontext)) .build; PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry); HttpClients.custom.setConnectionManager(connManager); //創建自定義的httpclient對象 CloseableHttpClient httpClient = HttpClients.custom.setConnectionManager(connManager).build; //創建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; }

  這樣就可以正常抓取了

  我們之前獲取項目路徑用的是

var ctx = /*[[@{/}]]*/'';

  突然發現不行了,跳轉的路徑直接是/開頭,現在改成這樣獲取

//項目路徑 var ctx = [[${#request.getContextPath}]];

  2019-08-01補充:大家如果看到有這個報錯,連接被重置,不要慌張,有可能是網站換域名了比如現在我們程序請求的是http://www.biquge.com.tw,但這個網址已經不能訪問了,筆趣閣已經改成https://www.biqudu.net/,我們改一下代碼就可以解決問題,要注意檢查各個源路徑是否能正常訪問,同時對方也可能改頁面格式,導致我們之前的規則無法匹配獲取數據,這種情況只能重新編寫爬取規則了

  2019-08-02補充:發現了個bug,我們的BookUtil.insertParams方法原理是替換#字符串

/** * 自動注入參數 * 例如: * * @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; }

  但是我們在搜索的時候,調用參數自動注入,形參src的值是來自靜態屬性Map,初始化的時候有兩個#字符串,在進行第一次搜索之後,#字符串被替換了,後面再進行搜索注入參數已經沒有#字符串了,因此後面的搜索結果都是第一次的結果...

  解決:獲取來源時不是用=賦值,而是複製一份,三個方法都要改

  修改前:

//獲取來源詳情 Map src = source.get(sourceKey);

  修改後:

//獲取來源詳情,複製一份 Map src = new HashMap; src.putAll(source.get(sourceKey));

  多端開發

  公司最近打算做手機端,學習了DCloud公司的uni-app,開發工具是HBuilderX,並用我們的小說爬蟲學習、練手,做了個H5手機端的頁面

  DCloud公司官網:https://www.dcloud.io/

  uni-app官網:https://uniapp.dcloud.io/

  uni-app 是一個使用 Vue.js 開發所有前端應用的框架,開發者編寫一套代碼,可編譯到iOS、Android、H5、以及各種小程序等多個平臺。

  效果圖:

  代碼開源

  代碼已經開源、託管到我的GitHub、碼云:

  GitHub:https://github.com/huanzi-qch/spider

  碼云:https://gitee.com/huanzi-qch/spider

版權聲明

作者:huanzi-qch

出處:https://www.cnblogs.com/huanzi-qch

若標題中有「轉載」字樣,則本文版權歸原作者所有。若無轉載字樣,本文版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連結,否則保留追究法律責任的權利.

,
同类文章
 陳冠希被曝當爸爸 老婆維密超模露點照觀看

陳冠希被曝當爸爸 老婆維密超模露點照觀看

6月12日,有網友曬出陳冠希和秦舒培為女兒舉辦百日宴的照片,陳冠希抱著女兒盡顯父愛,身旁站著秦舒培和家人。網友還曝陳冠希女兒名字是Alaia,此前秦舒培曾多次被傳懷孕及在美國生產。去年陳冠希秦舒培曾同逛嬰兒用品店;今年1月有網友偶遇秦舒培,稱其肚子凸起;今年4月有爆料稱秦舒培已誕下女兒。秦舒培是90
 日本十八禁的工口遊戲 電車之狼尾行系列你喜歡哪個?

日本十八禁的工口遊戲 電車之狼尾行系列你喜歡哪個?

工口遊戲,很多人應該都聽說過,工口遊戲其實就是日本遊戲產業裡面一些尺度較大的遊戲,我們也都了解日本的遊戲行業是很發達的,其中自然也少不了一些涉及大尺度的色情遊戲,工口遊戲就是這個一個類別的,下面讓我們來盤點幾個日本十八禁的工口遊戲。日本十八禁的工口遊戲:一、性感沙灘那個被碧撥蕩漾清可見底的海水圍繞的
 韓國十大被禁播的女團MV 尺度太大令人慾罷不能(視頻)

韓國十大被禁播的女團MV 尺度太大令人慾罷不能(視頻)

韓國的女團是一直是以性感而聞名,不僅在韓國有一大批的粉絲,就連中國和歐美國家也有一大票的粉絲,小編自然也是韓國女團粉絲大軍中的一員,看過韓國女團MV的人都知道,一向尺度是非常大的,各種誘惑性的東西看得欲罷不能,然而也正是因為尺度太大而遭到禁播,下面就讓我們一起去看看那些被禁播的MV。一、Stella
 莫菁門事件始末 因愛生恨散布大量豔照

莫菁門事件始末 因愛生恨散布大量豔照

莫菁門事件是發生在2010年廣西柳州的一次「豔照門事件」,那時候時下流行各種門事件,而廣西柳州莫菁門事件之所以能引起網絡上極大的討論,就是因為網友認為發帖者的行為已經超越了道德底線,莫菁門事件中究竟有什麼愛恨情仇呢?莫菁門事件:莫菁,女,廣西柳州人。2010年11月,一名柳州女子的不雅「豔照」在網際
 陳法蓉萬人騎是什麼意思?她演過哪些三級片?

陳法蓉萬人騎是什麼意思?她演過哪些三級片?

陳法蓉是香港著名的女藝人之一,曾經還獲得1989年香港小姐的冠軍,可以說是一位老牌的港姐代表,曾經也有演過三級片,網上曾經有一種對於她的說法是「萬人騎」,說的就是她的感情經歷十分豐富,交往過很多任的男朋友,最後都沒有一個好的結果。陳法蓉介紹:陳法蓉,1967年10月28日出生於香港,祖籍江蘇宿遷,中
 蔣英與李雙江婚外情?關係曖昧是真的嗎?

蔣英與李雙江婚外情?關係曖昧是真的嗎?

蔣英是中國最傑出的女聲樂家,中國航天之父錢學森的夫人,武俠小說大師金庸的表姐,大詩人徐志摩的表妹,看到這麼多人的名字你一定會驚嘆,而在網上曾有流傳蔣英與李雙江的婚外情事件也是引起不小的轟動,蔣英與李雙江是真的嗎,讓我們一起去揭秘事情的真相。蔣英簡介:蔣英生於1919年9月7日,浙江海寧人,中國最傑出
 《狼心狗肺》《你的淺笑》誰曲子更密鑼緊鼓

《狼心狗肺》《你的淺笑》誰曲子更密鑼緊鼓

港島妹妹和梵谷先生:天津嘉年華梁龍說:這不都坐著呢嗎我們說:坐下,牛逼安保說:菠菜賤賣。2019年10月28日 (114)|lululu0726:搖滾是音樂 聽音樂不聽音樂光聽歌詞?前戲不重要麼各位?2020年11月16日 (51)|死在柯本槍下:前面叨咕的是不是:上班了上班了他媽媽沒話說?2021
 柯凡錄音門事件 因侮辱詹姆斯而遭到封殺?

柯凡錄音門事件 因侮辱詹姆斯而遭到封殺?

柯凡,中國籃球解說員,看過NBA的朋友肯定對於他不會太陌生,柯凡搭檔過很多著名的體育解說員,但是因為在2015-2016NBA總決賽期間曝出的錄音門事件中侮辱了詹姆斯被球迷口誅筆伐,柯凡也因此被暫時停止工作反省,柯凡究竟有沒有被封殺呢?柯凡簡介:柯凡,男,北京市人,1986年3月29日出生。籃球解說
 病態三部曲背後虐心的故事 打回原形/防不勝防/十面埋伏

病態三部曲背後虐心的故事 打回原形/防不勝防/十面埋伏

黃偉文是香港樂壇著名的作詞人,他給很多音樂人都寫過歌詞也都是耳熟能詳,比如說《可惜我是水瓶座》《浮誇》《下一站天后》《喜帖街》等等,他的歌能讓人產生非常多的共鳴,在病態三部曲中更是引起無數人對於愛情的遺憾,他的病態三部曲分別是哪三首呢?病態三部曲:《打回原形》《打回原形》講愛之卑微。在愛情裡面,人難
 《愛你這樣傻》與《你從未說過愛我》哪首變幻莫測

《愛你這樣傻》與《你從未說過愛我》哪首變幻莫測

南極不季寞:90後聽這種歌的還有幾個2015年9月20日 (6017)|Ea-bon:真系好聽無得頂啊,睇下幾多人卑贊!!12015年12月2日 (2092)|麥芽先生:唱歌的也傻聽歌的也傻2015年3月27日 (867)|六級詞彙小能手:22歲的阿姨沒有談過戀愛但是喜歡著一個人。2017年10月1