2020-07-12 09:39 | カテゴリ:勉強や投資情報
塩漬けマンが自作したWEBスクレイピングツールをくださいってDMが時々来るんですが、WEBスクレイピングって、サイトによって禁止されてたり、法律的にもグレー(著作権的に)なので、販売するのは論外で、配布もやらない方がいいと思うんですよ。

なので、作り方を教えます(え?!)
個人で勝手に作って勝手に使う分には自己責任ですので。

因みに、塩漬けマンは今4つの機能を持つツールを作っています。
①適時開示リアルタイムチェック
②企業HP更新リアルタイムチェック
③投資情報自動取得(CSV)・・・PERとかの指標を自動で取得してくれる便利ツールの事
④メディア情報リアルタイムチェック・・・PRTIMESとか日経新聞のニュースサイトのチェックの事


自作する場合、一番難しいのは②です。
差分チェックのアルゴリズムが、プロでも作るのが難しいです。
塩漬けマンは、ボランティア精神溢れる人がC#のライブラリという形で差分チェッククラスを無償配布してくれてたのを使っています。
①・③・④は無茶苦茶簡単に作れます。
プログラムやったことがある人なら初級レベルです。

んで、プログラム言語ですけど、C#です。
理由は、現役プログラマ時代に最後に触ってた言語で覚えてたというのと、.NETフレームワークが充実しているので、誰でも簡単に高機能なWindowsアプリケーションが作れるからです。
ただし、初心者向けとはいえ、プロ仕様の言語ですので、ちょっと敷居が高いです。
多分一番素人でも簡単に始めれて、とっつきやすいのはエクセルVBAだと思います。
次にPowerShellかな・・・ただ、PowerShell、塩漬けマン使った事ないので、なんとも言えないです。
そして、やる気がある人はPythonをお勧めします。
本屋に行けば、どの言語が一番流行ってるか分かるのですが、塩漬けマンの現役時代ってJava一色でしたが、今はPython一色になっています。
しかも機械学習とかのAIソフトも簡単に作れる(フレームワークが充実してて何でもあるらしいです)らしく、多分今一番旬で、今後一番有望な言語です。

C#の欠点は、それでWinodwsフォームアプリケーションを作ると、ちゃんとマルチスレッド処理してても、処理(更新チェックとかの)が動いている時は、ツールが固まります。
誰でも簡単に高性能に作れる代わりに、処理が遅くて固まるというのはしょうがないって感じの言語です。

んで、詳細には説明しないです。
やる気がある人や経験者が出来るレベルでやり方を教えます。
経験者は「④HTML解析について」だけ読めば十分だと思います。

※例で使うのは、以下のようなユーザインタフェースの「④メディア情報リアルタイムチェック」です。
20200710メディアチェック画像

①開発環境の準備
C#で開発するにはVisualStudioが必須です。昔は数万円とかしましたが、今は以下から無料でダウンロードできます。
Visual Studio 2019ダウンロード

②一定時間間隔での処理
リアルタイム監視の場合は必須ですが、そうでなくても、タイマー機能というのを利用して作るべきです。
.NETフレームワークではタイマー機能を以下の三つ用意してくれています。
1.Windowsタイマ:System.Windows.Forms.Timerクラス
2.スレッドタイマ:System.Threading.Timerクラス
3.サーバベースタイマ:System.Timers.Timerクラス
三つの違いはここを参照
C# の Timer 種類別 特徴 と 使い方
結論から言うと、3を使うべきで、使い方はここを参照
タイマにより一定時間間隔で処理を行うには?(サーバベースタイマ編)

■System.Timers.Timerクラスの実装例■
※ボタンを押したら処理が実行される仕様
※大枠だけ残して、その他の詳細な処理は全部削除しています(実際は1000行を超えるソースになっています)
20200706タイマー大枠コード例

★ポイント★
10行目:これでSystem.Timers.Timerが使えるようになります
20行目:デザイナーにある「Timer」は「1.Windowsタイマ:System.Windows.Forms.Timerクラス」のです。
↓この一番下にあるTimerは「1.Windowsタイマ:System.Windows.Forms.Timerクラス」の事で違う
20200706ツールボックスのタイマー
「3.サーバベースタイマ:System.Timers.Timerクラス」を使用するためには、手動でツールボックスにSystem.Timers.TimerクラスのTimerを追加しないといけないのですが、そんな事をしなくても、20行目のように「System.Timers.Timer timer1 = new System.Timers.Timer();」ってコードを書けばいいだけです。
29行目:SynchronizingObjectプロパティに当フォーム(this)を設定しないと、フォームの更新処理が出来ません。
66行目、73行目:アクセス修飾子は「private」にして下さい。デフォルトでは違うのになっていました。上記の例のように、得られたメディアの情報を一覧(DataGridViewを使用)にどんどん追加していく仕様なのですが、デフォルトのアクセス修飾子のままだと、フォーム上のオブジェクト(DataGridView等全て)の内容を変更出来ませんでした。知ってる人にはどうでもいい事ですが、塩漬けマンは知らなかったので、実は一番迷った箇所です。

③WEB情報の取得(ログイン認証についても)
.NETフレームワークではWEBアクセスするのに以下の二つが用意されています。
1.HttpWebRequestクラス
2.WebClient クラス・・・使いやすい
どっちでも出来ますが、ログイン認証(クッキー認証)のあるメディア(日経新聞とか日刊工業新聞とか)にプログラムからログインする場合は、WEB上の説明は「1.HttpWebRequestクラス」しかないです。「2.WebClient クラス」でも出来るみたいですが、専門的な知識(クッキーの仕様理解)が必要となります。
クッキーを使ってWebページを取得するには?
因みに、塩漬けマン、それ知らなくて、「2.WebClient クラス」で作っちゃっています。
↓こんな感じで
 Uri webUri = new Uri(@"※アクセスしたいHPアドレス");
 WebClient client = new WebClient();
 client.Encoding = Encoding.UTF8;
 string result = client.DownloadString(webUri);

どうしてもログインしたくなったら、ちょっと変更するかもです。

④HTML解析について
C#では凄く優秀なHTMLパーサーである「HtmlAgilityPack」が使えます。
最初から使える訳ではなくて、「参照」に追加したりと準備が必要ですが、以下を見て設定しました。
Html Agility Packを使ってWebページをスクレイピングするには?[C#、VB]
※昔の画像で、今と少しやり方が違ったと思いますが、上記を見ながらやってたら出来ました。
「HtmlAgilityPack」の使い方ですが、難し過ぎて説明は出来ないし、しようと思ったら、膨大になるので、例を見せた方が早いです。
上記③で取得したHTMLを以下のようにHtmlAgilityPackパーサーにセットします。
 HtmlAgilityPack.HtmlDocument doc = new HtmlAgilityPack.HtmlDocument();
 doc.LoadHtml(result);

後はHTMLを解析して、情報を取得出来るかはパズルを解くような感じで、頭の良さが試されます。
1.単一ノードを取得する場合
<HTML例>
例えば、日経新聞の記事から更新日時を取得しようとした場合、HTMLを見ると以下のようになっています。
20200706単一ノード例
↓上記のHTMLから日付を取得するコード
 string datetime = "";
 HtmlAgilityPack.HtmlNode objNode = doc.DocumentNode.SelectSingleNode("//dd[contains(@class, 'cmnc-publish')]");
 if (objNode != null)
 {
  datetime = objNode.InnerText;
 }

※nullチェックとかしないで一気に変数に入れちゃいたい人はこうも書けますが、日経新聞はclass="cmnc-publish"を持ったddタグを持っていない記事もあるらしく、その場合nullになって、例外エラーが発生します。
 string datetime = doc.DocumentNode.SelectSingleNode("//dd[contains(@class, 'cmnc-publish')]").InnerText;

つまり、SelectSingleNodeメソッドに取得したいタグを以下の引数の書式にして渡せばいいのです。
//タグ名[contains(@属性名, '属性値')]
なので、例えばタグがdivで、属性がidの場合は以下になります。
//div[contains(@id, 'cmnc-publish')]
さらに、タグの中のタグとどんどん潜っていく場合は、以下のように、//で繋いでいきます。
//タグ名[contains(@属性名, '属性値')]//タグ名[contains(@属性名, '属性値')]
後は、属性の条件を増やしたい場合は、[]で繋いでいきます。
//タグ名[contains(@属性名, '属性値')][contains(@属性名, '属性値')]
(例)タグを辿りつつ、属性の条件が二つある場合
 doc.DocumentNode.SelectSingleNode("//div[contains(@class, 'cmn-article')]//a[contains(@class, 'm-inline_text')][contains(@href, 'javascript:void(0)')]");
※cmn-articleというclass属性を持つdivタグの中のm-inline_textというclass属性とjavascript:void(0)というhref属性を持つaタグの情報を取得している。

タグの中身はInnerTextに入っています。タグ付きで取得したい場合はInnerHtmlプロパティです。
その他、プロパティは多数ありますので、何か使えそうなのがあれば適時使う感じですが、とりあえず欲しい情報はInnerTextで取得するって覚えておけばいいです。

ここで、class="cmnc-publish"を持ったddタグが複数あったらどうなるの?っていう疑問が湧くと思います。
試してみた所、どうやら一番最初に出て来るddタグが取得されていました。
※これは欲しい情報が入ったタグが複数ある場合に、欲しい情報が一番最初に出て来るタグにあった場合は、上記が使えるという事ですので、覚えておいてください。

2.複数ノードを取得する場合
 var titleList = new List();
 var urlList = new List();
 foreach (HtmlAgilityPack.HtmlNode objNewsDataNode in docSokuho.DocumentNode.SelectNodes("//h3[contains(@class, 'm-miM09_title')]//a"))
 {
  if (objNewsDataNode.InnerText != "")
  {
   titleList.Add(objNewsDataNode.InnerText.Trim());
   urlList.Add("https://www.nikkei.com/" + objNewsDataNode.Attributes["href"].Value);
  }
 }


このように、取得したいデータが複数ある場合、はSelectNodesメソッドを使えば、複数ノードが配列で取得されます。
※上記の例は日経新聞のHPから記事のタイトルとリンク先のアドレスを取ってくる例です。
上記は全データを使うのでループさせていますが、取得したデータの3番目だけが欲しいから、ループしなくていいんだけどって場合は以下のように、配列の番号をダイレクトに指定してもいいです。
 objNewsDataNode[2].InnerText

※取得したデータの前後にいらない空白があったらよくない場合が多いので、自動で削除してくれるTrimをInnerTextとセットで使った方がいいです。
(例)string datetime = doc.DocumentNode.SelectSingleNode("//dd[contains(@class, 'cmnc-publish')]").InnerText.Trim();

⑤DataGridView等に取得したデータを新しい順に表示するアルゴリズム
皆がDataGridViewを使うとは限らないし、ここまでサービスしなくていいかなって思ったんですけど、出血大サービスでコード公開します。
フローチャートを作って提示して終わりにしようとしたのですが、フローチャートを作るのが面倒くさいので、もういっそコードで示すはって感じです。
※大枠が分かる所だけ残して、その他の処理は全部削除しています。実際はすごくガチャガチャ色々やってます。
20200710メディア監視ソース

ポイントは以下の通りです。
253行目:タイトルをキーにしてて、ニュースで取得したタイトルと同じのがDataGridViewにあったら追加しないってだけ
257行目:一回目(DataGridViewにデータがない)は、取得したデータを上から順番にAddで追加して、二回目以降(DataGridViewにデータがある)はAddだと一番下に追加されちゃうので、一番上から順に追加するためにInsertにしています。
※Insertする行番号(rowindex)は0でもいいけど、より精密に時系列でニュースを並べたいなら、追加した行数をカウントする変数(246・272行参照)を使ってください。
280行目:キーが重複したら処理を終わるかどうかで、コメントで詳しく書いている通りで、何気に重要です。
273行目:DataGridViewに行を追加する処理が終わったら、1行毎に
 mediaDgv.Refresh();
で、画面表示を一度更新しておいた方がいいです。これがないと全部の処理が終わってから表示が更新されるので、ちゃんと動いているか、今どこを処理しているのかが分からないです。

⑥パラレス(同時)処理について
処理を早くしようと思えば、パラレス処理が考えられます。
例えばチェックしているHPの数が100ある場合、上から順番に処理してたら、100番目のHPをチェックするのは凄く後になってしまいます。
そういう場合は処理するHPを配列系の変数で保有して、以下のようにパラレスのループで回せば、100のHPに一斉に処理を行ってくれるので凄く早くなります。
ループをParallelクラスで並列処理にするには?[C#/VB]
ループを使わない場合は以下のHPが参考になります。
C# 並行・並列プログラミング パターン集

※「④メディア情報リアルタイムチェック」には実装してなくて、多分実装したらDataGridViewの表示とか見づらくなる上に、同じDataGridViewに対して、同時に更新処理が走るので、バグが起きそうなので実装しないです。ただし、「②企業HP更新リアルタイムチェック」はチェックするHPの数が膨大になるので、パラレス処理は必須となります。

※WEBスクレイピング対策等で、多重アクセスを禁止しているWEBサーバの設定にしているサイトもあるので、そういうサイトでパラレルで同時にアクセスするとエラーとなるので注意して下さいね。株探とかそういう設定だったはずです。

⑦アプリの設定値の保有方法

設定値(今回の例で言えば、「更新チェックを行う間隔」「通知音」「監視ワード」)をどのように保有するかは常に開発者の悩みの種です。
だって、アプリを起動する度に、前の設定が消えてたり、デフォルト値に戻ってたら嫌でしょ。
そういう時に使う、昔からWindowsに用意されている仕組みがレジストリなのですが、PCに詳しい人は、これを聞いただけで嫌になりますよね。
レジストリにはWindowsOSの超重要な設定もあるため、詳しい人程、気軽にいじりたくないのです。
このような、適当に作る便利アプリでレジストリを使うべきではありません。
そこで、昔のプログラマはアプリの起動exeのあるディレクトリにiniファイルというテキストファイルを作って、そこに設定値を保存しておいて、アプリ起動時に読み込む処理をしていました。
それと全く同じ機能を提供してくれているのが、Properties.Settingsクラスとなります。
↓使い方は色んなHPで紹介されてますが、どうも、内容が古かったり、微妙に塩漬けマンが使ってるのと違うような・・・
「アプリケーション構成ファイル」を使用して設定を読み込む
「アプリケーション構成ファイル」を編集する
Visual Studioでアプリケーションの設定を保存する
App.configによる初期データの読み込み

んで、これって上記のリンクのやり方でApp.configを作ったら、ソリューションエクスプローラーに「App.config」が増えています。
中身はこんな感じですが、キーは勿論自分で好きなのを作ります。
↓以下の画像のコメントに書いてますが、アプリを再起動しないと設定値が反映されない定数っぽいのはappSettingsを使って、アプリを再起動しなくても設定が反映される変数っぽいのはuserSettingsを使います。この二種類は用途が違うので注意して下さい。
20200706AppConfig.jpg

んで、さらに言うと、これってソリューションエクスプローラーの「Properties」→「Settings.settings」と連動しています。
20200706Settings.jpg

んで、さらにさらに言うと、これって実は、エクスプローラで実行ファイルがあるディレクトリを覗いたら、「~~~.exe.config」という物理ファイルで存在してて、中身を見たら、「App.config」と全く同じ形式で、設定値が保存されている事が分かります。
20200706Config.jpg

そして、Properties.Settingsクラスの実際の使い方です。
1.設定を変更して保存する場合
 Properties.Settings.Default.■■■(自分で作ったキー) = ●●●(設定値);
 Properties.Settings.Default.Save();
 ←これを忘れがち。忘れちゃだめよ
2.設定を参照する場合
 string settei = Properties.Settings.Default.■■■(自分で作ったキー);

簡単でしょ?なんかリンク先の説明だと、こんな簡単な風に書いてないので、一応使用例を書いておきました。

尚、「監視ワード」は、単一の値ではなく、複数の値を、しかもそれぞれに「監視ワード」「詳細」「不使用」の三つのデータを持たないといけないので、CSVファイルを作って、そこにデータを保存するようにしています。
C#でのCSVファイルの使い方までは説明しません。
ググればいくらでも出て来るので。

====

これってNoteとかで10000円とかでも売れる内容だと思いません?
思った人は分かってるだろうなっ!(`・ω・´)シャキーン

↓コメントはツイッターからどうぞ ※忙しいと申し訳ありませんが、コメント返信出来ない事があります。
ツイッターアイコン1

↓応援クリックをして頂けたら感謝です。


↓拍手には特に意味はないのですが、ブログ内容の良し悪しパロメータとして使っています(´・ω・`)
トラックバックURL
→https://shiodukeman.jp/tb.php/2769-bc4b300a