玩轉C#之【爬蟲】

2022/10/10閱讀時間約 18 分鐘

介紹

基礎概念

爬蟲其實就是一個自動提取網頁的程式
程式基本運作:Url開始-->分析獲取數據&找到Url-->遞迴下去-->結束
分析獲取數據運作:下載html--解析獲取數據--數據保存

爬蟲可以做哪些事情?

數據為王:抓小說數據,做個內容站;
電影/動漫下載站
抓圖片
政府的公開招標數據,每天匯集這些數據

爬蟲的攻防

爬蟲的正義性問題:不違法、不問自取謂之偷
robots協議:君子的協定(360流氓),道德防線
每個網站會堤供robots.txt,來說明自己的哪些路徑允許爬資料
📷
防=>請求檢測header
攻=>爬蟲去都模擬一下
📷
防=>用戶登入
攻=>請求的時候帶上cookie
防:因為爬蟲的訪問頻率會很高,因此可以將訪問頻率高的IP加入黑名單,或者返回驗證碼
攻:可以透過以下方式產生多個ip(ADSL撥號/168偽裝IP/代理IP)
攻:破解驗證碼,有開源的圖片識別程式(OCR/打碼平台)
防:數據透過JS動態加載;將資料轉成圖片;透過JS收集用戶操作訊息,然後回傳伺服器;用戶控件(可以收集更多信息)
以上都是可以搞定的,道高一尺魔高一丈
手機板的請求抓取:裝模擬器=>電腦抓包Fiddler

下載html(HttpWebRequest)

string html = string.Empty;

HttpWebRequest request = HttpWebRequest.Create(url) as HttpWebRequest;//模擬請求
request.Timeout = 30 * 1000;//設置30s超時
request.UserAgent = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36";//pc瀏覽器
//request.UserAgent = "Ruanmou Crawler";
//request.UserAgent = "Mozilla / 5.0(iPhone; CPU iPhone OS 7_1_2 like Mac OS X) App leWebKit/ 537.51.2(KHTML, like Gecko) Version / 7.0 Mobile / 11D257 Safari / 9537.53";//手機板瀏覽器
request.ContentType = "text/html; charset=utf-8";// "text/html;charset=gbk";//
request.Host = "www.jd.com";

request.Headers.Add("Cookie", @"newUserFlag=1; guid=YFT7C9E6TMFU93FKFVEN7TEA5HTCF5DQ26HZ; gray=959782; cid=av9kKvNkAPJ10JGqM_rB_vDhKxKM62PfyjkB4kdFgFY5y5VO; abtest=31; _ga=GA1.2.334889819.1425524072; grouponAreaId=37; provinceId=20; search_showFreeShipping=1; rURL=http%3A%2F%2Fsearch.yhd.com%2Fc0-0%2Fkiphone%2F20%2F%3Ftp%3D1.1.12.0.73.Ko3mjRR-11-FH7eo; aut=5GTM45VFJZ3RCTU21MHT4YCG1QTYXERWBBUFS4; ac=57265177%40qq.com; msessionid=H5ACCUBNPHMJY3HCK4DRF5VD5VA9MYQW; gc=84358431%2C102362736%2C20001585%2C73387122; tma=40580330.95741028.1425524063040.1430288358914.1430790348439.9; tmd=23.40580330.95741028.1425524063040.; search_browse_history=998435%2C1092925%2C32116683%2C1013204%2C6486125%2C38022757%2C36224528%2C24281304%2C22691497%2C26029325; detail_yhdareas=""; cart_cookie_uuid=b64b04b6-fca7-423b-b2d1-ff091d17e5e5; gla=20.237_0_0; JSESSIONID=14F1F4D714C4EE1DD9E11D11DDCD8EBA; wide_screen=1; linkPosition=search");

//request.Headers.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
//request.Headers.Add("Accept-Encoding", "gzip, deflate, sdch");
//request.Headers.Add("Referer", "http://list.yhd.com/c0-0/b/a-s1-v0-p1-price-d0-f0-m1-rt0-pid-mid0-kiphone/");
request.Method = "GET";
//Encoding enc = Encoding.GetEncoding("GB2312"); // 如果是亂碼就改成 utf-8 / GB2312

#region Post
//int sort = 2;//人數
//string dataString = string.Format("k={0}&n=24&st={1}&iso=0&src=1&v=4093&p={2}&isRecommend=false&city_id=0&from=1&ldw=1361580739", keyword, sort, 1);
//Encoding encoding = Encoding.UTF8;//根據網站編碼自訂義
//byte[] postData = encoding.GetBytes(dataString);
//request.ContentLength = postData.Length;
//Stream requestStream = request.GetRequestStream();
//requestStream.Write(postData, 0, postData.Length);
#endregion

