2018年4月1日 星期日

Python自動化的樂趣學習筆記 (2)

  暨〈Python輕鬆上手學課堂筆記〉之後,這本由Al Sweigart著、H&C譯的《Python自動化的樂趣-搞定重複瑣碎&單調無聊的工作》(Automate the Boring Stuff with Python: Practical Programming for Total Beginners),非常適合初學者閱讀,使用Python打造各式客製化的程式小工具,以下為Timmy閱讀本書第1118章的學習筆記。

一、從Web擷取資訊

網頁擷取(Web Scraping)相關模組
l   Webbrowser
Python
內建,可開啟瀏覽器連到指定的網頁。
l   Requests
需下載安裝,可從網路上下載檔案和網頁。
l   BeautifulSoup
需下載安裝,可解析HTML的網頁編寫格式。
l   Selenium
需下載安裝,可啟動並控制Web瀏覽器、填寫表單,並模擬滑鼠游標在網頁中的點按動作。

Webbrowser模組
l   import webbrowser
webbrowser.open("http://inventwithpython.com/")

Requests模組
l   在命令提示字元輸入pip install requests,下載並安裝Requests模組。
l   使用requests.get()方法下載網頁,返回一個Response物件:
import requests
aRes = requests.get("https://automatetheboringstuff.com/files/rj.txt")
print(type(aRes))
à <class 'requests.models.Response'>
#
使用Response物件的status_code屬性檢查請求是否成功
print(aRes.status_code == requests.codes.ok)
à True
#
使用Response物件的text變數儲存下載網頁的字串,檢查字元長度
print(len(aRes.text))
à 174130
#
使用Response物件的text變數儲存下載網頁的字串,顯示前74個字元
print(aRes.text[:74])
à The Project Gutenberg EBook of Romeo and Juliet, by William Shakespeare
l   使用Response物件的raise_for_status()方法進行錯誤檢測:
import requests
#
如果下載網頁成功,什麼都不做
aRes = requests.get("https://automatetheboringstuff.com/files/rj.txt")
aRes.raise_for_status()
#
如果下載網頁失敗,會丟出例外
bRes = requests.get("https://automatetheboringstuff.com/ThisPageDoesNotExist")
bRes.raise_for_status()
à raise HTTPError
l   使用Response物件的iter_content()方法將下載的網頁寫入檔案之中:
import requests
aRes = requests.get("https://automatetheboringstuff.com/files/rj.txt")
aRes.raise_for_status()
#
第一個引數Romeo and Juliet.txt會儲存在目前工作目錄
#
第二個引數wb以二進位(Write Binary)模式來開啟檔案,目的是為了儲存Unicode編碼
fileTest = open("Romeo and Juliet.txt", "wb")
#
使用Response物件的iter_content()方法在迴圈中會返回磁碟區塊(Chunk),每一個磁碟區塊都是一個byte,常用100000,但欲下載的檔案原始大小若超過100000個位元組,下載會不完整
for chunk in aRes.iter_content(100000):
    fileTest.write(chunk)
fileTest.close()

BeautifulSoup模組
l   學習超文字標記語言HTML的資源:
HTML Beginner Tutorial
HTML & CSS
HTML - Learn webdevelopment
HTML基礎 - 學習該如何開發Web
使用requests模組下載網頁後,會取得HTML內容並存成字串值,因此必須學習檢視HTML原始碼、開啟瀏覽器開發人員工具,以及善用檢查HTML原始碼的功能,記住不要使用正規表示式來解析HTML,應該使用BeautifulSoup模組:http://stackoverflow.com/a/1732454/1893164/
l   本書範例檔example.htmlHTML原始碼如下:
<!-- This is the example.html file. -->

<html><head><title>The Website Title</title></head>
<body>
<p>Download my <strong>Python</strong> book from <a href="http://inventwithpython.com">my website</a>.</p>
<p class="slogan">Learn Python the easy way!</p>
<p>By <span id="author">Al Sweigart</span></p>
</body></html>
l   在命令提示字元輸入pip install beautifulsoup4,下載並安裝BeautifulSoup模組。
l   使用bs4.BeautifulSoup()方法解析HTML,返回一個BeautifulSoup物件:
import requests, bs4
#
解析網路上的網頁
aRes = requests.get("http://nostarch.com")
aRes.raise_for_status()
aBeaSou = bs4.BeautifulSoup(aRes.text, "html.parser")
print(type(aBeaSou))
à <class 'bs4.BeautifulSoup'>
#
解析從網路上下載的網頁,本書範例檔example.html在目前工作目錄
bRes = open("example.html")
bBeaSou = bs4.BeautifulSoup(bRes, "html.parser")
print(type(bBeaSou))
à <class 'bs4.BeautifulSoup'>
l   使用BeautifulSoup物件的select()方法尋找元素,返回一個Tag物件串列:
#
解析從網路上下載的網頁,本書範例檔example.html在目前工作目錄
import bs4
aRes = open("example.html")
aBeaSou = bs4.BeautifulSoup(aRes, "html.parser")
aTagLis = aBeaSou.select("span")
print(type(aTagLis))
à <class 'list'>
print(type(aTagLis[0]))
à <class 'bs4.element.Tag'>
#
在串列的Tag物件數目
print(len(aTagLis))
à 1
# Tag
物件的getText()方法可返回此元素的文字
print(aTagLis[0].getText())
à Al Sweigart
# Tag
物件可傳入str()函式轉成HTML的字串來顯示
print(str(aTagLis[0]))
à <span id="author">Al Sweigart</span>
# Tag
物件的get()方法可返回此元素的屬性,取得該屬性的值
print(aTagLis[0].get("id"))
à author
# Tag
物件的attrs屬性可將該Tag物件的所有HTML屬性當成字典
print(aTagLis[0].attrs)
à {'id': 'author'}
l   不同的選擇器模式可以組合運用,例如.select("p #author"),以下依選擇器模式的不同,顯示會找到的元素:
#
解析從網路上下載的網頁,本書範例檔example.html在目前工作目錄
import bs4
aRes = open("example.html")
aBeaSou = bs4.BeautifulSoup(aRes, "html.parser")
def selMode(mode):
    aTagLis = aBeaSou.select(mode)
    print(str(aTagLis[0]))
# .select("title")
會找到所有<title>元素
selMode("title")
à <title>The Website Title</title>
# .select("#author")
會找到所有id屬性為author的元素
selMode("#author")
à <span id="author">Al Sweigart</span>
# .select(".slogan")
會找到所有class類別為slogan的元素
selMode(".slogan")
à <p class="slogan">Learn Python the easy way!</p>
# .select("p strong")
會找到所有在<p>元素之內的<strong>元素
selMode("p strong")
à <strong>Python</strong>
# .select("p > strong")
會找到所有在<p>元素之內的<strong>元素,且中間沒有其它元素
selMode("p > strong")
à <strong>Python</strong>
# .select("span[id]")
會找到所有名為<span>且有id屬性的元素
selMode("span[id]")
à <span id="author">Al Sweigart</span>
# .select("span[id='author']")
會找到所有名為<span>id屬性為author的元素
selMode("span[id='author']")
à <span id="author">Al Sweigart</span>
l   BeautifulSoup模組相關內容:
Beautiful SoupDocumentation

