【java爬蟲】---爬蟲+基於接口的網絡爬蟲

爬蟲+基於接口的網絡爬蟲

         上一篇講了【java爬蟲】---爬蟲+jsoup輕鬆爬博客,該方式有個很大的侷限性,就是你通過jsoup爬蟲只適合爬靜態網頁,所以只能爬當前頁面的所有新聞。如果需要爬一個網站所有信息,就得通過接口,通過改變參數反覆調該網站的接口,爬到該網站的所有數據信息。

   本博客以爬金色財經新聞信息爲對象,去爬取該網站從建站以來發表的所有新聞信息。下面會一步一步講解。這裏重點重點講思路,最後我會提供完整源碼。

 

第一步:找接口

     你要獲得該網站所有新聞數據,第一步當然是獲得接口,通過接口來獲取所有信息。

  F12-->Network-->all,找到接口:https://api.jinse.com/v4/information/listcatelogue_key=news&limit=23&information_id=56630&flag=down&version=9.9.9

    對這三個參數做個說明:

     limit=23   代表每次調用該接口返回23條數據。

     information_id=56630  代表下面返回的23條數據是通過大於56630或者小於56630這個ID指來返回數據。

     flag=down   代表向下翻頁 這裏也就是指ID小於56630的23條數據。

 通過postMan測試

  輸入:https://api.jinse.com/v4/information/list?catelogue_key=news&limit=2&information_id=0&flag=down&version=9.9.9(這裏返回兩條,id=0這裏代表最新的兩條數據)

返回json數據格式:

接口返回信息

 

第二步:通過定時任務開啓爬蟲工作

@Slf4j
@Component
public class SchedulePressTrigger {

    @Autowired
    private CrawlerJinSeLivePressService crawlerJinSeLivePressService;

    /**
    * 定時抓取金色財經的新聞
    */
    @Scheduled(initialDelay = 1000, fixedRate = 600 * 1000)
    public void doCrawlJinSeLivePress() {

      //  log.info("開始抓取金色財經新聞, time:" + new Date());
        try {
            crawlerJinSeLivePressService.start();
        } catch (Exception e) {
          //  log.error("本次抓取金色財經新聞異常", e);
        }
      //  log.info("結束抓取金色財經新聞, time:" + new Date());
    }
}

 

第三步:主要實現類

/**
 * 抓取金色財經快訊
 * @author xub
 * @since 2018/6/29
 */
@Slf4j
@Service
public class CrawlerJinSeLivePressServiceImpl extends AbstractCrawlLivePressService implements
        CrawlerJinSeLivePressService {

    //這個參數代表每一次請求獲得多少個數據
    private static final int PAGE_SIZE = 15;

    //這個是真正翻頁參數,每一次找id比它小的15個數據(有寫接口是通過page=1,2來進行翻頁所以比較好理解一點,其實它們性質一樣)
    private long bottomId;


    //這個這裏沒有用到,但是如果有數據層,就需要用到,這裏我只是把它答應到控制檯
    @Autowired
    private LivePressService livePressService;
    
    
    
    //定時任務運行這個方法,doTask沒有被重寫,所有運行父類的方法
    @Override
    public void start() {
        try {
            doTask(CoinPressConsts.CHAIN_FOR_LIVE_PRESS_DATA_URL_FORMAT);
        } catch (IOException e) {
          //  log.error("抓取金色財經新聞異常", e);
        }
    }


    @Override
    protected List<PageListPress> crawlPage(int pageNum) throws IOException {
        // 最多抓取100頁,多抓取也沒有特別大的意思。
        if (pageNum >= 100) {
            return Collections.emptyList();
        }
        // 格式化翻頁參數(第一次bottomId爲0,第二次就是這次爬到的最小bottomId值)
        String requestUrl = String.format(CoinPressConsts.CHAIN_FOR_LIVE_PRESS_DATA_URL_FORMAT, PAGE_SIZE, bottomId);

        Response response = OkHttp.singleton().newCall(
                new Request.Builder().url(requestUrl).addHeader("referer", CoinPressConsts.CHAIN_FOR_LIVE_URL).get().build())
                .execute();
        if (response.isRedirect()) {
            // 如果請求發生了跳轉,說明請求不是原來的地址了,返回空數據。
            return Collections.emptyList();
        }

        //先獲得json數據格式
        String responseText = response.body().string();

        //在通過工具類進行數據賦值
        JinSePressResult jinSepressResult = JsonUtils.objectFromJson(responseText, JinSePressResult.class);
        if (null == jinSepressResult) {
            // 反序列化失敗
            System.out.println("抓取金色財經新聞列表反序列化異常");
            return Collections.emptyList();
        }
        // 取金色財經最小的記錄id,來進行翻頁
        bottomId = jinSepressResult.getBottomId();

        //這個是谷歌提供了guava包裏的工具類,Lists這個集合工具,對list集合操作做了些優化提升。
        List<PageListPress> pageListPresss = Lists.newArrayListWithExpectedSize(PAGE_SIZE);

        for (JinSePressResult.DayData dayData : jinSepressResult.getList()) {
            JinSePressData data = dayData.getExtra();
            //新聞發佈時間(時間戳格式)這裏可以來判斷只爬多久時間以內的新聞
            long   createTime = data.getPublishedAt() * 1000;
            Long timemill=System.currentTimeMillis();
//           if (System.currentTimeMillis() - createTime > CoinPressConsts.MAX_CRAWLER_TIME) {
//               // 快訊過老了,放棄
//               continue;
//           }
            SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String sd = sdf.format(new Date(createTime));   // 時間戳轉換成時間
            Date newsCreateTime=new Date();
            try {
                //獲得新聞發佈時間
                newsCreateTime = sdf.parse(sd);
            } catch (ParseException e) {
                e.printStackTrace();
            }
            //具體文章頁面路徑(這裏可以通過這個路徑+jsoup就可以爬新聞正文所有信息了)
            String href = data.getTopicUrl();
            //新聞摘要
            String summary = data.getSummary();
            //新聞閱讀數量
            String pressreadcount = data.getReadNumber();
            //新聞標題
            String title = dayData.getTitle();

                pageListPresss.add(new PageListPress(href,title, Integer.parseInt(pressreadcount),
                        newsCreateTime ,  summary));
        }
        return pageListPresss;
    }
}

