fc2ブログ

Webページの自動保存・送信システムを作る(2)

 前回の続きです。今回はRaspberry Piの中で実施している処理(Pythonプログラム)の詳細をお話しします。

Pythonプログラムのキーとなる部分は以下の2点です。
 1. Webページをpdfファイルにする技術
 2. メールを送受信する技術

1.については、pdfkitライブラリを用いました。このライブラリを利用する上で、同時にwkhtmltopdfライブラリもRaspberry Piにインストールしました。
pip3 install pdfkit
sudo apt-get install wkhtmltopdf
2.については、当初Gmailを使う方法を検討していましたが、最終的にoutlook(hotmail)を使うことにしました。以前はGoogleアカウント設定で「安全性の低いアプリ」として設定することでGmailのメールサーバーを外部アプリから利用することができましたが、2022/5/30にこの方法が廃止されたためです。安全性の低いアプリ_231022最近、ネット情報でGmailでも2段階認証を有効にしたら、アプリパスワードを使って、外部アプリからも利用できることを知りました。

以下、作成したPythonプログラムです。まとまりがないプログラムですが、エラーなく動作しています。
# ssmail.py
# Mail sender of the pdf file converted from the web page
import os
import sys
import pdfkit
import email, imaplib
import base64
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
from pathlib import Path
import logging

# Mail registration information
username= os.environ['USERNAME'] # Outlook Mail
password= os.environ['PASSWORD'] # Outlook Mail
to_email= os.environ['TO_EMAIL']
cc_email= os.environ['CC_EMAIL']

# Log file setting
logging.basicConfig(
filename="MailSenderLog.txt",
level=logging.INFO,
format="%(asctime)s:%(levelname)s:%(message)s",
)

# Mail receive
# IMAP4 protocol setting and login
mail= imaplib.IMAP4_SSL("outlook.office365.com")
res = mail.login(username, password)
print(res)
logging.info(res)

# Target folder dxds set
resp_code, mail_count = mail.select(mailbox="dxds", readonly=False)
mail_count_int = int(mail_count[0])
print('Mail: '+ str(mail_count_int))
logging.info('Mail: '+ str(mail_count_int ))

if mail_count_int == 0: # No target mail
mail.logout()
sys.exit()

# Mail check
resp_code, mail_ids = mail.search(None, "ALL")
logging.info("Mail IDs : {}\n".format(mail_ids[0].decode().split()))

mail_id = '1'
logging.info("================== Start of Mail [{}] ====================".format(mail_id))
resp_code, mail_data = mail.fetch(mail_id, '(RFC822)') ## Fetch mail data.
message = email.message_from_bytes(mail_data[0][1]) ## Construct Message from mail data
logging.info("From : {}".format(message.get("From")))
logging.info("To : {}".format(message.get("To")))
logging.info("Bcc : {}".format(message.get("Bcc")))
logging.info("Date : {}".format(message.get("Date")))
logging.info("Subject : {}".format(message.get("Subject")))
logging.info("Body : ")
for part in message.walk():
if part.get_content_type() == "text/plain":
body_lines = part.as_string().split("\n")
logging.info("\n".join(body_lines[:12])) ### Print first 12 lines of message
logging.info("\n")
data_b64_dec = base64.b64decode(''.join(body_lines[3:]))
data_dec = data_b64_dec.decode('utf-8')
logging.info(data_dec.splitlines()[0]) # Target WebPage URL
logging.info("================== End of Mail [{}] ====================\n".format(mail_id))


# WebPage to PDF file
url = data_dec.splitlines()[0]
pdf_file = '/home/pi/prog/output.pdf'
try:
pdfkit.from_url(url, pdf_file)
logging.info(f'PDF file "{pdf_file}" is created.')
except Exception as e:
logging.info(f'Error occurred.: {str(e)}')

# Delete Message
mail.store(mail_id, '+FLAGS', r'\Deleted')
mail.expunge()
# IMAP4 close
mail.logout()

# mail sender
mail_title = 'DX paper'
message = '''
DX paper will be sent.
'''

# Create mail with MIME multipart
msg = MIMEMultipart()
msg['Subject'] = mail_title
msg['To'] = to_email
msg['From'] = username
msg['cc'] = cc_email
msg.attach(MIMEText(message))

# Load attachments
filepath =Path( '/home/pi/prog/output.pdf')
filename = filepath.name #filepath.stem
with open(filepath, 'rb') as f:
attach = MIMEApplication(f.read())
attach.add_header('Content-Disposition', 'attachment', filename=filename)
msg.attach(attach)

