欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

抓取网站数据入库详解,附图文(更新中)

程序员文章站 2022-05-17 10:28:21
...

抓取网站数据入库详解,附图文

一. 分析需求

1.1 需求分析

  • 刚好有这样一个需求,去抓取下方网站的页面全部数据,并存入MySQL数据库。
  • 这个页面为: 爬取页面
    • 年月日选择抓取网站数据入库详解,附图文(更新中)
    • 出生于几点,性别: 男或者女 选择:
      抓取网站数据入库详解,附图文(更新中)
    • 选择年月日小时,性别后,跳转的页面(目标就是爬取此页面):
      抓取网站数据入库详解,附图文(更新中)

1.2 分析实现可行性

  • 经过对各个年份、月份、天、小时、男或女的点击后进入的页面发现如下特点:
    1. 页面数据是静态数据,并非从后端读取得到 (可考虑有哪些技术可以实现)
    2. 页面数据有固定的key:value属性,比如 生肖: 牛星座:双鱼座,且每个页面的key,value是固定的,简单来说,每个页面的key都是一样的,只是具体的value是根据年月日小时,性别会相应变动 (可考虑入库的时候,对数据库字段的定义)
    3. 页面的路径是有规律的。比如1950年1月1日0时,性别为女的,它的路径为:http://www.8gua.cn/huashengsuanming/1950/w-1950-1-1-0.html,所以分析出路径如下特点:
      1. 路径为: url/年/(男为m,女为w)性别-年-月-日-时.html组成;
      2. 可选出生的小时为:
        抓取网站数据入库详解,附图文(更新中)
        1. 页面路径与可选的小时有着一一对应的关系;

二. 分析技术

  • 解析静态页面,我们可以使用Jsoup来进行解析,它可以将页面中的元素内容加载为Document文档,我们可以操作指定;
    • Jsoup是什么? jsoup 是一款Java 的HTML解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,可通过DOM,CSS以及类似于jQuery的操作方法来取出和操作数据。
    • 通过Jsoup,我们可以访问指定的页面,抓取其中的内容,解析为文本(抓取数据文本)
  • 提取关键词key和value,我们可以使用正则表达式,将符合规则的数据截取出来(对数据文本的提取)
  • 期间还需要用到:
    1. 获取指定年、月有多少天的方法(对日期的处理)
    2. 获取指定范围内的集合(如1950年~2019年的集合,1到12月的集合等)

去实现一个功能时,先逐个分析用到哪些东西可以实现。由点到面,这样一个大的功能就可以落地了。

三. 业务流程梳理

  • 流程图:
    抓取网站数据入库详解,附图文(更新中)
  • 具体实现总结:我们先进行年月日时的遍历,然后选择男或女,这样能够获得1950年~2019年每一天中固定的那几个小时范围的男或女的数据;遍历最深层中写逻辑代码,在最里面写:
    1. 通过年月日时,男或女,以及分页等条件去拼接url
    2. 通过Jsoup获取指定url内的数据,将其主体内容转为文本,并过滤掉不需要的内容
    3. 通过正则表达式,将里面的数据进行提取,变为key:value形式;
    4. 将结果封装到对象中,然后存入数据库;

四. 实战代码

