[C#] มาใช้ HtmlAgilityPack ดึงข้อมูลจากเว็บ

ตอนที่ผมเขียน Blog น่าจะเป็นช่วงปลายปี 2016 นะครับ พอดีวันนี้เหลือเวลาอีก 2-3 ชั่วโมงจะเลิกงาน ผมเลยลองทำ Project ง่ายๆ ดูครับ โดยดึงข้อมูลจากเว็บ เกี่ยวกับข้อมูลพื้นฐานของหุ้นนะครับ ก่อนที่จะทำ สิ่งที่ตต้องทำลอง Research Library ดูก่อนครับ ว่ามีอะไรช่วยได้ไหม เท่าที่ดูมี Library ที่เหมาะสม ดังนี้ครับ

  • HtmlAgilityPack สามารถ Download มาจาก Nuget ได้เลยครับ

ถึงเวลาเริ่มทำ

  • ต้องไปส่องเว็บก่อน ว่าเราดึงข้อมูลอะไร
  • ลองดู Code ของ HTML ที่ได้ ลอง View Source ครับ
  • สิ่งที่ผมสนใจ คือ ข้อมูลงบการเงิน/ผลประกอบการ ตามนี้ครับ
<table class="table table-hover table-info">
  <caption>
	 <span style="float: right;">(หน่วย: ล้านบาท)</span>
  </caption>
  <thead>
    <tr align="center" valign="middle"> 
        <th height="30"><strong>งวดงบการเงิน<br> ณ วันที่</strong></th>
        <th ><strong>ไตรมาส3/59<br>30/09/2559</strong></th>
    </tr>
  </thead>
  <tbody>
    <tr > 
        <td height="15" style="text-align:left;" colspan="1"><strong>บัญชีทางการเงินที่สำคัญ</strong></td>
        <td style="background-color: #EAF0FE;">&nbsp;</td>
    </tr>
    <tr > 
        <td height="15" style="text-align:left;" valign="middle">สินทรัพย์รวม</td>
        <td style="background-color: #EAF0FE;" >80,682.71&nbsp;&nbsp;</td>
    </tr>
        <tr > 
        <td height="15" style="text-align:left;">หนี้สินรวม</td>
        <td style="background-color: #EAF0FE;" >51,430.42&nbsp;&nbsp;</td>
    </tr>
    <tr > 
        <td height="15" style="text-align:left;">ส่วนของผู้ถือหุ้น</td>
        <td style="background-color: #EAF0FE;" >29,160.30&nbsp;&nbsp;</td>
    </tr>
    <tr > 
        <td height="15" style="text-align:left;">มูลค่าหุ้นที่เรียกชำระแล้ว</td>
        <td style="background-color: #EAF0FE;" >15,285.00&nbsp;&nbsp;</td>
    </tr>
    <tr > 
        <td style="text-align:left;" height="15">รายได้รวม</td>
        <td style="background-color: #EAF0FE;" >9,775.47&nbsp;&nbsp;</td>
    </tr>
    <tr > 
        <td style="text-align:left;" height="15">กำไรสุทธิ</td>
        <td style="background-color: #EAF0FE;" >2,014.22&nbsp;&nbsp;</td>
    </tr>
    <tr > 
    <td style="text-align:left;" height="15">กำไรต่อหุ้น (บาท)</td>
        <td style="background-color: #EAF0FE;" >0.13&nbsp;&nbsp;</td>
    </tr>
    <tr > 
        <td height="15" style="text-align:left;" colspan="1"><strong>อัตราส่วนทางการเงินที่สำคัญ</strong></td>
        <td style="background-color: #EAF0FE;">&nbsp;</td>
    </tr>
    <tr > 
        <td style="text-align:left;" height="15">ROA(%)</td>
        <td style="background-color: #EAF0FE;" >7.21&nbsp;&nbsp;</td>
    </tr>
    <tr > 
        <td style="text-align:left;" height="15">ROE(%)</td>
        <td style="background-color: #EAF0FE;" >10.32&nbsp;&nbsp;</td>
    </tr>
    <tr > 
        <td style="text-align:left;" height="15">อัตรากำไรสุทธิ(%)</td>
        <td style="background-color: #EAF0FE;" >20.60&nbsp;&nbsp;</td>
    </tr>
    <thead>
        <tr align="center" valign="middle"> 
            <th  height="30"><strong>ค่าสถิติสำคัญ<br> ณ วันที่</strong></th>
            <th ><strong>29/12/2559</strong></div></th>
        </tr>
    </thead>
    <tr > 
        <td style="text-align:left;" height="15">ราคาล่าสุด(บาท)</td>
        <td style="background-color: #EAF0FE;" >7.45&nbsp;&nbsp;</td>
    </tr>
    <tr > 
        <td style="text-align:left;" height="15">มูลค่าหลักทรัพย์ตามราคาตลาด</td>
        <td style="background-color: #EAF0FE;" >113,873.25&nbsp;&nbsp;</td>
    </tr>
    <tr align="center" valign="middle"> 
        <td class="topicbg" style="text-align:left;">วันที่ของงบการเงินที่ใช้คำนวณค่าสถิติ</td>
        <td style="background-color: #EAF0FE;" >30/09/2559&nbsp;&nbsp;</td>
    </tr>
    <tr > 
        <td style="text-align:left;" height="15">P/E (เท่า)</td>
        <td style="background-color: #EAF0FE;" >37.91&nbsp;&nbsp;</td>
    </tr>
    <tr > 
        <td style="text-align:left;" height="15">P/BV (เท่า)</td>
        <td style="background-color: #EAF0FE;" >3.91&nbsp;&nbsp;</td>
    </tr>
    <tr > 
        <td style="text-align:left;" height="15">มูลค่าหุ้นทางบัญชีต่อหุ้น (บาท)</td>
        <td style="background-color: #EAF0FE;" >1.91&nbsp;&nbsp;</td>
    </tr>
    <tr > 
        <td style="text-align:left;" height="15">อัตราส่วนเงินปันผลตอบแทน(%)</td>
        <td style="background-color: #EAF0FE;" >0.94&nbsp;&nbsp;</td>
    </tr>
  </tbody>
</table>
  • หลังจากเข้าใจ Pattern แล้ว ลองลุยเขียน Code เลย สำหรับผมที่โง่ฝั่งเว็บมากๆ ขอเป็น Winform ครับ ลากๆแปะๆเสร็จ

ลองมาดูวิธีใช้ HtmlAgilityPack ดึงกว่า

  • ก่อนใช้ HtmlAgilityPack เราต้องมีข้อมูลเว็บก่อนครับ สำหรับผมใช้ HttpWebRequest กับ HttpWebResponse เข้ามาช่วยครับ ลองดู Code ตัวอย่างได้เลย
private string GrabWebpageData(string url)
{
    string appURL = url;
    HttpWebRequest Request = WebRequest.Create(appURL) as HttpWebRequest;
    HttpWebResponse Response = (HttpWebResponse)Request.GetResponse();

    StreamReader srResponseReader = new StreamReader(Response.GetResponseStream(), Encoding.UTF8);
    string strResponseData = srResponseReader.ReadToEnd();
    srResponseReader.Close();
    return strResponseData;
}
  • เมื่อมีข้อมูลแล้ว ต่อมาเป็นหน้าที่ของ HtmlAgilityPack ผมต้องโยนข้อมูลจาก Setup แรกเข้าไป มันมี 2 แบบ
    • Load - ส่ง Stream หรือ ส่ง file path เข้าไปก็ได้ครับ
    • LoadHtml - โยน String เว็บเข้าไปเลยครับ (ผมใช้วิธีนี้แหละ ลองดูตัวอย่าง Code ด้านล่างเลย
HtmlAgilityPack.HtmlDocument doc = new HtmlAgilityPack.HtmlDocument();
doc.LoadHtml(webdata);
  • เนื่องจากเว็บอาจจะใหญ่เกินไป เรามาใช้ลองข้อมูลในการค้นหาลงครับ อย่างผมกำหนด Criteria เป็น
string match = "<table class=\"table table-hover table-info\">";
  • ลองสังเกตุ HTML ส่วนที่ผมสนใจในข้างต้นครับ จากนั้นก็ตัดมันออกมาครับ
  • คราวนี้มาลองใช้ HtmlAgilityPack โดยผมสนใจเฉพาะ Tag "Table" นะครับ ผมใช้ตัว doc.DocumentNode.SelectNodes("//table") เพื่อให้ได้ตัว HtmlNode ครับ โดยตัว HtmlNode มองว่าเป็น Graph ครับ มันเก็บข้อมูล และมีความสัมพันธ์ไปยัง Node อื่นๆครับ
foreach (HtmlNode table in doc.DocumentNode.SelectNodes("//table"))
{
   //Do some business here!!!
}
  • ก่อนหน้านี้ foreach คราวนี้ลองดึงมาทั้งยวงครับ ตาม Code เลย สังเกตุดีนะครับ ผมดึงมาทั้ง Tag และเลย

HtmlNode[] tdData = trNode.DocumentNode.SelectNodes("//td|//th").ToArray();
  • เนื่องจากมันเป็น C# มันใช้ Linq ได้ด้วยครับ อย่างของผมจะเอา Attribute ชื่อ height ที่มีขนาด 15 และ 30 ครับ โดย Method ที่ใช้ คือ GetAttributeValue เป็นของ HtmlNode ครับ
HtmlNode[] detail = content.DocumentNode.SelectNodes("//td").ToArray();
detail = detail.Where(x => x.GetAttributeValue("height", "0") == "15" 
                        || x.GetAttributeValue("height", "0") == "30").ToArray();
  • Property ที่น่าสนใจ สำหรับที่ผมลองใช้นะครับ
    • ChildNode - บอกว่ามี Node ลูกไหม เช่น <tr> มี <td> เป็น node ลูก
    • InnerHtml - ข้อมูล HTML ของ Node นั้น
    • InnerText - ข้อมูล Text ที่อยู่ภายใน Tag HTML

ลองใช้งานจริง

  • ใส่รายชื่อลงไป จากนั้นกด Grab ครับ

Source Code

  • อยู่ที่นี้ครับ (ไม่รับประกันว่าสามารถ Run ใช้งานได้นะครับ ลงดูเหตุผลได้จาก หัวข้อสรุป)

สรุปสิ่งที่ได้ทำ

  • เข้าใจการทำงานของ HtmlAgilityPack ครับ แต่มันมีข้อเสียอย่างนึง คือ ไม่ค่อยมี Document ครับ ต้องลองหาๆจาก Google => StackOverflow ครับ
  • การดึงข้อมูลจากเว็บยากนะครับ ถ้าดึงแล้ว ถ้าเว็บมีการใส่ Tag แปลกๆมา หรือปรับเปลี่ยนโครงสร้าง Code อาจจะพังได้เลย
  • ใช้ DataTable ของ C# นี้ก็มืนเหมือนกันนะ ปกติใช้ List<Object-DTO> เป็นหลัก
  • บทความนี้ทำ เพื่อการศึกษาครับ