Selenium模組
l   在命令提示字元輸入pip install selenium,下載並安裝Selenium模組。
l   以下以Google Chrome瀏覽器為例,下載chromedriver
ChromeDriver -WebDriver for Chrome
l   WebDriver物件與WebElement物件範例:
from selenium import webdriver
chromedriverPath = r"C:\Users\Timmy\Documents\Python\chromedriver.exe"
driver = webdriver.Chrome(chromedriverPath)
print(type(driver))
à <class 'selenium.webdriver.chrome.webdriver.WebDriver'>
driver.get("http://inventwithpython.com")
#
參考下列WebDriver物件方法說明
element = driver.find_element_by_class_name("display-3")
print(type(element))
à <class 'selenium.webdriver.remote.webelement.WebElement'>
#
參考下列WebElement物件屬性或方法說明
print(element.text)
à Learn to Program. For Free.
l   WebDriver物件方法說明-find_element_*find_elements_*
WebDriver
物件方法find_element_*find_elements_*皆返回WebElement物件,前者尋找比對符合的第一個元素,後者尋找比對符合的所有元素串列;如果找不到,會丟出NoSuchElement例外。
返回元素標籤為test的元素(只有此方法不分大小寫,"test""TEST"都可以比對<test>元素標籤)
.find_element_by_tag_name("test")
.find_elements_by_tag_name("test")
返回元素標籤為pclass屬性的值為test的元素:
.find_element_by_css_selector("p.test")
.find_elements_by_css_selector("p.test")
返回class屬性的值為test的元素:
.find_element_by_class_name("test")
.find_elements_by_class_name("test")
返回id屬性的值為test的元素:
.find_element_by_id("test")
.find_elements_by_id("test")
返回name屬性的值為test的元素:
.find_element_by_name("test")
.find_elements_by_name("test")
返回完全符合網頁文字test<a>元素:
.find_element_by_link_text("test")
.find_elements_by_link_text("test")
返回部分符合網頁文字test<a>元素:
.find_element_by_partial_link_text("test")
.find_elements_by_partial_link_text("test")
l   WebDriver物件方法說明-.back().forward().refresh().quit()
點按瀏覽器下一頁按鈕:
.back()
點按瀏覽器上一頁按鈕:
.forward()
點按瀏覽器重新整理按鈕:
.refresh()
點按瀏覽器關閉按鈕:
.quit()
l   WebElement物件屬性或方法說明-.tag_name.get_attribute().text.location.is_displayed()
from selenium import webdriver
chromedriverPath = r"C:\Users\Timmy\Documents\Python\chromedriver.exe"
driver = webdriver.Chrome(chromedriverPath)
driver.get("http://inventwithpython.com")
element = driver.find_element_by_link_text("Al's blog")
#
返回元素標籤字串
print(element.tag_name)
à a
#
返回元素屬性的值的字串
print(element.get_attribute("href"))
à http://inventwithpython.com/blog/
#
返回元素文字字串
print(element.text)
à Al's blog
#
返回元素位置座標
print(element.location)
à {'x': 151, 'y': 8152}
#
返回元素可見與否
print(element.is_displayed())
à True
l   WebElement物件屬性或方法說明-.send_keys().clear().submit().is_enabled()
from selenium import webdriver
chromedriverPath = r"C:\Users\Timmy\Documents\Python\chromedriver.exe"
driver = webdriver.Chrome(chromedriverPath)
driver.get("https://mail.yahoo.com")
#
以下方法要對文字欄位的<input><textarea>元素進行操作
element = driver.find_element_by_id("login-username")
#
填寫表單
element.send_keys("Hello World")
#
清除表單
element.clear()
#
提交表單
element.submit()
#
返回提交的表單啟用與否
print(element.is_enabled())
à True
l   WebElement物件屬性或方法說明-.click().is_selected()
from selenium import webdriver
chromedriverPath = r"C:\Users\Timmy\Documents\Python\chromedriver.exe"
driver = webdriver.Chrome(chromedriverPath)
driver.get("http://www.wibibi.com/info.php?tid=190")
#
以下方法要對選項按鈕欄位的<input>元素進行操作
element = driver.find_element_by_name("location")
#
點按頁面
element.click()
#
返回核取方塊或選項按鈕勾選與否
print(element.is_selected())
à True
l   使用selenium.webdriver.common.keys模組傳送鍵盤按鍵:
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
chromedriverPath = r"C:\Users\Timmy\Documents\Python\chromedriver.exe"
driver = webdriver.Chrome(chromedriverPath)
driver.get("https://nostarch.com/")
element = driver.find_element_by_tag_name("html")
#
參考下列Keys物件屬性說明
element.send_keys(Keys.END)
l   Keys物件屬性說明:
鍵盤的上下左右方向鍵:
Keys.UP, Keys.DOWN, Keys.LEFT, Keys.RIGHT
鍵盤的Enter按鍵和MacReturn按鍵功能相同:
Keys.ENTER, Keys.RETURN
鍵盤的Esc按鍵:
Keys.ESCAPE
其它與鍵盤按鍵名稱相同的Keys物件屬性:
Keys.TAB, Keys.F1, Keys.F2, …, Keys.F12, Keys.BACK_SPACE, Keys.DELETE, Keys.HOME, Keys.END, Keys.PAGE_UP, Keys.PAGE_DOWN
l   Selenium模組相關內容:
Selenium with Python

二、處理Excel試算表

試算表軟體

OpenPyXL模組:基本功能操作
l   在命令提示字元輸入pip install openpyxl,下載並安裝OpenPyXL模組。
l   開啟Excel檔與工作表:
import openpyxl
#
本書範例檔example.xlsx在目前工作目錄
#
Workbook物件代表Excel
aBook = openpyxl.load_workbook("example.xlsx")
print(type(aBook))
à <class 'openpyxl.workbook.workbook.Workbook'>
print(aBook.sheetnames)
à ['Sheet1', 'Sheet2', 'Sheet3']
#
Worksheet物件代表工作表
aSheet = aBook["Sheet3"]
print(type(aSheet))
à <class 'openpyxl.worksheet.worksheet.Worksheet'>
print(aSheet)
à <Worksheet "Sheet3">
print(aSheet.title)
à Sheet3
bSheet = aBook.active
print(bSheet)
à <Worksheet "Sheet1">
l   取得Excel檔工作表的儲存格:
import openpyxl
#
本書範例檔example.xlsx在目前工作目錄
aBook = openpyxl.load_workbook("example.xlsx")
aSheet = aBook["Sheet1"]
# Cell
物件取得儲存格的方法一
aCell = aSheet["B1"]
print(aCell)
à <Cell 'Sheet1'.B1>
print("Row " + str(aCell.row) + ", Column " + aCell.column + " is " + aCell.value)
à Row 1, Column B is Apples
print("Cell " + aCell.coordinate + " is " + aCell.value)
à Cell B1 is Apples
# Cell
物件取得儲存格的方法二
print(aSheet.cell(row = 1, column = 2))
à <Cell 'Sheet1'.B1>
print(aSheet.cell(row = 1, column = 2).value)
à Apples
# Worksheet
物件取得使用中的欄列範圍
print(aSheet.max_row)
à 7
print(aSheet.max_column)
à 3
l   取得Excel檔工作表的儲存格範圍:
import openpyxl
#
本書範例檔example.xlsx在目前工作目錄
aBook = openpyxl.load_workbook("example.xlsx")
aSheet = aBook["Sheet1"]
# Generator
物件含有矩形範圍的Cell物件
print(list(aSheet["A1":"B2"]))
à [(<Cell 'Sheet1'.A1>, <Cell 'Sheet1'.B1>), (<Cell 'Sheet1'.A2>, <Cell 'Sheet1'.B2>)]
#
印出Generator物件矩形範圍的所有Cell物件
for rowCell in aSheet["A1":"C2"]:
    for iCell in rowCell:
        print(iCell.coordinate, iCell.value)
    print("---End of Row---")
l   以欄列取得Excel檔工作表的儲存格範圍:
import openpyxl
#
本書範例檔example.xlsx在目前工作目錄
aBook = openpyxl.load_workbook("example.xlsx")
aSheet = aBook.active
# Generator
物件含有矩形範圍的Cell物件
print(list(aSheet.rows))
à [(<Cell 'Sheet1'.A1>, <Cell 'Sheet1'.B1>, <Cell 'Sheet1'.C1>), …, (<Cell 'Sheet1'.A7>, <Cell 'Sheet1'.B7>, <Cell 'Sheet1'.C7>)]
print(list(aSheet.columns)[1])
à (<Cell 'Sheet1'.B1>, …, <Cell 'Sheet1'.B7>)
#
印出Generator物件矩形範圍的所有Cell物件
for iCell in list(aSheet.columns)[1]:
    print(iCell.coordinate, iCell.value)
l   欄的字母和數字之間的轉換:
import openpyxl
from openpyxl.utils import get_column_letter, column_index_from_string
print(get_column_letter(27))
à AA
print(column_index_from_string("AA"))
à 27
l   建立並儲存Excel檔:
import openpyxl
aBook = openpyxl.Workbook()
print(aBook.sheetnames)
à ['Sheet']
aSheet = aBook.active
aSheet.title = "Hello World"
print(aBook.sheetnames)
à ['Hello World']
aBook.save("test.xlsx")
l   建立與刪除Excel檔工作表:
import openpyxl
aBook = openpyxl.Workbook()
print(aBook.sheetnames)
à ['Sheet']
aBook.create_sheet()
print(aBook.sheetnames)
à ['Sheet', 'Sheet1']
aBook.create_sheet(index = 0, title = "First Sheet")
print(aBook.sheetnames)
à ['First Sheet', 'Sheet', 'Sheet1']
del aBook["Sheet"]
print(aBook.sheetnames)
à ['First Sheet', 'Sheet1']
aBook.remove(aBook["Sheet1"])
print(aBook.sheetnames)
à ['First Sheet']
l   將值寫入Excel檔工作表的儲存格:
import openpyxl
aBook = openpyxl.Workbook()
aSheet = aBook.active
aSheet["A1"] = "Hello World"
print(aSheet["A1"].value)
à Hello World

OpenPyXL模組:視覺化功能操作
l   設定儲存格的字型:
import openpyxl
from openpyxl.styles import Font
aBook = openpyxl.Workbook()
aSheet = aBook.active
# Font
物件為字體Times New Roman且粗體,並指定給font屬性
aSheet["A1"].font = Font(name = "Times New Roman", bold = True)
aSheet["A1"] = "Hello World"
# Font
物件為大小24點且斜體,並指定給font屬性
aSheet["C3"].font = Font(size = 24, italic = True)
aSheet["C3"] = "Hello Poor World"
aBook.save("test.xlsx")
l   設定儲存格的列高和欄寬:
import openpyxl
aBook = openpyxl.Workbook()
aSheet = aBook.active
# row_dimensions
含有RowDimension物件,可設定為0409之間
aSheet.row_dimensions[1].height = 70
# column_dimensions
含有ColumnDimension物件,可設定為0255之間
aSheet.column_dimensions["C"].width = 50
aBook.save("test.xlsx")
bBook = openpyxl.Workbook()
bSheet = bBook.active
#
可設定隱藏列高
bSheet.row_dimensions[1].hidden = True
#
可設定隱藏欄寬
bSheet.column_dimensions["C"].hidden = True
bBook.save("test2.xlsx")
l   設定儲存格的合併和取消合併:
import openpyxl
aBook = openpyxl.Workbook()
aSheet = aBook.active
aSheet.merge_cells("A1:C3")
aSheet["A1"] = "Hello World"
aBook.save("test.xlsx")
bBook = openpyxl.load_workbook("test.xlsx")
bSheet = bBook.active
bSheet.unmerge_cells("A1:C3")
bBook.save("test2.xlsx")
l   凍結窗格:
import openpyxl
#
本書範例檔produceSales.xlsx在目前工作目錄
aBook = openpyxl.load_workbook("produceSales.xlsx")
aSheet = aBook.active
#
儲存格左方和上方將被凍結,儲存格本身不凍結
aSheet.freeze_panes = "A2"
aBook.save("test.xlsx")
bBook = openpyxl.load_workbook("test.xlsx")
bSheet = bBook.active
#
若要解除凍結窗格,傳入None"A1"皆可
bSheet.freeze_panes = None
bBook.save("test2.xlsx")
l   繪製長條圖、折線圖、圓餅圖:
import openpyxl
aBook = openpyxl.Workbook()
aSheet = aBook.active
for i in range(1, 11):
    aSheet["A" + str(i)] = i