4.1 公共方法以及依赖的引入

  • 实战代码示例,文中代码是用Kotlin编写,与Java相差不大。
    1. 引入Jsoup依赖:
      	    compile group: 'org.jsoup', name: 'jsoup', version: '1.13.1'
      

      Java版本依赖可直接搜索jsoup ,去寻找Maven依赖即可

    2. 获取指定年、月下的最大天数:
      	 /**
           * 根据年 月 获取对应的月份 天数
           */
          fun getDaysByYearMonth(year: Int, month: Int): Int {
              val a = Calendar.getInstance()
              a[Calendar.YEAR] = year
              a[Calendar.MONTH] = month - 1
              a[Calendar.DATE] = 1
              a.roll(Calendar.DATE, -1)
              return a[Calendar.DATE]
          }
      
    3. 获取指定字符串范围内的数据(包含范围数据):
        		 /**
               * 获取从pre 开始 ,从post结束的字符串数据
               */
              fun parseTextAll(content: String, pre: String, post: String): String {
                  // 查找的字符串
                  //正则表达式
                  val pattern = "$pre(.*?)$post"; //Java正则表达式以括号分组,第一个括号表示以"(乙方):"开头,第三个括号表示以" "(空格)结尾,中间括号为目标值,
                  // 创建 Pattern 对象
                  val r = Pattern.compile(pattern);
                  // 创建 matcher 对象
                  val m = r.matcher(content);
                  while (m.find()) {
                      //  自动遍历打印所有结果   group方法打印捕获的组内容,以正则的括号角标从1开始计算,我们这里要第2个括号里的
                      //  值, 所以取 m.group(2), m.group(0)取整个表达式的值,如果越界取m.group(4),则抛出异常
                      return m.group(0)
                  }
                  return ""
              }
      
    4. 获取指定字符串范围内的数据(不包含范围数据):
      /**
       * 获取从pre 开始 ,从post结束的字符串数据,排除pre/post
       */
      fun parseText(content: String, pre: String, post: String): String {
          // 查找的字符串
          val pattern = "$pre(.*?)$post"
          // 创建 Pattern 对象
          val r = Pattern.compile(pattern);
          // 创建 matcher 对象
          val m = r.matcher(content);
          while (m.find()) {
              //  自动遍历打印所有结果   group方法打印捕获的组内容,以正则的括号角标从1开始计算,我们这里要第2个括号里的
              //  值, 所以取 m.group(2), m.group(0)取整个表达式的值,如果越界取m.group(4),则抛出异常
              return m.group(0).replace(pre, "").replace(post, "")
          }
          return ""
      }
      
    5. Jsoup根据指定url路径分析页面的文本内容:
      	  fun parseContent(urls: List<String>): String {
              val builder = StringBuilder()
              urls.forEach {
                  try {
                      val document: Document = Jsoup.parse(URL(it), 3 * 1000)
                      builder.append(document.getElementsByTag("p").text())
                  } catch (e: Exception) {
                  }
              }
              return builder.toString()
          }
      

      此处代码 document.getElementsByTag().text() 为获取指定标签名的文本数据。这里则为获取<p>标签内的全部内容;

    6. 根据指定范围的值获取范围内的集合:
      	fun getListByRange(startInt: Int, endInt: Int): List<Int>{
          val rangeList = mutableListOf<Int>()
          var startCount= startInt
          while (startCount <= endInt){
              rangeList.add(startCount++)
          }
          return rangeList
      }
      

