利用java爬取网页数据的方法与思路,以爬取“食品许可公示的数据”为例

  • 项目背景   

    先说下背景,前几天老哥让帮忙从网上抓点数据,因为他是做食品添加剂的推广工作的,所以需要知道有哪些工厂或者厂家可能需要食品添加剂,然后他给了我一个网址----湖北省食品药品行政许可公示平台。既然是公示平台,数据应该就是公开的,爬起来应该不会被查水表吧,看这个警徽还是怕怕的 .......>_>

如下:

利用java爬取网页数据的方法与思路,以爬取“食品许可公示的数据”为例_第1张图片

    抓取的数据类似图上列表中的数据,但是他说还要厂家地址和食品类型,能有其他数据更好。

    然后我研究了下,发现图上页面是没有厂家地址的(但是我这个页面也抓了,服务器返回的是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包添加到项目里):

 


   
   
   
   
  1. <dependency>
  2. <groupId>com.google.code.gson groupId>
  3. <artifactId>gson artifactId>
  4. <version>2.8.0 version>
  5. dependency>
  6. <dependency>
  7. <groupId>mysql groupId>
  8. <artifactId>mysql-connector-java artifactId>
  9. <version>5.1.38 version>
  10. dependency>

   
   
   
   
  1. <dependency>
  2. <groupId>org.jsoup groupId>
  3. <artifactId>jsoup artifactId>
  4. <version>1.9.2 version>
  5. dependency>
  • 项目详情

    思路上面说的差不多了,要补充的是,我们使用HttpURLConnection来连接服务器(httpclient等开源项目或者工具应该也行),对于POST请求和GET请求稍微有些区别,这个网上也比较多,大家可以自行百度或者Google。然后是代码,我把主要部分都贴出来,可能代码会有些不规范的地方,大家也可以指出,项目结构比较简单,代码也不长,直接主函数里面就执行完了所有的内容,新人拿来练手也不错。

