kotlin爬虫——利用lambda减少代码重用

kotlin爬虫——利用lambda减少代码重用

kotlin中很多的语句结构都是表达式,比如try{ … }catch(){ … }、if … else …在书写上比Java简单许多。Java的句法较为啰嗦,在制作爬虫程序时远不如Python和nodejs简单。
kotlin

在爬虫程序中,nodejs和Python有健全的库,如node的puppeteer模块,由谷歌开发,可以模拟chrome环境,可以执行事件,可以安装监听器,可以爬取动态网页。相比之下,Java就显得十分费力,在这方面的使用较为繁琐。kotlin吸收的许多语言的语法糖,而kotlin的lambda表达式有点类似Js的回调函数,使得写法上比Java更为简单,灵活,大大降低代码量。
puppeteer

kotlin很容易对异常进行处理,使得它尽可能不报错。因为这样的报错不是十分必要的,反而会终断程序.

同时,kotlin允许编写扩展函数,我们可以基于此写一写函数,使得原来的库更加好用。同时这写扩展函数和包内的函数使用起来没有任何区别。
这里,我爬取豆瓣图书Top250,只在列表界面进行爬取,不进入每一本书的页面。

读取和写入表格使用POI包,XSSFWorkBook对象可以读写xlsx格式文件。
表格文件层级关系是workbook>sheet>row>cell

首先,写表格的流程十分固定,创建WorkBook,传入输入流,向WorkBook写入数据,将WorkBook写入进文件的输出流。
于是将向workbook写入数据的过程用一个函数表示,我们只需补充这个函数数就行了.为了提高性能,将其定义为内联函数。

import org.apache.poi.ss.usermodel.CellType
import org.apache.poi.ss.usermodel.Sheet
import org.apache.poi.xssf.usermodel.XSSFSheet
import org.apache.poi.xssf.usermodel.XSSFWorkbook
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
inline fun writeXLSX(fileName: String, operateFile: (XSSFWorkbook) -> Unit) {
    
    
    //如果文件不存在,就创建一个新文件
    if (!File(fileName).exists()) {
    
    
        File(fileName).createNewFile()
    }
    val fIS = FileInputStream(fileName)
    //这里使用try{}catch{},如果输入流为空,就创建一个空的WorkBook
    val workbook = try {
    
    
        XSSFWorkbook(fIS)
    } catch (e: Exception) {
    
    
        XSSFWorkbook()
    }
    //这里调用lambda函数,可以对其自定义
    operateFile(workbook)
    val out = FileOutputStream(fileName)
    workbook.write(out)
    fIS.close()
    out.close()
    workbook.close()
}

这样当我们向表格写数据时,只需表格 的文件路径,和补充写数据的过程。

writeXLSX("D:\\IdeaProjects\\spider\\src\\main\\java\\org\\wang\\spider\\out.xlsx") {
    
     
//  这里写上对表格的数据,使用it便可以得到函数的自变量,一个XSSFWorkBook对象
  }

接下来,写一个扩展函数,可以向sheet中的一行默认从0开始写入一个数组,当然是可以通过关键字start修改的,isCreated表示是重新创建还是获取行

import org.apache.poi.ss.usermodel.CellType
import org.apache.poi.ss.usermodel.Sheet
import org.apache.poi.xssf.usermodel.XSSFSheet
import org.apache.poi.xssf.usermodel.XSSFWorkbook
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
fun Sheet.writeArray(data: Array<String>, row: Int, start: Int = 0, isCreate: Boolean = true) {
    
    
    val currentRow = if (isCreate) {
    
    
        createRow(row)
    } else {
    
    
        getRow(row)
    }
    for ((index, d) in data.withIndex()) {
    
    
        currentRow.createCell(index + start, CellType.STRING).setCellValue(d)
    }
}

然后,写一个函数用于创建sheet,即使sheet名字重复也不会报错

import org.apache.poi.ss.usermodel.CellType
import org.apache.poi.ss.usermodel.Sheet
import org.apache.poi.xssf.usermodel.XSSFSheet
import org.apache.poi.xssf.usermodel.XSSFWorkbook
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
fun XSSFWorkbook.creatSheetIfNotExit(name: String): XSSFSheet = try {
    
    
    getSheet(name)
} catch (e: IllegalArgumentException) {
    
    
    createSheet(name)
} catch (e: NullPointerException) {
    
    
    createSheet(name)
}

然后我们可以写一个函数用于测量程序运行的时间

import org.apache.poi.ss.usermodel.CellType
import org.apache.poi.ss.usermodel.Sheet
import org.apache.poi.xssf.usermodel.XSSFSheet
import org.apache.poi.xssf.usermodel.XSSFWorkbook
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
fun measureTime(operation: () -> Unit): Long {
    
    
    val t1 = System.currentTimeMillis()
    operation()
    val t2 = System.currentTimeMillis()
    return t2 - t1
}

我们可以看到,kotlin的lambda就像js的回调函数一样,使用起来十分方便

