メモ帳で職場のスクリーンセーバーを無効化する

職場のパソコンというものは、通常何らかのセキュリティが講じられています。例えば小職が前にいた会社では、5分おきに強制的にスクリーンセーバーがかかってしまい、ちょっと目を離した隙にPCがロックされる面倒が発生していました。理由があってのセキュリティではあるのは間違いないですが、それも度が過ぎると非効率の根源です。開発環境を自由に入れられない職場ですが、C#コンパイラWindows標準でついてくることが分かりましたので、暇つぶしにスクリーンセーバーを起動させないexeを調べながら作ってみました。

コンパイルのやりかた
C:\Windows\Microsoft.NET\Framework\v2.0.50727\csc.exe /target:winexe C:\temp\ScreenSaverKiller.cs


ScreenSaverKiller.cs

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Text;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace WindowsApplication1
{
    public class Form1 : System.Windows.Forms.Form
    {
        private System.Windows.Forms.Label label1;
        private System.Windows.Forms.Timer timer1;
        private System.Windows.Forms.Button button1;
        private System.ComponentModel.IContainer components;

        public Form1()
        {
            InitializeComponent();
        }
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (components != null)
                {
                    components.Dispose();
                }
            }
            base.Dispose(disposing);
        }


        #region Windows Form Designer
        private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();
            this.label1 = new System.Windows.Forms.Label();
            this.timer1 = new System.Windows.Forms.Timer(this.components);
            this.button1 = new System.Windows.Forms.Button();
            this.SuspendLayout();

            this.label1.Font = new System.Drawing.Font("MS UI Gothic", 36F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(128)));
            this.label1.Location = new System.Drawing.Point(16, 16);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(384, 64);
            this.label1.TabIndex = 0;
            this.label1.Text = "label1";
            this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;

            this.timer1.Enabled = true;
            this.timer1.Interval = 10;
            this.timer1.Tick += new System.EventHandler(this.timer1_Tick);

            this.button1.Location = new System.Drawing.Point(109, 96);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(192, 32);
            this.button1.TabIndex = 1;
            this.button1.Text = "Move cursor to the center";
            this.button1.Click += new System.EventHandler(this.button1_Click);

            this.AutoScaleBaseSize = new System.Drawing.Size(5, 12);
            this.ClientSize = new System.Drawing.Size(410, 144);
            this.Controls.Add(this.button1);
            this.Controls.Add(this.label1);
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
            this.Name = "Form1";
            this.Text = "ScreenSaverKiller";
            this.ResumeLayout(false);
        }
        #endregion

        [STAThread]
        static void Main()
        {
            Application.Run(new Form1());
        }

        private void timer1_Tick(object sender, System.EventArgs e)
        {
            Point pos = Cursor.Position;
            //Cursor.Position = new Point(pos.X + 1, pos.Y + 1);
            label1.Text = Control.MousePosition.ToString();
            PreventScreenSaverFromStarting();
        }

        private void button1_Click(object sender, System.EventArgs e)
        {
            int centerX = Screen.PrimaryScreen.Bounds.Right / 2;
            int centerY = Screen.PrimaryScreen.Bounds.Bottom / 2;
            Cursor.Position = new Point(centerX, centerY);
        }

        private void PreventScreenSaverFromStarting()
        {
            INPUT input = new INPUT();
            input.type = INPUT_MOUSE;
            input.mi = new MOUSEINPUT();
            input.mi.dwExtraInfo = IntPtr.Zero;
            input.mi.dx = 0;
            input.mi.dy = 0;
            input.mi.time = 0;
            input.mi.mouseData = 0;
            input.mi.dwFlags = 0x0001; //Move (Relative)
            int cbSize = Marshal.SizeOf(typeof(INPUT));
            uint r = SendInput(1, ref input, cbSize);
        }


        #region Win32 API
        [StructLayout(LayoutKind.Sequential)]
        struct MOUSEINPUT
        {
            public int dx;
            public int dy;
            public uint mouseData;
            public uint dwFlags;
            public uint time;
            public IntPtr dwExtraInfo;
        }

        const int INPUT_MOUSE = 0;

        [StructLayout(LayoutKind.Sequential)]
        struct KEYBDINPUT
        {
            ushort wVk;
            ushort wScan;
            uint dwFlags;
            uint time;
            IntPtr dwExtraInfo;
        }

        [StructLayout(LayoutKind.Sequential)]
        struct HARDWAREINPUT
        {
            uint uMsg;
            ushort wParamL;
            ushort wParamH;
        }

        [StructLayout(LayoutKind.Explicit)]
        struct INPUT
        {
            [FieldOffset(0)]
            public int type;
            [FieldOffset(4)]
            public MOUSEINPUT mi;
            [FieldOffset(4)]
            public KEYBDINPUT ki;
            [FieldOffset(4)]
            public HARDWAREINPUT hi;
        }

        [DllImport("user32.dll", SetLastError = true)]
        static extern uint SendInput(uint nInputs, ref INPUT pInputs, int cbSize);

        [FlagsAttribute]
        public enum EXECUTION_STATE : uint
        {
            ES_SYSTEM_REQUIRED = 0x00000001,
            ES_DISPLAY_REQUIRED = 0x00000002,
            ES_CONTINUOUS = 0x80000000,
        }

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags);
        #endregion
    }
}