AbstractCrawlLivePressService 類

 public abstract class AbstractCrawlLivePressService {
    String url;
    public void doTask(String url) throws IOException {
        this.url = url;
        int pageNum = 1;

        //通過 while (true)會一直循環調取接口,直到數據爲空或者時間過老跳出循環
        while (true) {
            List<PageListPress> newsList = crawlPage(pageNum++);
            // 抓取不到新的內容本次抓取結束
            if (CollectionUtils.isEmpty(newsList)) {
                break;
            }    
            //這裏並沒有把數據放到數據庫,而是直接從控制檯輸出
            for (int i = newsList.size() - 1; i >= 0; i--) {
                PageListPress pageListNews = newsList.get(i);
                System.out.println(pageListNews.toString());
          
            }
        }
    }
    //這個由具體實現類實現
    protected abstract List<PageListPress> crawlPage(int pageNum) throws IOException;

@Data
@AllArgsConstructor
@NoArgsConstructor
    public static class PageListPress {

        //新聞詳情頁面url
         private String href;
       //新聞標題
        private String title;
        //新聞閱讀數量
        private int readCounts;
        //新聞發佈時間
        private Date createTime;
        //新聞摘要
        private String summary;
    
        }
 }

JinSePressResult 

/**
 *在創建對象的時候一定要分析好json格式的類型
 *金色新聞的返回格式就是第一層有普通屬性和一個list集合
 *在list集合中又有普通屬性和一個extra的對象。
 */
@JsonIgnoreProperties(ignoreUnknown = true)
@Data
public class JinSePressResult {

    private int news;
    private int count;
    @JsonProperty("top_id")
    private long topId;
    @JsonProperty("bottom_id")
    private long bottomId;
    //list的名字也要和json數據的list名字一致,否則無用
    private List<DayData> list;

    @Data
    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class DayData {

        private String title;
        //這裏對象的屬性名extra也要和json的extra名字一致
        private JinSePressData extra;
        @JsonProperty("topic_url")
        private String topicUrl;
    }
}

這裏需要注意兩點

   (1)在創建對象時一定要先搞清楚json格式類型是對象裏含有集合,或者集合中還有對象等等。

   (2) 你可以只定義你需要的屬性字段,當你不能和json的屬性名一致但類型不一致。比如上面你改成 List extra 這個時候序列化就會失敗,因爲json的extra明顯是一個對象,而這邊接受的確實一個集合。關鍵是屬

性名一致 所以在賦值的時候就會報錯,序列化失敗。

 

第四步:看運行結果

   這裏只是截取控制檯輸出的部分信息,通過這種方式可以獲得該網站的所有新聞信息。同時我們已經獲得具體新聞的URL,那麼我們就可以通過JSOup來獲取該新聞的所有具體信息。(完美)

 

第五步:數據庫去重思路

   因爲你不可能每一次爬取玩數據都直接放到數據庫中,肯定要比較該條新聞數據庫中是否已經存在,不存在才放到數據庫中。思路如下:

   (1)數據庫表中添加一個能辨別該新聞唯一的屬性字段,比如jinse+bottomId組成唯一屬性,或者該新聞具體頁面路徑URI組成唯一屬性。

   (2)創建map<URL,Boolean>集合,通過URI看數據庫是否存在,有就<URL,true>,沒有<URL,flase>。

   (3)在存儲之前通過map.get(URI)如果爲false則存儲數據庫中。

 

Git源碼

 首先說明下,源碼本人是通過Idea測試運行通過,這裏用了lombok,你需要現在idea或者eclipse配置Lombok。

 源碼地址:https://github.com/yudiandemingzi/panjiekou

 

想太多,做太少,中間的落差就是煩惱。想沒有煩惱,要麼別想,要麼多做。中校【9】 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章