2025年7月21日 星期一

Python 學習筆記 : 網頁爬蟲實戰 (十八) 擷取隨機的格言 (上)

前陣子發現 Mapleboard 的 Ubuntu Mate 升版後自動新增了預設的 Guest 帳戶, 因為其權限不足以連線網路, 導致主機掛點網站連埠上. 修復此問題後花了近一個月時間來打理架站與主機安全防護. 這兩天完成後想說應該要讓主機每天早上傳送一個訊息 (Email 或 LINE/Telegram 訊息) 給我, 讓我知道系統仍正常運行中才對. 那要傳甚麼呢? 當然, 傳個 "早安" 是再簡單不過了, 但我想弄點較有意思的, 那就傳個名言或格言吧!

本系列全部測試文章索引參考 :



1. 撰寫格言爬蟲 : 

我找到下面這個免申請帳號與 Key 就能取得格言的網站 : 





它提供一個可隨機傳回格言的 API 網址 : 


在瀏覽器輸入此 API url 會傳回一個 JSON 字串 : 

[ {"q":"Pleasure can be supported by an illusion; but happiness rests upon truth. ","a":"Nicolas Chamfort","h":"<blockquote>&ldquo;Pleasure can be supported by an illusion; but happiness rests upon truth. &rdquo; &mdash; <footer>Nicolas Chamfort</footer></blockquote>"} ]

其中 "q" 鍵為格言本身, "a" 鍵為作者, 我們可以用 requests 來取得格言內容 :

>>> import requests  
>>> url='https://zenquotes.io/api/random'    
>>> res=requests.get(url)    
>>> res   
<Response [200]>

呼叫 Response 物件的 json() 方法將 JSON 字串轉成字典串列 : 

>>> data=res.json()  
>>> type(data)   
<class 'list'>   
>>> len(data)   
1

可見此串列只有一個元素 :

>>> data[0]  
{'q': 'Experience is a comb which nature gives us when we are bald. ', 'a': 'Chinese Proverb', 'h': '<blockquote>&ldquo;Experience is a comb which nature gives us when we are bald. &rdquo; &mdash; <footer>Chinese Proverb</footer></blockquote>'}
>>> data[0]['q']   
'Experience is a comb which nature gives us when we are bald. '
>>> data[0]['a']   
'Chinese Proverb'

這樣便取得格言內容與作者了, 可以將上面程序寫成 get_quote() 函式 :

>>> def get_quote(): 
    url='https://zenquotes.io/api/random'
    try:
        res=requests.get(url, timeout=10)
        data=res.json()
        return data[0]['q'], data[0]['a']  # 成功傳回 (quote, author)
    except Exception as e:
        return None, f'Failed to fetch quote: {e}'   # 失敗傳回 (None, '失敗訊息')
    
呼叫函式 : 

>>> quote, author=get_quote()   
>>> quote   
'You have to keep breaking your heart until it opens.'
>>> author    
'Rumi'


2. 利用 Google Gemini 翻譯格言 : 

但我想要同時顯示其中文翻譯 (中英對照), 格式例如 :

快樂可以靠幻想支撐,
但幸福建立在真理之上。
Pleasure can be supported by an illusion;
but happiness rests upon truth.

—— Nicolas Chamfort

中文翻譯可以利用 Google Gemini 大語言模型搭配合適的提示詞來達成, 這必須先申請 Gemini API Key 與安裝 google-generativeai 套件, 作法參考 :


我已有 Gemini API Key, 先存入變數中 :

>>> gemini_api_key='我的 Gemini API Key'     

然後匯入 google.generativeai 模組並取簡名 genai : 

>>> import google.generativeai as genai  

接著呼叫 configure() 函式並傳入 API Key 來認證金鑰 : 

>>> genai.configure(api_key=gemini_api_key)   

若沒有傳回錯誤訊息那就表示認證成功了,  呼叫 GenerativeModel() 函式並傳入模型名稱來建立模型物件, 考量反應速度我選擇 'gemini-2.0-flash' 模型 : 

>>> model=genai.GenerativeModel('gemini-2.0-flash')    

然後設計提示詞要求模型依照指示執行翻譯任務 : 

>>> prompt=f'請將以下英文翻譯為繁體中文, 只要翻譯一次即可:{quote}'   
>>> prompt    
'請將以下英文翻譯為繁體中文, 只要翻譯一次即可:You have to keep breaking your heart until it opens.'

最後呼叫模型物件的 generate_content() 方法來生成翻譯 : 

>>> reply=model.generate_content(prompt)      

回應內容放在回應物件的 text 屬性中 : 

>>> reply.text  
'你必須不斷讓你的心碎裂,直到它敞開。\n'

將上面程序寫成如下之函式 : 

>>> def translate_quote(quote, api_key):
    genai.configure(api_key=api_key)
    model=genai.GenerativeModel('gemini-2.0-flash')
    prompt=f'請將以下英文翻譯為繁體中文, 只要翻譯一次即可:{quote}'
    try:
        reply=model.generate_content(prompt)
        return reply.text.strip().strip('\"\n ')  # 清除結尾開頭之雙引號跳行與空格

    except Exception as e:
        return f'格言翻譯失敗:{e}'
  
>>> quote_zh_tw=translate_quote(quote, gemini_api_key)   
>>> quote_zh_tw  
'你必須不斷地讓你的心碎裂,直到它敞開。'

