python:實戰篇

Acegem發表於2022-06-22

python

實戰

§python 實戰篇

§1、python解壓

1. python解壓zip、7z

(1)python解壓zip:用自帶zipfile
(2)python 解壓7z:下載py7zr, 用aliyun映象
用法:

path_name = 'C:/p/test.zip'

# 寫法1. with自動關閉法。如同with開啟檔案自動關閉。
with zipfile.ZipFile(path_name) as zip_f:
	zip_f.extractall(path_name.rsplit('/', 1)[0]) # 解壓指定檔案路徑C:/p/test/下。可自定義
	# 如:zip_f.extractall("C:/AI/test/out/") 

# 寫法2: close()手動關閉法。
zip_f= zipfile.ZipFile(path_name)
zip_f.extractall("C:/AI/test/out/")  # 
zip_f.close()

例項:

import zipfile
import py7zr

def uncompress(path_name):
	suffix = path_name.rsplit('.', 1)[1]
	if suffix == 'zip':
		if zipfile.is_zipfile(path_name):
			try:
				with zipfile.ZipFile(path_name) as zip_f:
					zip_f.extractall(path_name.rsplit('/', 1)[0])
				# os.remove(path_name) # 解壓後刪除原壓縮檔案
			except Exception as e:
				print('Error when uncompress file! info: ', e)
				return False
			else:
				return True
		else:
			print('This is not a true zip file!)
			return False
	if suffix == '7z':
		if py7zr.is_7zfile(path_name):
			try:
				with py7zr.SevenZipFile(path_name, mode='r') as sevenZ_f:
					sevenZ_f.extractall(path_name.rsplit('/', 1)[0])
				# os.remove(path_name)
			except Exception as e:
				print('Error when uncompress file! info: ', e)
				return False
			else:
				return True
		else:
			print('This is not a true 7z file!')
			return False
			
			
if __name__ == '__main__':
	uncompress(path_name = 'C:/p/test.zip')

2. python 解壓rar

因為其他7z、zip等解壓演算法都是開源的,所以直接下包就可以解壓。
但rar未開源,故下載rarfile包後,還需要裝rar解壓軟體,然後去呼叫。

1)windows:

  • (i)pip install rarfile
    或者找官方下載本地壓縮包解壓後將 rarfile.py 放在C:\Anaconda3\Lib\site-packages下。
  • (ii)下載winrar軟體,裝好後。
    法(1):將unrar.exe賦值到專案執行根目錄下,我的是C:\Anaconda3下,這樣做的目的是rarfile.py 能呼叫到。
    所以下面
    法(2):將rarfile.py 中的UNRAR_TOOL = "unrar"改成你的winrar軟體安裝目錄,如我的改成UNRAR_TOOL = "C:/Program Files/WinRAR/UnRAR.exe",執行軟體名不區分大小寫,也可改成:
    UNRAR_TOOL = "C:/Program Files/WinRAR/unrar.exe"
    UNRAR_TOOL = "C:/Program Files/WinRAR/UnRAR"
    UNRAR_TOOL = "C:/Program Files/WinRAR/unrar"
    if rarfile.is_rarfile(path_name):
    	with rarfile.RarFile(path_name) as rf:
    		rf.extractall(path_name.rsplit('/', 1)[0])

此方法受overflow上的一篇回答啟發,參考原文:
在這裡插入圖片描述

2)linux:

(i)同樣,先將官方 rarfile.py 放在Anaconda3/Lib/site-packages下。(有的系統是anaconda3/lib/python3.7/site-packages
(ii)在https://www.rarlab.com/download.htm下載 64 位 的linux版winrar,因為現在大部分linux系統是64位,注意下載的一定是64位,否則報錯。
在這裡插入圖片描述解壓後,開啟rar資料夾,如下圖,將rar資料夾裡面的rar二進位制程式放在python執行的根目錄下。
在這裡插入圖片描述附1:終端執行which pythonwhereis python檢視python安裝目錄,即python執行根目錄
/home/你的使用者名稱/anaconda3/bin/python
附2:linux安裝winrar
將解壓後的rar資料夾存在任意位置,進入rar資料夾
輸入sudo makesudo make install
成功!有如下資訊說明已安裝:

mkdir -p /usr/local/bin
mkdir -p /usr/local/lib
cp rar unrar /usr/local/bin
cp rarfiles.lst /etc
cp default.sfx /usr/local/lib

(注:如果系統沒有安裝make命令,也可手動輸入上面的命令資訊,這些命令在解壓後的rar/makefile裡)
配圖:在這裡插入圖片描述測試:
輸入sudo rar會有以下資訊說明winrar安裝成功,可以使用:

RAR 5.91   Copyright (c) 1993-2020 Alexander Roshal   25 Jun 2020
Trial version             Type 'rar -?' for help

Usage:     rar <command> -<switch 1> -<switch N> <archive> <files...>
               <@listfiles...> <path_to_extract\>

<Commands>
  a             Add files to archive
  c             Add archive comment
  ch            Change archive parameters
  cw            Write archive comment to file

(。。。省略。。。)

使用:
(1)壓縮
將test目錄下的所有檔案壓縮成1.rar:
rar a 1.rar test/
將test目錄下的所有檔案及子目錄壓縮成1.rar:
rar -r a 1.rar test/ — r 表遞迴
a 表示 Add files to archive
(2)解壓
rar x 1.rar

§2、python執行Linux系統命令的3種方法

  1. os.system("命令")
  2. os.popen("命令")
  3. subprocess.call("命令")
    如下:
    在這裡插入圖片描述
    在這裡插入圖片描述

§3、python程式碼打包成exe安裝包

(cxfreeze法):適用於python3 — 推薦使用
關於cxfreeze的介紹和使用可參考官方手冊 https://cx-freeze.readthedocs.io/en/latest/distutils.html

安裝

pip install cx-freeze 安裝,安裝後輸入 cxfreeze -h進行檢測是否安裝成功。

打包

打包需要新建一個setup.py檔案存放打包的相關資訊。
例:
hello.py 檔案,程式碼如下:

import time
print("hello world")
time.sleep(2)

在hello.py同級目錄下新建一個setup.py檔案,程式碼如下:

# -*- coding: utf-8 -*-
from cx_Freeze import setup, Executable

executables = [
    Executable('hello.py')
]

setup(
    # 安裝在windows後,開啟控制皮膚-》程式-》程式和功能,會發現如下資訊。
    name = 'hello',
    version = '1.0', # 版本號
    author = 'Ace', # 釋出者。 可省略,若省略則釋出者為“未知”
    description = 'Sample cx_Freeze script',
    executables = executables
)

接下來便可打包了,有兩個方法:
法1:python setup.py build 打包成免安裝的exe檔案。
法2:python setup.py bdist_msi 打包成windows下可安裝的msi檔案。

加密

打包前可將程式碼加密,使用PyArmor法加密。PyArmor 是一個用於加密和保護 Python 指令碼的工具。
關於PyArmor的介紹和使用可參考官方手冊 https://pyarmor.readthedocs.io/zh/latest/index.html
輸入 pip install pyarmor 安裝,
obfuscate 命令引數用來加密指令碼的,輸入 pyarmor obfuscate hello.py加密 hello.py 檔案,然後 會生成 dist目錄,在dist目錄下會生成pytransform資料夾 以及 新的加密過的hello.py 檔案,同樣地,我們將setup.py放在這個加密後的hello.py下,輸入 python setup.py bdist_msi 即可打包成加密過的windows下可安裝的msi檔案。

【附】

除了cxfreeze法,還有(pyinstaller法):這個僅適用於python2
使用pyinstaller,這個是anaconda自帶,
開啟cmd命令列:
輸入pyinstaller -F test.py 即可將test.py檔案打包成exe,exe安裝包在當前目錄下的dist目錄中生成。

§4、windows下:py檔案引用其他py檔案

py檔案引用其他py檔案時,不同於linux,windows下有時會呼叫不到。
可以程式碼中將被引用的檔案新增到系統路徑裡, sys.path.append("路徑")為其新增路徑。
例子:
t1.py:

def A():
    print("sss")

t2.py:

import sys
sys.path.append("C:/test/t1.py") # 用左斜槓,或者兩個右斜槓\\, 避免轉義
import t1
def start():
    t1.A()
if __name__ == '__main__':
    start()

或者從工程根目錄下引用,
如工程目錄為C:/test,其下有t1.py和t2.py,程式碼同上,若想在t2中引用t1,
則在t2中:就得寫from test import t1或者 import test.t1

§5、python安裝tar.gz或whl包

  1. whl包:pip install 包名.whl
    2)tar.gz原始碼包:
    先解壓,tar -xzvf 包名.tar.gz,解壓後執行如下命令安裝:
    python setup.py install

§6、anaconda包遷移

將anaconda包遷移到其他磁碟或其他電腦,需要改裡面的一些配置資訊,否則會報錯。

1)配置資訊

將如下檔案中的舊的conda安裝路徑改成新的路徑(如/home/user/anaconda3改成/media/user/disk1/anaconda3):

#(1)conda 與 pip
anaconda3/bin/conda
anaconda3/bin/pip
anaconda3/bin/pip3