メモ帳でソースコード(ScreenSaverKiller.cs)をコピペし、適当な場所に保存。そしてコマンドプロンプトcscを使ってコンパイルすれば、exeファイルが出来上がります。コンパイルが通らなかったらすみません。私の環境では一応動いてます。これで5分おきにマウスを動かさなくても良いですね!

f:id:tranche:20180702095622p:plain
スクリーンショット

node.js+Puppeteerによる動的ページのスクレイピング

最近のかっこいいWebとかだと、どういう仕組みかは分からないけど動的にデータが作られていて、htmlソースを見てもスクレイピングできないことがよくある。
西友のお墨付きブランドのページも、なかなか洗練されている(DeNAが作ったらしい?)。だけど商品情報をスクレイピングしようとしても、ソースコードには何も書いてないから、僕が得意な普通のVBAではうまくいかない。
西友 - プライベートブランド みなさまのお墨付き | SEIYU

あれこれ悩んで数か月、ようやく解決方法が見つかった。Puppeteerという、node.jsで動かせるChromeのヘッドレスブラウザを使うといい。これはすごくて、スクレイピング以外にもスクリーンショットなども取れる。
対象ページから抽出したいタグは、普通のChromeでInspectを選ぶことで見られると思う。node.jsはよく分からなかったけど、見よう見まねでDQNコードを作って一応動いている。非同期のところが癖があって難しかった。
はじめの一歩だから、多分すっごく汚いコードなんだろうけど、何個か作りながらnode.jsのこととかちゃんと理解できたらいいなと思う。

node.jsの最新バージョン(async対応)と、puppeteerが必要
node script.jsで実行


script.js

const fs = require('fs');
const assert = require('assert');
const puppeteer = require('puppeteer');

const len = 1500;
const sequential = new Array(len)
    .fill(1)
    .map((n, i) => n + i);
console.log(sequential.join(','));

loop(sequential);

async function loop(v) {
	for (let n of v) {
		await hoge(n);
	}
}

async function hoge(n){
	const browser = await puppeteer.launch();
	const page = await browser.newPage();
	await page.goto('http://●●.●●●●●.●●.jp/#item_' + n);
	console.log('http://●●.●●●●●.●●.jp/#item_' + n)
	//await page.screenshot({path: 'example.png'});

	const Names = await page.evaluate(() => {
		var array = [];
		var el = document.querySelector('div.bdr')
	        var node = el.querySelectorAll("p.name, p.price, p.area, div.dsc, div.rating");
		for(item of node){
	        	array.push(item.innerText);
	        }
	        return array;
	});
	browser.close();
	//assert(fs.existsSync('example.png'));
	fs.appendFile('writetest.txt', n + ',' + replaceElement(Names) + '\n', function (err) {
	    //console.log(err);
	});
}

function replaceElement(array) {
  for(var i=0; i<array.length; i++){
    array[i] = array[i].replace(/\r?\n/g,"");
  }
  return array;
}

俺の語彙数は推定8,000語だって

WeblioとTest your vocabの両方で試してみた。
Test you vocabだと7,810語。Weblioの方は何度やってもLevel 13~14(7001語~8000語 :特待生)で止まるから、結果は概ね正しいのだろう。Guessで回答しているのもあるし。これはネイティブの小学生低学年レベルといわれている。

ということは5年前に取った英検一級は殆どまぐれだったということになる(はっきりいって語彙パートは勘で5割くらい取れてしまう試験だった)。MBA留学していてこの語彙数はちょっと少ないよな。これまでずっと単語はないがしろにしてきたけど、本を読むのに時間がかかるとか知的な文章が書けなかったりして、最近すごい弊害を感じている。

年末までにボキャビルを少し頑張ってみようと思う。

testyourvocab.com
どのレベルの語彙まで覚えるべきか?

VBAで手っ取り早くWebスクレイピング

某企業のインターンシッププライベートブランドの研究をしたことがあり、前々からWebスクレイピングに興味があったので、例によってExcelVBAで簡単なコードを書いて、セブンプレミアムの一覧表を作りました。セブンのWebページはURLが定型化(0~9999までのID)されており、普通にFor文をぶん回すという荒業で対応できます。要するにセブンのページに1万回アクセスするのがマクロの趣旨です。IDが不規則な場合には、まず商品一覧からID表をスクレイピングして、それをもとにもう一回内容を取りに行くという二段階になります。