项目整体的结构:

      1.主类部分

 


   
   
   
   
  1. package main; /**
  2. * @Author tang_zhen
  3. * @Date 2018/3/7
  4. * @Description
  5. */
  6. import model.DataBase;
  7. import com.google.gson.JsonObject;
  8. import service.dao.DBUtils;
  9. import service.MyParse;
  10. import java.io.*;
  11. import java.net.HttpURLConnection;
  12. import java.net.URL;
  13. import java.sql.Connection;
  14. import java.sql.PreparedStatement;
  15. import java.sql.ResultSet;
  16. import java.sql.SQLException;
  17. import java.util.List;
  18. public class GetData {
  19. /**
  20. * 发起http请求并获取结果
  21. * @param requestUrl 请求地址
  22. */
  23. public static String getRequest(String requestUrl){
  24. String res="";
  25. // JsonObject object = null;
  26. StringBuffer buffer = new StringBuffer();
  27. try{
  28. URL url = new URL(requestUrl);
  29. //打开连接
  30. HttpURLConnection urlCon= (HttpURLConnection)url.openConnection();
  31. if(200==urlCon.getResponseCode()){//连接正常,获取输入流
  32. InputStream is = urlCon.getInputStream();
  33. InputStreamReader isr = new InputStreamReader(is,"GBK");
  34. BufferedReader br = new BufferedReader(isr);
  35. String str = null;
  36. while((str = br.readLine())!=null){
  37. buffer.append(str);
  38. }
  39. //关闭流
  40. br.close();
  41. isr.close();
  42. is.close();
  43. res = buffer.toString();
  44. //如果是json数据可以这样解析然后返回JsonObject类型的对象
  45. // JsonParser parse =new JsonParser();
  46. // JsonObject res2 = (JsonObject)parse.parse(buffer.toString());
  47. }
  48. }catch(IOException e){
  49. e.printStackTrace();
  50. }
  51. return res;
  52. }
  53. public static String postDownloadRes(String path,String post){
  54. URL url = null;
  55. try {
  56. url = new URL(path);
  57. HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
  58. httpURLConnection.setRequestMethod("POST");// Post请求
  59. // conn.setConnectTimeout(10000);//连接超时 单位毫秒
  60. // conn.setReadTimeout(2000);//读取超时 单位毫秒
  61. // POST需设置如下两行
  62. httpURLConnection.setDoOutput(true);
  63. httpURLConnection.setDoInput(true);
  64. // 获取URLConnection对象对应的输出流
  65. PrintWriter printWriter = new PrintWriter(httpURLConnection.getOutputStream());
  66. // 发送请求参数(post请求的参数一般可以从浏览器里查请求的时候看到参数是哪些)
  67. printWriter.write(post);//post的参数 形式为xx=xx&yy=yy
  68. // flush输出流的缓冲
  69. printWriter.flush();
  70. //开始获取数据
  71. BufferedInputStream bis = new BufferedInputStream(httpURLConnection.getInputStream());
  72. ByteArrayOutputStream bos = new ByteArrayOutputStream();
  73. int len;
  74. byte[] arr = new byte[1024];
  75. while((len=bis.read(arr))!= -1){
  76. bos.write(arr,0,len);
  77. bos.flush();
  78. }
  79. bos.close();
  80. //如果是json数据可以这样解析然后返回JsonObject类型的对象
  81. // JsonParser parse =new JsonParser();
  82. // JsonObject res2 = (JsonObject)parse.parse(bos.toString("utf-8"));
  83. return bos.toString("utf-8");
  84. } catch (Exception e) {
  85. e.printStackTrace();
  86. }
  87. return null;
  88. }
  89. //测试
  90. public static void main(String args [] ) {
  91. // JsonObject res = null;
  92. String responseStr = null;
  93. JsonObject res2 = null;
  94. // for (int k=1;k <=631;k++) {
  95. // //获取某一页的数据可以根据“nextPageNo=*”来指定,就是字符串拼接下,把1换成n
  96.       String str = "gyEntcardprint.cardid=&gyEntcardprint.name=&pageModel.nextPageNo="+1+"&pageModel.pageSize=12&cardtype=103";
  97. responseStr = postDownloadRes("wssb/websearch/SearchCardAction.do?operate=searchGyEntCard&operPage=card_spscxkz_list&cardtype=103&pageModel.afreshQuery=true", str);
  98. //System.out.println(responseStr);
  99. try {
  100.          //第一个页面中拿到的是一个列表,是一页的数据
  101. List<DataBase> list = MyParse.getData(responseStr);
  102. for (DataBase dataBase : list) {
  103. String str1 = "wssb/websearch/SearchCardAction.do?operate=viewGyEntCard&operPage=card_spscxkz_view&recid="+dataBase.getId();
  104. String responseStr1 = getRequest(str1);
  105. System.out.println(responseStr1);
  106. MyParse.getTotalData(responseStr1,dataBase.getId());
  107. //存id和公司名到第一张表里
  108. // insert(dataBase);
  109. }
  110. } catch (Exception e) {
  111. e.printStackTrace();
  112. }
  113. //这部分注释的是json解析的部分,之前访问过的网址返回的是json数据(嵌套了有多层)
  114. // JsonArray member = responseStr.getAsJsonArray("zsList");
  115. // for (int i = 0; i < member.size(); i++) {
  116. // JsonElement elements = member.get(i);
  117. // JsonElement name = elements.getAsJsonObject().get("name");
  118. // JsonElement id = elements.getAsJsonObject().get("id");
  119. // DataBase db = new DataBase(id.toString(), name.toString());
  120. // System.out.println(name);
  121. // System.out.println(id);
  122. // insert(db);
  123. // }
  124. // }
  125. }
  126. }

