書本第430頁的練習:
登入IMAP server。
下載所有mail、
bs4找出連結的HTML標籤,存成URL串列,
以webbrowser.open()在瀏覽器中開啟。
雖然此題寫出來了,但自覺寫法笨拙。畢竟是照課本的教法一個個疊出來。
沒有正規學習python基本語法,就學這個各種應用,
可能寫得很醜……
3個問題
一、此篇程式碼是否有更有效率的寫法?感覺bs4的部份可以直接抓到,不需再用到regex,可惜我對html陌生。
二、gmail的「促銷內容」之標籤為何?'INBOX'似乎含全部,想單一抓出促銷內容類別的。
三、regex有沒有辦法直接取得第一個連結?像下面這樣要寫2層篩選,萬一有5個不就得「寫5次」?
import os, shutil, imapclient, pprint
a = 'c:\\temp'
b = 'p430cuse'
os.chdir(a)
os.makedirs(b, exist_ok=True)
os.chdir(os.path.join(a, b))
#print(os.getcwd())
for n in os.listdir():
os.unlink(n) # 清掉前次的記錄
i = imapclient.IMAPClient('imap.gmail.com', ssl=True)
# 以ssl加密方式,連進gmail的IMAP server
i.login('xxx@gmail.com', 'xxx') # 登入信箱
i.select_folder('INBOX', readonly=True) # 選擇收件匣目錄
# Promotions Tab 促銷內容
a = i.list_folders()
#pprint.pprint(a)
# ((b'\\All', b'\\HasNoChildren'), b'/', '[Gmail]/全部郵件'),
a = i.search(['ALL']) # 選擇收件匣所有信件
del a[11:] # 保留前11封
print(a)
# 郵件編號[86199, 86256, 86314, 86316, 86325, 86348, 86420, 86435, 86592, 86844, 87170]
import pyzmail, bs4, re, webbrowser
# 解析郵件、解析html碼、regex解析文字、開啟網頁
def s(k):
j = re.compile(r'<a href="(.*).html(.*).html(.*)')
g = j.sub(r"\1.html", k)
if g[-5:] == '.html': # 如果符合條件j,就能解析出網址g,網址g以html結尾
return g # 有解析出g就返回結束此自定函數,不然繼續往下篩選
j = re.compile(r'<a href="(.*).tw(.*).tw(.*)')
g = j.sub(r"\1.tw", k) # 網址以tw結尾
if g[-3:] == '.tw':
return g
j = re.compile(r'<a href="(.*).com(.*).com(.*)')
g = j.sub(r"\1.com", k) # 網址以com結尾,這個放在「條件tw」之後篩選。
if g[-4:] == '.com':
return g
j = re.compile(r'<a href="(.*).com(.*)')
g = j.sub(r"\1.com", k)
if g[-4:] == '.com':
return g
"""原始網址範例,這很多層,所以上面regex條件會重複多遍
[<a href="https://www.momoshop.com.tw/edm/cmmedm.jsp?lpn=Nh9W&n=1&
;cid=&oid=&mdiv=1000-bt_P1_1_e1&ctype=B">
<img border="0" src="https://img2.momoshop.com.tw/ecm/img/online/100/b
t__01_P1_1_e2.png?t=15815" style=" width:134px; height:
120px; "/></a>]"""
p = []
for x in range(len(a)): # 11 mail
y = i.fetch(a[x], ['BODY[]', 'FLAGS']) # 取得單一信文
z = pyzmail.PyzMessage.factory(y[a[x]][b'BODY[]'])
# 將信文製成pyzmail物件
if z.html_part != None: # 若信文不為空
b = z.html_part.get_payload().decode(z.html_part.charset)
# 將信文的html部份,以html語法來解析
d = bs4.BeautifulSoup(b, "html5lib") # 讀取html部份,製成bs4物件
e = d.select('a') # a href="link URL" 從bs4物件中,挑選出連結
#print(e)
e = e[:1] # 每封信只留一個連結
if len(e) != 0: # 也有整封信純文字無連結的
f = s(str(e[0])) # 使用regex,將連結去掉"a hrf="及後面的多餘文字
p.append(f) # 將取得的乾淨連結放進串列
p = list(dict.fromkeys(p))
# 不同的信件有些第一個連結可能是相同的,串列去重複
import pprint
# pprint.pprint(p) 好看列印法
for n in p:
webbrowser.open(n) # 以瀏覽器開啟所有網址
"""範例網址列表
['https://www.momoshop.com.tw',
'https://buy.itunes.apple.com',
'http://www.economist.com',
'mailto:chiawei.hung@fubon.com',
'https://www.fubon.com']
"""
Update: 修改 regex-Pattern、程式碼註解。
一、此篇程式碼是否有更有效率的寫法?感覺bs4的部份可以直接抓到,不需再用到regex,可惜我對html陌生。
如果沒有解析 HTML 的必要,我是偏好只用 regex~
(只是找 <a>
的 URL 就用 bs4
,這對我來說算是小題大作~)
另,regex 是很好用也很重要的技術,推薦學習!
(只是要注意,不同環境下的 regex 實作會有差異~)
二、gmail的「促銷內容」之標籤為何?'INBOX'似乎含全部,想單一抓出促銷內容類別的。
依這裡所言,Google 在 Gmail API 有提供自動類別標籤功能,但在 Gmail IMAP 擴充功能 沒有提供。
(至少我是試不出來~)
上本週作業(?)給你參考~
沒用 bs4
的版本:
##
import os, datetime, re, webbrowser
## https://pypi.org/project/imap-tools/
from imap_tools import MailBox, AND
## 丟組態用的類別物件;
class myCfg:
path = r'c:\temp\p430cuse'
host = r'imap.gmail.com'
luser = r'username@gmail.com'
lpass = r'password'
## 我看到你建立並切換工作目錄,但沒檔案操作存取?只是練習?
if not os.path.exists(myCfg.path):
os.makedirs(myCfg.path, exist_ok=True)
os.chdir(myCfg.path)
print(os.getcwd())
##
for n in os.listdir():
os.unlink(n)
##
## regex-Pattern: 我是直接抓 HTTP|mailto 協定的連結;
## (因為你的範例內有放 mailto 的,所以我也放進去~)
## 這不是完全符合 URL 的式子,只是我臨時隨便寫的(懶得再翻 RFC 之類的文件~);
## 要更詳細可以去 Google: 'Python regex URL',畢竟這算是老問題了~
myURLPattern = r'(?i)\b(?:https?|mailto):\S+'
## 這是抓 HTML-A-Tag 用的; match-group#1 可取得 URL;
myTagPattern = r'(?i)<a [^>]*href="((?:https?|mailto):[^"]*)"[^>]*>'
## https://docs.python.org/zh-tw/3/tutorial/datastructures.html#sets
myResult = set() ## 一個 set 是一組無序且沒有重複的元素;
##
with MailBox(myCfg.host).login(myCfg.luser, myCfg.lpass) as mailbox:
for msg in mailbox.fetch(
AND(date_gte=datetime.date(2023, 1, 1)), ## 篩出 2023-01-01 之後的信件;
mark_seen=False ## 不觸動信件的已讀狀態;
):
## 列印信件資訊;
print('■', msg.date, msg.subject, len(msg.html or msg.text))
if msg.html:
## 取得 Match 物件之生成疊代器(generator iterator);
myMatch = re.compile(myTagPattern).finditer(msg.html)
## 這裡用 Match.group() 取符合的子群組之內容;
## https://docs.python.org/3/library/re.html#re.Match.group
try: myResult.add(next(myMatch).group(1))
except StopIteration: pass
## 利用 set 沒有重複的特性而沒寫 set.add() 的檢查;
## 使用 next() 是因為 re.finditer() 回傳生成疊代器;
## https://docs.python.org/3.10/library/functions.html#next
elif msg.text:
myMatch = re.compile(myURLPattern).finditer(msg.text)
## 另一種處理 next() 的 StopIteration 之方式;
if (r := next(myMatch, None)):
myResult.add(r.group(0))
## 我習慣先斷線(by out-of-with)再處理結果;
mySorted = sorted(myResult)
for i in mySorted: print('■',i)
for i in mySorted: webbrowser.open(i)
##
用上 bs4
的版本:
(if msg.html:
這區段的內容才跟上例有差異,其他沒差~)
##
import os, datetime, re, webbrowser
from imap_tools import MailBox, AND
from bs4 import BeautifulSoup as bSoup
##
class myCfg:
path = r'c:\temp\p430cuse'
host = r'imap.gmail.com'
luser = r'username@gmail.com'
lpass = r'password'
##
if not os.path.exists(myCfg.path):
os.makedirs(myCfg.path, exist_ok=True)
os.chdir(myCfg.path)
print(os.getcwd())
##
for n in os.listdir():
os.unlink(n)
##
myURLPattern = r'(?i)\b(?:https?|mailto):\S+'
myTagPattern = r'(?i)<a [^>]*href="((?:https?|mailto):[^"]*)"[^>]*>'
myResult = set()
##
with MailBox(myCfg.host).login(myCfg.luser, myCfg.lpass) as mailbox:
for msg in mailbox.fetch(
AND(date_gte=datetime.date(2023, 1, 1)),
mark_seen=False
):
print('■', msg.date, msg.subject, len(msg.html or msg.text))
if msg.html:
mySoup = bSoup(msg.html, 'html.parser')
## https://www.crummy.com/software/BeautifulSoup/bs4/doc/#a-function
myA = mySoup.find('a', href = re.compile('^(?:https?|mailto):').search)
if myA: myResult.add(myA['href'])
elif msg.text:
myMatch = re.compile(myURLPattern).finditer(msg.text)
if (r := next(myMatch, None)):
myResult.add(r.group(0))
mySorted = sorted(myResult)
for i in mySorted: print('■',i)
for i in mySorted: webbrowser.open(i)
##
偷偷請 ChatGPT 寫寫看:
(請自行判斷有沒有 Bug)
import imaplib
import re
import webbrowser
from bs4 import BeautifulSoup
# Connect to IMAP server
imap = imaplib.IMAP4_SSL("imap.gmail.com")
imap.login("your_email_address", "your_email_password")
# Select inbox and download all mails
imap.select("inbox")
status, messages = imap.search(None, "ALL")
messages = messages[0].split()
# Find all links in HTML tags
links = []
for message in messages:
status, data = imap.fetch(message, "(RFC822)")
soup = BeautifulSoup(data[0][1].decode("utf-8"), "html.parser")
for link in soup.find_all("a"):
if "href" in link.attrs:
url = link.attrs["href"]
if re.match("^http", url):
links.append(url)
# Open all links in a web browser
for url in links:
webbrowser.open(url)
# Close the IMAP connection
imap.close()
imap.logout()