#(2)anaconda3/etc/profile.d/下的所有指令碼
anaconda3/etc/profile.d/conda.sh
anaconda3/etc/profile.d/conda.csh
anaconda3/etc/profile.d/conda.ksh   # 如果有這個檔案的話

注:改完 conda.sh要bash執行一下使生效,bash conda.sh,然後退出終端。

2)環境變數

vim ~/.bashrc,修改如下資訊:
export PATH="$PATH:/路徑/anaconda3/bin"
source ~/.bashrc使更改生效。

附:

如果有conda虛擬環境的,先檢視conda安裝的虛擬環境的所在路徑:
conda env list,然後同樣改相應的pip、pip3等。

注意:

這種遷移方法只能臨時解決大部分情況,最好還是重新安裝包。如有的需要啟動jupyter notebook,
在這裡插入圖片描述
開啟jupyter-run檔案,原路徑#!/home/user/anaconda3/bin/python這個就得需要改成新路徑#!/software/anaconda3/bin/python
在這裡插入圖片描述
類似這樣的檔案還有很多很多,無法全部手動改完。所以最好還是重新安裝anacoda包,再將需要的包拷貝過去。

§7、使用’utf-8’讀寫檔案。

— 解決UnicodeEncodeError: 'gbk' codec can't encode character '\u21b5' in position 90422: illegal multibyte sequence 問題

寫爬蟲時,將網路資料流寫入到本地檔案的時候,會遇到:UnicodeEncodeError: 'gbk' codec can't encode character '\u21b5' in position 90422: illegal multibyte sequence... 這個問題。我們使用了decode和encode,試遍了各種編碼,utf8,utf-8,gbk,gb2312等等,該有的編碼都試遍了,還是不好用。

原因:

windows下面,新檔案的預設編碼是gbk,
這樣的話,我們開啟檔案後,
f = open("out.html","w")
python直譯器會用gbk編碼去解析我們的網路資料流,然而此時網路資料流已經是decode過的unicode編碼,這樣的話就會導致解析不了,出現上述問題。

解決辦法:

改變winddows下目標檔案的編碼,開啟檔案:
f = open("out.html","w",encoding='utf-8')
其中 utf-8寫成utf8也可以哦。

§8、sys.stdout.flush()重新整理輸出 ---- “時時監控”

sys.stdout.flush()函式的作用是重新整理輸出

例子

import time
import sys

for i in range(5):
    print(i, end='')
    sys.stdout.flush() # 重新整理輸出。
    time.sleep(1)

這個程式本意是每隔一秒輸出一個數字,但是如果把這句話sys.stdout.flush()註釋的話,就只能等到程式執行完畢,螢幕上會一次性輸出01234
如果你加上sys.stdout.flush(),重新整理stdout,這樣就能每隔一秒輸出一個數字了。

典型應用(下載檔案進度條顯示)

import os
from six.moves import urllib
import sys

DATA_URL = 'http://www.python.org/ftp/python/2.7.5/Python-2.7.5.tar.bz2'
filename = DATA_URL.split('/')[-1]

def _loading(block_num, block_size, total_size):
    '''回撥函式(自定義)
       @block_num: 已經下載的資料塊數目
       @block_size: 每個資料塊的大小
       @total_size: 檔案總大小
    '''
    if total_size < 8192:
        sys.stdout.write('\r>> Downloading: 100.0%')
    else:
        percent = float(block_size * block_num) / float(total_size) * 100.0
        if percent > 100:
            percent = 100
        sys.stdout.write('\r>> Downloading: %0.1f%%' % percent) # 與print類似。輸出
    sys.stdout.flush()

# urllib.request.urlretrieve方法會返回一個包含兩個元素的元組(filename, headers),filename表示儲存到本地的路徑,header表示伺服器的響應頭。
filepath, _ = urllib.request.urlretrieve(url=DATA_URL, filename=filename, reporthook=_loading)
print() # 好習慣!呼叫完 _loading() 中的sys.stdout.write()記得要換行!否則:下一次sys.stdout.write()會覆蓋,下一次print會尾隨其後輸出

註釋: urllib.request.urlretrieve()函式

- 描述
  urllib.request.urlretrieve(url, filename=None, reporthook=None, data=None)

- 函式說明
  將URL表示的網路物件複製到本地檔案。如果URL指向本地檔案,則物件將不會被複制,除非提供檔名。返回一個元組()(filename,header),其中filename是可以找到物件的本地檔名,header是urlopen()返回的物件的info()方法(用於遠端物件)。第二個引數(如果存在)指定要複製到的檔案位置(如果沒有,該位置將是一個生成名稱的 tempfile)。第三個引數,如果存在,則是一個回撥函式,它將在建立網路連線時呼叫一次,並且在此後每個塊讀取後呼叫一次。這個回撥函式將傳遞三個引數;到目前為止傳輸的塊計數,以位元組為單位的塊大小,以及檔案的總大小。第四個引數可能是-1,在舊的FTP伺服器上,它不返回檔案大小以響應檢索請求。