4.2 数据库及存储相关的设计

  • 数据库设计如下:
    抓取网站数据入库详解,附图文(更新中)

    此处content_text为存储的全部内容的文本信息,因为数据量较大,且可能此字段使用率不高,博主暂时将其放弃,不为此字段赋值;

  • Java中的配置:
    • 我们使用的持久层框架为: Mybatis-plus
    • 数据库此表名称为:professional_letter
    • 创建的Mapper:
      	interface ProfessionalLetterMapper : BaseMapper<ProfessionalLetterEntity>
      
    • 创建的Entity:
      @TableName("professional_letter")
      class ProfessionalLetterEntity{
      
          @ApiModelProperty("主键id")
          var id: Long? = 0L
      
          @ApiModelProperty("所属年月日-时分秒,开始时间")
          var startTime: Date?=null
      
          @ApiModelProperty("所属年月日-时分秒,结束时间")
          var endTime: Date?=null
      
          @ApiModelProperty("性别")
          var sex: Short?=null
      
          @ApiModelProperty("标题")
          var title: String?=null
      
          @ApiModelProperty("阳历")
          var solarCalendar: String?=null
      
          @ApiModelProperty("农历")
          var lunarCalendar: String?=null
      
          @ApiModelProperty("节气")
          var solarTerms: String?=null
      
          @ApiModelProperty("星座")
          var constellation: String?= null
      
          @ApiModelProperty("十二生肖")
          var chineseZodiac: String?=null
      
          @ApiModelProperty("二十八星宿")
          var twentyEightNights: String?=null
      
          @ApiModelProperty("命主福元")
          var fortune: String?=null
      
          @ApiModelProperty("文本版内容")
          var contentText: String?=null
      
          @ApiModelProperty("json版本内容")
          var contentJson: String?=null
      
          @ApiModelProperty("胎元")
          var foetus: String?=null
      
          @ApiModelProperty("命宫")
          var mingGong: String?=null
      
          @ApiModelProperty("起大运周岁")
          var qiDaYun: String?=null
      }
      
    • 创建的ContentJson(用于保存解析后的全部字段数据)
      import com.sino.hardware.common.JsonSerializable
      import io.swagger.annotations.ApiModelProperty
      class ContentJson : JsonSerializable() {
          @ApiModelProperty("阳历")
          var solarCalendar: String? = null
      
          @ApiModelProperty("农历")
          var lunarCalendar: String? = null
      
          @ApiModelProperty("节气")
          var solarTerms: String? = null
      
          @ApiModelProperty("起大运周岁")
          var qiDaYun: String? = null
      
          @ApiModelProperty("星座")
          var constellation: String? = null
      
          @ApiModelProperty("十二生肖")
          var chineseZodiac: String? = null
      
          @ApiModelProperty("二十八星宿")
          var twentyEightNights: String? = null
      
          @ApiModelProperty("命主福元")
          var fortune: String? = null
      
          @ApiModelProperty("八字纳音")
          var baZiNaYin: String? = null
      
          @ApiModelProperty("排大运")
          var paiDaYun: String? = null
      
          @ApiModelProperty("排流年")
          var paiLiuNian: String? = null
      
          @ApiModelProperty("胎元")
          var foetus: String? = null
      
          @ApiModelProperty("命宫")
          var mingGong: String? = null
      
          @ApiModelProperty("终身卦")
          var zhongShenGua: String? = null
      
          @ApiModelProperty("吉神凶煞")
          var jiShenXiongSha: String? = null
      
          @ApiModelProperty("吉神凶煞提示")
          var jiShenXiongShaTiShi: String? = null
      
          @ApiModelProperty("命局生克制化")
          var mingJuShengKeZhiHua: String? = null
      
          @ApiModelProperty("日主综得分")
          var riZhuZhongDeiFen: String? = null
      
          @ApiModelProperty("日主综得分提示")
          var riZHuZhongDeiFenTiShi: String? = null
      
          @ApiModelProperty("三命通会论断")
          var sanMingTongHuiLunDuan: String? = null
      
          @ApiModelProperty("穷通宝鉴-调候用神参考")
          var qiongTongBaoJian: String? = null
      
          @ApiModelProperty("十神定位论断")
          var shiShenDingWeiLunDuan: String? = null
      
          @ApiModelProperty("八字重量")
          var baZiZhongLiang: String? = null
      
          @ApiModelProperty("八字重量提示")
          var baZiZhongLiangTiShi: String? = null
      
          @ApiModelProperty("命宫寓意")
          var mingGongYuYi: String? = null
      
          @ApiModelProperty("性格特征")
          var xingGeTeZheng: String? = null
      
          @ApiModelProperty("性格特征提示")
          var xingGeTeZhengTiShi: String? = null
      
          @ApiModelProperty("职业财运")
          var zhiYeCaiYun: String? = null
      
          @ApiModelProperty("功名官运")
          var gongMingGuanYun: String? = null
      
          @ApiModelProperty("婚姻择偶")
          var hunYingZeOu: String? = null
      
          @ApiModelProperty("配偶方向")
          var peiOuFangXiang: String? = null
      
          @ApiModelProperty("配偶方向提示:")
          var peiOuFangXiangTiShi: String? = null
      
          @ApiModelProperty("祖业遗产")
          var zuYeYiChan: String? = null
      
          @ApiModelProperty("体质健康")
          var tiZhiJianKang: String? = null
      
          @ApiModelProperty("体质健康提示")
          var tiZhiJianKangTiShi: String? = null
      
          @ApiModelProperty("有利选择")
          var youLiXuanZe: String? = null
      
          @ApiModelProperty("流年")
          var liuNianMap: Map<String,String>? = null
      
          @ApiModelProperty("起大运运势")
          var qiDaYunMap: Map<String,String>? = null
      
      }
      

4.3 核心代码

  1. 引入Mapper:
    @Autowired
    private lateinit var professionalLetterMapper: ProfessionalLetterMapper
    

五.

最后成功

抽口补上