接下来,对豆瓣的网页解析。解析网页使用的是Jsoup包,这个包和js
的用法几乎完全相同,可以使用css3选择器,对元素进行精确的选择。同时对其中一些极为特殊的书不做处理了,比如圣经,论语
这里同样使用lambda,处理返回的每一项的结果:

关键部分:

<td valign="top">
            
            <div class="pl2">


              <a href="https://book.douban.com/subject/1007305/" onclick="&quot;moreurl(this,{i:'0'})&quot;" title="红楼梦">
                红楼梦

                
              </a>




              
            </div>

              <p class="pl">[清] 曹雪芹 著 / 人民文学出版社 / 1996-12 / 59.70元</p>

            

              
              <div class="star clearfix">
                  <span class="allstar50"></span>
                  <span class="rating_nums">9.6</span>

                <span class="pl">(
                    314870人评价
                )</span>
              </div>

            
              <p class="quote" style="margin: 10px 0; color: #666">
                  <span class="inq">都云作者痴,谁解其中味?</span>
              </p>

              

<span class="rr">


        <a name="1007305" class="j ll colbutt a_add2cart add2cart" href="javascript:;"><span><em>加入购书单</em></span></a>
  <span class="hidden">已在<a href="https://book.douban.com/cart">购书单</a></span>
</span><br class="clearfix">

              
                
                
  
  <span class="gact">
    
    <a href="/wish/197805944/update?add=1007305" name="sbtn-1007305-wish" class="j a_collect_btn" rel="nofollow">想读</a>
  </span>&nbsp;&nbsp;

                
                
  
  <span class="gact">
    
    <a href="/do/197805944/update?add=1007305" name="sbtn-1007305-do" class="j a_collect_btn" rel="nofollow">在读</a>
  </span>&nbsp;&nbsp;

                
                
  
  <span class="gact">
    
    <a href="/collection/197805944/update?add=1007305" name="sbtn-1007305-collect" class="j a_collect_btn" rel="nofollow">读过</a>
  </span>&nbsp;&nbsp;


          </td>
import org.jsoup.nodes.Document
inline fun parserDomOfDouban(dom: Document, callBack: (Array<String>, Int) -> Unit) {
    
    
    /*  result[0] : 中文名,
        result[1] :  原名,
        result[2] :  作者,
        result[3] :  译者,
        result[4] :  出版社,
        result[5] :  出版时间,
        result[6] :  定价,
        result[7] :  评分,
        result[8] :  评价人数,
        result[9] :  一句话简介,
 */
    val items = dom.select("tr.item td[valign=top]:nth-child(2)")
    items.mapIndexed {
    
     index, it ->
        val result = Array(10) {
    
     "" }

        val bookName = it.select("div.pl2")
        val cName = bookName.select("a").text()
        val fName = it.select("div.pl2 > span").text()
        result[0] = cName
        result[1] = fName

        val infos = it.select("p.pl")[0].text()
//        println(infos)
        val infosSplit = infos.split("/")
        when (infosSplit.size) {
    
    
            5 -> {
    
    
                result[2] = infosSplit[0]
                result[3] = infosSplit[1]
                result[4] = infosSplit[2]
                result[5] = infosSplit[3]
                result[6] = infosSplit[4]
            }
            4 -> {
    
    
                result[2] = infosSplit[0]
                result[4] = infosSplit[1]
                result[5] = infosSplit[2]
                result[6] = infosSplit[3]
            }
            6 -> {
    
    
                result[2] = infosSplit[0]
                result[3] = infosSplit[1]
                result[4] = infosSplit[2]
                result[5] = infosSplit[3]
                result[6] = infosSplit[4] + " ; " + infosSplit[5]
            }
        }
        result[7] = it.select("span.rating_nums").text()
        result[8] = it.select("div.star.clearfix span:nth-child(3)").text().substringBefore('人').substring(2)

        result[9] = it.select("span.inq").text()
        callBack(result, index)
    }
}

mian函数运行程序

fun main() {
    
    
    val t = measureTime {
    
    
        for (i in 0..9) {
    
    
            val baseUrl = "https://book.douban.com/top250?start=${
      
      i * 25}"
            val dom = Jsoup.connect(baseUrl).get()
            writeXLSX("D:\\IdeaProjects\\spider\\src\\main\\java\\org\\wang\\spider\\out.xlsx") {
    
     wb ->
                val sheet = wb.creatSheetIfNotExit("豆瓣图书Top250")
                val title = listOf(
                    "中文名", "原名", "作者", "译者", "出版社", "出版时间", "定价",
                    "评分", "评价人数", "一句话简介"
                ).toTypedArray()
                sheet.writeArray(title, 0)
                parserDomOfDouban(dom) {
    
     data, index ->
                    sheet.writeArray(data, index+i*25+1)
                }
            }
        }
    }
    println("花费时间为 $t ms")
}

然后就大功告成了

猜你喜欢

转载自blog.csdn.net/m0_47202518/article/details/112393163
今日推荐