Recent in Recipes

[奇怪的案例怪談2]使用C#抓取股市資料(內涵VB寫法)

趁現在過年時間比較沒事的時候整理一下筆記,因為最近開始在研究Python的關係,爬資料的東西又開始出現啦!記得之前有寫過Java使用Jsoup套件來抓取資料[文章在這],當時是因為寫java寫到一個愛不釋手境界,那這次是用C#的方式來爬資料,原因是因為...工作再碰C#跟VB...(哀...嘆氣,所以就廢話不多說我就來開始進入正題囉!
我們要使用HtmlAgilityPack這個程式庫來完成這次抓取資料的功能,其實各位可以上他的官網來瞭解: https://html-agility-pack.net/,其實網站有似乎更新所以提供許多範例可以參考,但載點好像沒有單獨dll檔提供下載有點可惜QQ,而要使用這套件有兩種方式來Import:
  1. 載入外部dll檔(提供檔案載點:
https://www.dllme.com/dll/files/htmlagilitypack_dll.html )
  1. 使用NuGet套件管理員Import(適用Visual Studio 2013之後版本)
這次的環境是2013版本,所以我用的是第1種方式(咦!?怎麼不是第二種XD),因為我有前人所留下的dll檔所以我就直接Import啦!這裡先解說如何加入dll檔,首先把注意力移到右邊專案中尋找參考後點擊右鍵有個加入參考如圖一所示,點擊後會顯示圖二的畫面,將dll檔案瀏覽選取後即可加入。

P3
※圖一 加入參考

P4
※圖二 加入本機端的dll檔

 而第二種方法是給2013版本後的做參考,這方法其實更方便,那就是直接去套件管理員尋找並安裝如圖三與圖四,這樣可以省去搜尋dll檔的時間!
P1
※圖三 開啟套件管理員

P2
※圖四 搜尋HtmlAgilityPack 套件並安裝

倘若把套件Import好後,就可以開始打程式囉!關於程式的部分也可以參考:
  1. https://dotblogs.com.tw/jackbgova/2014/06/10/145471
  2. https://dotblogs.com.tw/shadow/2011/12/12/61598
第一篇是我在整理文章內容找到的,老實說有點可惜,因為我的問題都解決才找到的,第二篇是我覺得可以先看的,我覺得觀念的東西可以先看這個,然後看第一篇,因為兩個寫法不一樣可以學習到很多。
首先先貼上畫面與C#的程式碼

 
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="CshapClimbDemo.WebForm1" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title></title>
    <style type="text/css">
        #stock {
            text-align: left;
        }
    </style>
</head>
<body style="text-align: center">
    <form id="form1" runat="server">
    <div id="stock">
        <span id="title-stock">&nbsp;&nbsp;
        台積電(
    <asp:Label ID="Label01" runat="server"></asp:Label>) &nbsp;&nbsp;&nbsp;
        時間 :
        <asp:Label ID="Label03" runat="server"></asp:Label>&nbsp; &nbsp;&nbsp; </span><br />
        <span style ="padding-left :20px;">成交價 :<asp:Label ID="Label07" runat="server" Font-Bold="True" Font-Size="15pt"></asp:Label></span> <br />
        <span style ="padding-left :20px;">成交量 :<asp:Label ID="Label08" runat="server"></asp:Label></span><br />
        <span style ="padding-left :20px;">最高價 :<asp:Label ID="Label05" runat="server"></asp:Label>&nbsp;&nbsp; &nbsp;
        最低價 :<asp:Label ID="Label06" runat="server"></asp:Label></span> <br />
        <span style ="padding-left :20px; text-align: left;">開盤價 :<asp:Label ID="Label04" runat="server"></asp:Label>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
        <asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl="WebForm1.aspx" Target="_self">Refresh</asp:HyperLink>
        <br />
        <br />
        <asp:Label ID="Label9" runat="server" Text="Label"></asp:Label>
        </span></div>
    </form>
</body>
</html>

 
using HtmlAgilityPack; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Text; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; namespace CshapClimbDemo { public partial class WebForm1 : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { Getstock(); } private void Getstock() { //int i = 0; try { //1.指定要開啟的網站,然後使用MemoryStream暫存 此範例為奇摩台積電股票 WebClient climb = new WebClient(); MemoryStream ms = new MemoryStream(climb.DownloadData("http://tw.stock.yahoo.com/q/q?s=2330")); //2.使用HtmlDocument載入第一層網頁資料 HtmlDocument doc = new HtmlDocument(); doc.Load(ms, Encoding.Default); HtmlDocument docStockContext = new HtmlDocument(); docStockContext.LoadHtml(doc.DocumentNode.SelectSingleNode("html[1]/body[1]/center[1]/table[2]/tr[1]/td[1]/table[1]").InnerHtml); //3.根據資料在哪個標籤就讀取標籤的內容 我這裡使用陣列儲存讀到的資料 HtmlNodeCollection nodeHeaders = docStockContext.DocumentNode.SelectNodes("./tr[1]/th"); String[] sValues = docStockContext.DocumentNode.SelectSingleNode("./tr[2]").InnerText.Trim().Split(' '); //※可以使用這個迴圈觀察資料的內容以及分割情況 /*foreach(String s in sValues){ this.Label9.Text += s +" "+i++ +" "; }*/ //4.最後顯示資料 可以使用我存取的陣列來呈現 或是把剛剛讀取標籤的內容轉成字串來呈現 this.Label01.Text = docStockContext.DocumentNode.SelectSingleNode("./tr[2]/td[1]").InnerText.Trim().ToString().Substring(0,4); this.Label03.Text = sValues[16].Trim(); this.Label04.Text = sValues[128].Trim(); this.Label05.Text = sValues[144].Trim(); this.Label06.Text = sValues[160].Trim(); this.Label07.Text = sValues[32].Trim() + " ( " + sValues[80].Trim() + " )"; this.Label08.Text = sValues[96].Trim(); //※顏色編輯如果漲停就紅色 跌就綠色 if (sValues[80].Trim().IndexOf('△') > -1) { Label07.ForeColor = System.Drawing.Color.Red; } else { Label07.ForeColor = System.Drawing.Color.Green; } } catch (Exception e) { this.Label9.Text = e.Message.ToString(); } } } }
最後呈現的結如下圖五,我選了台積電當這次的範例,哇!!!!是紅字喲!如果有人買他們的股票我會含淚幫你們鼓掌的!(因為我買不起阿~~)...... 我稍微解說一下我程式的寫法,我寫一個方法然後透過Page_Load來呼叫他,然後用例外框架來顯示如果發生錯誤是如何發生的,可能有些人不習慣用例外直接寫也是可以的,不過還是要養成良好的習慣比較好喲!再來是解析標籤,他的路徑表示是XPath,大家可以參考微軟官網的資料 https://msdn.microsoft.com/en-us/library/ms256086(v=vs.110).aspx ,然後解析出來的資料用Split('')來存到陣列裡,其實也可以直接解析轉字串,但陣列真的方便許多!

P5
※圖五 顯示結果

 那如果是VB怎麼打呢?我就不多說直接貼給大家程式碼

 
Imports System.IO Imports System.Net Imports HtmlAgilityPack Imports System.Text Partial Class Stock2330 Inherits System.Web.UI.Page Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load GetData2330() End Sub Private Sub GetData2330 () Try Dim client As WebClient = New WebClient() Dim ms As MemoryStream = New MemoryStream(client.DownloadData("http://tw.stock.yahoo.com/q/q?s=2330")) ' Dim doc As HtmlDocument = New HtmlDocument() doc.Load(ms, Encoding.Default) Dim docStockContext As HtmlDocument = New HtmlDocument() '因錯誤訊息顯示無法讀取網站資料 XPath路徑做更改 docStockContext.LoadHtml(doc.DocumentNode.SelectSingleNode("html[1]//body[1]//center[1]//table[2]/tr[1]/td[1]/table[1]").InnerHtml) ' Dim nodeHeaders As HtmlNodeCollection = docStockContext.DocumentNode.SelectNodes("./tr[1]/th") Dim sValues() As String = docStockContext.DocumentNode.SelectSingleNode("./tr[2]").InnerText.Trim().Split(" ") ''輸出資料() 'Dim i As Integer = 0 'For Each nodeHeader As HtmlNode In nodeHeaders ' txtMsg.Text = txtMsg.Text + nodeHeader.InnerText + ":" + nodeData(i).InnerText ' i += 1 'Next Me.Label01.Text = sValues(0).Trim.Substring(0, 4) Me.Label03.Text = sValues(16).Trim Me.Label04.Text = sValues(128).Trim Me.Label05.Text = sValues(144).Trim Me.Label06.Text = sValues(160).Trim Me.Label07.Text = sValues(32).Trim + " ( " + sValues(80).Trim + " ) " Me.Label08.Text = sValues(96).Trim If sValues(80).Trim.IndexOf("△") > -1 Then Me.Label07.ForeColor = Drawing.Color.Red ElseIf sValues(80).Trim.IndexOf("▽") > -1 Then Me.Label07.ForeColor = Drawing.Color.Green End If Catch ex As Exception Me.Label01.Text = " " Me.Label03.Text = " " Me.Label04.Text = " " Me.Label05.Text = " " Me.Label06.Text = " " Me.Label07.Text = " " Me.Label08.Text = " " End Try End Sub

如果各位有感覺的話可以發現,其實兩個程式碼是差不多的(邏輯上)所以如果是寫VB的朋友也不用擔心,前面的套件也是一樣的做法,請大家安心服用!

最後要跟各位分享這次的奇怪案例,因為我們公司爬資料這塊是用VB來爬的,其實這是前人寫好的功能,我也沒有理他,好死不死去年12月底的時候這個功能給我掛掉了,然後內網網站呈現紅通通的錯誤訊息讓我差點嚇到100天才能回去過年!!於是我看來看去發現他的程式首先沒有用例外框架來包覆,所以我補上例外框架讓可以用的功能能正常運作,在來就是看為什麼會突然失效,我查了一下判定是沒讀到股市資料,這就表示XPath的抓取位置是有問題的,於是我就看了半天再"html[1]/body[1]/center[1]/table[2]/tr[1]/td[1]/table[1]"這個路徑補了一些東西那就是
"html[1]//body[1]//center[1]//table[2]/tr[1]/td[1]/table[1]" 大家有看出來嗎?我在table之前都多加了一個斜線,兩條斜線表示抓取所有那個標籤的資料,我就想一條抓不到我就設兩條就跑不了了吧!結果一更新上去後...沒錯...,他好了...所以為什麼會突然失效呢?我也是不知道,不過還好解決了這個問題,另外我在例外處裡的地方如果這功能有錯誤就顯示無資料,其實眼尖的朋友可以看一下我VB的程式碼就知道標籤的例外處裡就是空白,其實美觀以外就是跟我說這功能有問題了,也不會馬上被其他單位的主管發現(噓!~~~~)
好的!以上是我這次分享的奇怪案例