- 引數說明
  url:外部或者本地url
  filename:指定了儲存到本地的路徑(如果未指定該引數,urllib會生成一個臨時檔案來儲存資料);
  reporthook:是一個回撥函式,當連線上伺服器、以及相應的資料塊傳輸完畢的時候會觸發該回撥。我們可以利用這個回撥函式來顯示當前的下載進度。
  data:指post到伺服器的資料。
- 返回值
  該方法返回一個包含兩個元素的元組(filename, headers),filename表示儲存到本地的路徑,header表示伺服器的響應頭。

§9、python列表和字典的相互轉換---- 再談談對zip的理解

9.1 列表轉換為字典

例1. 如何列表 l = [‘aaa’, ‘ddd’, ‘ccc’] 轉換為字典?
由於字典屬於key-val,不妨用預設key為0,1,2… 去對應列表l

key = [0, 1, 2]
l = ['aaa', 'ddd', 'ccc']
dict_from_l = dict(zip(k , l))
print(dict_from_l) 

"""
結果:{0: 'aaa', 1: 'ddd', 2: 'ccc'}
"""

例2. 如何將兩個列表 k = [‘key1’, ‘key2’, ‘key3’] 和 v = [‘val1’, ‘val2’, ‘val3’] 轉換為字典?(其中k為鍵,v為值)

k = ['key1', 'key2', 'key3']
v = ['val1', 'val2', 'val3']
dic = dict(zip(k, v))
print(dic)

"""
結果:{'key1': 'val1', 'key2': 'val2', 'key3': 'val3'}
"""

9.2 字典轉換為列表

分為兩種
1)將字典value值轉換為列表

dic = {'key1': 'val1', 'key2': 'val2', 'key3': 'val3'}
l = list(dic.values())
print(l)

"""
結果:['val1', 'val2', 'val3']
"""

2)將字典key鍵轉換為列表

dic = {'key1': 'val1', 'key2': 'val2', 'key3': 'val3'}
l = list(dic)
print(l)

"""
結果:['key1', 'key2', 'key3']
"""

9.3 對zip的理解

zip 相當於將 k, v兩個列表有順序的打包捆綁了。就像將兩條長盒綁了起來(有順序,如:一左一右)。
例:

    k = ['key1', 'key2', 'key3']
    v = ['val1', 'val2', 'val3']

	""" 1. zip 相當於將 k, v兩個列表有順序的打包捆綁了。就像將兩條長盒綁了起來(有順序,如:一左一右)"""
    for k, v in zip(k, v):
        print(k, v)

	""" 2. zip將k, v兩個列表打包捆綁後,一齊轉換成字典"""
    dic = dict(zip(k, v))
    print(dic)

§10、字典操作

10.1 追加元素

法一、鍵值對直接賦值追加
dic = {
    'k1':'v1',
    'k2':'v2',
}
# 根據鍵值對追加
dic['k3'] = 'v3'
print(dic)
"""
結果:
{'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
"""

# 根據鍵值對追加
dic['k4'] = 'v4'
print(dic)
"""
結果:
{'k1': 'v1', 'k2': 'v2', 'k3': 'v3', 'k4': 'v4'}
可看到,前面新增的一組元素k3-v3還在,說明是追加
"""
方法二:使用update方法
dic = {
    'k1':'v1',
    'k2':'v2',
}

elem1 = {
    'k3':'v3',
}
# 使用update方法追加一組元素
dic.update(elem1)
print(dic)
"""
結果:
{'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
"""

elem2 = {
    'k4':'v4',
    'k5':'v5'
}
# 使用update方法追加多組元素
dic.update(elem2)
print(dic)
"""
結果:
{'k1': 'v1', 'k2': 'v2', 'k3': 'v3', 'k4': 'v4', 'k5': 'v5'}
可看到,前面新增的一組元素k3-v3還在,說明是追加
"""

10.2 刪除元素

(1)刪除單個元素

方法一:使用del函式
dic = {
    'k1':'v1',
    'k2':'v2',
    'k3':'v3',
    'k4':'v4'
}
del[dic['k4']]
print(dic)
"""
結果:
{'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
"""

