Java爬虫实践
Java_spider_實戰
源碼及資料點這里!!!
爬蟲的執行流程: 1) 確定首頁url 2) 發送請求, 獲取數據 3) 解析數據 4) 保存數據
爬蟲的三大核心模塊:
1發送請求獲取數據 : httpClient
* 1)獲取httpClient對象:
* 2) 創建請求方式的對象
* 3) 設置請求參數, 請求頭
* 4) 發送請求, 獲取響應對象
* 5) 獲取數據:
* 6) 釋放資源
2解析數據 : Jsoup
* 常見方法 :
* static parse(String html) ; 根據html字符串轉換成document對象
* select(“選擇器”) ; 根據選擇器獲取對應的元素
* text()/html() ; 獲取指定元素的內容體中數據
* attr(String name) ; 根據屬性的名稱獲取屬性的值
3保存數據 :
0. 梳理整個爬蟲的流程
0.1 163娛樂爬蟲的流程
0.2 騰訊娛樂爬蟲的流程
1.1 準備工作 :
- 1)創建項目 : gossip-spider-news (maven jar工程)
[外鏈圖片轉存失敗(img-zvL8ETKt-1563093902027)(assets/1557621834394.png)]
- 2)添加pom依賴:
- 3)加入工具類:
[外鏈圖片轉存失敗(img-wkct0RYO-1563093902031)(assets/1557623438827.png)]
1.2 確定首頁url
[外鏈圖片轉存失敗(img-5PjuFk4c-1563093902031)(assets/1557624105019.png)]
結論: 新聞數據, 不是通過同步請求, 獲取到, 而是通過異步請求, 悄悄獲取的
如何獲取異步請求的url :
首頁的url : https://ent.163.com/special/000380VU/newsdata_index.js?callback=data_callback 分頁的url : https://ent.163.com/special/000380VU/newsdata_index_02.js?callback=data_callback1.3 發送請求, 獲取數據
public class News163Spider {public static void main(String[] args ) throws Exception{//1. 確定首頁url:String indexUrl = "https://ent.163.com/special/000380VU/newsdata_index.js?callback=data_callback";//2. 發送請求, 獲取數據// 此處獲取的json的數據, 并不是一個非標準的jsonString jsonStr = HttpClientUtils.doGet(indexUrl);jsonStr = splitJson(jsonStr);System.out.println(jsonStr);//3. 解析數據(json) :/*** 1) json格式一共有幾種: 二種 一般復合格式認為是前二種的擴展格式* 一種: [value1,value2,value3 ....] ---數組格式* 二種: {key1:value1,key2:value2}* 三種: {key1:[value1,value2]}* 四種: [{},{}]** 2) 如何區分一個json的格式是一個對象呢, 還是一個數組呢?* 查看最外層的符號即可, 如果是[] 那就是數組, 如果{}那就是對象* [{key,[{key,value}]}] : 轉回對應的類型* List<Map<String,List<Map<String,String>>>>** 3) json格式本質上就是一個字符串: 在js 和 java中表示的類型有所不同的:* js java* [] 數組 數組/List/set* {} 對象 javaBean對象/Map** js中如何定義一個對象: var persion = {username:'張三'}; persion.username*/}// 將非標準的json轉換為標準的json字符串private static String splitJson(String jsonStr) {int firstIndex = jsonStr.indexOf("(");int lastIndex = jsonStr.lastIndexOf(")");return jsonStr.substring(firstIndex+1,lastIndex);} }1.4 解析數據(json)
- 解析新聞的列表頁:
- 創建news類(pojo):
- 解析新聞的詳情頁的內容:
1.5 保存數據
- 準備工作: 創建對應的庫 和 對應的表
- 1)創建一個NewsDao類:
-
2)在news163Spider類中, 執行保存即可
- 在成員位置創建dao對象
[外鏈圖片轉存失敗(img-i3Eqox0a-1563093902031)(assets/1557633998099.png)]
- 在parseJson方法中添加一下內容
[外鏈圖片轉存失敗(img-Y7Cn3KOr-1563093902031)(assets/1557634046293.png)]
如果將創建dao的代碼放置到了方法中, 后續可能會出現這個異常信息:
[外鏈圖片轉存失敗(img-iH44cMAk-1563093902034)(assets/1557633247460.png)]
錯誤異常原因: 太多的連接,超過mysql的最大連接數了由于創建了太多的連接池了, 每一次創建一個連接池就會初始化一些連接創建了多次Dao對象, 每創建一次dao, 執行一下dao中構造方法, 構造方法中創建連接池對象1.6 分頁獲取數據
// 執行分頁的方法public static void page(String indexUrl) throws Exception{String page = "02";while(true) {//1. 發送請求獲取數據// 此處獲取的json的數據, 并不是一個非標準的jsonString jsonStr = HttpClientUtils.doGet(indexUrl);if(jsonStr==null){System.out.println("數據獲取完成");break;}jsonStr = splitJson(jsonStr);//2. 解析數據, 3 保存數據parseJson(jsonStr);//4. 獲取下一頁的urlindexUrl = "https://ent.163.com/special/000380VU/newsdata_index_" + page + ".js?callback=data_callback";//5. page ++int pageNum = Integer.parseInt(page);pageNum++;if(pageNum <10){page = "0"+pageNum;}else{page = pageNum+"";}}}1.7 去重操作
[外鏈圖片轉存失敗(img-VBK124xP-1563093902034)(assets/1557634429779.png)]
[外鏈圖片轉存失敗(img-rS2aGC8J-1563093902035)(assets/1557635122858.png)]
清空數據的時候, 一定要注意: 除了清空mysql中數據外, 還的清空redis中的數據
[外鏈圖片轉存失敗(img-4JZLuLjH-1563093902035)(assets/1557635191819.png)]
1.8 獲取整個網易新聞中所有的娛樂信息
明星欄目首頁url: https://ent.163.com/special/000380VU/newsdata_star.js?callback=data_callback 明星欄目分頁url: https://ent.163.com/special/000380VU/newsdata_star_02.js?callback=data_callback電影欄目首頁url: https://ent.163.com/special/000380VU/newsdata_movie.js?callback=data_callback 電影欄目分頁url : https://ent.163.com/special/000380VU/newsdata_movie_02.js?callback=data_callback電視劇欄目首頁url: https://ent.163.com/special/000380VU/newsdata_tv.js?callback=data_callback 電視劇欄目分頁url: https://ent.163.com/special/000380VU/newsdata_tv_02.js?callback=data_callback綜藝欄目首頁url: https://ent.163.com/special/000380VU/newsdata_show.js?callback=data_callback 綜藝欄目分頁url: https://ent.163.com/special/000380VU/newsdata_show_02.js?callback=data_callback音樂欄目首頁url: https://ent.163.com/special/000380VU/newsdata_music.js?callback=data_callback 音樂欄目分頁url: https://ent.163.com/special/000380VU/newsdata_music_02.js?callback=data_callback- 1)在main方法中, 將所有的首頁的url放置到集合中
[外鏈圖片轉存失敗(img-wZqPj6he-1563093902036)(assets/1557644149947.png)]
- 2)在分頁的方法中, 執行分頁的時候, 也需要根據不同的地址進行分頁
[外鏈圖片轉存失敗(img-yuHIfNSS-1563093902036)(assets/1557644179735.png)]
1.9 將整體的爬蟲:
package com.itheima.spider.news163Spider;import com.google.gson.Gson; import com.itheima.spider.dao.NewsDao; import com.itheima.spider.pojo.News; import com.itheima.spider.utils.HttpClientUtils; import com.itheima.spider.utils.IdWorker; import com.itheima.spider.utils.JedisUtils; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.select.Elements; import redis.clients.jedis.Jedis;import java.util.ArrayList; import java.util.List; import java.util.Map;public class News163Spider {private static IdWorker idWorker = new IdWorker(0,1);private static NewsDao newsDao = new NewsDao();public static void main(String[] args) throws Exception {//2. 發送請求, 獲取數據// 此處獲取的json的數據, 并不是一個非標準的json//String jsonStr = HttpClientUtils.doGet(indexUrl);//jsonStr = splitJson(jsonStr);// System.out.println(jsonStr);//3. 解析數據(json) :/*** 1) json格式一共有幾種: 二種 一般見復合格式認為是前二種的擴展格式* 一種: [value1,value2,value3 ....] ---數組格式* 二種: {key1:value1,key2:value2}* 三種: {key1:[value1,value2]}* 四種: [{},{}]** 2) 如何區分一個json的格式是一個對象呢, 還是一個數組呢?* 查看最外層的符號即可, 如果是[] 那就是數組, 如果{}那就是對象* [{key,[{key,value}]}] : 轉回對應的類型* List<Map<String,List<Map<String,String>>>>** 3) json格式本質上就是一個字符串: 在js 和 java中表示的類型有所不同的:* js java* [] 數組 數組/List/set* {} 對象 javaBean對象/Map** js中如何定義一個對象: var persion = {username:'張三'}; persion.username*///parseJson(jsonStr);//1. 確定首頁url://String indexUrl = "https://ent.163.com/special/000380VU/newsdata_index.js?callback=data_callback";List<String> urlList = new ArrayList<String>();urlList.add("https://ent.163.com/special/000380VU/newsdata_index.js?callback=data_callback");urlList.add("https://ent.163.com/special/000380VU/newsdata_star.js?callback=data_callback");urlList.add("https://ent.163.com/special/000380VU/newsdata_movie.js?callback=data_callback");urlList.add("https://ent.163.com/special/000380VU/newsdata_tv.js?callback=data_callback");urlList.add("https://ent.163.com/special/000380VU/newsdata_show.js?callback=data_callback");urlList.add("https://ent.163.com/special/000380VU/newsdata_music.js?callback=data_callback");//5. 分頁獲取數據while(!urlList.isEmpty()) {String indexUrl = urlList.remove(0);System.out.println("獲取了下一個欄目的數據#######################################");page(indexUrl);}}// 執行分頁的方法public static void page(String indexUrl) throws Exception{String page = "02";while(true) {//1. 發送請求獲取數據// 此處獲取的json的數據, 并不是一個非標準的jsonString jsonStr = HttpClientUtils.doGet(indexUrl);if(jsonStr==null){System.out.println("數據獲取完成");break;}jsonStr = splitJson(jsonStr);//2. 解析數據, 3 保存數據parseJson(jsonStr);//4. 獲取下一頁的urlif(indexUrl.contains("newsdata_index")){indexUrl = "https://ent.163.com/special/000380VU/newsdata_index_" + page + ".js?callback=data_callback";}if(indexUrl.contains("newsdata_star")){indexUrl = "https://ent.163.com/special/000380VU/newsdata_star_" + page + ".js?callback=data_callback";}if(indexUrl.contains("newsdata_movie")){indexUrl = "https://ent.163.com/special/000380VU/newsdata_movie_" + page + ".js?callback=data_callback";}if(indexUrl.contains("newsdata_tv")){indexUrl = "https://ent.163.com/special/000380VU/newsdata_tv_" + page + ".js?callback=data_callback";}if(indexUrl.contains("newsdata_show")){indexUrl = "https://ent.163.com/special/000380VU/newsdata_show_" + page + ".js?callback=data_callback";}if(indexUrl.contains("newsdata_music")){indexUrl = "https://ent.163.com/special/000380VU/newsdata_music_" + page + ".js?callback=data_callback";}//5. page ++int pageNum = Integer.parseInt(page);pageNum++;if(pageNum <10){page = "0"+pageNum;}else{page = pageNum+"";}}}// 解析json的方法private static void parseJson(String jsonStr) throws Exception{//3.1 將json字符串轉換成 指定的對象Gson gson = new Gson();List<Map<String, Object>> newsList = gson.fromJson(jsonStr, List.class);// 3.2 遍歷整個新聞的結合, 獲取每一個新聞的對象for (Map<String, Object> newsObj : newsList) {// 新聞 : 標題, 時間,來源 , 內容 , 新聞編輯 , 新聞的url//3.2.1 獲取新聞的url , 需要根據url, 獲取詳情頁中新聞數據String docUrl = (String) newsObj.get("docurl");// 過濾掉一些不是新聞數據的urlif(docUrl.contains("photoview")){continue;}if(docUrl.contains("v.163.com")){continue;}if(docUrl.contains("c.m.163.com")){continue;}if(docUrl.contains("dy.163.com")){continue;}// ###################去重處理代碼######################Jedis jedis = JedisUtils.getJedis();Boolean flag = jedis.sismember("bigData:spider:163spider:docurl", docUrl);jedis.close();//一定一定一定不要忘記關閉, 否則用著用著沒了, 導致程序卡死不動if(flag){// 代表存在, 表示已經爬取過了continue;}// ###################去重處理代碼######################//System.out.println(docUrl);//3.3 獲取新聞詳情頁的數據News news = parseNewsItem(docUrl);// 4. 保存數據 ---- 一會 會有問題的//System.out.println(news);newsDao.saveNews(news);// ###################去重處理代碼######################// 將保存到數據庫中的docurl添加到redis的set集合中jedis = JedisUtils.getJedis();jedis.sadd("bigData:spider:163spider:docurl",news.getDocurl());jedis.close();// ###################去重處理代碼######################}}// 根據url 解析新聞詳情頁:private static News parseNewsItem(String docUrl) throws Exception {System.out.println(docUrl);// 3.3.1 發送請求, 獲取新聞詳情頁數據String html = HttpClientUtils.doGet(docUrl);//3.3.2 解析新聞詳情頁:Document document = Jsoup.parse(html);//3.3.2.1 : 解析新聞的標題:News news = new News();Elements h1El = document.select("#epContentLeft h1");String title = h1El.text();news.setTitle(title);//3.3.2.2 : 解析新聞的時間:Elements timeAndSourceEl = document.select(".post_time_source");String timeAndSource = timeAndSourceEl.text();String[] split = timeAndSource.split(" 來源: ");// 請各位一定一定一定要復制, 否則會切割失敗news.setTime(split[0]);//3.3.2.3 : 解析新聞的來源:news.setSource(split[1]);//3.3.2.4 : 解析新聞的正文:Elements ps = document.select("#endText p");String content = ps.text();news.setContent(content);//3.3.2.5 : 解析新聞的編輯:Elements spanEl = document.select(".ep-editor");// 責任編輯:陳少杰_b6952String editor = spanEl.text();// 一定要接收返回值, 否則白寫了editor = editor.substring(editor.indexOf(":")+1,editor.lastIndexOf("_"));news.setEditor(editor);//3.3.2.6 : 解析新聞的url:news.setDocurl(docUrl);//3.3.2.7: idlong id = idWorker.nextId();news.setId(id+"");return news;}// 將非標準的json轉換為標準的json字符串private static String splitJson(String jsonStr) {int firstIndex = jsonStr.indexOf("(");int lastIndex = jsonStr.lastIndexOf(")");return jsonStr.substring(firstIndex + 1, lastIndex);} }2. 爬取騰訊新聞娛樂數據
2.1 確定首頁url
熱點url : https://pacaio.match.qq.com/irs/rcd?cid=137&token=d0f13d594edfc180f5bf6b845456f3ea&ext=ent&num=60非熱點的首頁url : https://pacaio.match.qq.com/irs/rcd?cid=146&token=49cbb2154853ef1a74ff4e53723372ce&ext=ent&page=0非熱點的分頁url :https://pacaio.match.qq.com/irs/rcd?cid=146&token=49cbb2154853ef1a74ff4e53723372ce&ext=ent&page=02.2 發送請求, 獲取數據
// 騰訊娛樂新聞的爬蟲 public class TencentNewsSpider {public static void main(String[] args) throws Exception{//1. 確定首頁urlString topNewsUrl = "https://pacaio.match.qq.com/irs/rcd?cid=137&token=d0f13d594edfc180f5bf6b845456f3ea&ext=ent&num=60";String noTopNewsUrl = "https://pacaio.match.qq.com/irs/rcd?cid=146&token=49cbb2154853ef1a74ff4e53723372ce&ext=ent&page=0";//2. 發送請求, 獲取數據String topNewsJsonStr = HttpClientUtils.doGet(topNewsUrl);String noTopNewsJsonStr = HttpClientUtils.doGet(noTopNewsUrl);System.out.println(topNewsJsonStr);System.out.println(noTopNewsJsonStr);//3. 解析數據 :parseJson(topNewsJsonStr);parseJson(noTopNewsJsonStr);}// 解析新聞數據private static void parseJson(String newsJsonStr) {} }2.3 解析新聞數據(json)
// 解析新聞數據private static List<News> parseJson(String newsJsonStr) {//3.1 將字符串json數據轉換為指定的類型: mapGson gson = new Gson();Map<String,Object> map = gson.fromJson(newsJsonStr, Map.class);//3.2 獲取data中數據 : 列表頁中數據List<Map<String,Object>> newsList = (List<Map<String, Object>>) map.get("data");//3.3 遍歷這個列表, 獲取每一個新聞的數據List<News> tencentNewList = new ArrayList<News>();for (Map<String, Object> newsMap : newsList) {//3.3.1 封裝news對象News news = new News();news.setTitle((String)newsMap.get("title"));news.setTime((String)newsMap.get("update_time"));news.setSource((String)newsMap.get("source"));news.setContent((String)newsMap.get("intro"));news.setEditor((String)newsMap.get("source"));news.setDocurl((String)newsMap.get("vurl"));news.setId(idWorker.nextId() +"");tencentNewList.add(news);}return tencentNewList;}2.4 保存數據
- 1)創建一個saveNews的方法:
- 2)在main方法中, 添加保存數據的操作
[外鏈圖片轉存失敗(img-QqZpQhCN-1563093902039)(assets/1557647802086.png)]
2.5 分頁獲取數據
// 執行分頁的方法public static void page(String topNewsUrl,String noTopNewsUrl) throws Exception{//1. 熱點新聞數據的獲取: 只有一頁數據//1.1 發送請求, 獲取數據String topNewsJsonStr = HttpClientUtils.doGet(topNewsUrl);//1.2 解析數據List<News> topNewsList = parseJson(topNewsJsonStr);//1.3 保存數據saveNews(topNewsList);//2. 處理非熱點數據int page = 1;while(true){//2.1 發送請求, 獲取數據String noTopNewsJsonStr = HttpClientUtils.doGet(noTopNewsUrl);//2.2 解析數據List<News> noTopNewsList = parseJson(noTopNewsJsonStr);if(noTopNewsList == null){break;}//2.3 保存數據saveNews(noTopNewsList);//2.4 獲取下一頁urlnoTopNewsUrl = "https://pacaio.match.qq.com/irs/rcd?cid=146&token=49cbb2154853ef1a74ff4e53723372ce&ext=ent&page="+page;//2.5 自增 +1page++;System.out.println(page);}}- 過濾一些視頻的鏈接: 在parseJson的方法中過濾的
[外鏈圖片轉存失敗(img-2w8NM4Ey-1563093902040)(assets/1557648803513.png)]
2.6 去重處理
- 1)在獲取docurl后, 需要判斷, 這個url是否已經爬取過: parseJson方法中
- 不需要在執行封裝的代碼了
[外鏈圖片轉存失敗(img-VMMqN2G9-1563093902040)(assets/1557649544185.png)]
- 2)在保存數據之前, 再次進行判斷, 在保存之后, 將爬取過的url存儲到redis中
[外鏈圖片轉存失敗(img-D2GGal1g-1563093902040)(assets/1557649615117.png)]
2.7 整體騰訊爬蟲:
package com.itheima.spider.tencent;import com.google.gson.Gson; import com.itheima.spider.dao.NewsDao; import com.itheima.spider.pojo.News; import com.itheima.spider.utils.HttpClientUtils; import com.itheima.spider.utils.IdWorker; import com.itheima.spider.utils.JedisUtils; import redis.clients.jedis.Jedis;import java.util.ArrayList; import java.util.List; import java.util.Map;// 騰訊娛樂新聞的爬蟲 public class TencentNewsSpider {private static IdWorker idWorker = new IdWorker(0,2);private static NewsDao newsDao = new NewsDao();public static void main(String[] args) throws Exception{//1. 確定首頁urlString topNewsUrl = "https://pacaio.match.qq.com/irs/rcd?cid=137&token=d0f13d594edfc180f5bf6b845456f3ea&ext=ent&num=60";String noTopNewsUrl = "https://pacaio.match.qq.com/irs/rcd?cid=146&token=49cbb2154853ef1a74ff4e53723372ce&ext=ent&page=0";//2. 執行分頁:page(topNewsUrl,noTopNewsUrl);}// 執行分頁的方法public static void page(String topNewsUrl,String noTopNewsUrl) throws Exception{//1. 熱點新聞數據的獲取: 只有一頁數據//1.1 發送請求, 獲取數據String topNewsJsonStr = HttpClientUtils.doGet(topNewsUrl);//1.2 解析數據List<News> topNewsList = parseJson(topNewsJsonStr);//1.3 保存數據saveNews(topNewsList);//2. 處理非熱點數據int page = 1;while(true){//2.1 發送請求, 獲取數據String noTopNewsJsonStr = HttpClientUtils.doGet(noTopNewsUrl);//2.2 解析數據List<News> noTopNewsList = parseJson(noTopNewsJsonStr);if(noTopNewsList == null){break;}//2.3 保存數據saveNews(noTopNewsList);//2.4 獲取下一頁urlnoTopNewsUrl = "https://pacaio.match.qq.com/irs/rcd?cid=146&token=49cbb2154853ef1a74ff4e53723372ce&ext=ent&page="+page;//2.5 自增 +1page++;System.out.println(page);}}// 保存數據的操作 : 騰訊返回數據的時候, 就會有重復的數據public static void saveNews(List<News> newsList) {Jedis jedis = JedisUtils.getJedis();for (News news : newsList) {// ###################去重處理########################Boolean flag = jedis.sismember("bigData:spider:tencentSpider:docurl", news.getDocurl());if(flag){// 如果為true, 表示已經存在, 已經爬取過了continue;}// ###################去重處理########################newsDao.saveNews(news);// 保存數據之后, 將url保存到redis中//###################去重處理########################jedis.sadd("bigData:spider:tencentSpider:docurl",news.getDocurl());//###################去重處理########################}jedis.close();}// 解析新聞數據private static List<News> parseJson(String newsJsonStr) {//3.1 將字符串json數據轉換為指定的類型: mapGson gson = new Gson();Map<String,Object> map = gson.fromJson(newsJsonStr, Map.class);//獲取一下, 本次獲取了多少條數據Double datanum = (Double) map.get("datanum");if(datanum.intValue() == 0){return null;}//3.2 獲取data中數據 : 列表頁中數據List<Map<String,Object>> newsList = (List<Map<String, Object>>) map.get("data");//3.3 遍歷這個列表, 獲取每一個新聞的數據List<News> tencentNewList = new ArrayList<News>();for (Map<String, Object> newsMap : newsList) {String docurl = (String) newsMap.get("vurl");if(docurl.contains("video")){continue;}//######################去重處理############################33Jedis jedis = JedisUtils.getJedis();Boolean flag = jedis.sismember("bigData:spider:tencentSpider:docurl", docurl);jedis.close();if(flag){// 如果為true, 表示已經存在, 已經爬取過了continue;}//######################去重處理############################33//3.3.1 封裝news對象News news = new News();news.setTitle((String)newsMap.get("title"));news.setTime((String)newsMap.get("update_time"));news.setSource((String)newsMap.get("source"));news.setContent((String)newsMap.get("intro"));news.setEditor((String)newsMap.get("source"));news.setDocurl(docurl);news.setId(idWorker.nextId() +"");tencentNewList.add(news);System.out.println(docurl);}return tencentNewList;} }3. 梳理整個爬蟲的流程
3.1 163娛樂爬蟲的流程
[外鏈圖片轉存失敗(img-w1tjv4By-1563093902041)(assets/1557652264866.png)]
3.2 騰訊娛樂爬蟲的流程
[外鏈圖片轉存失敗(img-je6CPrCj-1563093902041)(assets/1557652742929.png)]
jedis.close();if(flag){// 如果為true, 表示已經存在, 已經爬取過了continue;}//######################去重處理############################33//3.3.1 封裝news對象News news = new News();news.setTitle((String)newsMap.get("title"));news.setTime((String)newsMap.get("update_time"));news.setSource((String)newsMap.get("source"));news.setContent((String)newsMap.get("intro"));news.setEditor((String)newsMap.get("source"));news.setDocurl(docurl);news.setId(idWorker.nextId() +"");tencentNewList.add(news);System.out.println(docurl);}return tencentNewList;} }總結
- 上一篇: wxFormBuilder摸索--小白上
- 下一篇: java 定时器 倒计时_Java:多种