超能组合:python 的开发效率 + go 的并发 + shell 的短小精悍

发布时间 2023-10-06 14:11:27作者: 琴水玉

工具思维:利用合适的工具做合适的事情,然后合理地加以组合。

在”谈谈程序员应当具备的技术思维“一文中谈到了工具思维。本文对工具思维作一发挥运用。

批量下载图片

程序员总是有点”美图“爱好的。由于程序员通常又是比较”懒惰“的(可没有那个耐心和体力去一页页点开再点击按钮),那么,就会想到用程序来自动化拉取美图啦。

“写了一个下载图片和视频的python小工具” 一文中,给出了一个下载图片和视频的 python 程序。

譬如,使用如下命令:

python3 dw.py -u https://dp.pconline.com.cn/list/all_t601.html -c picLink -t img -s "#J-BigPic img"

可以下载 https://dp.pconline.com.cn/list/all_t601.html 这个页面的每一个入口点进去的第一个图片(经测试,这个程序还有点不太稳定,主要是拉取网页内容不太稳定)。

假设有多个这样的页面呢?比如也要下载 https://dp.pconline.com.cn/list/all_t204.html 里的图片?

我们可以把这些命令放在一个文件 cmds.sh 里

python3 dw.py -u https://dp.pconline.com.cn/list/all_t601.html -c picLink -t img -s "#J-BigPic img"
python3 dw.py -u https://dp.pconline.com.cn/list/all_t204.html -c picLink -t img -s "#J-BigPic img"

然后:

chmod +w cmds.sh
./cmds.sh

这样可以依次执行这两个命令。

不过这样只能利用单核,难以利用多核的优势(目前主流电脑的配置都是多核的)。好在,我们可以使用 Go 的并发进行组合。

并发批量下载图片

再写一个 Go 的程序,这个程序能够从指定文件里读取命令,并发执行这些命令:

go run multi_run.go -f cmds.sh 

multi_run.go

package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os/exec"
	"strings"
	"sync"
)

var wg sync.WaitGroup

func RunCmd(cmdstr string) {

	defer wg.Done()
	cmd := exec.Command("/bin/bash", "-c", cmdstr)

	output, err := cmd.CombinedOutput()
	if err != nil {
		fmt.Printf("Exec cmd failed. error: %s, output: %s", err.Error(), string(output))
		return
	}
	fmt.Printf("Exec cmd finished. %s", string(output))
}

func parse() *string {
	filePtr := flag.String("f", "", "cmd文件")

	// 解析命令行参数
	flag.Parse()

	return filePtr
}

func Read(filename string) string {
	f, err := ioutil.ReadFile(filename)
	if err != nil {
		fmt.Println("read fail", err)
	}
	return string(f)
}

func main() {

	filePtr := parse()

	filecontent := Read(*filePtr)
	lines := strings.Split(filecontent, "\n")
	lens := len(lines)

	for i := 0; i < lens; i++ {
		line := lines[i]
		if len(line) != 0 {
			wg.Add(1)
			go RunCmd(line)
		}
	}

	wg.Wait()

}

如果这样的命令很多,可以切割为多个文件:

python3 divide.py -f cmds.sh -n 2 -s '-'

divide.py

#!/usr/bin/python
#_*_encoding:utf-8_*_

import sys
import os
import argparse
import traceback
import math


def usage():

    usage_info = '''
        This program is used to divide lines of specified file into specified numbers of file.

        options:
              -f --file file need to divide 
              -n --number divide into N files
        eg.
             python3 divide.py -f cmds.txt -n 5 -s '-'
             python3 divide.py -f cmds.txt -n 5

    '''

    print(usage_info)


def parseArgs():
    try:
        description = '''This program is used to batch download pictures or videos from specified urls.
                                will search and download pictures or videos from network url by specified rules.
                      '''
        parser = argparse.ArgumentParser(description=description)
        parser.add_argument('-f','--file', help='file need to divide', required=True)
        parser.add_argument('-n','--number', help='divide into N', required=False)
        parser.add_argument('-s', '--separator', help='separator specified', required=False)
        args = parser.parse_args()
        print(args)
        file = args.file
        number = int(args.number)
        separator = args.separator
        print("%s %s %s" % (file, number, separator))
        return (file, number, separator)
    except:
        usage()
        traceback.print_exc()
        exit(1)


def divideNParts(total, N):
    '''
       divide [0, total) into N parts:
        return [(0, total/N), (total/N, 2M/N), ((N-1)*total/N, total)]
    '''

    each = math.floor(total / N)
    parts = []
    for index in range(N):
        begin = index*each
        if index == N-1:
            end = total
        else:
            end = begin + each
        parts.append((begin, end))
    print('divided into: %s' % str(parts))
    return parts

def get_file_name(file_path):
    file_name = os.path.basename(file_path)
    file_name_without_extension = os.path.splitext(file_name)[0]
    return file_name_without_extension

def get_file_ext(file_path):
    return os.path.splitext(file_path)[1]

if '__main__' == __name__:

    (file_path, number, separator) = parseArgs()
    print("file_path: %s number:%s separator: %s" %(file_path, number, separator))

    if not number:
        number = 10
    if not separator:
        separator = '_'

    lines = open(file_path)

    all_lines = []
    for line in lines:
        all_lines.append(line.strip())

    nparts = divideNParts(len(all_lines), number)

    count = 0
    file_prefix = get_file_name(file_path)
    for part in nparts:
        parts = all_lines[part[0]:part[1]]
        with open(file_prefix + separator + str(count)+ get_file_ext(file_path),"w") as fo:
            for o in parts:
                fo.write(o)
                fo.write('\n')
        count += 1

循环一下:

for i in {0..1} ; do go run multi_go.run -f cmds-${i}.sh ; done

这样,我们就把 python, shell, go 三者的优势组合起来了:

  • python: 语法简洁、开发效率高、库丰富;
  • go: 利用多核并发
  • shell: 短小精悍、连接多个程序组成完整功能。

这就是学习多语言的益处:可以充分组合多种语言各自的优势,来更好滴完成任务。

避免 Chrome 自动更新的最新方法

由于 dw.py 使用了 chromedriver ,chromedriver 的版本需要与 chrome 的版本保持一致。而 chrome 会时常更新,而 chromedriver 不会时常更新,因此需要禁用 chrome 更新。

在 Mac chrome 114 版本上,最新的方法如下(其它方式试过不可行了):

在终端:

cd /Library/Google/
sudo chown nobody:nogroup GoogleSoftwareUpdate
sudo chmod 000 GoogleSoftwareUpdate
cd ~/Library/Google/
sudo chown nobody:nogroup GoogleSoftwareUpdate
sudo chmod 000 GoogleSoftwareUpdate

然后对文件夹 Google 上一级执行相同的操作。

cd /Library/
sudo chown nobody:nogroup Google
sudo chmod 000 Google
cd ~/Library/
sudo chown nobody:nogroup Google
sudo chmod 000 Google