主函数部分就是打开连接,模拟浏览器与服务器交互,接收从服务器返回的数据,再调用MyParse类中的getData等从数据中拿到想要的数据:

    2.解析部分:

 


   
   
   
   
  1. import model.DataBase;
  2. import org.jsoup.Jsoup;
  3. import org.jsoup.nodes.Document;
  4. import org.jsoup.nodes.Element;
  5. import org.jsoup.select.Elements;
  6. import service.dao.DBUtils;
  7. import java.util.ArrayList;
  8. import java.util.List;
  9. /**
  10. * @Author tang_zhen
  11. * @Date 2018/3/8
  12. * @Description
  13. */
  14. public class MyParse {
  15. /**
  16. * 获取每个公司的id和姓名
  17. * @param html
  18. * @return
  19. * @throws Exception
  20. */
  21. public static List <DataBase> getData(String html) throws Exception {
  22. //获取的数据,存放在集合中
  23. List <DataBase> data = new ArrayList <DataBase>();
  24. //System.out.println(html.length()+"HTML长度");
  25. //System.out.println(html);
  26. //采用Jsoup解析
  27. Document doc = Jsoup.parse(html);
  28. //System.out.println(doc.text() + "doc内容");
  29. //获取html标签中的内容
  30. Elements elements = doc.select("tr[class=Items]");
  31. System.out.println(elements.size() + "****条");
  32. for (Element element:elements)
  33. {
  34. Element link = element.select("a").first();
  35. String text = element.select("a").get(1).text();
  36. String linkHref = link.attr("href");
  37. String[] newLinkHrefArray = linkHref.split("\'");
  38. String newLinkHref =newLinkHrefArray[1];
  39. // System.out.println(newLinkHref+"------"+text);
  40. DataBase dataBase = new DataBase();
  41. dataBase.setId(newLinkHref);
  42. dataBase.setName(text);
  43. data.add(dataBase);
  44. // insert(dataBase);
  45. }
  46. return data;
  47. }
  48. /**
  49. * 根据id获取弹窗上详细的数据
  50. * @param html
  51. * @param id
  52. */
  53. public static void getTotalData(String html,String id) {
  54. List <DataBase> data = new ArrayList <DataBase>();
  55. //System.out.println(html.length()+"HTML长度");
  56. //采用Jsoup解析
  57. Document doc = Jsoup.parse(html);
  58. //System.out.println(doc.text() + "doc内容");
  59. //获取html标签中的内容
  60. Elements elements = doc.select("tr");
  61. // System.out.println(elements.size() + "****条");
  62. DataBase dataBase=new DataBase();
  63. for (Element element:elements)
  64. { //jsoup的具体解析你们可以百度一下,根据内容不一样,获取的方式也不太一样,我这个内容算是比较简单的了,
  65. // 复杂的html内容获取某个数据,一行可能都写不下
  66. if(element.select("td").first().text().equals("生产者名称"))
  67. {
  68. String name = element.select("td").get(1).text();
  69. if (name==null) name="";
  70. dataBase.setName(name);
  71. }
  72. if(element.select("td").first().text().equals("住所"))
  73. {
  74. String homeAddress = element.select("td").get(1).text();
  75. if(homeAddress==null) homeAddress="";
  76. dataBase.setHomeAddress(homeAddress);
  77. }
  78. if(element.select("td").first().text().equals("生产地址"))
  79. {
  80. String address = element.select("td").get(1).text();
  81. if (address==null) address="";
  82. dataBase.setAddress(address);
  83. }
  84. if(element.select("td").first().text().equals("食品类别"))
  85. {
  86. String foodType = element.select("td").get(1).text();
  87. if(foodType==null) foodType="";
  88. dataBase.setFoodType(foodType);
  89. }
  90. if(element.select("td").first().text().equals("发证日期"))
  91. {
  92. String dateOfIssue = element.select("td").get(1).text();
  93. if (dateOfIssue==null) dateOfIssue="";
  94. dataBase.setDateOfIssue(dateOfIssue);
  95. //截止日期
  96. String cutOffDate = element.select("td").get(3).text();
  97. if(cutOffDate==null) cutOffDate="";
  98. dataBase.setCutOffData(cutOffDate);
  99. }
  100. dataBase.setId(id);
  101. }
  102. //将bean中的数据存入数据库
  103. DBUtils.insert(dataBase);
  104. }
  105. }

3.数据库部分

主要是jdbc的内容,大家应该也都会:

 


   
   
   
   
  1. import model.DataBase;
  2. import java.sql.*;
  3. /**
  4. * @Author tang_zhen
  5. * @Date 2018/3/7
  6. * @Description
  7. */
  8. public class DBUtils {
  9. // 获取数据库连接
  10. public static Connection getConnection() {
  11. Connection con = null;
  12. //定义连接相关属性
  13. final String DRIVER = "com.mysql.jdbc.Driver";
  14. final String URL = "jdbc:mysql://127.0.0.1:3306/tang?useUnicode=true&characterEncoding=utf-8&useSSL=false";
  15. final String USER = "root";
  16. final String PWD = "111111";
  17. try {
  18. Class.forName(DRIVER);
  19. con = DriverManager.getConnection(URL, USER, PWD);
  20. } catch (ClassNotFoundException e) {
  21. e.printStackTrace();
  22. } catch (SQLException e) {
  23. e.printStackTrace();
  24. }
  25. return con;
  26. }
  27. //
  28. public static void insert(DataBase od) {
  29. Connection con = DBUtils.getConnection();
  30. try {
  31. PreparedStatement stat = con.prepareStatement("select * from new_get_data where id= ? ");
  32. stat.setString(1, od.getId());
  33. ResultSet resultSet = stat.executeQuery();
  34. if (resultSet.next()) {
  35. con.close();
  36. return;
  37. }
  38. String sql = "insert into new_get_data (id,name,address,home_address,cut_off_date,date_of_issue,food_type) values(?,?,?,?,?,?,?);";
  39. // Connection con = DBUtils.getConnection();
  40. // 预编译
  41. PreparedStatement ps = con.prepareStatement(sql);
  42. ps.setString(1, od.getId());
  43. ps.setString(2, od.getName());
  44. ps.setString(3, od.getAddress());
  45. ps.setString(4, od.getHomeAddress());
  46. ps.setString(5, od.getCutOffData());
  47. ps.setString(6, od.getDateOfIssue());
  48. ps.setString(7, od.getFoodType());
  49. // 执行
  50. ps.execute();
  51. } catch (SQLException e) {
  52. e.printStackTrace();
  53. } finally {
  54. //图方便,关闭连接就不单独写个方法调用了
  55. try {
  56. con.close();
  57. } catch (SQLException e) {
  58. e.printStackTrace();
  59. }
  60. }
  61. }
  62. }