1万回といった大量のアクセスを迷惑行為ととらえるかどうか。例えば2010年の岡崎図書館事件では、サーバーダウンが発生し作成者が逮捕され物議をかもしました(そののち起訴猶予処分)。私の見解としては、1万回程度のアクセスの単発実行であれば、スクリプトの作りにもよるが今どきのサーバーが落ちる可能性は薄いと考えています。クローリングという行為自体はグーグルやアマゾンのような大企業から、有象無象の個々人まで好き勝手にやっている現状であり、インターネットの自由空間では問題が無いと思います。しかしもしサーバーをダウンさせてしまうことになれば、たとえ法的な処罰を受けなくとも、誰かに迷惑をかけることになってしまいます。従って、以下ソースの利用は自己判断・自己責任で行ってください。以下のソースは、セブンに迷惑がかからないように、URLの一部をマスキングしています。

Excel VBAはどの会社でも使えるから最強の実行環境だと思います。でもソースも汚いし、VBAを沢山書いたからといってプログラマとしての成長にはつながりません。私はこれまで、あくまで事務職の立場から、以下にコピペ等で速く情報を処理できるかを考えてVBAを書いてきました。会社ではそういう態度を続けていくとして、プライベートではもう少し良い言語を学びたいものです。

セブンHDツールプレミアム.xlsm

Sub testIE2()
Dim ws As Worksheet
Dim i, j As Long
Dim countURI, countNextClm As Integer
Dim objIE As InternetExplorer 'IEオブジェクトを準備
Set objIE = CreateObject("Internetexplorer.Application") '新しいIEオブジェクトを作成してセット
Dim el As IHTMLElement
Dim htmlDoc As HTMLDocument 'HTMLドキュメントオブジェクトを準備
Dim colTitle, colTh, colTd, colData, colImg As IHTMLElementCollection 'IHTMLエレメントコレクションを準備
Set ws = ThisWorkbook.Worksheets("Sheet1")
ws.Cells.Clear

objIE.Visible = False 'IEを表示
countURI = 1

i = 1
For countURI = 0 To 9999
    Debug.Print countURI
    objIE.navigate "https://********.jp/*******/search/detail?id=" & countURI 'IEでURLを開く
    
    Do While objIE.Busy = True Or objIE.readyState < READYSTATE_COMPLETE '読み込み待ち
        DoEvents
    Loop
    
    Set htmlDoc = objIE.document 'objIEで読み込まれているHTMLドキュメントをセット
    Set colTitle = htmlDoc.getElementsByClassName("headLine1 headLine1-line")
    Set colTh = htmlDoc.getElementsByClassName("itemDetail_td")
    Set colTd = htmlDoc.getElementsByClassName("tag color01")
    Set colData = htmlDoc.getElementsByClassName("itemDetail_leadsTxt")
    Set colImg = htmlDoc.getElementsByClassName("itemDetail_img")
    
    If colTh.Length = 0 Then
        GoTo Continue
    End If
    
    ws.Cells(i, 1).Value = countURI
    ws.Cells(i, 2).Value = colTd(0).innerText
    ws.Cells(i, 3).Value = colTitle(0).innerText
    ws.Cells(i, 4).Value = colImg(0).src
    ws.Cells(i, 5).Value = colData(0).innerText
    
    countNextClm = 6
    For Each el In colTh
        ws.Cells(i, countNextClm).Value = "'" & el.innerText
        countNextClm = countNextClm + 1
    Next el
    i = i + 1
Continue:
Next
End Sub

VBAでキー操作を送信する

※金融機関における重要書面確認は顧客の責任です。この記事に書いてあることは真似せず、必ず自分の目で確認してください!!

久々にSBI証券にログインしたら「重要なお知らせ」が溜まっていました。全部確認しないと取引できませんが、いかんせん200件近くあり、手で確認していたのでは日が暮れます。こんなものはどうせ読まないのでパソコンにやらせてしまうことにしました。

手段としてはいろいろ考えられます。比較的しっかりとしたものであればobjIEを使うのが良いのだと思うのですが、数分で作る使い捨てマクロには向きません。今回はSendKeysを使って実現することにしました。やや頼りない感じもしますが、IE以外の様々なアプリを操作することができるので、覚えておいて損はないのでしょう。

なおSendKeysの実行後、なぜかNumLockがオフになってしまいます。なんでなんでしょうね。

www.youtube.com
参考Office TANAKA - Excel VBAステートメント[SendKeysステートメント]

Sub Sample()
    Dim i As Integer
    Application.Wait [Now() + "0:00:05"]
    For i = 1 To 100
        SendKeys "+{Tab 2}"
        SendKeys "{Enter}"
        Application.Wait [Now() + "0:00:02"]
        SendKeys "{Tab 5}"
        SendKeys "{Enter}"
        Application.Wait [Now() + "0:00:02"]
    Next i