# Send a mail
smtp_host = 'smtp.office365.com'
smtp_port = 587
smtp_password = password

server = smtplib.SMTP(smtp_host, smtp_port)
server.starttls()
server.login(username, smtp_password)
server.send_message(msg)
server.quit()
まとまりのないプログラムの中身をChatGPTに解説してもらうと以下の通りです。

--- ChatGPT Comments From Here ---
このプログラムは、WebページからPDFファイルを生成し、生成されたPDFファイルをメールで送信するPythonスクリプトです。以下はプログラムの主要な部分の説明です。

1. モジュールのインポート:
- `os`, `sys`: システム操作と環境変数へのアクセスを提供するモジュール。
- `pdfkit`: WebページからPDFファイルを生成するためのライブラリ。
- `email`, `imaplib`: 電子メールの操作(受信)に関するライブラリ。
- `base64`: ベース64エンコードとデコードを行うライブラリ。
- `smtplib`: SMTPプロトコルを使用してメールを送信するためのライブラリ。
- `email.mime.text`, `email.mime.multipart`, `email.mime.application`: メールの構築に使用されるMIME(Multipurpose Internet Mail Extensions)関連のモジュール。
- `pathlib`: ファイルパス操作のためのユーティリティ。

2. メールの設定情報:
- `username`, `password`, `to_email`, `cc_email`: メールの認証情報と送信先、CC(カーボンコピー)のアドレスを環境変数から取得します。

3. ログファイル設定:
- `MailSenderLog.txt` ファイルにログ情報を書き込むための設定。

4. メール受信:
- IMAP4プロトコルを使用してOutlookメールに接続し、ログインします。
- 受信対象のメールを`dxds`フォルダから取得し、メールの数を取得します。

5. メールの取得:
- 受信対象のメールが存在する場合、最初のメール(メールID 1)を取得し、その内容を解析します。
- メールの送信元、送信先、日付、件名、本文などの情報をログに記録します。
- メールの本文からWebページのURLを抽出します。

6. WebページからPDFファイル生成:
- 抽出したWebページのURLを使用して、`pdfkit`を使ってPDFファイルを生成します。
- 生成されたPDFファイルは`/home/pi/prog/output.pdf`に保存され、ログに生成の成功またはエラーが記録されます。

7. メッセージの削除:
- メールの処理が完了したら、受信したメールを削除し、IMAP4接続を閉じます。

8. メール送信:
- PDFファイルを添付して、指定された送信先とCCアドレスにメールを送信します。
- SMTPプロトコルを使用してOutlookのSMTPサーバーに接続し、メールを送信します。

このプログラムは、Outlookメールアカウントを使用して特定のフォルダからメールを受信し、WebページをPDFファイルに変換して指定の受信者に送信するためのスクリプトです。プログラム内でログも記録され、エラーが発生した場合に問題のトラッキングが可能です。
--- ChatGPT Comments To Here ---

完璧な解説ですね! 以下、補足です。

2. メールの設定情報: プログラム16〜19行目
 前回お話しした通り、Raspberry Piでは、crontabで1日あたり、6:30、6:35、6:40の3回、このPythonプログラム(シェルプログラム)を実行しています。各種ファイルはRaspberry Piの「/home/pi/prog」フォルダに置きました。

DXpapersender.shの中でプログラム16〜19行目の変数を指定しています。中身は以下の通り。
#!bin/bash
cd /home/pi/prog/
export USERNAME="my_mail@hotmail.co.jp"
export PASSWORD="my_mail_password"
export TO_EMAIL="to_email@xxx.co.jp"
export CC_EMAIL="my_mail@hotmail.co.jp"
python3 ssmail.py
3. ログファイル設定: プログラム22〜26行目
メールサービスとPythonプログラムとのやりとりのログを記録するためのものです。以下、4. メール受信から6. WebページからPDFファイル生成までのログを取っています。
2023-10-12 06:30:02,671:INFO:('OK', [b'LOGIN completed.'])
2023-10-12 06:30:02,715:INFO:Mail: 4
2023-10-12 06:30:02,736:INFO:Mail IDs : ['1', '2', '3', '4']

