零、内容简介

将脚本用好能给工作带来事半功倍的效果,本文的内容主要涉及三个脚本工具:jsawkpython, 主题如下:

  • 使用 js 在浏览器控制台写爬虫。
  • 使用 shell 统计数据。
  • 使用 python 进行 redisrabbitmq 运维。
  • 使用 python 生成 mysql 数据字典

话不多说,直接进入主题。

一、使用 js 在浏览器控制台写爬虫

1.1 抓取数据

使用 js 在浏览器控制台写爬虫的优势是不需要模拟浏览器请求、登录信息、基本不会被反爬虫技术禁止访问等,劣势是数据不易保存,如果爬虫是一次性的,且抓取的数据不多,使用 js 可以在几分钟就写好爬虫,至于获取的数据可以采取复制为 json 的方式导出,json 数据可以再进一步转换为易于阅读的 excel 等数据,读完这个章节你将掌握如何把复杂 json 转换为 excel 平面数据。

下面以获取某网站 gitlab 合并请求数据为例讲解整个数据抓取流程。

需求是获取所有 gitlab 项目的合并请求数据,项目 id 列表事先已经知道,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var ids = [1, 2, 3]; // 项目id

var mrs = []; // 数据结果

function mr(idx) {
  if (idx < ids.length) {
    fetch(
      "https://xxx.com/gitlab/projects/" +
        ids[idx] +
        "/getMergeRequests?order_by=updated_at&page=1&per_page=100&state=all",
      { credentials: "same-origin" }
    )
      .then((res) => res.json())
      .then((r) => {
        if (r.status === "success") {
          mrs.push(...r.result.merge_requests);
        } else {
          console.log(ids[idx] + " failed");
        }
        mr(idx + 1); // 为了让请求串行执行,所以这里采用回调递归调用
      });
  } else {
    console.log("done");
  }
}

mr(0); // 开始抓取

短短几行代码就完成了数据的抓取,整个编码过程只需要 5 分钟左右。

抓取之后,数据都存储在 mrs 变量中,直接在控制台输入 mrs ,右键选择拷贝 json 数据,新建一个 mrs.json 文件将其保存起来即可。

1.2 转换数据

接下来将 json 数据转换为 excel。

方案 1,使用 pandas

1
2
import pandas
pandas.read_json("mrs.json").to_excel("mrs.xlsx")

如果数据结构是下面这类简单 json 使用这两行脚本即可。

1
2
3
4
5
6
[
  {
    "title": "标题",
    "content": "内容"
  }
]

若数据结构较复杂,列表对象中含有子对象,例如:

1
2
3
4
5
6
7
8
9
[
  {
    "title": "标题",
    "content": "内容",
    "detail": {
      "createBy": "张三"
    }
  }
]

此时需要编码展开子对象,我们这里使用方案 2。

方案 2,使用 excel 导入 + Power Query M 公式语言转换数据。

  1. 首先导入 JSON 数据。

excel导入

  1. 在 Power Query 的高级编辑器中,修改 Power Query M 公式语言 脚本展开 json 数据。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    let responseJson = Json.Document(File.Contents("D:\mrs.json")),
    headers =
    let
    allHeaders = List.Combine(List.Transform(responseJson, Record.FieldNames)),
    uniqueHeaders = List.Distinct(allHeaders)
    in
    uniqueHeaders,
    testTable = Table.FromRecords(responseJson, headers, MissingField.UseNull)
    in
    testTable
    

这样就完成了数据的转换,后续再生成数据图表或导入数据库都非常方便。

这部分内容的重点在于要灵活使用各种工具以快速得到想要的结果。

二、使用 shell 统计数据

需求:在上百个 Java 代码仓库中统计单元测试方法的数量。

我们以这个例子来看看 awk 的用法。

说明:这个统计方法只是简单查看单元测试方法数量,不涉及单元测试覆盖率等。

步骤如下:

  1. 首先本地含有所有的代码仓库,cd 到代码仓库根目录。

  2. 使用以下命令统计每个仓库的单元测试方法数量。

    1
    
    find . -name '*Test.java' | xargs grep -i '@Test[^a-z]' | awk -F / '{count[$3]++;} END {for(i in count) {print i,count[i]}}' | clip.exe

注意:该命令运行在 WSL,所以可以在管道命令中包含 windows 命令。

命令说明:

1
find . -name '*Test.java' | xargs grep -i '@Test[^a-z]'