# Reference
物件的5個引數分別為Worksheet物件、範圍區域的左上角儲存格欄列、範圍區域的右下角儲存格欄列,第二至第五引數可只填正整數
aRefer = openpyxl.chart.Reference(aSheet, min_col = 1, min_row = 1, max_col = 1, max_row = 10)
# Series
物件
aSerie = openpyxl.chart.Series(aRefer, title = "First Series")
# Chart
物件:長條圖
aChart = openpyxl.chart.BarChart()
aChart.title = "Bar Chart Title"
aChart.append(aSerie)
aSheet.add_chart(aChart, "C1")
# Chart
物件:折線圖
bChart = openpyxl.chart.LineChart()
bChart.title = "Line Chart Title"
bChart.append(aSerie)
aSheet.add_chart(bChart, "C15")
# Chart
物件:圓餅圖
cChart = openpyxl.chart.PieChart()
cChart.title = "Pie Chart Title"
cChart.append(aSerie)
aSheet.add_chart(cChart, "C29")
aBook.save("test.xlsx")
l   繪製散佈圖:
import openpyxl
aBook = openpyxl.Workbook()
aSheet = aBook.active
for i in range(1, 11):
    aSheet["A" + str(i)] = i
    aSheet["B" + str(i)] = i**2 + 5
aRefer = openpyxl.chart.Reference(aSheet, 2, 1, 2, 10)
bRefer = openpyxl.chart.Reference(aSheet, 1, 1, 1, 10)
aSerie = openpyxl.chart.Series(aRefer, bRefer, title = "First Series")
# Chart
物件:散佈圖
aChart = openpyxl.chart.ScatterChart()
aChart.title = "Scatter Chart Title"
aChart.append(aSerie)
aSheet.add_chart(aChart, "C1")
aBook.save("test.xlsx")

三、處理PDF與Word文件

PyPDF2模組
l   在命令提示字元輸入pip install PyPDF2,下載並安裝PyPDF2模組。
l   PDF檔擷取文字:
import PyPDF2
#
本書範例檔meetingminutes.pdf在目前工作目錄
aReader = PyPDF2.PdfFileReader(open("meetingminutes.pdf", "rb"))
#
印出PDF檔頁數
print(aReader.numPages)
à19
aPage = aReader.getPage(0)
#
印出PDF檔頁面文字
print(aPage.extractText())
l   解密PDF檔:
import PyPDF2
#
本書範例檔encrypted.pdf在目前工作目錄
aReader = PyPDF2.PdfFileReader(open("encrypted.pdf", "rb"))
print(aReader.isEncrypted)
à True
aReader.getPage(0)
à PdfReadError
#
傳入的密碼錯誤會顯示0,正確會顯示1
print(aReader.decrypt("rosebud"))
à 1
aReader.getPage(0)
l   複製頁面並建立新PDF檔:
import PyPDF2
#
本書範例檔meetingminutes.pdf在目前工作目錄
aReader = PyPDF2.PdfFileReader(open("meetingminutes.pdf", "rb"))
#
本書範例檔meetingminutes2.pdf在目前工作目錄
bReader = PyPDF2.PdfFileReader(open("meetingminutes2.pdf", "rb"))
aWriter = PyPDF2.PdfFileWriter()
# addPage
方法僅能在PDF檔尾端加入頁面
for i in range(aReader.numPages):
    aWriter.addPage(aReader.getPage(i))
for j in range(bReader.numPages):
    aWriter.addPage(bReader.getPage(i))
aFile = open("combinedmimutes.pdf", "wb")
aWriter.write(aFile)
aFile.close()
l   旋轉頁面並建立新PDF檔:
import PyPDF2
#
本書範例檔meetingminutes.pdf在目前工作目錄
aReader = PyPDF2.PdfFileReader(open("meetingminutes.pdf", "rb"))
aPage = aReader.getPage(0)
#
將第一頁順時針旋轉90
aPage.rotateClockwise(90)
bPage = aReader.getPage(1)
#
將第二頁逆時針旋轉90
bPage.rotateCounterClockwise(90)
aWriter = PyPDF2.PdfFileWriter()
aWriter.addPage(aPage)
aWriter.addPage(bPage)
aFile = open("rotatedPage.pdf", "wb")
aWriter.write(aFile)
aFile.close()
l   加入浮水印並建立新PDF檔:
import PyPDF2
#
本書範例檔meetingminutes.pdf在目前工作目錄
aReader = PyPDF2.PdfFileReader(open("meetingminutes.pdf", "rb"))
#
本書範例檔watermark.pdf在目前工作目錄
bReader = PyPDF2.PdfFileReader(open("watermark.pdf", "rb"))
aPage = aReader.getPage(0)
aPage.mergePage(bReader.getPage(0))
aWriter = PyPDF2.PdfFileWriter()
aWriter.addPage(aPage)
for i in range(1, aReader.numPages):
    aWriter.addPage(aReader.getPage(i))
aFile = open("watermarkedCover.pdf", "wb")
aWriter.write(aFile)
aFile.close()
l   加密檔案並建立新PDF檔:
import PyPDF2
aReader = PyPDF2.PdfFileReader(open("meetingminutes.pdf", "rb"))
aWriter = PyPDF2.PdfFileWriter()
for i in range(aReader.numPages):
    aWriter.addPage(aReader.getPage(i))
# encrypt()
方法的第一個引數是使用者密碼,第二個引數是擁有者密碼,如果只傳入一個引數,當成擁有者密碼
#
使用者密碼:僅限閱讀瀏覽這份PDF
#
擁有者密碼:有所有權限,能列印、加注解、擷取文字和其它功能
aWriter.encrypt("rosebud", "adminpassword")
aFile = open("encryptedminutes.pdf", "wb")
aWriter.write(aFile)
aFile.close()

文件軟體

Python-Docx模組:基本功能操作
l   在命令提示字元輸入pip install python-docx,下載並安裝Python-Docx模組。
l   docx檔的Document物件、Paragraph物件、Run物件:
import docx
# Document
物件代表整份文件
#
本書範例檔demo.docx在目前工作目錄
aDocx = docx.Document("demo.docx")
# Paragraph
物件代表文件中的段落
print(len(aDocx.paragraphs))
à 7
print(aDocx.paragraphs[0].text)
à Document Title
# Run
物件代表相同樣式連續鄰接的文字
print(len(aDocx.paragraphs[1].runs))
à 5
print(aDocx.paragraphs[1].runs[0].text)
à A plain paragraph with
l   docx檔擷取文字:
import docx
aDocx = docx.Document("demo.docx")
textList = []
for i in aDocx.paragraphs:
    textList.append(i.text)
textStr = "\n".join(textList)
print(textStr)
l   寫入文字、換行符號、分頁符號並建立新docx檔:
import docx
aDocx = docx.Document()
#
寫入文字
aDocx.add_paragraph("Hello World")
aDocx.add_paragraph("Hello Poor World")
#
新增換行符號
aDocx.paragraphs[0].runs[0].add_break()
#
新增分頁符號
aDocx.paragraphs[1].runs[0].add_break(docx.enum.text.WD_BREAK.PAGE)
aDocx.save("test.docx")
l   Python-Docx模組相關內容:
python-docx 0.8.6 documentation

Python-Docx模組:視覺化功能操作
l   設定ParagraphRun物件的樣式:
import docx
#
本書範例檔demo.docx在目前工作目錄
aDocx = docx.Document("demo.docx")
print(aDocx.paragraphs[0].text)
à Document Title
print(aDocx.paragraphs[0].style)
à _ParagraphStyle('Title') id: 98056432
#
更改Paragraph物件style屬性,參考下列段落樣式表格說明
aDocx.paragraphs[0].style = "Normal"
print(aDocx.paragraphs[1].runs[2].text)
à bold
#
更改Run物件屬性,參考下列字元樣式表格說明
aDocx.paragraphs[1].runs[2].underline = True
aDocx.save("test.docx")
l   段落樣式表格:
Normal           
內文                Body Text        本文
Caption           
書名                Heading 1        標題1
Intense Quote
鮮明引文        List                   清單
List Bullet               
項目符號        List Continue   接續
List Number   
清單號碼        List Paragraph 清單段落
MacroText      
巨集文字        No Spacing      無間距
Quote             
引文                Subtitle            副標題
TOC Heading  
書目                Title                 標題
l   字元樣式表格:
bold                
粗體                italic                        斜體
underline        
底線                strike               刪除線
double_strike 
雙刪除線        all_caps           首字大寫
shadow           
陰影                outline             外框
imprint           
內陰影            emboss            浮凸
small_caps     
首字大寫,小寫縮寫兩點
rtl                   
由右至左書寫
l   段落樣式、字元樣式與連結樣式:
Word
文件有3種類型的樣式,段落樣式可套入Paragraph物件,字元樣式可套入Run物件,連結樣式則都可套入這2種物件;如果對Run物件套入連結樣式,樣式名稱最後需要加上Char
import docx
#
本書範例檔demo.docx在目前工作目錄
aDocx = docx.Document("demo.docx")
print(aDocx.paragraphs[0].text)
à Document Title
aDocx.paragraphs[0].style = "Quote"
print(aDocx.paragraphs[1].runs[2].text)
à bold
aDocx.paragraphs[1].runs[2].style = "Quote Char"
aDocx.save("test.docx")
l   寫入文字與樣式並建立新docx檔:
import docx
aDocx = docx.Document()
#
整數0代表套入Title標題樣式
aDocx.add_heading("Header 0", 0)
#
整數14代表套入不同的標題層級樣式
aDocx.add_heading("Header 1", 1)
aDocx.add_paragraph("Hello World")
aPara = aDocx.add_paragraph("Hello Poor World", "Title")
aPara.add_run(", the Poor and Dispirited World", "Quote Char")
aDocx.save("test.docx")
l   新增圖片並建立新docx檔:
import docx
aDocx = docx.Document()
#
本書範例檔zophie.png在目前工作目錄
#
寬度設為1英寸,高度設為4公分,為選擇性引數
aDocx.add_picture("zophie.png", width = docx.shared.Inches(1), height = docx.shared.Cm(4))
aDocx.save("test.docx")
l   Python-Docx模組使用樣式相關內容:
python-docx0.8.6 documentation - Working with Styles

四、處理CSV檔和JSON資料

CSV(Comma-Separated Values)
l   CSV是純文字格式的試算表,一行代表一列,以逗號分隔資料。
l   Reader物件:
import csv
#
本書範例檔example.csv在目前工作目錄
aReader = csv.reader(open("example.csv"))
aData = list(aReader)
print(aData)
à [['4/5/2014 13:34', 'Apples', '73'], …
print(aData[0][1])
à Apples
# Reader
物件只能遍訪一次,若想要再一次讀取CSV檔,則需要再一次呼叫csv.reader來建立物件
bReader = csv.reader(open("example.csv"))
for i in bReader:
    print("Row " + str(bReader.line_num) + " " + str(i))
à Row 1 ['4/5/2014 13:34', 'Apples', '73']…
l   Writer物件:
import csv
aFile = open("test.csv", "w", newline = "")
aWriter = csv.writer(aFile)
aWriter.writerow(["Hello", "World"])
aWriter.writerow(["Hello", "Poor", "World", "10,000 dollars"])
aWriter.writerow([1, 2, 3.14, 4])
aFile.close()
l   Writer物件的delimiterlineterminator引數:
import csv
aFile = open("test.tsv", "w", newline = "")
#
分隔符號delimiter預設為逗號,更換為"\t"
#
行尾終止符號lineterminator預設為一個換行符號,更換為"XXX"
aWriter = csv.writer(aFile, delimiter = "\t", lineterminator = "XXX")
aWriter.writerow(["Hello", "World"])
aWriter.writerow(["Hello", "Poor", "World", "10,000 dollars"])
aWriter.writerow([1, 2, 3.14, 4])
aFile.close()

JSON(JavaScript Object Notation)
l   JSONJavaScript程式碼形式的純文字格式,應用在許多Web API之中。
l   JSON不能存放每一種Python值,其值只能是以下資料型別:字串、整數、浮點數、布林、串列、字典和NoneType
l   使用loads()函式讀取JSON
stringOfJsonData = '{"pokemon": "Porygon2", "beAbleToEvolve": true, "owned": 0, "gender": null}'
import json
pythonValue = json.loads(stringOfJsonData)
print(pythonValue)
à {'pokemon': 'Porygon2', 'beAbleToEvolve': True, 'owned': 0, 'gender': None}
l   使用dumps()函式寫入JSON
pythonValue = {'pokemon': 'Porygon2', 'beAbleToEvolve': True, 'owned': 0, 'gender': None}
import json
stringOfJsonData = json.dumps(pythonValue)
print(stringOfJsonData)
à {"pokemon": "Porygon2", "beAbleToEvolve": true, "owned": 0, "gender": null}

五、保持時間、工作排程和程式啟動

time模組
l   time.time()函式:
import time
# Unix
紀元(Unix Epoch)是程式設計中常用參考的時間,為世界標準時間(UTC)1970110點,time.time()函式返回值是Unix紀元起與time.time()函式被呼叫的那一刻之間的秒數
a = time.time()
print(a)
à 1520826796.570343
print(round(a, 2))
à 1520826796.57
print(round(a))
à 1520826797
l   time.sleep()函式:
import time
for i in range(3):
    print("Tick")
    #
程式暫停秒數,注意在IDLE中按下Ctrl-C鍵也不能中斷time.sleep()的呼叫,因此必須善用迴圈
    time.sleep(1)
    print("Tock")
    time.sleep(1)
l   延伸閱讀另一個可以解析程式碼的方法是利用cProfile.run()函式:
Python SoftwareFoundation

datetime模組
l   現在時間點與指定時間點:
import datetime
print(datetime.datetime.now())
à 2018-03-12 20:49:23.106381
a = datetime.datetime(2018, 6, 21, 12, 0, 0)
print(a.year, a.month, a.day, a.hour, a.minute, a.second)
à 2018 6 21 12 0 0
l   Unix紀元時間戳(Epoch Timestamp)轉換成datetime物件:
import datetime, time
print(datetime.datetime.fromtimestamp(time.time()))
à 2018-03-12 20:50:35.826073
l   一段時間:
import datetime
a = datetime.timedelta(weeks = 1, days = 1, hours = 1, minutes = 1, seconds = 1, milliseconds = 1, microseconds = 1)
# days
屬性計算weeksdays總和,seconds屬性計算hoursminutesseconds總和,microseconds屬性計算millisecondsmicroseconds總和
print(a.days, a.seconds, a.microseconds)
à 8 3661 1001
#
僅能以秒計算全部時間的長度
print(a.total_seconds())
à 694861.001001
print(a)
à 8 days, 1:01:01.001001
l   時間點與一段時間的運算:
import datetime
a = datetime.datetime.now()
print(a)
à 2018-03-13 08:28:51.926122
b = datetime.timedelta(days = 1000)
print(a + b)
à 2020-12-07 08:28:51.926122
l   暫停到某個指定時間點:
import datetime, time
a = datetime.datetime.now()
b = datetime.timedelta(days = 3)
while datetime.datetime.now() < a + b:
    time.sleep(1)
l   datetime物件轉換成字串:
import datetime
a = datetime.datetime(2018, 6, 21, 13, 2, 1)
# %Y
代表年;%y代表年
# %m
代表月;%B代表月;%b代表月
# %d
代表月的第幾日;%j代表年的第幾日
# %w
代表星期;%A代表星期;%a代表星期
print(a.strftime("%Y, %y, %m, %B, %b, %d, %j, %w, %A, %a"))
à 2018, 18, 06, June, Jun, 21, 172, 4, Thursday, Thu
# %H
代表24小時制;%I代表12小時制
# %M
代表分鐘
# %S
代表秒鐘
# %p
代表"AM""PM"%%代表"%"字元
print(a.strftime("%H, %I, %M, %S, %p, %%"))
à 13, 01, 02, 01, PM, %
l   字串轉換成datetime物件:
import datetime
print(datetime.datetime.strptime("June 21, 2018", "%B %d, %Y"))
à 2018-06-21 00:00:00
print(datetime.datetime.strptime("2018/06/21 13:02:01", "%Y/%m/%d %H:%M:%S"))
à 2018-06-21 13:02:01

threading模組
l   多執行緒(Multithreading)指同時執行著不同的程式碼行。
l   無傳入引數到執行緒的目標函式內:
import time, threading
print("Start of Program")
def takeANap():
    time.sleep(5)
    print("Wake Up")
aThread = threading.Thread(target = takeANap)
aThread.start()
print("End of Program")
à
Start of Program
End of Program
>>> Wake Up
l   有傳入引數到執行緒的目標函式內:
import threading
aThread = threading.Thread(target = print, args = ["Hello", "World"], kwargs = {"sep": " Poor "})
aThread.start()
à Hello Poor World
l   Thread物件的join()方法來定住,直到所有執行緒都完成才放行:
import time, threading
print("Start of Program")
def takeANap():
    time.sleep(5)
    print("Wake Up")
aThread = threading.Thread(target = takeANap)
bThread = threading.Thread(target = print, args = ["Hello", "World"], kwargs = {"sep": " Poor "})
aThread.start()
bThread.start()
aThread.join()
bThread.join()
print("End of Program")
à
Start of Program
Hello Poor World
Wake Up
End of Program

subprocess模組
l   Python啟動其他應用程式,每個處理程序可有多個執行緒,然而處理程序不像執行緒,不能直接讀寫另一個處理程序的變數。
l   Popen物件:
import subprocess
a = subprocess.Popen(r"C:\Windows\System32\calc.exe")
# Popen
物件的poll()方法,程式仍在執行返回None,程式無錯結束返回退出碼(Exit Code)0,程式出錯結束返回退出碼不為0(多為1)
print(a.poll() == None)
à True
# Popen
物件的wait()方法,等著執行完程式碼,再繼續執行其他程式碼,返回值是退出碼
print(a.wait())
à 0
print(a.poll())
à 0
l   Popen()傳入命令提示列引數開啟檔案:
import subprocess
subprocess.Popen([r"C:\Windows\notepad.exe", "test.txt"])
subprocess.Popen([r"C:\Python36\python.exe", "test.py"])
# shell = True
只在Windows系統中有需要用到
subprocess.Popen(["start", "test2.txt"], shell = True)
l   Unix的哲學:
Unix philosophy

六、發送Email和文字簡訊

SMTP負責對別人傳送Email
l   import smtplib
#
連接到SMTP伺服器,第一個引數是SMTP伺服器網域名稱,第二個引數是連接埠埠號
#
支援TLS埠號587smtpObj = smtplib.SMTP("smtp.example.com", 587)
#
支援SSL埠號465smtpObj = smtplib.SMTP_SSL("smtp.example.com", 465)
smtpObj = smtplib.SMTP_SSL("smtp.ntnu.edu.tw", 465)
#
傳送SMTP"Hello"訊息,返回值250代表傳送訊息成功
print(smtpObj.ehlo())
#
啟動TLS加密才需要使用,返回值220代表伺服器已準備好
# smtpObj.starttls()
#
登入SMTP伺服器,返回值235代表授權驗證成功
#
若是使用GmailGmailGoogle帳號提供一個附加的安全功能稱之為應用程式密碼(Application-Specific Passwords)
smtpObj.login("myAccount@ntnu.edu.tw", "myPassword")
#
傳送Email,以2個換行符號將主旨和郵件的內文分隔,返回的字典以鍵值對的形式,記載傳送失敗的每個收件人
smtpObj.sendmail("from@example.com", "to@example.com", "Subject: Test\n\nContent: Test")
#
切斷SMTP伺服器連線,返回值221代表連線結束
print(smtpObj.quit())

IMAP負責收回給您的Email
l   在命令提示字元輸入pip install imapclient,下載並安裝IMAPClient模組。
在命令提示字元輸入pip install pyzmail(若安裝失敗,改輸入easy_install pyzmail),下載並安裝PyzMail模組。
l   import imapclient, pprint
#
連接到IMAP伺服器,第一個引數是IMAP伺服器網域名稱,第二個引數是SSL加密
imapObj = imapclient.IMAPClient("imap.mail.yahoo.com", ssl = True)
#
登入IMAP伺服器
#
若是使用GmailGmailGoogle帳號提供一個附加的安全功能稱之為應用程式密碼(Application-Specific Passwords)
imapObj.login("myAccount@yahoo.com.tw", "myPassword")
pprint.pprint(imapObj.list_folders())
#
選取資料夾,readonly = True以唯讀防止任何更動Email資料夾的動作
imapObj.select_folder("Inbox", readonly = True)
#
執行尋找,字串串列為IMAP搜尋鍵(Search Key),參考下列說明
#
若是使用Gmail,可使用Gmail網頁上方的搜尋列:UIDs = imapObj.gmail_search("Test")
UIDs = imapObj.search(["SINCE", "21-Jun-2017", "BEFORE", "21-Jun-2018"])
pprint.pprint(UIDs)
#
收取Email,第一個引數是UID串列,第二個引數是所有內文
rawMessages = imapObj.fetch([8755], [b"BODY[]"])
#
從原始訊息中取得Email地址
import pyzmail
message = pyzmail.PyzMessage.factory(rawMessages[8755][b"BODY[]"])
print(message.get_subject())
print(message.get_addresses("from"))
print(message.get_addresses("to"))
print(message.get_addresses("cc"))
print(message.get_addresses("bcc"))
#
從原始訊息中取得內文,如果Email是純文字格式,PzyMessage物件會把html_part屬性設成None
print(message.text_part != None)
print(message.text_part.get_payload().decode(message.text_part.charset))
#
從原始訊息中取得內文,如果EmailHTML格式,PzyMessage物件會把text_part屬性設成None
print(message.html_part != None)
print(message.html_part.get_payload().decode(message.html_part.charset))
#
切斷IMAP伺服器連線
imapObj.logout()
l   搜尋鍵(Search Key)
返回此資料夾中所有郵件:
["ALL"]
返回給予的日期之前、當天、之後所接收的郵件:
["BEFORE", "21-Jun-2018"]
["ON", "21-Jun-2018"]["SINCE", "21-Jun-2018"]
返回給予的字串出現在主旨、內文、兩者之中的郵件:
["SUBJECT", "String"]
["BODY", "String"]["TEXT", "String"]
返回給予的字串出現在郵件地址之中的郵件:
["FROM", "String"]
["TO", "String"]["CC", "String"]["BCC", "String"]
返回已讀或未讀的所有郵件:
["SEEN"]
["UNSEEN"]
返回已回覆或未回覆的所有郵件:
["ANSWERED"]
["UNANSWERED"]
返回已刪除或未刪除的所有郵件:
["DELETED"]
["UNDELETED"]
返回草稿或非草稿的所有郵件:
["DRAFT"]
["UNDRAFT"]
返回緊急或不緊急的所有郵件:
["FLAGGED"]
["UNFLAGGED"]
返回大於或小於n個位元組的所有郵件:
["LARGER", "n"]
["SMALLER", "n"]
返回不含給予字串的所有郵件:
["NOT", ["SUBJECT", "String"]]
返回含有第一個或第二個給予字串的所有郵件:
["OR", ["SUBJECT", "String"], ["BODY", "String"]]
l   更改大小限制:
搜尋比對找出大量郵件有大小限制,更改限制從預設10000bytes改成10000000bytes
import imaplib
imaplib._MAXLINE = 10000000
l   刪除電子郵件:
import imapclient
imapObj = imapclient.IMAPClient("imap.mail.yahoo.com", ssl = True)
imapObj.login("myAccount@yahoo.com.tw", "myPassword")
imapObj.select_folder("Inbox", readonly = False)
UIDs = imapObj.search(["SINCE", "21-Jun-2017"])
#
永久刪除,少數如Gmail呼叫.delete_message()就已永久刪除
imapObj.delete_messages(UIDs)
imapObj.expunge()
imapObj.logout()
l   IMAPClient模組相關內容:
IMAPClient 2.0.0 documentation
PyzMail
模組相關內容:
pyzmail: Python easy mail library

使用Twilio傳送簡訊
l   註冊免費的Twilio服務,並用它的Python模組傳送簡訊:
https://www.twilio.com/
l   未註冊使用Twilio,故略。

七、處理影像圖片

影像圖片的基礎
l   在命令提示字元輸入pip install pillow,下載並安裝Pillow模組。
l   RGBA值:
RGBA
值是0255的整數,以(0, 0, 0, 0)符號來指定,代表(R, G, B, Alpha透明度)
from PIL import ImageColor
#
色彩名稱不分大小寫
print(ImageColor.getcolor("red", "RGBA"))
à (255, 0, 0, 255)
print(ImageColor.getcolor("RED", "RGBA"))
à (255, 0, 0, 255)
print(ImageColor.getcolor("Black", "RGBA"))
à (0, 0, 0, 255)
print(ImageColor.getcolor("CornflowerBlue", "RGBA"))
à (100, 149, 237, 255)
l   座標、方框多元組(Box Tuple)與資料型別:
原點(Origin)是影像左上角的像素,以(0, 0)符號來指定,代表(, ),方框的座標是以左和上座標為起始,但右下角座標則超出右和下座標(本章節,本書範例檔zophie.png皆在目前工作目錄):
from PIL import Image
a = Image.open("zophie.png")
print(a.size)
à (816, 1088)
print(a.filename)
à zophie.png
print(a.format)
à PNG
print(a.format_description)
à Portable network graphics
a.save("zophie.jpg")
l   使用.getpixel()顯示影像圖片座標的RGBA值:
from PIL import Image
a = Image.open("zophie.png")
print(a.getpixel((408, 544)))
à (99, 72, 55)

影像圖片的處理
l   使用Image.new()新增影像圖片:
from PIL import Image
a = Image.new("RGBA", (100, 200), "CornflowerBlue")
a.save("cornflowerBlueImage.png")
b = Image.new("RGBA", (100, 200), (210, 105, 30, 255))
b.save("chocolateImage.png")
#
若無傳入第三引數背景色彩,預設為(0, 0, 0, 0)
c = Image.new("RGBA", (100, 100))
c.save("transparentImage.png")
l   使用.crop()裁切影像圖片、使用.copy()複製影像圖片、使用.paste()貼上影像圖片:
from PIL import Image
a = Image.open("zophie.png")
b = a.crop((335, 345, 565, 560))
aWidth, aHeight = a.size
bWidth, bHeight = b.size
c = a.copy()
for left in range(0, aWidth, bWidth):
    for top in range(0, aHeight, bHeight):
        #
貼上的是透明背景圖片,須新增第三引數c.paste(b, (left, top), b)
        c.paste(b, (left, top))
c.save("tiledImage.png")
l   使用.resize()縮放影像圖片:
from PIL import Image
a = Image.open("zophie.png")
aWidth, aHeight = a.size
b = a.resize((int(aWidth / 2), int(aHeight / 2)))
b.save("quartersizedImage.png")
l   使用.rotate()逆時針旋轉影像圖片:
from PIL import Image
a = Image.open("zophie.png")
a.rotate(90).save("rotated90Image.png")
a.rotate(350).save("rotated350Image.png")
# expand = True
會放大整個影像圖的版面尺度
a.rotate(350, expand = True).save("rotated350Image_expanded.png")
l   使用.transpose()翻轉影像圖片:
from PIL import Image
a = Image.open("zophie.png")
a.transpose(Image.FLIP_LEFT_RIGHT).save("horizontalFlipImage.png")
a.transpose(Image.FLIP_TOP_BOTTOM).save("verticalFlipImage.png")
l   使用.putpixel()修改單個像素:
from PIL import Image, ImageColor
a = Image.new("RGBA", (200, 200))
for x in range(200):
    for y in range(100):
        a.putpixel((x, y), (210, 210, 210))
for x in range(200):
    for y in range(100, 200):
        a.putpixel((x, y), ImageColor.getcolor("darkgray", "RGBA"))
a.save("putPixelImage.png")

影像圖片的繪製
l   以下圖形繪製,filloutline引數是選擇性的,若沒有指定,預設白色。
l   (Point)
from PIL import Image, ImageDraw
a = Image.new("RGBA", (200, 200), "White")
# .point(xy, fill)
,如.point([60, 80, 120, 140], (100, 149, 237, 255))
ImageDraw.Draw(a).point([(60, 80), (120, 140)], "CornflowerBlue")
a.save("pointImage.png")
l   (Line)
from PIL import Image, ImageDraw
a = Image.new("RGBA", (200, 200), "White")
# .line(xy, fill, width)
,如.line([60, 80, 120, 140], (100, 149, 237, 255), 5)
ImageDraw.Draw(a).line([(60, 80), (120, 140)], "CornflowerBlue", 5)
a.save("lineImage.png")
l   矩形(Rectangle)
from PIL import Image, ImageDraw
a = Image.new("RGBA", (200, 200), "White")
# .rectangle(xy, fill, outline)
,如.rectangle([60, 80, 120, 140], (100, 149, 237, 255), (0, 0, 0, 255))
ImageDraw.Draw(a).rectangle([(60, 80), (120, 140)], "CornflowerBlue", "Black")
a.save("rectangleImage.png")
l   橢圓(Ellipse)
from PIL import Image, ImageDraw
a = Image.new("RGBA", (200, 200), "White")
# .ellipse(xy, fill, outline)
,如.ellipse([60, 80, 120, 140], (100, 149, 237, 255), (0, 0, 0, 255))
ImageDraw.Draw(a).ellipse([(60, 80), (120, 140)], "CornflowerBlue", "Black")
a.save("ellipseImage.png")
l   多邊形(Polygon)
from PIL import Image, ImageDraw
a = Image.new("RGBA", (200, 200), "White")
# .polygon(xy, fill, outline)
,如.polygon([60, 80, 90, 100, 120, 80, 120, 140, 90, 120, 60, 140], (100, 149, 237, 255), (0, 0, 0, 255))
ImageDraw.Draw(a).polygon([(60, 80), (90, 100), (120, 80), (120, 140), (90, 120), (60, 140)], "CornflowerBlue", "Black")
a.save("polygonImage.png")
l   繪製文字:
from PIL import Image, ImageDraw, ImageFont
import os
a = Image.new("RGBA", (200, 200), "white")
# .text(xy, text, fill, font)
ImageDraw.Draw(a).text((60, 80), "Hello", fill = "Purple")
# ImageFont.truetype(
代表字型TrueType檔的字串, 字型大小點數)
arialFont = ImageFont.truetype(os.path.join("FONT_FOLDER", "arial.ttf"), 32)
ImageDraw.Draw(a).text((120, 140), "World", fill = "Gray", font = arialFont)
# .textsize(text, font)
返回文字的寬度與高度,做為繪製文字時的參考
print(ImageDraw.Draw(a).textsize("Poor", arialFont))
à (69, 29)
a.save("textImage.png")

八、以GUI自動化來控制鍵盤和滑鼠

關於GUI自動化
l   在命令提示字元輸入pip install pyautogui,下載並安裝PyAutoGUI模組。
l   暫停與失效安全防護(Fail-Safe)
import pyautogui
#
暫停1.5
pyautogui.PAUSE = 1.5
#
失效安全防護,把滑鼠游標移到畫面左上角,這會引起pyautogui產生pyautogui.FailSageException例外異常
pyautogui.FAILSAFE = True
l   螢幕的解析度:
import pyautogui
#
如果螢幕的解析度設為1920*1080,則左上角的座標為(0, 0),右下角的座標為(1919, 1079)
print(pyautogui.size())
à (1920, 1080)
l   PyAutoGUI模組相關內容:
PyAutoGUI 1.0.0documentation

滑鼠
l   取得滑鼠游標位置:
import pyautogui
print(pyautogui.position())
l   絕對位置移動滑鼠游標:
import pyautogui
for i in range(5):
    pyautogui.moveTo(100, 100, duration = 0.5)
    pyautogui.moveTo(200, 100, duration = 0.5)
    pyautogui.moveTo(200, 200, duration = 0.5)
    pyautogui.moveTo(100, 200, duration = 0.5)
l   相對位置移動滑鼠游標:
import pyautogui
for i in range(5):
    pyautogui.moveRel(100, 0, duration = 0.5)
    pyautogui.moveRel(0, 100, duration = 0.5)
    pyautogui.moveRel(-100, 0, duration = 0.5)
    pyautogui.moveRel(0, -100, duration = 0.5)
