既然知道了XML觀念,那是不是就可以透過程式擷取網頁內容並且將想要的資料擷取下來呢?,答案當然是可以,這就是所謂的爬蟲程式。既然如此.....那就來用司法判決書當作範例吧!
PS:本文章不會將程式做完整,因為真的有人在賣這個........
前言
雖然HTML與XML結構上很相似,但處理上會發現因為HTML的結構相對鬆散,導致實際處理時用微軟原生的XmlDocument處理會問題多多。為了解決這些問題,因此本文會採用一個叫做【html agility pack(HAP)】的套件來解讀HTML內容。(但因為實際上該套件底層還是微軟的XML,因此還是分類在XML中)
html agility pack (可由NuGet下載,或是到官網http://html-agility-pack.net/)
開始動手作
針對要抓取的資料,首先第一步就是要先了解"資料來源",到底網頁上顯示的資料是從哪個位置來的。這點可以靠著瀏覽器的開發者工具得知,以司法判決書例子,就是如下圖:
這樣我們就知道原來資料是從這幾張頁面依序來的,分別是
1. http://jirs.judicial.gov.tw/FJUD/FJUDQRY01M_1.aspx (查詢頁)
2. http://jirs.judicial.gov.tw/FJUD/FJUDQRY02_1.aspx (清單頁)
3. http://jirs.judicial.gov.tw/FJUD/FJUDQRY03_1.aspx (內容頁)
而要實踐自動抓內容的部分,主要在於清單頁與內容頁,查詢頁的部分可以簡化成為參數控制直接影響清單頁的條件,因此查詢頁基本上不用理他。那麼處理重點就是"如何傳給清單頁正確的參數,以便爬出結果的內容頁"。基本上就是檢查送出的Request內容,然後仿造送過去,同樣可以於瀏覽器檢查。
經過檢查後可以得知,該頁面的查詢條件基本上是放在QueryString中,也就是網址的後方,並不需要特別加工成為Content。
處理一下送出,就可以得到我們想要的HTML文字結果。
接著就是重頭戲了,將取得的文字結果放入HAP元件中,使其可以用後續的Xpath進行查詢節點,抓出我們實際上想從清單擷取的內容頁連結清單。概念很簡單,就是【我要抓到超連結標籤(<a>)中連結(@href)含有內容頁網址(FJUDQRY03_1.aspx)的元件】
var htmlListPage = new HtmlDocument();
htmlListPage.LoadHtml(strHtmData);
lisTarget = htmlListPage.DocumentNode.SelectNodes("//a[contains(@href,'FJUDQRY03_1.aspx')]");
這樣就可以抓出接著要去爬內容的清單了
接著針對內容連結清單,也採用同樣的方式處理
1.仿照網頁請求送出
2.內容放入HAP進行擷取
3.透過呈現的規則抓出想要爬出的資料
就可以將司法判決書給爬出了,太棒了呢。
範例程式碼
using HtmlAgilityPack;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Core
{
public partial class CrawlerDemo
{
public void Start()
{
var regex = new Regex(@"<table[\d\D]*<\/table>");
var url = $"http://jirs.judicial.gov.tw/FJUD/FJUDQRY02_1.aspx";
HtmlNodeCollection lisTarget;
{
var QueryStringDTO = new QueryStringDTO_FJUDQRY02_1()
{
courtFullName = "TPCM",
v_sys = "M",
sdate = "20180101",
edate = "20181231"
};
//QueryStringHelper.GetFromDTO,基本上就是把物件轉變為HtmlQueryString
url = $"{url}?{QueryStringHelper.GetFromDTO(QueryStringDTO)}";
var result = GetQueryResult02_1(url).Result;
var strHtmData = regex.Match(result).Value;
strHtmData = $"<html>{strHtmData}</html>";
var htmlListPage = new HtmlDocument();
htmlListPage.LoadHtml(strHtmData);
lisTarget = htmlListPage.DocumentNode.SelectNodes("//a[contains(@href,'FJUDQRY03_1.aspx')]");
}
var lisContent = new List<string>();
foreach (HtmlNode Node in lisTarget)
{
var strUrl = string.Concat("http://jirs.judicial.gov.tw/FJUD/", Node.Attributes["href"].Value);
var taskContent = GetQueryResult03_1(strUrl, url).Result;
var strHtmData = regex.Match(taskContent).Value;
strHtmData = $"<html>{strHtmData}</html>";
var html = new HtmlDocument();
html.LoadHtml(strHtmData);
var nodePoint = html.DocumentNode.SelectSingleNode("//span[contains(text(),'【裁判字號】')]");
var nodeTable = nodePoint.ParentNode.ParentNode.ParentNode;
var nodeTitle = nodeTable.SelectSingleNode("./tr[1]/td/span");
var nodeCreateDate = nodeTable.SelectSingleNode("./tr[2]/td/span");
var nodeReason = nodeTable.SelectSingleNode("./tr[3]/td/span");
var nodeContent = nodeTable.SelectSingleNode("./tr[5]/td/table/tr/td[1]/pre");
lisContent.Add(nodeTitle.InnerText +
nodeCreateDate.InnerText + Environment.NewLine +
nodeReason.InnerText + Environment.NewLine +
nodeContent.InnerText + Environment.NewLine);
}
foreach (var Content in lisContent)
{
Console.WriteLine("/******************************************/");
Console.WriteLine(Content);
Console.WriteLine("/******************************************/");
}
//-----非同步處理會因為一次要求太多被認定為機器人,而需要面對處理Captcher圖片(比較簡單的那個)
//var lisContent = new List<Task<string>>();
//foreach (HtmlNode Node in lisTarget)
//{
// var strUrl = string.Concat("http://jirs.judicial.gov.tw/FJUD/", Node.Attributes["href"].Value);
// var taskContent = GetQueryResult03_1(strUrl, url);
// lisContent.Add(taskContent);
//}
//var lisTask = new List<Task<string>>();
//foreach (var Content in lisContent)
//{
// var copyContent = Content;
// lisTask.Add(Task.Run(async () =>
// {
// await copyContent;
// var result = copyContent.Result;
// var strHtmData = regex.Match(result).Value;
// strHtmData = $"<html>{strHtmData}</html>";
// var html = new HtmlDocument();
// html.LoadHtml(strHtmData);
// var nodePoint = html.DocumentNode.SelectSingleNode("//span[contains(text(),'【裁判字號】')]");
// var nodeTable = nodePoint.ParentNode.ParentNode.ParentNode;
// var nodeTitle = nodeTable.SelectSingleNode("./tr[1]/td/span");
// var nodeCreateDate = nodeTable.SelectSingleNode("./tr[2]/td/span");
// var nodeReason = nodeTable.SelectSingleNode("./tr[3]/td/span");
// var nodeContent = nodeTable.SelectSingleNode("./tr[5]/td/span/td/table/tr/td[1]/pre");
// return nodeTitle.InnerText +
// nodeCreateDate.InnerText + Environment.NewLine +
// nodeReason.InnerText + Environment.NewLine +
// nodeContent.InnerText + Environment.NewLine;
// }));
//}
//foreach (var task in lisTask)
//{
// var strResult = task.Result;
// Console.WriteLine("/******************************************/");
// Console.WriteLine(strResult);
// Console.WriteLine("/******************************************/");
//}
//Task.WaitAll(lisTask.ToArray());
}
}
public partial class CrawlerDemo
{
private async Task<string> GetQueryResult02_1(string url)
{
var result = string.Empty;
using (var HttpClient = new HttpClient())
{
HttpClient.DefaultRequestHeaders.Clear();
HttpClient.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
HttpClient.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate");
HttpClient.DefaultRequestHeaders.Add("Accept-Language", "zh-TW,zh;q=0.9,en-US;q=0.8,en;q=0.7");
HttpClient.DefaultRequestHeaders.Add("Connection", "keep-alive");
//HttpClient.DefaultRequestHeaders.Add("Host", "jirs.judicial.gov.tw"); //似乎不影響
HttpClient.DefaultRequestHeaders.Add("Referer", "http://jirs.judicial.gov.tw/FJUD/indexContent_1.aspx"); //該頁面會判斷此參數,若參數不正確會被跳到其他頁面
HttpClient.DefaultRequestHeaders.Add("Upgrade-Insecure-Requests", "1");
HttpClient.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36");
result = await HttpClient.GetStringAsync(url);
}
return result;
}
private async Task<string> GetQueryResult03_1(string url, string url02_1)
{
var result = string.Empty;
using (var HttpClient = new HttpClient())
{
HttpClient.DefaultRequestHeaders.Clear();
HttpClient.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
HttpClient.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate");
HttpClient.DefaultRequestHeaders.Add("Accept-Language", "zh-TW,zh;q=0.9,en-US;q=0.8,en;q=0.7");
HttpClient.DefaultRequestHeaders.Add("Connection", "keep-alive");
HttpClient.DefaultRequestHeaders.Add("Host", "jirs.judicial.gov.tw");
HttpClient.DefaultRequestHeaders.Add("Referer", url02_1);
HttpClient.DefaultRequestHeaders.Add("Upgrade-Insecure-Requests", "1");
HttpClient.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36");
return HttpClient.GetStringAsync(url).Result;
}
}
}
}
namespace Core
{
class QueryStringDTO_FJUDQRY02_1
{
public string courtFullName { get; set; }
public string v_sys { get; set; }
public string jud_year { get; set; }
public string jud_case { get; set; }
public string jud_no { get; set; }
public string jud_no_end { get; set; }
public string jud_title { get; set; }
public string keyword { get; set; }
public string sdate { get; set; }
public string edate { get; set; }
public string page { get; set; }
public string id { get; set; }
public string searchkw { get; set; }
public string v_booktype { get; set; }
public string deepsearch { get; set; }
public string jmain { get; set; }
public string JSTOCK { get; set; }
public string JDG_COMMIS { get; set; }
public string JDG_PRESID { get; set; }
}
}
using System.Collections.Generic;
using System.Reflection;
using System.Web;
namespace Core
{
public static class QueryStringHelper
{
public static string GetFromDTO(object ObjectDTO)
{
var result = new List<string>();
var aryPropInfo = ObjectDTO.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var prop in aryPropInfo)
{
if (prop.CanRead)
{
result.Add($"{prop.Name}={HttpUtility.UrlEncode((prop.GetValue(ObjectDTO) ?? "") as string)}");
}
}
return string.Join("&", result);
}
}
}
後語
1.$""的字串處理法始於C#6,更多可以參考MSDN(https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/interpolated)
2.將內容用正規表示法處理是因為想濾掉無用資料
3.不處理機器人的圖片判斷是因為目前還不知道怎麼處理(炸),概念上大概知道產生邏輯是(文字轉圖片,扭轉圖片,雜訊化),但還不清楚如何反轉。
4.接著就可以去爬A漫了呢,可喜可賀