End Sub

VBSによるファイルダウンローダーの実現

企業内のPCでは往々にして自由なソフトウェアのインストールが禁じられています。その結果、例えばダウンローダーが入れられないため、URLは分かっているファイルを一括ダウンロードする術が無いということがあります(手作業で右クリック⇒保存をしていると日が暮れる数のファイル)。そこで当ブログでは、Windows機であれば環境問わず実行可能なスクリプト(VBS)で簡易的なダウンローダーの作成を行いました。ファイルの大量ダウンロードとかいう新入社員にありがちな糞な仕事を任せられたら、これでちゃっちゃと終わらせてしまいましょう。

使い方
(1)以下のソースファイル(myDownloader.vbs)と同じ階層に、boxという名前のフォルダとmyDownloadList.txtを作成する。
(2)myDownloadList.txt内に、ダウンロードしたいファイルのURLを1行ごとに列挙する。
(3)myDownloader.vbs を実行。するとbox内にファイルがダウンロードされて溜まっていく。


やっつけで調べながら作ったので、ソースが汚いのはご容赦ください。
暇があれば関数化して見やすく改良します。誰かしてください。


myDownloader.vbs

Dim objFSO
Dim objFile

Set objFSO = WScript.CreateObject("Scripting.FileSystemObject")
If Err.Number = 0 Then
    Set objFile = objFSO.OpenTextFile("myDownloadList.txt")
    If Err.Number = 0 Then
        Do While objFile.AtEndOfStream <> True

        cReqURL = objFile.ReadLine
        Set objFileSystem = WScript.CreateObject("Scripting.FileSystemObject")
        Set objShell = WScript.CreateObject("WScript.Shell")
        Set objXmlHttp = WScript.CreateObject("MSXML2.XmlHttp")

        cFileName = objFileSystem.getFileName(cReqURL)
        cReqPath = objFileSystem.getParentFolderName(cReqURL) & "/"


        objXmlHttp.Open "GET", cReqPath & cFileName, False
        objXmlHttp.SetRequestHeader "Pragma", "no-cache"
        objXmlHttp.SetRequestHeader "Cache-Control", "no-cache"
        objXmlHttp.SetRequestHeader "If-Modified-Since", "Thu, 01 Jan 1970 00:00:00 GMT"
        objXmlHttp.Send

        intStatus = objXmlHttp.status

        If (intStatus <> 200) Then
            WScript.Echo "FAILED" & vbCrLf & vbCrLf & "HTTP status code is" & intStatus & vbCrLf & cFileName
            WScript.Quit
        End If

        Set objStream = WScript.CreateObject("ADODB.Stream")
        objStream.Open
        objStream.Type = 1
        objStream.Write objXmlHttp.responseBody
        objStream.SaveToFile objShell.CurrentDirectory & "\box\" & cFileName, 2

        objStream.Close

        Set objFileSystem = Nothing
        Set objStream = Nothing
        Set objXmlHttp = Nothing

        Loop
        objFile.Close
    Else
        WScript.Echo "File couldn't be opened: " & Err.Description
    End If
Else
    WScript.Echo "Error: " & Err.Description
End If

Set objFile = Nothing
Set objFSO = Nothing

WScript.Echo "Successfully done."
WScript.Quit

普通自動二輪教習 2-5, 2-6

2016/5/3
【2-5】前半は実車で1・2コースをまわり、後半はシミュレーター室に移動してケース教習。シミュレーターに乗って、交差点で危険なシチュエーションを疑似体験する。例えばトラックの陰に乗用車が隠れていて、あやうく激突してしまうシーンなど、シミュレーターとはいえヒヤッとした。二輪教習は路上に出ないだけに貴重な体験になったと思う。

【2-6】第二段階は思いのほかやることが無く、今回もひたすら1・2コースの走り込み。苦手としていた平均台も最近は割と走りとおせるようになって、多少自信がついてきている。あとは右左折前のライン取りなどの細かい部分はまだまだ弱いと指摘。最後に波状路を体験するが、いきなり立ち乗りでコース一周を指示され、のろのろとそのまま波状路へ。体験しただけでハイおしまいだったので、正直何か身についた感じはしませんでした。

(ご利用条件)当ブログは筆者の個人的見解を述べたものであり、筆者の所属する団体またはその公式見解とは一切関係がありません。当ブログは特定の金融商品の売買を推奨または勧誘またはあっせんするものではありません。当ブログにおいて情報提供の対価として閲覧者から金銭を徴収することはありません。当ブログの内容の正確性に関しては万全を期していますが、筆者は何らその保証を行うものではありません。投資は自己責任です。当ブログの内容をもとにして生じた損害について、筆者は一切の責任を負いません。