找到所有 Test.java 结尾的文件,在文件中查找仅包含 @Test 的行,这一步的输出结果如下:

1
2
3
4
./project/repository1/module/src/.../xxx.java: @Test
./project/repository2/module/src/.../xxx.java: @Test
./project/repository3/module/src/.../xxx.java: @Test
...

根据这个数据模式,再使用 awk 编写脚本进行数据分析。

1
awk -F / '{count[$3]++;} END {for(i in count) {print i,count[i]}}'

awk 脚本中指定 / 为分隔符,第三位为仓库名称,使用仓库名称进行分组,最后输出仓库名称和分组数量。

输出结果:

1
2
3
4
repository1 5
repository2 10
repository3 7
...

最后我们利用管道 | clip.exe 将其拷贝到剪切板,再导入 excel 即可。

三、使用 python 进行 redis、rabbitmq 运维

3.1 扫描 redis

当 redis 内存过期速度赶不上内存增长,会导致 redis 内存占用越来越大,我们可以调整 redis 清理频率,也可以手动扫描 redis 来触发内存清理。

使用 python 执行以下代码即可,真是 人生苦短,我用 python

1
2
3
4
5
import redis
r = redis.Redis()
t = r.scan()
while t[0]:
    t = r.scan(t[0])

3.2 rabbitmq 消息队列转发

rabbitmq 上有消息队列转发的插件,但如果是云服务,通常无法使用该插件,此时用 python 写点转发脚本也是易事。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import pika

connection = pika.BlockingConnection(pika.ConnectionParameters(host='ip', port=5672, virtual_host='/', credentials=pika.PlainCredentials('account','password')))

channel = connection.channel()

def backcall(ch, method, properties, body):
    # 转发
    channel.basic_publish(exchange='exchange', routing_key='routing_key', body=body)

channel.basic_consume('原队列',backcall, True)

channel.start_consuming()
connection.close()

四、使用 python 生成 mysql 数据字典

生成数据字典的工具有很多,但往往生成的数据字典不易修改,试试用 python 生成 markdown 格式的数据字典如何?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import mysql.connector
import importlib
import sys


def generate(database_name):
    """
    生成数据库字典表
    """
    importlib.reload(sys)

    # 使用前修改配置
    conn = mysql.connector.connect(
        host='localhost',
        port='3306',
        user='',
        password='',
        use_pure=True
    )

    cursor = conn.cursor()

    cursor.execute(
        "SELECT TABLE_NAME, TABLE_COMMENT FROM information_schema.TABLES WHERE table_type='BASE TABLE' AND TABLE_SCHEMA='%s'" % database_name
    )

    tables = cursor.fetchall()

    markdown_table_header = """\n\n\n### %s (%s) \n| 序号 | 字段名称 | 数据类型 | 是否为空 | 字段说明 |\n| :--: |----| ---- | ---- | ---- |\n"""
    markdown_table_row = """| %s | %s | %s | %s | %s |"""

    f = open('dict/'+database_name + '.md', 'w', encoding="utf-8")

    for table in tables:

        cursor.execute(
            "SELECT ORDINAL_POSITION, COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_COMMENT "
            "FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='%s' AND TABLE_NAME='%s'" % (
                database_name, table[0]
            )
        )

        tmp_table = cursor.fetchall()
        p = markdown_table_header % (table[0], remove_newline(table[1]))
        for col in tmp_table:
            colf = list(col)
            colf[2]=col[2].decode() # mysql 高级版本需要解码,代码有点丑,临时性的,能用就行
            colf[4]=col[4].decode()
            p += (remove_newline(markdown_table_row % tuple(colf)) + "\n")
        print(p)
        f.writelines(p)

    f.close()
    cursor.close()
    conn.close()


def remove_newline(text):
    """
    去除文本中的换行符号
    """
    return text.replace("\r", "").replace("\n", "")


if __name__ == '__main__':
    conn = mysql.connector.connect(
        host='localhost',
        port='3306',
        user='',
        password='',
        use_pure=True
    )

    cursor = conn.cursor()

    cursor.execute("SHOW DATABASES");

    dbs = cursor.fetchall()

    for db in dbs:
        generate(db[0])

    cursor.close()
    conn.close()

五、后记

本篇文章内容比较杂,这些脚本不能说非常优美,但写起来确实是非常的高效,能够快速地完成需求,当我们把这些工具掌握的同时,偶尔发挥下想象力,必然能够体会到编程的乐趣和代码的魅力。