2023-10-12 06:30:02,737:INFO:================== Start of Mail [1] ====================
2023-10-12 06:30:03,022:INFO:From : xxxx
2023-10-12 06:30:03,024:INFO:To : xxxx
2023-10-12 06:30:03,025:INFO:Bcc : None
2023-10-12 06:30:03,025:INFO:Date : Tue, 10 Oct 2023 20:14:38 +0000
2023-10-12 06:30:03,026:INFO:Subject : dxdx
2023-10-12 06:30:03,026:INFO:Body :
2023-10-12 06:30:03,030:INFO:Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: base64

aHR0cHM6Ly9ub3RlLmNvbS9rb3VzZWluZW5fbm90ZS9uL25kNGRjYWVhOTNjY2Q/bWFnYXppbmVf
(中略)
LmNvbS9hcHAvZ29vZ2xlL2lkMjg0ODE1OTQyPuOBp+WFseacieOBl+OBvuOBl+OBnw0KDQoNCuW4
uOWyoQ0K

2023-10-12 06:30:03,031:INFO:

2023-10-12 06:30:03,032:INFO:https://note.com/xxxxxx

2023-10-12 06:30:03,033:INFO:================== End of Mail [1] ====================

2023-10-12 06:30:20,240:INFO:PDF file "/home/pi/prog/output.pdf" is created.
以後の流れは、ChatGPTの解説通りです。

 今回はRaspberry Piの中で実施している処理(Pythonプログラム)の詳細をお話しました。ChatGPTにプログラムの中身を解説してもらいましたが、うまく説明できていて驚きでした。ChatGPTにプログラムを書いてもらうのも良いですが、こういう書かれたプログラムの解説のような使い方も良いですね。
スポンサーサイト



Webページの自動保存・送信システムを作る(1)

 どこの会社でもSNSや個人ブログ等のWebページ閲覧が、不適切な内容が含まれている等の理由で、規制されていることがあると思います。その中で、最近のAI技術(LLM関連)を会社で調べていて、その技術が記載されているWebページ(ブログ)を見つけ、閲覧したかったのですが、ブロックされて結局タイムリーに閲覧できずじまい・・。
 業務に必要な場合は会社の情シス部門に事情を話して、内容によっては開放してくれるのですが、ドメイン単位でしか開放できないとのこと・・。今回の私の求めている内容は適切な内容ですが、同一ドメインの中に私が全く興味のない不適切な内容も含まれており、切り分けができないのでNGとのこと。万事休す・・。

 ただ、内心馬鹿馬鹿しくて納得が行かず、会社の業務で調べているのに本末転倒だなと強く考える中で、結局、自宅でそのWebページをpdfファイルにして、自宅のPCから自分の会社メールアドレス宛に添付メール送信するしかないなと考えました。
 会社の対応は、不真面目な社員が業務中に不適切なWebサイトを閲覧するのを避けたいのが目的なのでしょうが、私自身、毎日分刻みで仕事をしているので、そんな不適切なWebサイトを見に行く(現実は見れないですが・・)余裕もありません。
 いつもの長〜い前置き(愚痴)はその程度にして、成り立つ方法を以下の通り考えました。

設計思想は以下の通りです。
 1. 自宅(会社でない環境)で「端末A」を用いて、気になるWebページを確認する。
    ↓
 2. そのWebページのURLを「端末A」から「システムB」に知らせる。
    ↓
 3. その「システムB」のプログラムで、目的のWebページをpdfファイルにして「端末C」に知らせる。

今回の場合、実際の使用形態を想定して
 端末A: iPad
 システムB: Raspberry Pi + Pythonプログラム
 端末C: 会社のPC
で、知らせる手段は「メール」です。端末AとシステムBが一つにできればシンプルになるのですが、自分の使用形態を優先します・・。

実施例はこんな感じです。
1. 気になるWebページをiPadの「Googleアプリ」で確認し、右上の共有ボタン(四角形に上矢印のボタン)からメールを選択し、WebPageCheck_231022.png2. 目的のURLを、自動pdfファイル化処理を行うシステムB(端末はRaspberry Pi)にメール送信しました。この際、メールサービス側でこの自動処理用メールを識別するために件名を「dxdx」(仮)と固定件名としました。sendMail_231022.png送信されたメールは、Outlook(hotmail)で自分のメールとしてRaspberry Piで受信し、フォルダ仕分け(今回は「dxds」フォルダ)された状態で確認できます。recievedMail_231022.png3. Raspberry Piでは、crontabで1日あたり、6:30、6:35、6:40の3回Pythonプログラム(シェルプログラム)を実行するように仕込みました。crontab_sh_run_231022.png Raspberry Pi内のPythonプログラムでpdfファイルを作成した後に、会社PCに送付したメールは以下の通りです。画面はCcで自宅の個人メールアドレス宛に同時送信されたものです。output.pdfという形でWebページの内容が添付されます。finalRecievedMail_231022.png
  一連の実施した内容は以上の通りです。2.のRaspberry Piの中で実施している処理の詳細は次回お話しします。

