java實現一個簡單的爬蟲小程式

我恰芙蓉王發表於2020-08-11

前言

前些天無意間在百度搜尋了一下以前寫過的部落格

 

 

我啥時候在這麼多不知名的網站上發表部落格了???點進去一看, 內容一模一樣,作者卻不是我...

然後又去搜了其他篇部落格,果然,基本上每篇都在別的網站上有,細想,可能是通過網路爬蟲爬取部落格園首頁部落格,然後copy至自己網站中,於是乎,博主也來實現一遍爬取流程。。。

 

實現思路

先訪問部落格園首頁,F12檢視原始碼,可以看到部落格的連結和標題都是放在一個a標籤裡,

 

 

 

點選上一下、下一頁,再看一下請求引數,嗯。。。這個應該是頁碼引數

 

 

 

 

通過以上這些資訊,我們就可以知道只需要每次傳入不同的頁碼訪問部落格園首頁,就可以獲得相應部落格的html頁面返回,然後我們返回的html頁面,解析出當頁的部落格連結和標題就可以啦。

說幹就幹,下面我們用程式碼實現模擬下載部落格園200頁(200 * 20 = 4000篇)博文的程式

 

具體實現

直接上程式碼了,註釋都在程式碼中

import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @ClassName BKYPageReptile
 * @Description TODO(爬取部落格園文章)
 * @Author 我恰芙蓉王
 * @Date 2020年08月11日 9:38
 * @Version 2.0.0
 **/

public class BKYPageReptile {

    //請求地址
    private static final String URL = "https://www.cnblogs.com";

    //儲存路徑
    private static final String TARGET_PATH = "F://" + "部落格園";

    //行匹配正則
    private static final Pattern LINE_PATTERN = Pattern.compile("<a class=\"post-item-title\" href=\"https://www.cnblogs.com/.*?\\.html\" target=\"_blank\">.*?</a>");

    //url正則
    private static final Pattern URL_PATTERN = Pattern.compile("https://www.cnblogs.com/.*?\\.html");

    //標題/檔名正則
    private static final Pattern TITLE_PATTERN = Pattern.compile(">.*?</a>");

    //標題快取
    private static final List<String> TITLE_LIST = new CopyOnWriteArrayList<>();

    //當前頁數
    private static int PAGE = 1;

    //最大拉取頁數
    private static final int MAX_PAGE = 200;

    //一共拉取部落格篇數
    private static int ALL_COUNT = 0;