l   絕對位置拖曳滑鼠游標:
import time, pyautogui
#
可在5秒內移至小畫家畫面
time.sleep(5)
distance = 400
while distance > 200:
    pyautogui.dragTo(distance, distance, duration = 0.2)
    distance -= 10
    pyautogui.dragTo(distance * 2, distance, duration = 0.2)
    pyautogui.dragTo(distance * 2, distance * 2, duration = 0.2)
    distance -= 10
    pyautogui.dragTo(distance, distance * 2, duration = 0.2)
l   相對位置拖曳滑鼠游標:
import time, pyautogui
#
可在5秒內移至小畫家畫面
time.sleep(5)
distance = 200
while distance > 0:
    pyautogui.dragRel(distance, 0, duration = 0.2)
    distance -= 5
    pyautogui.dragRel(0, distance, duration = 0.2)
    pyautogui.dragRel(-distance, 0, duration = 0.2)
    distance -= 5
    pyautogui.dragRel(0, -distance, duration = 0.2)
l   按一下滑鼠:
pyautogui.click()
功能已整合pyautogui.mouseDown()按下滑鼠按鍵與pyautogui.mouseUp()放開滑鼠按鍵。
pyautogui.click(x, y, button = "left
middleright")已整合pyautogui.rightClick()按下滑鼠右鍵與pyautogui.middleClick()按下滑鼠中間鍵。
import pyautogui
pyautogui.click()
pyautogui.click(100, 150, button = "left")
l   按二下滑鼠:
import pyautogui
pyautogui.doubleClick()
pyautogui.doubleClick(100, 150, button = "left")
l   捲動滑鼠:
import time, pyautogui
#
可在5秒內移至有捲軸的瀏覽器畫面
time.sleep(5)
pyautogui.scroll(100)

螢幕
l   比對目前螢幕畫面與螢幕截圖,特定座標像素色彩是否相同:
import pyautogui
#
若需要將螢幕截圖存檔,再加入"screenshotImage.png"
a = pyautogui.screenshot("screenshotImage.png")
print(pyautogui.pixelMatchesColor(50, 200, a.getpixel((50, 200))))
l   比對目前螢幕畫面與影像圖片,若相同,返回螢幕畫面4點座標:
import pyautogui
#
返回找到的1個螢幕畫面4點座標
print(pyautogui.locateOnScreen("object.png"))
#
返回找到的全部螢幕畫面4點座標
print(list(pyautogui.locateAllOnScreen("object.png")))
l   返回螢幕畫面4點座標的中心座標,並點按該中心座標:
import pyautogui
a = pyautogui.locateOnScreen("object.png")
b = pyautogui.center(a)
pyautogui.click(b)

鍵盤
l   從鍵盤傳送字串:
import pyautogui
pyautogui.click()
pyautogui.typewrite("Hello World\n")
pyautogui.typewrite("Hello Poor World", 0.1)
l   從鍵盤按下按鍵:
import pyautogui
#
查閱按鍵
print(pyautogui.KEYBOARD_KEYS)
pyautogui.click()
pyautogui.typewrite(["o", "r", "left", "left", "P", "o"], 0.1)
l   按下和放開鍵盤按鍵:
pyautogui.press()
功能已整合pyautogui.keyDown()按下鍵盤按鍵與pyautogui.keyUp()放開鍵盤按鍵。
import pyautogui
pyautogui.click()
pyautogui.keyDown("shift")
pyautogui.press("4")
pyautogui.keyUp("shift")
l   按下和放開鍵盤按鍵-快速鍵(Hotkey)、快捷鍵(Shortcut)組合:
import pyautogui, time
pyautogui.click()
pyautogui.typewrite("In IDLE, Alt-3 comments out a line.")
time.sleep(2)
pyautogui.hotkey("alt", "3")

九、Python自動化的樂趣-第11章至第18章-實作專題參考程式碼

11章-實作專題參考程式碼
l   命令提示行郵件處理程式:
#! python3
import sys, time
from selenium import webdriver
if len(sys.argv) == 3:
    chromedriverPath = r"C:\Users\Timmy\Documents\Python\chromedriver.exe"
    driver = webdriver.Chrome(chromedriverPath)
    driver.get("https://webmail.ntnu.edu.tw/v2/")
    userElement = driver.find_element_by_name("user")
    userElement.send_keys("myAccount")
    pswdElement = driver.find_element_by_name("pswd")
    pswdElement.send_keys("myPassword")
    lnbnElement = driver.find_element_by_name("loginbutton")
    lnbnElement.click()
    time.sleep(5)
    compElement = driver.find_element_by_tag_name("a")
    compElement.click()
    time.sleep(5)
    subjElement = driver.find_element_by_name("subject")
    subjElement.send_keys(sys.argv[2])
    emaiElement = driver.find_element_by_name("to")
    emaiElement.send_keys(sys.argv[1])
    sdbnElement = driver.find_element_by_name("sendbutton")
    sdbnElement.click()
else:
    print("pythonScript emailAddress subject")
l   照片影像網站的下載:
import os, requests, bs4
url = https://bulbapedia.bulbagarden.net/wiki/Bulbasaur_(Pok%C3%A9mon)
os.makedirs("pokemon", exist_ok = True)
for pokedex in range(807):
    print("Downloading page %s..."%url)
    aRes = requests.get(url)
    aRes.raise_for_status()
    aBeaSou = bs4.BeautifulSoup(aRes.text, "html.parser")
    aTagLis = aBeaSou.select("a.image img")
    picUrl = ("https:" + aTagLis[0].get("src"))
    print("Downloading image %s..."%(picUrl))
    bRes = requests.get(picUrl)
    bRes.raise_for_status()
    imageFile = open(os.path.join("pokemon",os.path.basename(picUrl)), "wb")
    for chunk in bRes.iter_content(100000):
        imageFile.write(chunk)
        imageFile.close()
    bTagLis = aBeaSou.select("td[style='text-align: left'] a")
    url = "https://bulbapedia.bulbagarden.net" + bTagLis[0].get("href")
print("Done.")
l   2048
import random
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
chromedriverPath = r"C:\Users\Timmy\Documents\Python\chromedriver.exe"
driver = webdriver.Chrome(chromedriverPath)
driver.get("https://gabrielecirulli.github.io/2048/")
htmlElement = driver.find_element_by_tag_name("html")
arrows = [Keys.UP, Keys.DOWN, Keys.LEFT, Keys.RIGHT]
lowerElement = driver.find_element_by_css_selector("div.lower")
while not lowerElement.is_displayed():
    htmlElement.send_keys(random.sample(arrows, 1))
print("Game over! or You win!")
l   連結驗證:
import requests, bs4, webbrowser
aRes = requests.get("https://simp.stpi.narl.org.tw/")
aRes.raise_for_status()
aBeaSou = bs4.BeautifulSoup(aRes.text, "html.parser")
aTagLis = aBeaSou.select("a[href]")
for i in aTagLis:
    url = i.get("href")
    print(url)
    try:
        bRes = requests.get(url)
        bRes.raise_for_status()
        print("Open Web Browser\n")
        webbrowser.open(url)
    except Exception as error:
        print(str(error) + "\n")

12章-實作專題參考程式碼
l   乘法表:
#! python3
import openpyxl, sys
from openpyxl.styles import Font
from openpyxl.utils import get_column_letter, column_index_from_string
aBook = openpyxl.Workbook()
aSheet = aBook.active
n = int(sys.argv[1])
for i in range(1, n + 1):
    aSheet["A" + str(i + 1)] = i
    aSheet["A" + str(i + 1)].font = Font(bold = True)
    aSheet[get_column_letter(i + 1) + str(1)] = i
    aSheet[get_column_letter(i + 1) + str(1)].font = Font(bold = True)
for j in range(1, n + 1):
    for k in range(1, n + 1):
        aSheet[get_column_letter(k + 1) + str(j + 1)] = "=A" + str(j + 1) + "*" + get_column_letter(k + 1) + "1"
aBook.save("multiplicationTable.xlsx")
l   插入空白列的程式:
#! python3
import openpyxl, sys
from openpyxl.utils import get_column_letter, column_index_from_string
#
本書範例檔example.xlsx在目前工作目錄
aBook = openpyxl.load_workbook("example.xlsx")
aSheet = aBook["Sheet1"]
bSheet = aBook["Sheet2"]
n = int(sys.argv[1])
m = int(sys.argv[2])
for g in range(1, n):
    for h in range(1, aSheet.max_column + 1):
        bSheet[get_column_letter(h) + str(g)] = aSheet[get_column_letter(h) + str(g)].value
for i in range(n, aSheet.max_row + 1):
    for j in range(1, aSheet.max_column + 1):
        bSheet[get_column_letter(j) + str(i + m)] = aSheet[get_column_letter(j) + str(i)].value
aBook.save("blankRowInserter.xlsx")
l   試算表的儲存格轉置程式:
import openpyxl
from openpyxl.utils import get_column_letter, column_index_from_string
#
本書範例檔example.xlsx在目前工作目錄
aBook = openpyxl.load_workbook("example.xlsx")
aSheet = aBook["Sheet1"]
bSheet = aBook["Sheet2"]
for i in range(1, aSheet.max_row + 1):
    for j in range(1, aSheet.max_column + 1):
        bSheet[get_column_letter(i) + str(j)] = aSheet[get_column_letter(j) + str(i)].value
aBook.save("transpose.xlsx")
l   將文字檔寫入試算表:
import openpyxl, os
from openpyxl.utils import get_column_letter, column_index_from_string
aBook = openpyxl.Workbook()
aSheet = aBook.active
colNum = 1
for pathName, folderName, fileName in os.walk(r"C:\Users\Timmy\Desktop"):
    for aFile in fileName:
        if aFile.endswith(".txt"):
            txtFile = open(pathName + "\\" + aFile, "r")
            lyricsList = txtFile.readlines()
            txtFile.close()
            rowNum = 1
            for i in lyricsList:
                aSheet[get_column_letter(colNum) + str(rowNum)] = i
                rowNum += 1
            colNum += 1
aBook.save("txtFile.xlsx")
l   將試算表寫入文字檔:
import openpyxl
from openpyxl.utils import get_column_letter, column_index_from_string
#
本書範例檔example.xlsx在目前工作目錄
aBook = openpyxl.load_workbook("example.xlsx")
aSheet = aBook.active
for i in range(1, aSheet.max_column + 1):
    txtFile = open(str(i) + ".txt", "w")
    for j in range(1, aSheet.max_row + 1):
        txtFile.write(str(aSheet[get_column_letter(i) + str(j)].value) + "\n")
    txtFile.close()

13章-實作專題參考程式碼
l   PDF偏執狂-加密程式:
#! python3
import os, PyPDF2, sys
for pathName, folderName, fileName in os.walk(r"C:\Users\Timmy\Desktop"):
    for aFile in fileName:
        if aFile.endswith(".pdf"):
            aReader = PyPDF2.PdfFileReader(open(pathName + "\\" + aFile, "rb"))
            aWriter = PyPDF2.PdfFileWriter()
            for i in range(aReader.numPages):
                aWriter.addPage(aReader.getPage(i))
            aWriter.encrypt(sys.argv[1])
            bFile = open(aFile.strip(".pdf") + "_encrypted.pdf", "wb")
            aWriter.write(bFile)
            bFile.close()
l   PDF偏執狂-解密程式:
#! python3
import os, PyPDF2, sys
for pathName, folderName, fileName in os.walk(r"C:\Users\Timmy\Desktop"):
    for aFile in fileName:
        if aFile.endswith(".pdf"):
            aReader = PyPDF2.PdfFileReader(open(pathName + "\\" + aFile, "rb"))
            if aReader.isEncrypted == True:
                try:
                    if aReader.decrypt(sys.argv[1]) != 1:
                        raise Exception("Password for " + aFile + " Is Not Correct")
                    aWriter = PyPDF2.PdfFileWriter()
                    for i in range(aReader.numPages):
                        aWriter.addPage(aReader.getPage(i))
                    bFile = open(aFile.strip(".pdf") + "_decrypted.pdf", "wb")
                    aWriter.write(bFile)
                    bFile.close()
                except Exception as err:
                    print("An Error Happened: " + str(err))
l   自訂邀請函並儲存成Word檔:
import docx
#
本書範例檔guests.txt在目前工作目錄
aFile = open("guests.txt", "r")
guestsList = aFile.readlines()
aFile.close()
aDocx = docx.Document()
num = 0
for i in guestsList:
    aDocx.add_paragraph("It would be a pleasure to have the company of", "Body Text")
    aDocx.add_paragraph(i.strip("\n"), "Title")
    aDocx.add_paragraph("at 11010 Memory Lane one the Evening of", "Body Text")
    aDocx.add_paragraph("April 1st", "Subtitle")
    aDocx.add_paragraph("at 7 o'clock", "Intense Quote")
    aDocx.paragraphs[4 + 5 * num].runs[0].add_break(docx.enum.text.WD_BREAK.PAGE)
    num += 1
aDocx.save("guests.docx")
l   暴力PDF密碼破解程式:
import PyPDF2
#
本書範例檔dictionary.txt在目前工作目錄
aFile = open("dictionary.txt", "r")
dictionaryList = aFile.readlines()
aFile.close()
#
本書範例檔encrypted.pdf在目前工作目錄
aReader = PyPDF2.PdfFileReader(open("encrypted.pdf", "rb"))
for i in dictionaryList:
    upperI = i.strip("\n")
    lowerI = upperI.lower()
    if aReader.decrypt(upperI) == 1:
        print("Password: " + upperI)
        break
    if aReader.decrypt(lowerI) == 1:
        print("Password: " + lowerI)
        break
print("Done")

14章-實作專題參考程式碼
l   ExcelCSV的程式:
import os, openpyxl, csv
for excelFile in os.listdir("."):
    # Skip non-xlsx files, load the workbook object.
    if not excelFile.endswith(".xlsx"):
        continue
    wb = openpyxl.load_workbook(excelFile)
    for sheetName in wb.sheetnames:
        # Loop through every sheet in the workbook.
        sheet = wb[sheetName]
        # Create the CSV filename from the Excel filename and sheet title.
        csvFile = open(excelFile.strip(".xlsx") + "_" + sheetName + ".csv", "w", newline = "")
        # Create the csv.writer object for this CSV file.
        csvWriter = csv.writer(csvFile)
        # Loop through every row in the sheet.
        for rowNum in range(1, sheet.max_row + 1):
            rowData = []    # append each cell to this list
            # Loop through each cell in the row.
            for colNum in range(1, sheet.max_column + 1):
                # Append each cell's data to rowData.
                rowData.append(sheet.cell(row = rowNum, column = colNum).value)
            # Write the rowData list to the CSV file.
            csvWriter.writerow(rowData)
        csvFile.close()

15章-實作專題參考程式碼
l   修飾美化碼表:
import time, pyperclip
print("Press ENTER to begin. Afterwards, press ENTER to click the stopwatch. Press Ctrl-C to quit.")
lapNum = 1
infoList = []
input()
print("Started.")
startTime = time.time()
lastTime = startTime
try:
    while True:
        input()
        lapTime = round(time.time() - lastTime, 2)
        totalTime = round(time.time() - startTime, 2)
        infoStr = "Lap # " + str(lapNum).rjust(3) + ": " + str(totalTime).rjust(6) + " (" + str(lapTime).rjust(6) + ")"
        print(infoStr, end="")
        lastTime = time.time()
        lapNum += 1
        infoList.append(infoStr)
except KeyboardInterrupt:
    info = "\r\n".join(infoList)
    pyperclip.copy(info)
    print("\nInformation is copied to clipboard.")
l   Web漫畫圖片的排程下載:
import requests, bs4, os, sys, shutil
url = "https://xkcd.com"
aRes = requests.get(url)
aRes.raise_for_status()
aBeaSou = bs4.BeautifulSoup(aRes.text, "html.parser")
aTag = aBeaSou.select("a[rel='prev']")[0]
num = int(aTag.get("href").strip("/")) + 1
url = url + "/" + str(num) + "/"
while True:
    print("Downloading page %s..."%(url))
    bRes = requests.get(url)
    bRes.raise_for_status()
    bBeaSou = bs4.BeautifulSoup(bRes.text, "html.parser")
    bTagLis = bBeaSou.select("#comic img")
    comicUrl = "http:" + bTagLis[0].get("src")
    print("Downloading image %s..."%(comicUrl))
    cRes = requests.get(comicUrl)
    cRes.raise_for_status()
    imageName = str(num) + "_" + os.path.basename(comicUrl)
    for iImageFile in os.listdir(r".\xkcd"):
        if iImageFile == imageName:
            print("The image file already exists.")
            sys.exit()
    imageFile = open(os.path.join("xkcd", imageName), "wb")
    for chunk in cRes.iter_content(1000000):
        imageFile.write(chunk)
        imageFile.close()
    shutil.copy(".\\xkcd\\" + imageName, r"C:\Users\Timmy\Desktop")
    prevLink = bBeaSou.select("a[rel='prev']")[0]
    url = "https://xkcd.com" + prevLink.get("href")
    num = int(prevLink.get("href").strip("/"))
print("Done.")

16章-實作專題參考程式碼
l   隨機分配例行工作的Email程式:
import random, smtplib
emails = ["alice@example.com", "bob@example.com", "carol@example.com", "david@example.com"]
chores = ["dishes", "bathroom", "vacuum", "walk dog"]
matches = {}
for i in emails:
    randomChore = random.choice(chores)
    chores.remove(randomChore)
    matches.setdefault(i, randomChore)
smtpObj = smtplib.SMTP_SSL("smtp.ntnu.edu.tw", 465)
smtpObj.ehlo()
smtpObj.login("myAccount@ntnu.edu.tw", "myPassword")
for j, k in matches.items():
    smtpObj.sendmail("myAccount@ntnu.edu.tw", j, "Subject: Random Chore\n\nRandom Chore: %s"%k)
smtpObj.quit()
l   帶傘的提醒程式:
未註冊使用Twilio,故略。
l   自動取消訂閱:
import imapclient, pyzmail, bs4, webbrowser
imapObj = imapclient.IMAPClient("imap.mail.yahoo.com", ssl = True)
imapObj.login("myAccount@yahoo.com.tw", "myPassword")
imapObj.select_folder("Inbox", readonly = True)
UIDs = imapObj.search(["ALL"])
print(UIDs)
for i in UIDs:
    print("Search %s..."%i)
    rawMessages = imapObj.fetch([i], [b"BODY[]"])
    message = pyzmail.PyzMessage.factory(rawMessages[i][b"BODY[]"])
    if message.html_part == None:
        continue
    if message.html_part.charset == None:
        continue
    aText = message.html_part.get_payload().decode(message.html_part.charset)
    aBeaSou = bs4.BeautifulSoup(aText, "html.parser")
    #
因沒有取消訂閱的連結,將改為尋找特定格式的連結
    aTagLis = aBeaSou.select("span a")
    if len(aTagLis) >= 1:
        for j in range(len(aTagLis)):
            webbrowser.open(aTagLis[j].get("href"))
imapObj.logout()
l   利用Email控制您的電腦:
參考本書範例檔torrentStarter.py,故略。

17章-實作專題參考程式碼
l   擴充和修改本章程式專題的程式範例:
import os
from PIL import Image
SQUARE_FIT_SIZE = 1500
LOGO_FILENAME = "catlogo.png"
logoImage = Image.open(LOGO_FILENAME)
logoWidth, logoHeight = logoImage.size
os.makedirs("withLogo", exist_ok = True)
for originFilename in os.listdir("."):
    filename = originFilename.lower()
    if not (filename.endswith(".png") or filename.endswith(".jpg") or filename.endswith(".gif") or filename.endswith(".bmp")) or filename == LOGO_FILENAME:
        continue
    aImage = Image.open(filename)
    aWidth, aHeight = aImage.size
    if aWidth > SQUARE_FIT_SIZE or aHeight > SQUARE_FIT_SIZE:
        if aWidth > aHeight:
            aHeight = int((SQUARE_FIT_SIZE / aWidth) * aHeight)
            aWidth = SQUARE_FIT_SIZE
        else:
            aWidth = int((SQUARE_FIT_SIZE / aHeight) * aWidth)
            aHeight = SQUARE_FIT_SIZE
        print("Resizing %s..."%(filename))
        aImage = aImage.resize((aWidth, aHeight))
    if aWidth >= (logoWidth * 3) and aHeight >= (logoHeight * 3):
        print("Adding logo to %s..."%(filename))
        aImage.paste(logoImage, (aWidth - logoWidth, aHeight - logoHeight), logoImage)
    aImage.save(os.path.join("withLogo", filename))
l   在硬碟中找出照片資料夾:
import os
from PIL import Image
for pathName, folderName, fileName in os.walk("C:\\"):
    numPhotoFiles = 0
    numNonPhotoFiles = 0
    for aFile in fileName:
        # Check if file extension isn't .png or .jpg.
        if not (aFile.endswith(".png") or aFile.endswith(".jpg")):
            numNonPhotoFiles += 1
            continue
        # Open image file using Pillow.
        aImage = Image.open(os.path.join(pathName, aFile))
        aWidth, aHeight = aImage.size
        # Check if width & height are larger than 500.
        if aWidth > 500 and aHeight > 500:
            # Image is large enough to be considered a photo.
            numPhotoFiles += 1
        else:
            # Image is too small to be a photo.
            numNonPhotoFiles += 1
    # If more than half of files were photos, print the absolute path of the folder.
    if numPhotoFiles >= numNonPhotoFiles:
        print(os.path.abspath(pathName))
l   自訂的座位卡:
import os
from PIL import Image, ImageDraw, ImageFont
os.makedirs("seatCards", exist_ok = True)
#
本書範例檔guests.txt在目前工作目錄
txtFile = open("guests.txt")
names = []
for i in txtFile.readlines():
    names.append(i.strip("\n"))
txtFile.close()
aImage = Image.new("RGBA", (361, 289), "SkyBlue")
bImage = Image.open("tulip.png")
aImage.paste(bImage, (-72, 72), bImage)
ImageDraw.Draw(aImage).line([(0, 0), (360, 0), (360, 288), (0, 288), (0, 0)], "Black", 1)
arialFont = ImageFont.truetype(os.path.join("FONT_FOLDER", "arial.ttf"), 32)
for j in names:
    cImage = aImage.copy()
    ImageDraw.Draw(cImage).text((40, 40), j, fill = "Black", font = arialFont)
    cImage.save(os.path.join("seatCards", j + ".png"))

18章-實作專題參考程式碼
l   裝忙程式:
import time, pyautogui
try:
    while True:
        for i in range(60 * 10):
            time.sleep(1)
        pyautogui.moveRel(1, 1, duration = 1)
except KeyboardInterrupt:
    print("Done")
l   即時通訊機器人:
import time, pyautogui
#
facebook示範
time.sleep(5)
messenger = pyautogui.locateOnScreen("messenger.png")
pyautogui.click(pyautogui.center(messenger))
time.sleep(5)
friendName = pyautogui.locateOnScreen("friendName.png")
pyautogui.click(pyautogui.center(friendName))
time.sleep(1)
inputText = pyautogui.locateOnScreen("inputText.png")
pyautogui.click(pyautogui.center(inputText))
pyautogui.typewrite("Hello World")
pyautogui.press("enter")
l   Game機器人教學指南:
參考Programminga Bot to Play the "Sushi Go Round" Flash Game,故略。

沒有留言:

張貼留言