Python – SMTP

前情提要

痞客邦最近「新增文章」API,不斷地出現未知的伺服器錯誤,而且從回應的時間來猜測的話,
感覺像是他們自己本身內部的錯誤導致 Time out 之類的。

礙於目前還無法在短時間內處理好一台伺服器建構多個 WordPress,
所以只好先將內容發佈到 Blogger 來解決當前的問題。

而我們只需要使用到發文的功能,所以就可以簡單利用電子郵件來發文;

  1. Blogger 設定的「以電子郵件傳送」
  2. 使用電子郵件張貼填入電子信箱,並開啟「立即發佈電子郵件」
  3. 儲存設定

這樣只要寄信給設定的地址的話,Blogger 便會自動發文。

SMTP

首先在 Python 當中,我們可以使用 smtplib 來發送信件,
下列使用 gmail 為例:

import smtplib
smtpserver = smtplib.SMTP_SSL("smtp.gmail.com",465)
smtpserver.ehlo()
smtpserver.login(username, password)

若你直接執行的話,可能會收到登入失敗的訊息;
由於 Google 在安全性設定上,會阻擋安全性較低的應用程式,
所以若要使用的話,則需要開啟相關設定:

登入與安全性
啟用

這樣的話,就可以順利地登入 gmail。

再來準備信件的內容並寄出:

from email.mime.text import MIMEText
from email.header import Header 

from_address = 'Archie.Chang.iOS@gmail.com'
to_address = ['Archie.Chang.iOS@gmail.com']

message = MIMEText(html_body, 'html', 'utf-8')
message['From'] = from_address
message['To'] = to_address[0]
message['Subject'] = subject

smtpserver.sendmail(from_address, to_address, message.as_string())
smtpserver.quit()

小雷

其中,sendmail 的 to_address 為 list 型態;
之前測試的過程中,我將 sendmail 改為

smtpserver.sendmail(from_address, to_address[0], message.asString())

結果導致它不斷地寄同一封信,Blogger 文章就大爆發了。

Selenium

Selenium

需要在 Python 上操作瀏覽器的話,我會選擇使用 Selenium;
以爬蟲來說,和 requests 不同的地方在於,
像是在讀取網頁時,使用 Selenium 開啟瀏覽器的話可以幫我們處理掉渲染的問題。

這篇主要會以 Selenium 的方式來取得痞客邦的 Access Token。

先搞懂痞客邦的流程

首先,我們先到痞客邦的開發者網頁

PIXNET

或是可以直接到 API Explorer 的畫面

API Explorer

接下來會需要進行登入的動作

Login

登入成功後,便是授權給 API Explorer 權限來取得 Access token

Granted

最後可以在 API Explorer 的畫面上看到 Access Token。

AccessToken

程式方面

我們會使用到 Selenium 的這些元件

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import Select
from selenium.common.exceptions import NoSuchElementException
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
def get_token():
    url = 'https://devtool.pixnet.pro/#/'
    # 使用 Firefox 來開啟網頁
    driver = webdriver.Firefox()
    # 打開 API Explorer 的畫面
    driver.get(url)
    # 進行登入
    driver.get('https://panel.pixnet.cc/login/openid?done=https%3A%2F%2Fdevtool.pixnet.pro&openid=https%3A%2F%2Fmember.pixnet.cc%2Flogin&easy_login=1')
    # 選擇臉書登入
    driver.get('https://panel.pixnet.cc/login/facebooklogin?done=https%3A%2F%2Fdevtool.pixnet.pro&easy_login=1&register_url=%2F%2Fmember.pixnet.cc%2Fregister')
    # 在 Email 和 Password 欄位填上值,並按下登入按鈕
    email = driver.find_element_by_id('email_container')
    password = driver.find_element_by_id('pass')
    login_button = driver.find_element_by_id('loginbutton')
    email.send_keys('Your Facebook email')
    password.send_keys('Your Facebook password')
    login_button.click()
    # 跳轉至授權畫面並按下同意
    driver.get('https://emma.pixnet.cc/oauth2/authorize?redirect_uri=https://devtool.pixnet.pro/index/cb&client_id='Your client id'&response_type=code')
    driver.find_element_by_id('send-Allow').click()
    time.sleep(5)
    # 使用 WebDriverWait 來等候標題出現 EMMA API Explorer(這邊我只寫 EMMA)
    try:
        WebDriverWait(driver, 10).until(EC.title_contains('EMMA'))
    except TimeoutException:
        print('time out')
    finally:
        # 接著 Parse 出 Access token 並且 return。
        soup = BeautifulSoup(driver.page_source, 'html.parser')
        ps = soup.find_all('p', 'form-control-static ng-binding')
        token = ps[0].string.replace(' ', '').replace('n', '')
        driver.close()
        return token

可能遇到的問題

geckodriver

由於我是使用 Ubuntu + Firefox 來執行,而在 Firefox 後續的版本中,
並沒有內建 geckodriver,需要手動安裝到電腦之中。

Crontab

若你是使用 Crontab 來跑的話,由於 Crontab 本身並不會有 output 的輸出,
意思是指跑 Crontab 時,也不會自己跳出 Firefox 的畫面;
會導致 Selenium 發生錯誤。

解決的方式是使用 pyvirtualdisplay 來弄一個虛擬的畫面,
給 Firefox 作為使用。
上面的程式碼則補上:

...
from pyvirtualdisplay import Display

def get_token():
    # 先準備好虛擬的畫面
    display = Display(visible=0, size=(800, 600))
    display.start()
    ...
    # 最後記得關掉畫面
    display.stop()
    return token

大致上是這樣,就可以利用 Selenium 的方式取得痞客邦的 Access Token!