del[dic['k3']]
print(dic)
"""
結果:
{'k1': 'v1', 'k2': 'v2'}
可看到,前面刪除的一組元素k4-v4已不在,本次刪除的k3-v3也已成功刪除
"""
方法二:使用pop函式,並返回值
dic = {
    'k1':'v1',
    'k2':'v2',
    'k3':'v3',
    'k4':'v4'
}
current_elem_v = dic.pop('k4')
print(current_elem_v) # v4,返回刪除的k4鍵對應的值v4
print(dic) # {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
"""
結果:
v4
{'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
"""

dic.pop('k3')
print(dic)
"""
結果:
{'k1': 'v1', 'k2': 'v2'}
可看到,前面刪除的一組元素k4-v4已不在,本次刪除的k3-v3也已成功刪除
"""

(2)刪除所有元素(清空)

方法:clear函式:清空,刪除所有
dic = {
    'k1':'v1',
    'k2':'v2',
    'k3':'v3',
    'k4':'v4'
}
dic.clear()
print(dic) # {},為空
"""
結果:
{}
"""

§11、多程式和多執行緒應用場景

先了解下GIL(Global Interpreter Lock)全域性直譯器。
作用:管理執行緒的執行的。如同鑰匙,獲得GIL鑰匙的執行緒才能執行,會把其他執行緒上鎖,保證只有一個執行緒執行。
背景:在沒有GIL之前,一般,當啟用多執行緒會造成引用計數的競爭條件,從而導致記憶體出錯。為了避免,CPython引入了GIL來管理python執行緒的執行。即:Python執行緒的執行必須先競爭到GIL許可權才能執行。
結論:無論單核還是多核CPU,任意給定時刻,只有一個執行緒會被python直譯器執行。
(這也是在多核CPU上,python中的多執行緒有時效率並不高的根本原因。)
特點:當執行緒執行過程中遇到I/O處理時,該執行緒會暫時釋放鎖。

11.1 多程式 Process

適合CPU密集型:CPU需要大量的計算佔用大量的CPU資源。

例子:計數器count
def count(n):
    sum = 0
    while n > 0:
        sum += n
        n -= 1
    return sum

更適合多程式Process

11.2 多執行緒 Thread

適合I/O密集型:讀寫檔案、訪問DB、收發資料、網路連線等。
如:有3個執行緒要執行任務,執行緒1執行時獲得了GIL許可權,其他執行緒則一直在等待,但當遇到I/O處理時,執行緒1就會將GIL釋放了,從而執行緒2得到GIL,獲得執行許可權,執行緒2開始執行,如此反覆直到任務完成。

例子:延時器count

這裡同樣起名count,方便比較。

import time

def count(n):
    time.sleep(0.01)

11.3 多程式 Process和多執行緒 Thread對比

例子1、計數器count:
from multiprocessing import Process
from threading import Thread
from timeit import timeit
import time


# 計數器
def count(n):
    sum = 0
    while n > 0:
        sum += n
        n -= 1
    return sum

# 單執行緒方式
def test_normal():
    count(1000000)
    count(1000000)


# 多程式方式
def test_Process():
    t1 = Process(target=count, args=(1000000, ))
    t2 = Process(target=count, args=(1000000, ))
    t1.start()
    t2.start()
    t1.join()
    t2.join()

# 多執行緒方式
def test_Thread():
    t1 = Thread(target=count, args=(1000000, ))
    t2 = Thread(target=count, args=(1000000, ))
    t1.start()
    t2.start()
    t1.join()
    t2.join()


if __name__ == '__main__':
	# 下面timeit()專門測試效率的函式。
	# 用法:timeit('被測函式()', 'from 執行域 import 被測函式名', number=測試次數) # 測試number次,會取執行時間最小那次測試的值,以防止測試的時候被其他程式干擾時間不精準。
    print("test_normal:", timeit('test_normal()', 'from __main__ import test_normal', number=100))
    print("test_Process:", (timeit('test_Process', 'from __main__ import test_Process', number=100)))
    print("test_Thread", timeit('test_Thread', 'from __main__ import test_Thread', number=100))
例子2、延時器count:
from multiprocessing import Process
from threading import Thread
from timeit import timeit
import time


# 延時器
def count():
    time.sleep(0.01)

# 單執行緒方式
def test_normal():
    count()
    count()


# 多程式方式
def test_Process():
    t1 = Process(target=count, args=())
    t2 = Process(target=count, args=())
    t1.start()
    t2.start()
    t1.join()
    t2.join()

# 多執行緒方式
def test_Thread():
    t1 = Thread(target=count, args=())
    t2 = Thread(target=count, args=())
    t1.start()
    t2.start()
    t1.join()
    t2.join()


if __name__ == '__main__':
    print("test_normal:", timeit('test_normal()', 'from __main__ import test_normal', number=100))
    print("test_Process:", (timeit('test_Process', 'from __main__ import test_Process', number=100)))
    print("test_Thread", timeit('test_Thread', 'from __main__ import test_Thread', number=100))