Raspberry Pi Picoで遊ぶ(2)

 前回の続きです。今回はRaspi Picoの温度センサのデータをSDカードに保存する機能を追加しましたので、その備忘録です。
 SDカードのモジュールは以前購入して使われずに転がっていたAE-MICRO-SD-DIPを利用しました。前回のLCD表示もそのまま残し、10秒ごとに温度センサデータを収集して、SDカードに書き込むようにしました。また、データ収集開始からSDカードへのデータ書き込み完了まで、基板上のLEDを点灯させるようにしました。
 回路図は以下の通りです。SDカードモジュールとはSPI通信をします。RaspiPico_LCD_SDC_curcuit_210505.pngSDカードとのSPI通信のプログラム(クラスライブラリ)もGitHub上にあったので、それを利用させてもらいました。ありがとうございます。

 最終的に動かしたコードは以下の通りです。先人の書かれたコードの「つぎはぎ」になっていますが・・。
# Temperature Sensor data logging for Raspberry Pi Pico
import utime

# LCD class library
class ST7032():

def __init__(self, i2c, addr=0x3e):
self.i2c = i2c
self.addr = addr
self.buf = bytearray(2)
self.initDisplay()

def writeCmd(self, cmd):
self.buf[0] = 0x00
self.buf[1] = cmd
self.i2c.writeto(self.addr, self.buf)

def writeData(self, char):
self.buf[0] = 0x40
self.buf[1] = char
self.i2c.writeto(self.addr, self.buf)

def initDisplay(self):
self.i2c.writeto(self.addr, b'\x00\x38')
self.i2c.writeto(self.addr, b'\x00\x39')
self.i2c.writeto(self.addr, b'\x00\x14')
self.i2c.writeto(self.addr, b'\x00\x73')
self.i2c.writeto(self.addr, b'\x00\x56')
self.i2c.writeto(self.addr, b'\x00\x6c')
self.i2c.writeto(self.addr, b'\x00\x38')
self.i2c.writeto(self.addr, b'\x00\x0C')
self.i2c.writeto(self.addr, b'\x00\x01')

def clear(self):
self.writeCmd(0x01)
utime.sleep(0.01)
self.writeCmd(0x02)
utime.sleep(0.01)

def home(self):
self.writeCmd(0x02)
utime.sleep(0.01)

def setContrast(self, contrast):
if contrast < 0:
contrast = 0
if contrast > 0x0f:
contrast = 0x0f
self.writeCmd(0x39)
self.writeCmd(0x70 + contrast)

def setCursor(self, x, y):
if x < 0: x = 0
if y < 0: y = 0
addr = y * 0x40 + x
self.writeCmd(0x80 + addr)

def print(self, str):
for c in str:
self.writeData(ord(c))

if __name__ == '__main__':

import utime
import machine
from machine import Pin, I2C, SPI
import os
import sdcard

# Runnung lamp
led = Pin(25, Pin.OUT)

# LCD (I2C setting)
sensor_temp = machine.ADC(4)
conversion_factor = 3.3 / (65535)

i2c=I2C(0, scl=Pin(1), sda=Pin(0), freq=100000)
print('i2c devices found at')
devices = i2c.scan()

if devices:
for i in devices:
print(hex(i))
print()

utime.sleep(1)
lcd = ST7032(i2c)
lcd.setContrast(1)
lcd.clear()

# SD card (SPI setting)
spi = SPI(0, sck=Pin(2), mosi=Pin(3), miso=Pin(4))
sd = sdcard.SDCard(spi, Pin(5))
os.mount(sd, '/sd')
os.chdir('sd')

while True:
led.value(1) # Loop Start
# Temperature Measurement
reading = sensor_temp.read_u16() * conversion_factor
utime.sleep(1)
temperature = 27 - (reading - 0.706)/0.001721
tempStr = " {:5.1f}C".format(temperature)

# LCD output
lcd.setCursor(0, 0)
lcd.print('Temp:')
lcd.setCursor(0, 1)
lcd.print(tempStr)
print(temperature)

# SD Card output
with open("/sd/temp_measure.txt", "a") as file:
file.write(str(temperature) + "\r\n")

