前言

目前主要接触的爬虫开发主要有两种:

  1. 静态网页
  2. api

我司已经有静态网页的检测工具,不过随着 api 类爬虫日益增加,需要一个新的可以用来检测 api 变动的脚本.

这里我选择开发一个命令行工具,而非 web 平台服务.理由是前者更加 geek && cool.并且前者只要稍作改动,就可以很好地兼容后者.

我将其命名为 Mori Kokoro , 取自柯南的 毛利小五郎 . 就一个检测脚本而言,它的工作与侦探 🔍 相类似(发现坏家伙 😀).同时这个脚本算是我第一个开发的命令行脚本,无论是代码质量,还是功能实现,都缺乏信心,所以就以毛利为名.

Mori 项目地址

main

需求分析

核心需求

  1. 检测 api 是否仍然可用
  2. 检测 api 的格式是否有变化
  3. 检测 api 对应的位置是否有对应的值

隐藏需求

  1. 并发效率.需要检测的爬虫不是简单的十几个,如何在尽可能短的时间内完成大量的爬虫检测?
  2. 形成报告/发送邮件.每次的检测结果需要形成记录报告,固化结果,不过这并不是刚需.
  3. 可扩展性.可扩展性主要分为三个方面.
  • 参数扩展.主要体现在请求头的扩展
  • 反反爬虫扩展
  • 解密扩展
    某些网站的 api 并不是简单就能申请到数据,但是每个网站的反爬虫、加密都不一样,不存在通解的写法,这里作为脚本的开发者,需要提供对应的接口给使用者.

脚本执行流程

startinput url、regex、path、etc.status_code!=200error(status_code)==200path is exist?falseerror(path change)trueanchor(regex)falseerror(anchor)truestrict mode:check all itemsnomarl mode:only oneoutput resultend

脚本优化

对多个配置文件进行优化

不同的爬虫隶属的业务模块不同,笼统地使用一个配置文件在实际使用时还需要使用者手动再按照业务划分拆开检测结果,这属于”不必要的麻烦”.

所以将配置文件扩展为可以在命令行参数中指定.

发送邮件

仅仅生成报告还是不够,在企业中常常使用邮件来作为记录媒介.

通常不仅是脚本的执行者需要知道结果,业务的相关负责人都有这方面的需求.所以加入邮件模块.

email

发送邮件-优化

  1. 发送邮件的同时,将 xls 报告 attach
  2. 优化邮件内容,直接以 html-table 发送

使用 rich 美化输出结果

rich 是 python 知名的一个第三方输出模块.

通过该模块可以得到更好的 console output 体验.

开发过程中遇到的问题

xlwd 直接写入 stream

1. BytesStream 而非 StringStream

邮件和形成 xls 报告虽然是两种业务情况,但是在代码上存在一定的从属关系.

在将 xls 报告 attach 到邮件时,没有必要在本地生成该文件浪费存储空间.

xlwt write excel sheet on the fly 中,提到可以使用 StringIO 模块 ,不过在实际操作时,发现 xlwt 的 steam 并不是 String 流,而是 Bytes 流,所以代码改为如下的写法.

from io import BytesIO

fs = BytesIO()
workbook = xlwt.Workbook(encoding='utf-8')
workbook.save(fs)

2. getvalue() 而非 read()

在从 fs 中读取流数据时,有些网站教程的读取方式是使用 read().在实际测试过程中,read()仅能够取出初始化 stream 时的数据,而后期 write()的数据,无法读出.

在官方文档io — Core tools for working with streams中,使用的读取案例代码如下.

>>> b = io.BytesIO(b"abcdef")
>>> view = b.getbuffer()
>>> view[2:4] = b"56"
>>> b.getvalue()
b'ab56ef'

在改用 getvalue() 后,成功读出数据

rich 的 traceback

为了美化输出结果,使用了 rich 模块,将所有的 print 更改为 rich 的 print,在输出 dict 结果时,有更好的视觉体验.

这里就衍生除一个需求,对于运行出错的配置项,需要一个 traceback 来追踪错误的成因.

当配置文件中存在多个检测配置时,遇到错误就立刻抛出打印这种做法不利于我们锁定具体是那个配置除了问题.所以,这个 traceback 最好是存储在结果列表中,然后随着结果一个个输出.

但是 rich 的 traceback 并不是可以存储的对象.

try:
    do_something()
except:
    console.print_exception()

以上是调用代码.

在查阅源码之后,发现 exception 是先被捕获到一个 TraceBack 类中,然后以这个类作为对象,再调用 print 将其输出.

于是,提取出 TraceBack 类,将其暂存到结果中.在输出函数中,判断是否检测成功,如果失败,则输出 traceback 内容.

traceback

总结

第一次进行 python 的命令行脚本开发,要考虑的东西很多(需求分析、代码优化、修 bug、……),当然,完成之后的成就感让这一切都显得很值得.