§12、目錄操作

1) 獲取當前目錄

import os

current_dir = os.path.abspath('.') # 獲取當前目錄絕對路徑。== linux “pwd”

2) 遍歷當前目錄(常用!重要!)

實戰:遍歷某個資料夾,以便處理該資料夾下的所有子資料夾和子檔案。

import os

for root, dirs, files in os.walk('test_dir'):
    print(root) # 正在遍歷的資料夾(可以是,根資料夾/子資料夾)
    print(dirs) # 正在遍歷的資料夾,裡面的資料夾集合
    print(files) # 正在遍歷的資料夾,裡面的檔案集合
    print('------------------')

例如:
在這裡插入圖片描述
展開:
在這裡插入圖片描述
執行結果:

test_dir
['1', '2']
['readme1.md', 'readme2.md']
------------------
test_dir/1
[]
['1.txt', '2.txt']
------------------
test_dir/2
[]
['1.py', '2.py']
------------------

3) 列出當前目錄下的內容、刪除目錄、刪除檔案

實戰:處理某個目錄下所有子目錄和子檔案,其中,將空目錄刪除,將沒有字尾名的檔案刪除。

import os


def deal(input_dir):
		#  列出當前目錄下的內容
    dir_contents = os.listdir(input_dir)
    # 刪除空目錄
    if dir_contents == []:
        os.removedirs(input_dir)

    for i in dir_contents:
        path = os.path.join(input_dir, i)
        # 對於目錄。遞迴處理
        if os.path.isdir(path):
            deal(path)
        # 對於檔案。刪除無字尾名的檔案
        if os.path.isfile(path) and len(path.split('.', 1)) == 1:
            os.remove(path)


if __name__ == '__main__':
    input_dir = '/media/user/disk_1T/AI/test/test_dir'
    deal(input_dir)

§13、python語法糖測試函式執行時間

如下,先定義一個測試函式執行時間的timeit_test(func),其中func為被測函式;然後寫一個測試函式test(),要想知道test()函式的執行時間,只需要在定義函式的頂部加上前面定義好的函式@timeit_test,這樣當呼叫test()函式的時間,就會自動呼叫這個函式timeit_test,而且是在test()函式執行完以後自動呼叫這個函式timeit_test。這種寫法被稱為python語法糖。這種功能可以很方便的用在函式執行效率測試上!

import time


def timeit_test(func):
    """
    測試func函式的執行時間
    :param func: 被測函式
    :return: 函式wrapper
    """

    #----- 先定義函式wrapper -----#
    def wrapper(*args, **kwargs):
        """

        :param args: 元組。所有傳入的實參無賦值的,如a,b 會整合到元組(a,b)
        :param kwargs: 字典。所有傳入的實參有賦值的,如c=1, d=2 會整合到字典{"c":1, "d":2}
        :return:
        """

        """
        # time.time()精度上相對沒有那麼高,而且受系統的影響,適合表示日期時間或者大程式程式的計時。
        # time.perf_counter()適合小一點的程式測試,經常用在測試程式碼時間上,精度高!具有最高的可用解析度。
        """
        start = time.perf_counter()
        func(*args, **kwargs)
        elapsed = time.perf_counter() - start
        print('Time used: ', elapsed)

    #----- 再呼叫函式wrapper -----#
    return wrapper



@timeit_test
def test():
    sum = 0
    for i in range(1000000):
        sum += i
    return sum


if __name__ == '__main__':
    test()
    """
    執行結果:
	Time used:  0.0685293
    """

§14、JSON檔案處理

常用來資料預處理如股票資料等。
下面程式碼摘自我業餘開發的程式中的部分程式碼。
未經處理的原json檔案資料為

