java爬蟲--利用java爬取網頁數據的方法與思路,以爬取“食品許可公示的數據”爲例

  • 項目背景   

    先說下背景,前幾天老哥讓幫忙從網上抓點數據,因爲他是做食品添加劑的推廣工作的,所以需要知道有哪些工廠或者廠家可能需要食品添加劑,然後他給了我一個網址----某食品藥品許可公示平臺。既然是公示平臺,數據應該就是公開的,爬起來應該不會被查水錶吧,看這個警徽還是怕怕的 .......>_>

如下:

圖已省略..........防止被查水錶

    抓取的數據類似圖上列表中的數據,但是他說還要廠家地址和食品類型,能有其他數據更好。

    然後我研究了下,發現圖上頁面是沒有廠家地址的(但是我這個頁面也抓了,服務器返回的是json格式的數據,解析下json數據存入數據庫就行,這個平臺貌似是近幾個月纔出的),也不夠詳細,在其他頁面有帶廠家地址的數據,給服務器發送請求返回的是html的數據,解析html數據,將所需數據存入數據庫。

     帶地址的網頁長這樣:

(URL:http://******websearch/SearchCardAction.do?operate=searchGyEntCard&operPage=card_spscxkz_list&cardtype=103):

    點擊每個生產者名稱都會跳出一個彈窗,該彈窗顯示的是食品許可證的詳細數據(後臺服務器實際應該是以該公司在數據庫中的uuid爲條件,去詳細查了下該生產許可證的信息,然後返回給了前端):

(URL:http://******/websearch/SearchCardAction.do?operate=viewGyEntCard&operPage=card_spscxkz_view&recid=2c9080845707bcc30159a66100300bd3)

    ok,現在目標很明確,就是從帶廠家地址的網頁上把所有公司的uuid拿到,然後以每個uuid作爲URL變化的條件(“recid=”後面是變化的,拼接URL的時候將uuid拼在具體的鏈接上來訪問服務器),從彈窗的那個頁面拿到詳細的數據,存入數據庫。

  • 項目環境

    ide:intellij IDEA 2017.2.5

    編程語言:java

    數據庫:mysql

    管理工具:maven   

    需要導包:mysql-connector-java,jsoup(解析html)等,如果要解析json可能還要導入gson的包。

我把重要的依賴貼出來(默認大家都是使用過maven的,如果不使用maven,你可以到網上下載jar包添加到項目裏):

 

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.8.0</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.38</version>
</dependency>
<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.9.2</version>
</dependency>
  • 項目詳情

    思路上面說的差不多了,要補充的是,我們使用HttpURLConnection來連接服務器(httpclient等開源項目或者工具應該也行),對於POST請求和GET請求稍微有些區別,這個網上也比較多,大家可以自行百度或者Google。然後是代碼,我把主要部分都貼出來,可能代碼會有些不規範的地方,大家也可以指出,項目結構比較簡單,代碼也不長,直接主函數裏面就執行完了所有的內容,新人拿來練手也不錯。

項目整體的結構:

      1.主類部分

 

package main; /**
 * @Author tang_zhen
 * @Date 2018/3/7
 * @Description
 */
import model.DataBase;
import com.google.gson.JsonObject;
import service.dao.DBUtils;
import service.MyParse;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

public class GetData {
    /**
     * 發起http請求並獲取結果
     * @param requestUrl 請求地址
     */
    public static String getRequest(String requestUrl){
        String res="";
//        JsonObject object = null;
        StringBuffer buffer = new StringBuffer();
        try{
            URL url = new URL(requestUrl);
            //打開連接
            HttpURLConnection urlCon= (HttpURLConnection)url.openConnection();
            if(200==urlCon.getResponseCode()){//連接正常,獲取輸入流
                InputStream is = urlCon.getInputStream();
                InputStreamReader isr = new InputStreamReader(is,"GBK");
                BufferedReader br = new BufferedReader(isr);

                String str = null;
                while((str = br.readLine())!=null){
                    buffer.append(str);
                }
                //關閉流
                br.close();
                isr.close();
                is.close();
                res = buffer.toString();
                //如果是json數據可以這樣解析然後返回JsonObject類型的對象
//                JsonParser parse =new JsonParser();
//                JsonObject res2 = (JsonObject)parse.parse(buffer.toString());
            }
        }catch(IOException e){
            e.printStackTrace();
        }
        return res;
    }

    public static String postDownloadRes(String path,String post){
        URL url = null;
        try {
            url = new URL(path);
            HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
            httpURLConnection.setRequestMethod("POST");// Post請求
            // conn.setConnectTimeout(10000);//連接超時 單位毫秒
            // conn.setReadTimeout(2000);//讀取超時 單位毫秒
            // POST需設置如下兩行
            httpURLConnection.setDoOutput(true);
            httpURLConnection.setDoInput(true);
            // 獲取URLConnection對象對應的輸出流
            PrintWriter printWriter = new PrintWriter(httpURLConnection.getOutputStream());
            // 發送請求參數(post請求的參數一般可以從瀏覽器裏查請求的時候看到參數是哪些)
            printWriter.write(post);//post的參數 形式爲xx=xx&yy=yy
            
            // flush輸出流的緩衝
            printWriter.flush();
            //開始獲取數據
            BufferedInputStream bis = new BufferedInputStream(httpURLConnection.getInputStream());
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            int len;
            byte[] arr = new byte[1024];
            while((len=bis.read(arr))!= -1){
                bos.write(arr,0,len);
                bos.flush();
            }
            bos.close();
            //如果是json數據可以這樣解析然後返回JsonObject類型的對象
//          JsonParser parse =new JsonParser();
//          JsonObject res2 = (JsonObject)parse.parse(bos.toString("utf-8"));
            return bos.toString("utf-8");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    //測試
    public static void main(String args [] ) {
//        JsonObject res = null;
        String responseStr = null;
        JsonObject res2 = null;
//        for (int k=1;k<=631;k++) {
//           //獲取某一頁的數據可以根據“nextPageNo=*”來指定,就是字符串拼接下,把1換成n
       String str = "gyEntcardprint.cardid=&gyEntcardprint.name=&pageModel.nextPageNo="+1+"&pageModel.pageSize=12&cardtype=103";
            responseStr = postDownloadRes("wssb/websearch/SearchCardAction.do?operate=searchGyEntCard&operPage=card_spscxkz_list&cardtype=103&pageModel.afreshQuery=true", str);
            //System.out.println(responseStr);
            try {
         //第一個頁面中拿到的是一個列表,是一頁的數據
                List<DataBase> list = MyParse.getData(responseStr);
                for (DataBase dataBase : list) {
                    String str1 = "wssb/websearch/SearchCardAction.do?operate=viewGyEntCard&operPage=card_spscxkz_view&recid="+dataBase.getId();
                    String responseStr1 = getRequest(str1);
                    System.out.println(responseStr1);
                    MyParse.getTotalData(responseStr1,dataBase.getId());
                    //存id和公司名到第一張表裏
//                    insert(dataBase);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            //這部分註釋的是json解析的部分,之前訪問過的網址返回的是json數據(嵌套了有多層)
//            JsonArray member = responseStr.getAsJsonArray("zsList");
//            for (int i = 0; i < member.size(); i++) {
//                JsonElement elements = member.get(i);
//                JsonElement name = elements.getAsJsonObject().get("name");
//                JsonElement id = elements.getAsJsonObject().get("id");
//                DataBase db = new DataBase(id.toString(), name.toString());
//                System.out.println(name);
//                System.out.println(id);
//                insert(db);
//            }
//        }

    }
}

主函數部分就是打開連接,模擬瀏覽器與服務器交互,接收從服務器返回的數據,再調用MyParse類中的getData等從數據中拿到想要的數據:

    2.解析部分:

 

import model.DataBase;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import service.dao.DBUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author tang_zhen
 * @Date 2018/3/8
 * @Description
 */
public class MyParse {
    /**
     * 獲取每個公司的id和姓名
     * @param html
     * @return
     * @throws Exception
     */
    public static List<DataBase> getData(String html) throws Exception {
        //獲取的數據,存放在集合中
        List<DataBase> data = new ArrayList<DataBase>();
        //System.out.println(html.length()+"HTML長度");
        //System.out.println(html);
        //採用Jsoup解析
        Document doc = Jsoup.parse(html);
        //System.out.println(doc.text() + "doc內容");
        //獲取html標籤中的內容
        Elements elements = doc.select("tr[class=Items]");
        System.out.println(elements.size() + "****條");
        for (Element element:elements)
        {
            Element link = element.select("a").first();
            String text = element.select("a").get(1).text();

            String linkHref = link.attr("href");
            String[] newLinkHrefArray = linkHref.split("\'");
            String newLinkHref =newLinkHrefArray[1];

//            System.out.println(newLinkHref+"------"+text);
            DataBase dataBase = new DataBase();
            dataBase.setId(newLinkHref);
            dataBase.setName(text);
            data.add(dataBase);
//            insert(dataBase);
        }
        return data;
    }

    /**
     * 根據id獲取彈窗上詳細的數據
     * @param html
     * @param id
     */
    public static void getTotalData(String html,String id) {
        List<DataBase> data = new ArrayList<DataBase>();
        //System.out.println(html.length()+"HTML長度");
        //採用Jsoup解析
        Document doc = Jsoup.parse(html);
        //System.out.println(doc.text() + "doc內容");
        //獲取html標籤中的內容
        Elements elements = doc.select("tr");
//        System.out.println(elements.size() + "****條");
        DataBase dataBase=new DataBase();
        for (Element element:elements)
        {   //jsoup的具體解析你們可以百度一下,根據內容不一樣,獲取的方式也不太一樣,我這個內容算是比較簡單的了,
            // 複雜的html內容獲取某個數據,一行可能都寫不下
            if(element.select("td").first().text().equals("生產者名稱"))
            {
                String name = element.select("td").get(1).text();
                if (name==null) name="";
                dataBase.setName(name);
            }
            if(element.select("td").first().text().equals("住所"))
            {
                String homeAddress = element.select("td").get(1).text();
                if(homeAddress==null) homeAddress="";
                dataBase.setHomeAddress(homeAddress);
            }
            if(element.select("td").first().text().equals("生產地址"))
            {
                String address = element.select("td").get(1).text();
                if (address==null) address="";
                dataBase.setAddress(address);
            }

            if(element.select("td").first().text().equals("食品類別"))
            {
                String foodType = element.select("td").get(1).text();
                if(foodType==null) foodType="";
                dataBase.setFoodType(foodType);
            }
            if(element.select("td").first().text().equals("發證日期"))
            {
                String dateOfIssue = element.select("td").get(1).text();
                if (dateOfIssue==null) dateOfIssue="";
                dataBase.setDateOfIssue(dateOfIssue);
                //截止日期
                String cutOffDate = element.select("td").get(3).text();
                if(cutOffDate==null) cutOffDate="";
                dataBase.setCutOffData(cutOffDate);
            }
            dataBase.setId(id);
        }
        //將bean中的數據存入數據庫
        DBUtils.insert(dataBase);
    }
}

3.數據庫部分

主要是jdbc的內容,大家應該也都會(已省略...防止被查水錶):

 

最後是效果:

上圖是打印出來的服務器返回的html,下圖是存入數據庫的數據截圖:

    另外,我覺得有必要貼一下返回的html的部分內容,這樣如果想知道如何利用jsoup解析html的可以對照着html的結構看下我的代碼是如何解析的:

 

<table width="99%" border="0" cellpadding="0" cellspacing="1" id="wrap_1" align="center">
  <tr>
    <td class="td_content">
        <table width="100%" border="0" cellpadding="0" cellspacing="1" id="content">
        <tr>
             <td width="15%"  class="bai_right">證書名稱</td>
             <td   class="bai_left" colspan="3"><input type="hidden" name="cardtype" value="" id="cardnametype">
            食品生產許可證
          </td>
        </tr>
        <tr>
             <td width="15%"  class="bai_right">證書編號</td>
             <td class="bai_left" colspan="3" >QS4203 2401 0460</td>
      </tr>
        <tr>
             <td width="15%" class="bai_right">生產者名稱</td>
             <td  class="bai_left" colspan="3">房縣味味食品有限公司</td>
        </tr>
        <tr>
             <td width="15%" class="bai_right">社會信用代碼</td>
             <td width="35%" class="bai_left" >X1631412-7</td>
             <td width="15%" class="bai_right">法定代表人</td>
             <td width="35%" class="bai_left" >林益華</td>
        </tr>
           <tr>
             <td width="15%" class="bai_right">住所</td>
             <td  class="bai_left" colspan="3">湖北省十堰市房縣紅塔鎮西城工業園高碑村四組</td>
        </tr>
        <tr>
             <td width="15%" class="bai_right">生產地址</td>
             <td  class="bai_left" colspan="3">湖北省十堰市房縣紅塔鎮西城工業園高碑村四組</td>
        </tr>
      <tr>
             <td width="15%" class="bai_right">食品類別</td>
             <td class="bai_left" colspan="3"></td>
        </tr>
        <tr>
             <td width="15%" class="bai_right">日常監督管理機構</td>
             <td width="35%" class="bai_left"></td>
             <td width="15%" class="bai_right">日常監督管理人員</td>
             <td width="35%" class="bai_left"></td>
         </tr>
       <tr>
             <td width="15%" class="bai_right">簽發人</td>
             <td width="35%" class="bai_left"></td>
             <td width="15%" class="bai_right">發證機關</td>
             <td width="35%" class="bai_left"></td>
         </tr>
       <tr>
             <td width="15%" class="bai_right">發證日期</td>
             <td width="35%" class="bai_left">2012-08-08</td>
             <td width="15%" class="bai_right">截止日期</td>
             <td width="35%" class="bai_left">2018-07-30</td>
         </tr>
        </table>
  • 項目總結

    其實整個項目就是個小程序,小爬蟲,總體來說技術不算難,結構也很簡單,大部分用到的技術和知識網上也挺多的,不管咋說,作爲一個菜鳥程序猿,畢竟還是用自己所學的技術解決掉了一個比較現實的問題吧。然後就是,我發現代碼跑起來是真的慢啊!爬一頁數據,大概要兩秒多?我猜問題主要是出在了數據庫那,因爲我是用完一個連接就直接關閉了,早知道慢成這樣就用個數據庫的連接池了.....

    PS:代碼裏面的URL都不是完整的,如果你們要實驗,還是換個網站吧,這個畢竟是政府網站,雖然數據也是公開的數據,但是連接太多對服務器是有壓力的,崩了就不好了,萬一被查水錶呢。還有就是有的網站可能會拒絕連接,有的防止類似的爬蟲爬取頁面的時候會做一些措施,多次連接IP會被拉黑。有啥問題直接留言,第一次寫博客,大家多多包涵,之前都是在有道雲筆記裏記東西。

 

 

 

 

 

 

 

 

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