AMDlabの秋山です。
Electron講座2回目、本日は「ElectronとPythonを使ってウェブサイトからHtmlをスクレイピングをしていこう」です。
Electronに日々触れていると、せっかくならpythonを使ってなにか作れるのではと考えるようになりました。
python×Electronは日本語、英語でも情報がなかなか見つからず作成に苦労したので、皆さんに共有していきたいと思います。
前回の投稿でElectronのインストール方法から、helloworld表示、パッケージング化までひと通り説明しましたが
無事できましたでしょうか?不明な点、記載ミス等ございましたら、遠慮なくコメントください。
では早速、今回の成果物をはじめにお見せいたします。
海外のサイトで似たようなものがありますが、ウェブサイトのリンク切れやElectronのバージョンがV5以降になるとnode.jsの機能がうまく使えず
問題が出てきましたので改善を加え解説していきます。
■目次
- 1. Electronインストール
- 2. 必要なパッケージをインストール
- 3. Html・CSS作成
- 4. Javascript作成
- 5. Python作成
1.Electronインストール
前回の記事をご覧ください
コマンドラインでフォルダ名:WeatherForestを作成します。
以下が今回のフォルダ構造になります。
2.必要なパッケージをインストール
webスクレイピングを行うためコマンドライン上で必要なモジュールを含むパッケージを下記コマンドでインストールします。
ウェブスクレイピングwiki
とはウェブサイトから欲しい情報を取得することです。
requestsを使ってウェブサイトからhtmlを取得し、beautifulsoupを使いhtmlを解析し抽出するという流れになります。
- requests (ウェブサイトの情報取得や画像収集を行います)
- beautifulsoup4 (htmlやxmlからの希望のテキストデータを抽出します)
- python-shell (Node.jsからPythonのコードやファイルを実行します)
1 2 3 |
pip install requests pip install beautifulsoup4 pip install python-shell |
3.Html・CSS作成
レイアウトを作成します。html,cssを使って自由に作成していきましょう。
以下がレイアウトのサンプルです。
1.ホーム画面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>WeatherForcast</title> <link rel="stylesheet" href="stylesheet.css"> <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css"> </head> <body> <header> <div class="container"> <div class="header-left"> <img class="logo" src="../gui/images/photo-2.png"> </div> </div> </header> <div class="top-wrapper"> <div class="container"> <h1>Electron-Python</h1> <p>ElectronでPythonを動かす最初のプロジェクト</p> </div> </div> <div class="lesson-wrapper"> <div class="container"> <div class="heading"> <h2>WeatherForcast</h2> </div> <div class="lessons"> <div class="lesson"> <div class="lesson-icon"> <img src="../gui/images/photo-4.png"> <button class="btn2"><a style="color:black" href="weather.html">日本の天気</a></button> </div> <p class="txt-contents">お住いの地域を小文字ローマ字で入力してください。天気が表示されます。</p> </div> </div> </div> </div> <div class="message-wrapper"> <div class="container"> <div class="heading"> </div> <span class="btn message">Electronチュートリアルへ</span> </div> </div> </body> </html> |
2.WeatherForcast画面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<head> <html lang="ja"> <link rel="stylesheet" href="stylesheet.css"> <script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script> <script src="linkers/weather.js"></script> </head> <body> <br> <div class="container"> <button><a style="color:black" href="gui.html">< 戻る</a></button> <div class="jumbotron"> <h1>WeatherForcast</h1> <p>ウェブサイトからhtmlをスクレイピングし入力した都道府県の現在の天気を表示します。</p> </div> <br> <label>ローマ字でお住いの都道府県を入力してください</label> <input id="city" type="text" placeholder="City" /> <button onclick="get_weather()">検索</button> </div> <body> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
body { margin: 0; font-family: "Hiragino Kaku Gothic ProN"; } a { text-decoration: none; } .container { width: 700px; padding: 0 15px; margin: 0 auto; } .top-wrapper { padding: 300px 0 0 0; background-image: url("../gui/images/photo-1.png"); background-size: cover; color: white; text-align: center; } .top-wrapper h1 { opacity: 0.7; font-size: 30px; letter-spacing: 5px; color:black } .top-wrapper p { opacity: 0.8; color:black; } .btn-wrapper { margin: 20px 0; } .btn-wrapper p { margin: 10px 0; } .btn { padding: 8px 20px; color: black; display: inline-block; opacity: 0.8; border-radius: 4px; } .btn:hover { opacity: 1; } .fa { margin-right: 5px; } header { height: 40px; width: 100%; background-color: powderblue; /* positionプロパティをfixedに、topを0に指定してください */ position: fixed; top: 0; /* z-indexを10に指定してください */ z-index: 10; } .logo { width: 124px; margin-top: 5px; float : center; } .header-left { float: left; } .header-right { float: right; background-color: rgba(255, 255, 255, 0.3); transition: all 0.5s; } .header-right:hover { background-color: rgba(255, 255, 255, 0.5); } .header-right a { line-height: 65px; padding: 0 25px; color: white; display: block; } .lesson-wrapper { height: 500px; padding-bottom: 80px; background-color: #f7f7f7; text-align: center; } .heading { padding-top: 60px; padding-bottom: 30px; color: #5f5d60; } .heading h2 { font-weight: normal; } .lesson { float: left; width: 25%; } .lesson-icon { position: relative; left: 200px; } .lesson-icon p { position: absolute; top: 90px; width: 150%; color: darkslategray; } .btn2 { color : black; } .txt-contents { width: 400%; display: inline-block; margin-top: 20px; font-size: 12px; color: #b3aeb5; padding: 0 0 200px 0; } .heading h3 { font-weight: normal; } .message-wrapper { border-bottom: 1px solid #eee; padding-bottom: 80px; text-align: center; } .message { padding: 15px 40px; background-color: #5dca88; cursor: pointer; box-shadow: 0 7px #1a7940; } .message:active { position: relative; top: 7px; box-shadow: none; } footer img { width: 125px; } footer p { color: #b3aeb5; font-size: 12px; } footer { padding-top: 30px; } |
※テンプレートを使用しておりますので余分なクラスが入っております。
5.Javascript作成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
let {PythonShell} = require('python-shell') const path = require("path") function get_weather() { const city = document.getElementById("city").value const options = { scriptPath : path.join(__dirname, '/../engine/'), args : [city] } let pyshell = new PythonShell('weather_engine.py', options); pyshell.on('message', function(message) { swal(message); }) document.getElementById("city").value = ""; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
const electron = require('electron') // Module to control application life. const app = electron.app // Module to create native browser window. const BrowserWindow = electron.BrowserWindow const path = require('path') const url = require('url') // Keep a global reference of the window object, if you don't, the window will // be closed automatically when the JavaScript object is garbage collected. let mainWindow function createWindow () { // Create the browser window. const mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { preload: path.join(__dirname, 'preload.js'), nodeIntegration: true } }) // and load the index.html of the app. mainWindow.loadURL(url.format({ pathname: path.join(__dirname, '/gui/gui.html'), protocol: 'file:', slashes: true })) // Open the DevTools. // mainWindow.webContents.openDevTools() // Emitted when the window is closed. mainWindow.on('closed', function () { // Dereference the window object, usually you would store windows // in an array if your app supports multi windows, this is the time // when you should delete the corresponding element. mainWindow = null }) } // This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. app.on('ready', createWindow) // Quit when all windows are closed. app.on('window-all-closed', function () { // On OS X it is common for applications and their menu bar // to stay active until the user quits explicitly with Cmd + Q app.quit() }) app.on('activate', function () { // On OS X it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. if (mainWindow === null) { createWindow() } }) |
1 2 3 4 5 6 7 8 9 10 11 12 |
// All of the Node.js APIs are available in the preload process. // It has the same sandbox as a Chrome extension. window.addEventListener('DOMContentLoaded', () => { const replaceText = (selector, text) => { const element = document.getElementById(selector) if (element) element.innerText = text } for (const type of ['chrome', 'node', 'electron']) { replaceText(`${type}-version`, process.versions[type]) } }) |
1 2 3 4 5 6 7 8 9 |
function createWindow () { // Create the browser window. const mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { preload: path.join(__dirname, 'preload.js'), nodeIntegration: true } }) |
注1)Node.jsにおけるrequireは、npmで読み込んだモジュールに対してJavaScript側で利用できるようにするためのメソッドになりますが、electronのバージョンがv5以上の場合requireが使用できず、requie is not definedとエラーが出て先に進めません。
nodeIntegration: true を追記しましょう。
6.Python作成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import requests from bs4 import BeautifulSoup as bs import urllib import chardet import sys city = sys.argv[1] def get_weather(place): place = place.replace(" ", "-") url ="https://www.wunderground.com/weather/jp/" + place + "" r = requests.get(url) soup = bs(r.content, 'html.parser') weather = soup.findAll("div", {"class":"condition-icon small-6 medium-12 columns"})[0].text return weather print(get_weather(city)) sys.stdout.flush() |
- requestsをインポートします。
- bs4(beautifulsoup4の略)からbeautifulsoupをインポートします。(bsに省略します)
- sysをインポートします。(sysは標準ライブラリですのでpipインストールする必要はありません。)
- コマンド実行時に与えられた引数の 1 番目を city という変数に渡します。
(コマンド実行時に何も与えられていない場合sys.argv[0]となります) - def 関数名()でget_weather関数を定義します。
- urlにはスクレイピングしたいurlを持ってきます。
注1)placeにはユーザーが入力する地域が置換されるためurlの構造に地名が入ってる必要があります。
注2) ウェブサイトによっては、無許可のスクレイピングを禁止しているところがありますので、各サイトの注意事項をしっかり読んだうえで行ってください。
① シンプルに今日の天気だけを抽出します
② ウェブサイトのHTML (web上でctrl + u or F12で表示されます)
7.requestsで使われるメソッドには何種類かありますが、サーバーから情報を取得するget()メソッドを使用しresponseオブジェクト(r)を返します。
8. responseオブジェクトには様々な属性値がありますが、今回はレスポンスのテキストtxtではなくバイナリデータcontentを取得していきます。
注3)ウェブサイトの文字コードによっては取得時に文字化けをすることがあります。その対処法としてr.content, ‘html.parser’がよく使われております。
9. ①、②の情報から取得したいテキストを探し出し、findall()メソッドを使用しマッチするすべての部分文字列をリストにして返します。
以上がウェブサイトから取得したいhtmlデータをスクレイピングする方法になります。
いかがでしたでしょうか?
今回はpython×Electonがメインでしたので、アプリケーション画面のhtml・css・javascriptに関する詳しい説明を省きましたが、
どこかでフロントエンド側の記事も書いていきたいと思います。
COMMENTS