{"rc":0,"rt":6,"svr":181669432,"lt":1,"full":1,"data":{"total":3781,"diff":[{"f2":55.15,"f12":"300792","f13":0,"f14":"N壹網"},{"f2":3.17,"f12":"002504","f13":0,"f14":"弘高創意"},{"f2":5.91,"f12":"002699","f13":0,"f14":"美盛文化"},{"f2":13.04,"f12":"600363","f13":1,"f14":"聯創光電"},{"f2":18.65,"f12":"603933","f13":1,"f14":"睿能科技"},{"f2":7.46,"f12":"002581","f13":0,"f14":"未名醫藥"},{"f2":8.23,"f12":"002152","f13":0,"f14":"廣電運通"},{"f2":9.66,"f12":"002326","f13":0,"f14":"永太科技"},{"f2":24.26,"f12":"300576","f13":0,"f14":"容大感光"},{"f2":21.52,"f12":"300380","f13":0,"f14":"安碩資訊"},
......(此處省略10000)
......

記錄下我的處理過程:
(i)clean_jsonData.py

import json

with open('all_stock_list_initData_bak.json', 'r', encoding='gbk') as fs:
    content = json.load(fs)

print(content)
core_info = content['data']['diff']

all_stock_dic = {}
for item in core_info:
    stock_code = item['f12']
    stock_name = item['f14']
    all_stock_dic[stock_code] = stock_name

print(all_stock_dic)

# # 儲存法1(不推薦):將dict轉化為str儲存到json檔案中
# all_stock_str = str(all_stock_dic)
# print(all_stock_str)
# with open('all_stock_list.json', 'w', encoding='utf8') as fs:
#     fs.write(all_stock_str)

# 儲存法2(推薦):將dict以json方式儲存到json檔案中
with open('../all_stock_list.json', 'w', encoding='utf8') as fs:
    """
    indent=4 為了格式化美觀。 
    ensure_ascii=False為了不讓中文變成ascii碼
    """
    fs.write(json.dumps(all_stock_dic, indent=4, ensure_ascii=False))

(ii)deal_jsonData.py

import json

with open('../all_stock_list.json', 'r', encoding='utf8') as fs:
    all_stock_dic = json.load(fs)

print(all_stock_dic)

# 將all_stock_dic的鍵值翻轉。存放在all_stock_reverse_dic
all_stock_reverse_dic = {}
for key in all_stock_dic:
    all_stock_reverse_dic[all_stock_dic[key]] = key

print(all_stock_reverse_dic)

# 儲存
with open('../all_stock_list_reverse.json', 'w', encoding='utf8') as fs:
    fs.write(json.dumps(all_stock_reverse_dic, indent=4, ensure_ascii=False))

§15、檢視python某個包的版本

如檢視selenium包的版本

法1(推薦)------ pip show 包名

終端直接輸入:

pip show selenium

法2 ------- py程式碼:help(包名)

終端輸入python進入python環境,輸入如下程式碼:

import selenium
help(selenium)

§16、爬蟲selenium新版本的語法變化

新版selenium如4.1.3+ 用法有變化。

實戰

先貼上一個我預設自動化訪問(爬取)百度的程式碼:

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from bs4 import BeautifulSoup as bs
import re
import urllib.parse # url中文處理
import random
import time

def get_headers():
    user_agents = ['Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1',
                   'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50',
                   'Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11',
                   "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50",
                   "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50",
                   "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:38.0) Gecko/20100101 Firefox/38.0",
                   "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; InfoPath.3; rv:11.0) like Gecko",
                   "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)",
                   "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)",
                   "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)",
                   "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)",
                   "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0.1) Gecko/20100101 Firefox/4.0.1",
                   "Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1",
                   "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.8.131 Version/11.11",
                   "Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11",
                   "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",
                   "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Maxthon 2.0)",
                   "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; TencentTraveler 4.0)",
                   "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)",
                   "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; The World)",
                   "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SE 2.X MetaSr 1.0; SE 2.X MetaSr 1.0; .NET CLR 2.0.50727; SE 2.X MetaSr 1.0)",
                   "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; 360SE)",
                   "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Avant Browser)",
                   "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)",
                   "Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5",
                   "Mozilla/5.0 (iPod; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5",
                   "Mozilla/5.0 (iPad; U; CPU OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5",
                   "Mozilla/5.0 (Linux; U; Android 2.3.7; en-us; Nexus One Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
                   "MQQBrowser/26 Mozilla/5.0 (Linux; U; Android 2.3.7; zh-cn; MB200 Build/GRJ22; CyanogenMod-7) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
                   "Opera/9.80 (Android 2.3.4; Linux; Opera Mobi/build-1107180945; U; en-GB) Presto/2.8.149 Version/11.10",
                   "Mozilla/5.0 (Linux; U; Android 3.0; en-us; Xoom Build/HRI39) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13",
                   "Mozilla/5.0 (BlackBerry; U; BlackBerry 9800; en) AppleWebKit/534.1+ (KHTML, like Gecko) Version/6.0.0.337 Mobile Safari/534.1+",
                   "Mozilla/5.0 (hp-tablet; Linux; hpwOS/3.0.0; U; en-US) AppleWebKit/534.6 (KHTML, like Gecko) wOSBrowser/233.70 Safari/534.6 TouchPad/1.0",
                   "Mozilla/5.0 (SymbianOS/9.4; Series60/5.0 NokiaN97-1/20.0.019; Profile/MIDP-2.1 Configuration/CLDC-1.1) AppleWebKit/525 (KHTML, like Gecko) BrowserNG/7.1.18124",
                   "Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; HTC; Titan)",
                   "UCWEB7.0.2.37/28/999",
                   "NOKIA5700/ UCWEB7.0.2.37/28/999",
                   "Openwave/ UCWEB7.0.2.37/28/999",
                   "Mozilla/4.0 (compatible; MSIE 6.0; ) Opera/UCWEB7.0.2.37/28/999",
                   "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20",
                   "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
                   "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; 360SE)",
                   "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SE 2.X MetaSr 1.0; SE 2.X MetaSr 1.0; .NET CLR 2.0.50727; SE 2.X MetaSr 1.0)",
                   "Mozilla/5.0 (MSIE 9.0; Windows NT 6.1; Trident/5.0)",
                   "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36",
                   "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
                   "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; WOW64; Trident/4.0; SLCC1)",
                   "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)",
                   "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; GTB7.0)",
                   "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.3; .NET4.0C; .NET4.0E)",
                   "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)"
                   ]
    header = random.choice(user_agents)
    return header


def auto_access_internet():

    header = get_headers()
    chrome_options = Options()
    # chrome_options.add_argument('--headless')
    chrome_options.add_argument('user-agent=' + header)
    driver = webdriver.Chrome(
        options=chrome_options,
        # 舊版用 chrome_options=chrome_options
        service=Service(executable_path=r'/software/anaconda3/lib/python3.7/site-packages/scripts/chromedriver'),
        # 舊版用 executable_path=r'/software/anaconda3/lib/python3.7/site-packages/scripts/chromedriver'
    )


    driver.get('https://www.baidu.com')
    time.sleep(3)
    driver.maximize_window()

    time.sleep(4)

    """
    新版selenium不支援PhantomJS了,可以安裝舊版selenium: pip uninstall selenium 再pip install selenium==2.48.0
    新版selenium不建議用find_element_by_id、find_element_by_class_name等,而用如下代替:
    • find_element(By.ID,”loginName”)
    • find_element(By.NAME,”SubjectName”)
    • find_element(By.CLASS_NAME,”u-btn-levred”)
    • find_element(By.TAG_NAME,”input”)
    • find_element(By.LINK_TEXT,”退出”)
    • find_element(By.PARTIAL_LINK_TEXT,”退”)
    • find_element(By.XPATH,”.//*[@id=’Title”)
    • find_element(By.CSS_SELECTOR,”[type=submit]”)
    """
    driver.find_element(By.ID, 'kw').send_keys('python')
    # 舊版用 driver.find_element_by_id('kw').send_keys('python')
    time.sleep(1)
    s = driver.find_element(By.ID, 'su')
    # 舊版用  s = driver.find_element_by_id('su')
    print(s)
    s.click()

    time.sleep(3)
    a = driver.find_element(By.CLASS_NAME, 'wbrjf67')
    # 舊版用  a = driver.find_element_by_class_name("wbrjf67")
    time.sleep(1)
    print(a)
    a.click()
    time.sleep(2)
    driver.quit()

if __name__ == '__main__':
    auto_access_internet()

1) 新版呼叫chrome用法有變化

例如:

from selenium.webdriver.chrome.service import Service


chrome_options = Options()
# chrome_options.add_argument('--headless')
chrome_options.add_argument('user-agent=' + header)
driver = webdriver.Chrome(
   options=chrome_options,
   # 舊版用 chrome_options=chrome_options
   service=Service(executable_path=r'/software/anaconda3/lib/python3.7/site-packages/scripts/chromedriver'),
   # 舊版用 executable_path=r'/software/anaconda3/lib/python3.7/site-packages/scripts/chromedriver'
)

完整程式碼:


2) 新版selenium不建議用find_element_by_id、find_element_by_class_name等,而用如下代替:

    • find_element(By.ID,”loginName”)
    • find_element(By.NAME,”SubjectName”)
    • find_element(By.CLASS_NAME,”u-btn-levred”)
    • find_element(By.TAG_NAME,”input”)
    • find_element(By.LINK_TEXT,”退出”)
    • find_element(By.PARTIAL_LINK_TEXT,”退”)
    • find_element(By.XPATH,”.//*[@id=’Title”)
    • find_element(By.CSS_SELECTOR,”[type=submit]”)

例如:

from selenium.webdriver.common.by import By
driver.find_element(By.ID, 'kw').send_keys('python')
# 舊版用 driver.find_element_by_id('kw').send_keys('python')

如果不想用selenium的新語法,在安裝的時候注意要選擇低版本,如:

pip uninstall selenium
pip install selenium==3.11.0

另外,新版selenium如4.1.3+ 不再支援PhantomJS了,當然還是一直推薦用谷歌chrome。

相關文章