先说下背景,前几天老哥让帮忙从网上抓点数据,因为他是做食品添加剂的推广工作的,所以需要知道有哪些工厂或者厂家可能需要食品添加剂,然后他给了我一个网址----湖北省食品药品行政许可公示平台。既然是公示平台,数据应该就是公开的,爬起来应该不会被查水表吧,看这个警徽还是怕怕的 .......>_>
如下:
抓取的数据类似图上列表中的数据,但是他说还要厂家地址和食品类型,能有其他数据更好。
然后我研究了下,发现图上页面是没有厂家地址的(但是我这个页面也抓了,服务器返回的是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。然后是代码,我把主要部分都贴出来,可能代码会有些不规范的地方,大家也可以指出,项目结构比较简单,代码也不长,直接主函数里面就执行完了所有的内容,新人拿来练手也不错。
项目整体的结构:
-
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等从数据中拿到想要的数据:
-
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);
-
}
-
}
主要是jdbc的内容,大家应该也都会:
import model.DataBase; import java.sql.*; /** * @Author tang_zhen * @Date 2018/3/7 * @Description */ public class DBUtils { // 获取数据库连接 public static Connection getConnection() { Connection con = null; //定义连接相关属性 final String DRIVER = "com.mysql.jdbc.Driver"; final String URL = "jdbc:mysql://127.0.0.1:3306/tang?useUnicode=true&characterEncoding=utf-8&useSSL=false"; final String USER = "root"; final String PWD = "111111"; try { Class.forName(DRIVER); con = DriverManager.getConnection(URL, USER, PWD); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } return con; } // public static void insert(DataBase od) { Connection con = DBUtils.getConnection(); try { PreparedStatement stat = con.prepareStatement("select * from new_get_data where id= ? "); stat.setString(1, od.getId()); ResultSet resultSet = stat.executeQuery(); if (resultSet.next()) { con.close(); return; } String sql = "insert into new_get_data (id,name,address,home_address,cut_off_date,date_of_issue,food_type) values(?,?,?,?,?,?,?);"; // Connection con = DBUtils.getConnection(); // 预编译 PreparedStatement ps = con.prepareStatement(sql); ps.setString(1, od.getId()); ps.setString(2, od.getName()); ps.setString(3, od.getAddress()); ps.setString(4, od.getHomeAddress()); ps.setString(5, od.getCutOffData()); ps.setString(6, od.getDateOfIssue()); ps.setString(7, od.getFoodType()); // 执行 ps.execute(); } catch (SQLException e) { e.printStackTrace(); } finally { //图方便,关闭连接就不单独写个方法调用了 try { con.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
最后是效果:
上图是打印出来的服务器返回的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会被拉黑。有啥问题直接留言,第一次写博客,大家多多包涵,之前都是在有道云笔记里记东西。