超能组合:python 的开发效率 + go 的并发 + shell 的短小精悍
工具思维:利用合适的工具做合适的事情,然后合理地加以组合。
在“谈谈程序员应当具备的技术思维”一文中谈到了工具思维。本文对工具思维作一发挥运用。
批量下载图片
程序员总是有点”美图“爱好的。由于程序员通常又是比较”懒惰“的(可没有那个耐心和体力去一页页点开再点击按钮),那么,就会想到用程序来自动化拉取美图啦。
在 “写了一个下载图片和视频的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