這樣就可以將格言之中英文與作者用 f 字串組成輸出訊息了 :

>>> def bilingual_quote(quote, quote_zh_tw, author):
    return f'早安! 今日格言:\n「{quote_zh_tw}」\n{quote}\n—— {author}'   
  
>>> msg=bilingual_quote(quote, quote_zh_tw, author)    
>>> msg    
'早安! 今日格言:\n「你必須不斷地讓你的心破碎,直到它真正敞開。」\nYou have to keep breaking your heart until it opens.\n—— Rumi'

以上測試的完整程式碼如下 :

# dailly_quote.py
import requests
import google.generativeai as genai

def get_quote():
    url='https://zenquotes.io/api/random'
    try:
        res=requests.get(url, timeout=10)
        data=res.json()
        return data[0]['q'], data[0]['a']  # 成功傳回 (quote, author)
    except Exception as e:
        return None, f'Failed to fetch quote: {e}'  # 失敗傳回 (None, '失敗訊息')
    
def translate_quote(quote, api_key):
    genai.configure(api_key=api_key)
    model=genai.GenerativeModel('gemini-2.0-flash')
    prompt=f'請將以下英文翻譯為繁體中文, 只要翻譯一次即可:{quote}'
    try:
        reply=model.generate_content(prompt)
        return reply.text.strip().strip('\"\n ')  # 清除結尾開頭之雙引號跳行與空格
    except Exception as e:
        return f'格言翻譯失敗:{e}'
 
def bilingual_quote(quote, quote_zh_tw, author):
    return f'早安! 今日格言:\n「{quote_zh_tw}」\n{quote}\n—— {author}'

# 取得每日格言與其中文翻譯
quote, author=get_quote()
gemini_api_key='我的 Gemini API Key'
quote_zh_tw=translate_quote(quote, gemini_api_key)
msg=bilingual_quote(quote, quote_zh_tw, author)
print(msg)

執行結果如下 :

>>> %Run dailly_quote.py   
早安! 今日格言:
「開始一件事情遠比完成它容易得多。」
It is far easier to start something than it is to finish it.
—— Amelia Earhart 


3. 利用 Google SMTP 郵件伺服器傳送每日格言 : 

使用 Google SMTP 伺服器傳送郵件的方法參考 :


我取其中 smtp_mail_test_2.py 的 send_mail() 添加主旨 Unicode 處理, 改寫上面的 dailly_quote.py 如下 :

# dailly_quote.py
import requests
import google.generativeai as genai
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header  

def get_quote():
    url='https://zenquotes.io/api/random'
    try:
        res=requests.get(url, timeout=10)
        data=res.json()
        return data[0]['q'], data[0]['a']  # 成功傳回 (quote, author)
    except Exception as e:
        return None, f'Failed to fetch quote: {e}'  # 失敗傳回 (None, '失敗訊息')
    
def translate_quote(quote, api_key):
    genai.configure(api_key=api_key)
    model=genai.GenerativeModel('gemini-2.0-flash')
    prompt=f'請將以下英文翻譯為繁體中文, 只要翻譯一次即可:{quote}'
    try:
        reply=model.generate_content(prompt)
        return reply.text.strip().strip('\"\n ')  # 清除結尾開頭之雙引號跳行與空格
    except Exception as e:
        return f'格言翻譯失敗:{e}'
 
def bilingual_quote(quote, quote_zh_tw, author):
    return f'早安! 今日格言:\n「{quote_zh_tw}」\n{quote}\n—— {author}'

def send_mail(account, password, subject, from_addr, to_addr, cc_addr=None, body=''):
    if cc_addr is None:
        cc_addr=[]  # 預設空串列
    # 登入 Google SMTP 伺服器
    smtp=smtplib.SMTP('smtp.gmail.com', 587)
    smtp.ehlo()
    smtp.starttls()  # 啟動 TLS 安全傳輸
    smtp.login(account, password)  # 登入 Google SMTP 伺服器
    # 建立郵件內容
    content=MIMEMultipart()
    content['Subject']=Header(subject, 'utf-8') 
    content['From']=from_addr
    content['To']=', '.join(to_addr)
    content['Cc']=', '.join(cc_addr)
    # 添加郵件正文
    content.attach(MIMEText(body, 'plain', 'utf-8'))
    # 合併收件人和副本收件人
    all_recipients=to_addr + cc_addr
    status=smtp.sendmail(from_addr, all_recipients, content.as_string())
    if status == {}:
        print('郵件傳送成功!')
    else:
        print('郵件傳送失敗!')
    smtp.quit()

# 取得每日格言與其中文翻譯
quote, author=get_quote()
gemini_api_key='我的 Gemini API Key'
quote_zh_tw=translate_quote(quote, gemini_api_key)
msg=bilingual_quote(quote, quote_zh_tw, author)
# 用 Google SMTP 伺服器傳送信件
account='mygmail@gmail.com'     # Gmail 帳號
password='azfkqbjbftodjucd'         # Google 應用程式密碼 (這是樣本)
subject='今日格言'                        # 主旨
from_addr='mygmail@gmail.com'   # 寄件人
to_addr=['myhinet@ms5.hinet.net']  # 收件人
cc_addr=[]                            # 副本
body=msg                            # 信件內容
send_mail(account, password, subject, from_addr, to_addr, cc_addr, body)

結果如下 : 

 >>> %Run dailly_quote.py   
郵件傳送成功!



沒有留言 :