- 項目背景
先說下背景,前幾天老哥讓幫忙從網上抓點數據,因爲他是做食品添加劑的推廣工作的,所以需要知道有哪些工廠或者廠家可能需要食品添加劑,然後他給了我一個網址----某食品藥品許可公示平臺。既然是公示平臺,數據應該就是公開的,爬起來應該不會被查水錶吧,看這個警徽還是怕怕的 .......>_>
如下:
圖已省略..........防止被查水錶
抓取的數據類似圖上列表中的數據,但是他說還要廠家地址和食品類型,能有其他數據更好。
然後我研究了下,發現圖上頁面是沒有廠家地址的(但是我這個頁面也抓了,服務器返回的是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會被拉黑。有啥問題直接留言,第一次寫博客,大家多多包涵,之前都是在有道雲筆記裏記東西。