led.value(0)
utime.sleep(10)
 実行結果は以下の通りです。LEDが点灯しているのでSDカードに収集したデータを書き込んでいることが分かります。RaspiPico_LCD_SDC_210505.pngSDカードにはtemp_measure.txtというファイルが作成され、データが保存されました。こんな感じです。Result_file_210505.png時刻情報がないのは寂しいので、今後、RTCモジュールを追加しようと思います。Raspi PicoはArduinoのように遊べそうですね。

Raspberry Pi Picoで遊ぶ(1)

 コロナ禍でStay Homeな暮らしを送っています。ニュースを見ると都心部から郊外の観光地に人が出かけて集中しているという間抜けな報道がなされていますね。自分さえ良ければ良いという短絡的な考えですね。報道も報道で、外出している人々のみをクローズアップして、在宅で不要不急の外出をせずに我慢している所はフォーカスされていない(ニュースとしては面白みがないですが・・)。一方通行的な報道でマ○ゴミと言われる所以だと思います。おっと、悪態はこれぐらいにして・・。あくまでも個人の意見です・・。我慢のストレスが・・。

 明日から会社ですが、どこにも行けないので前回に引き続き、RaspiPicoと今回も遊びました。

 Raspi Picoは無線でセンサデータを飛ばせないので、少なくともデータをLCDで表示させたり、SDカードにデータ保存ぐらいはした方が良いかなと素朴に思いました。そこで、今回はLCDでRaspiPicoに実装されている温度センサのデータを表示させました。手元にあったI2Cで通信するLCDモジュールがあったのでそれを利用しました。回路図は以下の通りです。RaspiPico_LCD_curcuit_210505.pngコードはネット情報から仕入れ、ピン番号と一部のコードを変更して利用しました。

実行結果は以下の通りです。配線は相変わらず雑ですが、問題なく動作しました。
RaspiPico_LCD_210505.png次回はSDカードにデータを保存する所まで進めます。

Raspberry Pi Picoを買う

 Raspberry Pi Picoスイッチサイエンスで衝動買いしました。とはいえ、1個550円で2個購入、送料200円の合計1,300円なので衝動ということもないですが・・・・。以前、スイッチサイエンスで販売された際、入荷案内のメールが来たのですが、購入しようとした時にはすでに在庫がゼロでした。そのまま存在を忘れていた今日この頃、再度入荷メールが入ったので、即発注。今回は購入できました。届いた物は写真の通りで、半導体のテープリールからはさみで切り出した感じでした。RaspiPico_210425.png
 早速使ってみようと、ネットをさまよっていると、たくさん情報が出てきましたので、その中のサイトを参考にさせていただきました。Raspberry Pi Picoはパソコンというよりはマイコンで、コードはMicro Pythonか、Cで書き込みます。エディタは使い慣れたVS codeが使えるようなので、ネット情報を参考に、Pico-Goをインストールしました。

 Raspi Picoの基板に実装されているLEDのON/OFFと、温度測定を手始めに、TempMeasure_result_210425.png何度か利用したことがある温湿度、気圧センサのBME280でのI2C通信によるデータ収集を行いました。パーツをかき集め、格好は悪いですが、以下の通りセットアップしました。BME280_setting_210425.png
プログラムはGitHubにあったコードを利用させてもらいました。ありがとうございました。結果は以下の通りで、うまく動作しました。BME280_result_210425.png
 今回は何も考えずに、ネット上の情報を参考にRaspi Picoを味見しました。550円という安価なデバイスですが、無線機能がないので、IoT用途で直接利用するには無線モジュールが別途必要ですね。初めからWi-Fiを使う場合は、価格はUpしますが、ESP32やRaspi Zeroを1台の方がシンプルで良いと思いました。

 Raspi PicoはRaspbianの入っていないマイコンとして利用する形態なので、用途としては棲み分けていくのでしょうね。ArduinoやPICの親戚のような感じがします。ただ、Micro PythonでコーディングできるのはCの苦手なユーザにはうれしいかもしれません。私はどちらでも良いですが・・。まだ、可能性を十分引き出せていないので、何か面白いことができないか、GWにでも思案してみようと思います。

ご訪問者数

(Since 24 July, 2016)

タグクラウド


プロフィール

Dr.BobT

Author: Dr.BobT
興味のおもむくままに生涯考え続けるエンジニアでありたい。

月別アーカイブ

メールフォーム

名前:
メール:
件名:
本文: