Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
mtplib 簡(jiǎn)單郵件傳輸協(xié)議 simble mail transfer protocol library
import smtplib 引入的包
import email 多用戶郵件擴(kuò)充協(xié)議
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
文基于:Spring Boot 2.1.3,理論支持Spring Boot 2.x所有版本。
最近有童鞋問(wèn)到筆者如何用Spring Boot發(fā)送郵件,故而整理下Spring Boot發(fā)送郵件的各種姿勢(shì)。
說(shuō)到郵件放松,相信大家對(duì)Spring Framework提供的接口 JavaMailSender 都不陌生。那么Spring Boot是否有開箱即用的郵件發(fā)送呢?
答案是肯定的。Spring Boot為發(fā)送郵件提供了starter:spring-boot-starter-mail 。
本文詳細(xì)探討如何用Spring Boot發(fā)送郵件。
一、郵箱配置
以126郵箱為例:
1 開啟SMTP服務(wù)
2 設(shè)置/重置客戶端授權(quán)密碼
二、編碼
2.1 準(zhǔn)備工作
1 加依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency>
2 寫配置
spring: mail: host: smtp.126.com username: eacdy0000@126.com password: 上面設(shè)置的授權(quán)碼
2.2 發(fā)送簡(jiǎn)單郵件
public String simple() { SimpleMailMessage message=new SimpleMailMessage(); // 發(fā)件人郵箱 message.setFrom(this.mailProperties.getUsername()); // 收信人郵箱 message.setTo("511932633@qq.com"); // 郵件主題 message.setSubject("簡(jiǎn)單郵件測(cè)試"); // 郵件內(nèi)容 message.setText("簡(jiǎn)單郵件測(cè)試"); this.javaMailSender.send(message); return "success"; }
結(jié)果類似下圖:
2.3 發(fā)送HTML郵件
簡(jiǎn)單郵件是沒(méi)有樣式的,很多時(shí)候,我們希望發(fā)送的郵件內(nèi)容帶有樣式,此時(shí)可發(fā)送HTML郵件。
public String html() throws MessagingException { MimeMessage message=javaMailSender.createMimeMessage(); MimeMessageHelper messageHelper=new MimeMessageHelper(message); messageHelper.setFrom(this.mailProperties.getUsername()); messageHelper.setTo("511932633@qq.com"); messageHelper.setSubject("HTML內(nèi)容郵件測(cè)試"); // 第二個(gè)參數(shù)表示是否html,設(shè)為true messageHelper.setText("<h1>HTML內(nèi)容..</h1>", true); this.javaMailSender.send(message); return "success"; }
結(jié)果類似下圖:
2.4 發(fā)送帶附件的郵件
很多場(chǎng)景下,需要為郵件插入附件,此時(shí)該怎么辦呢?繼續(xù)上代碼——
@GetMapping("/attach") public String attach() throws MessagingException { MimeMessage message=this.javaMailSender.createMimeMessage(); // 第二個(gè)參數(shù)表示是否開啟multipart模式 MimeMessageHelper messageHelper=new MimeMessageHelper(message, true); messageHelper.setFrom(this.mailProperties.getUsername()); messageHelper.setTo("511932633@qq.com"); messageHelper.setSubject("帶附件的郵件測(cè)試"); // 第二個(gè)參數(shù)表示是否html,設(shè)為true messageHelper.setText("<h1>HTML內(nèi)容..</h1>", true); messageHelper.addAttachment("附件名稱", new ClassPathResource("wx.jpg")); this.javaMailSender.send(message); return "success"; }
結(jié)果類似下圖:
2.5 發(fā)送帶內(nèi)聯(lián)附件的郵件
附件 + HTML基本能滿足日常工作中多數(shù)需求。但如果能將附件內(nèi)聯(lián)在郵件內(nèi)容中,那么體驗(yàn)就更好啦!如何實(shí)現(xiàn)附件的內(nèi)聯(lián)呢?
@GetMapping("/inline-attach") public String inlineAttach() throws MessagingException { MimeMessage message=this.javaMailSender.createMimeMessage(); // 第二個(gè)參數(shù)表示是否開啟multipart模式 MimeMessageHelper messageHelper=new MimeMessageHelper(message, true); messageHelper.setFrom(this.mailProperties.getUsername()); messageHelper.setTo("511932633@qq.com"); messageHelper.setSubject("內(nèi)聯(lián)附件的郵件測(cè)試"); // 第二個(gè)參數(shù)表示是否html,設(shè)為true messageHelper.setText("<h1>HTML內(nèi)容..<img src=\"cid:attach\"/></h1>", true); messageHelper.addInline("attach", new ClassPathResource("wx.jpg")); this.javaMailSender.send(message); return "success"; }
由代碼可知,只需在想要內(nèi)聯(lián)的地方使用 cid:xx 引用內(nèi)聯(lián)附件,然后用 addInline(xx, file)指定附件即可。兩處的 xx 必須一致。
結(jié)果類似下圖:
2.6 發(fā)送基于Freemarker模板的郵件
上面的例子中,郵件內(nèi)容是直接以字符串體現(xiàn)的,這通常不適合生產(chǎn),因?yàn)閷?shí)際項(xiàng)目中郵件往往帶有變量。此時(shí),可考慮使用Freemarker模板(或者其他模板,Spring Boot 2.x默認(rèn)支持Freemarker、Groovy、Thymeleaf、Mustache四種模板引擎,也可根據(jù)需求使用其他模板引擎)。
?
創(chuàng)建Freemarker模板文件mail.ftl,并將其存放在resources/templates/ 目錄中
<h1>親愛(ài)的${username}, 歡迎關(guān)注${event}</h1>
?
編碼:
@GetMapping("/freemarker") public String freemarker() throws MessagingException, IOException, TemplateException { MimeMessage message=this.javaMailSender.createMimeMessage(); // 第二個(gè)參數(shù)表示是否開啟multipart模式 MimeMessageHelper messageHelper=new MimeMessageHelper(message, true); messageHelper.setFrom(this.mailProperties.getUsername()); messageHelper.setTo("511932633@qq.com"); messageHelper.setSubject("基于freemarker模板的郵件測(cè)試"); Map<String, Object> model=new HashMap<>(); model.put("username", "itmuch"); model.put("event", "IT牧場(chǎng)大事件"); String content=FreeMarkerTemplateUtils.processTemplateIntoString( this.freemarkerConfiguration.getTemplate("mail.ftl"), model); // 第二個(gè)參數(shù)表示是否html,設(shè)為true messageHelper.setText(content, true); this.javaMailSender.send(message); return "success"; }
此時(shí),結(jié)果類似下圖:
三、配套代碼
?GitHub[1]
?Gitee[2]
干貨分享
最近將個(gè)人學(xué)習(xí)筆記整理成冊(cè),使用PDF分享。關(guān)注我,回復(fù)如下代碼,即可獲得百度盤地址,無(wú)套路領(lǐng)取!
?001:《Java并發(fā)與高并發(fā)解決方案》學(xué)習(xí)筆記;
?002:《深入JVM內(nèi)核——原理、診斷與優(yōu)化》學(xué)習(xí)筆記;
?003:《Java面試寶典》
?004:《Docker開源書》
?005:《Kubernetes開源書》
?006:《DDD速成(領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)速成)》
References
[1] GitHub: https://github.com/eacdy/spring-boot-study/tree/master/spring-boot-mail
[2] Gitee: https://gitee.com/itmuch/spring-boot-study/tree/master/spring-boot-mail
查和答復(fù)電子郵件會(huì)占用大量的時(shí)間。當(dāng)然,你不能只寫一個(gè)程序來(lái)處理所有電子郵件,因?yàn)槊總€(gè)消息都需要有自己的回應(yīng)。但是,一旦知道怎么編寫收發(fā)電子郵件的程序,就可以自動(dòng)化大量與電子郵件相關(guān)的任務(wù)。
例如,也許你有一個(gè)電子表格,包含許多客戶記錄,希望根據(jù)他們的年齡和位置信息,向每個(gè)客戶發(fā)送不同格式的郵件。商業(yè)軟件可能無(wú)法做這一點(diǎn)。好在,可以編寫自己的程序來(lái)發(fā)送這些電子郵件,節(jié)省了大量復(fù)制和粘貼電子郵件的時(shí)間。
也可以編程發(fā)送電子郵件和短信,即使你遠(yuǎn)離計(jì)算機(jī)時(shí),也能通知你。如果要自動(dòng)化的任務(wù)需要執(zhí)行幾個(gè)小時(shí),你不希望每過(guò)幾分鐘就回到計(jì)算機(jī)旁邊,檢查程序的狀態(tài)。相反,程序可以在完成時(shí)向手機(jī)發(fā)短信,讓你在離開計(jì)算機(jī)時(shí),能專注于更重要的事情。
正如HTTP是計(jì)算機(jī)用來(lái)通過(guò)因特網(wǎng)發(fā)送網(wǎng)頁(yè)的協(xié)議,簡(jiǎn)單郵件傳輸協(xié)議(SMTP)是用于發(fā)送電子郵件的協(xié)議。SMTP 規(guī)定電子郵件應(yīng)該如何格式化、加密、在郵件服務(wù)器之間傳遞,以及在你點(diǎn)擊發(fā)送后,計(jì)算機(jī)要處理的所有其他細(xì)節(jié)。但是,你并不需要知道這些技術(shù)細(xì)節(jié),因?yàn)镻ython的smtplib模塊將它們簡(jiǎn)化成幾個(gè)函數(shù)。
SMTP只負(fù)責(zé)向別人發(fā)送電子郵件。另一個(gè)協(xié)議,名為IMAP,負(fù)責(zé)取回發(fā)送給你的電子郵件,在16.3節(jié)“IMAP”中介紹。
你可能對(duì)發(fā)送電子郵件很熟悉,通過(guò)Outlook、Thunderbird或某個(gè)網(wǎng)站,如Gmail或雅虎郵箱。遺憾的是,Python沒(méi)有像這些服務(wù)一樣提供一個(gè)漂亮的圖形用戶界面。作為替代,你調(diào)用函數(shù)來(lái)執(zhí)行SMTP的每個(gè)重要步驟,就像下面的交互式環(huán)境的例子。
{注意}
不要在IDLE中輸入這個(gè)例子,因?yàn)閟mtp.example.com、bob@example.com、MY_ SECRET_PASSWORD和alice@example.com只是占位符。這段代碼僅僅勾勒出Python發(fā)送電子郵件的過(guò)程。
>>> 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')
在下面的小節(jié)中,我們將探討每一步,用你的信息替換占位符,連接并登錄到SMTP服務(wù)器,發(fā)送電子郵件,并從服務(wù)器斷開連接。
如果你曾設(shè)置了Thunderbird、Outlook或其他程序,連接到你的電子郵件賬戶,你可能熟悉配置SMTP服務(wù)器和端口。這些設(shè)置因電子郵件提供商而不同,但在網(wǎng)上搜索“< 你的提供商> SMTP設(shè)置”,應(yīng)該能找到相應(yīng)的服務(wù)器和端口。
SMTP服務(wù)器的域名通常是電子郵件提供商的域名,前面加上SMTP。例如,Gmail的 SMTP 服務(wù)器是smtp.gmail.com。表 16-1 列出了一些常見的電子郵件提供商及其SMTP服務(wù)器(端口是一個(gè)整數(shù)值,幾乎總是587,該端口由命令加密標(biāo)準(zhǔn)TLS使用)。
表16-1 電子郵件提供商及其SMTP服務(wù)器
得到電子郵件提供商的域名和端口信息后,調(diào)用smtplib.SMTP()創(chuàng)建一個(gè)SMTP對(duì)象,傳入域名作為一個(gè)字符串參數(shù),傳入端口作為整數(shù)參數(shù)。SMTP對(duì)象表示與SMTP郵件服務(wù)器的連接,它有一些發(fā)送電子郵件的方法。例如,下面的調(diào)用創(chuàng)建了一個(gè)SMTP對(duì)象,連接到Gmail:
>>> smtpObj=smtplib.SMTP('smtp.gmail.com', 587)
>>> type(smtpObj)
< class 'smtplib.SMTP'>
輸入type(smtpObj)表明,smtpObj中保存了一個(gè)SMTP對(duì)象。你需要這個(gè)SMTP對(duì)象,以便調(diào)用它的方法,登錄并發(fā)送電子郵件。如果smtplib.SMTP()調(diào)用不成功,你的SMTP服務(wù)器可能不支持TLS端口587。在這種情況下,你需要利用smtplib.SMTP_SSL()和465端口,來(lái)創(chuàng)建SMTP對(duì)象。
>>> smtpObj=smtplib.SMTP_SSL('smtp.gmail.com', 465)
{注意}
如果沒(méi)有連接到因特網(wǎng),Python將拋出socket.gaierror: [Errno 11004] getaddrinfo failed或類似的異常。
對(duì)于你的程序,TLS和SSL之間的區(qū)別并不重要。只需要知道你的SMTP服務(wù)器使用哪種加密標(biāo)準(zhǔn),這樣就知道如何連接它。在接下來(lái)的所有交互式環(huán)境示例中,smtpObj變量將包含smtplib.SMTP()或smtplib.SMTP_SSL()函數(shù)返回的SMTP對(duì)象。
得到SMTP對(duì)象后,調(diào)用它的名字古怪的EHLO()方法,向SMTP電子郵件服務(wù)器“打招呼”。這種問(wèn)候是SMTP中的第一步,對(duì)于建立到服務(wù)器的連接是很重要的。你不需要知道這些協(xié)議的細(xì)節(jié)。只要確保得到SMTP對(duì)象后,第一件事就是調(diào)用ehlo()方法,否則以后的方法調(diào)用會(huì)導(dǎo)致錯(cuò)誤。下面是一個(gè)ehlo()調(diào)用和返回值的例子:
>>> smtpObj.ehlo()
(250, b'mx.google.com at your service, [216.172.148.131]\nSIZE 35882577\
n8BITMIME\nSTARTTLS\nENHANCEDSTATUSCODES\nCHUNKING')
如果在返回的元組中,第一項(xiàng)是整數(shù)250(SMTP中“成功”的代碼),則問(wèn)候成功了。
如果要連接到SMTP服務(wù)器的587端口(即使用TLS加密),接下來(lái)需要調(diào)用starttls()方法。這是為連接實(shí)現(xiàn)加密必須的步驟。如果要連接到465端口(使用SSL),加密已經(jīng)設(shè)置好了,你應(yīng)該跳過(guò)這一步。
下面是starttls()方法調(diào)用的例子:
>>> smtpObj.starttls()
(220, b'2.0.0 Ready to start TLS')
starttls()讓SMTP連接處于TLS模式。返回值220告訴你,該服務(wù)器已準(zhǔn)備就緒。
到SMTP服務(wù)器的加密連接建立后,可以調(diào)用login()方法,用你的用戶名(通常是你的電子郵件地址)和電子郵件密碼登錄。
>>> smtpObj.login('my_email_address@gmail.com', 'MY_SECRET_PASSWORD')
(235, b'2.7.0 Accepted')
傳入電子郵件地址字符串作為第一個(gè)參數(shù),密碼字符串作為第二個(gè)參數(shù)。返回值235表示認(rèn)證成功。如果密碼不正確,Python會(huì)拋出smtplib. SMTPAuthenticationError異常。
將密碼放在源代碼中要當(dāng)心。如果有人復(fù)制了你的程序,他們就能訪問(wèn)你的電子郵件賬戶!調(diào)用input(),讓用戶輸入密碼是一個(gè)好主意。每次運(yùn)行程序時(shí)輸入密碼可能不方便,但這種方法不會(huì)在未加密的文件中留下你的密碼,黑客或筆記本電腦竊賊不會(huì)輕易地得到它。
登錄到電子郵件提供商的SMTP服務(wù)器后,可以調(diào)用的sendmail()方法來(lái)發(fā)送電子郵件。sendmail()方法調(diào)用看起來(lái)像這樣:
>>> 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()方法需要三個(gè)參數(shù)。
電子郵件正文字符串必須以’Subject: \n’開頭,作為電子郵件的主題行。’\n’換行符將主題行與電子郵件的正文分開。
sendmail()的返回值是一個(gè)字典。對(duì)于電子郵件傳送失敗的每個(gè)收件人,該字典中會(huì)有一個(gè)鍵值對(duì)。空的字典意味著對(duì)所有收件人已成功發(fā)送電子郵件。
{Gmail應(yīng)用程序?qū)S妹艽a!!}
Gmail有針對(duì)谷歌賬戶的附加安全功能,稱為應(yīng)用程序?qū)S妹艽a。如果當(dāng)你的程序試圖登錄時(shí),收到“需要應(yīng)用程序?qū)S妹艽a”的錯(cuò)誤信息,就必須在Python腳本設(shè)置這樣一個(gè)密碼。具體如何設(shè)置谷歌賬戶的應(yīng)用程序?qū)S妹艽a,參見http://nostarch.com/automatestuff/。
確保在完成發(fā)送電子郵件時(shí),調(diào)用quit()方法。這讓程序從SMTP服務(wù)器斷開。
>>> smtpObj.quit()
(221, b'2.0.0 closing connection ko10sm23097611pbd.52 - gsmtp')
返回值221表示會(huì)話結(jié)束。
要復(fù)習(xí)連接和登錄服務(wù)器、發(fā)送電子郵件和斷開的所有步驟,請(qǐng)參閱 16.2節(jié)“發(fā)送電子郵件”。
正如SMTP是用于發(fā)送電子郵件的協(xié)議,因特網(wǎng)消息訪問(wèn)協(xié)議(IMAP)規(guī)定了如何與電子郵件服務(wù)提供商的服務(wù)器通信,取回發(fā)送到你的電子郵件地址的電子郵件。Python帶有一個(gè)imaplib模塊,但實(shí)際上第三方的imapclient模塊更易用。本章介紹了如何使用IMAPClient,完整的文檔在http://imapclient.readthedocs.org/。
imapclient模塊從IMAP服務(wù)器下載電子郵件,格式相當(dāng)復(fù)雜。你很可能希望將它們從這種格式轉(zhuǎn)換成簡(jiǎn)單的字符串。pyzmail模塊替你完成解析這些郵件的辛苦工作。在http://www.magiksys.net/pyzmail/可以找到PyzMail的完整文檔。
從終端窗口安裝imapclient和pyzmail。附錄A包含了如何安裝第三方模塊的步驟。
在Python中,查找和獲取電子郵件是一個(gè)多步驟的過(guò)程,需要第三方模塊imapclient和pyzmail。作為概述,這里有一個(gè)完整的例子,包括登錄到IMAP服務(wù)器,搜索電子郵件,獲取它們,然后從中提取電子郵件的文本。
>>> 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()
你不必記住這些步驟。在詳細(xì)介紹每一步之后,你可以回來(lái)看這個(gè)概述,加強(qiáng)記憶。
就像你需要一個(gè)SMTP對(duì)象連接到SMTP服務(wù)器并發(fā)送電子郵件一樣,你需要一個(gè)IMAPClient對(duì)象,連接到IMAP服務(wù)器并接收電子郵件。首先,你需要電子郵件服務(wù)提供商的IMAP服務(wù)器域名。這和SMTP服務(wù)器的域名不同。表16-2列出了幾個(gè)流行的電子郵件服務(wù)提供商的IMAP服務(wù)器。
表16-2 電子郵件提供商及其IMAP服務(wù)器
得到IMAP服務(wù)器域名后,調(diào)用imapclient.IMAPClient()函數(shù),創(chuàng)建一個(gè)IMAPClient對(duì)象。大多數(shù)電子郵件提供商要求SSL加密,傳入SSL=TRUE關(guān)鍵字參數(shù)。在交互式環(huán)境中輸入以下代碼(使用你的提供商的域名):
>>> import imapclient
>>> imapObj=imapclient.IMAPClient('imap.gmail.com', ssl=True)
在接下來(lái)的小節(jié)里所有交互式環(huán)境的例子中,imapObj變量將包含imapclient.IMAPClient()函數(shù)返回的IMAPClient對(duì)象。在這里,客戶端是連接到服務(wù)器的對(duì)象。
取得IMAPClient對(duì)象后,調(diào)用它的login()方法,傳入用戶名(這通常是你的電子郵件地址)和密碼字符串。
>>> imapObj.login('my_email_address@gmail.com', 'MY_SECRET_PASSWORD')
'my_email_address@gmail.com Jane Doe authenticated (Success)'
要記住,永遠(yuǎn)不要直接在代碼中寫入密碼!應(yīng)該讓程序從input()接受輸入的密碼。
如果IMAP服務(wù)器拒絕用戶名/密碼的組合,Python會(huì)拋出imaplib.error異常。對(duì)于Gmail賬戶,你可能需要使用應(yīng)用程序?qū)S玫拿艽a。詳細(xì)信息請(qǐng)參閱16.2.5節(jié)中的“Gmail應(yīng)用程序?qū)S妹艽a”。
登錄后,實(shí)際獲取你感興趣的電子郵件分為兩步。首先,必須選擇要搜索的文件夾。然后,必須調(diào)用IMAPClient對(duì)象的search()方法,傳入IMAP搜索關(guān)鍵詞字符串。
幾乎每個(gè)賬戶默認(rèn)都有一個(gè)INBOX文件夾,但也可以調(diào)用IMAPClient對(duì)象的list_folders()方法,獲取文件夾列表。這將返回一個(gè)元組的列表。每個(gè)元組包含一個(gè)文件夾的信息。輸入以下代碼,繼續(xù)交互式環(huán)境的例子:
>>> import pprint
>>> pprint.pprint(imapObj.list_folders())
[(('\\\HasNoChildren',), '/', 'Drafts'),
(('\\\HasNoChildren',), '/', 'Filler'),
(('\\\HasNoChildren',), '/', 'INBOX'),
(('\\\HasNoChildren',), '/', 'Sent'),
--snip--
(('\\\HasNoChildren', '\\\Flagged'), '/', '[Gmail]/Starred'),
(('\\\HasNoChildren', '\\\Trash'), '/', '[Gmail]/Trash')]
如果你有一個(gè)Gmail賬戶,這就是輸出可能的樣子(Gmail將文件夾稱為label,但它們的工作方式與文件夾相同)。每個(gè)元組的三個(gè)值,例如 ((‘\HasNoChildren’,), ‘/‘, ‘INBOX’),解釋如下:
要選擇一個(gè)文件夾進(jìn)行搜索,就調(diào)用IMAPClient對(duì)象的select_folder()方法,傳入該文件夾的名稱字符串。
>>> imapObj.select_folder('INBOX', readonly=True)
可以忽略select_folder()的返回值。如果所選文件夾不存在,Python會(huì)拋出imaplib.error異常。
readonly=True關(guān)鍵字參數(shù)可以防止你在隨后的方法調(diào)用中,不小心更改或刪除該文件夾中的任何電子郵件。除非你想刪除的電子郵件,否則將readonly設(shè)置為True總是個(gè)好主意。
文件夾選中后,就可以用IMAPClient對(duì)象的search()方法搜索電子郵件。search()的參數(shù)是一個(gè)字符串列表,每一個(gè)格式化為IMAP搜索鍵。表16-3介紹了各種搜索鍵。
表16-3 IMAP搜索鍵
請(qǐng)注意,在處理標(biāo)志和搜索鍵方面,某些IMAP服務(wù)器的實(shí)現(xiàn)可能稍有不同。可能需要在交互式環(huán)境中試驗(yàn)一下,看看它們實(shí)際的行為如何。
在傳入search()方法的列表參數(shù)中,可以有多個(gè)IMAP搜索鍵字符串。返回的消息將匹配所有的搜索鍵。如果想匹配任何一個(gè)搜索鍵,使用OR搜索鍵。對(duì)于NOT和OR搜索鍵,它們后邊分別跟著一個(gè)和兩個(gè)完整的搜索鍵。
下面是search()方法調(diào)用的一些例子,以及它們的含義:
imapObj.search([‘ALL’]) 返回當(dāng)前選定的文件夾中的每一個(gè)消息。
imapObj.search([‘ON 05-Jul-2015’])返回在2015年7月5日發(fā)送的每個(gè)消息。
imapObj.search([‘SINCE 01-Jan-2015’, ‘BEFORE 01-Feb-2015’, ‘UNSEEN’])返回2015年1月發(fā)送的所有未讀消息(注意,這意味著從1月1日直到2月1日,但不包括2月1日)。
imapObj.search([‘SINCE 01-Jan-2015’, ‘FROM alice@example.com’])返回自2015年開始以來(lái),發(fā)自alice@example.com的消息。
imapObj.search([‘SINCE 01-Jan-2015’, ‘NOT FROM alice@example.com’])返回自2015年開始以來(lái),除alice@example.com外,其他所有人發(fā)來(lái)的消息。
imapObj.search([‘OR FROM alice@example.com FROM bob@example.com’])返回發(fā)自alice@example.com或bob@example.com的所有信息。
imapObj.search([‘FROM alice@example.com’, ‘FROM bob@example.com’])惡作劇例子!該搜索不會(huì)返回任何消息,因?yàn)橄⒈仨毱ヅ渌兴阉麝P(guān)鍵詞。因?yàn)橹荒苡幸粋€(gè)“from”地址,所以一條消息不可能既來(lái)自alice@example.com,又來(lái)自bob@example.com。
search()方法不返回電子郵件本身,而是返回郵件的唯一整數(shù)ID(UID)。然后,可以將這些UID傳入fetch()方法,獲得郵件內(nèi)容。
輸入以下代碼,繼續(xù)交互式環(huán)境的例子:
>>> UIDs=imapObj.search(['SINCE 05-Jul-2015'])
>>> UIDs
[40032, 40033, 40034, 40035, 40036, 40037, 40038, 40039, 40040, 40041]
這里,search()返回的消息ID列表(針對(duì)7月5日以來(lái)接收的消息)保存在UIDs中。計(jì)算機(jī)上返回的UIDs列表與這里顯示的不同,它們對(duì)于特定的電子郵件賬戶是唯一的。如果你稍后將UID傳遞給其他函數(shù)調(diào)用,請(qǐng)用你收到的UID值,而不是本書例子中打印的。
如果你的搜索匹配大量的電子郵件,Python可能拋出異常imaplib.error: got more than 10000 bytes。如果發(fā)生這種情況,必須斷開并重連IMAP服務(wù)器,然后再試。
這個(gè)限制是防止Python程序消耗太多內(nèi)存。遺憾的是,默認(rèn)大小限制往往太小。可以執(zhí)行下面的代碼,將限制從10000字節(jié)改為10000000字節(jié):
>>> import imaplib
>>> imaplib._MAXLINE=10000000
這應(yīng)該能避免該錯(cuò)誤消息再次出現(xiàn)。也許要在你寫的每一個(gè)IMAP程序中加上這兩行。
得到UID的列表后,可以調(diào)用IMAPClient對(duì)象的fetch()方法,獲得實(shí)際的電子郵件內(nèi)容。
UID列表是fetch()的第一個(gè)參數(shù)。第二個(gè)參數(shù)應(yīng)該是[‘BODY[]’],它告訴fetch()下載UID列表中指定電子郵件的所有正文內(nèi)容。
{使用IMAPClient的gmail_search()方法!!}
如果登錄到imap.gmail.com服務(wù)器來(lái)訪問(wèn)Gmail賬戶,IMAPClient對(duì)象提供了一個(gè)額外的搜索函數(shù),模擬Gmail網(wǎng)頁(yè)頂部的搜索欄,如圖16-1中高亮的部分所示。
除了用IMAP搜索鍵搜索,可以使用Gmail更先進(jìn)的搜索引擎。Gmail在匹配密切相關(guān)的單詞方面做得很好(例如,搜索driving也會(huì)匹配drive和drove),并按照匹配的程度對(duì)搜索結(jié)果排序。也可以使用Gmail的高級(jí)搜索操作符(更多信息請(qǐng)參見http://nostarch.com/automatestuff/)。如果登錄到Gmail賬戶,向gmail_search()方法傳入搜索條件,而不是search()方法,就像下面交互式環(huán)境的例子:
>>> UIDs=imapObj.gmail_search('meaning of life')
>> UIDs
[42]
啊,是的,那封電子郵件包含了生命的意義!我一直在期待。
圖16-1 在Gmail網(wǎng)頁(yè)頂部的搜索欄
讓我們繼續(xù)交互式環(huán)境的例子。
>>> 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}}
導(dǎo)入 pprint,將 fetch()的返回值(保存在變量 rawMessages 中)傳入pprint.pprint(),“漂亮打印”它。你會(huì)看到,這個(gè)返回值是消息的嵌套字典,其中以UID作為鍵。每條消息都保存為一個(gè)字典,包含兩個(gè)鍵:’BODY[]’和’SEQ’。’BODY[]’鍵映射到電子郵件的實(shí)際正文。’SEQ’鍵是序列號(hào),它與UID的作用類似。你可以放心地忽略它。
正如你所看到的,在’BODY[]’鍵中的消息內(nèi)容是相當(dāng)難理解的。這種格式稱為RFC822,是專為IMAP服務(wù)器讀取而設(shè)計(jì)的。但你并不需要理解RFC 822格式,本章稍后的pyzmail模塊將替你來(lái)理解它。
如果你選擇一個(gè)文件夾進(jìn)行搜索,就用readonly=True關(guān)鍵字參數(shù)來(lái)調(diào)用select_ folder()。這樣做可以防止意外刪除電子郵件,但這也意味著你用fetch()方法獲取郵件時(shí),它們不會(huì)標(biāo)記為已讀。如果確實(shí)希望在獲取郵件時(shí)將它們標(biāo)記已讀,就需要將readonly=False傳入select_folder()。如果所選文件夾已處于只讀模式,可以用另一個(gè) select_folder()調(diào)用重新選擇當(dāng)前文件夾,這次用readonly=False關(guān)鍵字參數(shù):
>>> imapObj.select_folder('INBOX', readonly=False)
對(duì)于只想讀郵件的人來(lái)說(shuō),fetch()方法返回的原始消息仍然不太有用。pyzmail模塊解析這些原始消息,將它們作為PyzMessage對(duì)象返回,使郵件的主題、正文、“收件人”字段、“發(fā)件人”字段和其他部分能用Python代碼輕松訪問(wèn)。
用下面的代碼繼續(xù)交互式環(huán)境的例子(使用你自己的郵件賬戶的UID,而不是這里顯示的):
>>> import pyzmail>>> message=pyzmail.PyzMessage.factory(rawMessages[40041]['BODY[]'])
首先,導(dǎo)入pyzmail。然后,為了創(chuàng)建一個(gè)電子郵件的PyzMessage對(duì)象,調(diào)用pyzmail.PeekMessage.factory()函數(shù),并傳入原始郵件的’BODY[]’部分。結(jié)果保存在message中。現(xiàn)在,message中包含一個(gè)PyzMessage對(duì)象,它有幾個(gè)方法,可以很容易地獲得的電子郵件主題行,以及所有發(fā)件人和收件人的地址。get_subject()方法將主題返回為一個(gè)簡(jiǎn)單字符串。get_addresses()方法針對(duì)傳入的字段,返回一個(gè)地址列表。例如,該方法調(diào)用可能像這樣:
>>> 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')
[]
請(qǐng)注意,get_addresses()的參數(shù)是’from’、’to’、’cc’或 ‘bcc’。get_addresses()的返回值是一個(gè)元組列表。每個(gè)元組包含兩個(gè)字符串:第一個(gè)是與該電子郵件地址關(guān)聯(lián)的名稱,第二個(gè)是電子郵件地址本身。如果請(qǐng)求的字段中沒(méi)有地址,get_addresses()返回一個(gè)空列表。在這里,’cc’抄送和’bcc’密件抄送字段都沒(méi)有包含地址,所以返回空列表。
電子郵件可以是純文本、HTML 或兩者的混合。純文本電子郵件只包含文本,而HTML電子郵件可以有顏色、字體、圖像和其他功能,使得電子郵件看起來(lái)像一個(gè)小網(wǎng)頁(yè)。如果電子郵件僅僅是純文本,它的PyzMessage對(duì)象會(huì)將html_part屬性設(shè)為None。同樣,如果電子郵件只是HTML,它的PyzMessage對(duì)象會(huì)將text_part屬性設(shè)為None。
否則,text_part或html_part將有一個(gè)get_payload()方法,將電子郵件的正文返回為bytes數(shù)據(jù)類型(bytes數(shù)據(jù)類型超出了本書的范圍)。但是,這仍然不是我們可以使用的字符串。啊!最后一步對(duì)get_payload()返回的bytes值調(diào)用decode()方法。decode()方法接受一個(gè)參數(shù):這條消息的字符編碼,保存在text_part.charset或html_part.charset屬性中。最后,這返回了郵件正文的字符串。
輸入以下代碼,繼續(xù)交互式環(huán)境的例子:
? >>> 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內(nèi)容,因此保存在message中的PyzMessage對(duì)象的text_part和html_part屬性不等于None??。對(duì)消息的text_part調(diào)用get_payload(),然后在bytes值上調(diào)用decode(),返回電子郵件的文本版本的字符串?。對(duì)消息的html_part調(diào)用get_payload()和decode(),返回電子郵件的HTML版本的字符串?。
要?jiǎng)h除電子郵件,就向IMAPClient對(duì)象的delete_messages()方法傳入一個(gè)消息UID的列表。這為電子郵件加上\Deleted標(biāo)志。調(diào)用expunge()方法,將永久刪除當(dāng)前選中的文件夾中帶\Deleted標(biāo)志的所有電子郵件。請(qǐng)看下面的交互式環(huán)境的例子:
? >>> 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')])
這里,我們調(diào)用了IMAPClient對(duì)象的select_folder()方法,傳入’INBOX’作為第一個(gè)參數(shù),選擇了收件箱。我們也傳入了關(guān)鍵字參數(shù)readonly=False,這樣我們就可以刪除電子郵件?。我們搜索收件箱中的特定日期收到的消息,將返回的消息ID保存在UIDs中?。調(diào)用delete_message()并傳入U(xiǎn)IDs,返回一個(gè)字典,其中每個(gè)鍵值對(duì)是一個(gè)消息 ID 和消息標(biāo)志的元組,它現(xiàn)在應(yīng)該包含\Deleted標(biāo)志?。然后調(diào)用expunge(),永久刪除帶\Deleted標(biāo)志的郵件。如果清除郵件沒(méi)有問(wèn)題,就返回一條成功信息。請(qǐng)注意,一些電子郵件提供商,如Gmail,會(huì)自動(dòng)清除用delete_messages()刪除的電子郵件,而不是等待來(lái)自IMAP客戶端的expunge命令。
如果程序已經(jīng)完成了獲取和刪除電子郵件,就調(diào)用IMAPClient的logout()方法,從IMAP服務(wù)器斷開連接。
>>> imapObj.logout()
如果程序運(yùn)行了幾分鐘或更長(zhǎng)時(shí)間,IMAP服務(wù)器可能會(huì)超時(shí),或自動(dòng)斷開。在這種情況下,接下來(lái)程序?qū)MAPClient對(duì)象的方法調(diào)用會(huì)拋出異常,像下面這樣:
imaplib.abort: socket error: [WinError 10054] An existing connection was
forcibly closed by the remote host
在這種情況下,程序必須調(diào)用imapclient.IMAPClient(),再次連接。
喲!齊活了。要跳過(guò)很多圈圈,但你現(xiàn)在有辦法讓Python程序登錄到一個(gè)電子郵件賬戶,并獲取電子郵件。需要回憶所有步驟時(shí),你可以隨時(shí)參考16.4節(jié)“用IMAP獲取和刪除電子郵件”。
假定你一直“自愿”為“強(qiáng)制自愿俱樂(lè)部”記錄會(huì)員會(huì)費(fèi)。這確實(shí)是一項(xiàng)枯燥的工作,包括維護(hù)一個(gè)電子表格,記錄每個(gè)月誰(shuí)交了會(huì)費(fèi),并用電子郵件提醒那些沒(méi)交的會(huì)員。不必你自己查看電子表格,而是向會(huì)費(fèi)超期的會(huì)員復(fù)制和粘貼相同的電子郵件。你猜對(duì)了,讓我們編寫一個(gè)腳本,幫你完成任務(wù)。
在較高的層面上,下面是程序要做的事:
這意味著代碼需要做到以下幾點(diǎn):
打開一個(gè)新的文件編輯器窗口,并保存為sendDuesReminders.py。
假定用來(lái)記錄會(huì)費(fèi)支付的 Excel 電子表格看起來(lái)如圖 16-2 所示,放在名為duesRecords.xlsx的文件中。可以從http://nostarch.com/automatestuff/下載該文件。
圖16-2 記錄會(huì)員會(huì)費(fèi)支付電子表格
該電子表格中包含每個(gè)成員的姓名和電子郵件地址。每個(gè)月有一列,記錄會(huì)員的付款狀態(tài)。在成員交納會(huì)費(fèi)后,對(duì)應(yīng)的單元格就記為paid。
該程序必須打開duesRecords.xlsx,通過(guò)調(diào)用get_highest_column()方法,弄清楚最近一個(gè)月的列(可以參考第12章,了解用openpyxl模塊訪問(wèn)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.
導(dǎo)入openpyxl、smtplib和sys模塊后,我們打開duesRecords.xlsx文件,將得到的Workbook對(duì)象保存在wb中?。然后,取得Sheet 1,將得到的Worksheet對(duì)象保存在sheet中?。既然有了Worksheet對(duì)象,就可以訪問(wèn)行、列和單元格。我們將最后一列保存在lastCol中?,然后用行號(hào)1和lastCol來(lái)訪問(wèn)應(yīng)該記錄著最近月份的單元格。取得該單元格的值,并保存在latestMonth 中?。
一旦確定了最近一個(gè)月的列數(shù)(保存在lastCol中),就可以循環(huán)遍歷第一行(這是列標(biāo)題)之后的所有行,看看哪些成員在該月會(huì)費(fèi)的單元格中寫著paid。如果會(huì)員沒(méi)有支付,就可以從列1和2中分別抓取成員的姓名和電子郵件地址。這些信息將放入unpaidMembers字典,它記錄最近一個(gè)月沒(méi)有交費(fèi)的所有成員。將以下代碼添加到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
這段代碼設(shè)置了一個(gè)空字典unpaidMembers,然后循環(huán)遍歷第一行之后所有的行?。對(duì)于每一行,最近月份的值保存在payment中?。如果payment不等于’paid’,則第一列的值保存在name中?,第二列的值保存在email中?,name和email添加到unpaidMembers中?。
得到所有未付費(fèi)成員的名單后,就可以向他們發(fā)送電子郵件提醒了。將下面的代碼添加到程序中,但要代入你的真實(shí)電子郵件地址和提供商的信息:
#! 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])
調(diào)用smtplib.SMTP()并傳入提供商的域名和端口,創(chuàng)建一個(gè)SMTP對(duì)象。調(diào)用ehlo()和starttls(),然后調(diào)用login(),并傳入你的電子郵件地址和sys.argv[1],其中保存著你的密碼字符串。在每次運(yùn)行程序時(shí),將密碼作為命令行參數(shù)輸入,避免在源代碼中保存密碼。
程序登錄到你的電子郵件賬戶后,就應(yīng)該遍歷unpaidMembers字典,向每個(gè)會(huì)員的電子郵件地址發(fā)送針對(duì)個(gè)人的電子郵件。將以下代碼添加到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()
這段代碼循環(huán)遍歷unpaidMembers中的姓名和電子郵件。對(duì)于每個(gè)沒(méi)有付費(fèi)的成員,我們用最新的月份和成員的名稱,定制了一條消息,并保存在body中?。我們打印輸出,表示正在向這個(gè)會(huì)員的電子郵件地址發(fā)送電子郵件?。然后調(diào)用sendmail(),向它傳入地址和定制的消息?。返回值保存在sendmailStatus中。
回憶一下,如果SMTP服務(wù)器在發(fā)送某個(gè)電子郵件時(shí)報(bào)告錯(cuò)誤,sendmail()方法將返回一個(gè)非空的字典值。for循環(huán)最后部分在?行檢查返回的字典是否非空,如果非空,則打印收件人的電子郵件地址以及返回的字典。
程序完成發(fā)送所有電子郵件后,調(diào)用quit()方法,與SMTP服務(wù)器斷開連接。
如果運(yùn)行該程序,輸出會(huì)像這樣:
Sending email to alice@example.com...
Sending email to bob@example.com...
Sending email to eve@example.com...
收件人將收到如圖16-3所示的電子郵件。
圖16-3 從sendDuesReminders.py自動(dòng)發(fā)送的電子郵件
大多數(shù)人更可能靠近自己的手機(jī),而不是自己的電腦,所以與電子郵件相比,短信發(fā)送通知可能更直接、可靠。此外,短信的長(zhǎng)度較短,讓人更有可能閱讀它們。
在本節(jié)中,你將學(xué)習(xí)如何注冊(cè)免費(fèi)的Twilio服務(wù),并用它的Python模塊發(fā)送短信。Twilio是一個(gè)SMS網(wǎng)關(guān)服務(wù),這意味著它是一種服務(wù),讓你通過(guò)程序發(fā)送短信。雖然每月發(fā)送多少短信會(huì)有限制,并且文本前面會(huì)加上Sent from a Twilio trial account,但這項(xiàng)試用服務(wù)也許能滿足你的個(gè)人程序。免費(fèi)試用沒(méi)有限期,不必以后升級(jí)到付費(fèi)的套餐。
Twilio不是唯一的SMS網(wǎng)關(guān)服務(wù)。如果你不喜歡使用Twilio,可以在線搜索free sms gateway、python sms api,甚至twilio alternatives,尋找替代服務(wù)。
注冊(cè)Twilio賬戶之前,先安裝twilio模塊。附錄A詳細(xì)介紹了如何安裝第三方模塊。
本節(jié)特別針對(duì)美國(guó)。Twilio 確實(shí)也在美國(guó)以外的國(guó)家提供手機(jī)短信服務(wù),本書并不包括這些細(xì)節(jié)。但twilio 模塊及其功能,在美國(guó)以外的國(guó)家也能用。更多信息請(qǐng)參見http://twilio.com/。
訪問(wèn)http://twilio.com/并填寫注冊(cè)表單。注冊(cè)了新賬戶后,你需要驗(yàn)證一個(gè)手機(jī)號(hào)碼,短信將發(fā)給該號(hào)碼(這項(xiàng)驗(yàn)證是必要的,防止有人利用該服務(wù)向任意的手機(jī)號(hào)碼發(fā)送垃圾短信)。
收到驗(yàn)證號(hào)碼短信后,在Twilio網(wǎng)站上輸入它,證明你擁有要驗(yàn)證的手機(jī)。現(xiàn)在,就可以用twilio模塊向這個(gè)電話號(hào)碼發(fā)送短信了。
Twilio提供的試用賬戶包括一個(gè)電話號(hào)碼,它將作為短信的發(fā)送者。你將需要兩個(gè)信息:你的賬戶SID和AUTH(認(rèn)證)標(biāo)志。在登錄Twilio賬戶時(shí),可以在Dashboard頁(yè)面上找到這些信息。從Python程序登錄時(shí),這些值將作為你的Twilio用戶名和密碼。
一旦安裝了twilio模塊,注冊(cè)了Twilio賬號(hào),驗(yàn)證了你的手機(jī)號(hào)碼,登記了Twilio電話號(hào)碼,獲得了賬戶的SID和auth標(biāo)志,你就終于準(zhǔn)備好通過(guò)Python腳本向你自己發(fā)短信了。
與所有的注冊(cè)步驟相比,實(shí)際的Python代碼很簡(jiǎn)單。保持計(jì)算機(jī)連接到因特網(wǎng),在交互式環(huán)境中輸入以下代碼,用你的真實(shí)信息替換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)
鍵入最后一行后不久,你會(huì)收到一條短信,內(nèi)容為:Sent from your Twilio trial account - Mr. Watson - Come here – I want to see you.。
因?yàn)閠wilio模塊的設(shè)計(jì)方式,導(dǎo)入它時(shí)需要使用from twilio.rest import TwilioRestClient,而不僅僅是import twilio?。將賬戶的SID保存在accountSID,認(rèn)證標(biāo)志保存在authToken中,然后調(diào)用TwilioRestClient(),并傳入accountSID和authToken。TwilioRestClient()調(diào)用返回一個(gè)TwilioRestClient對(duì)象?。該對(duì)象有一個(gè)message屬性,該屬性又有一個(gè)create()方法,可以用來(lái)發(fā)送短信。正是這個(gè)方法,將告訴Twilio的服務(wù)器發(fā)送短信。將你的Twilio號(hào)碼和手機(jī)號(hào)碼分別保存在myTwilioNumber和myCellPhone中,然后調(diào)用create(),傳入關(guān)鍵字參數(shù),指明短信的正文、發(fā)件人的號(hào)碼(myTwilioNumber),以及收信人的電話號(hào)碼(myCellPhone)?
create()方法返回的Message對(duì)象將包含已發(fā)送短信的相關(guān)信息。輸入以下代碼,繼續(xù)交互式環(huán)境的例子:
>>> message.to
'+14955558888'
>>> message.from_
'+14955551234'
>>> message.body
'Mr. Watson - Come here - I want to see you.'
to、from和body屬性應(yīng)該分別保存了你的手機(jī)號(hào)碼、Twilio號(hào)碼和消息。請(qǐng)注意,發(fā)送手機(jī)號(hào)碼是在from屬性中,末尾有一個(gè)下劃線,而不是from。這是因?yàn)閒rom是一個(gè)Python關(guān)鍵字(例如,你在from modulename import *形式的import語(yǔ)句中見過(guò)它),所以它不能作為一個(gè)屬性名。輸入以下代碼,繼續(xù)交互式環(huán)境的例子:
>>> message.status
'queued'
>>> message.date_created
datetime.datetime(2015, 7, 8, 1, 36, 18)
>>> message.date_sent==None
True
status 屬性應(yīng)該包含一個(gè)字符串。如果消息被創(chuàng)建和發(fā)送,date_created 和date_sent屬性應(yīng)該包含一個(gè)datetime對(duì)象。如果已收到短信,而status屬性卻設(shè)置為’queued’,date_sent屬性設(shè)置為None,這似乎有點(diǎn)奇怪。這是因?yàn)槟阆葘essage對(duì)象記錄在message變量中,然后短信才實(shí)際發(fā)送。你需要重新獲取Message對(duì)象,查看它最新的status和date_sent。每個(gè)Twilio消息都有唯一的字符串ID(SID),可用于獲取Message對(duì)象的最新更新。輸入以下代碼,繼續(xù)交互式環(huán)境的例子:
>>> 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將顯示這個(gè)消息的SID。將這個(gè)SID傳入Twilio客戶端的get()方法?,你可以取得一個(gè)新的Message對(duì)象,包含最新的信息。在這個(gè)新的Message對(duì)象中,status和date_sent屬性是正確的。
status屬性將設(shè)置為下列字符串之一:’queued’、’sending’、’sent’、’delivered’、’undelivered’或’failed’。這些狀態(tài)不言自明,但對(duì)于更準(zhǔn)確的細(xì)節(jié),請(qǐng)查看http://nostarch. com/automatestuff/的資源。
{用Python接收短信!!}
遺憾的是,用Twilio接收短信比發(fā)送短信更復(fù)雜一些。Twilio需要你有一個(gè)網(wǎng)站,運(yùn)行自己的Web應(yīng)用程序。這已超出了本書的范圍,但你可以在本書的資源中找到更多細(xì)節(jié)(http://nostarch.com/automatestuff/)。
最常用你的程序發(fā)短信的人可能就是你。當(dāng)你遠(yuǎn)離計(jì)算機(jī)時(shí),短信是通知你自己的好方式。如果你已經(jīng)用程序自動(dòng)化了一個(gè)無(wú)聊的任務(wù),它需要運(yùn)行幾小時(shí),你可以在它完成時(shí),讓它用短信通知你。或者可以定期運(yùn)行某個(gè)程序,它有時(shí)需要與你聯(lián)系,例如天氣檢查程序,用短信提醒你帶傘。
舉一個(gè)簡(jiǎn)單的例子,下面是一個(gè)Python小程序,包含了textmyself()函數(shù),它將傳入的字符串參數(shù)作為短信發(fā)出。打開一個(gè)新的文件編輯器窗口,輸入以下代碼,用自己的信息替換帳戶SID,認(rèn)證標(biāo)志和電話號(hào)碼。將它保存為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、認(rèn)證標(biāo)志、發(fā)送號(hào)碼及接收號(hào)碼。然后它定義了textmyself(),接收參數(shù)?,創(chuàng)建TwilioRestClient對(duì)象?,并用你傳入的消息調(diào)用create()?。
如果你想讓其他程序使用textmyself()函數(shù),只需將textMyself.py文件和Python的可執(zhí)行文件放在同一個(gè)文件夾中(Windows上是C:\Python34,OS X上是/usr/local/lib/python3.4,Linux上是/usr/bin/python3)。現(xiàn)在,你可以在其他程序中使用該函數(shù)。只要想在程序中發(fā)短信給你,就添加以下代碼:
import textmyself
textmyself.textmyself('The boring task is finished.')
注冊(cè)Twilio和編寫短信代碼只要做一次。在此之后,從任何其他程序中發(fā)短信,只要兩行代碼。
通過(guò)因特網(wǎng)和手機(jī)網(wǎng)絡(luò),我們用幾十種不同的方式相互通信,但以電子郵件和短信為主。你的程序可以通過(guò)這些渠道溝通,這給它們帶來(lái)強(qiáng)大的新通知功能。甚至可以編程運(yùn)行在不同的計(jì)算機(jī)上,相互直接通過(guò)電子郵件能信,一個(gè)程序用SMTP發(fā)送電子郵件,另一個(gè)用IMAP收取。
Python 的 smtplib 提供了一些函數(shù),利用 SMTP,通過(guò)電子郵件提供商的SMTP服務(wù)器發(fā)送電子郵件。同樣,第三方的imapclient和pyzmail模塊讓你訪問(wèn)IMAP服務(wù)器,并取回發(fā)送給你的電子郵件。雖然IMAP比SMTP復(fù)雜一些,但它也相當(dāng)強(qiáng)大,允許你搜索特定電子郵件、下載它們、解析它們,提取主題和正文作為字符串值。
短信與電子郵件有點(diǎn)不同,因?yàn)樗幌耠娮余]件,發(fā)送短信不僅需要互聯(lián)網(wǎng)連接。好在,像Twilio這樣的服務(wù)提供了模塊,允許你通過(guò)程序發(fā)送短信。一旦通過(guò)了初始設(shè)置過(guò)程,就能夠只用幾行代碼來(lái)發(fā)送短信。掌握了這些模塊,就可以針對(duì)特定的情況編程,在這些情況下發(fā)送通知或提醒。現(xiàn)在,你的程序?qū)⒊竭\(yùn)行它們的計(jì)算機(jī)!
本文摘自《Python編程快速上手 讓繁瑣工作自動(dòng)化》
本書是一本面向?qū)嵺`的Python編程實(shí)用指南。本書的目的,不僅是介紹Python語(yǔ)言的基礎(chǔ)知識(shí),而且還通過(guò)項(xiàng)目實(shí)踐教會(huì)讀者如何應(yīng)用這些知識(shí)和技能。本書的第一部分介紹了基本的Python編程概念,第二部分介紹了一些不同的任務(wù),通過(guò)編寫Python程序,可以讓計(jì)算機(jī)自動(dòng)完成它們。第二部分的每一章都有一些項(xiàng)目程序,供讀者學(xué)習(xí)。每章的末尾還提供了一些習(xí)題和深入的實(shí)踐項(xiàng)目,幫助讀者鞏固所學(xué)的知識(shí)。附錄部分提供了所有習(xí)題的解答。
本書適合任何想要通過(guò)Python學(xué)習(xí)編程的讀者,尤其適合缺乏編程基礎(chǔ)的初學(xué)者。通過(guò)閱讀本書,讀者將能利用最強(qiáng)大的編程語(yǔ)言和工具,并且將體會(huì)到Python編程的快樂(lè)。
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。