、安裝Nuget包MailKit,引用命名空間。
using MailKit.Net.Smtp;
using MimeKit;
注意:引用MailKit對應最新版本
2、定義收發地址和標題
MimeMessage message=new MimeMessage();
MailboxAddress from=new MailboxAddress("Admin","admin@example.com");
message.From.Add(from);
MailboxAddress to=new MailboxAddress("User", "user@example.com");
message.To.Add(to);
message.Subject="This is email subject";
注意:Admin,User分別對應發送接收郵箱前綴
3、編寫內容
BodyBuilder bodyBuilder=new BodyBuilder();
bodyBuilder.HtmlBody="<h1>Hello World!</h1>";
bodyBuilder.TextBody="Hello World!";
message.Body=bodyBuilder.ToMessageBody();
注意:也可以自定義模板,插入圖片等等。
4、連接SMTP服務器發送郵件
SmtpClient client=new SmtpClient();client.Connect("smtp_address_here", port_here, true); //例如:smtp.exmail.qq.com,465client.Authenticate("admin@example.com", "password"); //發送郵件的賬戶密碼client.Send(message);client.Disconnect(true);client.Dispose();
時候我們需要以郵件形式發送附有條形碼的門票、實施通訊、請柬。那么面對這樣的情況我們該怎么處理呢?今天我們將介紹如何用Barcode Professional生成及發送有條形碼的HTML電子郵件。
參考步驟:
VB
Private?Function?GetBarcodeImage?As?System.IO.MemoryStream ?'Create?an?instance?of?BarcodeProfessional?class ?Dim?bcp?As?New?Neodynamic.WebControls.BarcodeProfessional.BarcodeProfessional ? ?'Set?barcode?settings... ?'Code?128?symbology ?bcp.Symbology?=?Neodynamic.WebControls.BarcodeProfessional.Symbology.Code128 ?'Set?a?fictitious?value?to?encode ?bcp.Code?=?Guid.NewGuid.ToString.Replace("-",?"").Substring(0,?20).ToUpper ? ?'Return?barcode?stream ?Return?New?System.IO.MemoryStream(bcp.GetBarcodeImage(System.Drawing.Imaging.ImageFormat.Png)) ?End?Function
C#
private?System.IO.MemoryStream?GetBarcodeImage ?{ ?//Create?an?instance?of?BarcodeProfessional?class ?Neodynamic.WebControls.BarcodeProfessional.BarcodeProfessional?bcp?=?new?Neodynamic.WebControls.BarcodeProfessional.BarcodeProfessional; ? ?//Set?barcode?settings... ?//Code?128?symbology ?bcp.Symbology?=?Neodynamic.WebControls.BarcodeProfessional.Symbology.Code128; ?//Set?a?fictitious?value?to?encode ?bcp.Code?=?Guid.NewGuid.ToString.Replace("-","").Substring(0,20).ToUpper; ? ?//Return?barcode?stream ?return?new?System.IO.MemoryStream(bcp.GetBarcodeImage(System.Drawing.Imaging.ImageFormat.Png)); ?}
VB
Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click 'Create the mail message Dim mail As New System.Net.Mail.MailMessage 'Set the email addresses mail.From=New System.Net.Mail.MailAddress("me@mycompany.com") mail.To.Add(Me.TextBox1.Text) 'Set the subject mail.Subject="John Doe in Concert - Barcode Ticket" 'Create the Html part. 'To embed the barcode image, we need to use the prefix cid in the img src attribute. 'The cid value will map to the Content-Id of a Linked resource. 'Example:
will map to a LinkedResource with a ContentId of barcodeticket Dim htmlContent1 As String="
NEOMIX | |
ADMIT ONE | |
NEO STADIUM | |
GENERAL ADMISSION | |
John Doe in Concert | |
May 19 2007 | SATURDAY 8:00 PM |
$ 98.00 |
" Dim htmlView As System.Net.Mail.AlternateView=System.Net.Mail.AlternateView.CreateAlternateViewFromString(htmlContent1 + htmlContent2 + htmlContent3, Nothing, "text/html") 'Create the LinkedResource (embedded barcode image) Dim barcode As New System.Net.Mail.LinkedResource(Me.GetBarcodeImage, "image/png") barcode.ContentId="barcodeticket" 'Add the LinkedResource to the view htmlView.LinkedResources.Add(barcode) 'Add the view mail.AlternateViews.Add(htmlView) 'specify the mail server address Dim smtp As New System.Net.Mail.SmtpClient("127.0.0.1") 'send the message smtp.Send(mail) End Sub
C#
protected void Button1_Click(object sender, EventArgs e) { //Create the mail message System.Net.Mail.MailMessage mail=new System.Net.Mail.MailMessage; //Set the email addresses mail.From=new System.Net.Mail.MailAddress("me@mycompany.com"); mail.To.Add(this.TextBox1.Text); //Set the subject mail.Subject="John Doe in Concert - Barcode Ticket"; //Create the Html part. //To embed the barcode image, we need to use the prefix 'cid' in the img src attribute. //The cid value will map to the Content-Id of a Linked resource. //Example:
will map to a LinkedResource with a ContentId of 'barcodeticket' string htmlContent1="
NEOMIX | |
ADMIT ONE | |
NEO STADIUM | |
GENERAL ADMISSION | |
John Doe in Concert | |
May 19 2007 | SATURDAY 8:00 PM |
$ 98.00 |
"; System.Net.Mail.AlternateView htmlView=System.Net.Mail.AlternateView.CreateAlternateViewFromString(htmlContent1 + htmlContent2 + htmlContent3, null, "text/html"); //Create the LinkedResource (embedded barcode image) System.Net.Mail.LinkedResource barcode=new System.Net.Mail.LinkedResource(this.GetBarcodeImage, "image/png"); barcode.ContentId="barcodeticket"; //Add the LinkedResource to the view htmlView.LinkedResources.Add(barcode); //Add the view mail.AlternateViews.Add(htmlView); //specify the mail server address System.Net.Mail.SmtpClient smtp=new System.Net.Mail.SmtpClient("127.0.0.1"); //send the message smtp.Send(mail); }
當你指定有效地址并點擊Send Barcode Ticket后,你將收到上文附有條碼的HTML郵件
本文譯自neodynamic
本站文章除注明轉載外,均為本站原創或翻譯
查和答復電子郵件會占用大量的時間。當然,你不能只寫一個程序來處理所有電子郵件,因為每個消息都需要有自己的回應。但是,一旦知道怎么編寫收發電子郵件的程序,就可以自動化大量與電子郵件相關的任務。
例如,也許你有一個電子表格,包含許多客戶記錄,希望根據他們的年齡和位置信息,向每個客戶發送不同格式的郵件。商業軟件可能無法做這一點。好在,可以編寫自己的程序來發送這些電子郵件,節省了大量復制和粘貼電子郵件的時間。
也可以編程發送電子郵件和短信,即使你遠離計算機時,也能通知你。如果要自動化的任務需要執行幾個小時,你不希望每過幾分鐘就回到計算機旁邊,檢查程序的狀態。相反,程序可以在完成時向手機發短信,讓你在離開計算機時,能專注于更重要的事情。
正如HTTP是計算機用來通過因特網發送網頁的協議,簡單郵件傳輸協議(SMTP)是用于發送電子郵件的協議。SMTP 規定電子郵件應該如何格式化、加密、在郵件服務器之間傳遞,以及在你點擊發送后,計算機要處理的所有其他細節。但是,你并不需要知道這些技術細節,因為Python的smtplib模塊將它們簡化成幾個函數。
SMTP只負責向別人發送電子郵件。另一個協議,名為IMAP,負責取回發送給你的電子郵件,在16.3節“IMAP”中介紹。
你可能對發送電子郵件很熟悉,通過Outlook、Thunderbird或某個網站,如Gmail或雅虎郵箱。遺憾的是,Python沒有像這些服務一樣提供一個漂亮的圖形用戶界面。作為替代,你調用函數來執行SMTP的每個重要步驟,就像下面的交互式環境的例子。
{注意}
不要在IDLE中輸入這個例子,因為smtp.example.com、bob@example.com、MY_ SECRET_PASSWORD和alice@example.com只是占位符。這段代碼僅僅勾勒出Python發送電子郵件的過程。
>>> smtpObj=smtplib.SMTP('smtp.example.com', 587)
>>> smtpObj.ehlo()
(250, b'mx.example.com at your service, [216.172.148.131]\nSIZE 35882577\
n8BITMIME\nSTARTTLS\nENHANCEDSTATUSCODES\nCHUNKING')
>>> smtpObj.starttls()
(220, b'2.0.0 Ready to start TLS')
>>> smtpObj.login('bob@example.com', 'MY_SECRET_PASSWORD')
(235, b'2.7.0 Accepted')
>>> smtpObj.sendmail('bob@example.com', 'alice@example.com', 'Subject: So
long.\nDear Alice, so long and thanks for all the fish. Sincerely, Bob')
{}
>>> smtpObj.quit()
(221, b'2.0.0 closing connection ko10sm23097611pbd.52 - gsmtp')
在下面的小節中,我們將探討每一步,用你的信息替換占位符,連接并登錄到SMTP服務器,發送電子郵件,并從服務器斷開連接。
如果你曾設置了Thunderbird、Outlook或其他程序,連接到你的電子郵件賬戶,你可能熟悉配置SMTP服務器和端口。這些設置因電子郵件提供商而不同,但在網上搜索“< 你的提供商> SMTP設置”,應該能找到相應的服務器和端口。
SMTP服務器的域名通常是電子郵件提供商的域名,前面加上SMTP。例如,Gmail的 SMTP 服務器是smtp.gmail.com。表 16-1 列出了一些常見的電子郵件提供商及其SMTP服務器(端口是一個整數值,幾乎總是587,該端口由命令加密標準TLS使用)。
表16-1 電子郵件提供商及其SMTP服務器
得到電子郵件提供商的域名和端口信息后,調用smtplib.SMTP()創建一個SMTP對象,傳入域名作為一個字符串參數,傳入端口作為整數參數。SMTP對象表示與SMTP郵件服務器的連接,它有一些發送電子郵件的方法。例如,下面的調用創建了一個SMTP對象,連接到Gmail:
>>> smtpObj=smtplib.SMTP('smtp.gmail.com', 587)
>>> type(smtpObj)
< class 'smtplib.SMTP'>
輸入type(smtpObj)表明,smtpObj中保存了一個SMTP對象。你需要這個SMTP對象,以便調用它的方法,登錄并發送電子郵件。如果smtplib.SMTP()調用不成功,你的SMTP服務器可能不支持TLS端口587。在這種情況下,你需要利用smtplib.SMTP_SSL()和465端口,來創建SMTP對象。
>>> smtpObj=smtplib.SMTP_SSL('smtp.gmail.com', 465)
{注意}
如果沒有連接到因特網,Python將拋出socket.gaierror: [Errno 11004] getaddrinfo failed或類似的異常。
對于你的程序,TLS和SSL之間的區別并不重要。只需要知道你的SMTP服務器使用哪種加密標準,這樣就知道如何連接它。在接下來的所有交互式環境示例中,smtpObj變量將包含smtplib.SMTP()或smtplib.SMTP_SSL()函數返回的SMTP對象。
得到SMTP對象后,調用它的名字古怪的EHLO()方法,向SMTP電子郵件服務器“打招呼”。這種問候是SMTP中的第一步,對于建立到服務器的連接是很重要的。你不需要知道這些協議的細節。只要確保得到SMTP對象后,第一件事就是調用ehlo()方法,否則以后的方法調用會導致錯誤。下面是一個ehlo()調用和返回值的例子:
>>> smtpObj.ehlo()
(250, b'mx.google.com at your service, [216.172.148.131]\nSIZE 35882577\
n8BITMIME\nSTARTTLS\nENHANCEDSTATUSCODES\nCHUNKING')
如果在返回的元組中,第一項是整數250(SMTP中“成功”的代碼),則問候成功了。
如果要連接到SMTP服務器的587端口(即使用TLS加密),接下來需要調用starttls()方法。這是為連接實現加密必須的步驟。如果要連接到465端口(使用SSL),加密已經設置好了,你應該跳過這一步。
下面是starttls()方法調用的例子:
>>> smtpObj.starttls()
(220, b'2.0.0 Ready to start TLS')
starttls()讓SMTP連接處于TLS模式。返回值220告訴你,該服務器已準備就緒。
到SMTP服務器的加密連接建立后,可以調用login()方法,用你的用戶名(通常是你的電子郵件地址)和電子郵件密碼登錄。
>>> smtpObj.login('my_email_address@gmail.com', 'MY_SECRET_PASSWORD')
(235, b'2.7.0 Accepted')
傳入電子郵件地址字符串作為第一個參數,密碼字符串作為第二個參數。返回值235表示認證成功。如果密碼不正確,Python會拋出smtplib. SMTPAuthenticationError異常。
將密碼放在源代碼中要當心。如果有人復制了你的程序,他們就能訪問你的電子郵件賬戶!調用input(),讓用戶輸入密碼是一個好主意。每次運行程序時輸入密碼可能不方便,但這種方法不會在未加密的文件中留下你的密碼,黑客或筆記本電腦竊賊不會輕易地得到它。
登錄到電子郵件提供商的SMTP服務器后,可以調用的sendmail()方法來發送電子郵件。sendmail()方法調用看起來像這樣:
>>> smtpObj.sendmail('my_email_address@gmail.com', 'recipient@example.com',
'Subject: So long.\nDear Alice, so long and thanks for all the fish. Sincerely,
Bob')
{}
sendmail()方法需要三個參數。
電子郵件正文字符串必須以’Subject: \n’開頭,作為電子郵件的主題行。’\n’換行符將主題行與電子郵件的正文分開。
sendmail()的返回值是一個字典。對于電子郵件傳送失敗的每個收件人,該字典中會有一個鍵值對。空的字典意味著對所有收件人已成功發送電子郵件。
{Gmail應用程序專用密碼!!}
Gmail有針對谷歌賬戶的附加安全功能,稱為應用程序專用密碼。如果當你的程序試圖登錄時,收到“需要應用程序專用密碼”的錯誤信息,就必須在Python腳本設置這樣一個密碼。具體如何設置谷歌賬戶的應用程序專用密碼,參見http://nostarch.com/automatestuff/。
確保在完成發送電子郵件時,調用quit()方法。這讓程序從SMTP服務器斷開。
>>> smtpObj.quit()
(221, b'2.0.0 closing connection ko10sm23097611pbd.52 - gsmtp')
返回值221表示會話結束。
要復習連接和登錄服務器、發送電子郵件和斷開的所有步驟,請參閱 16.2節“發送電子郵件”。
正如SMTP是用于發送電子郵件的協議,因特網消息訪問協議(IMAP)規定了如何與電子郵件服務提供商的服務器通信,取回發送到你的電子郵件地址的電子郵件。Python帶有一個imaplib模塊,但實際上第三方的imapclient模塊更易用。本章介紹了如何使用IMAPClient,完整的文檔在http://imapclient.readthedocs.org/。
imapclient模塊從IMAP服務器下載電子郵件,格式相當復雜。你很可能希望將它們從這種格式轉換成簡單的字符串。pyzmail模塊替你完成解析這些郵件的辛苦工作。在http://www.magiksys.net/pyzmail/可以找到PyzMail的完整文檔。
從終端窗口安裝imapclient和pyzmail。附錄A包含了如何安裝第三方模塊的步驟。
在Python中,查找和獲取電子郵件是一個多步驟的過程,需要第三方模塊imapclient和pyzmail。作為概述,這里有一個完整的例子,包括登錄到IMAP服務器,搜索電子郵件,獲取它們,然后從中提取電子郵件的文本。
>>> import imapclient
>>> imapObj=imapclient.IMAPClient('imap.gmail.com', ssl=True)
>>> imapObj.login('my_email_address@gmail.com', 'MY_SECRET_PASSWORD')
'my_email_address@gmail.com Jane Doe authenticated (Success)'
>>> imapObj.select_folder('INBOX', readonly=True)
>>> UIDs=imapObj.search(['SINCE 05-Jul-2014'])
>>> UIDs
[40032, 40033, 40034, 40035, 40036, 40037, 40038, 40039, 40040, 40041]
>>> rawMessages=imapObj.fetch([40041], ['BODY[]', 'FLAGS'])
>>> import pyzmail
>>> message=pyzmail.PyzMessage.factory(rawMessages[40041]['BODY[]'])
>>> message.get_subject()
'Hello!'
>>> message.get_addresses('from')
[('Edward Snowden', 'esnowden@nsa.gov')]
>>> message.get_addresses('to')
[(Jane Doe', 'jdoe@example.com')]
>>> message.get_addresses('cc')
[]
>>> message.get_addresses('bcc')
[]
>>> message.text_part !=None
True
>>> message.text_part.get_payload().decode(message.text_part.charset)
'Follow the money.\r\n\r\n-Ed\r\n'
>>> message.html_part !=None
True
>>> message.html_part.get_payload().decode(message.html_part.charset)
'< div dir="ltr">< div>So long, and thanks for all the fish!< br>< br>< /div>-
Al< br>< /div>\r\n'
>>> imapObj.logout()
你不必記住這些步驟。在詳細介紹每一步之后,你可以回來看這個概述,加強記憶。
就像你需要一個SMTP對象連接到SMTP服務器并發送電子郵件一樣,你需要一個IMAPClient對象,連接到IMAP服務器并接收電子郵件。首先,你需要電子郵件服務提供商的IMAP服務器域名。這和SMTP服務器的域名不同。表16-2列出了幾個流行的電子郵件服務提供商的IMAP服務器。
表16-2 電子郵件提供商及其IMAP服務器
得到IMAP服務器域名后,調用imapclient.IMAPClient()函數,創建一個IMAPClient對象。大多數電子郵件提供商要求SSL加密,傳入SSL=TRUE關鍵字參數。在交互式環境中輸入以下代碼(使用你的提供商的域名):
>>> import imapclient
>>> imapObj=imapclient.IMAPClient('imap.gmail.com', ssl=True)
在接下來的小節里所有交互式環境的例子中,imapObj變量將包含imapclient.IMAPClient()函數返回的IMAPClient對象。在這里,客戶端是連接到服務器的對象。
取得IMAPClient對象后,調用它的login()方法,傳入用戶名(這通常是你的電子郵件地址)和密碼字符串。
>>> imapObj.login('my_email_address@gmail.com', 'MY_SECRET_PASSWORD')
'my_email_address@gmail.com Jane Doe authenticated (Success)'
要記住,永遠不要直接在代碼中寫入密碼!應該讓程序從input()接受輸入的密碼。
如果IMAP服務器拒絕用戶名/密碼的組合,Python會拋出imaplib.error異常。對于Gmail賬戶,你可能需要使用應用程序專用的密碼。詳細信息請參閱16.2.5節中的“Gmail應用程序專用密碼”。
登錄后,實際獲取你感興趣的電子郵件分為兩步。首先,必須選擇要搜索的文件夾。然后,必須調用IMAPClient對象的search()方法,傳入IMAP搜索關鍵詞字符串。
幾乎每個賬戶默認都有一個INBOX文件夾,但也可以調用IMAPClient對象的list_folders()方法,獲取文件夾列表。這將返回一個元組的列表。每個元組包含一個文件夾的信息。輸入以下代碼,繼續交互式環境的例子:
>>> import pprint
>>> pprint.pprint(imapObj.list_folders())
[(('\\\HasNoChildren',), '/', 'Drafts'),
(('\\\HasNoChildren',), '/', 'Filler'),
(('\\\HasNoChildren',), '/', 'INBOX'),
(('\\\HasNoChildren',), '/', 'Sent'),
--snip--
(('\\\HasNoChildren', '\\\Flagged'), '/', '[Gmail]/Starred'),
(('\\\HasNoChildren', '\\\Trash'), '/', '[Gmail]/Trash')]
如果你有一個Gmail賬戶,這就是輸出可能的樣子(Gmail將文件夾稱為label,但它們的工作方式與文件夾相同)。每個元組的三個值,例如 ((‘\HasNoChildren’,), ‘/‘, ‘INBOX’),解釋如下:
要選擇一個文件夾進行搜索,就調用IMAPClient對象的select_folder()方法,傳入該文件夾的名稱字符串。
>>> imapObj.select_folder('INBOX', readonly=True)
可以忽略select_folder()的返回值。如果所選文件夾不存在,Python會拋出imaplib.error異常。
readonly=True關鍵字參數可以防止你在隨后的方法調用中,不小心更改或刪除該文件夾中的任何電子郵件。除非你想刪除的電子郵件,否則將readonly設置為True總是個好主意。
文件夾選中后,就可以用IMAPClient對象的search()方法搜索電子郵件。search()的參數是一個字符串列表,每一個格式化為IMAP搜索鍵。表16-3介紹了各種搜索鍵。
表16-3 IMAP搜索鍵
請注意,在處理標志和搜索鍵方面,某些IMAP服務器的實現可能稍有不同。可能需要在交互式環境中試驗一下,看看它們實際的行為如何。
在傳入search()方法的列表參數中,可以有多個IMAP搜索鍵字符串。返回的消息將匹配所有的搜索鍵。如果想匹配任何一個搜索鍵,使用OR搜索鍵。對于NOT和OR搜索鍵,它們后邊分別跟著一個和兩個完整的搜索鍵。
下面是search()方法調用的一些例子,以及它們的含義:
imapObj.search([‘ALL’]) 返回當前選定的文件夾中的每一個消息。
imapObj.search([‘ON 05-Jul-2015’])返回在2015年7月5日發送的每個消息。
imapObj.search([‘SINCE 01-Jan-2015’, ‘BEFORE 01-Feb-2015’, ‘UNSEEN’])返回2015年1月發送的所有未讀消息(注意,這意味著從1月1日直到2月1日,但不包括2月1日)。
imapObj.search([‘SINCE 01-Jan-2015’, ‘FROM alice@example.com’])返回自2015年開始以來,發自alice@example.com的消息。
imapObj.search([‘SINCE 01-Jan-2015’, ‘NOT FROM alice@example.com’])返回自2015年開始以來,除alice@example.com外,其他所有人發來的消息。
imapObj.search([‘OR FROM alice@example.com FROM bob@example.com’])返回發自alice@example.com或bob@example.com的所有信息。
imapObj.search([‘FROM alice@example.com’, ‘FROM bob@example.com’])惡作劇例子!該搜索不會返回任何消息,因為消息必須匹配所有搜索關鍵詞。因為只能有一個“from”地址,所以一條消息不可能既來自alice@example.com,又來自bob@example.com。
search()方法不返回電子郵件本身,而是返回郵件的唯一整數ID(UID)。然后,可以將這些UID傳入fetch()方法,獲得郵件內容。
輸入以下代碼,繼續交互式環境的例子:
>>> UIDs=imapObj.search(['SINCE 05-Jul-2015'])
>>> UIDs
[40032, 40033, 40034, 40035, 40036, 40037, 40038, 40039, 40040, 40041]
這里,search()返回的消息ID列表(針對7月5日以來接收的消息)保存在UIDs中。計算機上返回的UIDs列表與這里顯示的不同,它們對于特定的電子郵件賬戶是唯一的。如果你稍后將UID傳遞給其他函數調用,請用你收到的UID值,而不是本書例子中打印的。
如果你的搜索匹配大量的電子郵件,Python可能拋出異常imaplib.error: got more than 10000 bytes。如果發生這種情況,必須斷開并重連IMAP服務器,然后再試。
這個限制是防止Python程序消耗太多內存。遺憾的是,默認大小限制往往太小。可以執行下面的代碼,將限制從10000字節改為10000000字節:
>>> import imaplib
>>> imaplib._MAXLINE=10000000
這應該能避免該錯誤消息再次出現。也許要在你寫的每一個IMAP程序中加上這兩行。
得到UID的列表后,可以調用IMAPClient對象的fetch()方法,獲得實際的電子郵件內容。
UID列表是fetch()的第一個參數。第二個參數應該是[‘BODY[]’],它告訴fetch()下載UID列表中指定電子郵件的所有正文內容。
{使用IMAPClient的gmail_search()方法!!}
如果登錄到imap.gmail.com服務器來訪問Gmail賬戶,IMAPClient對象提供了一個額外的搜索函數,模擬Gmail網頁頂部的搜索欄,如圖16-1中高亮的部分所示。
除了用IMAP搜索鍵搜索,可以使用Gmail更先進的搜索引擎。Gmail在匹配密切相關的單詞方面做得很好(例如,搜索driving也會匹配drive和drove),并按照匹配的程度對搜索結果排序。也可以使用Gmail的高級搜索操作符(更多信息請參見http://nostarch.com/automatestuff/)。如果登錄到Gmail賬戶,向gmail_search()方法傳入搜索條件,而不是search()方法,就像下面交互式環境的例子:
>>> UIDs=imapObj.gmail_search('meaning of life')
>> UIDs
[42]
啊,是的,那封電子郵件包含了生命的意義!我一直在期待。
圖16-1 在Gmail網頁頂部的搜索欄
讓我們繼續交互式環境的例子。
>>> rawMessages=imapObj.fetch(UIDs, ['BODY[]'])
>>> import pprint
>>> pprint.pprint(rawMessages)
{40040: {'BODY[]': 'Delivered-To: my_email_address@gmail.com\r\n'
'Received: by 10.76.71.167 with SMTP id '
--snip--
'\r\n'
'------=_Part_6000970_707736290.1404819487066--\r\n',
'SEQ': 5430}}
導入 pprint,將 fetch()的返回值(保存在變量 rawMessages 中)傳入pprint.pprint(),“漂亮打印”它。你會看到,這個返回值是消息的嵌套字典,其中以UID作為鍵。每條消息都保存為一個字典,包含兩個鍵:’BODY[]’和’SEQ’。’BODY[]’鍵映射到電子郵件的實際正文。’SEQ’鍵是序列號,它與UID的作用類似。你可以放心地忽略它。
正如你所看到的,在’BODY[]’鍵中的消息內容是相當難理解的。這種格式稱為RFC822,是專為IMAP服務器讀取而設計的。但你并不需要理解RFC 822格式,本章稍后的pyzmail模塊將替你來理解它。
如果你選擇一個文件夾進行搜索,就用readonly=True關鍵字參數來調用select_ folder()。這樣做可以防止意外刪除電子郵件,但這也意味著你用fetch()方法獲取郵件時,它們不會標記為已讀。如果確實希望在獲取郵件時將它們標記已讀,就需要將readonly=False傳入select_folder()。如果所選文件夾已處于只讀模式,可以用另一個 select_folder()調用重新選擇當前文件夾,這次用readonly=False關鍵字參數:
>>> imapObj.select_folder('INBOX', readonly=False)
對于只想讀郵件的人來說,fetch()方法返回的原始消息仍然不太有用。pyzmail模塊解析這些原始消息,將它們作為PyzMessage對象返回,使郵件的主題、正文、“收件人”字段、“發件人”字段和其他部分能用Python代碼輕松訪問。
用下面的代碼繼續交互式環境的例子(使用你自己的郵件賬戶的UID,而不是這里顯示的):
>>> import pyzmail>>> message=pyzmail.PyzMessage.factory(rawMessages[40041]['BODY[]'])
首先,導入pyzmail。然后,為了創建一個電子郵件的PyzMessage對象,調用pyzmail.PeekMessage.factory()函數,并傳入原始郵件的’BODY[]’部分。結果保存在message中。現在,message中包含一個PyzMessage對象,它有幾個方法,可以很容易地獲得的電子郵件主題行,以及所有發件人和收件人的地址。get_subject()方法將主題返回為一個簡單字符串。get_addresses()方法針對傳入的字段,返回一個地址列表。例如,該方法調用可能像這樣:
>>> message.get_subject()
'Hello!'
>>> message.get_addresses('from')
[('Edward Snowden', 'esnowden@nsa.gov')]
>>> message.get_addresses('to')
[(Jane Doe', 'my_email_address@gmail.com')]
>>> message.get_addresses('cc')
[]
>>> message.get_addresses('bcc')
[]
請注意,get_addresses()的參數是’from’、’to’、’cc’或 ‘bcc’。get_addresses()的返回值是一個元組列表。每個元組包含兩個字符串:第一個是與該電子郵件地址關聯的名稱,第二個是電子郵件地址本身。如果請求的字段中沒有地址,get_addresses()返回一個空列表。在這里,’cc’抄送和’bcc’密件抄送字段都沒有包含地址,所以返回空列表。
電子郵件可以是純文本、HTML 或兩者的混合。純文本電子郵件只包含文本,而HTML電子郵件可以有顏色、字體、圖像和其他功能,使得電子郵件看起來像一個小網頁。如果電子郵件僅僅是純文本,它的PyzMessage對象會將html_part屬性設為None。同樣,如果電子郵件只是HTML,它的PyzMessage對象會將text_part屬性設為None。
否則,text_part或html_part將有一個get_payload()方法,將電子郵件的正文返回為bytes數據類型(bytes數據類型超出了本書的范圍)。但是,這仍然不是我們可以使用的字符串。啊!最后一步對get_payload()返回的bytes值調用decode()方法。decode()方法接受一個參數:這條消息的字符編碼,保存在text_part.charset或html_part.charset屬性中。最后,這返回了郵件正文的字符串。
輸入以下代碼,繼續交互式環境的例子:
? >>> message.text_part !=None
True
>>> message.text_part.get_payload().decode(message.text_part.charset)
? 'So long, and thanks for all the fish!\r\n\r\n-Al\r\n'
? >>> message.html_part !=None
True
? >>> message.html_part.get_payload().decode(message.html_part.charset)
'< div dir="ltr">< div>So long, and thanks for all the fish!< br>< br>< /div>-Al
< br>< /div>\r\n'
我們正在處理的電子郵件包含純文本和HTML內容,因此保存在message中的PyzMessage對象的text_part和html_part屬性不等于None??。對消息的text_part調用get_payload(),然后在bytes值上調用decode(),返回電子郵件的文本版本的字符串?。對消息的html_part調用get_payload()和decode(),返回電子郵件的HTML版本的字符串?。
要刪除電子郵件,就向IMAPClient對象的delete_messages()方法傳入一個消息UID的列表。這為電子郵件加上\Deleted標志。調用expunge()方法,將永久刪除當前選中的文件夾中帶\Deleted標志的所有電子郵件。請看下面的交互式環境的例子:
? >>> imapObj.select_folder('INBOX', readonly=False)
? >>> UIDs=imapObj.search(['ON 09-Jul-2015'])
>>> UIDs
[40066]
>>> imapObj.delete_messages(UIDs)
? {40066: ('\\\Seen', '\\\Deleted')}
>>> imapObj.expunge()
('Success', [(5452, 'EXISTS')])
這里,我們調用了IMAPClient對象的select_folder()方法,傳入’INBOX’作為第一個參數,選擇了收件箱。我們也傳入了關鍵字參數readonly=False,這樣我們就可以刪除電子郵件?。我們搜索收件箱中的特定日期收到的消息,將返回的消息ID保存在UIDs中?。調用delete_message()并傳入UIDs,返回一個字典,其中每個鍵值對是一個消息 ID 和消息標志的元組,它現在應該包含\Deleted標志?。然后調用expunge(),永久刪除帶\Deleted標志的郵件。如果清除郵件沒有問題,就返回一條成功信息。請注意,一些電子郵件提供商,如Gmail,會自動清除用delete_messages()刪除的電子郵件,而不是等待來自IMAP客戶端的expunge命令。
如果程序已經完成了獲取和刪除電子郵件,就調用IMAPClient的logout()方法,從IMAP服務器斷開連接。
>>> imapObj.logout()
如果程序運行了幾分鐘或更長時間,IMAP服務器可能會超時,或自動斷開。在這種情況下,接下來程序對IMAPClient對象的方法調用會拋出異常,像下面這樣:
imaplib.abort: socket error: [WinError 10054] An existing connection was
forcibly closed by the remote host
在這種情況下,程序必須調用imapclient.IMAPClient(),再次連接。
喲!齊活了。要跳過很多圈圈,但你現在有辦法讓Python程序登錄到一個電子郵件賬戶,并獲取電子郵件。需要回憶所有步驟時,你可以隨時參考16.4節“用IMAP獲取和刪除電子郵件”。
假定你一直“自愿”為“強制自愿俱樂部”記錄會員會費。這確實是一項枯燥的工作,包括維護一個電子表格,記錄每個月誰交了會費,并用電子郵件提醒那些沒交的會員。不必你自己查看電子表格,而是向會費超期的會員復制和粘貼相同的電子郵件。你猜對了,讓我們編寫一個腳本,幫你完成任務。
在較高的層面上,下面是程序要做的事:
這意味著代碼需要做到以下幾點:
打開一個新的文件編輯器窗口,并保存為sendDuesReminders.py。
假定用來記錄會費支付的 Excel 電子表格看起來如圖 16-2 所示,放在名為duesRecords.xlsx的文件中。可以從http://nostarch.com/automatestuff/下載該文件。
圖16-2 記錄會員會費支付電子表格
該電子表格中包含每個成員的姓名和電子郵件地址。每個月有一列,記錄會員的付款狀態。在成員交納會費后,對應的單元格就記為paid。
該程序必須打開duesRecords.xlsx,通過調用get_highest_column()方法,弄清楚最近一個月的列(可以參考第12章,了解用openpyxl模塊訪問Excel電子表格文件單元格的更多信息)。在文件編輯器窗口中輸入以下代碼:
#! python3
# sendDuesReminders.py - Sends emails based on payment status in spreadsheet.
import openpyxl, smtplib, sys
# Open the spreadsheet and get the latest dues status.
? wb=openpyxl.load_workbook('duesRecords.xlsx')
? sheet=wb.get_sheet_by_name('Sheet1')
? lastCol=sheet.get_highest_column()
? latestMonth=sheet.cell(row=1, column=lastCol).value
# TODO: Check each member's payment status.
# TODO: Log in to email account.
# TODO: Send out reminder emails.
導入openpyxl、smtplib和sys模塊后,我們打開duesRecords.xlsx文件,將得到的Workbook對象保存在wb中?。然后,取得Sheet 1,將得到的Worksheet對象保存在sheet中?。既然有了Worksheet對象,就可以訪問行、列和單元格。我們將最后一列保存在lastCol中?,然后用行號1和lastCol來訪問應該記錄著最近月份的單元格。取得該單元格的值,并保存在latestMonth 中?。
一旦確定了最近一個月的列數(保存在lastCol中),就可以循環遍歷第一行(這是列標題)之后的所有行,看看哪些成員在該月會費的單元格中寫著paid。如果會員沒有支付,就可以從列1和2中分別抓取成員的姓名和電子郵件地址。這些信息將放入unpaidMembers字典,它記錄最近一個月沒有交費的所有成員。將以下代碼添加到sendDuesReminder.py中。
#! python3
# sendDuesReminders.py - Sends emails based on payment status in spreadsheet.
--snip--
# Check each member's payment status.
unpaidMembers={}
? for r in range(2, sheet.get_highest_row() + 1):
? payment=sheet.cell(row=r, column=lastCol).value
if payment !='paid':
? name=sheet.cell(row=r, column=1).value
? email=sheet.cell(row=r, column=2).value
? unpaidMembers[name]=email
這段代碼設置了一個空字典unpaidMembers,然后循環遍歷第一行之后所有的行?。對于每一行,最近月份的值保存在payment中?。如果payment不等于’paid’,則第一列的值保存在name中?,第二列的值保存在email中?,name和email添加到unpaidMembers中?。
得到所有未付費成員的名單后,就可以向他們發送電子郵件提醒了。將下面的代碼添加到程序中,但要代入你的真實電子郵件地址和提供商的信息:
#! python3
# sendDuesReminders.py - Sends emails based on payment status in spreadsheet.
--snip--
# Log in to email account.
smtpObj=smtplib.SMTP('smtp.gmail.com', 587)
smtpObj.ehlo()
smtpObj.starttls()
smtpObj.login('my_email_address@gmail.com', sys.argv[1])
調用smtplib.SMTP()并傳入提供商的域名和端口,創建一個SMTP對象。調用ehlo()和starttls(),然后調用login(),并傳入你的電子郵件地址和sys.argv[1],其中保存著你的密碼字符串。在每次運行程序時,將密碼作為命令行參數輸入,避免在源代碼中保存密碼。
程序登錄到你的電子郵件賬戶后,就應該遍歷unpaidMembers字典,向每個會員的電子郵件地址發送針對個人的電子郵件。將以下代碼添加到sendDuesReminders.py:
#! python3
# sendDuesReminders.py - Sends emails based on payment status in spreadsheet.
--snip--
# Send out reminder emails.
for name, email in unpaidMembers.items():
? body="Subject: %s dues unpaid.\nDear %s,\nRecords show that you have not
paid dues for %s. Please make this payment as soon as possible. Thank you!'" %
(latestMonth, name, latestMonth)
? print('Sending email to %s...' % email)
? sendmailStatus=smtpObj.sendmail('my_email_address@gmail.com', email, body)
? if sendmailStatus !={}:
print('There was a problem sending email to %s: %s' % (email,
sendmailStatus))
smtpObj.quit()
這段代碼循環遍歷unpaidMembers中的姓名和電子郵件。對于每個沒有付費的成員,我們用最新的月份和成員的名稱,定制了一條消息,并保存在body中?。我們打印輸出,表示正在向這個會員的電子郵件地址發送電子郵件?。然后調用sendmail(),向它傳入地址和定制的消息?。返回值保存在sendmailStatus中。
回憶一下,如果SMTP服務器在發送某個電子郵件時報告錯誤,sendmail()方法將返回一個非空的字典值。for循環最后部分在?行檢查返回的字典是否非空,如果非空,則打印收件人的電子郵件地址以及返回的字典。
程序完成發送所有電子郵件后,調用quit()方法,與SMTP服務器斷開連接。
如果運行該程序,輸出會像這樣:
Sending email to alice@example.com...
Sending email to bob@example.com...
Sending email to eve@example.com...
收件人將收到如圖16-3所示的電子郵件。
圖16-3 從sendDuesReminders.py自動發送的電子郵件
大多數人更可能靠近自己的手機,而不是自己的電腦,所以與電子郵件相比,短信發送通知可能更直接、可靠。此外,短信的長度較短,讓人更有可能閱讀它們。
在本節中,你將學習如何注冊免費的Twilio服務,并用它的Python模塊發送短信。Twilio是一個SMS網關服務,這意味著它是一種服務,讓你通過程序發送短信。雖然每月發送多少短信會有限制,并且文本前面會加上Sent from a Twilio trial account,但這項試用服務也許能滿足你的個人程序。免費試用沒有限期,不必以后升級到付費的套餐。
Twilio不是唯一的SMS網關服務。如果你不喜歡使用Twilio,可以在線搜索free sms gateway、python sms api,甚至twilio alternatives,尋找替代服務。
注冊Twilio賬戶之前,先安裝twilio模塊。附錄A詳細介紹了如何安裝第三方模塊。
本節特別針對美國。Twilio 確實也在美國以外的國家提供手機短信服務,本書并不包括這些細節。但twilio 模塊及其功能,在美國以外的國家也能用。更多信息請參見http://twilio.com/。
訪問http://twilio.com/并填寫注冊表單。注冊了新賬戶后,你需要驗證一個手機號碼,短信將發給該號碼(這項驗證是必要的,防止有人利用該服務向任意的手機號碼發送垃圾短信)。
收到驗證號碼短信后,在Twilio網站上輸入它,證明你擁有要驗證的手機。現在,就可以用twilio模塊向這個電話號碼發送短信了。
Twilio提供的試用賬戶包括一個電話號碼,它將作為短信的發送者。你將需要兩個信息:你的賬戶SID和AUTH(認證)標志。在登錄Twilio賬戶時,可以在Dashboard頁面上找到這些信息。從Python程序登錄時,這些值將作為你的Twilio用戶名和密碼。
一旦安裝了twilio模塊,注冊了Twilio賬號,驗證了你的手機號碼,登記了Twilio電話號碼,獲得了賬戶的SID和auth標志,你就終于準備好通過Python腳本向你自己發短信了。
與所有的注冊步驟相比,實際的Python代碼很簡單。保持計算機連接到因特網,在交互式環境中輸入以下代碼,用你的真實信息替換accountSID、authToken、myTwilioNumber和myCellPhone變量的值:
? >>> from twilio.rest import TwilioRestClient
>>> accountSID='ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
>>> authToken='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
? >>> twilioCli=TwilioRestClient(accountSID, authToken)
>>> myTwilioNumber='+14955551234'
>>> myCellPhone='+14955558888'
? >>> message=twilioCli.messages.create(body='Mr. Watson - Come here - I want
to see you.', from_=myTwilioNumber, to=myCellPhone)
鍵入最后一行后不久,你會收到一條短信,內容為:Sent from your Twilio trial account - Mr. Watson - Come here – I want to see you.。
因為twilio模塊的設計方式,導入它時需要使用from twilio.rest import TwilioRestClient,而不僅僅是import twilio?。將賬戶的SID保存在accountSID,認證標志保存在authToken中,然后調用TwilioRestClient(),并傳入accountSID和authToken。TwilioRestClient()調用返回一個TwilioRestClient對象?。該對象有一個message屬性,該屬性又有一個create()方法,可以用來發送短信。正是這個方法,將告訴Twilio的服務器發送短信。將你的Twilio號碼和手機號碼分別保存在myTwilioNumber和myCellPhone中,然后調用create(),傳入關鍵字參數,指明短信的正文、發件人的號碼(myTwilioNumber),以及收信人的電話號碼(myCellPhone)?
create()方法返回的Message對象將包含已發送短信的相關信息。輸入以下代碼,繼續交互式環境的例子:
>>> message.to
'+14955558888'
>>> message.from_
'+14955551234'
>>> message.body
'Mr. Watson - Come here - I want to see you.'
to、from和body屬性應該分別保存了你的手機號碼、Twilio號碼和消息。請注意,發送手機號碼是在from屬性中,末尾有一個下劃線,而不是from。這是因為from是一個Python關鍵字(例如,你在from modulename import *形式的import語句中見過它),所以它不能作為一個屬性名。輸入以下代碼,繼續交互式環境的例子:
>>> message.status
'queued'
>>> message.date_created
datetime.datetime(2015, 7, 8, 1, 36, 18)
>>> message.date_sent==None
True
status 屬性應該包含一個字符串。如果消息被創建和發送,date_created 和date_sent屬性應該包含一個datetime對象。如果已收到短信,而status屬性卻設置為’queued’,date_sent屬性設置為None,這似乎有點奇怪。這是因為你先將Message對象記錄在message變量中,然后短信才實際發送。你需要重新獲取Message對象,查看它最新的status和date_sent。每個Twilio消息都有唯一的字符串ID(SID),可用于獲取Message對象的最新更新。輸入以下代碼,繼續交互式環境的例子:
>>> message.sid
'SM09520de7639ba3af137c6fcb7c5f4b51'
? >>> updatedMessage=twilioCli.messages.get(message.sid)
>>> updatedMessage.status
'delivered'
>>> updatedMessage.date_sent
datetime.datetime(2015, 7, 8, 1, 36, 18)
輸入message.sid將顯示這個消息的SID。將這個SID傳入Twilio客戶端的get()方法?,你可以取得一個新的Message對象,包含最新的信息。在這個新的Message對象中,status和date_sent屬性是正確的。
status屬性將設置為下列字符串之一:’queued’、’sending’、’sent’、’delivered’、’undelivered’或’failed’。這些狀態不言自明,但對于更準確的細節,請查看http://nostarch. com/automatestuff/的資源。
{用Python接收短信!!}
遺憾的是,用Twilio接收短信比發送短信更復雜一些。Twilio需要你有一個網站,運行自己的Web應用程序。這已超出了本書的范圍,但你可以在本書的資源中找到更多細節(http://nostarch.com/automatestuff/)。
最常用你的程序發短信的人可能就是你。當你遠離計算機時,短信是通知你自己的好方式。如果你已經用程序自動化了一個無聊的任務,它需要運行幾小時,你可以在它完成時,讓它用短信通知你。或者可以定期運行某個程序,它有時需要與你聯系,例如天氣檢查程序,用短信提醒你帶傘。
舉一個簡單的例子,下面是一個Python小程序,包含了textmyself()函數,它將傳入的字符串參數作為短信發出。打開一個新的文件編輯器窗口,輸入以下代碼,用自己的信息替換帳戶SID,認證標志和電話號碼。將它保存為textMyself.py。
#! python3
# textMyself.py - Defines the textmyself() function that texts a message
# passed to it as a string.
# Preset values:
accountSID='ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
authToken='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
myNumber='+15559998888'
twilioNumber='+15552225678'
from twilio.rest import TwilioRestClient
? def textmyself(message):
? twilioCli=TwilioRestClient(accountSID, authToken)
? twilioCli.messages.create(body=message, from_=twilioNumber, to=myNumber)
該程序保存了賬戶的SID、認證標志、發送號碼及接收號碼。然后它定義了textmyself(),接收參數?,創建TwilioRestClient對象?,并用你傳入的消息調用create()?。
如果你想讓其他程序使用textmyself()函數,只需將textMyself.py文件和Python的可執行文件放在同一個文件夾中(Windows上是C:\Python34,OS X上是/usr/local/lib/python3.4,Linux上是/usr/bin/python3)。現在,你可以在其他程序中使用該函數。只要想在程序中發短信給你,就添加以下代碼:
import textmyself
textmyself.textmyself('The boring task is finished.')
注冊Twilio和編寫短信代碼只要做一次。在此之后,從任何其他程序中發短信,只要兩行代碼。
通過因特網和手機網絡,我們用幾十種不同的方式相互通信,但以電子郵件和短信為主。你的程序可以通過這些渠道溝通,這給它們帶來強大的新通知功能。甚至可以編程運行在不同的計算機上,相互直接通過電子郵件能信,一個程序用SMTP發送電子郵件,另一個用IMAP收取。
Python 的 smtplib 提供了一些函數,利用 SMTP,通過電子郵件提供商的SMTP服務器發送電子郵件。同樣,第三方的imapclient和pyzmail模塊讓你訪問IMAP服務器,并取回發送給你的電子郵件。雖然IMAP比SMTP復雜一些,但它也相當強大,允許你搜索特定電子郵件、下載它們、解析它們,提取主題和正文作為字符串值。
短信與電子郵件有點不同,因為它不像電子郵件,發送短信不僅需要互聯網連接。好在,像Twilio這樣的服務提供了模塊,允許你通過程序發送短信。一旦通過了初始設置過程,就能夠只用幾行代碼來發送短信。掌握了這些模塊,就可以針對特定的情況編程,在這些情況下發送通知或提醒。現在,你的程序將超越運行它們的計算機!
本文摘自《Python編程快速上手 讓繁瑣工作自動化》
本書是一本面向實踐的Python編程實用指南。本書的目的,不僅是介紹Python語言的基礎知識,而且還通過項目實踐教會讀者如何應用這些知識和技能。本書的第一部分介紹了基本的Python編程概念,第二部分介紹了一些不同的任務,通過編寫Python程序,可以讓計算機自動完成它們。第二部分的每一章都有一些項目程序,供讀者學習。每章的末尾還提供了一些習題和深入的實踐項目,幫助讀者鞏固所學的知識。附錄部分提供了所有習題的解答。
本書適合任何想要通過Python學習編程的讀者,尤其適合缺乏編程基礎的初學者。通過閱讀本書,讀者將能利用最強大的編程語言和工具,并且將體會到Python編程的快樂。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。