    //時間格式
    private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) {
        //建立根目錄
        File rootDir = new File(TARGET_PATH);
        if (!rootDir.exists()) {
            rootDir.mkdir();
        }

        //建立日誌資料夾
        String logPath = TARGET_PATH + "//拉取日誌";
        File logDir = new File(logPath);
        if (!logDir.exists()) {
            logDir.mkdir();
        }

        //建立日誌檔案
        File logFile = new File(logPath + "//log.txt");
        if (!logFile.exists()) {
            try {
                logFile.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        //迴圈拉取
        while (PAGE <= MAX_PAGE) {
            //日誌內容
            String logContent = "正在拉取第" + PAGE + "頁\n";
            System.err.println("\n" + logContent);
            String param = "PageIndex=" + PAGE;

            try {
                //獲取指定頁頁返回內容
                String response = sendPost(URL, param);

                Matcher matcher = LINE_PATTERN.matcher(response);

                //需要寫入的檔案集合
                ArrayList<FileTemplate> urlList = new ArrayList<>(20);

                /**
                 * 解析返回內容封裝成FileTemplate
                 */
                while (matcher.find()) {
                    //匹配行
                    String matchLine = matcher.group();

                    Matcher matcher1 = TITLE_PATTERN.matcher(matchLine);
                    String title = null;
                    while (matcher1.find()) {
                        //匹配的標題    >標題</a>
                        title = matcher1.group();
                    }
                    //擷取拿到真實標題
                    title = title.substring(1, title.length() - 4);
                    //特殊字元處理
                    title = title.replace("<", "《")
                            .replace(">", "》")
                            .replace("\\", "-")
                            .replace("/", "-")
                            .replace(":", ":")
                            .replace("*", "")
                            .replace("?", "?")
                            .replace("|", "")
                            + ".html";
                    System.err.println("title = " + title);

                    //如果已經拉取了此標題的html檔案   則跳過此篇
                    if (TITLE_LIST.contains(title)) {
                        continue;
                    }

                    Matcher matcher2 = URL_PATTERN.matcher(matchLine);
                    String url = null;
                    while (matcher2.find()) {
                        //匹配部落格的請求url
                        url = matcher2.group();
                    }
                    //封裝成檔案模板物件
                    urlList.add(new FileTemplate(url, title, false));
                }


                /**
                 * 寫入磁碟
                 */
                urlList.parallelStream().forEach(v -> {
                    FileOutputStream fos = null;
                    PrintWriter pw = null;
                    try {
                        String result = sendGet(v.getGetUrl(), "");
                        File file = new File(TARGET_PATH + File.separator + v.getTitle());
                        file.createNewFile();

                        fos = new FileOutputStream(file);
                        pw = new PrintWriter(fos);
                        pw.write(result.toCharArray());
                        pw.flush();
                        v.setFlag(true);

                        TITLE_LIST.add(v.getTitle());
                    } catch (Exception e) {
                        System.out.println(v.toString());
                        e.printStackTrace();
                    } finally {
                        try {
                            if (fos != null) {
                                fos.close();
                            }
                            if (pw != null) {
                                pw.close();
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }

                    }
                });

                /**
                 * 記錄日誌
                 */
                //本次寫入成功部落格數
                long count = urlList.stream().filter(v -> v.getFlag()).count();

                String date = SDF.format(new Date());

                //累加次數
                ALL_COUNT += count;

                logContent += "本次拉取完成,共  " + count + "  篇新部落格\r\n";
                logContent += "一共拉取了  " + ALL_COUNT + "  篇\r\n";
                logContent += "時間 : " + date + "\n\n";
                BufferedWriter out = null;
                try {
                    out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(logFile, true)));
                    out.write(logContent + "\r\n");
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    try {
                        out.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

                PAGE++;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 檔案模板類
     */
    private static class FileTemplate {
        /**
         * 請求地址
         */
        private String getUrl;

        /**
         * 標題
         */
        private String title;

        /**
         * 已經爬取標識
         */
        private boolean flag;

        public FileTemplate(String getUrl, String title, boolean flag) {
            this.getUrl = getUrl;
            this.title = title;
            this.flag = flag;
        }

        public String getGetUrl() {
            return getUrl;
        }

        public void setGetUrl(String getUrl) {
            this.getUrl = getUrl;
        }

        public String getTitle() {
            return title;
        }

        public void setTitle(String title) {
            this.title = title;
        }

        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder("FileTemplate{");
            sb.append("getUrl='").append(getUrl).append('\'');
            sb.append(", title='").append(title).append('\'');
            sb.append('}');
            return sb.toString();
        }

        public boolean getFlag() {
            return flag;
        }

        public void setFlag(boolean flag) {
            this.flag = flag;
        }
    }

    /**
     * 功能描述: 向指定URL傳送GET請求
     *
     * @param url   傳送請求的URL
     * @param param 請求引數,請求引數應該是 name1=value1&name2=value2 的形式
     * @建立人: 我恰芙蓉王
     * @建立時間: 2020年08月11日 16:42:17
     * @return: java.lang.String  響應結果
     **/
    public static String sendGet(String url, String param) {
        StringBuilder sb = new StringBuilder();
        BufferedReader in = null;
        try {
            String urlNameString = url + "?" + param;
            URL realUrl = new URL(urlNameString);
            // 開啟和URL之間的連線
            URLConnection connection = realUrl.openConnection();
            // 設定通用的請求屬性
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("user-agent",
                    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // 建立實際的連線
            connection.connect();
            // 獲取所有響應頭欄位
            Map<String, List<String>> map = connection.getHeaderFields();
            // 定義 BufferedReader輸入流來讀取URL的響應
            in = new BufferedReader(new InputStreamReader(
                    connection.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                sb.append(line);
            }
        } catch (Exception e) {
            System.out.println("傳送GET請求出現異常!" + e);
            e.printStackTrace();
        }
        // 使用finally塊來關閉輸入流
        finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
        return sb.toString();
    }

    /**
     * 功能描述: 向指定URL傳送POST請求
     *
     * @param url   傳送請求的URL
     * @param param 請求引數,請求引數應該是 name1=value1&name2=value2 的形式
     * @建立人: 我恰芙蓉王
     * @建立時間: 2020年08月11日 16:42:17
     * @return: java.lang.String  響應結果
     **/
    public static String sendPost(String url, String param) {
        PrintWriter out = null;
        BufferedReader in = null;
        StringBuilder sb = new StringBuilder();
        try {
            URL realUrl = new URL(url);
            // 開啟和URL之間的連線
            URLConnection conn = realUrl.openConnection();
            // 設定通用的請求屬性
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("user-agent",
                    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // 傳送POST請求必須設定如下兩行
            conn.setDoOutput(true);
            conn.setDoInput(true);
            // 獲取URLConnection物件對應的輸出流
            out = new PrintWriter(conn.getOutputStream());
            // 傳送請求引數
            out.print(param);
            // flush輸出流的緩衝
            out.flush();
            // 定義BufferedReader輸入流來讀取URL的響應
            in = new BufferedReader(
                    new InputStreamReader(conn.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                sb.append(line);
            }
        } catch (Exception e) {
            System.out.println("傳送 POST 請求出現異常!" + e);
            e.printStackTrace();
        }
        //使用finally塊來關閉輸出流、輸入流
        finally {
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        return sb.toString();
    }
}

 

 

測試結果

控制檯輸出

 

 

下載在電腦磁碟中

 

 

 

日誌檔案內容

 

 

隨便開啟一個html檔案

 

相關文章