Encoding enc = Encoding.UTF8;//.GetEncoding("GB2312");
using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)//發起請求
{
if (response.StatusCode != HttpStatusCode.OK)
{
logger.Warn(string.Format("抓取{0}地址返回失敗,response.StatusCode為{1}", url, response.StatusCode));
}
else
{
try
{
StreamReader sr = new StreamReader(response.GetResponseStream(), enc);
html = sr.ReadToEnd();//讀取數據
sr.Close();
}
catch (Exception ex)
{
logger.Error(string.Format($"DownloadHtml抓取{url}失敗"), ex);
html = null;
}
}
}
return html

日誌紀錄(Log4Net)

log4net.cfg設定檔
<?xml version="1.0" encoding="utf-8"?>
<log4net>
<!-- Define some output appenders -->
<appender name="rollingAppender" type="log4net.Appender.RollingFileAppender">
<file value="log\log.txt" />

<!--追加日誌內容-->
<appendToFile value="true" />

<!--防止多線程時不能寫Log,官方說線程非安全-->
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />

<!--可以為:Once|Size|Date|Composite-->
<!--Composite為=為Size和Date的組合-->
<rollingStyle value="Composite" />

<!--當備份文件時,為文件名加的後綴-->
<datePattern value="yyyyMMdd.TXT" />

<!--日誌最大個數,都是最新的-->
<!--rollingStyle節點為Size時,只能有value個日誌-->
<!--rollingStyle節點為Composite時,每天有value個日誌-->
<maxSizeRollBackups value="20" />

<!--可用的單位:KB|MB|GB-->
<maximumFileSize value="3MB" />

<!--設置為true,當前最新日誌文件名永遠為file節中的名字-->
<staticLogFileName value="true" />

<!--輸出級別在INFO和ERROR之間的日誌-->
<filter type="log4net.Filter.LevelRangeFilter">
<param name="LevelMin" value="INFO" />
<param name="LevelMax" value="FATAL" />
</filter>

<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline"/>
</layout>
</appender>

<!-- levels: OFF > FATAL > ERROR > WARN > INFO > DEBUG > ALL -->
<root>
<priority value="ALL"/>
<level value="ALL"/>
<appender-ref ref="rollingAppender" />
</root>
</log4net>
Log Level的等級
OFF > FATAL > ERROR > WARN > INFO > DEBUG > ALL
初始化
static Logger()
{
XmlConfigurator.Configure(new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "CfgFiles\\log4net.cfg.xml")));
ILog Log = LogManager.GetLogger(typeof(Logger));
Log.Info("系統初始化Logger模塊");
}

private ILog loger = null;
public Logger(Type type)
{
loger = LogManager.GetLogger(type);
}
紀錄log的方式
使用log選擇要紀錄的等級
loger.Error(msg, ex);
loger.Warn(msg);
loger.Info(msg);
loger.Debug(msg);

解析HTML資料(HtmlAgiliytyPack)

通常在解析HttpWebRequest下載回來的HTML資料有兩種方式:
  1. 透過正則表達式搭配Substring/indexof/replace
  2. HtmlAgiliytyPack;基於Xpath解析

Xpath

什麼是XPath
XPath (XML Path Language) 是一種用來尋找XML文件中某個節點(node)位置的查詢語言。
XPath使用類似路徑的語法來尋找節點。
XPath一共有七種節點:element, attribute, text, namespace, processing-instruction, comment, document
XML文件是由許多節點組成的樹狀結構,最上層的結點稱作root element
  1. 假設要找這段文字的Xpath,先找到它的HTML位置
    📷
  2. 再對它的位置點右鍵=>Copy => CopyXPath
    📷
  3. 再透過XPath Helper 套件,可以驗證剛剛複製的XPath的路徑,是否正確
    📷

HtmlAgiliytyPack用法

  1. 新增HtmlDocument物件
  2. 使用LoadHtml存進HtmlDocument物件
  3. 使用SelectNodes找尋Xpath節點內容
public void Crawl()
{
//下載HTML
string html = HttpHelper.DownloadHtml("https://www.jd.com/allSort.aspx");
if (string.IsNullOrEmpty(html))
{
//需要重試。記錄下來
}
HtmlDocument document = new HtmlDocument();
document.LoadHtml(html);
{
//Xpath路徑
string secondPath = "//dl/dt/a";
HtmlNodeCollection nodeList = document.DocumentNode.SelectNodes(secondPath);//找多個節點
if (nodeList != null)
{
foreach (HtmlNode node in nodeList)
{
string url = node.Attributes["href"].Value;
string name = node.InnerText;
}
}
}
}

參考資料

本篇已同步發表至個人部落格
https://moushih.com/2022ithome26/
鐵人賽文章
為什麼會看到廣告
8會員
39內容數
我是這個部落格的作者,喜歡分享有關投資 💰、軟體開發 💻、占卜 🔮 和虛擬貨幣 🚀 的知識和經驗。
留言0
查看全部
發表第一個留言支持創作者!