最后是效果:

上图是打印出来的服务器返回的html,下图是存入数据库的数据截图:

    另外,我觉得有必要贴一下返回的html的部分内容,这样如果想知道如何利用jsoup解析html的可以对照着html的结构看下我的代码是如何解析的:

 


   
   
   
   
  1. <table width="99%" border="0" cellpadding="0" cellspacing="1" id="wrap_1" align="center">
  2. <tr>
  3. <td class="td_content">
  4. <table width="100%" border="0" cellpadding="0" cellspacing="1" id="content">
  5. <tr>
  6. <td width="15%" class="bai_right">证书名称 td>
  7. <td class="bai_left" colspan="3"> <input type="hidden" name="cardtype" value="" id="cardnametype">
  8. 食品生产许可证
  9. td>
  10. tr>
  11. <tr>
  12. <td width="15%" class="bai_right">证书编号 td>
  13. <td class="bai_left" colspan="3" >QS4203 2401 0460 td>
  14. tr>
  15. <tr>
  16. <td width="15%" class="bai_right">生产者名称 td>
  17. <td class="bai_left" colspan="3"> 房县味味食品有限公司 td>
  18. tr>
  19. <tr>
  20. <td width="15%" class="bai_right">社会信用代码 td>
  21. <td width="35%" class="bai_left" >X1631412-7 td>
  22. <td width="15%" class="bai_right">法定代表人 td>
  23. <td width="35%" class="bai_left" >林益华 td>
  24. tr>
  25. <tr>
  26. <td width="15%" class="bai_right">住所 td>
  27. <td class="bai_left" colspan="3"> 湖北省十堰市房县红塔镇西城工业园高碑村四组 td>
  28. tr>
  29. <tr>
  30. <td width="15%" class="bai_right">生产地址 td>
  31. <td class="bai_left" colspan="3">湖北省十堰市房县红塔镇西城工业园高碑村四组 td>
  32. tr>
  33. <tr>
  34. <td width="15%" class="bai_right">食品类别 td>
  35. <td class="bai_left" colspan="3"> td>
  36. tr>
  37. <tr>
  38. <td width="15%" class="bai_right">日常监督管理机构 td>
  39. <td width="35%" class="bai_left"> td>
  40. <td width="15%" class="bai_right">日常监督管理人员 td>
  41. <td width="35%" class="bai_left"> td>
  42. tr>
  43. <tr>
  44. <td width="15%" class="bai_right">签发人 td>
  45. <td width="35%" class="bai_left"> td>
  46. <td width="15%" class="bai_right">发证机关 td>
  47. <td width="35%" class="bai_left"> td>
  48. tr>
  49. <tr>
  50. <td width="15%" class="bai_right">发证日期 td>
  51. <td width="35%" class="bai_left">2012-08-08 td>
  52. <td width="15%" class="bai_right">截止日期 td>
  53. <td width="35%" class="bai_left">2018-07-30 td>
  54. tr>
  55. table>
  • 项目总结

    其实整个项目就是个小程序,小爬虫,总体来说技术不算难,结构也很简单,大部分用到的技术和知识网上也挺多的,不管咋说,作为一个菜鸟程序猿,毕竟还是用自己所学的技术解决掉了一个比较现实的问题吧。然后就是,我发现代码跑起来是真的慢啊!爬一页数据,大概要两秒多?我猜问题主要是出在了数据库那,因为我是用完一个连接就直接关闭了,早知道慢成这样就用个数据库的连接池了.....

    PS:代码里面的URL都不是完整的,如果你们要实验,还是换个网站吧,这个毕竟是政府网站,虽然数据也是公开的数据,但是连接太多对服务器是有压力的,崩了就不好了,万一被查水表呢。还有就是有的网站可能会拒绝连接,有的防止类似的爬虫爬取页面的时候会做一些措施,多次连接IP会被拉黑。有啥问题直接留言,第一次写博客,大家多多包涵,之前都是在有道云笔记里记东西。

 

 

 

 

 

 

 

 

你可能感兴趣的:(22爬虫)